diff --git a/.eslintignore b/.eslintignore
index e1b0ceb50cac38015961c557cb266b77ce3c27dd..08ec761fb381cc8ed4b1fe91cf117c6694966939 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -7,3 +7,7 @@ test/end-to-end-tests/lib/
 src/component-index.js
 # Auto-generated file
 src/modules.ts
+src/modules.js
+# Test result files
+/playwright/test-results/
+/playwright/html-report/
diff --git a/.eslintrc.js b/.eslintrc.js
index f310384972be838a5aa9e3bfe7321cc7f5930cac..26865d55ec22c653304bcab7dddc6b5732741750 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -30,6 +30,10 @@ module.exports = {
                 ["window.innerHeight", "window.innerWidth", "window.visualViewport"],
                 "Use UIStore to access window dimensions instead.",
             ),
+            ...buildRestrictedPropertiesOptions(
+                ["React.forwardRef", "*.forwardRef", "forwardRef"],
+                "Use ref props instead.",
+            ),
             ...buildRestrictedPropertiesOptions(
                 ["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
                 "Use Media helper instead to centralise access for customisation.",
@@ -55,6 +59,11 @@ module.exports = {
             "error",
             {
                 paths: [
+                    {
+                        name: "react",
+                        importNames: ["forwardRef"],
+                        message: "Use ref props instead.",
+                    },
                     {
                         name: "@testing-library/react",
                         message: "Please use jest-matrix-react instead",
@@ -200,8 +209,13 @@ module.exports = {
                 "@typescript-eslint/ban-ts-comment": "off",
                 // We're okay with assertion errors when we ask for them
                 "@typescript-eslint/no-non-null-assertion": "off",
-                // We do this sometimes to brand interfaces
-                "@typescript-eslint/no-empty-object-type": "off",
+                "@typescript-eslint/no-empty-object-type": [
+                    "error",
+                    {
+                        // We do this sometimes to brand interfaces
+                        allowInterfaces: "with-single-extends",
+                    },
+                ],
             },
         },
         // temporary override for offending icon require files
@@ -247,6 +261,7 @@ module.exports = {
                 // We don't need super strict typing in test utilities
                 "@typescript-eslint/explicit-function-return-type": "off",
                 "@typescript-eslint/explicit-member-accessibility": "off",
+                "@typescript-eslint/no-empty-object-type": "off",
 
                 // Jest/Playwright specific
 
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b31ec5e3bf9d42c7d91535da07386c4e085c218c..34431799f4f762402663cfff626c67be3d8dbc77 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -10,10 +10,12 @@
 /test/components/views/dialogs/security/                                @element-hq/element-crypto-web-reviewers
 /src/stores/SetupEncryptionStore.ts                                     @element-hq/element-crypto-web-reviewers
 /test/stores/SetupEncryptionStore-test.ts                               @element-hq/element-crypto-web-reviewers
-/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx   @element-hq/element-crypto-web-reviewers
-/src/src/components/views/settings/encryption/                          @element-hq/element-crypto-web-reviewers
+/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx  @element-hq/element-crypto-web-reviewers
+/src/components/views/settings/encryption/                              @element-hq/element-crypto-web-reviewers
 /test/unit-tests/components/views/settings/encryption/                  @element-hq/element-crypto-web-reviewers
-/playwright/e2e/settings/encryption-user-tab/                            @element-hq/element-crypto-web-reviewers
+/src/components/views/dialogs/devtools/Crypto.tsx                       @element-hq/element-crypto-web-reviewers
+/playwright/e2e/crypto/                                                 @element-hq/element-crypto-web-reviewers
+/playwright/e2e/settings/encryption-user-tab/                           @element-hq/element-crypto-web-reviewers
 
 # Ignore translations as those will be updated by GHA for Localazy download
 /src/i18n/strings
diff --git a/.github/actions/download-verify-element-tarball/action.yml b/.github/actions/download-verify-element-tarball/action.yml
index 75d325c5438503b1b441bd7f770fd38d45123d91..a64bc3241b0faf11e33ab2885ed2c0e138e3d8de 100644
--- a/.github/actions/download-verify-element-tarball/action.yml
+++ b/.github/actions/download-verify-element-tarball/action.yml
@@ -11,7 +11,7 @@ runs:
     using: composite
     steps:
         - name: Download release tarball
-          uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
+          uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1
           with:
               tag: ${{ inputs.tag }}
               fileName: element-*.tar.gz*
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 21a6b0e7ab85d7617930e4963c916cc0b416baad..68b9f4e703ad2a044775423aa079179a48a6f5b3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -43,9 +43,9 @@ jobs:
             run:
                 shell: bash
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   # Disable cache on Windows as it is slower than not caching
                   # https://github.com/actions/setup-node/issues/975
@@ -77,7 +77,7 @@ jobs:
                   yarn build
 
             - name: Upload Artifact
-              uses: actions/upload-artifact@v4
+              uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: webapp-${{ matrix.image }}
                   path: webapp
diff --git a/.github/workflows/build_debian.yaml b/.github/workflows/build_debian.yaml
index f46678512a7a9fb3d410306246274ee4a2372bac..247e5604eeeee22e0b0b27a2771225ba850d9ae9 100644
--- a/.github/workflows/build_debian.yaml
+++ b/.github/workflows/build_debian.yaml
@@ -14,7 +14,7 @@ jobs:
             R2_URL: ${{ vars.CF_R2_S3_API }}
             VERSION: ${{ github.ref_name }}
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
             - name: Download package
               run: |
@@ -62,7 +62,7 @@ jobs:
                   dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
                   dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
 
-            - uses: actions/upload-artifact@v4
+            - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: element-web.deb
                   path: element-web.deb
diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml
index 8bbcfe726f5f0104d4c4823ba2382d42a8db63a1..9550bf91392a1a21139124c9404b204af9c73b47 100644
--- a/.github/workflows/build_develop.yml
+++ b/.github/workflows/build_develop.yml
@@ -26,9 +26,9 @@ jobs:
             R2_URL: ${{ vars.CF_R2_S3_API }}
             R2_PUBLIC_URL: "https://element-web-develop.element.io"
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -53,7 +53,7 @@ jobs:
 
             - run: mv dist/element-*.tar.gz dist/develop.tar.gz
 
-            - uses: actions/upload-artifact@v4
+            - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: webapp
                   path: dist/develop.tar.gz
@@ -109,10 +109,11 @@ jobs:
             # We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
             # as the expires after 24h and requires auth to download.
             # Element Desktop's fetch script uses this tarball to fetch latest develop to build Nightlies.
+            # Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
             - name: Deploy to R2
               run: |
-                  aws s3 cp dist/develop.tar.gz s3://$R2_BUCKET/develop.tar.gz --endpoint-url $R2_URL --region=auto
-                  aws s3 cp _deploy/ s3://$R2_BUCKET/ --recursive --endpoint-url $R2_URL --region=auto
+                  aws s3 cp dist/develop.tar.gz s3://$R2_BUCKET/develop.tar.gz --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
+                  aws s3 cp _deploy/ s3://$R2_BUCKET/ --recursive --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
               env:
                   AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
                   AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 14fbd22086524243e77c6d7dcae7d21aa64ce669..8b52e6764c6e0d0e23752335407af02ea5153a07 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -34,7 +34,7 @@ jobs:
         env:
             SITE: ${{ inputs.site || 'staging.element.io' }}
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
             - name: Load GPG key
               run: |
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..75a00af7aa25cf4a31b704b2895e72b9524735dd
--- /dev/null
+++ b/.github/workflows/docker.yaml
@@ -0,0 +1,141 @@
+name: Docker
+on:
+    workflow_dispatch: {}
+    push:
+        tags: [v*]
+    pull_request: {}
+    schedule:
+        # This job can take a while, and we have usage limits, so just publish develop only twice a day
+        - cron: "0 7/12 * * *"
+concurrency: ${{ github.workflow }}-${{ github.ref_name }}
+permissions: {}
+jobs:
+    buildx:
+        name: Docker Buildx
+        runs-on: ubuntu-24.04
+        environment: ${{ github.event_name != 'pull_request' && 'dockerhub' || '' }}
+        permissions:
+            id-token: write # needed for signing the images with GitHub OIDC Token
+            packages: write # needed for publishing packages to GHCR
+        env:
+            TEST_TAG: vectorim/element-web:test
+        steps:
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+              with:
+                  fetch-depth: 0 # needed for docker-package to be able to calculate the version
+
+            - name: Install Cosign
+              uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3
+              if: github.event_name != 'pull_request'
+
+            - name: Set up QEMU
+              uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
+
+            - name: Set up Docker Buildx
+              uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
+              with:
+                  install: true
+
+            - name: Login to Docker Hub
+              uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
+              if: github.event_name != 'pull_request'
+              with:
+                  username: ${{ secrets.DOCKERHUB_USERNAME }}
+                  password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+            - name: Login to GitHub Container Registry
+              uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
+              if: github.event_name != 'pull_request'
+              with:
+                  registry: ghcr.io
+                  username: ${{ github.repository_owner }}
+                  password: ${{ secrets.GITHUB_TOKEN }}
+
+            - name: Build and load
+              id: test-build
+              uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
+              with:
+                  context: .
+                  load: true
+
+            - name: Test the image
+              env:
+                  IMAGEID: ${{ steps.test-build.outputs.imageid }}
+              timeout-minutes: 2
+              run: |
+                  set -x
+
+                  # Make a fake module to test the image
+                  MODULE_PATH="modules/module_name/index.js"
+                  mkdir -p $(dirname $MODULE_PATH)
+                  echo 'alert("Testing");' > $MODULE_PATH
+
+                  # Spin up a container of the image
+                  ELEMENT_WEB_PORT=8181
+                  CONTAINER_ID=$(
+                      docker run \
+                          --rm \
+                          -e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
+                          -dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
+                          -v $(pwd)/modules:/modules \
+                          "$IMAGEID" \
+                  )
+
+                  # Run some smoke tests
+                  wget --retry-connrefused --tries=5 -q --wait=3 --spider "http://localhost:$ELEMENT_WEB_PORT/modules/module_name/index.js"
+                  MODULE_0=$(curl "http://localhost:$ELEMENT_WEB_PORT/config.json" | jq -r .modules[0])
+                  test "$MODULE_0" = "/${MODULE_PATH}"
+
+                  # Check healthcheck
+                  until test "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_ID)" == "healthy"; do
+                      sleep 1
+                  done
+
+                  # Clean up
+                  docker stop "$CONTAINER_ID"
+
+            - name: Docker meta
+              id: meta
+              uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
+              if: github.event_name != 'pull_request'
+              with:
+                  images: |
+                      vectorim/element-web
+                      ghcr.io/element-hq/element-web
+                  tags: |
+                      type=ref,event=branch
+                      type=ref,event=tag
+                  flavor: |
+                      latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
+
+            - name: Build and push
+              id: build-and-push
+              uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
+              if: github.event_name != 'pull_request'
+              with:
+                  context: .
+                  push: true
+                  platforms: linux/amd64,linux/arm64
+                  tags: ${{ steps.meta.outputs.tags }}
+                  labels: ${{ steps.meta.outputs.labels }}
+
+            - name: Sign the images with GitHub OIDC Token
+              env:
+                  DIGEST: ${{ steps.build-and-push.outputs.digest }}
+                  TAGS: ${{ steps.meta.outputs.tags }}
+              if: github.event_name != 'pull_request'
+              run: |
+                  images=""
+                  for tag in ${TAGS}; do
+                      images+="${tag}@${DIGEST} "
+                  done
+                  cosign sign --yes ${images}
+
+            - name: Update repo description
+              uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
+              if: github.event_name != 'pull_request'
+              continue-on-error: true
+              with:
+                  username: ${{ secrets.DOCKERHUB_USERNAME }}
+                  password: ${{ secrets.DOCKERHUB_TOKEN }}
+                  repository: vectorim/element-web
diff --git a/.github/workflows/dockerhub.yaml b/.github/workflows/dockerhub.yaml
deleted file mode 100644
index 6cf8b44876eab04e3402321d46e8c9e643e99b04..0000000000000000000000000000000000000000
--- a/.github/workflows/dockerhub.yaml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: Dockerhub
-on:
-    workflow_dispatch: {}
-    push:
-        tags: [v*]
-    schedule:
-        # This job can take a while, and we have usage limits, so just publish develop only twice a day
-        - cron: "0 7/12 * * *"
-concurrency: ${{ github.workflow }}-${{ github.ref_name }}
-permissions: {}
-jobs:
-    buildx:
-        name: Docker Buildx
-        runs-on: ubuntu-24.04
-        environment: dockerhub
-        permissions:
-            id-token: write # needed for signing the images with GitHub OIDC Token
-        steps:
-            - uses: actions/checkout@v4
-              with:
-                  fetch-depth: 0 # needed for docker-package to be able to calculate the version
-
-            - name: Install Cosign
-              uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3
-
-            - name: Set up QEMU
-              uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3
-
-            - name: Set up Docker Buildx
-              uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
-              with:
-                  install: true
-
-            - name: Login to Docker Hub
-              uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
-              with:
-                  username: ${{ secrets.DOCKERHUB_USERNAME }}
-                  password: ${{ secrets.DOCKERHUB_TOKEN }}
-
-            - name: Docker meta
-              id: meta
-              uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
-              with:
-                  images: |
-                      vectorim/element-web
-                  tags: |
-                      type=ref,event=branch
-                      type=ref,event=tag
-                  flavor: |
-                      latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
-
-            - name: Build and push
-              id: build-and-push
-              uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6
-              with:
-                  context: .
-                  push: true
-                  platforms: linux/amd64,linux/arm64
-                  tags: ${{ steps.meta.outputs.tags }}
-                  labels: ${{ steps.meta.outputs.labels }}
-
-            - name: Sign the images with GitHub OIDC Token
-              env:
-                  DIGEST: ${{ steps.build-and-push.outputs.digest }}
-                  TAGS: ${{ steps.meta.outputs.tags }}
-              run: |
-                  images=""
-                  for tag in ${TAGS}; do
-                      images+="${tag}@${DIGEST} "
-                  done
-                  cosign sign --yes ${images}
-
-            - name: Update repo description
-              uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4
-              continue-on-error: true
-              with:
-                  username: ${{ secrets.DOCKERHUB_USERNAME }}
-                  password: ${{ secrets.DOCKERHUB_TOKEN }}
-                  repository: vectorim/element-web
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index a301b6daf6ff87e079a3e8527a4143c80128dca8..e7d69cf4775764910973881eeda15e4410ec3378 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,23 +17,23 @@ jobs:
         runs-on: ubuntu-24.04
         steps:
             - name: Fetch element-desktop
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   repository: element-hq/element-desktop
                   path: element-desktop
 
             - name: Fetch element-web
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   path: element-web
 
             - name: Fetch matrix-js-sdk
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   repository: matrix-org/matrix-js-sdk
                   path: matrix-js-sdk
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   cache-dependency-path: element-web/yarn.lock
@@ -47,7 +47,7 @@ jobs:
                   echo "-   [Automations](automations.md)" >> docs/SUMMARY.md
 
             - name: Setup mdBook
-              uses: peaceiris/actions-mdbook@v2
+              uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2
               with:
                   mdbook-version: "0.4.10"
 
@@ -88,7 +88,7 @@ jobs:
               run: mdbook build
 
             - name: Upload artifact
-              uses: actions/upload-pages-artifact@v3
+              uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
               with:
                   path: ./book
 
@@ -104,4 +104,4 @@ jobs:
         steps:
             - name: Deploy to GitHub Pages
               id: deployment
-              uses: actions/deploy-pages@v4
+              uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/end-to-end-tests-netlify.yaml
index e25994ec9df44e33898ed2216724302e107d1862..90a8c3d24bfe3d1a58a08bc070eeaaad59135696 100644
--- a/.github/workflows/end-to-end-tests-netlify.yaml
+++ b/.github/workflows/end-to-end-tests-netlify.yaml
@@ -25,7 +25,7 @@ jobs:
             actions: read
         steps:
             - name: Download HTML report
-              uses: actions/download-artifact@v4
+              uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
               with:
                   github-token: ${{ secrets.GITHUB_TOKEN }}
                   run-id: ${{ github.event.workflow_run.id }}
@@ -33,7 +33,7 @@ jobs:
                   path: playwright-report
 
             - name: 📤 Deploy to Netlify
-              uses: matrix-org/netlify-pr-preview@v3
+              uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
               with:
                   path: playwright-report
                   owner: ${{ github.event.workflow_run.head_repository.owner.login }}
diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml
index 276420e1f8094080e93a2c2aac23da8357ca75d5..2932b02ffd966787a8bb4f1950743865b043e553 100644
--- a/.github/workflows/end-to-end-tests.yaml
+++ b/.github/workflows/end-to-end-tests.yaml
@@ -50,11 +50,11 @@ jobs:
             runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
         steps:
             - name: Checkout code
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   repository: element-hq/element-web
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -81,7 +81,7 @@ jobs:
                   yarn build
 
             - name: Upload Artifact
-              uses: actions/upload-artifact@v4
+              uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: webapp
                   path: webapp
@@ -89,7 +89,7 @@ jobs:
 
             - name: Calculate runner variables
               id: runner-vars
-              uses: actions/github-script@v7
+              uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               with:
                   script: |
                       const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
@@ -129,18 +129,18 @@ jobs:
                     - runAllTests: false
                       project: Pinecone
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   persist-credentials: false
                   repository: element-hq/element-web
 
             - name: 📥 Download artifact
-              uses: actions/download-artifact@v4
+              uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
               with:
                   name: webapp
                   path: webapp
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   cache-dependency-path: yarn.lock
@@ -154,12 +154,11 @@ jobs:
               run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
 
             - name: Cache playwright binaries
-              uses: actions/cache@v4
+              uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
               id: playwright-cache
               with:
-                  path: |
-                      ~/.cache/ms-playwright
-                  key: ${{ runner.os }}-playwright-${{ steps.playwright.outputs.version }}
+                  path: ~/.cache/ms-playwright
+                  key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}
 
             - name: Install Playwright browsers
               if: steps.playwright-cache.outputs.cache-hit != 'true'
@@ -181,25 +180,35 @@ jobs:
 
             - name: Upload blob report to GitHub Actions Artifacts
               if: always()
-              uses: actions/upload-artifact@v4
+              uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
                   path: blob-report
                   retention-days: 1
 
+    downstream-modules:
+        name: Downstream Playwright tests [element-modules]
+        needs: build
+        if: inputs.skip != true && github.event_name == 'merge_group'
+        uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main
+        with:
+            webapp-artifact: webapp
+
     complete:
         name: end-to-end-tests
-        needs: playwright
+        needs:
+            - playwright
+            - downstream-modules
         if: always()
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               if: inputs.skip != true
               with:
                   persist-credentials: false
                   repository: element-hq/element-web
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               if: inputs.skip != true
               with:
                   cache: "yarn"
@@ -211,7 +220,7 @@ jobs:
 
             - name: Download blob reports from GitHub Actions Artifacts
               if: inputs.skip != true
-              uses: actions/download-artifact@v4
+              uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
               with:
                   pattern: all-blob-reports-*
                   path: all-blob-reports
@@ -227,11 +236,11 @@ jobs:
             # Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
             - name: Upload HTML report
               if: always() && inputs.skip != true
-              uses: actions/upload-artifact@v4
+              uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: html-report
                   path: playwright-report
                   retention-days: 14
 
-            - if: needs.playwright.result != 'skipped' && needs.playwright.result != 'success'
+            - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
               run: exit 1
diff --git a/.github/workflows/issue_closed.yml b/.github/workflows/issue_closed.yml
index 2cffae0011ab469a02b450ebcf37c2965f4c675c..249f1eb342b36aba017ad12df2b01153ab38b841 100644
--- a/.github/workflows/issue_closed.yml
+++ b/.github/workflows/issue_closed.yml
@@ -10,7 +10,7 @@ jobs:
         name: Tidy closed issues
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               id: main
               with:
                   # PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
@@ -142,7 +142,7 @@ jobs:
                           });
                         }
                       }
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               name: Close duplicate as Not Planned
               if: steps.main.outputs.closeAsNotPlanned
               with:
diff --git a/.github/workflows/netlify.yaml b/.github/workflows/netlify.yaml
index cd03ca5140fae6ecea8b91b544608e5151268f45..a7909265c7bbb7c080e6f618d23982f098639533 100644
--- a/.github/workflows/netlify.yaml
+++ b/.github/workflows/netlify.yaml
@@ -28,7 +28,7 @@ jobs:
                       Exercise caution. Use test accounts.
 
             - name: 📥 Download artifact
-              uses: actions/download-artifact@v4
+              uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
               with:
                   github-token: ${{ secrets.GITHUB_TOKEN }}
                   run-id: ${{ github.event.workflow_run.id }}
@@ -36,7 +36,7 @@ jobs:
                   path: webapp
 
             - name: 📤 Deploy to Netlify
-              uses: matrix-org/netlify-pr-preview@v3
+              uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
               with:
                   path: webapp
                   owner: ${{ github.event.workflow_run.head_repository.owner.login }}
diff --git a/.github/workflows/pending-reviews.yaml b/.github/workflows/pending-reviews.yaml
index c96ed3f17e860c67c9c02557662bf9cbe63cd861..199eb60daa7eb2447fe08e17983fc86e59ce1019 100644
--- a/.github/workflows/pending-reviews.yaml
+++ b/.github/workflows/pending-reviews.yaml
@@ -16,7 +16,7 @@ jobs:
             URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
             RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               env:
                   HS_URL: ${{ secrets.BETABOT_HS_URL }}
                   ROOM_ID: ${{ secrets.ROOM_ID }}
diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml
index e5e2f739c0911d9043f298fbf8cf7474b56d6012..4cbdb17bbd01d5289eeb07027d21ce1479baf7b8 100644
--- a/.github/workflows/playwright-image-updates.yaml
+++ b/.github/workflows/playwright-image-updates.yaml
@@ -10,7 +10,7 @@ jobs:
         permissions:
             pull-requests: write
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
             - name: Update synapse image
               run: |
@@ -23,7 +23,7 @@ jobs:
 
             - name: Create Pull Request
               id: cpr
-              uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
+              uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
               with:
                   token: ${{ secrets.ELEMENT_BOT_TOKEN }}
                   branch: actions/playwright-image-updates
diff --git a/.github/workflows/pull_request_base_branch.yaml b/.github/workflows/pull_request_base_branch.yaml
index 6610ee48791f357ee175213db08770a1139db49f..fbdebfbed0b3aaf351f477c6983d021956f20dab 100644
--- a/.github/workflows/pull_request_base_branch.yaml
+++ b/.github/workflows/pull_request_base_branch.yaml
@@ -8,7 +8,7 @@ jobs:
         name: Check PR base branch
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               with:
                   script: |
                       const baseBranch = context.payload.pull_request.base.ref;
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 019bc1b9ce2d746393f5fbce31fffdf7060718c2..78383e8bf5e69a7ee065d8e920ad9bfa1e261ade 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -19,6 +19,7 @@ jobs:
             contents: write
             issues: write
             pull-requests: read
+            id-token: write
         secrets:
             ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
             GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
@@ -50,7 +51,7 @@ jobs:
         permissions:
             checks: read
         steps:
-            - name: Wait for dockerhub
+            - name: Wait for docker build
               uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
               with:
                   ref: master
diff --git a/.github/workflows/release_prepare.yml b/.github/workflows/release_prepare.yml
index 031221041ab50136ba99ce5d018efa578a913faf..2f36644c2ea310dd91612281c8403ea59c1f1a41 100644
--- a/.github/workflows/release_prepare.yml
+++ b/.github/workflows/release_prepare.yml
@@ -41,7 +41,7 @@ jobs:
             REPOS: matrix-js-sdk element-web element-desktop
         steps:
             - name: Checkout Element Desktop
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               if: inputs.element-desktop
               with:
                   repository: element-hq/element-desktop
@@ -51,7 +51,7 @@ jobs:
                   fetch-tags: true
                   token: ${{ secrets.ELEMENT_BOT_TOKEN }}
             - name: Checkout Element Web
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               if: inputs.element-web
               with:
                   repository: element-hq/element-web
@@ -61,7 +61,7 @@ jobs:
                   fetch-tags: true
                   token: ${{ secrets.ELEMENT_BOT_TOKEN }}
             - name: Checkout Matrix JS SDK
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               if: inputs.matrix-js-sdk
               with:
                   repository: matrix-org/matrix-js-sdk
@@ -100,7 +100,7 @@ jobs:
                   repo: matrix-org/matrix-js-sdk
                   repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
                   wait-interval: 10
-                  check-name: draft
+                  check-name: "draft / draft"
                   allowed-conclusions: success
 
             - name: Wait for element-web draft
@@ -111,7 +111,7 @@ jobs:
                   repo: element-hq/element-web
                   repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
                   wait-interval: 10
-                  check-name: draft
+                  check-name: "draft / draft"
                   allowed-conclusions: success
 
             - name: Wait for element-desktop draft
@@ -122,5 +122,5 @@ jobs:
                   repo: element-hq/element-desktop
                   repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
                   wait-interval: 10
-                  check-name: draft
+                  check-name: "draft / draft"
                   allowed-conclusions: success
diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml
index ee731b3ac3817dd12f15c77f848f7e177af584ab..d63e0da8edae34295b3c7ec6049ab143b82c8310 100644
--- a/.github/workflows/static_analysis.yaml
+++ b/.github/workflows/static_analysis.yaml
@@ -23,9 +23,9 @@ jobs:
         name: "Typescript Syntax Check"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -51,12 +51,13 @@ jobs:
                 error|invalid_json
                 error|misconfigured
                 welcome_to_element
+                devtools|settings|elementCallUrl
 
     rethemendex_lint:
         name: "Rethemendex Check"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
             - run: ./res/css/rethemendex.sh
 
@@ -66,9 +67,9 @@ jobs:
         name: "ESLint"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -84,9 +85,9 @@ jobs:
         name: "Style Lint"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -102,9 +103,9 @@ jobs:
         name: "Workflow Lint"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -120,9 +121,9 @@ jobs:
         name: "Analyse Dead Code"
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index dfb92e8ba025bf31c9d5b6a42c41d07299daa2a5..276c53c09826d12ef4e1638f67d1c5f937fd0b67 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -39,12 +39,12 @@ jobs:
                 runner: [1, 2]
         steps:
             - name: Checkout code
-              uses: actions/checkout@v4
+              uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
               with:
                   repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
 
             - name: Yarn cache
-              uses: actions/setup-node@v4
+              uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   node-version: "lts/*"
                   cache: "yarn"
@@ -55,7 +55,7 @@ jobs:
                   JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
 
             - name: Jest Cache
-              uses: actions/cache@v4
+              uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
               with:
                   path: /tmp/jest_cache
                   key: ${{ hashFiles('**/yarn.lock') }}
@@ -84,7 +84,7 @@ jobs:
 
             - name: Upload Artifact
               if: env.ENABLE_COVERAGE == 'true'
-              uses: actions/upload-artifact@v4
+              uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
               with:
                   name: coverage-${{ matrix.runner }}
                   path: |
@@ -104,7 +104,7 @@ jobs:
 
             - name: Skip SonarCloud in merge queue
               if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
-              uses: guibranco/github-status-action-v2@56cd38caf0615dd03f49d42ed301f1469911ac61
+              uses: guibranco/github-status-action-v2@5f2b01ce1394109f70954ae6b69ef41cf7928e63
               with:
                   authToken: ${{ secrets.GITHUB_TOKEN }}
                   state: success
diff --git a/.github/workflows/triage-assigned.yml b/.github/workflows/triage-assigned.yml
index e43eb9461833591979cfab7c9538e46675b80a16..f190122a1ced941622f9dffb41e482f45d687287 100644
--- a/.github/workflows/triage-assigned.yml
+++ b/.github/workflows/triage-assigned.yml
@@ -11,7 +11,8 @@ jobs:
         runs-on: ubuntu-24.04
         if: |
             contains(github.event.issue.assignees.*.login, 't3chguy') ||
-            contains(github.event.issue.assignees.*.login, 'andybalaam') ||
+            contains(github.event.issue.assignees.*.login, 'florianduros') ||
+            contains(github.event.issue.assignees.*.login, 'dbkr') ||
             contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
         steps:
             - uses: actions/add-to-project@main
diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml
index 2cb05a8bcff6af096fe4b04aa863a988ee25bff8..e1849e0efca89b938f2b1b3b253c536c2be294b5 100644
--- a/.github/workflows/triage-labelled.yml
+++ b/.github/workflows/triage-labelled.yml
@@ -27,7 +27,7 @@ jobs:
             contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
             contains(github.event.issue.labels.*.name, 'A-Element-Call')
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               with:
                   script: |
                       github.rest.issues.addLabels({
@@ -44,7 +44,7 @@ jobs:
             contains(github.event.issue.labels.*.name, 'good first issue') ||
             contains(github.event.issue.labels.*.name, 'Hacktoberfest')
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               with:
                   script: |
                       github.rest.issues.addLabels({
@@ -61,7 +61,7 @@ jobs:
             contains(github.event.issue.labels.*.name, 'X-Needs-Info')
         steps:
             - id: add_to_project
-              uses: actions/add-to-project@v1.0.2
+              uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
               with:
                   project-url: ${{ env.PROJECT_URL }}
                   github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -84,7 +84,7 @@ jobs:
             contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
         steps:
             - id: add_to_project
-              uses: actions/add-to-project@v1.0.2
+              uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
               with:
                   project-url: ${{ env.PROJECT_URL }}
                   github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -150,15 +150,15 @@ jobs:
                   project-url: https://github.com/orgs/element-hq/projects/41
                   github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
 
-    verticals_feature:
-        name: Add labelled issues to Verticals Feature project
+    crypto:
+        name: Add labelled issues to Crypto project
         runs-on: ubuntu-24.04
         if: >
-            contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
+            contains(github.event.issue.labels.*.name, 'Team: Crypto')
         steps:
             - uses: actions/add-to-project@main
               with:
-                  project-url: https://github.com/orgs/element-hq/projects/57
+                  project-url: https://github.com/orgs/element-hq/projects/76
                   github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
 
     tech_debt:
diff --git a/.github/workflows/triage-stale-flaky-tests.yml b/.github/workflows/triage-stale-flaky-tests.yml
deleted file mode 100644
index 3d3bcb0b13c1a4a8e95fa5e2eae8445e0f382f60..0000000000000000000000000000000000000000
--- a/.github/workflows/triage-stale-flaky-tests.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Close stale flaky issues
-on:
-    workflow_dispatch: {}
-    schedule:
-        - cron: "30 1 * * *"
-permissions: {}
-jobs:
-    close:
-        runs-on: ubuntu-24.04
-        permissions:
-            actions: write
-            issues: write
-        steps:
-            - uses: actions/stale@v9
-              with:
-                  only-labels: "Z-Flaky-Test"
-                  days-before-stale: 14
-                  days-before-close: 0
-                  close-issue-message: "This flaky test issue has not been updated in 14 days. It is being closed as presumed resolved."
-                  exempt-issue-labels: "Z-Flaky-Test-Disabled"
-                  operations-per-run: 100
diff --git a/.github/workflows/triage-stale.yml b/.github/workflows/triage-stale.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f76cd299ccf3d9ddbe5703d80e4b48313464374b
--- /dev/null
+++ b/.github/workflows/triage-stale.yml
@@ -0,0 +1,27 @@
+name: Close stale issues & PRs
+on:
+    workflow_dispatch: {}
+    schedule:
+        - cron: "30 1 * * *"
+permissions: {}
+jobs:
+    close:
+        runs-on: ubuntu-24.04
+        permissions:
+            actions: write
+            issues: write
+            pull-requests: write
+        steps:
+            - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
+              with:
+                  operations-per-run: 100
+                  # Flaky test issue closing
+                  only-issue-labels: "Z-Flaky-Test"
+                  days-before-issue-stale: 14
+                  days-before-issue-close: 0
+                  close-issue-message: "This flaky test issue has not been updated in 14 days. It is being closed as presumed resolved."
+                  exempt-issue-labels: "Z-Flaky-Test-Disabled"
+                  # Stale PR closing
+                  days-before-pr-stale: 180
+                  days-before-pr-close: 0
+                  close-pr-message: "This PR has been automatically closed because it has been stale for 180 days. If you wish to continue working on this PR, please ping a maintainer to reopen it."
diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml
index efbf80eea998703ddfb6f65f92bb37e0ef7c3ffc..d3bda6df1f17c6598436fd0f49814833dd9320dc 100644
--- a/.github/workflows/triage-unlabelled.yml
+++ b/.github/workflows/triage-unlabelled.yml
@@ -62,7 +62,7 @@ jobs:
             contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
             contains(github.event.issue.labels.*.name, 'Z-Labs')
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               with:
                   script: |
                       github.rest.issues.removeLabel({
diff --git a/.github/workflows/update-jitsi.yml b/.github/workflows/update-jitsi.yml
index a3abcb002f562bfdd505b36dbc631dcad6dc854f..da386c544db4025b803a31573aa68f316dd383a1 100644
--- a/.github/workflows/update-jitsi.yml
+++ b/.github/workflows/update-jitsi.yml
@@ -9,9 +9,9 @@ jobs:
     update:
         runs-on: ubuntu-24.04
         steps:
-            - uses: actions/checkout@v4
+            - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
-            - uses: actions/setup-node@v4
+            - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
               with:
                   cache: "yarn"
                   node-version: "lts/*"
@@ -23,7 +23,7 @@ jobs:
               run: "yarn update:jitsi"
 
             - name: Create Pull Request
-              uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
+              uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
               with:
                   token: ${{ secrets.ELEMENT_BOT_TOKEN }}
                   branch: actions/jitsi-update
diff --git a/.github/workflows/update-topics.yaml b/.github/workflows/update-topics.yaml
index cd6c2fc55338618530818defecda0cb6714387c8..5ee9f2b608c99f0e9e26dc30458d07786c4b1419 100644
--- a/.github/workflows/update-topics.yaml
+++ b/.github/workflows/update-topics.yaml
@@ -22,7 +22,7 @@ jobs:
         runs-on: ubuntu-24.04
         environment: Matrix
         steps:
-            - uses: actions/github-script@v7
+            - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
               env:
                   HS_URL: ${{ secrets.BETABOT_HS_URL }}
                   LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
diff --git a/.gitignore b/.gitignore
index 685a2cc3172c28ac09c4ead65db7e7c68277636e..429b317a4f0b03673abe5bcb26707daa1df93109 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,7 +25,7 @@ electron/pub
 .env
 /coverage
 # Auto-generated file
-/src/modules.ts
+/src/modules.js
 /build_config.yaml
 /book
 /index.html
diff --git a/.prettierignore b/.prettierignore
index 418329cf287e726df46fc9486a765a92390ef6dc..46b1ac5b549db6ba5914763f0f6c0bd7cdc94af9 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -17,6 +17,7 @@ electron/pub
 /coverage
 # Auto-generated file
 /src/modules.ts
+/src/modules.js
 /src/i18n/strings
 /build_config.yaml
 # Raises an error because it contains a template var breaking the script tag
diff --git a/.stylelintrc.js b/.stylelintrc.js
index fa36402ff14b7e68d3362a7b48c3b80826f70201..ffc6c345b96b6db981193b2e2986b424cd7410e9 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -33,19 +33,15 @@ module.exports = {
         "import-notation": null,
         "value-keyword-case": null,
         "declaration-block-no-redundant-longhand-properties": null,
-        "declaration-block-no-duplicate-properties": [
-            true,
-            // useful for fallbacks
-            { ignore: ["consecutive-duplicates-with-different-values"] },
-        ],
         "shorthand-property-no-redundant-values": null,
         "property-no-vendor-prefix": null,
-        "value-no-vendor-prefix": null,
         "selector-no-vendor-prefix": null,
         "media-feature-name-no-vendor-prefix": null,
         "number-max-precision": null,
         "no-invalid-double-slash-comments": true,
         "media-feature-range-notation": null,
+        "declaration-property-value-no-unknown": null,
+        "declaration-property-value-keyword-no-deprecated": null,
         "csstools/value-no-unknown-custom-properties": [
             true,
             {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5411b67428c36e69f2bce7611665da9c0dd8886d..48a721c013096d4d9092d6b8fdd2a3a3be07b52c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,279 @@
+Changes in [1.11.101](https://github.com/element-hq/element-web/releases/tag/v1.11.101) (2025-05-20)
+====================================================================================================
+## ✨ Features
+
+* New room list: add keyboard navigation support ([#29805](https://github.com/element-hq/element-web/pull/29805)). Contributed by @florianduros.
+* Use the JoinRuleSettings component for the guest link access prompt. ([#28614](https://github.com/element-hq/element-web/pull/28614)). Contributed by @toger5.
+* Add loading state to the new room list view ([#29725](https://github.com/element-hq/element-web/pull/29725)). Contributed by @langleyd.
+* Make OIDC identity reset consistent with EX ([#29854](https://github.com/element-hq/element-web/pull/29854)). Contributed by @andybalaam.
+* Support error code for email / phone adding unsupported (MSC4178) ([#29855](https://github.com/element-hq/element-web/pull/29855)). Contributed by @dbkr.
+* Update identity reset UI (Make consistent with EX) ([#29701](https://github.com/element-hq/element-web/pull/29701)). Contributed by @andybalaam.
+* Add secondary filters to the new room list ([#29818](https://github.com/element-hq/element-web/pull/29818)). Contributed by @dbkr.
+* Fix battery drain from Web Audio ([#29203](https://github.com/element-hq/element-web/pull/29203)). Contributed by @mbachry.
+
+## 🐛 Bug Fixes
+
+* Fix go home shortcut on macos and change toggle action events shortcut ([#29929](https://github.com/element-hq/element-web/pull/29929)). Contributed by @florianduros.
+* New room list: fix outdated message preview when space or filter change ([#29925](https://github.com/element-hq/element-web/pull/29925)). Contributed by @florianduros.
+* Stop migrating to MSC4278 if the config exists. ([#29924](https://github.com/element-hq/element-web/pull/29924)). Contributed by @Half-Shot.
+* Ensure consistent download file name on download from ImageView ([#29913](https://github.com/element-hq/element-web/pull/29913)). Contributed by @t3chguy.
+* Add error toast when service worker registration fails ([#29895](https://github.com/element-hq/element-web/pull/29895)). Contributed by @t3chguy.
+* New Room List: Prevent old tombstoned rooms from appearing in the list ([#29881](https://github.com/element-hq/element-web/pull/29881)). Contributed by @MidhunSureshR.
+* Remove lag in search field ([#29885](https://github.com/element-hq/element-web/pull/29885)). Contributed by @florianduros.
+* Respect UIFeature.Voip ([#29873](https://github.com/element-hq/element-web/pull/29873)). Contributed by @langleyd.
+* Allow jumping to message search from spotlight ([#29850](https://github.com/element-hq/element-web/pull/29850)). Contributed by @t3chguy.
+
+
+Changes in [1.11.100](https://github.com/element-hq/element-web/releases/tag/v1.11.100) (2025-05-06)
+====================================================================================================
+## ✨ Features
+
+* Move rich topics out of labs / stabilise MSC3765 ([#29817](https://github.com/element-hq/element-web/pull/29817)). Contributed by @Johennes.
+* Spell out that Element Web does \*not\* work on mobile. ([#29211](https://github.com/element-hq/element-web/pull/29211)). Contributed by @ara4n.
+* Add message preview support to the new room list ([#29784](https://github.com/element-hq/element-web/pull/29784)). Contributed by @dbkr.
+* Global configuration flag for media previews ([#29582](https://github.com/element-hq/element-web/pull/29582)). Contributed by @Half-Shot.
+* New room list: add partial keyboard shortcuts support ([#29783](https://github.com/element-hq/element-web/pull/29783)). Contributed by @florianduros.
+* MVVM RoomSummaryCard Topic ([#29710](https://github.com/element-hq/element-web/pull/29710)). Contributed by @MarcWadai.
+* Warn on self change from settings > roles ([#28926](https://github.com/element-hq/element-web/pull/28926)). Contributed by @MarcWadai.
+* New room list: new visual for invitation ([#29773](https://github.com/element-hq/element-web/pull/29773)). Contributed by @florianduros.
+
+## 🐛 Bug Fixes
+
+* Fix incorrect display of the user info display name ([#29826](https://github.com/element-hq/element-web/pull/29826)). Contributed by @langleyd.
+* RoomListStore: Remove invite rooms on decline ([#29804](https://github.com/element-hq/element-web/pull/29804)). Contributed by @MidhunSureshR.
+* Fix the buttons not being displayed with long preview text ([#29811](https://github.com/element-hq/element-web/pull/29811)). Contributed by @dbkr.
+* New room list: fix missing/incorrect notification decoration  ([#29796](https://github.com/element-hq/element-web/pull/29796)). Contributed by @florianduros.
+* New Room List: Prevent potential scroll jump/flicker when switching spaces ([#29781](https://github.com/element-hq/element-web/pull/29781)). Contributed by @MidhunSureshR.
+* New room list: fix incorrect decoration ([#29770](https://github.com/element-hq/element-web/pull/29770)). Contributed by @florianduros.
+
+
+Changes in [1.11.99](https://github.com/element-hq/element-web/releases/tag/v1.11.99) (2025-04-23)
+==================================================================================================
+No changes, just bumping the version to accommodate a new Element Desktop release
+
+Changes in [1.11.98](https://github.com/element-hq/element-web/releases/tag/v1.11.98) (2025-04-22)
+==================================================================================================
+## ✨ Features
+
+* print better errors in the search view instead of a blocking modal ([#29724](https://github.com/element-hq/element-web/pull/29724)). Contributed by @Jujure.
+* New room list: video room and video call decoration  ([#29693](https://github.com/element-hq/element-web/pull/29693)). Contributed by @florianduros.
+* Remove Secure Backup, Cross-signing and Cryptography sections in `Security & Privacy` user settings ([#29088](https://github.com/element-hq/element-web/pull/29088)). Contributed by @florianduros.
+* Allow reporting a room when rejecting an invite. ([#29570](https://github.com/element-hq/element-web/pull/29570)). Contributed by @Half-Shot.
+* RoomListViewModel: Reset primary and secondary filters on space change ([#29672](https://github.com/element-hq/element-web/pull/29672)). Contributed by @MidhunSureshR.
+* RoomListStore: Support specific sorting requirements for muted rooms ([#29665](https://github.com/element-hq/element-web/pull/29665)). Contributed by @MidhunSureshR.
+* New room list: add notification options menu ([#29639](https://github.com/element-hq/element-web/pull/29639)). Contributed by @florianduros.
+* Room List: Scroll to top of the list when active room is not in the list ([#29650](https://github.com/element-hq/element-web/pull/29650)). Contributed by @MidhunSureshR.
+
+## 🐛 Bug Fixes
+
+* Fix unwanted form submit behaviour in memberlist ([#29747](https://github.com/element-hq/element-web/pull/29747)). Contributed by @MidhunSureshR.
+* New room list: fix public room icon visibility when filter change ([#29737](https://github.com/element-hq/element-web/pull/29737)). Contributed by @florianduros.
+* Fix custom theme support for short hex \& rgba hex strings ([#29726](https://github.com/element-hq/element-web/pull/29726)). Contributed by @t3chguy.
+* New room list: minor visual fixes ([#29723](https://github.com/element-hq/element-web/pull/29723)). Contributed by @florianduros.
+* Fix getOidcCallbackUrl for Element Desktop ([#29711](https://github.com/element-hq/element-web/pull/29711)). Contributed by @t3chguy.
+* Fix some webp images improperly marked as animated ([#29713](https://github.com/element-hq/element-web/pull/29713)). Contributed by @Petersmit27.
+* Revert deletion of hydrateSession ([#29703](https://github.com/element-hq/element-web/pull/29703)). Contributed by @Jujure.
+* Fix converttoroom \& converttodm not working ([#29705](https://github.com/element-hq/element-web/pull/29705)). Contributed by @t3chguy.
+* Ensure forceCloseAllModals also closes priority/static modals ([#29706](https://github.com/element-hq/element-web/pull/29706)). Contributed by @t3chguy.
+* Continue button is disabled when uploading a recovery key file ([#29695](https://github.com/element-hq/element-web/pull/29695)). Contributed by @Giwayume.
+* Catch errors after syncing recovery ([#29691](https://github.com/element-hq/element-web/pull/29691)). Contributed by @andybalaam.
+* New room list: fix multiple visual issues ([#29673](https://github.com/element-hq/element-web/pull/29673)). Contributed by @florianduros.
+* New Room List: Fix mentions filter matching rooms with any highlight ([#29668](https://github.com/element-hq/element-web/pull/29668)). Contributed by @MidhunSureshR.
+*  Fix truncated emoji label during emoji SAS ([#29643](https://github.com/element-hq/element-web/pull/29643)). Contributed by @florianduros.
+* Remove duplicate jitsi link ([#29642](https://github.com/element-hq/element-web/pull/29642)). Contributed by @dbkr.
+
+
+Changes in [1.11.97](https://github.com/element-hq/element-web/releases/tag/v1.11.97) (2025-04-08)
+==================================================================================================
+## ✨ Features
+
+* New room list: reduce padding between avatar and room list border ([#29634](https://github.com/element-hq/element-web/pull/29634)). Contributed by @florianduros.
+* Bundle Element Call with Element Web packages ([#29309](https://github.com/element-hq/element-web/pull/29309)). Contributed by @t3chguy.
+* Hide an event notification if it is redacted ([#29605](https://github.com/element-hq/element-web/pull/29605)). Contributed by @Half-Shot.
+* Docker: Use nginx-unprivileged as base image ([#29353](https://github.com/element-hq/element-web/pull/29353)). Contributed by @AndrewFerr.
+* Switch away from nesting React trees and mangling the DOM ([#29586](https://github.com/element-hq/element-web/pull/29586)). Contributed by @t3chguy.
+* New room list: add notification decoration ([#29552](https://github.com/element-hq/element-web/pull/29552)). Contributed by @florianduros.
+* RoomListStore: Unread filter should match rooms that were marked as unread ([#29580](https://github.com/element-hq/element-web/pull/29580)). Contributed by @MidhunSureshR.
+* Add support for hiding videos ([#29496](https://github.com/element-hq/element-web/pull/29496)). Contributed by @Half-Shot.
+* Use an outline icon for the report room button ([#29573](https://github.com/element-hq/element-web/pull/29573)). Contributed by @robintown.
+* Generate/load pickle key on SSO ([#29568](https://github.com/element-hq/element-web/pull/29568)). Contributed by @Jujure.
+* Add report room dialog button/dialog. ([#29513](https://github.com/element-hq/element-web/pull/29513)). Contributed by @Half-Shot.
+* RoomListViewModel: Make the active room sticky in the list ([#29551](https://github.com/element-hq/element-web/pull/29551)). Contributed by @MidhunSureshR.
+* Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. ([#29363](https://github.com/element-hq/element-web/pull/29363)). Contributed by @Half-Shot.
+* New room list: add selection decoration ([#29531](https://github.com/element-hq/element-web/pull/29531)). Contributed by @florianduros.
+* Simplified Sliding Sync ([#28515](https://github.com/element-hq/element-web/pull/28515)). Contributed by @dbkr.
+* Add ability to hide images after clicking "show image" ([#29467](https://github.com/element-hq/element-web/pull/29467)). Contributed by @Half-Shot.
+
+## 🐛 Bug Fixes
+
+* Fix scroll issues in memberlist ([#29392](https://github.com/element-hq/element-web/pull/29392)). Contributed by @MidhunSureshR.
+* Ensure clicks on spoilers do not get handled by the hidden content ([#29618](https://github.com/element-hq/element-web/pull/29618)). Contributed by @t3chguy.
+* New room list: add cursor pointer on room list item ([#29627](https://github.com/element-hq/element-web/pull/29627)). Contributed by @florianduros.
+* Fix missing ambiguous url tooltips on Element Desktop ([#29619](https://github.com/element-hq/element-web/pull/29619)). Contributed by @t3chguy.
+* New room list: fix spacing and padding ([#29607](https://github.com/element-hq/element-web/pull/29607)). Contributed by @florianduros.
+* Make fetchdep check out matching branch name ([#29601](https://github.com/element-hq/element-web/pull/29601)). Contributed by @dbkr.
+* Fix MFileBody fileName not considering `filename` ([#29589](https://github.com/element-hq/element-web/pull/29589)). Contributed by @t3chguy.
+* Fix token expiry racing with login causing wrong error to be shown ([#29566](https://github.com/element-hq/element-web/pull/29566)). Contributed by @t3chguy.
+* Fix bug which caused startup to hang if the clock was wound back since a previous session ([#29558](https://github.com/element-hq/element-web/pull/29558)). Contributed by @richvdh.
+* RoomListViewModel: Reset any primary filter on secondary filter change ([#29562](https://github.com/element-hq/element-web/pull/29562)). Contributed by @MidhunSureshR.
+* RoomListStore: Unread filter should only filter rooms having unread counts ([#29555](https://github.com/element-hq/element-web/pull/29555)). Contributed by @MidhunSureshR.
+* In force-verify mode, prevent bypassing by cancelling device verification ([#29487](https://github.com/element-hq/element-web/pull/29487)). Contributed by @andybalaam.
+* Add title attribute to user identifier ([#29547](https://github.com/element-hq/element-web/pull/29547)). Contributed by @arpitbatra123.
+
+
+Changes in [1.11.96](https://github.com/element-hq/element-web/releases/tag/v1.11.96) (2025-03-25)
+==================================================================================================
+## ✨ Features
+
+* RoomListViewModel: Track the index of the active room in the list ([#29519](https://github.com/element-hq/element-web/pull/29519)). Contributed by @MidhunSureshR.
+* New room list: add empty state ([#29512](https://github.com/element-hq/element-web/pull/29512)). Contributed by @florianduros.
+* Implement `MessagePreviewViewModel` ([#29514](https://github.com/element-hq/element-web/pull/29514)). Contributed by @MidhunSureshR.
+* RoomListViewModel: Add functionality to toggle message preview setting ([#29511](https://github.com/element-hq/element-web/pull/29511)). Contributed by @MidhunSureshR.
+* New room list: add more options menu on room list item ([#29445](https://github.com/element-hq/element-web/pull/29445)). Contributed by @florianduros.
+* RoomListViewModel: Provide a way to resort the room list and track the active sort method ([#29499](https://github.com/element-hq/element-web/pull/29499)). Contributed by @MidhunSureshR.
+* Change \*All rooms\* meta space name to \*All Chats\* ([#29498](https://github.com/element-hq/element-web/pull/29498)). Contributed by @florianduros.
+* Add setting to hide avatars of rooms you have been invited to. ([#29497](https://github.com/element-hq/element-web/pull/29497)). Contributed by @Half-Shot.
+* Room List Store: Save preferred sorting algorithm and use that on app launch ([#29493](https://github.com/element-hq/element-web/pull/29493)). Contributed by @MidhunSureshR.
+* Add key storage toggle to Encryption settings ([#29310](https://github.com/element-hq/element-web/pull/29310)). Contributed by @dbkr.
+* New room list: add primary filters ([#29481](https://github.com/element-hq/element-web/pull/29481)). Contributed by @florianduros.
+* Implement MSC4142: Remove unintentional intentional mentions in replies ([#28209](https://github.com/element-hq/element-web/pull/28209)). Contributed by @tulir.
+* White background for 'They do not match' button ([#29470](https://github.com/element-hq/element-web/pull/29470)). Contributed by @andybalaam.
+* RoomListViewModel: Support secondary filters in the view model ([#29465](https://github.com/element-hq/element-web/pull/29465)). Contributed by @MidhunSureshR.
+* RoomListViewModel: Support primary filters in the view model ([#29454](https://github.com/element-hq/element-web/pull/29454)). Contributed by @MidhunSureshR.
+* Room List Store: Implement secondary filters ([#29458](https://github.com/element-hq/element-web/pull/29458)). Contributed by @MidhunSureshR.
+* Room List Store: Implement rest of the primary filters ([#29444](https://github.com/element-hq/element-web/pull/29444)). Contributed by @MidhunSureshR.
+* Room List Store: Support filters by implementing just the favourite filter ([#29433](https://github.com/element-hq/element-web/pull/29433)). Contributed by @MidhunSureshR.
+* Move toggle switch for integration manager for a11y ([#29436](https://github.com/element-hq/element-web/pull/29436)). Contributed by @Half-Shot.
+* New room list: basic flat list ([#29368](https://github.com/element-hq/element-web/pull/29368)). Contributed by @florianduros.
+* Improve rageshake upload experience by providing useful error information ([#29378](https://github.com/element-hq/element-web/pull/29378)). Contributed by @Half-Shot.
+* Add more functionality to the room list vm ([#29402](https://github.com/element-hq/element-web/pull/29402)). Contributed by @MidhunSureshR.
+
+## 🐛 Bug Fixes
+
+* New room list: fix compose menu action in space  ([#29500](https://github.com/element-hq/element-web/pull/29500)). Contributed by @florianduros.
+* Change ToggleHiddenEventVisibility \& GoToHome KeyBindingActions ([#29374](https://github.com/element-hq/element-web/pull/29374)). Contributed by @gy-mate.
+* Fix Docker Healthcheck ([#29471](https://github.com/element-hq/element-web/pull/29471)). Contributed by @benbz.
+* Room List Store: Fetch rooms after space store is ready + attach store to window ([#29453](https://github.com/element-hq/element-web/pull/29453)). Contributed by @MidhunSureshR.
+* Room List Store: Fix bug where left rooms appear in room list ([#29452](https://github.com/element-hq/element-web/pull/29452)). Contributed by @MidhunSureshR.
+* Add space to the bottom of the room summary actions below leave room ([#29270](https://github.com/element-hq/element-web/pull/29270)). Contributed by @langleyd.
+* Show error screens in group calls ([#29254](https://github.com/element-hq/element-web/pull/29254)). Contributed by @robintown.
+* Prevent user from accidentally triggering multiple identity resets ([#29388](https://github.com/element-hq/element-web/pull/29388)). Contributed by @uhoreg.
+* Remove buggy tooltip on room intro \& homepage ([#29406](https://github.com/element-hq/element-web/pull/29406)). Contributed by @t3chguy.
+
+
+Changes in [1.11.95](https://github.com/element-hq/element-web/releases/tag/v1.11.95) (2025-03-11)
+==================================================================================================
+## ✨ Features
+
+* Room List Store: Filter rooms by active space ([#29399](https://github.com/element-hq/element-web/pull/29399)). Contributed by @MidhunSureshR.
+* Room List - Update the room list store on actions from the dispatcher ([#29397](https://github.com/element-hq/element-web/pull/29397)). Contributed by @MidhunSureshR.
+* Room List  - Implement a minimal view model ([#29357](https://github.com/element-hq/element-web/pull/29357)). Contributed by @MidhunSureshR.
+* New room list: add space menu in room header ([#29352](https://github.com/element-hq/element-web/pull/29352)). Contributed by @florianduros.
+* Room List - Store sorted rooms in skip list ([#29345](https://github.com/element-hq/element-web/pull/29345)). Contributed by @MidhunSureshR.
+* New room list: add dial to search section ([#29359](https://github.com/element-hq/element-web/pull/29359)). Contributed by @florianduros.
+* New room list: add compose menu for spaces in header ([#29347](https://github.com/element-hq/element-web/pull/29347)). Contributed by @florianduros.
+* Use EditInPlace control for Identity Server picker to improve a11y ([#29280](https://github.com/element-hq/element-web/pull/29280)). Contributed by @Half-Shot.
+* First step to add header to new room list ([#29320](https://github.com/element-hq/element-web/pull/29320)). Contributed by @florianduros.
+* Add Windows 64-bit arm link and remove 32-bit link on compatibility page ([#29312](https://github.com/element-hq/element-web/pull/29312)). Contributed by @t3chguy.
+* Honour the backup disable flag from Element X ([#29290](https://github.com/element-hq/element-web/pull/29290)). Contributed by @dbkr.
+
+## 🐛 Bug Fixes
+
+* Fix edited code block width ([#29394](https://github.com/element-hq/element-web/pull/29394)). Contributed by @florianduros.
+* new room list: keep space name in one line in header ([#29369](https://github.com/element-hq/element-web/pull/29369)). Contributed by @florianduros.
+* Dismiss "Key storage out of sync" toast when secrets received ([#29348](https://github.com/element-hq/element-web/pull/29348)). Contributed by @richvdh.
+* Minor CSS fixes for the new room list ([#29334](https://github.com/element-hq/element-web/pull/29334)). Contributed by @florianduros.
+* Add padding to room header icon ([#29271](https://github.com/element-hq/element-web/pull/29271)). Contributed by @langleyd.
+
+
+Changes in [1.11.94](https://github.com/element-hq/element-web/releases/tag/v1.11.94) (2025-02-27)
+==================================================================================================
+## 🐛 Bug Fixes
+
+* [Backport staging] fix: /tmp/element-web-config may already exist preventing the container from booting up ([#29377](https://github.com/element-hq/element-web/pull/29377)). Contributed by @RiotRobot.
+
+
+Changes in [1.11.93](https://github.com/element-hq/element-web/releases/tag/v1.11.93) (2025-02-25)
+==================================================================================================
+## ✨ Features
+
+* [backport] Dynamically load Element Web modules in Docker entrypoint ([#29358](https://github.com/element-hq/element-web/pull/29358)). Contributed by @t3chguy.
+* ChangeRecoveryKey: error handling ([#29262](https://github.com/element-hq/element-web/pull/29262)). Contributed by @richvdh.
+* Dehydration: enable dehydrated device on "Set up recovery" ([#29265](https://github.com/element-hq/element-web/pull/29265)). Contributed by @richvdh.
+* Render reason for invite rejection. ([#29257](https://github.com/element-hq/element-web/pull/29257)). Contributed by @Half-Shot.
+* New room list: add search section ([#29251](https://github.com/element-hq/element-web/pull/29251)). Contributed by @florianduros.
+* New room list: hide favourites and people meta spaces ([#29241](https://github.com/element-hq/element-web/pull/29241)). Contributed by @florianduros.
+* New Room List: Create new labs flag ([#29239](https://github.com/element-hq/element-web/pull/29239)). Contributed by @MidhunSureshR.
+* Stop URl preview from covering message box ([#29215](https://github.com/element-hq/element-web/pull/29215)). Contributed by @edent.
+* Rename "security key" into "recovery key" ([#29217](https://github.com/element-hq/element-web/pull/29217)). Contributed by @florianduros.
+* Add new verification section to user profile ([#29200](https://github.com/element-hq/element-web/pull/29200)). Contributed by @MidhunSureshR.
+* Initial support for runtime modules ([#29104](https://github.com/element-hq/element-web/pull/29104)). Contributed by @t3chguy.
+* Add `Forgot recovery key?` button to encryption tab ([#29202](https://github.com/element-hq/element-web/pull/29202)). Contributed by @florianduros.
+* Add KeyIcon to key storage out of sync toast ([#29201](https://github.com/element-hq/element-web/pull/29201)). Contributed by @florianduros.
+* Improve rendering of empty topics in the timeline  ([#29152](https://github.com/element-hq/element-web/pull/29152)). Contributed by @Half-Shot.
+
+## 🐛 Bug Fixes
+
+* Fix font scaling in member list ([#29285](https://github.com/element-hq/element-web/pull/29285)). Contributed by @florianduros.
+* Grow member list search field when resizing the right panel ([#29267](https://github.com/element-hq/element-web/pull/29267)). Contributed by @langleyd.
+* Don't reload roomview on offline connectivity check ([#29243](https://github.com/element-hq/element-web/pull/29243)). Contributed by @dbkr.
+* Respect user's 12/24 hour preference consistently ([#29237](https://github.com/element-hq/element-web/pull/29237)). Contributed by @t3chguy.
+* Restore the accessibility role on call views ([#29225](https://github.com/element-hq/element-web/pull/29225)). Contributed by @robintown.
+* Revert `GoToHome` keyboard shortcut to `Ctrl`–`Shift`–`H` on macOS ([#28577](https://github.com/element-hq/element-web/pull/28577)). Contributed by @gy-mate.
+* Encryption tab: display correct encryption panel when user cancels the reset identity flow ([#29216](https://github.com/element-hq/element-web/pull/29216)). Contributed by @florianduros.
+
+
+Changes in [1.11.92](https://github.com/element-hq/element-web/releases/tag/v1.11.92) (2025-02-11)
+==================================================================================================
+## ✨ Features
+
+* [Backport staging] Log when we show, and hide, encryption setup toasts ([#29238](https://github.com/element-hq/element-web/pull/29238)). Contributed by @richvdh.
+* Make profile header section match the designs ([#29163](https://github.com/element-hq/element-web/pull/29163)). Contributed by @MidhunSureshR.
+* Always show back button in the right panel ([#29128](https://github.com/element-hq/element-web/pull/29128)). Contributed by @MidhunSureshR.
+* Schedule dehydration on reload if the dehydration key is already cached locally ([#29021](https://github.com/element-hq/element-web/pull/29021)). Contributed by @uhoreg.
+* update to twemoji 15.1.0 ([#29115](https://github.com/element-hq/element-web/pull/29115)). Contributed by @ara4n.
+* Update matrix-widget-api ([#29112](https://github.com/element-hq/element-web/pull/29112)). Contributed by @toger5.
+* Allow navigating through the memberlist using up/down keys ([#28949](https://github.com/element-hq/element-web/pull/28949)). Contributed by @MidhunSureshR.
+* Style room header icons and facepile for toggled state ([#28968](https://github.com/element-hq/element-web/pull/28968)). Contributed by @MidhunSureshR.
+* Move threads header below base card header ([#28969](https://github.com/element-hq/element-web/pull/28969)). Contributed by @MidhunSureshR.
+* Add `Advanced` section to the user settings encryption tab ([#28804](https://github.com/element-hq/element-web/pull/28804)). Contributed by @florianduros.
+* Fix outstanding UX issues with replies/mentions/keyword notifs ([#28270](https://github.com/element-hq/element-web/pull/28270)). Contributed by @taffyko.
+* Distinguish room state and timeline events when dealing with widgets ([#28681](https://github.com/element-hq/element-web/pull/28681)). Contributed by @robintown.
+* Switch OIDC primarily to new `/auth_metadata` API ([#29019](https://github.com/element-hq/element-web/pull/29019)). Contributed by @t3chguy.
+* More memberlist changes ([#29069](https://github.com/element-hq/element-web/pull/29069)). Contributed by @MidhunSureshR.
+
+## 🐛 Bug Fixes
+
+* [Backport staging] Wire up the "Forgot recovery key" button for the "Key storage out of sync" toast ([#29190](https://github.com/element-hq/element-web/pull/29190)). Contributed by @RiotRobot.
+* Encryption tab: hide `Advanced` section when the key storage is out of sync ([#29129](https://github.com/element-hq/element-web/pull/29129)). Contributed by @florianduros.
+* Fix share button in discovery settings being disabled incorrectly ([#29151](https://github.com/element-hq/element-web/pull/29151)). Contributed by @t3chguy.
+* Ensure switching rooms does not wrongly focus timeline search ([#29153](https://github.com/element-hq/element-web/pull/29153)). Contributed by @t3chguy.
+* Stop showing a dialog prompting the user to enter an old recovery key ([#29143](https://github.com/element-hq/element-web/pull/29143)). Contributed by @richvdh.
+* Make themed widgets reflect the effective theme ([#28342](https://github.com/element-hq/element-web/pull/28342)). Contributed by @robintown.
+* support non-VS16 emoji ligatures in TwemojiMozilla ([#29100](https://github.com/element-hq/element-web/pull/29100)). Contributed by @ara4n.
+* e2e test: Verify session with the encryption tab instead of the security \& privacy tab ([#29090](https://github.com/element-hq/element-web/pull/29090)). Contributed by @florianduros.
+* Work around cloudflare R2 / aws client incompatability ([#29086](https://github.com/element-hq/element-web/pull/29086)). Contributed by @dbkr.
+* Fix identity server settings visibility ([#29083](https://github.com/element-hq/element-web/pull/29083)). Contributed by @dbkr.
+
+
+Changes in [1.11.91](https://github.com/element-hq/element-web/releases/tag/v1.11.91) (2025-01-28)
+==================================================================================================
+## ✨ Features
+
+* Implement changes to memberlist from feedback ([#29029](https://github.com/element-hq/element-web/pull/29029)). Contributed by @MidhunSureshR.
+* Add toast for recovery keys being out of sync ([#28946](https://github.com/element-hq/element-web/pull/28946)). Contributed by @dbkr.
+* Refactor LegacyCallHandler event emitter to use TypedEventEmitter ([#29008](https://github.com/element-hq/element-web/pull/29008)). Contributed by @t3chguy.
+* Add `Recovery` section in the new user settings `Encryption` tab ([#28673](https://github.com/element-hq/element-web/pull/28673)). Contributed by @florianduros.
+* Retry loading chunks to make the app more resilient ([#29001](https://github.com/element-hq/element-web/pull/29001)). Contributed by @t3chguy.
+* Clear account idb table on logout ([#28996](https://github.com/element-hq/element-web/pull/28996)). Contributed by @t3chguy.
+* Implement new memberlist design with MVVM architecture  ([#28874](https://github.com/element-hq/element-web/pull/28874)). Contributed by @MidhunSureshR.
+
+## 🐛 Bug Fixes
+
+* [Backport staging] Switch to secure random strings ([#29035](https://github.com/element-hq/element-web/pull/29035)). Contributed by @RiotRobot.
+* React to MatrixEvent sender/target being updated for rendering state events ([#28947](https://github.com/element-hq/element-web/pull/28947)). Contributed by @t3chguy.
+
+
 Changes in [1.11.90](https://github.com/element-hq/element-web/releases/tag/v1.11.90) (2025-01-14)
 ==================================================================================================
 ## ✨ Features
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fa887929fb683e434f649a785b3e7de6486510f0..fd94f18f85902707aebeab9aaee54a577db279f5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -189,89 +189,6 @@ give away to contributors - if you feel that Matrix-branded apparel is missing
 from your life, please mail us your shipping address to matrix at matrix.org
 and we'll try to fix it :)
 
-## Sign off
-
-In order to have a concrete record that your contribution is intentional
-and you agree to license it under the same terms as the project's license, we've
-adopted the same lightweight approach that the Linux Kernel
-(https://www.kernel.org/doc/html/latest/process/submitting-patches.html), Docker
-(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
-projects use: the DCO (Developer Certificate of Origin:
-http://developercertificate.org/). This is a simple declaration that you wrote
-the contribution or otherwise have the right to contribute it to Matrix:
-
-```
-Developer Certificate of Origin
-Version 1.1
-
-Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
-660 York Street, Suite 102,
-San Francisco, CA 94110 USA
-
-Everyone is permitted to copy and distribute verbatim copies of this
-license document, but changing it is not allowed.
-
-Developer's Certificate of Origin 1.1
-
-By making a contribution to this project, I certify that:
-
-(a) The contribution was created in whole or in part by me and I
-    have the right to submit it under the open source license
-    indicated in the file; or
-
-(b) The contribution is based upon previous work that, to the best
-    of my knowledge, is covered under an appropriate open source
-    license and I have the right under that license to submit that
-    work with modifications, whether created in whole or in part
-    by me, under the same open source license (unless I am
-    permitted to submit under a different license), as indicated
-    in the file; or
-
-(c) The contribution was provided directly to me by some other
-    person who certified (a), (b) or (c) and I have not modified
-    it.
-
-(d) I understand and agree that this project and the contribution
-    are public and that a record of the contribution (including all
-    personal information I submit with it, including my sign-off) is
-    maintained indefinitely and may be redistributed consistent with
-    this project or the open source license(s) involved.
-```
-
-If you agree to this for your contribution, then all that's needed is to
-include the line in your commit or pull request comment:
-
-```
-Signed-off-by: Your Name <your@email.example.org>
-```
-
-We accept contributions under a legally identifiable name, such as your name on
-government documentation or common-law names (names claimed by legitimate usage
-or repute). Unfortunately, we cannot accept anonymous contributions at this
-time.
-
-Git allows you to add this signoff automatically when using the `-s` flag to
-`git commit`, which uses the name and email set in your `user.name` and
-`user.email` git configs.
-
-If you forgot to sign off your commits before making your pull request and are
-on Git 2.17+ you can mass signoff using rebase:
-
-```
-git rebase --signoff origin/develop
-```
-
-## Private sign off
-
-If you would like to provide your legal name privately to the Matrix.org
-Foundation (instead of in a public commit or comment), you can do so by emailing
-your legal name and a link to the pull request to dco@matrix.org. It helps to
-include "sign off" or similar in the subject line. You will then be instructed
-further.
-
-Once private sign off is complete, doing so for future contributions will not
-be required.
-
 # Review expectations
 
 See https://github.com/element-hq/element-meta/wiki/Review-process
diff --git a/Dockerfile b/Dockerfile
index 93d7c676d9fca36f7fcdc5bd7472192a5414f513..a1813cf66de844a046f38bb17254dff1ecfa6e54 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,7 @@
+# syntax=docker.io/docker/dockerfile:1.16-labs@sha256:bb5e2b225985193779991f3256d1901a0b3e6a0b284c7bffa0972064f4a6d458
+
 # Builder
-FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder
+FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f16d8e8af67bb6361231e932b8b3e7afa040cbfed181719a450b02c3821b26c1 AS builder
 
 # Support custom branch of the js-sdk. This also helps us build images of element-web develop.
 ARG USE_CUSTOM_SDKS=false
@@ -8,7 +10,7 @@ ARG JS_SDK_BRANCH="master"
 
 WORKDIR /src
 
-COPY . /src
+COPY --exclude=docker . /src
 RUN /src/scripts/docker-link-repos.sh
 RUN yarn --network-timeout=200000 install
 RUN /src/scripts/docker-package.sh
@@ -17,20 +19,20 @@ RUN /src/scripts/docker-package.sh
 RUN cp /src/config.sample.json /src/webapp/config.json
 
 # App
-FROM nginx:alpine-slim
+FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:2acffd86b1bdefb8fa6b48b6e9aadf75430e8ab9c43c54c515ea7df77897f987
+
+# Need root user to install packages & manipulate the usr directory
+USER root
+
+# Install jq and moreutils for sponge, both used by our entrypoints
+RUN apk add jq moreutils
 
 COPY --from=builder /src/webapp /app
 
 # Override default nginx config. Templates in `/etc/nginx/templates` are passed
 # through `envsubst` by the nginx docker image entry point.
 COPY /docker/nginx-templates/* /etc/nginx/templates/
-
-# Tell nginx to put its pidfile elsewhere, so it can run as non-root
-RUN sed -i -e 's,/var/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf
-
-# nginx user must own the cache and etc directory to write cache and tweak the nginx config
-RUN chown -R nginx:0 /var/cache/nginx /etc/nginx
-RUN chmod -R g+w /var/cache/nginx /etc/nginx
+COPY /docker/docker-entrypoint.d/* /docker-entrypoint.d/
 
 RUN rm -rf /usr/share/nginx/html \
   && ln -s /app /usr/share/nginx/html
@@ -40,3 +42,5 @@ USER nginx
 
 # HTTP listen port
 ENV ELEMENT_WEB_PORT=80
+
+HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json
diff --git a/README.md b/README.md
index 5924f9349818523d852110dbcfbbea871fa6467e..0f8a721f903f1c600bf285e69a4bd9fb97ea6dce 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ guide](https://classic.yarnpkg.com/en/docs/install) if you do not have it alread
 1. Install the prerequisites: `yarn install`.
     - If you're using the `develop` branch, then it is recommended to set up a
       proper development environment (see [Setting up a dev
-      environment](#setting-up-a-dev-environment) below). Alternatively, you
+      environment](./developer_guide.md#setting-up-a-dev-environment) below). Alternatively, you
       can use <https://develop.element.io> - the continuous integration release of
       the develop branch.
 1. Configure the app by copying `config.sample.json` to `config.json` and
@@ -182,123 +182,11 @@ Dockerfile.
 
 # Development
 
-Before attempting to develop on Element you **must** read the [developer guide
-for `matrix-react-sdk`](https://github.com/matrix-org/matrix-react-sdk#developer-guide), which
-also defines the design, architecture and style for Element too.
+Please read through the following:
 
-Read the [Choosing an issue](docs/choosing-an-issue.md) page for some guidance
-about where to start. Before starting work on a feature, it's best to ensure
-your plan aligns well with our vision for Element. Please chat with the team in
-[#element-dev:matrix.org](https://matrix.to/#/#element-dev:matrix.org) before
-you start so we can ensure it's something we'd be willing to merge.
-
-You should also familiarise yourself with the ["Here be Dragons" guide
-](https://docs.google.com/document/d/12jYzvkidrp1h7liEuLIe6BMdU0NUjndUYI971O06ooM)
-to the tame & not-so-tame dragons (gotchas) which exist in the codebase.
-
-The idea of Element is to be a relatively lightweight "skin" of customisations on
-top of the underlying `matrix-react-sdk`. `matrix-react-sdk` provides both the
-higher and lower level React components useful for building Matrix communication
-apps using React.
-
-Please note that Element is intended to run correctly without access to the public
-internet. So please don't depend on resources (JS libs, CSS, images, fonts)
-hosted by external CDNs or servers but instead please package all dependencies
-into Element itself.
-
-# Setting up a dev environment
-
-Much of the functionality in Element is actually in the `matrix-js-sdk` module.
-It is possible to set these up in a way that makes it easy to track the `develop` branches
-in git and to make local changes without having to manually rebuild each time.
-
-First clone and build `matrix-js-sdk`:
-
-```bash
-git clone https://github.com/matrix-org/matrix-js-sdk.git
-pushd matrix-js-sdk
-yarn link
-yarn install
-popd
-```
-
-Clone the repo and switch to the `element-web` directory:
-
-```bash
-git clone https://github.com/element-hq/element-web.git
-cd element-web
-```
-
-Configure the app by copying `config.sample.json` to `config.json` and
-modifying it. See the [configuration docs](docs/config.md) for details.
-
-Finally, build and start Element itself:
-
-```bash
-yarn link matrix-js-sdk
-yarn install
-yarn start
-```
-
-Wait a few seconds for the initial build to finish; you should see something like:
-
-```
-[element-js] <s> [webpack.Progress] 100%
-[element-js]
-[element-js] ℹ 「wdm」:    1840 modules
-[element-js] ℹ 「wdm」: Compiled successfully.
-```
-
-Remember, the command will not terminate since it runs the web server
-and rebuilds source files when they change. This development server also
-disables caching, so do NOT use it in production.
-
-Open <http://127.0.0.1:8080/> in your browser to see your newly built Element.
-
-**Note**: The build script uses inotify by default on Linux to monitor directories
-for changes. If the inotify limits are too low your build will fail silently or with
-`Error: EMFILE: too many open files`. To avoid these issues, we recommend a watch limit
-of at least `128M` and instance limit around `512`.
-
-You may be interested in issues [#15750](https://github.com/element-hq/element-web/issues/15750) and
-[#15774](https://github.com/element-hq/element-web/issues/15774) for further details.
-
-To set a new inotify watch and instance limit, execute:
-
-```
-sudo sysctl fs.inotify.max_user_watches=131072
-sudo sysctl fs.inotify.max_user_instances=512
-sudo sysctl -p
-```
-
-If you wish, you can make the new limits permanent, by executing:
-
-```
-echo fs.inotify.max_user_watches=131072 | sudo tee -a /etc/sysctl.conf
-echo fs.inotify.max_user_instances=512 | sudo tee -a /etc/sysctl.conf
-sudo sysctl -p
-```
-
----
-
-When you make changes to `matrix-js-sdk` they should be automatically picked up by webpack and built.
-
-If any of these steps error with, `file table overflow`, you are probably on a mac
-which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
-You'll need to do this in each new terminal you open before building Element.
-
-## Running the tests
-
-There are a number of application-level tests in the `tests` directory; these
-are designed to run with Jest and JSDOM. To run them
-
-```
-yarn test
-```
-
-### End-to-End tests
-
-See [matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/#end-to-end-tests) for how to run the end-to-end tests.
+1. [Developer guide](./developer_guide.md)
+2. [Code style](./code_style.md)
+3. [Contribution guide](./CONTRIBUTING.md)
 
 # Translations
 
diff --git a/babel.config.js b/babel.config.js
index b63a90e5ff757414aeb925cc4329cb475407f452..58df067b7951efa66bbdfc8350cf728c9610f3e6 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -31,5 +31,7 @@ module.exports = {
 
         "@babel/plugin-syntax-dynamic-import",
         "@babel/plugin-transform-runtime",
+        ["@babel/plugin-proposal-decorators", { version: "2023-11" }], // only needed by the js-sdk
+        "@babel/plugin-transform-class-static-block", // only needed by the js-sdk for decorators
     ],
 };
diff --git a/code_style.md b/code_style.md
index 9aa6836442f83a06b627c16b4ee10c12aa27dc3a..9f0501ccd88cd842dbc337b3471dfc2267059b3d 100644
--- a/code_style.md
+++ b/code_style.md
@@ -5,15 +5,6 @@ adjacent to. As of writing, these are:
 
 - element-desktop
 - element-web
-- matrix-js-sdk
-
-Other projects might extend this code style for increased strictness. For example, matrix-events-sdk
-has stricter code organization to reduce the maintenance burden. These projects will declare their code
-style within their own repos.
-
-Note that some requirements will be layer-specific. Where the requirements don't make sense for the
-project, they are used to the best of their ability, used in spirit, or ignored if not applicable,
-in that order.
 
 ## Guiding principles
 
@@ -234,17 +225,19 @@ Unless otherwise specified, the following applies to all code:
 
 Inheriting all the rules of TypeScript, the following additionally apply:
 
-1. Types for lifecycle functions are not required (render, componentDidMount, and so on).
-2. Class components must always have a `Props` interface declared immediately above them. It can be
+1. Component source files are named with upper camel case (e.g. views/rooms/EventTile.js)
+2. They are organised in a typically two-level hierarchy - first whether the component is a view or a structure, and then a broad functional grouping (e.g. 'rooms' here)
+3. Types for lifecycle functions are not required (render, componentDidMount, and so on).
+4. Class components must always have a `Props` interface declared immediately above them. It can be
    empty if the component accepts no props.
-3. Class components should have an `State` interface declared immediately above them, but after `Props`.
-4. Props and State should not be exported. Use `React.ComponentProps<typeof ComponentNameHere>`
+5. Class components should have an `State` interface declared immediately above them, but after `Props`.
+6. Props and State should not be exported. Use `React.ComponentProps<typeof ComponentNameHere>`
    instead.
-5. One component per file, except when a component is a utility component specifically for the "primary"
+7. One component per file, except when a component is a utility component specifically for the "primary"
    component. The utility component should not be exported.
-6. Exported constants, enums, interfaces, functions, etc must be separate from files containing components
+8. Exported constants, enums, interfaces, functions, etc must be separate from files containing components
    or stores.
-7. Stores should use a singleton pattern with a static instance property:
+9. Stores should use a singleton pattern with a static instance property:
 
     ```typescript
     class FooStore {
@@ -261,44 +254,41 @@ Inheriting all the rules of TypeScript, the following additionally apply:
     }
     ```
 
-8. Stores must support using an alternative MatrixClient and dispatcher instance.
-9. Utilities which require JSX must be split out from utilities which do not. This is to prevent import
-   cycles during runtime where components accidentally include more of the app than they intended.
-10. Interdependence between stores should be kept to a minimum. Break functions and constants out to utilities
+10. Stores must support using an alternative MatrixClient and dispatcher instance.
+11. Utilities which require JSX must be split out from utilities which do not. This is to prevent import
+    cycles during runtime where components accidentally include more of the app than they intended.
+12. Interdependence between stores should be kept to a minimum. Break functions and constants out to utilities
     if at all possible.
-11. A component should only use CSS class names in line with the component name.
+13. A component should only use CSS class names in line with the component name.
 
     1. When knowingly using a class name from another component, document it with a [comment](#comments).
 
-12. Curly braces within JSX should be padded with a space, however properties on those components should not.
+14. Curly braces within JSX should be padded with a space, however properties on those components should not.
     See above code example.
-13. Functions used as properties should either be defined on the class or stored in a variable. They should not
+15. Functions used as properties should either be defined on the class or stored in a variable. They should not
     be inline unless mocking/short-circuiting the value.
-14. Prefer hooks (functional components) over class components. Be consistent with the existing area if unsure
+16. Prefer hooks (functional components) over class components. Be consistent with the existing area if unsure
     which should be used.
     1. Unless the component is considered a "structure", in which case use classes.
-15. Write more views than structures. Structures are chunks of functionality like MatrixChat while views are
+17. Write more views than structures. Structures are chunks of functionality like MatrixChat while views are
     isolated components.
-16. Components should serve a single, or near-single, purpose.
-17. Prefer to derive information from component properties rather than establish state.
-18. Do not use `React.Component::forceUpdate`.
+18. Components should serve a single, or near-single, purpose.
+19. Prefer to derive information from component properties rather than establish state.
+20. Do not use `React.Component::forceUpdate`.
 
 ## Stylesheets (\*.pcss = PostCSS + Plugins)
 
 Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, but actually it is not.
 
-1. Class names must be prefixed with "mx\_".
-2. Class names must denote the component which defines them, followed by any context.
-   The context is not further specified here in terms of meaning or syntax.
-   Use whatever is appropriate for your implementation use case.
-   Some examples:
-    1. `mx_MyFoo`
-    2. `mx_MyFoo_avatar`
-    3. `mx_MyFoo_avatarUser`
-    4. `mx_MyFoo_avatar--user`
-3. Use the `$font` variables instead of manual values.
-4. Keep indentation/nesting to a minimum. Maximum suggested nesting is 5 layers.
-5. Use the whole class name instead of shortcuts:
+1. The view's CSS file MUST have the same name as the component (e.g. `view/rooms/_MessageTile.css` for `MessageTile.tsx` component).
+2. Per-view CSS is optional - it could choose to inherit all its styling from the context of the rest of the app, although this is unusual.
+3. Class names must be prefixed with "mx\_".
+4. Class names must strictly denote the component which defines them.
+   For example: `mx_MyFoo` for `MyFoo` component.
+5. Class names for DOM elements within a view which aren't components are named by appending a lower camel case identifier to the view's class name - e.g. .mx_MyFoo_randomDiv is how you'd name the class of an arbitrary div within the MyFoo view.
+6. Use the `$font` variables instead of manual values.
+7. Keep indentation/nesting to a minimum. Maximum suggested nesting is 5 layers.
+8. Use the whole class name instead of shortcuts:
 
     ```scss
     .mx_MyFoo {
@@ -309,7 +299,7 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
     }
     ```
 
-6. Break multiple selectors over multiple lines this way:
+9. Break multiple selectors over multiple lines this way:
 
     ```scss
     .mx_MyFoo,
@@ -319,9 +309,9 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
     }
     ```
 
-7. Non-shared variables should use $lowerCamelCase. Shared variables use $dashed-naming.
-8. Overrides to Z indexes, adjustments of dimensions/padding with pixels, and so on should all be
-   [documented](#comments) for what the values mean:
+10. Non-shared variables should use $lowerCamelCase. Shared variables use $dashed-naming.
+11. Overrides to Z indexes, adjustments of dimensions/padding with pixels, and so on should all be
+    [documented](#comments) for what the values mean:
 
     ```scss
     .mx_MyFoo {
@@ -331,7 +321,9 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
     }
     ```
 
-9. Avoid the use of `!important`. If `!important` is necessary, add a [comment](#comments) explaining why.
+12. Avoid the use of `!important`. If `!important` is necessary, add a [comment](#comments) explaining why.
+13. The CSS for a component can override the rules for child components. For instance, .mxRoomList .mx_RoomTile {} would be the selector to override styles of RoomTiles when viewed in the context of a RoomList view. Overrides must be scoped to the View's CSS class - i.e. don't just define .mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override only to the context of RoomList views. N.B. overrides should be relatively rare as in general CSS inheritance should be enough.
+14. Components should render only within the bounding box of their outermost DOM element. Page-absolute positioning and negative CSS margins and similar are generally not cool and stop the component from being reused easily in different places.
 
 ## Tests
 
diff --git a/contribute.json b/contribute.json
deleted file mode 100644
index fdd84bd8a049ed1245b7e5b82f0cb4f955e2b933..0000000000000000000000000000000000000000
--- a/contribute.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "name": "Element",
-    "description": "A glossy Matrix collaboration client for the web.",
-    "repository": {
-        "url": "https://github.com/element-hq/element-web",
-        "license": "AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial"
-    },
-    "bugs": {
-        "list": "https://github.com/element-hq/element-web/issues",
-        "report": "https://github.com/element-hq/element-web/issues/new/choose"
-    },
-    "keywords": ["chat", "riot", "matrix"]
-}
diff --git a/debian/control b/debian/control
index 158c3ada1744dfaee6b56abc9d172234945bd26f..506ae1eb3325284c94c4ee9732bbbf5e12db31e0 100755
--- a/debian/control
+++ b/debian/control
@@ -8,6 +8,6 @@ Package: element-web
 Architecture: all
 Recommends: httpd, element-io-archive-keyring
 Description:
-  A feature-rich client for Matrix.org
+  Element: the future of secure communication
   This package contains the web-based client that can be served through a web
    server.
diff --git a/developer_guide.md b/developer_guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..fa4bb9a239fc79017055b28733c55a91e53e11d5
--- /dev/null
+++ b/developer_guide.md
@@ -0,0 +1,126 @@
+# Developer Guide
+
+## Development
+
+Read the [Choosing an issue](docs/choosing-an-issue.md) page for some guidance
+about where to start. Before starting work on a feature, it's best to ensure
+your plan aligns well with our vision for Element. Please chat with the team in
+[#element-dev:matrix.org](https://matrix.to/#/#element-dev:matrix.org) before
+you start so we can ensure it's something we'd be willing to merge.
+
+You should also familiarise yourself with the ["Here be Dragons" guide
+](https://docs.google.com/document/d/12jYzvkidrp1h7liEuLIe6BMdU0NUjndUYI971O06ooM)
+to the tame & not-so-tame dragons (gotchas) which exist in the codebase.
+
+Please note that Element is intended to run correctly without access to the public
+internet. So please don't depend on resources (JS libs, CSS, images, fonts)
+hosted by external CDNs or servers but instead please package all dependencies
+into Element itself.
+
+## Setting up a dev environment
+
+Much of the functionality in Element is actually in the `matrix-js-sdk` module.
+It is possible to set these up in a way that makes it easy to track the `develop` branches
+in git and to make local changes without having to manually rebuild each time.
+
+First clone and build `matrix-js-sdk`:
+
+```bash
+git clone https://github.com/matrix-org/matrix-js-sdk.git
+pushd matrix-js-sdk
+yarn link
+yarn install
+popd
+```
+
+Clone the repo and switch to the `element-web` directory:
+
+```bash
+git clone https://github.com/element-hq/element-web.git
+cd element-web
+```
+
+Configure the app by copying `config.sample.json` to `config.json` and
+modifying it. See the [configuration docs](docs/config.md) for details.
+
+Finally, build and start Element itself:
+
+```bash
+yarn link matrix-js-sdk
+yarn install
+yarn start
+```
+
+Wait a few seconds for the initial build to finish; you should see something like:
+
+```
+[element-js] <s> [webpack.Progress] 100%
+[element-js]
+[element-js] ℹ 「wdm」:    1840 modules
+[element-js] ℹ 「wdm」: Compiled successfully.
+```
+
+Remember, the command will not terminate since it runs the web server
+and rebuilds source files when they change. This development server also
+disables caching, so do NOT use it in production.
+
+Open <http://127.0.0.1:8080/> in your browser to see your newly built Element.
+
+**Note**: The build script uses inotify by default on Linux to monitor directories
+for changes. If the inotify limits are too low your build will fail silently or with
+`Error: EMFILE: too many open files`. To avoid these issues, we recommend a watch limit
+of at least `128M` and instance limit around `512`.
+
+You may be interested in issues [#15750](https://github.com/element-hq/element-web/issues/15750) and
+[#15774](https://github.com/element-hq/element-web/issues/15774) for further details.
+
+To set a new inotify watch and instance limit, execute:
+
+```
+sudo sysctl fs.inotify.max_user_watches=131072
+sudo sysctl fs.inotify.max_user_instances=512
+sudo sysctl -p
+```
+
+If you wish, you can make the new limits permanent, by executing:
+
+```
+echo fs.inotify.max_user_watches=131072 | sudo tee -a /etc/sysctl.conf
+echo fs.inotify.max_user_instances=512 | sudo tee -a /etc/sysctl.conf
+sudo sysctl -p
+```
+
+---
+
+When you make changes to `matrix-js-sdk` they should be automatically picked up by webpack and built.
+
+If any of these steps error with, `file table overflow`, you are probably on a mac
+which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
+You'll need to do this in each new terminal you open before building Element.
+
+## Running the tests
+
+There are a number of application-level tests in the `tests` directory; these
+are designed to run with Jest and JSDOM. To run them
+
+```
+yarn test
+```
+
+### End-to-End tests
+
+See [matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/#end-to-end-tests) for how to run the end-to-end tests.
+
+## General github guidelines
+
+1. **Pull requests must only be filed against the `develop` branch.**
+2. Try to keep your pull requests concise. Split them up if necessary.
+3. Ensure that you provide a description that explains the fix/feature and its intent.
+
+## Adding new code
+
+New code should be committed as follows:
+
+- All new components: https://github.com/element-hq/element-web/tree/develop/src/components
+- CSS: https://github.com/element-hq/element-web/tree/develop/res/css
+- Theme specific CSS & resources: https://github.com/element-hq/element-web/tree/develop/res/themes
diff --git a/docker/docker-entrypoint.d/18-load-element-modules.sh b/docker/docker-entrypoint.d/18-load-element-modules.sh
new file mode 100755
index 0000000000000000000000000000000000000000..235c4edcf250c5380a5da2f34bf3a642c9db8c99
--- /dev/null
+++ b/docker/docker-entrypoint.d/18-load-element-modules.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# Loads modules from `/modules` into config.json's `modules` field
+
+set -e
+
+entrypoint_log() {
+    if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
+        echo "$@"
+    fi
+}
+
+# Copy these config files as a base
+mkdir -p /tmp/element-web-config
+cp /app/config*.json /tmp/element-web-config/
+
+# If there are modules to be loaded
+if [ -d "/modules" ]; then
+    cd /modules
+
+    for MODULE in *
+    do
+        # If the module has a package.json, use its main field as the entrypoint
+        ENTRYPOINT="index.js"
+        if [ -f "/modules/$MODULE/package.json" ]; then
+            ENTRYPOINT=$(jq -r '.main' "/modules/$MODULE/package.json")
+        fi
+
+        entrypoint_log "Loading module $MODULE with entrypoint $ENTRYPOINT"
+
+        # Append the module to the config
+        jq ".modules += [\"/modules/$MODULE/$ENTRYPOINT\"]" /tmp/element-web-config/config.json | sponge /tmp/element-web-config/config.json
+    done
+fi
diff --git a/docker/nginx-templates/default.conf.template b/docker/nginx-templates/default.conf.template
index 06f33e08dd2a3025405b5f5c9d0a7cffb85885ce..b4690ce146f338bbc9d90c4e1f2c08a505be61b9 100644
--- a/docker/nginx-templates/default.conf.template
+++ b/docker/nginx-templates/default.conf.template
@@ -18,8 +18,12 @@ server {
     }
     # covers config.json and config.hostname.json requests as it is prefix.
     location /config {
+        root /tmp/element-web-config;
         add_header Cache-Control "no-cache";
     }
+    location /modules {
+        alias /modules;
+    }
     # redirect server error pages to the static page /50x.html
     #
     error_page   500 502 503 504  /50x.html;
diff --git a/docs/MVVM.md b/docs/MVVM.md
new file mode 100644
index 0000000000000000000000000000000000000000..9dfb8e477643db9d263404626f208af419ecae05
--- /dev/null
+++ b/docs/MVVM.md
@@ -0,0 +1,67 @@
+# MVVM
+
+General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
+
+1. Model: This is where the business logic and data resides.
+2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
+3. View: This is the UI code itself and depends on the view model.
+
+If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
+
+### Practical guidelines for MVVM in element-web
+
+#### Model
+
+This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
+
+#### View Model
+
+1. View model is always a custom react hook named like `useFooViewModel()`.
+2. The return type of your view model (known as view state) must be defined as a typescript interface:
+    ```ts
+    inteface FooViewState {
+    	somethingUseful: string;
+    	somethingElse: BarType;
+    	update: () => Promise<void>
+    	...
+    }
+    ```
+3. Any react state that your UI needs must be in the view model.
+
+#### View
+
+1. Views are simple react components (eg: `FooView`).
+2. Views usually start by calling the view model hook, eg:
+    ```tsx
+    const FooView: React.FC<IProps> = (props: IProps) => {
+    	const vm = useFooViewModel();
+    	....
+    	return(
+    		<div>
+    			{vm.somethingUseful}
+    		</div>
+    	);
+    }
+    ```
+3. Views are also allowed to accept the view model as a prop, eg:
+    ```tsx
+    const FooView: React.FC<IProps> = ({ vm }: IProps) => {
+    	....
+    	return(
+    		<div>
+    			{vm.somethingUseful}
+    		</div>
+    	);
+    }
+    ```
+4. Multiple views can share the same view model if necessary.
+
+### Benefits
+
+1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
+2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
+3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
+
+### Example
+
+We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 57b017bc1c6453f64fadab73b4a9d3080136cb8e..de93ce2812ddbc56fac70b4f18e24a9f4ea356f6 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -46,7 +46,6 @@
 - [Skinning](skinning.md)
 - [Cider editor](ciderEditor.md)
 - [Iconography](icons.md)
-- [Jitsi](jitsi.md)
 - [Local echo](local-echo-dev.md)
 - [Media](media-handling.md)
 - [Room List Store](room-list-store.md)
diff --git a/docs/config.md b/docs/config.md
index 8ca4ba4eb8b5545ef42f8dde5b8c231d17fcd0a9..f1ced14fd2844cb46750e91681d184080a5a525a 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -155,7 +155,7 @@ complete re-branding/private labeling, a more personalised experience can be ach
     3. `show_once`: Optional. If true then the notice will only be shown once per device.
 18. `help_url`: The URL to point users to for help with the app, defaults to `https://element.io/help`.
 19. `help_encryption_url`: The URL to point users to for help with encryption, defaults to `https://element.io/help#encryption`.
-20. `force_verification`: If true, users must verify new logins (eg. with another device / their security key)
+20. `force_verification`: If true, users must verify new logins (eg. with another device / their recovery key)
 
 ### `desktop_builds` and `mobile_builds`
 
@@ -163,14 +163,14 @@ These two options describe the various availability for the application. When th
 such as trying to get the user to use an Android app or the desktop app for encrypted search, the config options will be looked
 at to see if the link should be to somewhere else.
 
-Starting with `desktop_builds`, the following subproperties are available:
+Starting with `desktop_builds`, the following sub-properties are available:
 
 1. `available`: Required. When `true`, the desktop app can be downloaded from somewhere.
 2. `logo`: Required. A URL to a logo (SVG), intended to be shown at 24x24 pixels.
 3. `url`: Required. The download URL for the app. This is used as a hyperlink.
 4. `url_macos`: Optional. Direct link to download macOS desktop app.
-5. `url_win32`: Optional. Direct link to download Windows 32-bit desktop app.
-6. `url_win64`: Optional. Direct link to download Windows 64-bit desktop app.
+5. `url_win64`: Optional. Direct link to download Windows x86 64-bit desktop app.
+6. `url_win64arm`: Optional. Direct link to download Windows ARM 64-bit desktop app.
 7. `url_linux`: Optional. Direct link to download Linux desktop app.
 
 When `desktop_builds` is not specified at all, the app will assume desktop downloads are available from https://element.io
@@ -384,8 +384,6 @@ The VoIP and Jitsi options are:
 5. `audio_stream_url`: Optional URL to pass to Jitsi to enable live streaming. This option is considered experimental and may be removed
    at any time without notice.
 6. `element_call`: Optional configuration for native group calls using Element Call, with the following subkeys:
-    - `url`: The URL of the Element Call instance to use for native group calls. This option is considered experimental
-      and may be removed at any time without notice. Defaults to `https://call.element.io`.
     - `use_exclusively`: A boolean specifying whether Element Call should be used exclusively as the only VoIP stack in
       the app, removing the ability to start legacy 1:1 calls or Jitsi calls. Defaults to `false`.
     - `participant_limit`: The maximum number of users who can join a call; if
@@ -592,3 +590,4 @@ The following are undocumented or intended for developer use only.
 2. `sync_timeline_limit`
 3. `dangerously_allow_unsafe_and_insecure_passwords`
 4. `latex_maths_delims`: An optional setting to override the default delimiters used for maths parsing. See https://github.com/matrix-org/matrix-react-sdk/pull/5939 for details. Only used when `feature_latex_maths` is enabled.
+5. `modules`: An optional list of modules to load. This is used for testing and development purposes only.
diff --git a/docs/install.md b/docs/install.md
index f6bd98611cbedb4aadfb22f1a94626045bfc9748..5f9e6ddd2e65d49da59e4b838744af7d6105ca06 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -66,6 +66,18 @@ on other runtimes may require root privileges. To resolve this, either run the
 image as root (`docker run --user 0`) or, better, change the port that nginx
 listens on via the `ELEMENT_WEB_PORT` environment variable.
 
+[Element Web Modules](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api) can be dynamically loaded
+by being made available (e.g. via bind mount) in a directory within `/modules/`.
+The default entrypoint will be index.js in that directory but can be overridden if a package.json file is found with a `main` directive.
+These modules will be presented in a `/modules` subdirectory within the webroot, and automatically added to the config.json `modules` field.
+
+If you wish to use docker in read-only mode,
+you should follow the [upstream instructions](https://hub.docker.com/_/nginx#:~:text=Running%20nginx%20in%20read%2Donly%20mode)
+but additionally include the following directories:
+
+- /tmp/
+- /etc/nginx/conf.d/
+
 The behaviour of the docker image can be customised via the following
 environment variables:
 
diff --git a/docs/labs.md b/docs/labs.md
index cad9c5dd7c1e61b57141e21ce4ddc7d60f68ccd7..60f35dd4a46238a8f5bd39a6236d430023a6ac85 100644
--- a/docs/labs.md
+++ b/docs/labs.md
@@ -101,10 +101,6 @@ Under the hood this stops Element Web from adding the `perParticipantE2EE` flag
 
 This is useful while we experiment with encryption and to make calling compatible with platforms that don't use encryption yet.
 
-## Rich text in room topics (`feature_html_topic`) [In Development]
-
-Enables rendering of MD / HTML in room topics.
-
 ## Enable the notifications panel in the room header (`feature_notifications`)
 
 Unreliable in encrypted rooms.
@@ -112,3 +108,7 @@ Unreliable in encrypted rooms.
 ## Knock rooms (`feature_ask_to_join`) [In Development]
 
 Enables knock feature for rooms. This allows users to ask to join a room.
+
+## New room list (`feature_new_room_list`) [In Development]
+
+Enable the new room list that is currently in development.
diff --git a/docs/release.md b/docs/release.md
index b2c797b66b56e1bc60627d582114b537eca87cb8..26b507c737817fd57b6dd0c499b97d27801e9368 100644
--- a/docs/release.md
+++ b/docs/release.md
@@ -8,11 +8,13 @@
 
 #### develop
 
-The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable. It corresponds to the develop.element.io CD platform.
+The develop branch holds the very latest and greatest code we have to offer, as such it may be less stable.
+It is auto-deployed on every commit to element-web or matrix-js-sdk to develop.element.io via GitHub Actions `build_develop.yml`.
 
 #### staging
 
 The staging branch corresponds to the very latest release regardless of whether it is an RC or not. Deployed to staging.element.io manually.
+It is auto-deployed on every release of element-web to staging.element.io via GitHub Actions `deploy.yml`.
 
 #### master
 
@@ -126,7 +128,7 @@ flowchart TD
 
     subgraph Deploying
         D1[\Deploy staging.element.io/]
-        D2[\Check dockerhub/]
+        D2[\Check docker build/]
         D3[\Deploy app.element.io/]
         D4[\Check desktop package/]
 
@@ -211,11 +213,11 @@ switched back to the version of the dependency from the master branch to not lea
 # Deploying
 
 We ship the SDKs to npm, this happens as part of the release process.
-We ship Element Web to dockerhub, `*.element.io`, and packages.element.io.
+We ship Element Web to dockerhub, ghcr.io, `*.element.io`, and packages.element.io.
 We ship Element Desktop to packages.element.io.
 
-- [ ] Check that element-web has shipped to dockerhub
-- [ ] Deploy staging.element.io. [See docs.](https://handbook.element.io/books/element-web-team/page/deploying-appstagingelementio)
+- [ ] Check that element-web has shipped to dockerhub & ghcr.io
+- [ ] Check that the staging [deployment](https://github.com/element-hq/element-web/actions/workflows/deploy.yml) has completed successfully
 - [ ] Test staging.element.io
 
 For final releases additionally do these steps:
@@ -225,6 +227,9 @@ For final releases additionally do these steps:
 - [ ] Ensure Element Web package has shipped to packages.element.io
 - [ ] Ensure Element Desktop packages have shipped to packages.element.io
 
+If you need to roll back a deployment to staging.element.io,
+you can run the `deploy.yml` automation choosing an older tag which you wish to deploy.
+
 # Housekeeping
 
 We have some manual housekeeping to do in order to prepare for the next release.
diff --git a/jest.config.ts b/jest.config.ts
index b70b21bc979dd19914cbcdbf45e20ca744f3563e..ad31f2fecc40022429dd3936e606f13dd29e1803 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -38,6 +38,8 @@ const config: Config = {
         "^!!raw-loader!.*": "jest-raw-loader",
         "recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
         "^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
+        // Requires ESM which is incompatible with our current Jest setup
+        "^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
     },
     transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
     collectCoverageFrom: [
diff --git a/knip.ts b/knip.ts
index 17ad531332a0f9d7095c3dcc3d3ad94f5eaac28b..e129c11481916cbbe978d7454aec97319c848c77 100644
--- a/knip.ts
+++ b/knip.ts
@@ -19,6 +19,7 @@ export default {
     ignore: [
         // Keep for now
         "src/hooks/useLocalStorageState.ts",
+        "src/hooks/useTimeout.ts",
         "src/components/views/elements/InfoTooltip.tsx",
         "src/components/views/elements/StyledCheckbox.tsx",
     ],
@@ -39,6 +40,8 @@ export default {
         // Used by webpack
         "process",
         "util",
+        // Embedded into webapp
+        "@element-hq/element-call-embedded",
     ],
     ignoreBinaries: [
         // Used in scripts & workflows
diff --git a/module_system/installer.ts b/module_system/installer.ts
index 4e677b7d67414488f983591d770b4a4f7ba5cc8a..66df4e92b65ef06bd2d6c2546a32d168d764a92c 100644
--- a/module_system/installer.ts
+++ b/module_system/installer.ts
@@ -9,7 +9,7 @@ import * as fs from "fs";
 import * as childProcess from "child_process";
 import * as semver from "semver";
 
-import { BuildConfig } from "./BuildConfig";
+import { type BuildConfig } from "./BuildConfig";
 
 // This expects to be run from ./scripts/install.ts
 
@@ -23,10 +23,9 @@ const MODULES_TS_HEADER = `
  * You are not a salmon.
  */
 
-import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
 `;
 const MODULES_TS_DEFINITIONS = `
-export const INSTALLED_MODULES: RuntimeModule[] = [];
+export const INSTALLED_MODULES = [];
 `;
 
 export function installer(config: BuildConfig): void {
@@ -78,8 +77,8 @@ export function installer(config: BuildConfig): void {
             return; // hit the finally{} block before exiting
         }
 
-        // If we reach here, everything seems fine. Write modules.ts and log some output
-        // Note: we compile modules.ts in two parts for developer friendliness if they
+        // If we reach here, everything seems fine. Write modules.js and log some output
+        // Note: we compile modules.js in two parts for developer friendliness if they
         // happen to look at it.
         console.log("The following modules have been installed: ", installedModules);
         let modulesTsHeader = MODULES_TS_HEADER;
@@ -193,5 +192,5 @@ function isModuleVersionCompatible(ourApiVersion: string, moduleApiVersion: stri
 }
 
 function writeModulesTs(content: string): void {
-    fs.writeFileSync("./src/modules.ts", content, "utf-8");
+    fs.writeFileSync("./src/modules.js", content, "utf-8");
 }
diff --git a/package.json b/package.json
index 2fb13e438c3de7d73d3597d0d056a19280c5da70..5700077f658111269167675f7d5966265330e000 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
     "name": "element-web",
-    "version": "1.11.90",
-    "description": "A feature-rich client for Matrix.org",
+    "version": "1.11.101",
+    "description": "Element: the future of secure communication",
     "author": "New Vector Ltd.",
     "repository": {
         "type": "git",
@@ -22,8 +22,7 @@
         "LICENSE",
         "README.md",
         "AUTHORS.rst",
-        "package.json",
-        "contribute.json"
+        "package.json"
     ],
     "style": "bundle.css",
     "matrix_i18n_extra_translation_funcs": [
@@ -62,37 +61,40 @@
         "test": "jest",
         "test:playwright": "playwright test",
         "test:playwright:open": "yarn test:playwright --ui",
-        "test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run",
-        "test:playwright:screenshots:build": "docker build playwright -t element-web-playwright",
-        "test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright --grep @screenshot --project=Chrome",
+        "test:playwright:screenshots": "playwright-screenshots --project=Chrome",
         "coverage": "yarn test --coverage",
         "analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
-        "update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js"
+        "update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
+        "postinstall": "patch-package"
     },
     "resolutions": {
-        "@types/react": "18.3.18",
-        "@types/react-dom": "18.3.5",
-        "oidc-client-ts": "3.1.0",
+        "**/pretty-format/react-is": "19.1.0",
+        "@playwright/test": "1.52.0",
+        "@types/react": "19.1.6",
+        "@types/react-dom": "19.1.5",
+        "oidc-client-ts": "3.2.1",
         "jwt-decode": "4.0.0",
-        "caniuse-lite": "1.0.30001690",
+        "caniuse-lite": "1.0.30001718",
+        "testcontainers": "^11.0.0",
         "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
         "wrap-ansi": "npm:wrap-ansi@^7.0.0"
     },
     "dependencies": {
         "@babel/runtime": "^7.12.5",
+        "@element-hq/element-web-module-api": "1.0.0",
         "@fontsource/inconsolata": "^5",
         "@fontsource/inter": "^5",
         "@formatjs/intl-segmenter": "^11.5.7",
-        "@matrix-org/analytics-events": "^0.29.0",
-        "@matrix-org/emojibase-bindings": "^1.3.3",
+        "@matrix-org/analytics-events": "^0.29.2",
+        "@matrix-org/emojibase-bindings": "^1.3.4",
         "@matrix-org/react-sdk-module-api": "^2.4.0",
         "@matrix-org/spec": "^1.7.0",
-        "@sentry/browser": "^8.0.0",
+        "@sentry/browser": "^9.0.0",
         "@types/png-chunks-extract": "^1.0.2",
         "@types/react-virtualized": "^9.21.30",
-        "@vector-im/compound-design-tokens": "^2.1.0",
-        "@vector-im/compound-web": "^7.5.0",
-        "@vector-im/matrix-wysiwyg": "2.38.0",
+        "@vector-im/compound-design-tokens": "^4.0.0",
+        "@vector-im/compound-web": "^7.11.0",
+        "@vector-im/matrix-wysiwyg": "2.38.3",
         "@zxcvbn-ts/core": "^3.0.4",
         "@zxcvbn-ts/language-common": "^3.0.4",
         "@zxcvbn-ts/language-en": "^3.0.2",
@@ -106,6 +108,7 @@
         "css-tree": "^3.0.0",
         "diff-dom": "^5.0.0",
         "diff-match-patch": "^1.0.5",
+        "domutils": "^3.2.2",
         "emojibase-regex": "15.3.2",
         "escape-html": "^1.0.3",
         "file-saver": "^2.0.5",
@@ -114,15 +117,15 @@
         "glob-to-regexp": "^0.4.1",
         "highlight.js": "^11.3.1",
         "html-entities": "^2.0.0",
+        "html-react-parser": "^5.2.2",
         "is-ip": "^3.1.0",
         "js-xxhash": "^4.0.0",
         "jsrsasign": "^11.0.0",
         "jszip": "^3.7.0",
         "katex": "^0.16.0",
-        "linkify-element": "4.2.0",
-        "linkify-react": "4.2.0",
-        "linkify-string": "4.2.0",
-        "linkifyjs": "4.2.0",
+        "linkify-react": "4.3.1",
+        "linkify-string": "4.3.1",
+        "linkifyjs": "4.3.1",
         "lodash": "^4.17.21",
         "maplibre-gl": "^5.0.0",
         "matrix-encrypt-attachment": "^1.0.3",
@@ -135,21 +138,22 @@
         "opus-recorder": "^8.0.3",
         "pako": "^2.0.3",
         "png-chunks-extract": "^1.0.0",
-        "posthog-js": "1.157.2",
+        "posthog-js": "1.246.0",
         "qrcode": "1.5.4",
-        "re-resizable": "6.10.3",
-        "react": "^18.3.1",
+        "re-resizable": "6.11.2",
+        "react": "^19.0.0",
         "react-beautiful-dnd": "^13.1.0",
         "react-blurhash": "^0.3.0",
-        "react-dom": "^18.3.1",
+        "react-dom": "^19.0.0",
         "react-focus-lock": "^2.5.1",
+        "react-string-replace": "^1.1.1",
         "react-transition-group": "^4.4.1",
         "react-virtualized": "^9.22.5",
         "rfc4648": "^1.4.0",
         "sanitize-filename": "^1.6.3",
-        "sanitize-html": "2.14.0",
+        "sanitize-html": "2.17.0",
         "tar-js": "^0.3.0",
-        "temporal-polyfill": "^0.2.5",
+        "temporal-polyfill": "^0.3.0",
         "ua-parser-js": "^1.0.2",
         "uuid": "^11.0.0",
         "what-input": "^5.2.10"
@@ -157,13 +161,14 @@
     "devDependencies": {
         "@action-validator/cli": "^0.6.0",
         "@action-validator/core": "^0.6.0",
-        "@axe-core/playwright": "^4.8.1",
         "@babel/core": "^7.12.10",
         "@babel/eslint-parser": "^7.12.10",
         "@babel/eslint-plugin": "^7.12.10",
+        "@babel/plugin-proposal-decorators": "^7.25.9",
         "@babel/plugin-proposal-export-default-from": "^7.12.1",
         "@babel/plugin-syntax-dynamic-import": "^7.8.3",
         "@babel/plugin-transform-class-properties": "^7.12.1",
+        "@babel/plugin-transform-class-static-block": "^7.26.0",
         "@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
         "@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
         "@babel/plugin-transform-numeric-separator": "^7.12.7",
@@ -175,13 +180,15 @@
         "@babel/preset-typescript": "^7.12.7",
         "@babel/runtime": "^7.12.5",
         "@casualbot/jest-sonar-reporter": "2.2.7",
+        "@element-hq/element-call-embedded": "0.11.1",
+        "@element-hq/element-web-playwright-common": "^1.1.5",
         "@peculiar/webcrypto": "^1.4.3",
-        "@playwright/test": "^1.40.1",
+        "@playwright/test": "^1.50.1",
         "@principalstudio/html-webpack-inject-preload": "^1.2.7",
-        "@sentry/webpack-plugin": "^2.7.1",
-        "@stylistic/eslint-plugin": "^2.9.0",
+        "@rrweb/types": "^2.0.0-alpha.18",
+        "@sentry/webpack-plugin": "^3.0.0",
+        "@stylistic/eslint-plugin": "^4.0.0",
         "@svgr/webpack": "^8.0.0",
-        "@testcontainers/postgresql": "^10.16.0",
         "@testing-library/dom": "^10.4.0",
         "@testing-library/jest-dom": "^6.4.8",
         "@testing-library/react": "^16.0.0",
@@ -205,11 +212,11 @@
         "@types/node-fetch": "^2.6.2",
         "@types/pako": "^2.0.0",
         "@types/qrcode": "^1.3.5",
-        "@types/react": "18.3.18",
+        "@types/react": "19.1.6",
         "@types/react-beautiful-dnd": "^13.0.0",
-        "@types/react-dom": "18.3.5",
+        "@types/react-dom": "19.1.5",
         "@types/react-transition-group": "^4.4.0",
-        "@types/sanitize-html": "2.13.0",
+        "@types/sanitize-html": "2.16.0",
         "@types/semver": "^7.5.8",
         "@types/tar-js": "^0.3.5",
         "@types/ua-parser-js": "^0.7.36",
@@ -217,12 +224,12 @@
         "@typescript-eslint/eslint-plugin": "^8.19.0",
         "@typescript-eslint/parser": "^8.19.0",
         "babel-jest": "^29.0.0",
-        "babel-loader": "^9.0.0",
+        "babel-loader": "^10.0.0",
         "babel-plugin-jsx-remove-data-test-id": "^3.0.0",
         "blob-polyfill": "^9.0.0",
         "chokidar": "^4.0.0",
         "concurrently": "^9.0.0",
-        "copy-webpack-plugin": "^12.0.0",
+        "copy-webpack-plugin": "^13.0.0",
         "core-js": "^3.38.1",
         "cronstrue": "^2.41.0",
         "css-loader": "^7.0.0",
@@ -230,7 +237,7 @@
         "dotenv": "^16.0.2",
         "eslint": "8.57.1",
         "eslint-config-google": "^0.14.0",
-        "eslint-config-prettier": "^9.0.0",
+        "eslint-config-prettier": "^10.0.0",
         "eslint-plugin-deprecate": "0.8.5",
         "eslint-plugin-import": "^2.25.4",
         "eslint-plugin-jest": "^28.0.0",
@@ -240,7 +247,7 @@
         "eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
         "eslint-plugin-react-hooks": "^5.0.0",
         "eslint-plugin-unicorn": "^56.0.0",
-        "express": "^4.18.2",
+        "express": "^5.0.0",
         "fake-indexeddb": "^6.0.0",
         "fetch-mock": "9.11.0",
         "fetch-mock-jest": "^1.5.1",
@@ -255,14 +262,14 @@
         "jest-raw-loader": "^1.0.1",
         "jsqr": "^1.4.0",
         "knip": "^5.36.2",
-        "lint-staged": "^15.0.2",
-        "mailhog": "^4.16.0",
+        "lint-staged": "^16.0.0",
         "matrix-web-i18n": "^3.2.1",
         "mini-css-extract-plugin": "2.9.2",
         "minimist": "^1.2.6",
         "modernizr": "^3.12.0",
         "node-fetch": "^2.6.7",
-        "playwright-core": "^1.45.1",
+        "patch-package": "^8.0.0",
+        "playwright-core": "^1.51.0",
         "postcss": "8.4.46",
         "postcss-easings": "^4.0.0",
         "postcss-hexrgba": "2.1.0",
@@ -273,21 +280,20 @@
         "postcss-preset-env": "^10.0.0",
         "postcss-scss": "^4.0.4",
         "postcss-simple-vars": "^7.0.1",
-        "prettier": "3.4.2",
+        "prettier": "3.5.3",
         "process": "^0.11.10",
         "raw-loader": "^4.0.2",
         "rimraf": "^6.0.0",
         "semver": "^7.5.2",
         "source-map-loader": "^5.0.0",
-        "strip-ansi": "^7.1.0",
-        "stylelint": "^16.1.0",
-        "stylelint-config-standard": "^36.0.0",
+        "stylelint": "^16.13.0",
+        "stylelint-config-standard": "^38.0.0",
         "stylelint-scss": "^6.0.0",
         "stylelint-value-no-unknown-custom-properties": "^6.0.1",
         "terser-webpack-plugin": "^5.3.9",
-        "testcontainers": "^10.16.0",
+        "testcontainers": "^11.0.0",
         "ts-node": "^10.9.1",
-        "typescript": "5.7.2",
+        "typescript": "5.8.3",
         "util": "^0.12.5",
         "web-streams-polyfill": "^4.0.0",
         "webpack": "^5.89.0",
@@ -305,5 +311,6 @@
     },
     "engines": {
         "node": ">=20.0.0"
-    }
+    },
+    "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 }
diff --git a/patches/@matrix-org+react-sdk-module-api+2.5.0.patch b/patches/@matrix-org+react-sdk-module-api+2.5.0.patch
new file mode 100644
index 0000000000000000000000000000000000000000..5b17244da0aebce72c0b09a7d189f4f84a49e90c
--- /dev/null
+++ b/patches/@matrix-org+react-sdk-module-api+2.5.0.patch
@@ -0,0 +1,13 @@
+diff --git a/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts b/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
+index 917a7fc..a2710c6 100644
+--- a/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
++++ b/node_modules/@matrix-org/react-sdk-module-api/lib/ModuleApi.d.ts
+@@ -37,7 +37,7 @@ export interface ModuleApi {
+      * @returns Whether the user submitted the dialog or closed it, and the model returned by the
+      * dialog component if submitted.
+      */
+-    openDialog<M extends object, P extends DialogProps = DialogProps, C extends DialogContent<P> = DialogContent<P>>(initialTitleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject<C>) => React.ReactNode, props?: Omit<P, keyof DialogProps>): Promise<{
++    openDialog<M extends object, P extends DialogProps = DialogProps, C extends DialogContent<P> = DialogContent<P>>(initialTitleOrOptions: string | ModuleUiDialogOptions, body: (props: P, ref: React.RefObject<C | null>) => React.ReactNode, props?: Omit<P, keyof DialogProps>): Promise<{
+         didOkOrSubmit: boolean;
+         model: M;
+     }>;
diff --git a/patches/@types+react+19.1.4.patch b/patches/@types+react+19.1.4.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ceba85b000d2d3e4b1882331c324a3f863fbf66e
--- /dev/null
+++ b/patches/@types+react+19.1.4.patch
@@ -0,0 +1,31 @@
+diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
+index d3318dc..c2b2c77 100644
+--- a/node_modules/@types/react/index.d.ts
++++ b/node_modules/@types/react/index.d.ts
+@@ -134,7 +134,7 @@ declare namespace React {
+             props: P,
+         ) => ReactNode | Promise<ReactNode>)
+         // constructor signature must match React.Component
+-        | (new(props: P) => Component<any, any>);
++        | (new(props: P, context?: any) => Component<any, any>);
+ 
+     /**
+      * Created by {@link createRef}, or {@link useRef} when passed `null`.
+@@ -945,7 +945,7 @@ declare namespace React {
+         context: unknown;
+ 
+         // Keep in sync with constructor signature of JSXElementConstructor and ComponentClass.
+-        constructor(props: P);
++        constructor(props: P, context?: unknown);
+ 
+         // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
+         // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
+@@ -1117,7 +1117,7 @@ declare namespace React {
+      */
+     interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
+         // constructor signature must match React.Component
+-        new(props: P): Component<P, S>;
++        new(props: P, context?: any): Component<P, S>;
+         /**
+          * Ignored by React.
+          * @deprecated Only kept in types for backwards compatibility. Will be removed in a future major release.
diff --git a/patches/react-blurhash+0.3.0.patch b/patches/react-blurhash+0.3.0.patch
new file mode 100644
index 0000000000000000000000000000000000000000..93e4b05d6e16702cf22f8bcd12fc7df4935e48c2
--- /dev/null
+++ b/patches/react-blurhash+0.3.0.patch
@@ -0,0 +1,22 @@
+diff --git a/node_modules/react-blurhash/dist/index.d.ts b/node_modules/react-blurhash/dist/index.d.ts
+index 3adbd0a..32e8c13 100644
+--- a/node_modules/react-blurhash/dist/index.d.ts
++++ b/node_modules/react-blurhash/dist/index.d.ts
+@@ -19,7 +19,7 @@ declare class Blurhash extends React.PureComponent<Props$1> {
+         resolutionY: number;
+     };
+     componentDidUpdate(): void;
+-    render(): JSX.Element;
++    render(): React.JSX.Element;
+ }
+ 
+ declare type Props = React.CanvasHTMLAttributes<HTMLCanvasElement> & {
+@@ -37,7 +37,7 @@ declare class BlurhashCanvas extends React.PureComponent<Props> {
+     componentDidUpdate(): void;
+     handleRef: (canvas: HTMLCanvasElement) => void;
+     draw: () => void;
+-    render(): JSX.Element;
++    render(): React.JSX.Element;
+ }
+ 
+ export { Blurhash, BlurhashCanvas };
diff --git a/playwright.config.ts b/playwright.config.ts
index 09bd07bb3b8913a0e10eb79f53ea2724e99d9753..519e2d9c3bac7f9079c7c0dcc8931d1b72fba3b6 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { defineConfig, devices } from "@playwright/test";
 
-import { Options } from "./playwright/services";
+import { type WorkerOptions } from "./playwright/services";
 
 const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080";
 
@@ -21,7 +21,7 @@ const chromeProject = {
     },
 };
 
-export default defineConfig<Options>({
+export default defineConfig<WorkerOptions>({
     projects: [
         {
             name: "Chrome",
@@ -83,6 +83,7 @@ export default defineConfig<Options>({
         url: `${baseURL}/config.json`,
         reuseExistingServer: true,
         timeout: (process.env.CI ? 30 : 120) * 1000,
+        stdout: "pipe",
     },
     testDir: "playwright/e2e",
     outputDir: "playwright/test-results",
diff --git a/playwright/@types/playwright-core.d.ts b/playwright/@types/playwright-core.d.ts
deleted file mode 100644
index 244f3c91d4638e703eb909c3064dd2163a210356..0000000000000000000000000000000000000000
--- a/playwright/@types/playwright-core.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2024 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-declare module "playwright-core/lib/utils" {
-    // This type is not public in playwright-core utils
-    export function sanitizeForFilePath(filePath: string): string;
-}
diff --git a/playwright/Dockerfile b/playwright/Dockerfile
deleted file mode 100644
index 7e918e04f7c1eb37dc6422115b82ab48c7b902da..0000000000000000000000000000000000000000
--- a/playwright/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM mcr.microsoft.com/playwright:v1.49.1-noble
-
-WORKDIR /work
-
-# fonts-dejavu is needed for the same RTL rendering as on CI
-RUN apt-get update && apt-get -y install docker.io fonts-dejavu
-
-COPY docker-entrypoint.sh /opt/docker-entrypoint.sh
-ENTRYPOINT ["bash", "/opt/docker-entrypoint.sh"]
diff --git a/playwright/docker-entrypoint.sh b/playwright/docker-entrypoint.sh
deleted file mode 100644
index 241528a29aaec476f2b5cfe2b05b2e48580859ce..0000000000000000000000000000000000000000
--- a/playwright/docker-entrypoint.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-set -e
-
-npx playwright test --update-snapshots --reporter line $@
diff --git a/playwright/e2e/audio-player/audio-player.spec.ts b/playwright/e2e/audio-player/audio-player.spec.ts
index bb766d6b88b36b9d66311264fd59de8239d79646..a8cb15a5dabe50fcf98a7ab3341b574217ee77d9 100644
--- a/playwright/e2e/audio-player/audio-player.spec.ts
+++ b/playwright/e2e/audio-player/audio-player.spec.ts
@@ -11,7 +11,7 @@ import type { Locator, Page } from "@playwright/test";
 import { test, expect } from "../../element-web-test";
 import { SettingLevel } from "../../../src/settings/SettingLevel";
 import { Layout } from "../../../src/settings/enums/Layout";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 
 // Find and click "Reply" button
 const clickButtonReply = async (tile: Locator) => {
diff --git a/playwright/e2e/crypto/backups-mas.spec.ts b/playwright/e2e/crypto/backups-mas.spec.ts
index a6f4fb93907837db2d4dcdac7b48173926b07def..6519a484e9fd3407fd9d224ae19d8f114c6d8664 100644
--- a/playwright/e2e/crypto/backups-mas.spec.ts
+++ b/playwright/e2e/crypto/backups-mas.spec.ts
@@ -11,6 +11,7 @@ import { registerAccountMas } from "../oidc";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 import { TestClientServerAPI } from "../csAPI";
 import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
+import { checkDeviceIsConnectedKeyBackup } from "./utils";
 
 // These tests register an account with MAS because then we go through the "normal" registration flow
 // and crypto gets set up. Using the 'user' fixture create a user and synthesizes an existing login,
@@ -19,19 +20,22 @@ test.use(masHomeserver);
 test.describe("Encryption state after registration", () => {
     test.skip(isDendrite, "does not yet support MAS");
 
-    test("Key backup is enabled by default", async ({ page, mailhogClient, app }, testInfo) => {
+    test("Key backup is enabled by default", async ({ page, mailpitClient, app }, testInfo) => {
         await page.goto("/#/login");
         await page.getByRole("button", { name: "Continue" }).click();
-        await registerAccountMas(page, mailhogClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
+        await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
 
-        await app.settings.openUserSettings("Security & Privacy");
-        await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
+        // Wait for the ui to load
+        await expect(page.locator(".mx_MatrixChat")).toBeVisible();
+
+        // Recovery is not set up yet
+        await checkDeviceIsConnectedKeyBackup(app, "1", true, false);
     });
 
-    test("user is prompted to set up recovery", async ({ page, mailhogClient, app }, testInfo) => {
+    test("user is prompted to set up recovery", async ({ page, mailpitClient, app }, testInfo) => {
         await page.goto("/#/login");
         await page.getByRole("button", { name: "Continue" }).click();
-        await registerAccountMas(page, mailhogClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
+        await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!");
 
         await page.getByRole("button", { name: "Add room" }).click();
         await page.getByRole("menuitem", { name: "New room" }).click();
@@ -47,7 +51,7 @@ test.describe("Key backup reset from elsewhere", () => {
 
     test("Key backup is disabled when reset from elsewhere", async ({
         page,
-        mailhogClient,
+        mailpitClient,
         request,
         homeserver,
     }, testInfo) => {
@@ -60,7 +64,7 @@ test.describe("Key backup reset from elsewhere", () => {
 
         await page.goto("/#/login");
         await page.getByRole("button", { name: "Continue" }).click();
-        await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword);
+        await registerAccountMas(page, mailpitClient, testUsername, "alice@email.com", testPassword);
 
         await page.getByRole("button", { name: "Add room" }).click();
         await page.getByRole("menuitem", { name: "New room" }).click();
diff --git a/playwright/e2e/crypto/backups.spec.ts b/playwright/e2e/crypto/backups.spec.ts
deleted file mode 100644
index 95bf708122e424dedcb764d8e6e7f3393f271d0c..0000000000000000000000000000000000000000
--- a/playwright/e2e/crypto/backups.spec.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { type Page } from "@playwright/test";
-
-import { test, expect } from "../../element-web-test";
-import { isDendrite } from "../../plugins/homeserver/dendrite";
-
-async function expectBackupVersionToBe(page: Page, version: string) {
-    await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
-        version + " (Algorithm: m.megolm_backup.v1.curve25519-aes-sha2)",
-    );
-
-    await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version);
-}
-
-test.describe("Backups", () => {
-    test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
-    test.use({
-        displayName: "Hanako",
-    });
-
-    test(
-        "Create, delete and recreate a keys backup",
-        { tag: "@no-webkit" },
-        async ({ page, user, app }, workerInfo) => {
-            // Create a backup
-            const securityTab = await app.settings.openUserSettings("Security & Privacy");
-
-            await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
-            await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
-
-            const currentDialogLocator = page.locator(".mx_Dialog");
-
-            // It's the first time and secure storage is not set up, so it will create one
-            await expect(currentDialogLocator.getByRole("heading", { name: "Set up Secure Backup" })).toBeVisible();
-            await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
-            await expect(currentDialogLocator.getByRole("heading", { name: "Save your Security Key" })).toBeVisible();
-            await currentDialogLocator.getByRole("button", { name: "Copy", exact: true }).click();
-            // copy the recovery key to use it later
-            const securityKey = await app.getClipboard();
-            await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
-
-            await expect(currentDialogLocator.getByRole("heading", { name: "Secure Backup successful" })).toBeVisible();
-            await currentDialogLocator.getByRole("button", { name: "Done", exact: true }).click();
-
-            // Open the settings again
-            await app.settings.openUserSettings("Security & Privacy");
-            await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
-
-            // expand the advanced section to see the active version in the reports
-            await page
-                .locator(".mx_Dialog .mx_SettingsSubsection_content details .mx_SecureBackupPanel_advanced")
-                .locator("..")
-                .click();
-
-            await expectBackupVersionToBe(page, "1");
-
-            await securityTab.getByRole("button", { name: "Delete Backup", exact: true }).click();
-            await expect(currentDialogLocator.getByRole("heading", { name: "Delete Backup" })).toBeVisible();
-            // Delete it
-            await currentDialogLocator.getByTestId("dialog-primary-button").click(); // Click "Delete Backup"
-
-            // Create another
-            await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
-            await expect(currentDialogLocator.getByRole("heading", { name: "Security Key" })).toBeVisible();
-            await currentDialogLocator.getByLabel("Security Key").fill(securityKey);
-            await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
-
-            // Should be successful
-            await expect(currentDialogLocator.getByRole("heading", { name: "Success!" })).toBeVisible();
-            await currentDialogLocator.getByRole("button", { name: "OK", exact: true }).click();
-
-            // Open the settings again
-            await app.settings.openUserSettings("Security & Privacy");
-            await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
-
-            // expand the advanced section to see the active version in the reports
-            await page
-                .locator(".mx_Dialog .mx_SettingsSubsection_content details .mx_SecureBackupPanel_advanced")
-                .locator("..")
-                .click();
-
-            await expectBackupVersionToBe(page, "2");
-
-            // ==
-            // Ensure that if you don't have the secret storage passphrase the backup won't be created
-            // ==
-
-            // First delete version 2
-            await securityTab.getByRole("button", { name: "Delete Backup", exact: true }).click();
-            await expect(currentDialogLocator.getByRole("heading", { name: "Delete Backup" })).toBeVisible();
-            // Click "Delete Backup"
-            await currentDialogLocator.getByTestId("dialog-primary-button").click();
-
-            // Try to create another
-            await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
-            await expect(currentDialogLocator.getByRole("heading", { name: "Security Key" })).toBeVisible();
-            // But cancel the security key dialog, to simulate not having the secret storage passphrase
-            await currentDialogLocator.getByTestId("dialog-cancel-button").click();
-
-            await expect(currentDialogLocator.getByRole("heading", { name: "Starting backup…" })).toBeVisible();
-            // check that it failed
-            await expect(currentDialogLocator.getByText("Unable to create key backup")).toBeVisible();
-            // cancel
-            await currentDialogLocator.getByTestId("dialog-cancel-button").click();
-
-            // go back to the settings to check that no backup was created (the setup button should still be there)
-            await app.settings.openUserSettings("Security & Privacy");
-            await expect(securityTab.getByRole("button", { name: "Set up", exact: true })).toBeVisible();
-        },
-    );
-});
diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts
index f99a7a645888bcc03102990f0cccb219533af8f3..bad9072f0cba66580e1fcfd0f05d4058a856c77d 100644
--- a/playwright/e2e/crypto/crypto.spec.ts
+++ b/playwright/e2e/crypto/crypto.spec.ts
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import type { Page } from "@playwright/test";
 import { expect, test } from "../../element-web-test";
-import { autoJoin, copyAndContinue, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils";
-import { Bot } from "../../pages/bot";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { autoJoin, createSharedRoomWithUser, enableKeyBackup, verify } from "./utils";
+import { type Bot } from "../../pages/bot";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
 const checkDMRoom = async (page: Page) => {
@@ -21,7 +21,7 @@ const checkDMRoom = async (page: Page) => {
 };
 
 const startDMWithBob = async (page: Page, bob: Bot) => {
-    await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
+    await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
     await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
     await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
     await expect(
@@ -77,96 +77,43 @@ test.describe("Cryptography", function () {
         },
     });
 
-    for (const isDeviceVerified of [true, false]) {
-        test.describe(`setting up secure key backup should work isDeviceVerified=${isDeviceVerified}`, () => {
-            /**
-             * Verify that the `m.cross_signing.${keyType}` key is available on the account data on the server
-             * @param keyType
-             */
-            async function verifyKey(app: ElementAppPage, keyType: "master" | "self_signing" | "user_signing") {
-                const accountData: { encrypted: Record<string, Record<string, string>> } = await app.client.evaluate(
-                    (cli, keyType) => cli.getAccountDataFromServer(`m.cross_signing.${keyType}`),
-                    keyType,
-                );
-                expect(accountData.encrypted).toBeDefined();
-                const keys = Object.keys(accountData.encrypted);
-                const key = accountData.encrypted[keys[0]];
-                expect(key.ciphertext).toBeDefined();
-                expect(key.iv).toBeDefined();
-                expect(key.mac).toBeDefined();
-            }
-
-            test("by recovery code", async ({ page, app, user: aliceCredentials }) => {
-                // Verified the device
-                if (isDeviceVerified) {
-                    await app.client.bootstrapCrossSigning(aliceCredentials);
-                }
-
-                await page.route("**/_matrix/client/v3/keys/signatures/upload", async (route) => {
-                    // We delay this API otherwise the `Setting up keys` may happen too quickly and cause flakiness
-                    await new Promise((resolve) => setTimeout(resolve, 500));
-                    await route.continue();
-                });
-
-                await app.settings.openUserSettings("Security & Privacy");
-                await page.getByRole("button", { name: "Set up Secure Backup" }).click();
-
-                const dialog = page.locator(".mx_Dialog");
-                // Recovery key is selected by default
-                await dialog.getByRole("button", { name: "Continue" }).click();
-                await copyAndContinue(page);
+    /**
+     * Verify that the `m.cross_signing.${keyType}` key is available on the account data on the server
+     * @param keyType
+     */
+    async function verifyKey(app: ElementAppPage, keyType: "master" | "self_signing" | "user_signing") {
+        const accountData: { encrypted: Record<string, Record<string, string>> } = await app.client.evaluate(
+            (cli, keyType) => cli.getAccountDataFromServer(`m.cross_signing.${keyType}`),
+            keyType,
+        );
+
+        expect(accountData.encrypted).toBeDefined();
+        const keys = Object.keys(accountData.encrypted);
+        const key = accountData.encrypted[keys[0]];
+        expect(key.ciphertext).toBeDefined();
+        expect(key.iv).toBeDefined();
+        expect(key.mac).toBeDefined();
+    }
 
-                // If the device is unverified, there should be a "Setting up keys" step; however, it
-                // can be quite quick, and playwright can miss it, so we can't test for it.
+    test("Setting up key backup by recovery key", async ({ page, app, user: aliceCredentials }) => {
+        await app.client.bootstrapCrossSigning(aliceCredentials);
 
-                // Either way, we end up at a success dialog:
-                await expect(dialog.getByText("Secure Backup successful")).toBeVisible();
-                await dialog.getByRole("button", { name: "Done" }).click();
-                await expect(dialog.getByText("Secure Backup successful")).not.toBeVisible();
+        await enableKeyBackup(app);
 
-                // Verify that the SSSS keys are in the account data stored in the server
-                await verifyKey(app, "master");
-                await verifyKey(app, "self_signing");
-                await verifyKey(app, "user_signing");
-            });
+        // Wait for the cross signing keys to be uploaded
+        // Waiting for "Change the recovery key" button ensure that all the secrets are uploaded and cached locally
+        const encryptionTab = await app.settings.openUserSettings("Encryption");
+        await expect(encryptionTab.getByRole("button", { name: "Change recovery key" })).toBeVisible();
 
-            test("by passphrase", async ({ page, app, user: aliceCredentials }) => {
-                // Verified the device
-                if (isDeviceVerified) {
-                    await app.client.bootstrapCrossSigning(aliceCredentials);
-                }
-
-                await app.settings.openUserSettings("Security & Privacy");
-                await page.getByRole("button", { name: "Set up Secure Backup" }).click();
-
-                const dialog = page.locator(".mx_Dialog");
-                // Select passphrase option
-                await dialog.getByText("Enter a Security Phrase").click();
-                await dialog.getByRole("button", { name: "Continue" }).click();
-
-                // Fill passphrase input
-                await dialog.locator("input").fill("new passphrase for setting up a secure key backup");
-                await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
-                // Confirm passphrase
-                await dialog.locator("input").fill("new passphrase for setting up a secure key backup");
-                await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
-
-                await copyAndContinue(page);
-
-                await expect(dialog.getByText("Secure Backup successful")).toBeVisible();
-                await dialog.getByRole("button", { name: "Done" }).click();
-                await expect(dialog.getByText("Secure Backup successful")).not.toBeVisible();
-
-                // Verify that the SSSS keys are in the account data stored in the server
-                await verifyKey(app, "master");
-                await verifyKey(app, "self_signing");
-                await verifyKey(app, "user_signing");
-            });
-        });
-    }
+        // Verify that the SSSS keys are in the account data stored in the server
+        await verifyKey(app, "master");
+        await verifyKey(app, "self_signing");
+        await verifyKey(app, "user_signing");
+    });
 
     test("Can reset cross-signing keys", async ({ page, app, user: aliceCredentials }) => {
-        const secretStorageKey = await enableKeyBackup(app);
+        await app.client.bootstrapCrossSigning(aliceCredentials);
+        await enableKeyBackup(app);
 
         // Fetch the current cross-signing keys
         async function fetchMasterKey() {
@@ -180,18 +127,15 @@ test.describe("Cryptography", function () {
                 return k;
             });
         }
+
         const masterKey1 = await fetchMasterKey();
 
-        // Find the "reset cross signing" button, and click it
-        await app.settings.openUserSettings("Security & Privacy");
-        await page.locator("div.mx_CrossSigningPanel_buttonRow").getByRole("button", { name: "Reset" }).click();
+        // Find "the Reset cryptographic identity" button
+        const encryptionTab = await app.settings.openUserSettings("Encryption");
+        await encryptionTab.getByRole("button", { name: "Reset cryptographic identity" }).click();
 
         // Confirm
-        await page.getByRole("button", { name: "Clear cross-signing keys" }).click();
-
-        // Enter the 4S key
-        await page.getByPlaceholder("Security Key").fill(secretStorageKey);
-        await page.getByRole("button", { name: "Continue" }).click();
+        await encryptionTab.getByRole("button", { name: "Continue" }).click();
 
         // Enter the password
         await page.getByPlaceholder("Password").fill(aliceCredentials.password);
@@ -201,9 +145,6 @@ test.describe("Cryptography", function () {
             const masterKey2 = await fetchMasterKey();
             expect(masterKey1).not.toEqual(masterKey2);
         }).toPass();
-
-        // The dialog should have gone away
-        await expect(page.locator(".mx_Dialog")).toHaveCount(1);
     });
 
     test(
diff --git a/playwright/e2e/crypto/dehydration.spec.ts b/playwright/e2e/crypto/dehydration.spec.ts
index 9beb05393289d9d43bebaf59ab1f266ad8bfeb3b..379fc36cf996cc06bd7ad2f2ee1c93b32cfc4c77 100644
--- a/playwright/e2e/crypto/dehydration.spec.ts
+++ b/playwright/e2e/crypto/dehydration.spec.ts
@@ -6,19 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator, type Page } from "@playwright/test";
-
 import { test, expect } from "../../element-web-test";
-import { viewRoomSummaryByName } from "../right-panel/utils";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
+import { createBot, logIntoElement } from "./utils.ts";
+import { type Client } from "../../pages/client.ts";
+import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
 
-const ROOM_NAME = "Test room";
 const NAME = "Alice";
 
-function getMemberTileByName(page: Page, name: string): Locator {
-    return page.locator(`.mx_MemberTileView, [title="${name}"]`);
-}
-
 test.use({
     displayName: NAME,
     synapseConfig: {
@@ -27,73 +22,182 @@ test.use({
             msc3814_enabled: true,
         },
     },
-    config: async ({ config, context }, use) => {
-        const wellKnown = {
-            ...config.default_server_config,
-            "org.matrix.msc3814": true,
-        };
-
-        await context.route("https://localhost/.well-known/matrix/client", async (route) => {
-            await route.fulfill({ json: wellKnown });
-        });
-
-        await use(config);
-    },
 });
 
 test.describe("Dehydration", () => {
     test.skip(isDendrite, "does not yet support dehydration v2");
 
-    test("Create dehydrated device", async ({ page, user, app }, workerInfo) => {
-        // Create a backup (which will create SSSS, and dehydrated device)
+    test("Verify device and reset creates dehydrated device", async ({ page, user, credentials, app }, workerInfo) => {
+        // Verify the device by resetting the identity key, and then set up recovery (which will create SSSS, and dehydrated device)
 
         const securityTab = await app.settings.openUserSettings("Security & Privacy");
-
-        await expect(securityTab.getByRole("heading", { name: "Secure Backup" })).toBeVisible();
         await expect(securityTab.getByText("Offline device enabled")).not.toBeVisible();
-        await securityTab.getByRole("button", { name: "Set up", exact: true }).click();
-
-        const currentDialogLocator = page.locator(".mx_Dialog");
 
-        // It's the first time and secure storage is not set up, so it will create one
-        await expect(currentDialogLocator.getByRole("heading", { name: "Set up Secure Backup" })).toBeVisible();
-        await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
-        await expect(currentDialogLocator.getByRole("heading", { name: "Save your Security Key" })).toBeVisible();
-        await currentDialogLocator.getByRole("button", { name: "Copy", exact: true }).click();
-        await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
+        await app.closeDialog();
 
-        await expect(currentDialogLocator.getByRole("heading", { name: "Secure Backup successful" })).toBeVisible();
-        await currentDialogLocator.getByRole("button", { name: "Done", exact: true }).click();
+        // Reset the identity key
+        const settings = await app.settings.openUserSettings("Encryption");
+        await settings.getByRole("button", { name: "Verify this device" }).click();
+        await page.getByRole("button", { name: "Proceed with reset" }).click();
+        await page.getByRole("button", { name: "Continue" }).click();
 
-        // Open the settings again
-        await app.settings.openUserSettings("Security & Privacy");
+        // Set up recovery
+        await page.getByRole("button", { name: "Set up recovery" }).click();
+        await page.getByRole("button", { name: "Continue" }).click();
+        const recoveryKey = await page.getByTestId("recoveryKey").innerText();
+        await page.getByRole("button", { name: "Continue" }).click();
+        await page.getByRole("textbox").fill(recoveryKey);
+        await page.getByRole("button", { name: "Finish set up" }).click();
+        await page.getByRole("button", { name: "Close" }).click();
 
-        // The Security tab should indicate that there is a dehydrated device present
-        await expect(securityTab.getByText("Offline device enabled")).toBeVisible();
-
-        await app.settings.closeDialog();
+        await expectDehydratedDeviceEnabled(app);
 
         // the dehydrated device gets created with the name "Dehydrated
         // device".  We want to make sure that it is not visible as a normal
         // device.
         const sessionsTab = await app.settings.openUserSettings("Sessions");
         await expect(sessionsTab.getByText("Dehydrated device")).not.toBeVisible();
+    });
+
+    test("'Set up recovery' creates dehydrated device", async ({ app, credentials, page }) => {
+        await logIntoElement(page, credentials);
+
+        const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
+        await settingsDialogLocator.getByRole("button", { name: "Set up recovery" }).click();
+
+        // First it displays an informative panel about the recovery key
+        await expect(settingsDialogLocator.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
+        await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
+
+        // Next, it displays the new recovery key. We click on the copy button.
+        await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
+        await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
+        const recoveryKey = await app.getClipboard();
+        await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
+
+        await expect(
+            settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
+        ).toBeVisible();
+        await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
+        await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
 
         await app.settings.closeDialog();
 
-        // now check that the user info right-panel shows the dehydrated device
-        // as a feature rather than as a normal device
-        await app.client.createRoom({ name: ROOM_NAME });
+        await expectDehydratedDeviceEnabled(app);
+    });
+
+    test("Reset identity during login and set up recovery re-creates dehydrated device", async ({
+        page,
+        homeserver,
+        app,
+        credentials,
+    }) => {
+        // Set up cross-signing and recovery
+        const { botClient } = await createBot(page, homeserver, credentials);
+        // ... and dehydration
+        await botClient.evaluate(async (client) => await client.getCrypto().startDehydration());
+
+        const initialDehydratedDeviceIds = await getDehydratedDeviceIds(botClient);
+        expect(initialDehydratedDeviceIds.length).toBe(1);
+
+        await botClient.evaluate(async (client) => client.stopClient());
+
+        // Log in our client
+        await logIntoElement(page, credentials);
+
+        // Oh no, we forgot our recovery key - reset our identity
+        await page.locator(".mx_AuthPage").getByRole("button", { name: "Reset all" }).click();
+        await expect(
+            page.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
+        ).toBeVisible();
+        await page.getByRole("button", { name: "Continue", exact: true }).click();
+        await page.getByPlaceholder("Password").fill(credentials.password);
+        await page.getByRole("button", { name: "Continue" }).click();
+
+        // And set up recovery
+        const settings = await app.settings.openUserSettings("Encryption");
+        await settings.getByRole("button", { name: "Set up recovery" }).click();
+        await settings.getByRole("button", { name: "Continue" }).click();
+        const recoveryKey = await settings.getByTestId("recoveryKey").innerText();
+        await settings.getByRole("button", { name: "Continue" }).click();
+        await settings.getByRole("textbox").fill(recoveryKey);
+        await settings.getByRole("button", { name: "Finish set up" }).click();
+
+        // There should be a brand new dehydrated device
+        await expectDehydratedDeviceEnabled(app);
+    });
+
+    test("'Reset cryptographic identity' removes dehydrated device", async ({ page, homeserver, app, credentials }) => {
+        await logIntoElement(page, credentials);
+
+        // Create a dehydrated device by setting up recovery (see "'Set up
+        // recovery' creates dehydrated device" test above)
+        const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
+        await settingsDialogLocator.getByRole("button", { name: "Set up recovery" }).click();
+
+        // First it displays an informative panel about the recovery key
+        await expect(settingsDialogLocator.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
+        await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
+
+        // Next, it displays the new recovery key. We click on the copy button.
+        await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
+        await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
+        const recoveryKey = await app.getClipboard();
+        await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
 
-        await viewRoomSummaryByName(page, app, ROOM_NAME);
+        await expect(
+            settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
+        ).toBeVisible();
+        await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
+        await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
 
-        await page.locator(".mx_RightPanel").getByRole("menuitem", { name: "People" }).click();
-        await expect(page.locator(".mx_MemberListView")).toBeVisible();
+        await expectDehydratedDeviceEnabled(app);
 
-        await getMemberTileByName(page, NAME).click();
-        await page.locator(".mx_UserInfo_devices .mx_UserInfo_expand").click();
+        // After recovery is set up, we reset our cryptographic identity, which
+        // should drop the dehydrated device.
+        await settingsDialogLocator.getByRole("button", { name: "Reset cryptographic identity" }).click();
+        await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
 
-        await expect(page.locator(".mx_UserInfo_devices").getByText("Offline device enabled")).toBeVisible();
-        await expect(page.locator(".mx_UserInfo_devices").getByText("Dehydrated device")).not.toBeVisible();
+        await expectDehydratedDeviceDisabled(app);
     });
 });
+
+async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
+    return await client.evaluate(async (client) => {
+        const userId = client.getUserId();
+        const devices = await client.getCrypto().getUserDeviceInfo([userId]);
+        return Array.from(
+            devices
+                .get(userId)
+                .values()
+                .filter((d) => d.dehydrated)
+                .map((d) => d.deviceId),
+        );
+    });
+}
+
+/** Wait for our user to have a dehydrated device */
+async function expectDehydratedDeviceEnabled(app: ElementAppPage): Promise<void> {
+    // It might be nice to do this via the UI, but currently this info is not exposed via the UI.
+    //
+    // Note we might have to wait for the device list to be refreshed, so we wrap in `expect.poll`.
+    await expect
+        .poll(async () => {
+            const dehydratedDeviceIds = await getDehydratedDeviceIds(app.client);
+            return dehydratedDeviceIds.length;
+        })
+        .toEqual(1);
+}
+
+/** Wait for our user to not have a dehydrated device */
+async function expectDehydratedDeviceDisabled(app: ElementAppPage): Promise<void> {
+    // It might be nice to do this via the UI, but currently this info is not exposed via the UI.
+    //
+    // Note we might have to wait for the device list to be refreshed, so we wrap in `expect.poll`.
+    await expect
+        .poll(async () => {
+            const dehydratedDeviceIds = await getDehydratedDeviceIds(app.client);
+            return dehydratedDeviceIds.length;
+        })
+        .toEqual(0);
+}
diff --git a/playwright/e2e/crypto/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts
index 4091b1e676140e360b204d706203e31a0f25d042..64846ac86dc90d7ed3b9a8055ed76b6fcf1c906d 100644
--- a/playwright/e2e/crypto/device-verification.spec.ts
+++ b/playwright/e2e/crypto/device-verification.spec.ts
@@ -20,7 +20,8 @@ import {
     logIntoElement,
     waitForVerificationRequest,
 } from "./utils";
-import { Bot } from "../../pages/bot";
+import { type Bot } from "../../pages/bot";
+import { Toasts } from "../../pages/toasts.ts";
 
 test.describe("Device verification", { tag: "@no-webkit" }, () => {
     let aliceBotClient: Bot;
@@ -29,7 +30,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
     let expectedBackupVersion: string;
 
     test.beforeEach(async ({ page, homeserver, credentials }) => {
-        const res = await createBot(page, homeserver, credentials);
+        const res = await createBot(page, homeserver, credentials, true);
         aliceBotClient = res.botClient;
         expectedBackupVersion = res.expectedBackupVersion;
     });
@@ -68,8 +69,53 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
 
         // Check that the current device is connected to key backup
         // For now we don't check that the backup key is in cache because it's a bit flaky,
-        // as we need to wait for the secret gossiping to happen and the settings dialog doesn't refresh automatically.
-        await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, false);
+        // as we need to wait for the secret gossiping to happen.
+        await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, false);
+    });
+
+    // Regression test for https://github.com/element-hq/element-web/issues/29110
+    test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
+        // Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
+        // when we are in an encrypted room.
+        await aliceBotClient.createRoom({
+            initial_state: [
+                {
+                    type: "m.room.encryption",
+                    state_key: "",
+                    content: { algorithm: "m.megolm.v1.aes-sha2" },
+                },
+            ],
+        });
+
+        // In order to simulate a real environment more accurately, we need to slow down the arrival of the
+        // `m.secret.send` to-device messages. That's slightly tricky to do directly, so instead we delay the *outgoing*
+        // `m.secret.request` messages.
+        await page.route("**/_matrix/client/v3/sendToDevice/m.secret.request/**", async (route) => {
+            await route.fulfill({ json: {} });
+            await new Promise((f) => setTimeout(f, 1000));
+            await route.fetch();
+        });
+
+        await logIntoElement(page, credentials);
+
+        // Launch the verification request between alice and the bot
+        const verificationRequest = await initiateAliceVerificationRequest(page);
+
+        // Handle emoji SAS verification
+        const infoDialog = page.locator(".mx_InfoDialog");
+        // the bot chooses to do an emoji verification
+        const verifier = await verificationRequest.evaluateHandle((request) => request.startVerification("m.sas.v1"));
+
+        // Handle emoji request and check that emojis are matching
+        await doTwoWaySasVerification(page, verifier);
+
+        await infoDialog.getByRole("button", { name: "They match" }).click();
+        await infoDialog.getByRole("button", { name: "Got it" }).click();
+
+        // There should be no toast (other than the notifications one)
+        const toasts = new Toasts(page);
+        await toasts.rejectToast("Notifications");
+        await toasts.assertNoToasts();
     });
 
     test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
@@ -112,21 +158,19 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
         await checkDeviceIsCrossSigned(app);
 
         // Check that the current device is connected to key backup
-        // For now we don't check that the backup key is in cache because it's a bit flaky,
-        // as we need to wait for the secret gossiping to happen and the settings dialog doesn't refresh automatically.
-        await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, false);
+        await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
     });
 
     test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => {
         await logIntoElement(page, credentials);
 
         // Select the security phrase
-        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Security Key or Phrase" }).click();
+        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
 
         // Fill the passphrase
         const dialog = page.locator(".mx_Dialog");
-        await dialog.locator("input").fill("new passphrase");
-        await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
+        await dialog.locator("textarea").fill("new passphrase");
+        await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
 
         await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click();
 
@@ -135,21 +179,20 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
 
         // Check that the current device is connected to key backup
         // The backup decryption key should be in cache also, as we got it directly from the 4S
-        await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
+        await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
     });
 
-    test("Verify device with Security Key during login", async ({ page, app, credentials, homeserver }) => {
+    test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => {
         await logIntoElement(page, credentials);
 
         // Select the security phrase
-        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Security Key or Phrase" }).click();
+        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click();
 
-        // Fill the security key
+        // Fill the recovery key
         const dialog = page.locator(".mx_Dialog");
-        await dialog.getByRole("button", { name: "use your Security Key" }).click();
         const aliceRecoveryKey = await aliceBotClient.getRecoveryKey();
-        await dialog.locator("#mx_securityKey").fill(aliceRecoveryKey.encodedPrivateKey);
-        await dialog.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
+        await dialog.locator("textarea").fill(aliceRecoveryKey.encodedPrivateKey);
+        await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
 
         await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click();
 
@@ -158,7 +201,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
 
         // Check that the current device is connected to key backup
         // The backup decryption key should be in cache also, as we got it directly from the 4S
-        await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
+        await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
     });
 
     test("Handle incoming verification request with SAS", async ({ page, credentials, homeserver, toasts }) => {
diff --git a/playwright/e2e/crypto/event-shields.spec.ts b/playwright/e2e/crypto/event-shields.spec.ts
index c0f1e280a2f4a11cfecb11be1fe1e02f2ac8b596..e577a6646736507d4ed9c6b6cb84a0b5144a5c0a 100644
--- a/playwright/e2e/crypto/event-shields.spec.ts
+++ b/playwright/e2e/crypto/event-shields.spec.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator } from "@playwright/test";
+import { type Locator } from "@playwright/test";
 
 import { expect, test } from "../../element-web-test";
 import {
@@ -17,9 +17,10 @@ import {
     logIntoElement,
     logOutOfElement,
     verify,
+    waitForDevices,
 } from "./utils";
 import { bootstrapCrossSigningForClient } from "../../pages/client.ts";
-import { ElementAppPage } from "../../pages/ElementAppPage.ts";
+import { type ElementAppPage } from "../../pages/ElementAppPage.ts";
 
 test.describe("Cryptography", function () {
     test.use({
@@ -66,8 +67,9 @@ test.describe("Cryptography", function () {
             // Bob has a second, not cross-signed, device
             const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
 
-            // Dismiss the toast nagging us to set up recovery otherwise it gets in the way of clicking the room list
-            await page.getByRole("button", { name: "Not now" }).click();
+            // Dismiss the toasts nagging us, otherwise they get in the way of clicking the room list
+            await page.getByRole("button", { name: "Dismiss" }).click();
+            await page.getByRole("button", { name: "Yes, dismiss" }).click();
 
             await bob.sendEvent(testRoomId, null, "m.room.encrypted", {
                 algorithm: "m.megolm.v1.aes-sha2",
@@ -144,25 +146,8 @@ test.describe("Cryptography", function () {
             // bob deletes his second device
             await bobSecondDevice.evaluate((cli) => cli.logout(true));
 
-            // wait for the logout to propagate. Workaround for https://github.com/vector-im/element-web/issues/26263 by repeatedly closing and reopening Bob's user info.
-            async function awaitOneDevice(iterations = 1) {
-                const rightPanel = page.locator(".mx_RightPanel");
-                await rightPanel.getByTestId("base-card-back-button").click();
-                await rightPanel.getByText("Bob").click();
-                const sessionCountText = await rightPanel
-                    .locator(".mx_UserInfo_devices")
-                    .getByText(" session", { exact: false })
-                    .textContent();
-                // cf https://github.com/vector-im/element-web/issues/26279: Element-R uses the wrong text here
-                if (sessionCountText != "1 session" && sessionCountText != "1 verified session") {
-                    if (iterations >= 10) {
-                        throw new Error(`Bob still has ${sessionCountText} after 10 iterations`);
-                    }
-                    await awaitOneDevice(iterations + 1);
-                }
-            }
-
-            await awaitOneDevice();
+            // wait for the logout to propagate.
+            await waitForDevices(app, bob.credentials.userId, 1);
 
             // close and reopen the room, to get the shield to update.
             await app.viewRoomByName("Bob");
@@ -285,11 +270,7 @@ test.describe("Cryptography", function () {
             // Workaround for https://github.com/element-hq/element-web/issues/28640:
             // make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
             // his user info.
-            await app.toggleRoomInfoPanel();
-            const rightPanel = page.locator(".mx_RightPanel");
-            await rightPanel.getByRole("menuitem", { name: "People" }).click();
-            await rightPanel.getByRole("button", { name: bob.credentials!.userId }).click();
-            await expect(rightPanel.locator(".mx_UserInfo_devices")).toContainText("1 session");
+            await waitForDevices(app, bob.credentials.userId, 1);
 
             // Our app is blocked from syncing while Bob sends his messages.
             await app.client.network.goOffline();
@@ -344,7 +325,7 @@ test.describe("Cryptography", function () {
             await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
             await lastE2eIcon.focus();
             await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
-                "Sender's verified identity has changed",
+                "Sender's verified identity was reset",
             );
         });
     });
diff --git a/playwright/e2e/crypto/invisible-crypto.spec.ts b/playwright/e2e/crypto/invisible-crypto.spec.ts
index 198abdac6d91d9d88e4e1e8425b860b815c7e846..aadb6817ff5f9a7b757e83a67b8bef82d836ae2f 100644
--- a/playwright/e2e/crypto/invisible-crypto.spec.ts
+++ b/playwright/e2e/crypto/invisible-crypto.spec.ts
@@ -52,6 +52,6 @@ test.describe("Invisible cryptography", () => {
         /* should show an error for a message from a previously verified device */
         await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
         const lastTile = page.locator(".mx_EventTile_last");
-        await expect(lastTile).toContainText("Sender's verified identity has changed");
+        await expect(lastTile).toContainText("Sender's verified identity was reset");
     });
 });
diff --git a/playwright/e2e/crypto/toasts.spec.ts b/playwright/e2e/crypto/toasts.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7b838edaacd79d33c3c11c4e554357dbb54f62f2
--- /dev/null
+++ b/playwright/e2e/crypto/toasts.spec.ts
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
+
+import { test, expect } from "../../element-web-test";
+import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement } from "./utils";
+import { type Bot } from "../../pages/bot";
+
+test.describe("Key storage out of sync toast", () => {
+    let recoveryKey: GeneratedSecretStorageKey;
+
+    test.beforeEach(async ({ page, homeserver, credentials }) => {
+        const res = await createBot(page, homeserver, credentials);
+        recoveryKey = res.recoveryKey;
+
+        await logIntoElement(page, credentials, recoveryKey.encodedPrivateKey);
+
+        await deleteCachedSecrets(page);
+
+        // We won't be prompted for crypto setup unless we have an e2e room, so make one
+        await page.getByRole("button", { name: "Add room" }).click();
+        await page.getByRole("menuitem", { name: "New room" }).click();
+        await page.getByRole("textbox", { name: "Name" }).fill("Test room");
+        await page.getByRole("button", { name: "Create room" }).click();
+    });
+
+    test("should prompt for recovery key if 'enter recovery key' pressed", { tag: "@screenshot" }, async ({ page }) => {
+        // We need to wait for there to be two toasts as the wait below won't work in isolation:
+        // playwright only evaluates the 'first()' call initially, not subsequent times it checks, so
+        // it would always be checking the same toast, even if another one is now the first.
+        await expect(page.getByRole("alert")).toHaveCount(2);
+        await expect(page.getByRole("alert").first()).toMatchScreenshot("key-storage-out-of-sync-toast.png");
+
+        await page.getByRole("button", { name: "Enter recovery key" }).click();
+
+        await page.getByRole("textbox", { name: "Recovery Key" }).fill(recoveryKey.encodedPrivateKey);
+        await page.getByRole("button", { name: "Continue" }).click();
+
+        await expect(page.getByRole("button", { name: "Enter recovery key" })).not.toBeVisible();
+    });
+
+    test("should open settings to reset flow if 'forgot recovery key' pressed", async ({ page, app, credentials }) => {
+        await expect(page.getByRole("button", { name: "Enter recovery key" })).toBeVisible();
+
+        await page.getByRole("button", { name: "Forgot recovery key?" }).click();
+
+        await expect(
+            page.getByRole("heading", { name: "Forgot your recovery key? You’ll need to reset your identity." }),
+        ).toBeVisible();
+    });
+});
+
+test.describe("'Turn on key storage' toast", () => {
+    let botClient: Bot | undefined;
+
+    test.beforeEach(async ({ page, homeserver, credentials, toasts }) => {
+        // Set up all crypto stuff. Key storage defaults to on.
+
+        const res = await createBot(page, homeserver, credentials);
+        const recoveryKey = res.recoveryKey;
+        botClient = res.botClient;
+
+        await logIntoElement(page, credentials, recoveryKey.encodedPrivateKey);
+
+        // We won't be prompted for crypto setup unless we have an e2e room, so make one
+        await page.getByRole("button", { name: "Add room" }).click();
+        await page.getByRole("menuitem", { name: "New room" }).click();
+        await page.getByRole("textbox", { name: "Name" }).fill("Test room");
+        await page.getByRole("button", { name: "Create room" }).click();
+
+        await toasts.rejectToast("Notifications");
+    });
+
+    test("should not show toast if key storage is on", async ({ page, toasts }) => {
+        // Given the default situation after signing in
+        // Then no toast is shown (because key storage is on)
+        await toasts.assertNoToasts();
+
+        // When we reload
+        await page.reload();
+
+        // Give the toasts time to appear
+        await new Promise((resolve) => setTimeout(resolve, 2000));
+
+        // Then still no toast is shown
+        await toasts.assertNoToasts();
+    });
+
+    test("should not show toast if key storage is off because we turned it off", async ({ app, page, toasts }) => {
+        // Given the backup is disabled because we disabled it
+        await disableKeyBackup(app);
+
+        // Then no toast is shown
+        await toasts.assertNoToasts();
+
+        // When we reload
+        await page.reload();
+
+        // Give the toasts time to appear
+        await new Promise((resolve) => setTimeout(resolve, 2000));
+
+        // Then still no toast is shown
+        await toasts.assertNoToasts();
+    });
+
+    test("should show toast if key storage is off but account data is missing", async ({ app, page, toasts }) => {
+        // Given the backup is disabled but we didn't set account data saying that is expected
+        await disableKeyBackup(app);
+        await botClient.setAccountData("m.org.matrix.custom.backup_disabled", { disabled: false });
+
+        // Wait for the account data setting to stick
+        await new Promise((resolve) => setTimeout(resolve, 2000));
+
+        // When we enter the app
+        await page.reload();
+
+        // Then the toast is displayed
+        let toast = await toasts.getToast("Turn on key storage");
+
+        // And when we click "Continue"
+        await toast.getByRole("button", { name: "Continue" }).click();
+
+        // Then we see the Encryption settings dialog with an option to turn on key storage
+        await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
+
+        // And when we close that
+        await page.getByRole("button", { name: "Close dialog" }).click();
+
+        // Then we see the toast again
+        toast = await toasts.getToast("Turn on key storage");
+
+        // And when we click "Dismiss"
+        await toast.getByRole("button", { name: "Dismiss" }).click();
+
+        // Then we see the "are you sure?" dialog
+        await expect(
+            page.getByRole("heading", { name: "Are you sure you want to keep key storage turned off?" }),
+        ).toBeVisible();
+
+        // And when we close it by clicking away
+        await page.getByTestId("dialog-background").click({ force: true, position: { x: 10, y: 10 } });
+
+        // Then we see the toast again
+        toast = await toasts.getToast("Turn on key storage");
+
+        // And when we click Dismiss and then "Go to Settings"
+        await toast.getByRole("button", { name: "Dismiss" }).click();
+        await page.getByRole("button", { name: "Go to Settings" }).click();
+
+        // Then we see Encryption settings again
+        await expect(page.getByRole("checkbox", { name: "Allow key storage" })).toBeVisible();
+
+        // And when we close that, see the toast, click Dismiss, and Yes, Dismiss
+        await page.getByRole("button", { name: "Close dialog" }).click();
+        toast = await toasts.getToast("Turn on key storage");
+        await toast.getByRole("button", { name: "Dismiss" }).click();
+        await page.getByRole("button", { name: "Yes, dismiss" }).click();
+
+        // Then the toast is gone
+        await toasts.assertNoToasts();
+    });
+});
diff --git a/playwright/e2e/crypto/user-verification.spec.ts b/playwright/e2e/crypto/user-verification.spec.ts
index 175c8d5fdfd519316390c6b90cf315a880a3ccc3..ebe86c0a6e0d1dfdf1e249f32388b820dc2d8bc1 100644
--- a/playwright/e2e/crypto/user-verification.spec.ts
+++ b/playwright/e2e/crypto/user-verification.spec.ts
@@ -8,10 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix";
 
-import type { Page } from "@playwright/test";
 import { test, expect } from "../../element-web-test";
-import { doTwoWaySasVerification, awaitVerifier } from "./utils";
-import { Client } from "../../pages/client";
+import { doTwoWaySasVerification, awaitVerifier, waitForDevices } from "./utils";
+import { type Client } from "../../pages/client";
 
 test.describe("User verification", () => {
     // note that there are other tests that check user verification works in `crypto.spec.ts`.
@@ -33,13 +32,17 @@ test.describe("User verification", () => {
     });
 
     test("can receive a verification request when there is no existing DM", async ({
+        app,
         page,
         bot: bob,
         user: aliceCredentials,
         toasts,
         room: { roomId: dmRoomId },
     }) => {
-        await waitForDeviceKeys(page);
+        await waitForDevices(app, bob.credentials.userId, 1);
+        await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
+        const avatar = page.getByRole("button", { name: "Avatar" });
+        await avatar.click();
 
         // once Alice has joined, Bob starts the verification
         const bobVerificationRequest = await bob.evaluateHandle(
@@ -84,13 +87,17 @@ test.describe("User verification", () => {
     });
 
     test("can abort emoji verification when emoji mismatch", async ({
+        app,
         page,
         bot: bob,
         user: aliceCredentials,
         toasts,
         room: { roomId: dmRoomId },
     }) => {
-        await waitForDeviceKeys(page);
+        await waitForDevices(app, bob.credentials.userId, 1);
+        await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
+        const avatar = page.getByRole("button", { name: "Avatar" });
+        await avatar.click();
 
         // once Alice has joined, Bob starts the verification
         const bobVerificationRequest = await bob.evaluateHandle(
@@ -154,15 +161,3 @@ async function createDMRoom(client: Client, userId: string): Promise<string> {
         ],
     });
 }
-
-/**
- * Wait until we get the other user's device keys.
- * In newer rust-crypto versions, the verification request will be ignored if we
- * don't have the sender's device keys.
- */
-async function waitForDeviceKeys(page: Page): Promise<void> {
-    await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
-    const avatar = await page.getByRole("button", { name: "Avatar" });
-    await avatar.click();
-    await expect(page.getByText("1 session")).toBeVisible();
-}
diff --git a/playwright/e2e/crypto/utils.ts b/playwright/e2e/crypto/utils.ts
index 7474c5a4354a42c65a1a5ae8a44640fcc6bf96ca..289b123e86e7c38abad0bf32c40c9b32ca2eb515 100644
--- a/playwright/e2e/crypto/utils.ts
+++ b/playwright/e2e/crypto/utils.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { expect, JSHandle, type Page } from "@playwright/test";
+import { expect, type JSHandle, type Page } from "@playwright/test";
 
 import type { ICreateRoomOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
 import type {
@@ -18,9 +18,9 @@ import type {
     Verifier,
     VerifierEvent,
 } from "matrix-js-sdk/src/crypto-api";
-import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
-import { Client } from "../../pages/client";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type Credentials, type HomeserverInstance } from "../../plugins/homeserver";
+import { type Client } from "../../pages/client";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 import { Bot } from "../../pages/bot";
 
 /**
@@ -28,11 +28,13 @@ import { Bot } from "../../pages/bot";
  * @param page - the playwright `page` fixture
  * @param homeserver - the homeserver to use
  * @param credentials - the credentials to use for the bot client
+ * @param usePassphrase - whether to use a passphrase when creating the recovery key
  */
 export async function createBot(
     page: Page,
     homeserver: HomeserverInstance,
     credentials: Credentials,
+    usePassphrase = false,
 ): Promise<{ botClient: Bot; recoveryKey: GeneratedSecretStorageKey; expectedBackupVersion: string }> {
     // Visit the login page of the app, to load the matrix sdk
     await page.goto("/#/login");
@@ -44,6 +46,7 @@ export async function createBot(
     const botClient = new Bot(page, homeserver, {
         bootstrapCrossSigning: true,
         bootstrapSecretStorage: true,
+        usePassphrase,
     });
     botClient.setCredentials(credentials);
     // Backup is prepared in the background. Poll until it is ready.
@@ -139,14 +142,16 @@ export async function checkDeviceIsCrossSigned(app: ElementAppPage): Promise<voi
  * Check that the current device is connected to the expected key backup.
  * Also checks that the decryption key is known and cached locally.
  *
- * @param page - the page to check
+ * @param app -` ElementAppPage` wrapper for the playwright `Page`.
  * @param expectedBackupVersion - the version of the backup we expect to be connected to.
- * @param checkBackupKeyInCache - whether to check that the backup key is cached locally.
+ * @param checkBackupPrivateKeyInCache - whether to check that the backup decryption key is cached locally
+ * @param checkBackupKeyIn4S - whether to check that the backup key is stored in 4S
  */
 export async function checkDeviceIsConnectedKeyBackup(
-    page: Page,
+    app: ElementAppPage,
     expectedBackupVersion: string,
-    checkBackupKeyInCache: boolean,
+    checkBackupPrivateKeyInCache: boolean,
+    checkBackupKeyIn4S: boolean = true,
 ): Promise<void> {
     // Sanity check the given backup version: if it's null, something went wrong earlier in the test.
     if (!expectedBackupVersion) {
@@ -155,23 +160,48 @@ export async function checkDeviceIsConnectedKeyBackup(
         );
     }
 
-    await page.getByRole("button", { name: "User menu" }).click();
-    await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Security & Privacy" }).click();
-    await expect(page.locator(".mx_Dialog").getByRole("button", { name: "Restore from Backup" })).toBeVisible();
-
-    // expand the advanced section to see the active version in the reports
-    await page.locator(".mx_SecureBackupPanel_advanced").locator("..").click();
+    const backupData = await app.client.evaluate(async (client: MatrixClient) => {
+        const crypto = client.getCrypto();
+        if (!crypto) return;
+
+        const backupInfo = await crypto.getKeyBackupInfo();
+        const backupKeyIn4S = Boolean(await client.isKeyBackupKeyStored());
+        const backupPrivateKeyFromCache = await crypto.getSessionBackupPrivateKey();
+        const hasBackupPrivateKeyFromCache = Boolean(backupPrivateKeyFromCache);
+        const backupPrivateKeyWellFormed = backupPrivateKeyFromCache instanceof Uint8Array;
+        const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
+
+        return {
+            backupInfo,
+            hasBackupPrivateKeyFromCache,
+            backupPrivateKeyWellFormed,
+            backupKeyIn4S,
+            activeBackupVersion,
+        };
+    });
 
-    if (checkBackupKeyInCache) {
-        const cacheDecryptionKeyStatusElement = page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(2) td");
-        await expect(cacheDecryptionKeyStatusElement).toHaveText("cached locally, well formed");
+    if (!backupData) {
+        throw new Error("Crypto module is not available");
     }
 
-    await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
-        expectedBackupVersion + " (Algorithm: m.megolm_backup.v1.curve25519-aes-sha2)",
-    );
-
-    await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(expectedBackupVersion);
+    const { backupInfo, backupKeyIn4S, hasBackupPrivateKeyFromCache, backupPrivateKeyWellFormed, activeBackupVersion } =
+        backupData;
+
+    // We have a key backup
+    expect(backupInfo).toBeDefined();
+    // The key backup version is as expected
+    expect(backupInfo.version).toBe(expectedBackupVersion);
+    // The active backup version is as expected
+    expect(activeBackupVersion).toBe(expectedBackupVersion);
+    // The backup key is stored in 4S
+    if (checkBackupKeyIn4S) expect(backupKeyIn4S).toBe(true);
+
+    if (checkBackupPrivateKeyInCache) {
+        // The backup key is available locally
+        expect(hasBackupPrivateKeyFromCache).toBe(true);
+        // The backup key is well-formed
+        expect(backupPrivateKeyWellFormed).toBe(true);
+    }
 }
 
 /**
@@ -188,10 +218,18 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
 
     // if a securityKey was given, verify the new device
     if (securityKey !== undefined) {
-        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Security Key" }).click();
-        // Fill in the security key
-        await page.locator(".mx_Dialog").locator('input[type="password"]').fill(securityKey);
-        await page.locator(".mx_Dialog_primary:not([disabled])", { hasText: "Continue" }).click();
+        await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key" }).click();
+
+        const useSecurityKey = page.locator(".mx_Dialog").getByRole("button", { name: "use your Recovery Key" });
+        // If the user has set a recovery *passphrase*, they'll be prompted for that first and have to click
+        // through to enter the recovery key which is what we have here. If they haven't, they'll be prompted
+        // for a recovery key straight away. We click the button if it's there so this works in both cases.
+        if (await useSecurityKey.isVisible()) {
+            await useSecurityKey.click();
+        }
+        // Fill in the recovery key
+        await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
+        await page.getByRole("button", { name: "Continue", disabled: false }).click();
         await page.getByRole("button", { name: "Done" }).click();
     }
 }
@@ -216,18 +254,19 @@ export async function logOutOfElement(page: Page, discardKeys: boolean = false)
 }
 
 /**
- * Open the security settings, and verify the current session using the security key.
+ * Open the encryption settings, and verify the current session using the recovery key.
  *
  * @param app - `ElementAppPage` wrapper for the playwright `Page`.
- * @param securityKey - The security key (i.e., 4S key), set up during a previous session.
+ * @param securityKey - The recovery key (i.e., 4S key), set up during a previous session.
  */
 export async function verifySession(app: ElementAppPage, securityKey: string) {
-    const settings = await app.settings.openUserSettings("Security & Privacy");
-    await settings.getByRole("button", { name: "Verify this session" }).click();
-    await app.page.getByRole("button", { name: "Verify with Security Key" }).click();
-    await app.page.locator(".mx_Dialog").locator('input[type="password"]').fill(securityKey);
+    const settings = await app.settings.openUserSettings("Encryption");
+    await settings.getByRole("button", { name: "Verify this device" }).click();
+    await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
+    await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
     await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
     await app.page.getByRole("button", { name: "Done" }).click();
+    await app.settings.closeDialog();
 }
 
 /**
@@ -253,32 +292,95 @@ export async function doTwoWaySasVerification(page: Page, verifier: JSHandle<Ver
 }
 
 /**
- * Open the security settings and enable secure key backup.
- *
- * Assumes that the current device has been cross-signed (which means that we skip a step where we set it up).
+ * Open the encryption settings and enable key storage and recovery
+ * Assumes that the current device has been verified
  *
- * Returns the security key
+ * Returns the recovery key
  */
 export async function enableKeyBackup(app: ElementAppPage): Promise<string> {
-    await app.settings.openUserSettings("Security & Privacy");
-    await app.page.getByRole("button", { name: "Set up Secure Backup" }).click();
-    const dialog = app.page.locator(".mx_Dialog");
-    // Recovery key is selected by default
-    await dialog.getByRole("button", { name: "Continue" }).click({ timeout: 60000 });
+    const encryptionTab = await app.settings.openUserSettings("Encryption");
 
-    // copy the text ourselves
-    const securityKey = await dialog.locator(".mx_CreateSecretStorageDialog_recoveryKey code").textContent();
-    await copyAndContinue(app.page);
+    const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
+    if (!(await keyStorageToggle.isChecked())) {
+        await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
+    }
 
-    await expect(dialog.getByText("Secure Backup successful")).toBeVisible();
-    await dialog.getByRole("button", { name: "Done" }).click();
-    await expect(dialog.getByText("Secure Backup successful")).not.toBeVisible();
+    await encryptionTab.getByRole("button", { name: "Set up recovery" }).click();
+    await encryptionTab.getByRole("button", { name: "Continue" }).click();
 
-    return securityKey;
+    const recoveryKey = await encryptionTab.getByTestId("recoveryKey").innerText();
+    await encryptionTab.getByRole("button", { name: "Continue" }).click();
+    await encryptionTab.getByRole("textbox").fill(recoveryKey);
+    await encryptionTab.getByRole("button", { name: "Finish set up" }).click();
+    await app.settings.closeDialog();
+    return recoveryKey;
 }
 
 /**
- * Click on copy and continue buttons to dismiss the security key dialog
+ * Open the encryption settings and disable key storage (and recovery)
+ * Assumes that the current device has been verified
+ */
+export async function disableKeyBackup(app: ElementAppPage): Promise<void> {
+    const encryptionTab = await app.settings.openUserSettings("Encryption");
+
+    const keyStorageToggle = encryptionTab.getByRole("checkbox", { name: "Allow key storage" });
+    if (await keyStorageToggle.isChecked()) {
+        await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).click();
+        await encryptionTab.getByRole("button", { name: "Delete key storage" }).click();
+        await encryptionTab.getByRole("checkbox", { name: "Allow key storage" }).isVisible();
+
+        // Wait for the update to account data to stick
+        await new Promise((resolve) => setTimeout(resolve, 2000));
+    }
+    await app.settings.closeDialog();
+}
+
+/**
+ * Go through the "Set up Secure Backup" dialog (aka the `CreateSecretStorageDialog`).
+ *
+ * Assumes the dialog is already open for some reason (see also {@link enableKeyBackup}).
+ *
+ * @param page - The playwright `Page` fixture.
+ * @param opts - Options object
+ * @param opts.accountPassword - The user's account password. If we are also resetting cross-signing, then we will need
+ *   to upload the public cross-signing keys, which will cause the app to prompt for the password.
+ *
+ * @returns the new recovery key.
+ */
+export async function completeCreateSecretStorageDialog(
+    page: Page,
+    opts?: { accountPassword?: string },
+): Promise<string> {
+    const currentDialogLocator = page.locator(".mx_Dialog");
+
+    await expect(currentDialogLocator.getByRole("heading", { name: "Set up Secure Backup" })).toBeVisible();
+    // "Generate a Recovery Key" is selected by default
+    await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
+    await expect(currentDialogLocator.getByRole("heading", { name: "Save your Recovery Key" })).toBeVisible();
+    await currentDialogLocator.getByRole("button", { name: "Copy", exact: true }).click();
+    // copy the recovery key to use it later
+    const recoveryKey = await page.evaluate(() => navigator.clipboard.readText());
+    await currentDialogLocator.getByRole("button", { name: "Continue", exact: true }).click();
+
+    // If the device is unverified, there should be a "Setting up keys" step.
+    // If this is not the first time we are setting up cross-signing, the app will prompt for our password; otherwise
+    // the step is quite quick, and playwright can miss it, so we can't test for it.
+    if (opts && Object.hasOwn(opts, "accountPassword")) {
+        await expect(currentDialogLocator.getByRole("heading", { name: "Setting up keys" })).toBeVisible();
+        await page.getByPlaceholder("Password").fill(opts!.accountPassword);
+        await currentDialogLocator.getByRole("button", { name: "Continue" }).click();
+    }
+
+    // Either way, we end up at a success dialog:
+    await expect(currentDialogLocator.getByRole("heading", { name: "Secure Backup successful" })).toBeVisible();
+    await currentDialogLocator.getByRole("button", { name: "Done", exact: true }).click();
+    await expect(currentDialogLocator.getByText("Secure Backup successful")).not.toBeVisible();
+
+    return recoveryKey;
+}
+
+/**
+ * Click on copy and continue buttons to dismiss the recovery key dialog
  */
 export async function copyAndContinue(page: Page) {
     await page.getByRole("button", { name: "Copy" }).click();
@@ -435,3 +537,31 @@ export async function deleteCachedSecrets(page: Page) {
     });
     await page.reload();
 }
+
+/**
+ * Wait until the given user has a given number of devices.
+ * This function will check the device keys ten times and if
+ * the expected number of devices were not found by then, an
+ * error is thrown.
+ */
+export async function waitForDevices(
+    app: ElementAppPage,
+    userId: string,
+    expectedNumberOfDevices: number,
+): Promise<void> {
+    const result = await app.client.evaluate(
+        async (cli, { userId, expectedNumberOfDevices }) => {
+            for (let i = 0; i < 10; ++i) {
+                const userDeviceMap = await cli.getCrypto()?.getUserDeviceInfo([userId], true);
+                const deviceMap = userDeviceMap?.get(userId);
+                if (deviceMap.size === expectedNumberOfDevices) return true;
+                await new Promise((r) => setTimeout(r, 500));
+            }
+            return false;
+        },
+        { userId, expectedNumberOfDevices },
+    );
+    if (!result) {
+        throw new Error(`User ${userId} did not have ${expectedNumberOfDevices} devices within ten iterations!`);
+    }
+}
diff --git a/playwright/e2e/csAPI.ts b/playwright/e2e/csAPI.ts
index 4153d09199c56ceb68b5806b98dc8914e77e0a8c..c622ac99cec9568ba788e7b1c467b548a286d6fc 100644
--- a/playwright/e2e/csAPI.ts
+++ b/playwright/e2e/csAPI.ts
@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
 Please see LICENSE files in the repository root for full details.
 */
 
-import { APIRequestContext } from "playwright-core";
-import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { type APIRequestContext } from "@playwright/test";
+import { type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { ClientServerApi } from "@element-hq/element-web-playwright-common/lib/utils/api.js";
 
-import { HomeserverInstance } from "../plugins/homeserver";
-import { ClientServerApi } from "../plugins/utils/api.ts";
+import { type HomeserverInstance } from "../plugins/homeserver";
 
 /**
  * A small subset of the Client-Server API used to manipulate the state of the
diff --git a/playwright/e2e/editing/editing.spec.ts b/playwright/e2e/editing/editing.spec.ts
index 934c4aa42ec841f503a0663a889a0ceb6bf25846..6f8e68bbc3d6ba99e0fbce196287fe3e72dccca0 100644
--- a/playwright/e2e/editing/editing.spec.ts
+++ b/playwright/e2e/editing/editing.spec.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator, Page } from "@playwright/test";
+import { type Locator, type Page } from "@playwright/test";
 
 import type { EventType, IContent, ISendEventResponse, MsgType, Visibility } from "matrix-js-sdk/src/matrix";
 import { expect, test } from "../../element-web-test";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 import { SettingLevel } from "../../../src/settings/SettingLevel";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
@@ -267,7 +267,6 @@ test.describe("Editing", () => {
         app,
         room,
         axe,
-        checkA11y,
     }) => {
         axe.disableRules("color-contrast"); // XXX: We have some known contrast issues here
 
@@ -282,7 +281,7 @@ test.describe("Editing", () => {
             const line = tile.locator(".mx_EventTile_line");
             await line.hover();
             await line.getByRole("button", { name: "Edit" }).click();
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
             const editComposer = page.getByRole("textbox", { name: "Edit message" });
             await editComposer.pressSequentially("Foo");
             await editComposer.press("Backspace");
@@ -290,7 +289,7 @@ test.describe("Editing", () => {
             await editComposer.press("Backspace");
             await editComposer.press("Enter");
             await app.getComposerField().hover(); // XXX: move the hover to get rid of the "Edit" tooltip
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
         }
         await expect(
             page.locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", { hasText: "Message" }),
@@ -305,7 +304,6 @@ test.describe("Editing", () => {
         user,
         app,
         axe,
-        checkA11y,
         bot: bob,
     }) => {
         // This tests the behaviour when a message has been edited some time after it has been sent, and we
diff --git a/playwright/e2e/forgot-password/forgot-password.spec.ts b/playwright/e2e/forgot-password/forgot-password.spec.ts
index af4e6def7edc8f4f4ca152d01540d901683ea996..d075afda734a54910e2489d2e9ce91eab8655b61 100644
--- a/playwright/e2e/forgot-password/forgot-password.spec.ts
+++ b/playwright/e2e/forgot-password/forgot-password.spec.ts
@@ -6,22 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { expect, test as base } from "../../element-web-test";
+import { type CredentialsWithDisplayName, expect, test as base } from "../../element-web-test";
 import { selectHomeserver } from "../utils";
 import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
-import { Credentials } from "../../plugins/homeserver";
 
 const email = "user@nowhere.dummy";
 
-const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
+const test = base.extend({
     // eslint-disable-next-line no-empty-pattern
     credentials: async ({}, use, testInfo) => {
         await use({
             username: `user_${testInfo.testId}`,
             // this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
             password: "oETo7MPf0o",
-        });
+        } as CredentialsWithDisplayName);
     },
 });
 
diff --git a/playwright/e2e/invite/invite-dialog.spec.ts b/playwright/e2e/invite/invite-dialog.spec.ts
index 73238f8c3dca16af084ee64b8cef153e403e6924..8d64e6e04743b02d7587dd9f92d5588a4f317ce8 100644
--- a/playwright/e2e/invite/invite-dialog.spec.ts
+++ b/playwright/e2e/invite/invite-dialog.spec.ts
@@ -77,7 +77,7 @@ test.describe("Invite dialog", function () {
         "should support inviting a user to Direct Messages",
         { tag: "@screenshot" },
         async ({ page, app, user, bot }) => {
-            await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
+            await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
 
             const other = page.locator(".mx_InviteDialog_other");
             // Assert that the header is rendered
diff --git a/playwright/e2e/lazy-loading/lazy-loading.spec.ts b/playwright/e2e/lazy-loading/lazy-loading.spec.ts
index 06fb0b3a71eecfaea500da867800a004ec523c2a..7c31c288fa9e29a3d3d3effe59ce3ce6ecf8e776 100644
--- a/playwright/e2e/lazy-loading/lazy-loading.spec.ts
+++ b/playwright/e2e/lazy-loading/lazy-loading.spec.ts
@@ -10,7 +10,7 @@ import { Bot } from "../../pages/bot";
 import type { Locator, Page } from "@playwright/test";
 import type { ElementAppPage } from "../../pages/ElementAppPage";
 import { test, expect } from "../../element-web-test";
-import { Credentials } from "../../plugins/homeserver";
+import { type Credentials } from "../../plugins/homeserver";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
 test.describe("Lazy Loading", () => {
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d3aa060a2721f561400991092a6447feb1a33177
--- /dev/null
+++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Visibility } from "matrix-js-sdk/src/matrix";
+import { type Locator, type Page } from "@playwright/test";
+
+import { expect, test } from "../../../element-web-test";
+import { SettingLevel } from "../../../../src/settings/SettingLevel";
+
+test.describe("Room list filters and sort", () => {
+    test.use({
+        displayName: "Alice",
+        botCreateOpts: {
+            displayName: "BotBob",
+            autoAcceptInvites: true,
+        },
+        labsFlags: ["feature_new_room_list"],
+    });
+
+    function getPrimaryFilters(page: Page): Locator {
+        return page.getByRole("listbox", { name: "Room list filters" });
+    }
+
+    function getRoomOptionsMenu(page: Page): Locator {
+        return page.getByRole("button", { name: "Room Options" });
+    }
+
+    /**
+     * Get the room list
+     * @param page
+     */
+    function getRoomList(page: Page) {
+        return page.getByTestId("room-list");
+    }
+
+    test.beforeEach(async ({ page, app, bot, user }) => {
+        // The notification toast is displayed above the search section
+        await app.closeNotificationToast();
+    });
+
+    test("Tombstoned rooms are not shown even when they receive updates", async ({ page, app, bot }) => {
+        // This bug shows up with this setting turned on
+        await app.settings.setValue("Spaces.allRoomsInHome", null, SettingLevel.DEVICE, true);
+
+        /*
+        We will first create a room named 'Old Room' and will invite the bot user to this room.
+        We will also send a simple message in this room.
+        */
+        const oldRoomId = await app.client.createRoom({ name: "Old Room" });
+        await app.client.inviteUser(oldRoomId, bot.credentials.userId);
+        await bot.joinRoom(oldRoomId);
+        const response = await app.client.sendMessage(oldRoomId, "Hello!");
+
+        /*
+        At this point, we haven't done anything interesting.
+        So we expect 'Old Room' to show up in the room list.
+        */
+        const roomListView = getRoomList(page);
+        const oldRoomTile = roomListView.getByRole("gridcell", { name: "Open room Old Room" });
+        await expect(oldRoomTile).toBeVisible();
+
+        /*
+        Now let's tombstone 'Old Room'.
+        First we create a new room ('New Room') with the predecessor set to the old room..
+        */
+        const newRoomId = await bot.createRoom({
+            name: "New Room",
+            creation_content: {
+                predecessor: {
+                    event_id: response.event_id,
+                    room_id: oldRoomId,
+                },
+            },
+            visibility: "public" as Visibility,
+        });
+
+        /*
+        Now we can send the tombstone event itself to the 'Old Room'.
+        */
+        await app.client.sendStateEvent(oldRoomId, "m.room.tombstone", {
+            body: "This room has been replaced",
+            replacement_room: newRoomId,
+        });
+
+        // Let's join the replaced room.
+        await app.client.joinRoom(newRoomId);
+
+        // We expect 'Old Room' to be hidden from the room list.
+        await expect(oldRoomTile).not.toBeVisible();
+
+        /*
+        Let's say some user in the 'Old Room' changes their display name.
+        This will send events to the all the rooms including 'Old Room'.
+        Nevertheless, the replaced room should not be shown in the room list.
+        */
+        await bot.setDisplayName("MyNewName");
+        await expect(oldRoomTile).not.toBeVisible();
+    });
+
+    test.describe("Scroll behaviour", () => {
+        test("should scroll to the top of list when filter is applied and active room is not in filtered list", async ({
+            page,
+            app,
+        }) => {
+            const createFavouriteRoom = async (name: string) => {
+                const id = await app.client.createRoom({
+                    name,
+                });
+                await app.client.evaluate(async (client, favouriteId) => {
+                    await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
+                }, id);
+            };
+
+            // Create 5 favourite rooms
+            let i = 0;
+            for (; i < 5; i++) {
+                await createFavouriteRoom(`room${i}-fav`);
+            }
+
+            // Create a non-favourite room
+            await app.client.createRoom({ name: `room-non-fav` });
+
+            // Create rest of the favourite rooms
+            for (; i < 20; i++) {
+                await createFavouriteRoom(`room${i}-fav`);
+            }
+
+            // Open the non-favourite room
+            const roomListView = getRoomList(page);
+            const tile = roomListView.getByRole("gridcell", { name: "Open room room-non-fav" });
+            await tile.scrollIntoViewIfNeeded();
+            await tile.click();
+
+            // Enable Favourite filter
+            const primaryFilters = getPrimaryFilters(page);
+            await primaryFilters.getByRole("option", { name: "Favourite" }).click();
+            await expect(tile).not.toBeVisible();
+
+            // Ensure the room list is not scrolled
+            const isScrolledDown = await page
+                .getByRole("grid", { name: "Room list" })
+                .evaluate((e) => e.scrollTop !== 0);
+            expect(isScrolledDown).toStrictEqual(false);
+        });
+    });
+
+    test.describe("Room list", () => {
+        let unReadDmId: string | undefined;
+        let unReadRoomId: string | undefined;
+
+        test.beforeEach(async ({ page, app, bot, user }) => {
+            await app.client.createRoom({ name: "empty room" });
+
+            unReadDmId = await bot.createRoom({
+                name: "unread dm",
+                invite: [user.userId],
+                is_direct: true,
+            });
+            await app.client.joinRoom(unReadDmId);
+            await bot.sendMessage(unReadDmId, "I am a robot. Beep.");
+
+            unReadRoomId = await app.client.createRoom({ name: "unread room" });
+            await app.client.inviteUser(unReadRoomId, bot.credentials.userId);
+            await bot.joinRoom(unReadRoomId);
+            await bot.sendMessage(unReadRoomId, "I am a robot. Beep.");
+
+            const favouriteId = await app.client.createRoom({ name: "favourite room" });
+            await app.client.evaluate(async (client, favouriteId) => {
+                await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
+            }, favouriteId);
+
+            const lowPrioId = await app.client.createRoom({ name: "Low prio room" });
+            await app.client.evaluate(async (client, id) => {
+                await client.setRoomTag(id, "m.lowpriority", { order: 0.5 });
+            }, lowPrioId);
+
+            await bot.createRoom({
+                name: "invited room",
+                invite: [user.userId],
+                is_direct: true,
+            });
+
+            const mentionRoomId = await app.client.createRoom({ name: "room with mention" });
+            await app.client.inviteUser(mentionRoomId, bot.credentials.userId);
+            await bot.joinRoom(mentionRoomId);
+
+            const clientBot = await bot.prepareClient();
+            await clientBot.evaluate(
+                async (client, { mentionRoomId, userId }) => {
+                    await client.sendMessage(mentionRoomId, {
+                        // @ts-ignore ignore usage of MsgType.text
+                        "msgtype": "m.text",
+                        "body": "User",
+                        "format": "org.matrix.custom.html",
+                        "formatted_body": `<a href="https://matrix.to/#/${userId}">User</a>`,
+                        "m.mentions": {
+                            user_ids: [userId],
+                        },
+                    });
+                },
+                { mentionRoomId, userId: user.userId },
+            );
+        });
+
+        test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            const roomList = getRoomList(page);
+            const primaryFilters = getPrimaryFilters(page);
+
+            const allFilters = await primaryFilters.locator("option").all();
+            for (const filter of allFilters) {
+                expect(await filter.getAttribute("aria-selected")).toBe("false");
+            }
+            await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png");
+
+            await primaryFilters.getByRole("option", { name: "Unread" }).click();
+            // only one room should be visible
+            await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(4);
+            await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
+
+            await primaryFilters.getByRole("option", { name: "Favourite" }).click();
+            await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(1);
+
+            await primaryFilters.getByRole("option", { name: "People" }).click();
+            await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(2);
+
+            await primaryFilters.getByRole("option", { name: "Rooms" }).click();
+            await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
+            await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(5);
+
+            await primaryFilters.getByRole("option", { name: "Mentions" }).click();
+            await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(1);
+
+            await primaryFilters.getByRole("option", { name: "Invites" }).click();
+            await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible();
+            expect(await roomList.locator("role=gridcell").count()).toBe(1);
+        });
+
+        test(
+            "unread filter should only match unread rooms that have a count",
+            { tag: "@screenshot" },
+            async ({ page, app, bot }) => {
+                const roomListView = getRoomList(page);
+
+                // Let's configure unread dm room so that we only get notification for mentions and keywords
+                await app.viewRoomById(unReadDmId);
+                await app.settings.openRoomSettings("Notifications");
+                await page.getByText("@mentions & keywords").click();
+                await app.settings.closeDialog();
+
+                // Let's open a room other than unread room or unread dm
+                await roomListView.getByRole("gridcell", { name: "Open room favourite room" }).click();
+
+                // Let's make the bot send a new message in both rooms
+                await bot.sendMessage(unReadDmId, "Hello!");
+                await bot.sendMessage(unReadRoomId, "Hello!");
+
+                // Let's activate the unread filter now
+                await page.getByRole("option", { name: "Unread" }).click();
+
+                // Unread filter should only show unread room and not unread dm!
+                const unreadDm = roomListView.getByRole("gridcell", { name: "Open room unread room" });
+                await expect(unreadDm).toBeVisible();
+                await expect(unreadDm).toMatchScreenshot("unread-dm.png");
+                await expect(roomListView.getByRole("gridcell", { name: "Open room unread dm" })).not.toBeVisible();
+            },
+        );
+
+        test("should sort the room list alphabetically", async ({ page }) => {
+            const roomListView = getRoomList(page);
+
+            await getRoomOptionsMenu(page).click();
+            await page.getByRole("menuitemradio", { name: "A-Z" }).click();
+
+            await expect(roomListView.getByRole("gridcell").first()).toHaveText(/empty room/);
+        });
+
+        test("should move room to the top on message when sorting by activity", async ({ page, bot }) => {
+            const roomListView = getRoomList(page);
+
+            await bot.sendMessage(unReadDmId, "Hello!");
+
+            await expect(roomListView.getByRole("gridcell").first()).toHaveText(/unread dm/);
+        });
+    });
+
+    test.describe("Empty room list", () => {
+        /**
+         * Get the empty state
+         * @param page
+         */
+        function getEmptyRoomList(page: Page) {
+            return page.getByTestId("empty-room-list");
+        }
+
+        test(
+            "should render the default placeholder when there is no filter",
+            { tag: "@screenshot" },
+            async ({ page, app, user }) => {
+                const emptyRoomList = getEmptyRoomList(page);
+                await expect(emptyRoomList).toMatchScreenshot("default-empty-room-list.png");
+                await expect(page.getByTestId("room-list-panel")).toMatchScreenshot("room-panel-empty-room-list.png");
+            },
+        );
+
+        [
+            { filter: "Unreads", action: "Show all chats" },
+            { filter: "Mentions", action: "See all activity" },
+            { filter: "Invites", action: "See all activity" },
+        ].forEach(({ filter, action }) => {
+            test(
+                `should render the placeholder for ${filter} filter`,
+                { tag: "@screenshot" },
+                async ({ page, app, user }) => {
+                    const primaryFilters = getPrimaryFilters(page);
+                    await primaryFilters.getByRole("option", { name: filter }).click();
+
+                    const emptyRoomList = getEmptyRoomList(page);
+                    await expect(emptyRoomList).toMatchScreenshot(`${filter}-empty-room-list.png`);
+
+                    await emptyRoomList.getByRole("button", { name: action }).click();
+                    await expect(primaryFilters.getByRole("option", { name: filter })).not.toBeChecked();
+                },
+            );
+        });
+
+        ["People", "Rooms", "Favourite"].forEach((filter) => {
+            test(
+                `should render the placeholder for ${filter} filter`,
+                { tag: "@screenshot" },
+                async ({ page, app, user }) => {
+                    const primaryFilters = getPrimaryFilters(page);
+                    await primaryFilters.getByRole("option", { name: filter }).click();
+
+                    const emptyRoomList = getEmptyRoomList(page);
+                    await expect(emptyRoomList).toMatchScreenshot(`${filter}-empty-room-list.png`);
+                },
+            );
+        });
+    });
+});
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..daa8d3869ffb1941004fa44980989021d1020b14
--- /dev/null
+++ b/playwright/e2e/left-panel/room-list-panel/room-list-header.spec.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { test, expect } from "../../../element-web-test";
+import type { Page } from "@playwright/test";
+
+test.describe("Header section of the room list", () => {
+    test.use({
+        labsFlags: ["feature_new_room_list"],
+    });
+
+    /**
+     * Get the header section of the room list
+     * @param page
+     */
+    function getHeaderSection(page: Page) {
+        return page.getByTestId("room-list-header");
+    }
+
+    test.beforeEach(async ({ page, app, user }) => {
+        // The notification toast is displayed above the search section
+        await app.closeNotificationToast();
+    });
+
+    test("should render the header section", { tag: "@screenshot" }, async ({ page, app, user }) => {
+        const roomListHeader = getHeaderSection(page);
+        await expect(roomListHeader).toMatchScreenshot("room-list-header.png");
+
+        const composeMenu = roomListHeader.getByRole("button", { name: "Add" });
+        await composeMenu.click();
+
+        await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
+
+        // New message should open the direct messages dialog
+        await page.getByRole("menuitem", { name: "New message" }).click();
+        await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
+        await app.closeDialog();
+
+        // New room should open the room creation dialog
+        await composeMenu.click();
+        await page.getByRole("menuitem", { name: "New room" }).click();
+        await expect(page.getByRole("heading", { name: "Create a private room" })).toBeVisible();
+        await app.closeDialog();
+    });
+
+    test("should render the header section for a space", { tag: "@screenshot" }, async ({ page, app, user }) => {
+        await app.client.createSpace({ name: "MySpace" });
+        await page.getByRole("button", { name: "MySpace" }).click();
+
+        const roomListHeader = getHeaderSection(page);
+        await expect(roomListHeader).toMatchScreenshot("room-list-space-header.png");
+
+        await expect(roomListHeader.getByRole("heading", { name: "MySpace" })).toBeVisible();
+        await expect(roomListHeader.getByRole("button", { name: "Add" })).toBeVisible();
+
+        const spaceMenu = roomListHeader.getByRole("button", { name: "Open space menu" });
+        await spaceMenu.click();
+
+        await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-space-menu.png");
+
+        // It should open the space home
+        await page.getByRole("menuitem", { name: "Space home" }).click();
+        await expect(page.getByRole("main").getByRole("heading", { name: "MySpace" })).toBeVisible();
+
+        // It should open the invite dialog
+        await spaceMenu.click();
+        await page.getByRole("menuitem", { name: "Invite" }).click();
+        await expect(page.getByRole("heading", { name: "Invite to MySpace" })).toBeVisible();
+        await app.closeDialog();
+
+        // It should open the space preferences
+        await spaceMenu.click();
+        await page.getByRole("menuitem", { name: "Preferences" }).click();
+        await expect(page.getByRole("heading", { name: "Preferences" })).toBeVisible();
+        await app.closeDialog();
+
+        // It should open the space settings
+        await spaceMenu.click();
+        await page.getByRole("menuitem", { name: "Space Settings" }).click();
+        await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
+        await app.closeDialog();
+    });
+});
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8ca138a707f13e4d85c5ee907d28f606bf3235ad
--- /dev/null
+++ b/playwright/e2e/left-panel/room-list-panel/room-list-panel.spec.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Page } from "@playwright/test";
+
+import { test, expect } from "../../../element-web-test";
+
+test.describe("Room list panel", () => {
+    test.use({
+        labsFlags: ["feature_new_room_list"],
+    });
+
+    /**
+     * Get the room list view
+     * @param page
+     */
+    function getRoomListView(page: Page) {
+        return page.getByTestId("room-list-panel");
+    }
+
+    test.beforeEach(async ({ page, app, user }) => {
+        // The notification toast is displayed above the search section
+        await app.closeNotificationToast();
+
+        // Populate the room list
+        for (let i = 0; i < 20; i++) {
+            await app.client.createRoom({ name: `room${i}` });
+        }
+
+        // focus the user menu to avoid to have hover decoration
+        await page.getByRole("button", { name: "User menu" }).focus();
+    });
+
+    test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
+        const roomListView = getRoomListView(page);
+        // Wait for the last room to be visible
+        await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
+        await expect(roomListView).toMatchScreenshot("room-list-panel.png");
+    });
+
+    test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page }) => {
+        await page.setViewportSize({ width: 575, height: 600 });
+        const roomListPanel = page.getByTestId("room-list-panel");
+        await expect(roomListPanel).toMatchScreenshot("room-list-panel-smallscreen.png");
+    });
+});
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..028503f622d411405e0b9684bda7abda6fa79dda
--- /dev/null
+++ b/playwright/e2e/left-panel/room-list-panel/room-list-search.spec.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Page } from "@playwright/test";
+
+import { test, expect } from "../../../element-web-test";
+
+test.describe("Search section of the room list", () => {
+    test.use({
+        labsFlags: ["feature_new_room_list"],
+    });
+
+    /**
+     * Get the search section of the room list
+     * @param page
+     */
+    function getSearchSection(page: Page) {
+        return page.getByRole("search");
+    }
+
+    test.beforeEach(async ({ page, app, user }) => {
+        // The notification toast is displayed above the search section
+        await app.closeNotificationToast();
+    });
+
+    test("should render the search section", { tag: "@screenshot" }, async ({ page, app, user }) => {
+        const searchSection = getSearchSection(page);
+        // exact=false to ignore the shortcut which is related to the OS
+        await expect(searchSection.getByRole("button", { name: "Search", exact: false })).toBeVisible();
+        await expect(searchSection).toMatchScreenshot("search-section.png");
+    });
+
+    test("should open the spotlight when the search button is clicked", async ({ page, app, user }) => {
+        const searchSection = getSearchSection(page);
+        await searchSection.getByRole("button", { name: "Search", exact: false }).click();
+        // The spotlight should be displayed
+        await expect(page.getByRole("dialog", { name: "Search Dialog" })).toBeVisible();
+    });
+
+    test("should open the room directory when the search button is clicked", async ({ page, app, user }) => {
+        const searchSection = getSearchSection(page);
+        await searchSection.getByRole("button", { name: "Explore rooms" }).click();
+        const dialog = page.getByRole("dialog", { name: "Search Dialog" });
+        // The room directory should be displayed
+        await expect(dialog).toBeVisible();
+        // The public room filter should be displayed
+        await expect(dialog.getByText("Public rooms")).toBeVisible();
+    });
+});
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9e19229348454f236dca5cc6db27304b0ca8475
--- /dev/null
+++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Page } from "@playwright/test";
+
+import { expect, test } from "../../../element-web-test";
+
+test.describe("Room list", () => {
+    test.use({
+        displayName: "Alice",
+        labsFlags: ["feature_new_room_list"],
+        botCreateOpts: {
+            displayName: "BotBob",
+        },
+    });
+
+    /**
+     * Get the room list
+     * @param page
+     */
+    function getRoomList(page: Page) {
+        return page.getByTestId("room-list");
+    }
+
+    test.beforeEach(async ({ page, app, user }) => {
+        // The notification toast is displayed above the search section
+        await app.closeNotificationToast();
+
+        // focus the user menu to avoid to have hover decoration
+        await page.getByRole("button", { name: "User menu" }).focus();
+    });
+
+    test.describe("Room list", () => {
+        test.beforeEach(async ({ page, app, user }) => {
+            for (let i = 0; i < 30; i++) {
+                await app.client.createRoom({ name: `room${i}` });
+            }
+        });
+
+        test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            const roomListView = getRoomList(page);
+            await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
+            await expect(roomListView).toMatchScreenshot("room-list.png");
+
+            // Put focus on the room list
+            await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
+            // Scroll to the end of the room list
+            await page.mouse.wheel(0, 1000);
+            await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
+            await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
+        });
+
+        test("should open the room when it is clicked", async ({ page, app, user }) => {
+            const roomListView = getRoomList(page);
+            await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
+            await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
+        });
+
+        test("should open the more options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            const roomListView = getRoomList(page);
+            const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
+            await roomItem.hover();
+
+            await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
+            const roomItemMenu = roomItem.getByRole("button", { name: "More Options" });
+            await roomItemMenu.click();
+            await expect(page).toMatchScreenshot("room-list-item-open-more-options.png");
+
+            // It should make the room favourited
+            await page.getByRole("menuitemcheckbox", { name: "Favourited" }).click();
+
+            // Check that the room is favourited
+            await roomItem.hover();
+            await roomItemMenu.click();
+            await expect(page.getByRole("menuitemcheckbox", { name: "Favourited" })).toBeChecked();
+            // It should show the invite dialog
+            await page.getByRole("menuitem", { name: "invite" }).click();
+            await expect(page.getByRole("heading", { name: "Invite to room29" })).toBeVisible();
+            await app.closeDialog();
+
+            // It should leave the room
+            await roomItem.hover();
+            await roomItemMenu.click();
+            await page.getByRole("menuitem", { name: "leave room" }).click();
+            await expect(roomItem).not.toBeVisible();
+        });
+
+        test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            const roomListView = getRoomList(page);
+
+            const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
+            await roomItem.hover();
+
+            await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
+            let roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
+            await roomItemMenu.click();
+
+            // Default settings should be selected
+            await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
+                "aria-selected",
+                "true",
+            );
+            await expect(page).toMatchScreenshot("room-list-item-open-notification-options.png");
+
+            // It should make the room muted
+            await page.getByRole("menuitem", { name: "Mute room" }).click();
+
+            // Put focus on the room list
+            await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
+            // Scroll to the end of the room list
+            await page.mouse.wheel(0, 1000);
+
+            // The room decoration should have the muted icon
+            await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
+
+            await roomItem.hover();
+            // On hover, the room should show the muted icon
+            await expect(roomItem).toMatchScreenshot("room-list-item-hover-silent.png");
+
+            roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
+            await roomItemMenu.click();
+            // The Mute room option should be selected
+            await expect(page.getByRole("menuitem", { name: "Mute room" })).toHaveAttribute("aria-selected", "true");
+            await expect(page).toMatchScreenshot("room-list-item-open-notification-options-selection.png");
+        });
+
+        test("should scroll to the current room", async ({ page, app, user }) => {
+            const roomListView = getRoomList(page);
+            // Put focus on the room list
+            await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
+            // Scroll to the end of the room list
+            await page.mouse.wheel(0, 1000);
+
+            await roomListView.getByRole("gridcell", { name: "Open room room0" }).click();
+
+            const filters = page.getByRole("listbox", { name: "Room list filters" });
+            await filters.getByRole("option", { name: "People" }).click();
+            await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).not.toBeVisible();
+
+            await filters.getByRole("option", { name: "People" }).click();
+            await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
+        });
+
+        test.describe("Shortcuts", () => {
+            test("should select the next room", async ({ page, app, user }) => {
+                const roomListView = getRoomList(page);
+                await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
+                await page.keyboard.press("Alt+ArrowDown");
+
+                await expect(page.getByRole("heading", { name: "room28", level: 1 })).toBeVisible();
+            });
+
+            test("should select the previous room", async ({ page, app, user }) => {
+                const roomListView = getRoomList(page);
+                await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
+                await page.keyboard.press("Alt+ArrowUp");
+
+                await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
+            });
+
+            test("should select the last room", async ({ page, app, user }) => {
+                const roomListView = getRoomList(page);
+                await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
+                await page.keyboard.press("Alt+ArrowUp");
+
+                await expect(page.getByRole("heading", { name: "room0", level: 1 })).toBeVisible();
+            });
+
+            test("should select the next unread room", async ({ page, app, user, bot }) => {
+                const roomListView = getRoomList(page);
+
+                const roomId = await app.client.createRoom({ name: "1 notification" });
+                await app.client.inviteUser(roomId, bot.credentials.userId);
+                await bot.joinRoom(roomId);
+                await bot.sendMessage(roomId, "I am a robot. Beep.");
+
+                await roomListView.getByRole("gridcell", { name: "Open room room20" }).click();
+
+                await page.keyboard.press("Alt+Shift+ArrowDown");
+
+                await expect(page.getByRole("heading", { name: "1 notification", level: 1 })).toBeVisible();
+            });
+        });
+
+        test.describe("Keyboard navigation", () => {
+            test("should navigate to the room list", async ({ page, app, user }) => {
+                const roomListView = getRoomList(page);
+
+                const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
+                const room28 = roomListView.getByRole("gridcell", { name: "Open room room28" });
+
+                // open the room
+                await room29.click();
+                // put focus back on the room list item
+                await room29.click();
+                await expect(room29).toBeFocused();
+
+                await page.keyboard.press("ArrowDown");
+                await expect(room28).toBeFocused();
+                await expect(room29).not.toBeFocused();
+
+                await page.keyboard.press("ArrowUp");
+                await expect(room29).toBeFocused();
+                await expect(room28).not.toBeFocused();
+            });
+
+            test("should navigate to the notification menu", async ({ page, app, user }) => {
+                const roomListView = getRoomList(page);
+                const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
+                const moreButton = room29.getByRole("button", { name: "More options" });
+                const notificationButton = room29.getByRole("button", { name: "Notification options" });
+
+                await room29.click();
+                // put focus back on the room list item
+                await room29.click();
+                await page.keyboard.press("Tab");
+                await expect(moreButton).toBeFocused();
+                await page.keyboard.press("Tab");
+                await expect(notificationButton).toBeFocused();
+
+                // Open the menu
+                await notificationButton.click();
+                // Wait for the menu to be open
+                await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
+                    "aria-selected",
+                    "true",
+                );
+
+                // Close the menu
+                await page.keyboard.press("Escape");
+                // Focus should be back on the room list item
+                await expect(room29).toBeFocused();
+            });
+        });
+    });
+
+    test.describe("Avatar decoration", () => {
+        test.use({ labsFlags: ["feature_video_rooms", "feature_new_room_list"] });
+
+        test("should be a public room", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            // @ts-ignore Visibility enum is not accessible
+            await app.client.createRoom({ name: "public room", visibility: "public" });
+
+            // focus the user menu to avoid to have hover decoration
+            await page.getByRole("button", { name: "User menu" }).focus();
+
+            const roomListView = getRoomList(page);
+            const publicRoom = roomListView.getByRole("gridcell", { name: "public room" });
+
+            await expect(publicRoom).toBeVisible();
+            await expect(publicRoom).toMatchScreenshot("room-list-item-public.png");
+        });
+
+        test("should be a video room", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            await page.getByTestId("room-list-panel").getByRole("button", { name: "Add" }).click();
+            await page.getByRole("menuitem", { name: "New video room" }).click();
+            await page.getByRole("textbox", { name: "Name" }).fill("video room");
+            await page.getByRole("button", { name: "Create video room" }).click();
+
+            const roomListView = getRoomList(page);
+            const videoRoom = roomListView.getByRole("gridcell", { name: "video room" });
+
+            // focus the user menu to avoid to have hover decoration
+            await page.getByRole("button", { name: "User menu" }).focus();
+
+            await expect(videoRoom).toBeVisible();
+            await expect(videoRoom).toMatchScreenshot("room-list-item-video.png");
+        });
+    });
+
+    test.describe("Notification decoration", () => {
+        test("should render the invitation decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            await bot.createRoom({
+                name: "invited room",
+                invite: [user.userId],
+                is_direct: true,
+            });
+            const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" });
+            await expect(invitedRoom).toBeVisible();
+            await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
+        });
+
+        test("should render the regular decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            const roomId = await app.client.createRoom({ name: "2 notifications" });
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+
+            await bot.sendMessage(roomId, "I am a robot. Beep.");
+            await bot.sendMessage(roomId, "I am a robot. Beep.");
+
+            const room = roomListView.getByRole("gridcell", { name: "2 notifications" });
+            await expect(room).toBeVisible();
+            await expect(room.getByTestId("notification-decoration")).toHaveText("2");
+            await expect(room).toMatchScreenshot("room-list-item-notification.png");
+        });
+
+        test("should render the mention decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            const roomId = await app.client.createRoom({ name: "mention" });
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+
+            const clientBot = await bot.prepareClient();
+            await clientBot.evaluate(
+                async (client, { roomId, userId }) => {
+                    await client.sendMessage(roomId, {
+                        // @ts-ignore ignore usage of MsgType.text
+                        "msgtype": "m.text",
+                        "body": "User",
+                        "format": "org.matrix.custom.html",
+                        "formatted_body": `<a href="https://matrix.to/#/${userId}">User</a>`,
+                        "m.mentions": {
+                            user_ids: [userId],
+                        },
+                    });
+                },
+                { roomId, userId: user.userId },
+            );
+            await bot.sendMessage(roomId, "I am a robot. Beep.");
+
+            const room = roomListView.getByRole("gridcell", { name: "mention" });
+            await expect(room).toBeVisible();
+            await expect(room).toMatchScreenshot("room-list-item-mention.png");
+        });
+
+        test("should render a message preview", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            await page.getByRole("button", { name: "Room Options" }).click();
+            await page.getByRole("menuitemcheckbox", { name: "Show message previews" }).click();
+
+            const roomId = await app.client.createRoom({ name: "activity" });
+
+            // focus the user menu to avoid to have hover decoration
+            await page.getByRole("button", { name: "User menu" }).focus();
+
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+            await bot.sendMessage(roomId, "I am a robot. Beep.");
+
+            const room = roomListView.getByRole("gridcell", { name: "activity" });
+            await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
+            await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
+        });
+
+        test("should render an activity decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            const otherRoomId = await app.client.createRoom({ name: "other room" });
+
+            const roomId = await app.client.createRoom({ name: "activity" });
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+
+            await app.viewRoomById(roomId);
+            await app.settings.openRoomSettings("Notifications");
+            await page.getByText("@mentions & keywords").click();
+            await app.settings.closeDialog();
+
+            await app.settings.openUserSettings("Notifications");
+            await page.getByText("Show all activity in the room list (dots or number of unread messages)").click();
+            await app.settings.closeDialog();
+
+            // Switch to the other room to avoid the notification to be cleared
+            await app.viewRoomById(otherRoomId);
+            await bot.sendMessage(roomId, "I am a robot. Beep.");
+
+            const room = roomListView.getByRole("gridcell", { name: "activity" });
+            await expect(room.getByTestId("notification-decoration")).toBeVisible();
+            await expect(room).toMatchScreenshot("room-list-item-activity.png");
+        });
+
+        test("should render a mark as unread decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            const roomId = await app.client.createRoom({ name: "mark as unread" });
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+
+            const room = roomListView.getByRole("gridcell", { name: "mark as unread" });
+            await room.hover();
+            await room.getByRole("button", { name: "More Options" }).click();
+            await page.getByRole("menuitem", { name: "mark as unread" }).click();
+
+            // focus the user menu to avoid to have hover decoration
+            await page.getByRole("button", { name: "User menu" }).focus();
+
+            await expect(room).toMatchScreenshot("room-list-item-mark-as-unread.png");
+        });
+
+        test("should render silent decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
+            const roomListView = getRoomList(page);
+
+            const roomId = await app.client.createRoom({ name: "silent" });
+            await app.client.inviteUser(roomId, bot.credentials.userId);
+            await bot.joinRoom(roomId);
+
+            await app.viewRoomById(roomId);
+            await app.settings.openRoomSettings("Notifications");
+            await page.getByText("Off").click();
+            await app.settings.closeDialog();
+
+            const room = roomListView.getByRole("gridcell", { name: "silent" });
+            await expect(room.getByTestId("notification-decoration")).toBeVisible();
+            await expect(room).toMatchScreenshot("room-list-item-silent.png");
+        });
+    });
+});
diff --git a/playwright/e2e/location/location.spec.ts b/playwright/e2e/location/location.spec.ts
index 6f4db3ae72dc8f641415b43193a56fecd983f5d3..52afd5e173d286f062bc605672e2e13a0509720c 100644
--- a/playwright/e2e/location/location.spec.ts
+++ b/playwright/e2e/location/location.spec.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator, Page } from "@playwright/test";
+import { type Locator, type Page } from "@playwright/test";
 
 import { test, expect } from "../../element-web-test";
 
diff --git a/playwright/e2e/login/login-consent.spec.ts b/playwright/e2e/login/login-consent.spec.ts
index d92b427b93134bd0cb57800238d84a09ca6dfbec..23baf023fa394ff9b3fdbeced64103c2d0d9afce 100644
--- a/playwright/e2e/login/login-consent.spec.ts
+++ b/playwright/e2e/login/login-consent.spec.ts
@@ -6,13 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Page } from "playwright-core";
+import { type Page } from "@playwright/test";
 
 import { expect, test } from "../../element-web-test";
 import { selectHomeserver } from "../utils";
-import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
+import { type Credentials, type HomeserverInstance } from "../../plugins/homeserver";
 import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
+import { createBot } from "../crypto/utils.ts";
 
 // This test requires fixed credentials for the device signing keys below to work
 const username = "user1234";
@@ -120,7 +121,7 @@ test.describe("Login", () => {
             credentials,
             page,
             homeserver,
-            checkA11y,
+            axe,
         }) => {
             await page.goto("/");
 
@@ -149,7 +150,7 @@ test.describe("Login", () => {
             await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
             // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688
             // cy.percySnapshot("Login");
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
             await page.getByRole("textbox", { name: "Username" }).fill(credentials.username);
             await page.getByPlaceholder("Password").fill(credentials.password);
@@ -258,6 +259,71 @@ test.describe("Login", () => {
 
                     await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
                 });
+
+                test("Continues to show verification prompt after cancelling device verification", async ({
+                    page,
+                    homeserver,
+                    credentials,
+                }) => {
+                    // Create a different device which is cross-signed, meaning we need to verify this device
+                    await createBot(page, homeserver, credentials, true);
+
+                    // Wait to avoid homeserver rate limit on logins
+                    await page.waitForTimeout(100);
+
+                    // Load the page and see that we are asked to verify
+                    await page.goto("/#/welcome");
+                    await login(page, homeserver, credentials);
+                    let h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
+                    await expect(h1).toBeVisible();
+
+                    // Click "Verify with another device"
+                    await page.getByRole("button", { name: "Verify with another device" }).click();
+
+                    // Cancel the new dialog
+                    await page.getByRole("button", { name: "Close dialog" }).click();
+
+                    // Check that we are still being asked to verify
+                    h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
+                    await expect(h1).toBeVisible();
+                });
+            });
+
+            test("Can reset identity to become verified", async ({ page, homeserver, request, credentials }) => {
+                // Log in
+                const res = await request.post(`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, {
+                    headers: { Authorization: `Bearer ${credentials.accessToken}` },
+                    data: DEVICE_SIGNING_KEYS_BODY,
+                });
+                if (!res.ok()) {
+                    console.log(`Uploading dummy keys failed with HTTP status ${res.status}`, await res.json());
+                    throw new Error("Uploading dummy keys failed");
+                }
+
+                await page.goto("/");
+                await login(page, homeserver, credentials);
+
+                await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible();
+
+                // Start the reset process
+                await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+                // First try cancelling and restarting
+                await page.getByRole("button", { name: "Cancel" }).click();
+                await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+                // Then click outside the dialog and restart
+                await page.getByRole("link", { name: "Powered by Matrix" }).click({ force: true });
+                await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+                // Finally we actually continue
+                await page.getByRole("button", { name: "Continue" }).click();
+                await page.getByPlaceholder("Password").fill(credentials.password);
+                await page.getByRole("button", { name: "Continue" }).click();
+
+                // We end up at the Home screen
+                await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
+                await expect(page.getByRole("heading", { name: "Welcome Dave", exact: true })).toBeVisible();
             });
         });
     });
diff --git a/playwright/e2e/login/utils.ts b/playwright/e2e/login/utils.ts
index 59a5c330f3012e9824d2660ef2dd1a0cfb1bb218..d74300908ad1ba261ff76d666481401d93ffff91 100644
--- a/playwright/e2e/login/utils.ts
+++ b/playwright/e2e/login/utils.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Page, expect, TestInfo } from "@playwright/test";
+import { type Page, expect, type TestInfo } from "@playwright/test";
 
-import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
+import { type Credentials, type HomeserverInstance } from "../../plugins/homeserver";
 
 /** Visit the login page, choose to log in with "OAuth test", register a new account, and redirect back to Element
  */
diff --git a/playwright/e2e/messages/messages.spec.ts b/playwright/e2e/messages/messages.spec.ts
index 5185be43c9533f3cb813d1fc877434e8686f8ed3..f430d6b18b7c8b84fadfede1760d4f32868b4158 100644
--- a/playwright/e2e/messages/messages.spec.ts
+++ b/playwright/e2e/messages/messages.spec.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 /* See readme.md for tips on writing these tests. */
 
-import { Locator, Page } from "playwright-core";
+import { type Locator, type Page } from "@playwright/test";
 
 import { test, expect } from "../../element-web-test";
 
diff --git a/playwright/e2e/modules/loader.spec.ts b/playwright/e2e/modules/loader.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52c7a02988c8a6232cbbd2cd03edc419c1ef7f15
--- /dev/null
+++ b/playwright/e2e/modules/loader.spec.ts
@@ -0,0 +1,56 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { test, expect } from "../../element-web-test";
+
+test.describe("Module loading", () => {
+    test.use({
+        displayName: "Manny",
+    });
+
+    test.describe("Example Module", () => {
+        test.use({
+            config: {
+                brand: "TestBrand",
+                modules: ["/modules/example-module.js"],
+            },
+            page: async ({ page }, use) => {
+                await page.route("/modules/example-module.js", async (route) => {
+                    await route.fulfill({ path: "playwright/sample-files/example-module.js" });
+                });
+                await use(page);
+            },
+        });
+
+        const testCases = [
+            ["en", "TestBrand module loading successful!"],
+            ["de", "TestBrand-Module erfolgreich geladen!"],
+        ];
+
+        for (const [lang, message] of testCases) {
+            test.describe(`language-${lang}`, () => {
+                test.use({
+                    config: async ({ config }, use) => {
+                        await use({
+                            ...config,
+                            setting_defaults: {
+                                language: lang,
+                            },
+                        });
+                    },
+                });
+
+                test("should show alert", async ({ page }) => {
+                    const dialogPromise = page.waitForEvent("dialog");
+                    await page.goto("/");
+                    const dialog = await dialogPromise;
+                    expect(dialog.message()).toBe(message);
+                });
+            });
+        }
+    });
+});
diff --git a/playwright/e2e/oidc/index.ts b/playwright/e2e/oidc/index.ts
index bfd49b496a07423887e00441dc7d54e84fae60dc..02de6e2f035570bd77926a771ac41bbd155220aa 100644
--- a/playwright/e2e/oidc/index.ts
+++ b/playwright/e2e/oidc/index.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { API, Messages } from "mailhog";
-import { Page } from "@playwright/test";
+import { type MailpitClient } from "@element-hq/element-web-playwright-common/lib/testcontainers";
+import { type Page } from "@playwright/test";
 
 import { expect } from "../../element-web-test";
 
 export async function registerAccountMas(
     page: Page,
-    mailhog: API,
+    mailpit: MailpitClient,
     username: string,
     email: string,
     password: string,
@@ -27,16 +27,18 @@ export async function registerAccountMas(
     await page.getByRole("textbox", { name: "Confirm Password" }).fill(password);
     await page.getByRole("button", { name: "Continue" }).click();
 
-    let messages: Messages;
+    let code: string;
     await expect(async () => {
-        messages = await mailhog.messages();
-        expect(messages.items).toHaveLength(1);
+        const messages = await mailpit.listMessages();
+        expect(messages.messages[0].To[0].Address).toEqual(email);
+        const text = await mailpit.renderMessageText(messages.messages[0].ID);
+        [, code] = text.match(/Your verification code to confirm this email address is: (\d{6})/);
     }).toPass();
-    expect(messages.items[0].to).toEqual(`${username} <${email}>`);
-    const [, code] = messages.items[0].text.match(/Your verification code to confirm this email address is: (\d{6})/);
 
     await page.getByRole("textbox", { name: "6-digit code" }).fill(code);
     await page.getByRole("button", { name: "Continue" }).click();
+    await page.getByRole("textbox", { name: "Display Name" }).fill(username);
+    await page.getByRole("button", { name: "Continue" }).click();
     await expect(page.getByText("Allow access to your account?")).toBeVisible();
     await page.getByRole("button", { name: "Continue" }).click();
 }
diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts
index 4d7fc7538d3d72a0bff0e9c9c148e937e57b577d..e985e58d09ed54b2264f34048167747517f5846d 100644
--- a/playwright/e2e/oidc/oidc-native.spec.ts
+++ b/playwright/e2e/oidc/oidc-native.spec.ts
@@ -19,7 +19,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
         context,
         page,
         homeserver,
-        mailhogClient,
+        mailpitClient,
         mas,
     }, testInfo) => {
         await page.clock.install();
@@ -33,7 +33,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
         await page.getByRole("button", { name: "Continue" }).click();
 
         const userId = `alice_${testInfo.testId}`;
-        await registerAccountMas(page, mailhogClient, userId, "alice@email.com", "Pa$sW0rD!");
+        await registerAccountMas(page, mailpitClient, userId, "alice@email.com", "Pa$sW0rD!");
 
         // Eventually, we should end up at the home screen.
         await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
@@ -73,4 +73,32 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
         await revokeAccessTokenPromise;
         await revokeRefreshTokenPromise;
     });
+
+    test(
+        "it should log out the user & wipe data when logging out via MAS",
+        { tag: "@screenshot" },
+        async ({ mas, page, mailpitClient }, testInfo) => {
+            // We use this over the `user` fixture to ensure we get an OIDC session rather than a compatibility one
+            await page.goto("/#/login");
+            await page.getByRole("button", { name: "Continue" }).click();
+
+            const userId = `alice_${testInfo.testId}`;
+            await registerAccountMas(page, mailpitClient, userId, "alice@email.com", "Pa$sW0rD!");
+
+            await expect(page.getByText("Welcome")).toBeVisible();
+            await page.goto("about:blank");
+
+            const result = await mas.manage("kill-sessions", userId);
+            expect(result.output).toContain("Ended 1 active OAuth 2.0 session");
+
+            await page.goto("http://localhost:8080");
+            await expect(
+                page.getByText("For security, this session has been signed out. Please sign in again."),
+            ).toBeVisible();
+            await expect(page).toMatchScreenshot("token-expired.png", { includeDialogBackground: true });
+
+            const localStorageKeys = await page.evaluate(() => Object.keys(localStorage));
+            expect(localStorageKeys).toHaveLength(0);
+        },
+    );
 });
diff --git a/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts b/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts
index d03767205a55ef8879b5c24a9e2ed3ff48ece5d7..6ad69deba92bbc994cb8f700818c3a2b74ff4443 100644
--- a/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts
+++ b/playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { test as base, expect } from "../../element-web-test";
-import { Credentials } from "../../plugins/homeserver";
+import { type Credentials } from "../../plugins/homeserver";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
 const test = base.extend<{
diff --git a/playwright/e2e/pinned-messages/index.ts b/playwright/e2e/pinned-messages/index.ts
index bb65f31627795c3a8dbe27d7abf375f3564ac760..198ab24118894566d906b1635c1f863030b5b4cf 100644
--- a/playwright/e2e/pinned-messages/index.ts
+++ b/playwright/e2e/pinned-messages/index.ts
@@ -6,12 +6,12 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Page } from "@playwright/test";
+import { type Page } from "@playwright/test";
 
 import { test as base, expect } from "../../element-web-test";
-import { Client } from "../../pages/client";
-import { ElementAppPage } from "../../pages/ElementAppPage";
-import { Bot } from "../../pages/bot";
+import { type Client } from "../../pages/client";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
+import { type Bot } from "../../pages/bot";
 
 type RoomRef = { name: string; roomId: string };
 
diff --git a/playwright/e2e/polls/pollHistory.spec.ts b/playwright/e2e/polls/pollHistory.spec.ts
index 319a08cda916d0d7b780aff1043125d118e4b1a1..4f2adff40d9b3726ef9d113c3bfc98716c0f698c 100644
--- a/playwright/e2e/polls/pollHistory.spec.ts
+++ b/playwright/e2e/polls/pollHistory.spec.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 import { test, expect } from "../../element-web-test";
 import type { Bot } from "../../pages/bot";
 import type { Client } from "../../pages/client";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 
 test.describe("Poll history", () => {
     type CreatePollOptions = {
diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts
index eab261042e718362129717f8d6fbbb9ee026e215..067d3d16d2ef815ab446befd92d5bed98e808d64 100644
--- a/playwright/e2e/read-receipts/index.ts
+++ b/playwright/e2e/read-receipts/index.ts
@@ -9,9 +9,9 @@ Please see LICENSE files in the repository root for full details.
 import type { JSHandle, Page } from "@playwright/test";
 import type { MatrixEvent, Room, IndexedDBStore, ReceiptType } from "matrix-js-sdk/src/matrix";
 import { test as base, expect } from "../../element-web-test";
-import { Bot } from "../../pages/bot";
-import { Client } from "../../pages/client";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type Bot } from "../../pages/bot";
+import { type Client } from "../../pages/client";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 
 type RoomRef = { name: string; roomId: string };
 
@@ -526,9 +526,10 @@ class Helpers {
         await expect(threadPanel).toBeVisible();
         await threadPanel.evaluate(($panel) => {
             const $button = $panel.querySelector<HTMLElement>('[data-testid="base-card-back-button"]');
+            const title = $panel.querySelector<HTMLElement>(".mx_BaseCard_header_title")?.textContent;
             // If the Threads back button is present then click it - the
             // threads button can open either threads list or thread panel
-            if ($button) {
+            if ($button && title !== "Threads") {
                 $button.click();
             }
         });
diff --git a/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts b/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts
index a711d889a168131ee5563ae2180a25daaaa215e7..fbb70776a8acead52c00f52ab6f84d4348c4ab49 100644
--- a/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts
+++ b/playwright/e2e/read-receipts/new-messages-thread-roots.spec.ts
@@ -57,8 +57,8 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
                 await util.openThread("ThreadRoot");
 
                 // Then the thread root is marked as read in the main timeline,
-                // 30 remaining messages are unread - 7 messages are displayed under the thread root
-                await util.assertUnread(room2, 30 - 7);
+                // 30 remaining messages are unread - 6 messages are displayed under the thread root
+                await util.assertUnread(room2, 30 - 6);
             });
 
             test("Creating a new thread based on a reply makes the room unread", async ({
diff --git a/playwright/e2e/read-receipts/read-receipts.spec.ts b/playwright/e2e/read-receipts/read-receipts.spec.ts
index 6b9415e7ba685196b4bfc1184693421813a10628..8ebce22b52e3ffbb359032254e9a09ac4836b8db 100644
--- a/playwright/e2e/read-receipts/read-receipts.spec.ts
+++ b/playwright/e2e/read-receipts/read-receipts.spec.ts
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import type { JSHandle } from "@playwright/test";
 import type { MatrixEvent, ISendEventResponse, ReceiptType } from "matrix-js-sdk/src/matrix";
 import { expect } from "../../element-web-test";
-import { ElementAppPage } from "../../pages/ElementAppPage";
-import { Bot } from "../../pages/bot";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
+import { type Bot } from "../../pages/bot";
 import { test } from ".";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
diff --git a/playwright/e2e/register/email.spec.ts b/playwright/e2e/register/email.spec.ts
index cd990f9eafdb647ab32fed8e89d1dd3d19933f28..bf8a2157f5400008662e91fca273baacb0d1b020 100644
--- a/playwright/e2e/register/email.spec.ts
+++ b/playwright/e2e/register/email.spec.ts
@@ -34,7 +34,7 @@ test.describe("Email Registration", async () => {
     test(
         "registers an account and lands on the home page",
         { tag: "@screenshot" },
-        async ({ page, mailhogClient, request, checkA11y }) => {
+        async ({ page, mailpitClient, request, axe }) => {
             await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
             // Hide the server text as it contains the randomly allocated Homeserver port
             const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")] };
@@ -47,14 +47,15 @@ test.describe("Email Registration", async () => {
 
             await expect(page.getByText("Check your email to continue")).toBeVisible();
             await expect(page).toMatchScreenshot("registration_check_your_email.png", screenshotOptions);
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
             await expect(page.getByText("An error was encountered when sending the email")).not.toBeVisible();
 
-            const messages = await mailhogClient.messages();
-            expect(messages.items).toHaveLength(1);
-            expect(messages.items[0].to).toEqual("alice@email.com");
-            const [emailLink] = messages.items[0].text.match(/http.+/);
+            const messages = await mailpitClient.listMessages();
+            expect(messages.messages).toHaveLength(1);
+            expect(messages.messages[0].To[0].Address).toEqual("alice@email.com");
+            const text = await mailpitClient.renderMessageText(messages.messages[0].ID);
+            const [emailLink] = text.match(/http.+/);
             await request.get(emailLink); // "Click" the link in the email
 
             await expect(page.getByText("Welcome alice")).toBeVisible();
diff --git a/playwright/e2e/register/register.spec.ts b/playwright/e2e/register/register.spec.ts
index 3df1d016785b20da52aaa28e1cae4cc7778d18f0..c481b6ac43d3b45fc4e54c83f1f248505c98e7c1 100644
--- a/playwright/e2e/register/register.spec.ts
+++ b/playwright/e2e/register/register.spec.ts
@@ -33,12 +33,12 @@ test.describe("Registration", () => {
     test(
         "registers an account and lands on the home screen",
         { tag: "@screenshot" },
-        async ({ homeserver, page, checkA11y, crypto }) => {
+        async ({ homeserver, page, axe, crypto }) => {
             await page.getByRole("button", { name: "Edit", exact: true }).click();
             await expect(page.getByRole("button", { name: "Continue", exact: true })).toBeVisible();
 
             await expect(page.locator(".mx_Dialog")).toMatchScreenshot("server-picker.png");
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
             await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.baseUrl);
             await page.getByRole("button", { name: "Continue", exact: true }).click();
@@ -52,7 +52,7 @@ test.describe("Registration", () => {
                 includeDialogBackground: true,
             };
             await expect(page).toMatchScreenshot("registration.png", screenshotOptions);
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
             await page.getByRole("textbox", { name: "Username", exact: true }).fill("alice");
             await page.getByPlaceholder("Password", { exact: true }).fill("totally a great password");
@@ -62,12 +62,12 @@ test.describe("Registration", () => {
             const dialog = page.getByRole("dialog");
             await expect(dialog).toBeVisible();
             await expect(page).toMatchScreenshot("email-prompt.png", screenshotOptions);
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
             await dialog.getByRole("button", { name: "Continue", exact: true }).click();
 
             await expect(page.locator(".mx_InteractiveAuthEntryComponents_termsPolicy")).toBeVisible();
             await expect(page).toMatchScreenshot("terms-prompt.png", screenshotOptions);
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
             const termsPolicy = page.locator(".mx_InteractiveAuthEntryComponents_termsPolicy");
             await termsPolicy.getByRole("checkbox").click(); // Click the checkbox before terms of service anchor link
diff --git a/playwright/e2e/regression-tests/pills-click-in-app.spec.ts b/playwright/e2e/regression-tests/pills-click-in-app.spec.ts
index 7486af703378610885d04344d298234e8c166e48..3670e64308c486f7fbb23afb63eab43591bf2895 100644
--- a/playwright/e2e/regression-tests/pills-click-in-app.spec.ts
+++ b/playwright/e2e/regression-tests/pills-click-in-app.spec.ts
@@ -43,6 +43,7 @@ test.describe("Pills", () => {
 
         // go back to the message room and try to click on the pill text, as a user would
         await app.viewRoomByName(messageRoom);
+        await expect(page).toHaveURL(new RegExp(`/#/room/${messageRoomId}`));
         const pillText = page.locator(".mx_EventTile_body .mx_Pill .mx_Pill_text");
         await expect(pillText).toHaveCSS("pointer-events", "none");
         await pillText.click({ force: true }); // force is to ensure we bypass pointer-events
diff --git a/playwright/e2e/release-announcement/index.ts b/playwright/e2e/release-announcement/index.ts
index e20dfb85b4ab936c61f7719ca45de3fdf8b68cf6..3b6c2dd38a8f20c07747c8aba20915f1bdd45ec2 100644
--- a/playwright/e2e/release-announcement/index.ts
+++ b/playwright/e2e/release-announcement/index.ts
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Page } from "@playwright/test";
+import { type Page } from "@playwright/test";
 
 import { test as base, expect } from "../../element-web-test";
 
diff --git a/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts b/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts
index c2ebd5b85397191a7f36447e8bcce4d6909c9149..812b66b796b8bf7414bc84d4fee932b94dd9c39f 100644
--- a/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts
+++ b/playwright/e2e/release-announcement/releaseAnnouncement.spec.ts
@@ -15,20 +15,34 @@ test.describe("Release announcement", () => {
                 feature_release_announcement: true,
             },
         },
-        labsFlags: ["threadsActivityCentre"],
+        room: async ({ app, user }, use) => {
+            const roomId = await app.client.createRoom({
+                name: "Test room",
+            });
+            await app.viewRoomById(roomId);
+            await use({ roomId });
+        },
     });
 
-    test("should display the release announcement process", { tag: "@screenshot" }, async ({ page, app, util }) => {
-        // The TAC release announcement should be displayed
-        await util.assertReleaseAnnouncementIsVisible("Threads Activity Centre");
-        // Hide the release announcement
-        await util.markReleaseAnnouncementAsRead("Threads Activity Centre");
-        await util.assertReleaseAnnouncementIsNotVisible("Threads Activity Centre");
+    test(
+        "should display the pinned messages release announcement",
+        { tag: "@screenshot" },
+        async ({ page, app, room, util }) => {
+            await app.toggleRoomInfoPanel();
 
-        await page.reload();
-        // Wait for EW to load
-        await expect(page.getByRole("navigation", { name: "Spaces" })).toBeVisible();
-        // Check that once the release announcement has been marked as viewed, it does not appear again
-        await util.assertReleaseAnnouncementIsNotVisible("Threads Activity Centre");
-    });
+            const name = "All new pinned messages";
+
+            // The release announcement should be displayed
+            await util.assertReleaseAnnouncementIsVisible(name);
+            // Hide the release announcement
+            await util.markReleaseAnnouncementAsRead(name);
+            await util.assertReleaseAnnouncementIsNotVisible(name);
+
+            await page.reload();
+            await app.toggleRoomInfoPanel();
+            await expect(page.getByRole("menuitem", { name: "Pinned messages" })).toBeVisible();
+            // Check that once the release announcement has been marked as viewed, it does not appear again
+            await util.assertReleaseAnnouncementIsNotVisible(name);
+        },
+    );
 });
diff --git a/playwright/e2e/right-panel/file-panel.spec.ts b/playwright/e2e/right-panel/file-panel.spec.ts
index 1c936f43b89b7e0954d37eaa9e4560c6bbf4dfdb..d69b7d473144892a38e7fda64db1371b22af8aaa 100644
--- a/playwright/e2e/right-panel/file-panel.spec.ts
+++ b/playwright/e2e/right-panel/file-panel.spec.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Download, type Page } from "@playwright/test";
+import { type Download, type Page } from "@playwright/test";
 
 import { test, expect } from "../../element-web-test";
 import { viewRoomSummaryByName } from "./utils";
diff --git a/playwright/e2e/right-panel/memberlist.spec.ts b/playwright/e2e/right-panel/memberlist.spec.ts
index 25d5f34663164a152709ca1e16444e99a8f8eb83..cd22626575f88850c0dc64ecafc2ed7f1bdc9b1d 100644
--- a/playwright/e2e/right-panel/memberlist.spec.ts
+++ b/playwright/e2e/right-panel/memberlist.spec.ts
@@ -42,7 +42,7 @@ test.describe("Memberlist", () => {
         await app.viewRoomByName(ROOM_NAME);
         const memberlist = await app.toggleMemberlistPanel();
         await expect(memberlist.locator(".mx_MemberTileView")).toHaveCount(4);
-        await expect(memberlist.getByText("(Invited)")).toHaveCount(1);
+        await expect(memberlist.getByText("Invited")).toHaveCount(1);
         await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png");
     });
 });
diff --git a/playwright/e2e/right-panel/right-panel.spec.ts b/playwright/e2e/right-panel/right-panel.spec.ts
index de86d6bfe62d9994a7857b45d1d92f066d0a48a2..0fbd306d86fe14b082e09fa0fe76be0b3d538625 100644
--- a/playwright/e2e/right-panel/right-panel.spec.ts
+++ b/playwright/e2e/right-panel/right-panel.spec.ts
@@ -1,15 +1,17 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator, type Page } from "@playwright/test";
+import { type Locator, type Page } from "@playwright/test";
 
 import { test, expect } from "../../element-web-test";
 import { checkRoomSummaryCard, viewRoomSummaryByName } from "./utils";
+import { isDendrite } from "../../plugins/homeserver/dendrite";
+import { Bot } from "../../pages/bot";
 
 const ROOM_NAME = "Test room";
 const ROOM_NAME_LONG =
@@ -20,20 +22,23 @@ const ROOM_NAME_LONG =
     "officia deserunt mollit anim id est laborum.";
 const SPACE_NAME = "Test space";
 const NAME = "Alice";
+const LONG_NAME = "Bob long long long long long long long long long long long long long long long name";
+
 const ROOM_ADDRESS_LONG =
     "loremIpsumDolorSitAmetConsecteturAdipisicingElitSedDoEiusmodTemporIncididuntUtLaboreEtDoloreMagnaAliqua";
 
 function getMemberTileByName(page: Page, name: string): Locator {
-    return page.locator(`.mx_MemberTileView, [title="${name}"]`);
+    return page.locator(".mx_MemberListView .mx_MemberTileView_name").filter({ hasText: name });
 }
 
 test.describe("RightPanel", () => {
+    let testRoomId: string;
     test.use({
         displayName: NAME,
     });
 
     test.beforeEach(async ({ app, user }) => {
-        await app.client.createRoom({ name: ROOM_NAME });
+        testRoomId = await app.client.createRoom({ name: ROOM_NAME });
         await app.client.createSpace({ name: SPACE_NAME });
     });
 
@@ -67,10 +72,21 @@ test.describe("RightPanel", () => {
             },
         );
 
-        test("should handle clicking add widgets", async ({ page, app }) => {
+        test("should have padding under leave room", { tag: "@screenshot" }, async ({ page, app }) => {
+            await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+            const leaveButton = await page.getByRole("menuitem", { name: "Leave Room" });
+            await leaveButton.scrollIntoViewIfNeeded();
+
+            await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png");
+        });
+
+        test("should handle clicking add widgets", { tag: "@screenshot" }, async ({ page, app }) => {
             await viewRoomSummaryByName(page, app, ROOM_NAME);
 
             await page.getByRole("menuitem", { name: "Extensions" }).click();
+            await expect(page.getByTestId("right-panel")).toMatchScreenshot("with-extensions.png");
+
             await page.getByRole("button", { name: "Add extensions" }).click();
             await expect(page.locator(".mx_IntegrationManager")).toBeVisible();
         });
@@ -124,6 +140,65 @@ test.describe("RightPanel", () => {
             await page.getByLabel("Room info").nth(1).click();
             await checkRoomSummaryCard(page, ROOM_NAME);
         });
+
+        test(
+            "should handle viewing long room member name",
+            { tag: "@screenshot" },
+            async ({ page, homeserver, app }) => {
+                const bobLongName = new Bot(page, homeserver, { displayName: LONG_NAME });
+                await bobLongName.prepareClient();
+                await app.client.inviteUser(testRoomId, bobLongName.credentials.userId);
+                await bobLongName.joinRoom(testRoomId);
+
+                await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+                await page.locator(".mx_RightPanel").getByRole("menuitem", { name: "People" }).click();
+                await expect(page.locator(".mx_MemberListView")).toBeVisible();
+
+                await getMemberTileByName(page, LONG_NAME).click();
+                await expect(page.locator(".mx_UserInfo")).toBeVisible();
+                await expect(page.locator(".mx_UserInfo_profile").getByText(LONG_NAME)).toBeVisible();
+
+                await expect(page.locator(".mx_UserInfo")).toMatchScreenshot("with-long-name.png", {
+                    mask: [page.locator(".mx_UserInfo_profile_mxid")],
+                    css: `
+                        /* Use monospace font for consistent mask width */
+                        .mx_UserInfo_profile_mxid {
+                            font-family: Inconsolata !important;
+                        }
+                    `,
+                });
+            },
+        );
+
+        test.describe("room reporting", () => {
+            test.skip(isDendrite, "Dendrite does not implement room reporting");
+            test("should handle reporting a room", { tag: "@screenshot" }, async ({ page, app }) => {
+                await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+                await page.getByRole("menuitem", { name: "Report room" }).click();
+                const dialog = await page.getByRole("dialog", { name: "Report Room" });
+                await dialog.getByLabel("reason").fill("This room should be reported");
+                await expect(dialog).toMatchScreenshot("room-report-dialog.png");
+                await dialog.getByRole("button", { name: "Send report" }).click();
+
+                // Dialog should have gone
+                await expect(page.locator(".mx_Dialog")).toHaveCount(0);
+            });
+            test("should handle reporting a room and leaving the room", async ({ page, app }) => {
+                await viewRoomSummaryByName(page, app, ROOM_NAME);
+
+                await page.getByRole("menuitem", { name: "Report room" }).click();
+                const dialog = await page.getByRole("dialog", { name: "Report room" });
+                await dialog.getByRole("switch", { name: "Leave room" }).click();
+                await dialog.getByLabel("reason").fill("This room should be reported");
+                await dialog.getByRole("button", { name: "Send report" }).click();
+                await page.getByRole("dialog", { name: "Leave room" }).getByRole("button", { name: "Leave" }).click();
+
+                // Dialog should have gone
+                await expect(page.locator(".mx_Dialog")).toHaveCount(0);
+            });
+        });
     });
 
     test.describe("in spaces", () => {
diff --git a/playwright/e2e/right-panel/utils.ts b/playwright/e2e/right-panel/utils.ts
index 0f57178f50ac04bc30518af19cb366cef8d20729..f4745b01588706aa80c2e2d0556d87104117ddf5 100644
--- a/playwright/e2e/right-panel/utils.ts
+++ b/playwright/e2e/right-panel/utils.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { type Page, expect } from "@playwright/test";
 
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 
 export async function viewRoomSummaryByName(page: Page, app: ElementAppPage, name: string): Promise<void> {
     await app.viewRoomByName(name);
diff --git a/playwright/e2e/room/invites.spec.ts b/playwright/e2e/room/invites.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9312b244450f7f5de091190fc8ce515c96956e5b
--- /dev/null
+++ b/playwright/e2e/room/invites.spec.ts
@@ -0,0 +1,74 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { test, expect } from "../../element-web-test";
+
+test.describe("Invites", () => {
+    test.use({
+        displayName: "Alice",
+        botCreateOpts: {
+            displayName: "Bob",
+        },
+    });
+
+    test("should render an invite view", { tag: "@screenshot" }, async ({ page, homeserver, user, bot, app }) => {
+        const roomId = await bot.createRoom({ is_direct: true });
+        await bot.inviteUser(roomId, user.userId);
+        await app.viewRoomByName("Bob");
+        await expect(page.locator(".mx_RoomView")).toMatchScreenshot("Invites_room_view.png", {
+            // Hide the mxid, which is not stable.
+            css: `
+                .mx_RoomPreviewBar_inviter_mxid {
+                    display: none !important;
+                }
+            `,
+        });
+    });
+
+    test("should be able to decline an invite", async ({ page, homeserver, user, bot, app }) => {
+        const roomId = await bot.createRoom({ is_direct: true });
+        await bot.inviteUser(roomId, user.userId);
+        await app.viewRoomByName("Bob");
+        await page.getByRole("button", { name: "Decline", exact: true }).click();
+        await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible();
+        await expect(
+            page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Bob", exact: true }),
+        ).not.toBeVisible();
+    });
+
+    test(
+        "should be able to decline an invite, report the room and ignore the user",
+        { tag: "@screenshot" },
+        async ({ page, homeserver, user, bot, app }) => {
+            const roomId = await bot.createRoom({ is_direct: true });
+            await bot.inviteUser(roomId, user.userId);
+            await app.viewRoomByName("Bob");
+            await page.getByRole("button", { name: "Decline and block" }).click();
+            await page.getByLabel("Ignore user").click();
+            await page.getByLabel("Report room").click();
+            await page.getByLabel("Reason").fill("Do not want the room");
+            const roomReported = page.waitForRequest(
+                (req) =>
+                    req.url().endsWith(`/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/report`) &&
+                    req.method() === "POST",
+            );
+            await expect(page.getByRole("dialog", { name: "Decline invitation" })).toMatchScreenshot(
+                "Invites_reject_dialog.png",
+            );
+            await page.getByRole("button", { name: "Decline invite" }).click();
+
+            // Check room was reported.
+            await roomReported;
+
+            // Check user is ignored.
+            await app.settings.openUserSettings("Security & Privacy");
+            const ignoredUsersList = page.getByRole("list", { name: "Ignored users" });
+            await ignoredUsersList.scrollIntoViewIfNeeded();
+            await expect(ignoredUsersList.getByRole("listitem", { name: bot.credentials.userId })).toBeVisible();
+        },
+    );
+});
diff --git a/playwright/e2e/room/room-header.spec.ts b/playwright/e2e/room/room-header.spec.ts
index f19bd68f1459949bd13ba28fe9be528f2a13931c..78e37cd4d25f1a8b54693185bf5b78b0bd212f81 100644
--- a/playwright/e2e/room/room-header.spec.ts
+++ b/playwright/e2e/room/room-header.spec.ts
@@ -6,10 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Page } from "@playwright/test";
+import { type Page } from "@playwright/test";
+import { type Visibility } from "matrix-js-sdk/src/matrix";
 
 import { test, expect } from "../../element-web-test";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 
 test.describe("Room Header", () => {
     test.use({
@@ -85,6 +86,15 @@ test.describe("Room Header", () => {
                 await expect(header).toMatchScreenshot("room-header-long-name.png");
             },
         );
+
+        test("should render room header icon correctly", { tag: "@screenshot" }, async ({ page, app, user }) => {
+            await app.client.createRoom({ name: "Test Room", visibility: "public" as Visibility });
+            await app.viewRoomByName("Test Room");
+
+            const header = page.locator(".mx_RoomHeader");
+
+            await expect(header).toMatchScreenshot("room-header-with-icon.png");
+        });
     });
 
     test.describe("with a video room", () => {
diff --git a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts
index b4eeed7728f403b7982916a89317b082b656493c..53f9c37dd011497157d69c3f85084ebd1d019c84 100644
--- a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts
+++ b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2023 Suguru Hirahara
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -50,8 +50,8 @@ test.describe("Appearance user settings tab", () => {
         // Click "Show advanced" link button
         await tab.getByRole("button", { name: "Show advanced" }).click();
 
-        await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click();
-        await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click();
+        await tab.getByLabel("Use bundled emoji font").click();
+        await tab.getByLabel("Use a system font").click();
 
         // Assert that the font-family value was removed
         await expect(page.locator("body")).toHaveCSS("font-family", '""');
diff --git a/playwright/e2e/settings/appearance-user-settings-tab/index.ts b/playwright/e2e/settings/appearance-user-settings-tab/index.ts
index be609edf9f5c66a06f496541af435fab78f938c7..29e51fb0ddc13823e8877a394c3836bcbafa3655 100644
--- a/playwright/e2e/settings/appearance-user-settings-tab/index.ts
+++ b/playwright/e2e/settings/appearance-user-settings-tab/index.ts
@@ -6,9 +6,9 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Locator, Page } from "@playwright/test";
+import { type Locator, type Page } from "@playwright/test";
 
-import { ElementAppPage } from "../../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../../pages/ElementAppPage";
 import { test as base, expect } from "../../../element-web-test";
 import { SettingLevel } from "../../../../src/settings/SettingLevel";
 import { Layout } from "../../../../src/settings/enums/Layout";
diff --git a/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts b/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b459c2e1195b793af88b12c1391a1b91a8c3d67
--- /dev/null
+++ b/playwright/e2e/settings/encryption-user-tab/advanced.spec.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { test, expect } from "./index";
+import { checkDeviceIsCrossSigned } from "../../crypto/utils";
+import { bootstrapCrossSigningForClient } from "../../../pages/client";
+
+test.describe("Advanced section in Encryption tab", () => {
+    test.beforeEach(async ({ page, app, homeserver, credentials, util }) => {
+        const clientHandle = await app.client.prepareClient();
+        // Reset cross signing in order to have a verified session
+        await bootstrapCrossSigningForClient(clientHandle, credentials, true);
+    });
+
+    test("should show the encryption details", { tag: "@screenshot" }, async ({ page, app, util }) => {
+        await util.openEncryptionTab();
+        const section = util.getEncryptionDetailsSection();
+
+        const deviceId = await page.evaluate(() => window.mxMatrixClientPeg.get().getDeviceId());
+        await expect(section.getByText(deviceId)).toBeVisible();
+
+        await expect(section).toMatchScreenshot("encryption-details.png", {
+            mask: [section.getByTestId("deviceId"), section.getByTestId("sessionKey")],
+        });
+    });
+
+    test("should show the import room keys dialog", async ({ page, app, util }) => {
+        await util.openEncryptionTab();
+        const section = util.getEncryptionDetailsSection();
+
+        await section.getByRole("button", { name: "Import keys" }).click();
+        await expect(page.getByRole("heading", { name: "Import room keys" })).toBeVisible();
+    });
+
+    test("should show the export room keys dialog", async ({ page, app, util }) => {
+        await util.openEncryptionTab();
+        const section = util.getEncryptionDetailsSection();
+
+        await section.getByRole("button", { name: "Export keys" }).click();
+        await expect(page.getByRole("heading", { name: "Export room keys" })).toBeVisible();
+    });
+
+    test(
+        "should reset the cryptographic identity",
+        { tag: "@screenshot" },
+        async ({ page, app, credentials, util }) => {
+            const tab = await util.openEncryptionTab();
+            const section = util.getEncryptionDetailsSection();
+
+            await section.getByRole("button", { name: "Reset cryptographic identity" }).click();
+            await expect(util.getEncryptionTabContent()).toMatchScreenshot("reset-cryptographic-identity.png");
+            await tab.getByRole("button", { name: "Continue" }).click();
+
+            // Fill password dialog and validate
+            const dialog = page.locator(".mx_InteractiveAuthDialog");
+            await dialog.getByRole("textbox", { name: "Password" }).fill(credentials.password);
+            await dialog.getByRole("button", { name: "Continue" }).click();
+
+            await expect(section.getByRole("button", { name: "Reset cryptographic identity" })).toBeVisible();
+
+            // After resetting the identity, the user should set up a new recovery key
+            await expect(
+                util.getEncryptionRecoverySection().getByRole("button", { name: "Set up recovery" }),
+            ).toBeVisible();
+
+            await checkDeviceIsCrossSigned(app);
+        },
+    );
+});
diff --git a/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts b/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed1daecf350f308c9b910d5e61e061b0d7752584
--- /dev/null
+++ b/playwright/e2e/settings/encryption-user-tab/encryption-tab.spec.ts
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
+
+import { test, expect } from ".";
+import {
+    checkDeviceIsConnectedKeyBackup,
+    checkDeviceIsCrossSigned,
+    createBot,
+    deleteCachedSecrets,
+    verifySession,
+} from "../../crypto/utils";
+
+test.describe("Encryption tab", () => {
+    test.use({ displayName: "Alice" });
+
+    test.describe("when encryption is set up", () => {
+        let recoveryKey: GeneratedSecretStorageKey;
+        let expectedBackupVersion: string;
+
+        test.beforeEach(async ({ page, homeserver, credentials }) => {
+            // The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
+            const res = await createBot(page, homeserver, credentials);
+            recoveryKey = res.recoveryKey;
+            expectedBackupVersion = res.expectedBackupVersion;
+        });
+
+        test(
+            "should show a 'Verify this device' button if the device is unverified",
+            { tag: "@screenshot" },
+            async ({ page, app, util }) => {
+                const dialog = await util.openEncryptionTab();
+                const content = util.getEncryptionTabContent();
+
+                // The user's device is in an unverified state, therefore the only option available to them here is to verify it
+                const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
+                await expect(verifyButton).toBeVisible();
+                await expect(content).toMatchScreenshot("verify-device-encryption-tab.png");
+                await verifyButton.click();
+
+                await util.verifyDevice(recoveryKey);
+
+                await expect(content).toMatchScreenshot("default-tab.png", {
+                    mask: [content.getByTestId("deviceId"), content.getByTestId("sessionKey")],
+                });
+
+                // Check that our device is now cross-signed
+                await checkDeviceIsCrossSigned(app);
+
+                // Check that the current device is connected to key backup
+                // The backup decryption key should be in cache also, as we got it directly from the 4S
+                await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
+            },
+        );
+
+        // Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
+        //
+        // This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
+        // We simulate this case by deleting the cached secrets in the indexedDB.
+        test(
+            "should prompt to enter the recovery key when the secrets are not cached locally",
+            { tag: "@screenshot" },
+            async ({ page, app, util }) => {
+                await verifySession(app, recoveryKey.encodedPrivateKey);
+                // We need to delete the cached secrets
+                await deleteCachedSecrets(page);
+
+                await util.openEncryptionTab();
+                // We ask the user to enter the recovery key
+                const dialog = util.getEncryptionTabContent();
+                const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
+                await expect(enterKeyButton).toBeVisible();
+                await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
+                await enterKeyButton.click();
+
+                // Fill the recovery key
+                await util.enterRecoveryKey(recoveryKey);
+                await expect(dialog).toMatchScreenshot("default-tab.png", {
+                    mask: [dialog.getByTestId("deviceId"), dialog.getByTestId("sessionKey")],
+                });
+
+                // Check that our device is now cross-signed
+                await checkDeviceIsCrossSigned(app);
+
+                // Check that the current device is connected to key backup
+                // The backup decryption key should be in cache also, as we got it directly from the 4S
+                await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
+            },
+        );
+
+        test("should display the reset identity panel when the user clicks on 'Forgot recovery key?'", async ({
+            page,
+            app,
+            util,
+        }) => {
+            await verifySession(app, recoveryKey.encodedPrivateKey);
+            // We need to delete the cached secrets
+            await deleteCachedSecrets(page);
+
+            // The "Key storage is out sync" section is displayed and the user click on the "Forgot recovery key?" button
+            await util.openEncryptionTab();
+            const dialog = util.getEncryptionTabContent();
+            await dialog.getByRole("button", { name: "Forgot recovery key?" }).click();
+
+            // The user is prompted to reset their identity
+            await expect(
+                dialog.getByText("Forgot your recovery key? You’ll need to reset your identity."),
+            ).toBeVisible();
+        });
+
+        test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => {
+            await verifySession(app, recoveryKey.encodedPrivateKey);
+            await util.openEncryptionTab();
+
+            await page.getByRole("checkbox", { name: "Allow key storage" }).click();
+
+            await expect(
+                page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }),
+            ).toBeVisible();
+
+            await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png");
+
+            const deleteRequestPromises = [
+                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.master")),
+                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.self_signing")),
+                page.waitForRequest((req) => req.url().endsWith("/account_data/m.cross_signing.user_signing")),
+                page.waitForRequest((req) => req.url().endsWith("/account_data/m.megolm_backup.v1")),
+                page.waitForRequest((req) => req.url().endsWith("/account_data/m.secret_storage.default_key")),
+                page.waitForRequest((req) => req.url().includes("/account_data/m.secret_storage.key.")),
+            ];
+
+            await page.getByRole("button", { name: "Delete key storage" }).click();
+
+            await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked();
+
+            for (const prom of deleteRequestPromises) {
+                const request = await prom;
+                expect(request.method()).toBe("PUT");
+                expect(request.postData()).toBe(JSON.stringify({}));
+            }
+        });
+    });
+
+    test.describe("when encryption is not set up", () => {
+        test("'Verify this device' allows us to become verified", async ({
+            page,
+            user,
+            credentials,
+            app,
+        }, workerInfo) => {
+            const settings = await app.settings.openUserSettings("Encryption");
+
+            // Initially, our device is not verified
+            await expect(settings.getByRole("heading", { name: "Device not verified" })).toBeVisible();
+
+            // We will reset our identity
+            await settings.getByRole("button", { name: "Verify this device" }).click();
+            await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+            // First try cancelling and restarting
+            await page.getByRole("button", { name: "Cancel" }).click();
+            await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+            // Then click outside the dialog and restart
+            await page.locator("li").filter({ hasText: "Encryption" }).click({ force: true });
+            await page.getByRole("button", { name: "Proceed with reset" }).click();
+
+            // Finally we actually continue
+            await page.getByRole("button", { name: "Continue" }).click();
+
+            // Now we are verified, so we see the Key storage toggle
+            await expect(settings.getByRole("heading", { name: "Key storage" })).toBeVisible();
+        });
+    });
+});
diff --git a/playwright/e2e/settings/encryption-user-tab/index.ts b/playwright/e2e/settings/encryption-user-tab/index.ts
index 9fc8eecb7127070b208d401670e404061e44656e..a7351fd2b4f6c0b159a0639cafb17a998925a441 100644
--- a/playwright/e2e/settings/encryption-user-tab/index.ts
+++ b/playwright/e2e/settings/encryption-user-tab/index.ts
@@ -5,10 +5,10 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Page } from "@playwright/test";
-import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
+import { type Page } from "@playwright/test";
+import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
 
-import { ElementAppPage } from "../../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../../pages/ElementAppPage";
 import { test as base, expect } from "../../../element-web-test";
 export { expect };
 
@@ -18,6 +18,8 @@ export { expect };
 export const test = base.extend<{
     util: Helpers;
 }>({
+    displayName: "Alice",
+
     util: async ({ page, app, bot }, use) => {
         await use(new Helpers(page, app));
     },
@@ -41,7 +43,7 @@ class Helpers {
      */
     async verifyDevice(recoveryKey: GeneratedSecretStorageKey) {
         // Select the security phrase
-        await this.page.getByRole("button", { name: "Verify with Security Key or Phrase" }).click();
+        await this.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
         await this.enterRecoveryKey(recoveryKey);
         await this.page.getByRole("button", { name: "Done" }).click();
     }
@@ -51,9 +53,6 @@ class Helpers {
      * @param recoveryKey
      */
     async enterRecoveryKey(recoveryKey: GeneratedSecretStorageKey) {
-        // Select to use recovery key
-        await this.page.getByRole("button", { name: "use your Security Key" }).click();
-
         // Fill the recovery key
         const dialog = this.page.locator(".mx_Dialog");
         await dialog.getByRole("textbox").fill(recoveryKey.encodedPrivateKey);
@@ -67,6 +66,20 @@ class Helpers {
         return this.page.getByTestId("encryptionTab");
     }
 
+    /**
+     * Get the recovery section
+     */
+    getEncryptionRecoverySection() {
+        return this.page.getByTestId("recoveryPanel");
+    }
+
+    /**
+     * Get the encryption details section
+     */
+    getEncryptionDetailsSection() {
+        return this.page.getByTestId("encryptionDetails");
+    }
+
     /**
      * Set the default key id of the secret storage to `null`
      */
@@ -78,7 +91,7 @@ class Helpers {
     }
 
     /**
-     * Get the security key from the clipboard and fill in the input field
+     * Get the recovery key from the clipboard and fill in the input field
      * Then click on the finish button
      * @param title - The title of the dialog
      * @param confirmButtonLabel - The label of the confirm button
@@ -92,6 +105,6 @@ class Helpers {
         const clipboardContent = await this.app.getClipboard();
         await dialog.getByRole("textbox").fill(clipboardContent);
         await dialog.getByRole("button", { name: confirmButtonLabel }).click();
-        await expect(dialog).toMatchScreenshot("default-recovery.png");
+        await expect(this.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
     }
 }
diff --git a/playwright/e2e/settings/encryption-user-tab/recovery.spec.ts b/playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
index 7ce769059a1e8da149d9287d856ad400883a55af..8895e4a7ee264924b08489598731b3713d4e2215 100644
--- a/playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
+++ b/playwright/e2e/settings/encryption-user-tab/recovery.spec.ts
@@ -5,16 +5,9 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
-
 import { test, expect } from ".";
-import {
-    checkDeviceIsConnectedKeyBackup,
-    checkDeviceIsCrossSigned,
-    createBot,
-    deleteCachedSecrets,
-    verifySession,
-} from "../../crypto/utils";
+import { checkDeviceIsConnectedKeyBackup, createBot, verifySession } from "../../crypto/utils";
+import type { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
 
 test.describe("Recovery section in Encryption tab", () => {
     test.use({
@@ -22,46 +15,23 @@ test.describe("Recovery section in Encryption tab", () => {
     });
 
     let recoveryKey: GeneratedSecretStorageKey;
-    let expectedBackupVersion: string;
-
     test.beforeEach(async ({ page, homeserver, credentials }) => {
+        // The bot bootstraps cross-signing, creates a key backup and sets up a recovery key
         const res = await createBot(page, homeserver, credentials);
         recoveryKey = res.recoveryKey;
-        expectedBackupVersion = res.expectedBackupVersion;
-    });
-
-    test("should verify the device", { tag: "@screenshot" }, async ({ page, app, util }) => {
-        const dialog = await util.openEncryptionTab();
-
-        // The user's device is in an unverified state, therefore the only option available to them here is to verify it
-        const verifyButton = dialog.getByRole("button", { name: "Verify this device" });
-        await expect(verifyButton).toBeVisible();
-        await expect(util.getEncryptionTabContent()).toMatchScreenshot("verify-device-encryption-tab.png");
-        await verifyButton.click();
-
-        await util.verifyDevice(recoveryKey);
-        await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
-
-        // Check that our device is now cross-signed
-        await checkDeviceIsCrossSigned(app);
-
-        // Check that the current device is connected to key backup
-        // The backup decryption key should be in cache also, as we got it directly from the 4S
-        await app.closeDialog();
-        await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
     });
 
     test(
         "should change the recovery key",
         { tag: ["@screenshot", "@no-webkit"] },
         async ({ page, app, homeserver, credentials, util, context }) => {
-            await verifySession(app, "new passphrase");
+            await verifySession(app, recoveryKey.encodedPrivateKey);
             const dialog = await util.openEncryptionTab();
 
             // The user can only change the recovery key
             const changeButton = dialog.getByRole("button", { name: "Change recovery key" });
             await expect(changeButton).toBeVisible();
-            await expect(util.getEncryptionTabContent()).toMatchScreenshot("default-recovery.png");
+            await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("default-recovery.png");
             await changeButton.click();
 
             // Display the new recovery key and click on the copy button
@@ -82,14 +52,14 @@ test.describe("Recovery section in Encryption tab", () => {
     );
 
     test("should setup the recovery key", { tag: ["@screenshot", "@no-webkit"] }, async ({ page, app, util }) => {
-        await verifySession(app, "new passphrase");
+        await verifySession(app, recoveryKey.encodedPrivateKey);
         await util.removeSecretStorageDefaultKeyId();
 
         // The key backup is deleted and the user needs to set it up
         const dialog = await util.openEncryptionTab();
         const setupButton = dialog.getByRole("button", { name: "Set up recovery" });
         await expect(setupButton).toBeVisible();
-        await expect(util.getEncryptionTabContent()).toMatchScreenshot("set-up-recovery.png");
+        await expect(util.getEncryptionRecoverySection()).toMatchScreenshot("set-up-recovery.png");
         await setupButton.click();
 
         // Display an informative panel about the recovery key
@@ -115,42 +85,7 @@ test.describe("Recovery section in Encryption tab", () => {
         // The recovery key is now set up and the user can change it
         await expect(dialog.getByRole("button", { name: "Change recovery key" })).toBeVisible();
 
-        await app.closeDialog();
         // Check that the current device is connected to key backup and the backup version is the expected one
-        await checkDeviceIsConnectedKeyBackup(page, "1", true);
+        await checkDeviceIsConnectedKeyBackup(app, "1", true);
     });
-
-    // Test what happens if the cross-signing secrets are in secret storage but are not cached in the local DB.
-    //
-    // This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
-    // We simulate this case by deleting the cached secrets in the indexedDB.
-    test(
-        "should enter the recovery key when the secrets are not cached",
-        { tag: "@screenshot" },
-        async ({ page, app, util }) => {
-            await verifySession(app, "new passphrase");
-            // We need to delete the cached secrets
-            await deleteCachedSecrets(page);
-
-            await util.openEncryptionTab();
-            // We ask the user to enter the recovery key
-            const dialog = util.getEncryptionTabContent();
-            const enterKeyButton = dialog.getByRole("button", { name: "Enter recovery key" });
-            await expect(enterKeyButton).toBeVisible();
-            await expect(dialog).toMatchScreenshot("out-of-sync-recovery.png");
-            await enterKeyButton.click();
-
-            // Fill the recovery key
-            await util.enterRecoveryKey(recoveryKey);
-            await expect(dialog).toMatchScreenshot("default-recovery.png");
-
-            // Check that our device is now cross-signed
-            await checkDeviceIsCrossSigned(app);
-
-            // Check that the current device is connected to key backup
-            // The backup decryption key should be in cache also, as we got it directly from the 4S
-            await app.closeDialog();
-            await checkDeviceIsConnectedKeyBackup(page, expectedBackupVersion, true);
-        },
-    );
 });
diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts
index 5c7c9efffb81e1540e22cd5860d7adb4d4ed07d2..33392d884859a2d4c16d19e4cbc6e1531bae609f 100644
--- a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts
+++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts
@@ -28,7 +28,10 @@ test.describe("Preferences user settings tab", () => {
         const tab = await app.settings.openUserSettings("Preferences");
         // Assert that the top heading is rendered
         await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
-        await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png");
+        await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png", {
+            // masked due to daylight saving time
+            mask: [tab.locator("#mx_dropdownUserTimezone_value")],
+        });
     });
 
     test("should be able to change the app language", { tag: ["@no-firefox", "@no-webkit"] }, async ({ uut, user }) => {
diff --git a/playwright/e2e/settings/quick-settings-menu.spec.ts b/playwright/e2e/settings/quick-settings-menu.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e58d523c2197ecd2249ee5b8c7514a66bd61d3cd
--- /dev/null
+++ b/playwright/e2e/settings/quick-settings-menu.spec.ts
@@ -0,0 +1,18 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { test, expect } from "../../element-web-test";
+
+test.describe("Quick settings menu", () => {
+    test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
+        await page.getByRole("button", { name: "Quick settings" }).click();
+        // Assert that the top heading is renderedc
+        const settings = page.getByTestId("quick-settings-menu");
+        await expect(settings).toBeVisible();
+        await expect(settings).toMatchScreenshot("quick-settings.png");
+    });
+});
diff --git a/playwright/e2e/settings/roles-permissions-room-settings-tab.spec.ts b/playwright/e2e/settings/roles-permissions-room-settings-tab.spec.ts
index 1193afe135c821a277ecbd5f928c63adc4401d61..06ee25e9cb04d726af4d97be20045b95c636e50a 100644
--- a/playwright/e2e/settings/roles-permissions-room-settings-tab.spec.ts
+++ b/playwright/e2e/settings/roles-permissions-room-settings-tab.spec.ts
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Locator } from "@playwright/test";
+import { type Locator } from "@playwright/test";
 
 import { test, expect } from "../../element-web-test";
 
@@ -37,6 +37,15 @@ test.describe("Roles & Permissions room settings tab", () => {
         // Change the role of Alice to Moderator (50)
         await combobox.selectOption("Moderator");
         await expect(combobox).toHaveValue("50");
+
+        // Should display a modal to warn that we are demoting the only admin user
+        const modal = await page.locator(".mx_Dialog", {
+            hasText: "Warning",
+        });
+        await expect(modal).toBeVisible();
+        // Click on the continue button in the modal
+        await modal.getByRole("button", { name: "Continue" }).click();
+
         const respPromise = page.waitForRequest("**/state/**");
         await applyButton.click();
         await respPromise;
diff --git a/playwright/e2e/settings/security-user-settings-tab.spec.ts b/playwright/e2e/settings/security-user-settings-tab.spec.ts
index b723d1398fe3a1d8390a1a7462f9a98303b3efe6..25bf1a9dbed51c5cd3fcccb828c10bdd2577869c 100644
--- a/playwright/e2e/settings/security-user-settings-tab.spec.ts
+++ b/playwright/e2e/settings/security-user-settings-tab.spec.ts
@@ -25,18 +25,14 @@ test.describe("Security user settings tab", () => {
             },
         });
 
-        test.beforeEach(async ({ page, user }) => {
+        test.beforeEach(async ({ page, app, user }) => {
             // Dismiss "Notification" toast
-            await page
-                .locator(".mx_Toast_toast", { hasText: "Notifications" })
-                .getByRole("button", { name: "Dismiss" })
-                .click();
-
+            await app.closeNotificationToast();
             await page.locator(".mx_Toast_buttons").getByRole("button", { name: "Yes" }).click(); // Allow analytics
         });
 
         test.describe("AnalyticsLearnMoreDialog", () => {
-            test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
+            test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
                 const tab = await app.settings.openUserSettings("Security");
                 await tab.getByRole("button", { name: "Learn more" }).click();
                 await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
@@ -45,16 +41,57 @@ test.describe("Security user settings tab", () => {
             });
         });
 
-        test("should contain section to set ID server", async ({ app }) => {
+        test("should be able to set an ID server", async ({ app, context, user, page }) => {
             const tab = await app.settings.openUserSettings("Security");
 
-            const setIdServer = tab.locator(".mx_SetIdServer");
+            await context.route("https://identity.example.org/_matrix/identity/v2", async (route) => {
+                await route.fulfill({
+                    status: 200,
+                    json: {},
+                });
+            });
+            await context.route("https://identity.example.org/_matrix/identity/v2/account/register", async (route) => {
+                await route.fulfill({
+                    status: 200,
+                    json: {
+                        token: "AToken",
+                    },
+                });
+            });
+            await context.route("https://identity.example.org/_matrix/identity/v2/account", async (route) => {
+                await route.fulfill({
+                    status: 200,
+                    json: {
+                        user_id: user.userId,
+                    },
+                });
+            });
+            await context.route("https://identity.example.org/_matrix/identity/v2/terms", async (route) => {
+                await route.fulfill({
+                    status: 200,
+                    json: {
+                        policies: {},
+                    },
+                });
+            });
+            const setIdServer = tab.locator(".mx_IdentityServerPicker");
             await setIdServer.scrollIntoViewIfNeeded();
-            // Assert that an input area for identity server exists
-            await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
+
+            const textElement = setIdServer.getByRole("textbox", { name: "Enter a new identity server" });
+            await textElement.click();
+            await textElement.fill("https://identity.example.org");
+            await setIdServer.getByRole("button", { name: "Change" }).click();
+
+            await expect(setIdServer.getByText("Checking server")).toBeVisible();
+            // Accept terms
+            await page.getByTestId("dialog-primary-button").click();
+            // Check identity has changed.
+            await expect(setIdServer.getByText("Your identity server has been changed")).toBeVisible();
+            // Ensure section title is updated.
+            await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
         });
 
-        test("should enable show integrations as enabled", async ({ app, page }) => {
+        test("should show integrations as enabled", async ({ app, page, user }) => {
             const tab = await app.settings.openUserSettings("Security");
 
             const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");
@@ -65,7 +102,9 @@ test.describe("Security user settings tab", () => {
                 }),
             ).toBeVisible();
             // Make sure integration manager's toggle switch is enabled
-            await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
+            const toggleswitch = setIntegrationManager.getByLabel("Enable the integration manager");
+            await expect(toggleswitch).toBeVisible();
+            await expect(toggleswitch).toBeChecked();
             await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
                 "Manage integrations(scalar.vector.im)",
             );
diff --git a/playwright/e2e/share-dialog/share-by-url.spec.ts b/playwright/e2e/share-dialog/share-by-url.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5b9174782f4f5d3a318771e6eee0fa51270a1d0
--- /dev/null
+++ b/playwright/e2e/share-dialog/share-by-url.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { test, expect } from "../../element-web-test";
+
+test.describe("share from URL", () => {
+    test.use({
+        displayName: "Alice",
+        room: async ({ app }, use) => {
+            const roomId = await app.client.createRoom({ name: "A test room" });
+            await use({ roomId });
+        },
+    });
+
+    test("should share message when users navigates to share URL", async ({ page, user, room, app }) => {
+        await page.goto("/#/share?msg=Hello+world");
+        // The forward message dialog doesn't update as new infomation arrives via sync, which means sometimes
+        // this is just says, "Empty room". For the same reason, we can't reliably write a test for loading the
+        // app straight away with a /#/share url as the room doesn't appear until the client syncs.]
+        // Ideally we should fix the forward dialog to update and eliminate races, until then, there is only one
+        // room so we click the first button.
+        await page.getByRole("listitem" /*, { name: "A test room" }*/).getByRole("button", { name: "Send" }).click();
+        await page.keyboard.press("Escape");
+        await app.viewRoomByName("A test room");
+        const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
+        await expect(lastMessage).toBeVisible();
+        const lastMessageText = await lastMessage.locator(".mx_EventTile_body").innerText();
+        await expect(lastMessageText).toBe("Hello world");
+    });
+});
diff --git a/playwright/e2e/sliding-sync/sliding-sync.spec.ts b/playwright/e2e/sliding-sync/sliding-sync.spec.ts
index 0fde7ddefb837588842298ed97094c5740893e23..b540cd11d51309ae5c1d136883e7daf96d330609 100644
--- a/playwright/e2e/sliding-sync/sliding-sync.spec.ts
+++ b/playwright/e2e/sliding-sync/sliding-sync.spec.ts
@@ -6,48 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Page, Request } from "@playwright/test";
-import { GenericContainer, StartedTestContainer, Wait } from "testcontainers";
+import { type Page, type Request } from "@playwright/test";
 
 import { test as base, expect } from "../../element-web-test";
 import type { ElementAppPage } from "../../pages/ElementAppPage";
 import type { Bot } from "../../pages/bot";
 
 const test = base.extend<{
-    slidingSyncProxy: StartedTestContainer;
     testRoom: { roomId: string; name: string };
     joinedBot: Bot;
 }>({
-    slidingSyncProxy: async ({ logger, network, postgres, page, homeserver }, use, testInfo) => {
-        const container = await new GenericContainer("ghcr.io/matrix-org/sliding-sync:v0.99.3")
-            .withNetwork(network)
-            .withExposedPorts(8008)
-            .withLogConsumer(logger.getConsumer("sliding-sync-proxy"))
-            .withWaitStrategy(Wait.forHttp("/client/server.json", 8008))
-            .withEnvironment({
-                SYNCV3_SECRET: "bwahahaha",
-                SYNCV3_DB: `user=${postgres.getUsername()} dbname=postgres password=${postgres.getPassword()} host=postgres sslmode=disable`,
-                SYNCV3_SERVER: `http://homeserver:8008`,
-            })
-            .start();
-
-        const proxyAddress = `http://${container.getHost()}:${container.getMappedPort(8008)}`;
-        await page.addInitScript((proxyAddress) => {
-            window.localStorage.setItem(
-                "mx_local_settings",
-                JSON.stringify({
-                    feature_sliding_sync_proxy_url: proxyAddress,
-                }),
-            );
-            window.localStorage.setItem("mx_labs_feature_feature_sliding_sync", "true");
-        }, proxyAddress);
-        await use(container);
-        await container.stop();
-    },
-    // Ensure slidingSyncProxy is set up before the user fixture as it relies on an init script
-    credentials: async ({ slidingSyncProxy, credentials }, use) => {
-        await use(credentials);
-    },
     testRoom: async ({ user, app }, use) => {
         const name = "Test Room";
         const roomId = await app.client.createRoom({ name });
@@ -82,6 +50,14 @@ test.describe("Sliding Sync", () => {
         });
     };
 
+    test.use({
+        config: {
+            features: {
+                feature_simplified_sliding_sync: true,
+            },
+        },
+    });
+
     // Load the user fixture for all tests
     test.beforeEach(({ user }) => {});
 
@@ -188,15 +164,7 @@ test.describe("Sliding Sync", () => {
         ).not.toBeAttached();
     });
 
-    test("should not show unread indicators", async ({ page, app, joinedBot: bot, testRoom }) => {
-        // TODO: for now. Later we should.
-
-        // disable notifs in this room (TODO: CS API call?)
-        const locator = page.getByRole("treeitem", { name: "Test Room" });
-        await locator.hover();
-        await locator.getByRole("button", { name: "Notification options" }).click();
-        await page.getByRole("menuitemradio", { name: "Mute room" }).click();
-
+    test("should show unread indicators", async ({ page, app, joinedBot: bot, testRoom }) => {
         // create a new room so we know when the message has been received as it'll re-shuffle the room list
         await app.client.createRoom({ name: "Dummy" });
 
@@ -207,9 +175,7 @@ test.describe("Sliding Sync", () => {
         // wait for this message to arrive, tell by the room list resorting
         await checkOrder(["Test Room", "Dummy"], page);
 
-        await expect(
-            page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge"),
-        ).not.toBeAttached();
+        await expect(page.getByRole("treeitem", { name: "Test Room" }).locator(".mx_NotificationBadge")).toBeAttached();
     });
 
     test("should update user settings promptly", async ({ page, app }) => {
@@ -221,6 +187,37 @@ test.describe("Sliding Sync", () => {
         await expect(locator.locator(".mx_ToggleSwitch_on")).toBeAttached();
     });
 
+    test("should send subscribe_rooms on room switch if room not already subscribed", async ({ page, app }) => {
+        // create rooms and check room names are correct
+        const roomIds: string[] = [];
+        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
+            const id = await app.client.createRoom({ name: fruit });
+            roomIds.push(id);
+            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
+        }
+        const [roomAId, roomPId] = roomIds;
+
+        const matchRoomSubRequest = (subRoomId: string) => (request: Request) => {
+            if (!request.url().includes("/sync")) return false;
+            const body = request.postDataJSON();
+            return body.room_subscriptions?.[subRoomId];
+        };
+
+        // Select the Test Room and wait for playwright to get the request
+        const [request] = await Promise.all([
+            page.waitForRequest(matchRoomSubRequest(roomAId)),
+            page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
+        ]);
+        const roomSubscriptions = request.postDataJSON().room_subscriptions;
+        expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
+
+        // Switch to another room and wait for playwright to get the request
+        await Promise.all([
+            page.waitForRequest(matchRoomSubRequest(roomPId)),
+            page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
+        ]);
+    });
+
     test("should show and be able to accept/reject/rescind invites", async ({
         page,
         app,
@@ -258,8 +255,8 @@ test.describe("Sliding Sync", () => {
         // Select the room to reject
         await page.getByRole("treeitem", { name: "Room to Reject" }).click();
 
-        // Reject the invite
-        await page.locator(".mx_RoomView").getByRole("button", { name: "Reject", exact: true }).click();
+        // Decline the invite
+        await page.locator(".mx_RoomView").getByRole("button", { name: "Decline", exact: true }).click();
 
         await expect(
             page.getByRole("group", { name: "Invites" }).locator(".mx_RoomSublist_tiles").getByRole("treeitem"),
@@ -361,52 +358,4 @@ test.describe("Sliding Sync", () => {
         // ensure the reply-to does not disappear
         await expect(page.locator(".mx_ReplyPreview")).toBeVisible();
     });
-
-    test("should send unsubscribe_rooms for every room switch", async ({ page, app }) => {
-        // create rooms and check room names are correct
-        const roomIds: string[] = [];
-        for (const fruit of ["Apple", "Pineapple", "Orange"]) {
-            const id = await app.client.createRoom({ name: fruit });
-            roomIds.push(id);
-            await expect(page.getByRole("treeitem", { name: fruit })).toBeVisible();
-        }
-        const [roomAId, roomPId, roomOId] = roomIds;
-
-        const matchRoomSubRequest = (subRoomId: string) => (request: Request) => {
-            if (!request.url().includes("/sync")) return false;
-            const body = request.postDataJSON();
-            return body.txn_id && body.room_subscriptions?.[subRoomId];
-        };
-        const matchRoomUnsubRequest = (unsubRoomId: string) => (request: Request) => {
-            if (!request.url().includes("/sync")) return false;
-            const body = request.postDataJSON();
-            return (
-                body.txn_id && body.unsubscribe_rooms?.includes(unsubRoomId) && !body.room_subscriptions?.[unsubRoomId]
-            );
-        };
-
-        // Select the Test Room and wait for playwright to get the request
-        const [request] = await Promise.all([
-            page.waitForRequest(matchRoomSubRequest(roomAId)),
-            page.getByRole("treeitem", { name: "Apple", exact: true }).click(),
-        ]);
-        const roomSubscriptions = request.postDataJSON().room_subscriptions;
-        expect(roomSubscriptions, "room_subscriptions is object").toBeDefined();
-
-        // Switch to another room and wait for playwright to get the request
-        await Promise.all([
-            page.waitForRequest(matchRoomSubRequest(roomPId)),
-            page.waitForRequest(matchRoomUnsubRequest(roomAId)),
-            page.getByRole("treeitem", { name: "Pineapple", exact: true }).click(),
-        ]);
-
-        // And switch to even another room and wait for playwright to get the request
-        await Promise.all([
-            page.waitForRequest(matchRoomSubRequest(roomOId)),
-            page.waitForRequest(matchRoomUnsubRequest(roomPId)),
-            page.getByRole("treeitem", { name: "Orange", exact: true }).click(),
-        ]);
-
-        // TODO: Add tests for encrypted rooms
-    });
 });
diff --git a/playwright/e2e/spaces/spaces.spec.ts b/playwright/e2e/spaces/spaces.spec.ts
index 37e5606cc135936e81bc33ab6f1b22209220515b..56c8ae22b82644bc867dbd7a71fb93b7439effdb 100644
--- a/playwright/e2e/spaces/spaces.spec.ts
+++ b/playwright/e2e/spaces/spaces.spec.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import type { Locator, Page } from "@playwright/test";
 import { test, expect } from "../../element-web-test";
 import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 import { isDendrite } from "../../plugins/homeserver/dendrite";
 
 async function openSpaceCreateMenu(page: Page): Promise<Locator> {
@@ -35,17 +35,18 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
                     name: spaceName,
                 },
             },
-            ...roomIds.map(spaceChildInitialState),
+            ...roomIds.map((r) => spaceChildInitialState(r)),
         ],
     };
 }
 
-function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"]["0"] {
+function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
     return {
         type: "m.space.child",
         state_key: roomId,
         content: {
             via: [roomId.split(":")[1]],
+            order,
         },
     };
 }
@@ -121,9 +122,10 @@ test.describe("Spaces", () => {
         await page.getByRole("button", { name: "Skip for now" }).click();
 
         // Assert rooms exist in the room list
-        await expect(page.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
-        await expect(page.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
-        await expect(page.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
+        const roomList = page.getByRole("tree", { name: "Rooms" });
+        await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
+        await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
+        await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
 
         // Assert rooms exist in the space explorer
         await expect(
@@ -155,7 +157,7 @@ test.describe("Spaces", () => {
 
         await page.getByRole("button", { name: "Just me" }).click();
 
-        await page.getByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
+        await page.getByRole("checkbox", { name: "Sample Room" }).click();
 
         // Temporal implementation as multiple elements with the role "button" and name "Add" are found
         await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
@@ -165,6 +167,50 @@ test.describe("Spaces", () => {
         ).toBeVisible();
     });
 
+    test(
+        "should allow user to add an existing room to a space after creation",
+        { tag: "@screenshot" },
+        async ({ page, app, user }) => {
+            await app.client.createRoom({
+                name: "Sample Room",
+            });
+            await app.client.createRoom({
+                name: "A Room that will not be selected",
+            });
+
+            const menu = await openSpaceCreateMenu(page);
+            await menu.getByRole("button", { name: "Private" }).click();
+
+            await menu
+                .locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
+                .setInputFiles("playwright/sample-files/riot.png");
+            await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
+            await menu
+                .getByRole("textbox", { name: "Description" })
+                .fill("This is a personal space to mourn Riot.im...");
+            await menu.getByRole("textbox", { name: "Name" }).fill("This is my Riot");
+            await menu.getByRole("textbox", { name: "Name" }).press("Enter");
+
+            await page.getByRole("button", { name: "Just me" }).click();
+
+            await page.getByRole("button", { name: "Skip for now" }).click();
+
+            await page.getByRole("button", { name: "Add room" }).click();
+            await page.getByRole("menuitem", { name: "Add existing room" }).click();
+
+            await page.getByRole("checkbox", { name: "Sample Room" }).click();
+
+            await expect(page.getByRole("dialog", { name: "Avatar Add existing rooms" })).toMatchScreenshot(
+                "add-existing-rooms-dialog.png",
+            );
+
+            await page.getByRole("button", { name: "Add" }).click();
+            await expect(
+                page.locator(".mx_SpaceHierarchy_list").getByRole("treeitem", { name: "Sample Room" }),
+            ).toBeVisible();
+        },
+    );
+
     test("should allow user to invite another to a space", { tag: "@no-webkit" }, async ({ page, app, user, bot }) => {
         await app.client.createSpace({
             visibility: "public" as any,
@@ -227,7 +273,7 @@ test.describe("Spaces", () => {
     test(
         "should render subspaces in the space panel only when expanded",
         { tag: "@screenshot" },
-        async ({ page, app, user, axe, checkA11y }) => {
+        async ({ page, app, user, axe }) => {
             axe.disableRules([
                 // Disable this check as it triggers on nested roving tab index elements which are in practice fine
                 "nested-interactive",
@@ -249,7 +295,7 @@ test.describe("Spaces", () => {
             await expect(spaceTree.getByRole("button", { name: "Root Space" })).toBeVisible();
             await expect(spaceTree.getByRole("button", { name: "Child Space" })).not.toBeVisible();
 
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
             await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-collapsed.png");
 
             // This finds the expand button with the class name "mx_SpaceButton_toggleCollapse". Note there is another
@@ -261,7 +307,7 @@ test.describe("Spaces", () => {
             await expect(item).toBeVisible();
             await expect(item.locator(".mx_SpaceItem", { hasText: "Child Space" })).toBeVisible();
 
-            await checkA11y();
+            await expect(axe).toHaveNoViolations();
             await expect(page.locator(".mx_SpacePanel")).toMatchScreenshot("space-panel-expanded.png");
         },
     );
@@ -291,4 +337,36 @@ test.describe("Spaces", () => {
         // Assert we get shown the new room intro, and thus not the soft crash screen
         await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
     });
+
+    test("should render spaces view", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
+        axe.disableRules([
+            // Disable this check as it triggers on nested roving tab index elements which are in practice fine
+            "nested-interactive",
+            // XXX: We have some known contrast issues here
+            "color-contrast",
+        ]);
+
+        const childSpaceId1 = await app.client.createSpace({
+            name: "Child Space 1",
+            initial_state: [],
+        });
+        const childSpaceId2 = await app.client.createSpace({
+            name: "Child Space 2",
+            initial_state: [],
+        });
+        const childSpaceId3 = await app.client.createSpace({
+            name: "Child Space 3",
+            initial_state: [],
+        });
+        await app.client.createSpace({
+            name: "Root Space",
+            initial_state: [
+                spaceChildInitialState(childSpaceId1, "a"),
+                spaceChildInitialState(childSpaceId2, "b"),
+                spaceChildInitialState(childSpaceId3, "c"),
+            ],
+        });
+        await app.viewSpaceByName("Root Space");
+        await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("space-room-view.png");
+    });
 });
diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts
index 7da6974a92caa71e0d146b0abcc3646b30c706fa..a050175c83ab93e9924e39a9466a681cf2e30cfe 100644
--- a/playwright/e2e/spaces/threads-activity-centre/index.ts
+++ b/playwright/e2e/spaces/threads-activity-centre/index.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JSHandle, Locator, Page } from "@playwright/test";
+import { type JSHandle, type Locator, type Page } from "@playwright/test";
 
 import type { MatrixEvent, IContent, Room } from "matrix-js-sdk/src/matrix";
 import { test as base, expect } from "../../../element-web-test";
-import { Bot } from "../../../pages/bot";
-import { Client } from "../../../pages/client";
-import { ElementAppPage } from "../../../pages/ElementAppPage";
-import { Credentials } from "../../../plugins/homeserver";
+import { type Bot } from "../../../pages/bot";
+import { type Client } from "../../../pages/client";
+import { type ElementAppPage } from "../../../pages/ElementAppPage";
+import { type Credentials } from "../../../plugins/homeserver";
 
 type RoomRef = { name: string; roomId: string };
 
diff --git a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts
index 683577dce414d1a4f21b3d31c69c577aa4ae0326..eec28099a59104d82e3111afb60c3616454e2335 100644
--- a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts
+++ b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts
@@ -19,7 +19,6 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => {
     test.use({
         displayName: "Alice",
         botCreateOpts: { displayName: "Other User" },
-        labsFlags: ["threadsActivityCentre"],
     });
 
     test(
diff --git a/playwright/e2e/timeline/media-preview-settings.spec.ts b/playwright/e2e/timeline/media-preview-settings.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e32a7dbc82c8972f210b24bdd0f79f52689d695b
--- /dev/null
+++ b/playwright/e2e/timeline/media-preview-settings.spec.ts
@@ -0,0 +1,139 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import * as fs from "node:fs";
+import { type EventType, type MsgType, type RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
+
+import { test, expect } from "../../element-web-test";
+
+const MEDIA_FILE = fs.readFileSync("playwright/sample-files/riot.png");
+
+test.describe("Media preview settings", () => {
+    test.use({
+        displayName: "Alan",
+        botCreateOpts: {
+            displayName: "Bob",
+        },
+        room: async ({ app, page, homeserver, bot, user }, use) => {
+            const mxc = (await bot.uploadContent(MEDIA_FILE, { name: "image.png", type: "image/png" })).content_uri;
+            const roomId = await bot.createRoom({
+                name: "Test room",
+                invite: [user.userId],
+                initial_state: [{ type: "m.room.avatar", content: { url: mxc }, state_key: "" }],
+            });
+            await bot.sendEvent(roomId, null, "m.room.message" as EventType, {
+                msgtype: "m.image" as MsgType,
+                body: "image.png",
+                url: mxc,
+            });
+
+            await use({ roomId });
+        },
+    });
+
+    test("should be able to hide avatars of inviters", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
+        let settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Hide avatars of room and inviter").click();
+        await app.closeDialog();
+        await app.viewRoomById(room.roomId);
+        await expect(
+            page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
+        ).toMatchScreenshot("invite-no-avatar.png", {
+            // Hide the mxid, which is not stable.
+            css: `
+                .mx_RoomPreviewBar_inviter_mxid {
+                    display: none !important;
+                }
+            `,
+        });
+        await expect(
+            page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
+        ).toMatchScreenshot("invite-room-tree-no-avatar.png");
+
+        // And then go back to being visible
+        settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Hide avatars of room and inviter").click();
+        await app.closeDialog();
+        await page.goto("#/home");
+        await app.viewRoomById(room.roomId);
+        await expect(
+            page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
+        ).toMatchScreenshot("invite-with-avatar.png", {
+            // Hide the mxid, which is not stable.
+            css: `
+                .mx_RoomPreviewBar_inviter_mxid {
+                    display: none !important;
+                }
+            `,
+        });
+        await expect(
+            page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
+        ).toMatchScreenshot("invite-room-tree-with-avatar.png");
+    });
+
+    test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {
+        const settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
+        await app.closeDialog();
+        await app.viewRoomById(room.roomId);
+        await page.getByRole("button", { name: "Accept" }).click();
+        await expect(page.getByText("Show image")).toBeVisible();
+    });
+    test("should be able to hide media in non-private rooms globally", async ({ page, app, room, user, bot }) => {
+        await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
+            join_rule: "public",
+        });
+        const settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Show media in timeline").getByLabel("In private rooms").click();
+        await app.closeDialog();
+        await app.viewRoomById(room.roomId);
+        await page.getByRole("button", { name: "Accept" }).click();
+        await expect(page.getByText("Show image")).toBeVisible();
+        for (const joinRule of ["invite", "knock", "restricted"] as RoomJoinRulesEventContent["join_rule"][]) {
+            await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
+                join_rule: joinRule,
+            } satisfies RoomJoinRulesEventContent);
+            await expect(page.getByText("Show image")).not.toBeVisible();
+        }
+    });
+    test("should be able to show media in rooms globally", async ({ page, app, room, user }) => {
+        const settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
+        await app.closeDialog();
+        await app.viewRoomById(room.roomId);
+        await page.getByRole("button", { name: "Accept" }).click();
+        await expect(page.getByText("Show image")).not.toBeVisible();
+    });
+    test("should be able to hide media in an individual room", async ({ page, app, room, user }) => {
+        const settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
+        await app.closeDialog();
+
+        await app.viewRoomById(room.roomId);
+        await page.getByRole("button", { name: "Accept" }).click();
+
+        const roomSettings = await app.settings.openRoomSettings("General");
+        await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
+        await app.closeDialog();
+
+        await expect(page.getByText("Show image")).toBeVisible();
+    });
+    test("should be able to show media in an individual room", async ({ page, app, room, user }) => {
+        const settings = await app.settings.openUserSettings("Preferences");
+        await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
+        await app.closeDialog();
+
+        await app.viewRoomById(room.roomId);
+        await page.getByRole("button", { name: "Accept" }).click();
+
+        const roomSettings = await app.settings.openRoomSettings("General");
+        await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
+        await app.closeDialog();
+
+        await expect(page.getByText("Show image")).not.toBeVisible();
+    });
+});
diff --git a/playwright/e2e/timeline/timeline.spec.ts b/playwright/e2e/timeline/timeline.spec.ts
index 3de0f7c0f2981f6d3c14e009b4b9b9a94b57eded..e5bfa0c71bfc65c5d4bb7dda110887e3f3772cd7 100644
--- a/playwright/e2e/timeline/timeline.spec.ts
+++ b/playwright/e2e/timeline/timeline.spec.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -13,8 +13,8 @@ import type { ISendEventResponse, EventType, MsgType } from "matrix-js-sdk/src/m
 import { test, expect } from "../../element-web-test";
 import { SettingLevel } from "../../../src/settings/SettingLevel";
 import { Layout } from "../../../src/settings/enums/Layout";
-import { Client } from "../../pages/client";
-import { ElementAppPage } from "../../pages/ElementAppPage";
+import { type Client } from "../../pages/client";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
 import { Bot } from "../../pages/bot";
 
 // The avatar size used in the timeline
@@ -28,6 +28,8 @@ const NEW_AVATAR = fs.readFileSync("playwright/sample-files/element.png");
 const OLD_NAME = "Alan";
 const NEW_NAME = "Alan (away)";
 
+const VIDEO_FILE = fs.readFileSync("playwright/sample-files/5secvid.webm");
+
 const getEventTilesWithBodies = (page: Page): Locator => {
     return page.locator(".mx_EventTile").filter({ has: page.locator(".mx_EventTile_body") });
 };
@@ -277,7 +279,7 @@ test.describe("Timeline", () => {
         test(
             "should add inline start margin to an event line on IRC layout",
             { tag: "@screenshot" },
-            async ({ page, app, room, axe, checkA11y }) => {
+            async ({ page, app, room, axe }) => {
                 axe.disableRules("color-contrast");
 
                 await page.goto(`/#/room/${room.roomId}`);
@@ -318,7 +320,7 @@ test.describe("Timeline", () => {
                 `,
                     },
                 );
-                await checkA11y();
+                await expect(axe).toHaveNoViolations();
             },
         );
     });
@@ -743,68 +745,64 @@ test.describe("Timeline", () => {
             ).toBeVisible();
         });
 
-        test(
-            "should render url previews",
-            { tag: "@screenshot" },
-            async ({ page, app, room, axe, checkA11y, context }) => {
-                axe.disableRules("color-contrast");
-
-                // Element Web uses a Service Worker to rewrite unauthenticated media requests to authenticated ones, but
-                // the page can't see this happening. We intercept the route at the BrowserContext to ensure we get it
-                // post-worker, but we can't waitForResponse on that, so the page context is still used there. Because
-                // the page doesn't see the rewrite, it waits for the unauthenticated route. This is only confusing until
-                // the js-sdk (and thus the app as a whole) switches to using authenticated endpoints by default, hopefully.
-                await context.route(
-                    "**/_matrix/client/v1/media/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*",
-                    async (route) => {
-                        await route.fulfill({
-                            path: "playwright/sample-files/riot.png",
-                        });
-                    },
-                );
-                await page.route(
-                    "**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*",
-                    async (route) => {
-                        await route.fulfill({
-                            json: {
-                                "og:title": "Element Call",
-                                "og:description": null,
-                                "og:image:width": 48,
-                                "og:image:height": 48,
-                                "og:image": "mxc://matrix.org/2022-08-16_yaiSVSRIsNFfxDnV",
-                                "og:image:type": "image/png",
-                                "matrix:image:size": 2121,
-                            },
-                        });
-                    },
-                );
+        test("should render url previews", { tag: "@screenshot" }, async ({ page, app, room, axe, context }) => {
+            axe.disableRules("color-contrast");
+
+            // Element Web uses a Service Worker to rewrite unauthenticated media requests to authenticated ones, but
+            // the page can't see this happening. We intercept the route at the BrowserContext to ensure we get it
+            // post-worker, but we can't waitForResponse on that, so the page context is still used there. Because
+            // the page doesn't see the rewrite, it waits for the unauthenticated route. This is only confusing until
+            // the js-sdk (and thus the app as a whole) switches to using authenticated endpoints by default, hopefully.
+            await context.route(
+                "**/_matrix/client/v1/media/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*",
+                async (route) => {
+                    await route.fulfill({
+                        path: "playwright/sample-files/riot.png",
+                    });
+                },
+            );
+            await page.route(
+                "**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*",
+                async (route) => {
+                    await route.fulfill({
+                        json: {
+                            "og:title": "Element Call",
+                            "og:description": null,
+                            "og:image:width": 48,
+                            "og:image:height": 48,
+                            "og:image": "mxc://matrix.org/2022-08-16_yaiSVSRIsNFfxDnV",
+                            "og:image:type": "image/png",
+                            "matrix:image:size": 2121,
+                        },
+                    });
+                },
+            );
 
-                const requestPromises: Promise<any>[] = [
-                    page.waitForResponse("**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*"),
-                    // see context.route above for why we listen for the unauthenticated endpoint
-                    page.waitForResponse("**/_matrix/media/v3/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*"),
-                ];
+            const requestPromises: Promise<any>[] = [
+                page.waitForResponse("**/_matrix/media/v3/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*"),
+                // see context.route above for why we listen for the unauthenticated endpoint
+                page.waitForResponse("**/_matrix/media/v3/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*"),
+            ];
 
-                await app.client.sendMessage(room.roomId, "https://call.element.io/");
-                await page.goto(`/#/room/${room.roomId}`);
+            await app.client.sendMessage(room.roomId, "https://call.element.io/");
+            await page.goto(`/#/room/${room.roomId}`);
 
-                await expect(page.locator(".mx_LinkPreviewWidget").getByText("Element Call")).toBeVisible();
-                await Promise.all(requestPromises);
+            await expect(page.locator(".mx_LinkPreviewWidget").getByText("Element Call")).toBeVisible();
+            await Promise.all(requestPromises);
 
-                await checkA11y();
+            await expect(axe).toHaveNoViolations();
 
-                await app.timeline.scrollToBottom();
-                await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot("url-preview.png", {
-                    // Exclude timestamp and read marker from snapshot
-                    mask: [page.locator(".mx_MessageTimestamp")],
-                    css: `
+            await app.timeline.scrollToBottom();
+            await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot("url-preview.png", {
+                // Exclude timestamp and read marker from snapshot
+                mask: [page.locator(".mx_MessageTimestamp")],
+                css: `
                     .mx_TopUnreadMessagesBar, .mx_MessagePanel_myReadMarker {
                         display: none !important;
                     }
                 `,
-                });
-            },
-        );
+            });
+        });
 
         test.describe("on search results panel", () => {
             test(
@@ -875,6 +873,73 @@ test.describe("Timeline", () => {
                 );
             });
         });
+
+        test("should render a code block", { tag: "@screenshot" }, async ({ page, app, room }) => {
+            await page.goto(`/#/room/${room.roomId}`);
+            await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
+
+            // Wait until configuration is finished
+            await expect(
+                page
+                    .locator(".mx_GenericEventListSummary_summary")
+                    .getByText(`${OLD_NAME} created and configured the room.`),
+            ).toBeVisible();
+
+            // Send a code block
+            const composer = app.getComposerField();
+            await composer.fill("```\nconsole.log('Hello, world!');\n```");
+            await composer.press("Enter");
+
+            const tile = page.locator(".mx_EventTile");
+            await expect(tile).toBeVisible();
+            await expect(tile).toMatchScreenshot("code-block.png", { mask: [page.locator(".mx_MessageTimestamp")] });
+
+            // Edit a code block and assert the edited code block has been correctly rendered
+            await tile.hover();
+            await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }).click();
+            await page
+                .getByRole("textbox", { name: "Edit message" })
+                .fill("```\nconsole.log('Edited: Hello, world!');\n```");
+            await page.getByRole("textbox", { name: "Edit message" }).press("Enter");
+
+            const newTile = page.locator(".mx_EventTile");
+            await expect(newTile).toMatchScreenshot("edited-code-block.png", {
+                mask: [page.locator(".mx_MessageTimestamp")],
+            });
+        });
+
+        test("should be able to hide an image", { tag: "@screenshot" }, async ({ page, app, room, context }) => {
+            await app.viewRoomById(room.roomId);
+            await sendImage(app.client, room.roomId, NEW_AVATAR);
+            await app.timeline.scrollToBottom();
+            const imgTile = page.locator(".mx_MImageBody").first();
+            await expect(imgTile).toBeVisible();
+            await imgTile.hover();
+            await page.getByRole("button", { name: "Hide" }).click();
+
+            // Check that the image is now hidden.
+            await expect(page.getByRole("button", { name: "Show image" })).toBeVisible();
+        });
+
+        test("should be able to hide a video", async ({ page, app, room, context }) => {
+            await app.viewRoomById(room.roomId);
+            const upload = await app.client.uploadContent(VIDEO_FILE, { name: "bbb.webm", type: "video/webm" });
+            await app.client.sendEvent(room.roomId, null, "m.room.message" as EventType, {
+                msgtype: "m.video" as MsgType,
+                body: "bbb.webm",
+                url: upload.content_uri,
+            });
+
+            await app.timeline.scrollToBottom();
+            const imgTile = page.locator(".mx_MVideoBody").first();
+            await expect(imgTile).toBeVisible();
+            await imgTile.hover();
+            await page.getByRole("button", { name: "Hide" }).click();
+
+            // Check that the video is now hidden.
+            await expect(page.getByRole("button", { name: "Show video" })).toBeVisible();
+            await expect(page.locator("video")).not.toBeVisible();
+        });
     });
 
     test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
@@ -1276,4 +1341,44 @@ test.describe("Timeline", () => {
             );
         });
     });
+
+    test.describe("spoilers", { tag: "@screenshot" }, () => {
+        test("clicking a spoiler containing the pill de-spoilers on 1st click, then follows link on 2nd", async ({
+            page,
+            user,
+            app,
+            room,
+        }) => {
+            // View room
+            await page.goto(`/#/room/${room.roomId}`);
+
+            // Send a spoilered pill
+            await app.client.sendMessage(room.roomId, {
+                msgtype: "m.text",
+                body: user.userId,
+                format: "org.matrix.custom.html",
+                formatted_body: `<span data-mx-spoiler>https://matrix.to/#/${user.userId}</span>`,
+            });
+
+            const screenshotOptions = {
+                css: `
+                    .mx_MessageTimestamp {
+                        display: none !important;
+                    }
+                `,
+            };
+
+            const eventTile = page.locator(".mx_RoomView_body .mx_EventTile_last");
+            await expect(eventTile).toMatchScreenshot("spoiler.png", screenshotOptions);
+
+            const rightPanelButton = page.getByText("Share profile");
+            const pill = page.locator(".mx_UserPill");
+            await pill.click({ force: true }); // force to click the spoiler wrapper instead
+            await expect(eventTile).toMatchScreenshot("spoiler-uncovered.png", screenshotOptions);
+            await expect(rightPanelButton).not.toBeVisible(); // assert the right panel is not yet open
+
+            await pill.click();
+            await expect(rightPanelButton).toBeVisible(); // assert the right panel is open
+        });
+    });
 });
diff --git a/playwright/e2e/user-view/user-view.spec.ts b/playwright/e2e/user-view/user-view.spec.ts
index f3745e7859528340d94ea2e6bd472506208246df..de97133e6a0c4412997825a474af37417244696e 100644
--- a/playwright/e2e/user-view/user-view.spec.ts
+++ b/playwright/e2e/user-view/user-view.spec.ts
@@ -19,7 +19,6 @@ test.describe("UserView", () => {
 
         const rightPanel = page.locator("#mx_RightPanel");
         await expect(rightPanel.getByRole("heading", { name: bot.credentials.displayName, exact: true })).toBeVisible();
-        await expect(rightPanel.getByText("1 session")).toBeVisible();
         await expect(rightPanel).toMatchScreenshot("user-info.png", {
             mask: [page.locator(".mx_UserInfo_profile_mxid")],
             css: `
diff --git a/playwright/e2e/utils.ts b/playwright/e2e/utils.ts
index 49e7577bf6d2d50641a0c3ed91c8d70bfaab1584..49561ed1b49f4942b9e7253af3647527db7234f0 100644
--- a/playwright/e2e/utils.ts
+++ b/playwright/e2e/utils.ts
@@ -12,7 +12,7 @@ import { uniqueId } from "lodash";
 import { expect, type Page } from "@playwright/test";
 
 import type { ClientEvent, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
-import { Client } from "../pages/client";
+import { type Client } from "../pages/client";
 
 /**
  * Resolves when room state matches predicate.
diff --git a/playwright/e2e/widgets/stickers.spec.ts b/playwright/e2e/widgets/stickers.spec.ts
index 601dcd8b79626ddb26de200a1564c63a5f620cc3..e7b86e411d86cf91cadc1c69928b4ba5435820f9 100644
--- a/playwright/e2e/widgets/stickers.spec.ts
+++ b/playwright/e2e/widgets/stickers.spec.ts
@@ -10,8 +10,8 @@ import * as fs from "node:fs";
 
 import type { Page } from "@playwright/test";
 import { test, expect } from "../../element-web-test";
-import { ElementAppPage } from "../../pages/ElementAppPage";
-import { Credentials } from "../../plugins/homeserver";
+import { type ElementAppPage } from "../../pages/ElementAppPage";
+import { type Credentials } from "../../plugins/homeserver";
 import type { UserWidget } from "../../../src/utils/WidgetUtils-types.ts";
 
 const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker";
diff --git a/playwright/e2e/widgets/widget-pip-close.spec.ts b/playwright/e2e/widgets/widget-pip-close.spec.ts
index ec3184ed6c2ad94d8ef84454e090f75d9591ad71..e8163b5ee21f0bf9986a9aabdcd6b42e8bcbff44 100644
--- a/playwright/e2e/widgets/widget-pip-close.spec.ts
+++ b/playwright/e2e/widgets/widget-pip-close.spec.ts
@@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
 import type { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
 import type { MatrixEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { test, expect } from "../../element-web-test";
-import { Client } from "../../pages/client";
+import { type Client } from "../../pages/client";
 
 const DEMO_WIDGET_ID = "demo-widget-id";
 const DEMO_WIDGET_NAME = "Demo Widget";
diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts
index 9468ddeec379c5c23b4d45ef16578cd6250a980e..852053346130afc12a392b49018979cc59575e50 100644
--- a/playwright/element-web-test.ts
+++ b/playwright/element-web-test.ts
@@ -7,91 +7,42 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    expect as baseExpect,
-    Locator,
-    Page,
-    ExpectMatcherState,
-    ElementHandle,
-    PlaywrightTestArgs,
-    Fixtures as _Fixtures,
+    type ExpectMatcherState,
+    type MatcherReturnType,
+    type Page,
+    type Locator,
+    type PlaywrightTestArgs,
+    type Fixtures as _Fixtures,
 } from "@playwright/test";
-import { sanitizeForFilePath } from "playwright-core/lib/utils";
-import AxeBuilder from "@axe-core/playwright";
-import _ from "lodash";
-import { extname } from "node:path";
+import {
+    type TestFixtures as BaseTestFixtures,
+    expect as baseExpect,
+    type ToMatchScreenshotOptions,
+} from "@element-hq/element-web-playwright-common";
 
 import type { IConfigOptions } from "../src/IConfigOptions";
-import { Credentials } from "./plugins/homeserver";
+import { type Credentials } from "./plugins/homeserver";
 import { ElementAppPage } from "./pages/ElementAppPage";
 import { Crypto } from "./pages/crypto";
 import { Toasts } from "./pages/toasts";
-import { Bot, CreateBotOpts } from "./pages/bot";
+import { Bot, type CreateBotOpts } from "./pages/bot";
 import { Webserver } from "./plugins/webserver";
-import { Options, Services, test as base } from "./services.ts";
+import { type WorkerOptions, type Services, test as base } from "./services";
 
 // Enable experimental service worker support
 // See https://playwright.dev/docs/service-workers-experimental#how-to-enable
 process.env["PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS"] = "1";
 
-// This is deliberately quite a minimal config.json, so that we can test that the default settings actually work.
-const CONFIG_JSON: Partial<IConfigOptions> = {
-    // The default language is set here for test consistency
-    setting_defaults: {
-        language: "en-GB",
-    },
-
-    // the location tests want a map style url.
-    map_style_url: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
-
-    features: {
-        // We don't want to go through the feature announcement during the e2e test
-        feature_release_announcement: false,
-    },
-};
+declare module "@element-hq/element-web-playwright-common" {
+    // Improve the type for the config fixture based on the real type
+    export interface Config extends Omit<IConfigOptions, "default_server_config"> {}
+}
 
 export interface CredentialsWithDisplayName extends Credentials {
     displayName: string;
 }
 
-export interface TestFixtures {
-    axe: AxeBuilder;
-    checkA11y: () => Promise<void>;
-
-    /**
-     * The contents of the config.json to send when the client requests it.
-     */
-    config: typeof CONFIG_JSON;
-
-    /**
-     * The displayname to use for the user registered in {@link #credentials}.
-     *
-     * To set it, call `test.use({ displayName: "myDisplayName" })` in the test file or `describe` block.
-     * See {@link https://playwright.dev/docs/api/class-test#test-use}.
-     */
-    displayName?: string;
-
-    /**
-     * A test fixture which registers a test user on the {@link #homeserver} and supplies the details
-     * of the registered user.
-     */
-    credentials: CredentialsWithDisplayName;
-
-    /**
-     * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`},
-     * but adds an initScript which will populate localStorage with the user's details from
-     * {@link #credentials} and {@link #homeserver}.
-     *
-     * Similar to {@link #user}, but doesn't load the app.
-     */
-    pageWithCredentials: Page;
-
-    /**
-     * A (rather poorly-named) test fixture which registers a user per {@link #credentials}, stores
-     * the credentials into localStorage per {@link #homeserver}, and then loads the front page of the
-     * app.
-     */
-    user: CredentialsWithDisplayName;
-
+export interface TestFixtures extends BaseTestFixtures {
     /**
      * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`},
      * but wraps the returned `Page` in a class of utilities for interacting with the Element-Web UI,
@@ -105,13 +56,11 @@ export interface TestFixtures {
     uut?: Locator; // Unit Under Test, useful place to refer a prepared locator
     botCreateOpts: CreateBotOpts;
     bot: Bot;
-    labsFlags: string[];
     webserver: Webserver;
-    disablePresence: boolean;
 }
 
 type CombinedTestFixtures = PlaywrightTestArgs & TestFixtures;
-export type Fixtures = _Fixtures<CombinedTestFixtures, Services & Options, CombinedTestFixtures>;
+export type Fixtures = _Fixtures<CombinedTestFixtures, Services & WorkerOptions, CombinedTestFixtures>;
 export const test = base.extend<TestFixtures>({
     context: async ({ context }, use, testInfo) => {
         // We skip tests instead of using grep-invert to still surface the counts in the html report
@@ -121,101 +70,11 @@ export const test = base.extend<TestFixtures>({
         );
         await use(context);
     },
-    disablePresence: false,
-    config: {}, // We merge this atop the default CONFIG_JSON in the page fixture to make extending it easier
-    page: async ({ homeserver, context, page, config, labsFlags, disablePresence }, use) => {
-        await context.route(`http://localhost:8080/config.json*`, async (route) => {
-            const json = {
-                ...CONFIG_JSON,
-                ...config,
-                default_server_config: {
-                    "m.homeserver": {
-                        base_url: homeserver.baseUrl,
-                    },
-                    ...config.default_server_config,
-                },
-            };
-            json["features"] = {
-                ...json["features"],
-                // Enable the lab features
-                ...labsFlags.reduce((obj, flag) => {
-                    obj[flag] = true;
-                    return obj;
-                }, {}),
-            };
-            if (disablePresence) {
-                json["enable_presence_by_hs_url"] = {
-                    [homeserver.baseUrl]: false,
-                };
-            }
-            await route.fulfill({ json });
-        });
-        await use(page);
-    },
-
-    displayName: undefined,
-    credentials: async ({ context, homeserver, displayName: testDisplayName }, use, testInfo) => {
-        const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"];
-        const password = _.uniqueId("password_");
-        const displayName = testDisplayName ?? _.sample(names)!;
-
-        const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName);
-        console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`);
-
-        await use({
-            ...credentials,
-            displayName,
-        });
-    },
-    labsFlags: [],
-
-    pageWithCredentials: async ({ page, homeserver, credentials }, use) => {
-        await page.addInitScript(
-            ({ baseUrl, credentials }) => {
-                // Seed the localStorage with the required credentials
-                window.localStorage.setItem("mx_hs_url", baseUrl);
-                window.localStorage.setItem("mx_user_id", credentials.userId);
-                window.localStorage.setItem("mx_access_token", credentials.accessToken);
-                window.localStorage.setItem("mx_device_id", credentials.deviceId);
-                window.localStorage.setItem("mx_is_guest", "false");
-                window.localStorage.setItem("mx_has_pickle_key", "false");
-                window.localStorage.setItem("mx_has_access_token", "true");
-
-                window.localStorage.setItem(
-                    "mx_local_settings",
-                    JSON.stringify({
-                        // Retain any other settings which may have already been set
-                        ...JSON.parse(window.localStorage.getItem("mx_local_settings") || "{}"),
-                        // Ensure the language is set to a consistent value
-                        language: "en",
-                    }),
-                );
-            },
-            { baseUrl: homeserver.baseUrl, credentials },
-        );
-        await use(page);
-    },
-
-    user: async ({ pageWithCredentials: page, credentials }, use) => {
-        await page.goto("/");
-        await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 });
-        await use(credentials);
-    },
 
-    axe: async ({ page }, use) => {
-        await use(new AxeBuilder({ page }).exclude("[data-floating-ui-portal]"));
+    axe: async ({ axe }, use) => {
+        // Exclude floating UI for now
+        await use(axe.exclude("[data-floating-ui-portal]"));
     },
-    checkA11y: async ({ axe }, use, testInfo) =>
-        use(async () => {
-            const results = await axe.analyze();
-
-            await testInfo.attach("accessibility-scan-results", {
-                body: JSON.stringify(results, null, 2),
-                contentType: "application/json",
-            });
-
-            expect(results.violations).toEqual([]);
-        }),
 
     app: async ({ page }, use) => {
         const app = new ElementAppPage(page);
@@ -244,35 +103,23 @@ export const test = base.extend<TestFixtures>({
     },
 });
 
-// Based on https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/util.ts#L206C8-L210C2
-function sanitizeFilePathBeforeExtension(filePath: string): string {
-    const ext = extname(filePath);
-    const base = filePath.substring(0, filePath.length - ext.length);
-    return sanitizeForFilePath(base) + ext;
+interface ExtendedToMatchScreenshotOptions extends ToMatchScreenshotOptions {
+    includeDialogBackground?: boolean;
+    showTooltips?: boolean;
+    timeout?: number;
 }
 
-export const expect = baseExpect.extend({
-    async toMatchScreenshot(
+type Expectations = {
+    toMatchScreenshot: (
         this: ExpectMatcherState,
         receiver: Page | Locator,
         name: `${string}.png`,
-        options?: {
-            mask?: Array<Locator>;
-            includeDialogBackground?: boolean;
-            showTooltips?: boolean;
-            timeout?: number;
-            css?: string;
-        },
-    ) {
-        const testInfo = test.info();
-        if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`);
-
-        if (!testInfo.tags.includes("@screenshot")) {
-            throw new Error("toMatchScreenshot() must be used in a test tagged with @screenshot");
-        }
-
-        const page = "page" in receiver ? receiver.page() : receiver;
+        options?: ExtendedToMatchScreenshotOptions,
+    ) => Promise<MatcherReturnType>;
+};
 
+export const expect = baseExpect.extend<Expectations>({
+    async toMatchScreenshot(receiver, name, options) {
         let css = `
             .mx_MessagePanel_myReadMarker {
                 display: none !important;
@@ -322,21 +169,9 @@ export const expect = baseExpect.extend({
             css += options.css;
         }
 
-        // We add a custom style tag before taking screenshots
-        const style = (await page.addStyleTag({
-            content: css,
-        })) as ElementHandle<Element>;
-
-        const screenshotName = sanitizeFilePathBeforeExtension(name);
-        await baseExpect(receiver).toHaveScreenshot(screenshotName, options);
-
-        await style.evaluate((tag) => tag.remove());
-
-        testInfo.annotations.push({
-            // `_` prefix hides it from the HTML reporter
-            type: "_screenshot",
-            // include a path relative to `playwright/snapshots/`
-            description: testInfo.snapshotPath(screenshotName).split("/playwright/snapshots/", 2)[1],
+        await baseExpect(receiver).toMatchScreenshot(name, {
+            ...options,
+            css,
         });
 
         return { pass: true, message: () => "", name: "toMatchScreenshot" };
diff --git a/playwright/logger.ts b/playwright/logger.ts
deleted file mode 100644
index 857faaca69f109ac138ad246a861d81483655418..0000000000000000000000000000000000000000
--- a/playwright/logger.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { BrowserContext, Page, TestInfo } from "@playwright/test";
-import { Readable } from "stream";
-import stripAnsi from "strip-ansi";
-
-export class Logger {
-    private pages: Page[] = [];
-    private logs: Record<string, string> = {};
-
-    public getConsumer(container: string) {
-        this.logs[container] = "";
-        return (stream: Readable) => {
-            stream.on("data", (chunk) => {
-                this.logs[container] += chunk.toString();
-            });
-            stream.on("err", (chunk) => {
-                this.logs[container] += "ERR " + chunk.toString();
-            });
-        };
-    }
-
-    public async onTestStarted(context: BrowserContext) {
-        this.pages = [];
-        for (const id in this.logs) {
-            if (id.startsWith("page-")) {
-                delete this.logs[id];
-            } else {
-                this.logs[id] = "";
-            }
-        }
-
-        context.on("console", (msg) => {
-            const page = msg.page();
-            let pageIdx = this.pages.indexOf(page);
-            if (pageIdx === -1) {
-                this.pages.push(page);
-                pageIdx = this.pages.length - 1;
-                this.logs[`page-${pageIdx}`] = `Console logs for page with URL: ${page.url()}\n\n`;
-            }
-            const type = msg.type();
-            const text = msg.text();
-            this.logs[`page-${pageIdx}`] += `${type}: ${text}\n`;
-        });
-    }
-
-    public async onTestFinished(testInfo: TestInfo) {
-        if (testInfo.status !== "passed") {
-            for (const id in this.logs) {
-                if (!this.logs[id]) continue;
-                await testInfo.attach(id, {
-                    body: stripAnsi(this.logs[id]),
-                    contentType: "text/plain",
-                });
-            }
-        }
-    }
-}
diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts
index d530c75b54ecb92c766c06e27f59e53ee53b1aaa..afc814b3e10cb0880fd77ea878f2574c360b2134 100644
--- a/playwright/pages/ElementAppPage.ts
+++ b/playwright/pages/ElementAppPage.ts
@@ -114,7 +114,7 @@ export class ElementAppPage {
      * @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
      */
     public getComposerField(isRightPanel?: boolean): Locator {
-        return this.getComposer(isRightPanel).locator("[contenteditable]");
+        return this.getComposer(isRightPanel).locator("div[contenteditable]");
     }
 
     /**
@@ -202,4 +202,15 @@ export class ElementAppPage {
         }
         return this.page.locator(`id=${labelledById ?? describedById}`);
     }
+
+    /**
+     * Close the notification toast
+     */
+    public closeNotificationToast(): Promise<void> {
+        // Dismiss "Notification" toast
+        return this.page
+            .locator(".mx_Toast_toast", { hasText: "Notifications" })
+            .getByRole("button", { name: "Dismiss" })
+            .click();
+    }
 }
diff --git a/playwright/pages/bot.ts b/playwright/pages/bot.ts
index 435e4a1cbbb145c9def9997cefb52a7e63685c8f..05a8948a65e2dea8c3cebc359851d0b0204bb87e 100644
--- a/playwright/pages/bot.ts
+++ b/playwright/pages/bot.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JSHandle, Page } from "@playwright/test";
+import { type JSHandle, type Page } from "@playwright/test";
 import { uniqueId } from "lodash";
 import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
@@ -41,6 +41,10 @@ export interface CreateBotOpts {
      * Whether to bootstrap the secret storage
      */
     bootstrapSecretStorage?: boolean;
+    /**
+     * Whether to use a passphrase when creating the recovery key
+     */
+    usePassphrase?: boolean;
 }
 
 const defaultCreateBotOptions = {
@@ -48,6 +52,7 @@ const defaultCreateBotOptions = {
     autoAcceptInvites: true,
     startClient: true,
     bootstrapCrossSigning: true,
+    usePassphrase: false,
 } satisfies CreateBotOpts;
 
 type ExtendedMatrixClient = MatrixClient & { __playwright_recovery_key: GeneratedSecretStorageKey };
@@ -192,7 +197,6 @@ export class Bot extends Client {
 
         await clientHandle.evaluate(async (cli) => {
             await cli.initRustCrypto({ useIndexedDB: false });
-            cli.setGlobalErrorOnUnknownDevices(false);
             await cli.startClient();
         });
 
@@ -207,8 +211,8 @@ export class Bot extends Client {
         }
 
         if (this.opts.bootstrapSecretStorage) {
-            await clientHandle.evaluate(async (cli) => {
-                const passphrase = "new passphrase";
+            await clientHandle.evaluate(async (cli, usePassphrase) => {
+                const passphrase = usePassphrase ? "new passphrase" : undefined;
                 const recoveryKey = await cli.getCrypto().createRecoveryKeyFromPassphrase(passphrase);
                 Object.assign(cli, { __playwright_recovery_key: recoveryKey });
 
@@ -217,7 +221,7 @@ export class Bot extends Client {
                     setupNewKeyBackup: true,
                     createSecretStorageKey: () => Promise.resolve(recoveryKey),
                 });
-            });
+            }, this.opts.usePassphrase);
         }
 
         return clientHandle;
diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts
index 611a6cef19003363e367a860c6f7dacfd05d96b4..8296e9111e08537bca37f6063880853ce87ea56d 100644
--- a/playwright/pages/client.ts
+++ b/playwright/pages/client.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JSHandle, Page } from "@playwright/test";
-import { PageFunctionOn } from "playwright-core/types/structs";
+import { type JSHandle, type Page } from "@playwright/test";
+import { type PageFunctionOn } from "playwright-core/types/structs";
 
 import { Network } from "./network";
 import type {
@@ -25,9 +25,10 @@ import type {
     StateEvents,
     TimelineEvents,
     AccountDataEvents,
+    EmptyObject,
 } from "matrix-js-sdk/src/matrix";
 import type { RoomMessageEventContent } from "matrix-js-sdk/src/types";
-import { Credentials } from "../plugins/homeserver";
+import { type Credentials } from "../plugins/homeserver";
 
 export class Client {
     public network: Network;
@@ -363,7 +364,7 @@ export class Client {
         event: JSHandle<MatrixEvent>,
         receiptType?: ReceiptType,
         unthreaded?: boolean,
-    ): Promise<{}> {
+    ): Promise<EmptyObject> {
         const client = await this.prepareClient();
         return client.evaluate(
             (client, { event, receiptType, unthreaded }) => {
@@ -386,7 +387,7 @@ export class Client {
      * @return {Promise} Resolves: {} an empty object.
      * @return {module:http-api.MatrixError} Rejects: with an error response.
      */
-    public async setDisplayName(name: string): Promise<{}> {
+    public async setDisplayName(name: string): Promise<EmptyObject> {
         const client = await this.prepareClient();
         return client.evaluate(async (cli: MatrixClient, name) => cli.setDisplayName(name), name);
     }
@@ -397,7 +398,7 @@ export class Client {
      * @return {Promise} Resolves: {} an empty object.
      * @return {module:http-api.MatrixError} Rejects: with an error response.
      */
-    public async setAvatarUrl(url: string): Promise<{}> {
+    public async setAvatarUrl(url: string): Promise<EmptyObject> {
         const client = await this.prepareClient();
         return client.evaluate(async (cli: MatrixClient, url) => cli.setAvatarUrl(url), url);
     }
diff --git a/playwright/pages/crypto.ts b/playwright/pages/crypto.ts
index c31e7fbedb33c6a2085621b6ae316e30aae4db10..c882040987e08fb5de1d80bcfa44622bb325682d 100644
--- a/playwright/pages/crypto.ts
+++ b/playwright/pages/crypto.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { APIRequestContext, Page, expect } from "@playwright/test";
+import { type APIRequestContext, type Page, expect } from "@playwright/test";
 
-import { HomeserverInstance } from "../plugins/homeserver";
+import { type HomeserverInstance } from "../plugins/homeserver";
 
 export class Crypto {
     public constructor(
diff --git a/playwright/pages/settings.ts b/playwright/pages/settings.ts
index 7dbd183233c63aa803c999dc0aabf6438d153108..a08ca65f34544a93615734d7c2ccc6d7b32778d0 100644
--- a/playwright/pages/settings.ts
+++ b/playwright/pages/settings.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Locator, Page } from "@playwright/test";
+import { type Locator, type Page } from "@playwright/test";
 
 import type { SettingLevel } from "../../src/settings/SettingLevel";
 
diff --git a/playwright/pages/toasts.ts b/playwright/pages/toasts.ts
index 4fadfb9a9b3cb0c35484e6073cb15d1ade0b82d5..cfab354aaf6a992228da540fff623b6317227f3c 100644
--- a/playwright/pages/toasts.ts
+++ b/playwright/pages/toasts.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Page, expect, Locator } from "@playwright/test";
+import { type Page, expect, type Locator } from "@playwright/test";
 
 export class Toasts {
     public constructor(private readonly page: Page) {}
diff --git a/playwright/plugins/homeserver/dendrite/index.ts b/playwright/plugins/homeserver/dendrite/index.ts
index fb3537f41702f3664fa7b448747274acffb76458..c13965040599d15705fc7beeebb974fc44a73f27 100644
--- a/playwright/plugins/homeserver/dendrite/index.ts
+++ b/playwright/plugins/homeserver/dendrite/index.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Options } from "../../../services.ts";
+import { type WorkerOptions } from "../../../services.ts";
 
-export const isDendrite = ({ homeserverType }: Options): boolean => {
+export const isDendrite = ({ homeserverType }: WorkerOptions): boolean => {
     return homeserverType === "dendrite" || homeserverType === "pinecone";
 };
diff --git a/playwright/plugins/homeserver/index.ts b/playwright/plugins/homeserver/index.ts
index e5359ad45439cda36c397c23accfb763754596ce..0571cd961530932fc781cda81016c342a55ab71a 100644
--- a/playwright/plugins/homeserver/index.ts
+++ b/playwright/plugins/homeserver/index.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientServerApi } from "../utils/api.ts";
+import { type ClientServerApi } from "@element-hq/element-web-playwright-common/lib/utils/api.js";
 
 export interface HomeserverInstance {
     readonly baseUrl: string;
diff --git a/playwright/plugins/homeserver/synapse/consentHomeserver.ts b/playwright/plugins/homeserver/synapse/consentHomeserver.ts
index e714e8a9c11d0abebbd8c84eabb45999ccae3519..9b3316bf5789d8ea43631513132cd2f51c9ec8a0 100644
--- a/playwright/plugins/homeserver/synapse/consentHomeserver.ts
+++ b/playwright/plugins/homeserver/synapse/consentHomeserver.ts
@@ -6,30 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Fixtures } from "../../../element-web-test.ts";
+import { type SynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
+
+import { type Fixtures } from "../../../element-web-test.ts";
 
 export const consentHomeserver: Fixtures = {
     _homeserver: [
-        async ({ _homeserver: container, mailhog }, use) => {
-            container
+        async ({ _homeserver: container, mailpit }, use) => {
+            (container as SynapseContainer)
                 .withCopyDirectoriesToContainer([
                     { source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
                 ])
+                .withSmtpServer(mailpit)
                 .withConfig({
-                    email: {
-                        enable_notifs: false,
-                        smtp_host: "mailhog",
-                        smtp_port: 1025,
-                        smtp_user: "username",
-                        smtp_pass: "password",
-                        require_transport_security: false,
-                        notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
-                        app_name: "Matrix",
-                        notif_template_html: "notif_mail.html",
-                        notif_template_text: "notif_mail.txt",
-                        notif_for_new_users: true,
-                        client_base_url: "http://localhost/element",
-                    },
                     user_consent: {
                         template_dir: "/data/res/templates/privacy",
                         version: "1.0",
diff --git a/playwright/plugins/homeserver/synapse/emailHomeserver.ts b/playwright/plugins/homeserver/synapse/emailHomeserver.ts
index f7dee7b01a9424eb031aa05e2d281aa8cce35183..889d2a57d8e725c7f7fbee2f43eaee0a04579726 100644
--- a/playwright/plugins/homeserver/synapse/emailHomeserver.ts
+++ b/playwright/plugins/homeserver/synapse/emailHomeserver.ts
@@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Fixtures } from "../../../element-web-test.ts";
+import { type Fixtures } from "../../../element-web-test.ts";
 
 export const emailHomeserver: Fixtures = {
     _homeserver: [
-        async ({ _homeserver: container, mailhog }, use) => {
+        async ({ _homeserver: container, mailpit }, use) => {
             container.withConfig({
                 enable_registration_without_verification: undefined,
                 disable_msisdn_registration: undefined,
                 registrations_require_3pid: ["email"],
                 email: {
-                    smtp_host: "mailhog",
+                    smtp_host: "mailpit",
                     smtp_port: 1025,
                     notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
                     app_name: "my_branded_matrix_server",
diff --git a/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts b/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts
index 7414fdb015239c3a12fe2fdff216daa83dd87aa9..b6ef7243fd3ca779388fb71cff53e7ba7e80711f 100644
--- a/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts
+++ b/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import { TestContainers } from "testcontainers";
 
 import { OAuthServer } from "../../oauth_server";
-import { Fixtures } from "../../../element-web-test.ts";
+import { type Fixtures } from "../../../element-web-test.ts";
 
 export const legacyOAuthHomeserver: Fixtures = {
     oAuthServer: [
diff --git a/playwright/plugins/homeserver/synapse/masHomeserver.ts b/playwright/plugins/homeserver/synapse/masHomeserver.ts
index d52c446e9bddd164a525c4ac9d1b317534630232..342737d80d4d78b05a68347733c542ba9099862a 100644
--- a/playwright/plugins/homeserver/synapse/masHomeserver.ts
+++ b/playwright/plugins/homeserver/synapse/masHomeserver.ts
@@ -6,12 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
-import { Fixtures } from "../../../element-web-test.ts";
+import { MatrixAuthenticationServiceContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
+
+import { type Fixtures } from "../../../element-web-test.ts";
 
 export const masHomeserver: Fixtures = {
     mas: [
-        async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => {
+        async ({ _homeserver: homeserver, logger, network, postgres, mailpit }, use) => {
             const config = {
                 clients: [
                     {
diff --git a/playwright/plugins/homeserver/synapse/uiaLongSessionTimeoutHomeserver.ts b/playwright/plugins/homeserver/synapse/uiaLongSessionTimeoutHomeserver.ts
index 87d2a0f752d29ac412708cbb45b2c8791e630411..ee11a966fdc122ae2ad043841761790f0e543e41 100644
--- a/playwright/plugins/homeserver/synapse/uiaLongSessionTimeoutHomeserver.ts
+++ b/playwright/plugins/homeserver/synapse/uiaLongSessionTimeoutHomeserver.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Fixtures } from "../../../element-web-test.ts";
+import { type Fixtures } from "../../../element-web-test.ts";
 
 export const uiaLongSessionTimeoutHomeserver: Fixtures = {
     synapseConfig: [
diff --git a/playwright/plugins/oauth_server/index.ts b/playwright/plugins/oauth_server/index.ts
index df5ee0f461b208d614fbd942e3080351bd029ed7..446426a9c1409bdb151e612b9a96924ea8ba6b41 100644
--- a/playwright/plugins/oauth_server/index.ts
+++ b/playwright/plugins/oauth_server/index.ts
@@ -8,10 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import http from "http";
 import express from "express";
-import { AddressInfo } from "net";
-import { TestInfo } from "@playwright/test";
-
-import { randB64Bytes } from "../utils/rand.ts";
+import { type AddressInfo } from "net";
+import { type TestInfo } from "@playwright/test";
+import { randB64Bytes } from "@element-hq/element-web-playwright-common/lib/utils/rand.js";
 
 export class OAuthServer {
     private server?: http.Server;
diff --git a/playwright/plugins/utils/api.ts b/playwright/plugins/utils/api.ts
deleted file mode 100644
index 40dade1302717324115c3c7f92a114473ba114ae..0000000000000000000000000000000000000000
--- a/playwright/plugins/utils/api.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-Copyright 2025 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { APIRequestContext } from "@playwright/test";
-
-import { Credentials } from "../homeserver";
-
-export type Verb = "GET" | "POST" | "PUT" | "DELETE";
-
-export class Api {
-    private _request?: APIRequestContext;
-
-    constructor(private readonly baseUrl: string) {}
-
-    public setRequest(request: APIRequestContext): void {
-        this._request = request;
-    }
-
-    public async request<R extends {}>(verb: "GET", path: string, token?: string, data?: never): Promise<R>;
-    public async request<R extends {}>(verb: Verb, path: string, token?: string, data?: object): Promise<R>;
-    public async request<R extends {}>(verb: Verb, path: string, token?: string, data?: object): Promise<R> {
-        const url = `${this.baseUrl}${path}`;
-        const res = await this._request.fetch(url, {
-            data,
-            method: verb,
-            headers: token
-                ? {
-                      Authorization: `Bearer ${token}`,
-                  }
-                : undefined,
-        });
-
-        if (!res.ok()) {
-            throw new Error(
-                `Request to ${url} failed with status ${res.status()}: ${JSON.stringify(await res.json())}`,
-            );
-        }
-
-        return res.json();
-    }
-}
-
-export class ClientServerApi extends Api {
-    constructor(baseUrl: string) {
-        super(`${baseUrl}/_matrix/client`);
-    }
-
-    public async loginUser(userId: string, password: string): Promise<Credentials> {
-        const json = await this.request<{
-            access_token: string;
-            user_id: string;
-            device_id: string;
-            home_server: string;
-        }>("POST", "/v3/login", undefined, {
-            type: "m.login.password",
-            identifier: {
-                type: "m.id.user",
-                user: userId,
-            },
-            password: password,
-        });
-
-        return {
-            password,
-            accessToken: json.access_token,
-            userId: json.user_id,
-            deviceId: json.device_id,
-            homeServer: json.home_server || json.user_id.split(":").slice(1).join(":"),
-            username: userId.slice(1).split(":")[0],
-        };
-    }
-}
diff --git a/playwright/plugins/utils/object.ts b/playwright/plugins/utils/object.ts
deleted file mode 100644
index bfb92fecec211bf8de19522f66bc57d1aaa8e889..0000000000000000000000000000000000000000
--- a/playwright/plugins/utils/object.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
-Please see LICENSE files in the repository root for full details.
-*/
-
-/**
- * Deep copy the given object. The object MUST NOT have circular references and
- * MUST NOT have functions.
- * @param obj - The object to deep copy.
- * @returns A copy of the object without any references to the original.
- */
-export function deepCopy<T>(obj: T): T {
-    return JSON.parse(JSON.stringify(obj));
-}
diff --git a/playwright/plugins/utils/port.ts b/playwright/plugins/utils/port.ts
deleted file mode 100644
index b54e251f2f20f90bba0dd1e771d582384a98b7c6..0000000000000000000000000000000000000000
--- a/playwright/plugins/utils/port.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import * as net from "net";
-
-export async function getFreePort(): Promise<number> {
-    return new Promise<number>((resolve) => {
-        const srv = net.createServer();
-        srv.listen(0, () => {
-            const port = (<net.AddressInfo>srv.address()).port;
-            srv.close(() => resolve(port));
-        });
-    });
-}
diff --git a/playwright/plugins/utils/rand.ts b/playwright/plugins/utils/rand.ts
deleted file mode 100644
index 94f723f0a6c6a4ff6a08494fb8f2fb9cf2bd033c..0000000000000000000000000000000000000000
--- a/playwright/plugins/utils/rand.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import crypto from "node:crypto";
-
-export function randB64Bytes(numBytes: number): string {
-    return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
-}
diff --git a/playwright/plugins/webserver/index.ts b/playwright/plugins/webserver/index.ts
index 7645e9cff3d062f5f485607e73fe98fe324bb82e..fe236116bc6d7f24604e13d877b653285f2c9526 100644
--- a/playwright/plugins/webserver/index.ts
+++ b/playwright/plugins/webserver/index.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as http from "http";
-import { AddressInfo } from "net";
+import * as http from "node:http";
+import { type AddressInfo } from "node:net";
 
 export class Webserver {
     private server?: http.Server;
diff --git a/playwright/sample-files/5secvid.webm b/playwright/sample-files/5secvid.webm
new file mode 100644
index 0000000000000000000000000000000000000000..ec5a695fa4e7f506551aae306c67a8f16351f128
Binary files /dev/null and b/playwright/sample-files/5secvid.webm differ
diff --git a/playwright/sample-files/example-module.js b/playwright/sample-files/example-module.js
new file mode 100644
index 0000000000000000000000000000000000000000..561dea5fd32e68ba206d17846e4d23b140e941df
--- /dev/null
+++ b/playwright/sample-files/example-module.js
@@ -0,0 +1,24 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+export default class ExampleModule {
+    static moduleApiVersion = "^1.0.0";
+    constructor(api) {
+        this.api = api;
+
+        this.api.i18n.register({
+            key: {
+                en: "%(brand)s module loading successful!",
+                de: "%(brand)s-Module erfolgreich geladen!",
+            },
+        });
+    }
+    async load() {
+        const brand = this.api.config.get("brand");
+        alert(this.api.i18n.translate("key", { brand }));
+    }
+}
diff --git a/playwright/services.ts b/playwright/services.ts
index a501bf61384f40a1adb416b2329c1581d3b66a09..8ecf10e20e123b745795614886023ade29d163c6 100644
--- a/playwright/services.ts
+++ b/playwright/services.ts
@@ -5,113 +5,32 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { test as base } from "@playwright/test";
-import mailhog from "mailhog";
-import { Network, StartedNetwork } from "testcontainers";
-import { PostgreSqlContainer, StartedPostgreSqlContainer } from "@testcontainers/postgresql";
-
-import { SynapseConfig, SynapseContainer } from "./testcontainers/synapse.ts";
-import { Logger } from "./logger.ts";
-import { StartedMatrixAuthenticationServiceContainer } from "./testcontainers/mas.ts";
-import { HomeserverContainer, StartedHomeserverContainer } from "./testcontainers/HomeserverContainer.ts";
-import { MailhogContainer, StartedMailhogContainer } from "./testcontainers/mailhog.ts";
-import { OAuthServer } from "./plugins/oauth_server";
-import { DendriteContainer, PineconeContainer } from "./testcontainers/dendrite.ts";
-import { HomeserverType } from "./plugins/homeserver";
-
-export interface TestFixtures {
-    mailhogClient: mailhog.API;
-}
-
-export interface Services {
-    logger: Logger;
-
-    network: StartedNetwork;
-    postgres: StartedPostgreSqlContainer;
-    mailhog: StartedMailhogContainer;
-
-    synapseConfig: SynapseConfig;
-    _homeserver: HomeserverContainer<any>;
-    homeserver: StartedHomeserverContainer;
-    // Set in masHomeserver only
-    mas?: StartedMatrixAuthenticationServiceContainer;
+import { test as base } from "@element-hq/element-web-playwright-common";
+import {
+    type Services as BaseServices,
+    type WorkerOptions as BaseWorkerOptions,
+} from "@element-hq/element-web-playwright-common/lib/fixtures";
+import { type HomeserverContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
+
+import { type OAuthServer } from "./plugins/oauth_server";
+import { DendriteContainer, PineconeContainer } from "./testcontainers/dendrite";
+import { type HomeserverType } from "./plugins/homeserver";
+import { SynapseContainer } from "./testcontainers/synapse";
+
+export interface Services extends BaseServices {
     // Set in legacyOAuthHomeserver only
     oAuthServer?: OAuthServer;
 }
 
-export interface Options {
+export interface WorkerOptions extends BaseWorkerOptions {
     homeserverType: HomeserverType;
 }
 
-export const test = base.extend<TestFixtures, Services & Options>({
-    logger: [
-        // eslint-disable-next-line no-empty-pattern
-        async ({}, use) => {
-            const logger = new Logger();
-            await use(logger);
-        },
-        { scope: "worker" },
-    ],
-    network: [
-        // eslint-disable-next-line no-empty-pattern
-        async ({}, use) => {
-            const network = await new Network().start();
-            await use(network);
-            await network.stop();
-        },
-        { scope: "worker" },
-    ],
-    postgres: [
-        async ({ logger, network }, use) => {
-            const container = await new PostgreSqlContainer()
-                .withNetwork(network)
-                .withNetworkAliases("postgres")
-                .withLogConsumer(logger.getConsumer("postgres"))
-                .withTmpFs({
-                    "/dev/shm/pgdata/data": "",
-                })
-                .withEnvironment({
-                    PG_DATA: "/dev/shm/pgdata/data",
-                })
-                .withCommand([
-                    "-c",
-                    "shared_buffers=128MB",
-                    "-c",
-                    `fsync=off`,
-                    "-c",
-                    `synchronous_commit=off`,
-                    "-c",
-                    "full_page_writes=off",
-                ])
-                .start();
-            await use(container);
-            await container.stop();
-        },
-        { scope: "worker" },
-    ],
-
-    mailhog: [
-        async ({ logger, network }, use) => {
-            const container = await new MailhogContainer()
-                .withNetwork(network)
-                .withNetworkAliases("mailhog")
-                .withLogConsumer(logger.getConsumer("mailhog"))
-                .start();
-            await use(container);
-            await container.stop();
-        },
-        { scope: "worker" },
-    ],
-    mailhogClient: async ({ mailhog: container }, use) => {
-        await container.client.deleteAll();
-        await use(container.client);
-    },
-
-    synapseConfig: [{}, { scope: "worker" }],
+export const test = base.extend<{}, Services & WorkerOptions>({
     homeserverType: ["synapse", { option: true, scope: "worker" }],
     _homeserver: [
         async ({ homeserverType }, use) => {
-            let container: HomeserverContainer<any>;
+            let container: HomeserverContainer<unknown>;
             switch (homeserverType) {
                 case "synapse":
                     container = new SynapseContainer();
@@ -128,46 +47,12 @@ export const test = base.extend<TestFixtures, Services & Options>({
         },
         { scope: "worker" },
     ],
-    homeserver: [
-        async ({ homeserverType, logger, network, _homeserver: homeserver, synapseConfig, mas }, use) => {
-            if (homeserver instanceof SynapseContainer) {
-                homeserver.withConfig(synapseConfig);
-            }
-            const container = await homeserver
-                .withNetwork(network)
-                .withNetworkAliases("homeserver")
-                .withLogConsumer(logger.getConsumer(homeserverType))
-                .withMatrixAuthenticationService(mas)
-                .start();
-
-            await use(container);
-            await container.stop();
-        },
-        { scope: "worker" },
-    ],
-    mas: [
-        // eslint-disable-next-line no-empty-pattern
-        async ({}, use) => {
-            // we stub the mas fixture to allow `homeserver` to depend on it to ensure
-            // when it is specified by `masHomeserver` it is started before the homeserver
-            await use(undefined);
-        },
-        { scope: "worker" },
-    ],
 
-    context: async (
-        { homeserverType, synapseConfig, logger, context, request, _homeserver, homeserver },
-        use,
-        testInfo,
-    ) => {
+    context: async ({ homeserverType, synapseConfig, context, _homeserver }, use, testInfo) => {
         testInfo.skip(
             !(_homeserver instanceof SynapseContainer) && Object.keys(synapseConfig).length > 0,
             `Test specifies Synapse config options so is unsupported with ${homeserverType}`,
         );
-        homeserver.setRequest(request);
-        await logger.onTestStarted(context);
         await use(context);
-        await logger.onTestFinished(testInfo);
-        await homeserver.onTestFinished(testInfo);
     },
 });
diff --git a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png
index 6524a45a6723324b8873302007022c3bef2be69f..b0bb64273eb61530c58d447ada1c26c60fd62a2e 100644
Binary files a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png and b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-CompatibilityView-linux.png differ
diff --git a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png
index 9a7760bfd015ce7d0c63f5d36365ff518a6a71bd..6a72dec4e2780fc3c77d39ddd4b7d319c4a912b4 100644
Binary files a/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png and b/playwright/snapshots/app-loading/feature-detection.spec.ts/unsupported-browser-linux.png differ
diff --git a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png
index ffe1b0be50e2575ea314993734bb0131685e28cc..bc9d6c88c3347e5c49735c04f82371401da222fe 100644
Binary files a/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png and b/playwright/snapshots/audio-player/audio-player.spec.ts/Selected-EventTile-of-audio-player-with-a-reply-chain-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png
index 7a108b441c8838fce84eb05e78672850f0056e37..26c506097ae5f26bd7548dfa2cb96e36680877dd 100644
Binary files a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png and b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png differ
diff --git a/playwright/snapshots/crypto/toasts.spec.ts/key-storage-out-of-sync-toast-linux.png b/playwright/snapshots/crypto/toasts.spec.ts/key-storage-out-of-sync-toast-linux.png
index 8e335bd2323b56429f8b03fdbcefd5ed77e9d6f2..8cf3cb7d698428342e8a144b9548b4b817e099f4 100644
Binary files a/playwright/snapshots/crypto/toasts.spec.ts/key-storage-out-of-sync-toast-linux.png and b/playwright/snapshots/crypto/toasts.spec.ts/key-storage-out-of-sync-toast-linux.png differ
diff --git a/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png b/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png
index e3c37e79c9bbf9a6781162dabab5a905b426e1e0..be47bf9e4426f2c07f73306cff654e66803b1aa5 100644
Binary files a/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png and b/playwright/snapshots/file-upload/image-upload.spec.ts/image-upload-preview-linux.png differ
diff --git a/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png b/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png
index 377e1931be87b8d786aa708b2b5355f4d024adf7..8bc25d6f2f3fbb8cad7259d7e2e7fdac3ee0bf94 100644
Binary files a/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png and b/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-linux.png differ
diff --git a/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-verify-email-linux.png b/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-verify-email-linux.png
index ec7b9a174d5c65a3eb0e8d048f2dde66335c0618..5537ae9991f8c8e83701a9c5533f14633a2d482d 100644
Binary files a/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-verify-email-linux.png and b/playwright/snapshots/forgot-password/forgot-password.spec.ts/forgot-password-verify-email-linux.png differ
diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png
index c66606efcdab3321deff618bcb3a5bc6b30f217a..1a66050e5fe2a995a252c98ed95084adbfbedfa5 100644
Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png differ
diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png
index 02cbff0e3fb5c61fc12e860ab24abb51ff6f7826..86cb11aad9a9527642c3dfe1ec18c6d77a897b29 100644
Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png differ
diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png
index fa3cf904303a22a2a01a225cd54628d037e4aab9..043e35b6be9a9a61af4b40fbd9665fcfcc2108df 100644
Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c71c11b1d41832fad170f51d4663ce4943adc99
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7396da41d9b653dabe8c46f088985e8ba1f1088
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..15620d361269e2d4cc192737e09da573b100a1fb
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b73e0b819f00ade315b3e3fe7fb1771950668b7
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb3add5733661d3673b984e88caba9c44d2275f7
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..70ed4bb782d0f5d37e2f001186a49804ae602d34
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..6781c1d3641440d541c651f2e22825ef5794ad62
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe056af3d355b3f13f5f415de413442fed361720
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/filter-menu-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..34924cf69f95cd0a9ad44f1003dbfab2757e2b48
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f12ee4e41563b54423a9f3783bfa5ee223322d3
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..f0cda0b5771d506d289ae84b3fd5bcac6fe64757
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..7d63f923b0f85ac001cc25c1ed250d244bdadf5e
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f501a58d46ae52754de45414ace23f5b1e9d3e0
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-compose-menu-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..46ff1a53be33b06d7b86a27ece4e84ea484a456d
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..c706e71b0fae1d008fe0cd0f405b0ad802528b2e
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a4aea566ea458c5206b85292c5c23a846f468b8
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..9b130b73c410dab3d5fb2fb344162e1db0b350ef
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..92b81245a2d4cd22ef8caefcede7f5d927119c97
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-search.spec.ts/search-section-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-search.spec.ts/search-section-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..123cf3758623f9e70cbbb90dd4e66959b4ffa002
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list-search.spec.ts/search-section-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa73d79988bb19ee375f2f18861c1aa7028c8fe6
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..fba408c922e505bc666aa94f19010f486707e7d1
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..36b7304a010eaec4031c30431448628387f7d90f
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f2b691b4ac3f243d34205d78b034f4de2156361
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..310912e50da212422f864b4593234c6029856284
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..9fa531f5b1548493313039c8afcf471bdecd62a9
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..dac349eb2dbfe1c7a37291f23466a1d5b9e5947e
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..144604ffebfa0538133418329d0d50ae528c3287
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..99bb312695a69a9c3db072c5a397021d0d7a8f6d
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..c91ebf3b3a060e9bbed20b73b2b3c1f1a93954c4
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b3ac052caedac055a60933f52f53061d421a778
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..e951f77ef2fea1eb2516ae85c939560f7fe360c2
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..57c5cb1eb7a33c9349200520f57f3a50c71d0614
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..16ea458274446c0569cce33ce421800139371a2e
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..f2b625f4983c6509789eef1ace997f76aeb37333
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..a1752e30d31d84e9fdfb0cee89bc353c48560c52
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/basic-message-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/basic-message-ltr-rtldisplayname-linux.png
index 4bf016b82559dd645f5c64995dc985a8102bdb3e..35523b7db8a21804377803815f96ee2af7079129 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/basic-message-ltr-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/basic-message-ltr-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/basic-message-rtl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/basic-message-rtl-rtldisplayname-linux.png
index ee699905239248dea280c5606e96752b1471d95b..40a096409ecea7ebe832866f0a46899391a3646f 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/basic-message-rtl-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/basic-message-rtl-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/edited-message-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/edited-message-ltr-rtldisplayname-linux.png
index eea2b474693e5f09c56e60b09007d7a15ce4c5cb..557613e7e6b524e15c63b187ba26d517e272fd7f 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/edited-message-ltr-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/edited-message-ltr-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/edited-message-rtl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/edited-message-rtl-rtldisplayname-linux.png
index 4d2e9c593dcae2ba7d06e2d04a70b3dc0b358fed..bd26e84628d0f8b4d4510ef3cf2c5771359d19fa 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/edited-message-rtl-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/edited-message-rtl-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/emote-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/emote-ltr-rtldisplayname-linux.png
index 1bb3582578202918b11884de66f2ca821772c37e..055ad23a81ba133dbc12bab1d05e083a7ef4d517 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/emote-ltr-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/emote-ltr-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/emote-rich-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/emote-rich-ltr-rtldisplayname-linux.png
index a4b7d0a99228e43d3451c21763b81640450292d2..cfb905b68969467a2521c3af289539fe89e122fa 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/emote-rich-ltr-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/emote-rich-ltr-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/emote-rich-rtl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/emote-rich-rtl-rtldisplayname-linux.png
index 26bd9f7523c92f555a24b5c6cbe35cb8b6e68b5b..ea29e98a757592866ee31a3f339f81075cf4a4bb 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/emote-rich-rtl-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/emote-rich-rtl-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/emote-rtl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/emote-rtl-rtldisplayname-linux.png
index 3f50a1406f12cab397cf7dd99c902de43c82d059..d84780f53018aa7fa0fb909440a56a1aa4135e2d 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/emote-rtl-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/emote-rtl-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png
index 37096f025c1856c1ab82843175c79cb3fea800ed..b59d960f4fd6152139dc3d9d0ff281fa4bfb82c7 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png
index 52ced56c83ed135922e518d7399c8eb551b46fa3..f6eaea241a9786efbd035def20819f3033ce4565 100644
Binary files a/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png and b/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png differ
diff --git a/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png b/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..e85b83f2f140038e8c9bd98eac564c3edfac3667
Binary files /dev/null and b/playwright/snapshots/oidc/oidc-native.spec.ts/token-expired-linux.png differ
diff --git a/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png b/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png
index 62ffcead9988b436700e725e3702a55817497e0f..b386eaa564e1c5356677fb4345a1e886255be87a 100644
Binary files a/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png and b/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png differ
diff --git a/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png b/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png
index 6818add73e4f2a470a3a08f42719d5d0a45e90e5..7b7f296be11027d9ee5874e0e329e70781c93302 100644
Binary files a/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png and b/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png differ
diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png
index a842b686dda2812c029b48f9b3cbec01debfe6b0..0a4abba83398bf8c4967de96fc88b802e5ddca27 100644
Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png
index d9d12951df0c88d49560fb2fe80cfeb37979f72d..f00047fe847e2f43e000f9c5ba10f5174fe52943 100644
Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png differ
diff --git a/playwright/snapshots/register/email.spec.ts/registration-check-your-email-linux.png b/playwright/snapshots/register/email.spec.ts/registration-check-your-email-linux.png
index 4b724d77725ab60f2ac8d9f69636e26a8fd662aa..fc52c2cb65eb3f97689ef33c83293ba78146d4ee 100644
Binary files a/playwright/snapshots/register/email.spec.ts/registration-check-your-email-linux.png and b/playwright/snapshots/register/email.spec.ts/registration-check-your-email-linux.png differ
diff --git a/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png b/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png
index f78a2a0b1655d75f80cff279d246bfe3f5913698..9bc2ed101b4be3d19b6fd5a018ab1bbd12583e14 100644
Binary files a/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png and b/playwright/snapshots/register/register.spec.ts/email-prompt-linux.png differ
diff --git a/playwright/snapshots/register/register.spec.ts/server-picker-linux.png b/playwright/snapshots/register/register.spec.ts/server-picker-linux.png
index da924bae82c0c8b93aad4d887836ea3046e5b424..db3ec188d1fa9fb45d3e33a69be8a442d5b6de4a 100644
Binary files a/playwright/snapshots/register/register.spec.ts/server-picker-linux.png and b/playwright/snapshots/register/register.spec.ts/server-picker-linux.png differ
diff --git a/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png b/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..f466c17d6453f89cb5f809c9c20fae5e319f479d
Binary files /dev/null and b/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-All-new-pinned-messages-linux.png differ
diff --git a/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-Threads-Activity-Centre-linux.png b/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-Threads-Activity-Centre-linux.png
deleted file mode 100644
index fee99165ab9c3984dcd704f59171bf8ee4485b87..0000000000000000000000000000000000000000
Binary files a/playwright/snapshots/release-announcement/releaseAnnouncement.spec.ts/release-announcement-Threads-Activity-Centre-linux.png and /dev/null differ
diff --git a/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png
index 0566a21175ab0dbaf75a47aa41731e428a35d21a..2dbd40ffd91851bcdb914bb9bf74e5d6716a6751 100644
Binary files a/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png and b/playwright/snapshots/right-panel/file-panel.spec.ts/empty-linux.png differ
diff --git a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png
index f0e14ee55cf0c7489e8bb91dfeb41b2880f323dd..a5db88aae638325eee39eefd8b087d0b27d1e5eb 100644
Binary files a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png and b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png differ
diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..627071591c8567bf72876d3512a2f2d18f46d235
Binary files /dev/null and b/playwright/snapshots/right-panel/right-panel.spec.ts/room-report-dialog-linux.png differ
diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-extensions-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-extensions-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..8333d55b46480c4b4da89a00456e75f5b1da8fcf
Binary files /dev/null and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-extensions-linux.png differ
diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..febb73c23530515dfb14c57424bd2f00db471752
Binary files /dev/null and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png differ
diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-long-name-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-long-name-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..9a3479a1e610e20a4187496ac8fd9af0662c372e
Binary files /dev/null and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-long-name-linux.png differ
diff --git a/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png b/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..22ef895d1ab22c41f4e7487265032202eb4a84c9
Binary files /dev/null and b/playwright/snapshots/room/invites.spec.ts/Invites-reject-dialog-linux.png differ
diff --git a/playwright/snapshots/room/invites.spec.ts/Invites-room-view-linux.png b/playwright/snapshots/room/invites.spec.ts/Invites-room-view-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..86d0e37b9383d38bdc6cd6bf6e747fd7cdd90f05
Binary files /dev/null and b/playwright/snapshots/room/invites.spec.ts/Invites-room-view-linux.png differ
diff --git a/playwright/snapshots/room/room-header.spec.ts/room-header-with-icon-linux.png b/playwright/snapshots/room/room-header.spec.ts/room-header-with-icon-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..471c26ccdbbd7644eba226495df82e4fa7e7daa3
Binary files /dev/null and b/playwright/snapshots/room/room-header.spec.ts/room-header-with-icon-linux.png differ
diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png
index 12fd5c79d32a0281f2ab74cd5b5d1bc7223ddc42..3b4031063c96c6f763a1d2eb2659e601f61f0f0f 100644
Binary files a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png differ
diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png
index ac6f86e81c44d315510b9b00f55965ac6f821718..e0e46682a3e01276367c104ad23bd9941f9c422d 100644
Binary files a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png
index a8b3ae3ea7784cdda5184e54b4d7b5064c385123..20518942b030131016dd6b01ab54f746e6161a3c 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png
index 5307b7400a30cdfa4a7b35bac37e2a66dbc3a6ff..a847075a4d4588163880ac1e6c7ad114f1d38679 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-bubble-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-bubble-linux.png
index eaa68eae4abae8c71eca5e43dc9e96776559f158..a913d4ab19b8c3ce1f7032fe829bf592019a1292 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-bubble-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-bubble-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-modern-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-modern-linux.png
index 036ff61851e17f1216e3f6bfd03a3d3d3e45bed6..b2167e0a12669b2339eb31a434b757d306c58479 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-modern-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/message-layout-panel.spec.ts/message-layout-panel-modern-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-added-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-added-linux.png
index 147fcfa057d4ce9d81d1d2dae3a968163c552347..60678f3bb43e500c22125e7cfcb9494f35389515 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-added-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-added-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-linux.png
index 5475f9a5379e5ff44849ccfdd9797e23f8f93cb7..cc2dc4a0a739f8a5a57448057268eb72527adb15 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png
index 23b88c022c86abef6e6066cb04d5670690862e5a..068aa2901f02d20e03ef12e11beebf3d87f76020 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-dark-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-dark-linux.png
index 6378098d7ac99bc2cec0392e7fa1aa096a1faec4..1acda5968ea804f2be6ee5a43321b136ae8e0ad5 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-dark-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-dark-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-light-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-light-linux.png
index f2269a0532afa85a6d0489cacdf70f013e07bafe..3b221183fb49df4ec4e5715556997c7eb8125955 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-light-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-light-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-match-system-enabled-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-match-system-enabled-linux.png
index 6b41f30acd52bb7eebb486f19549e83a38583f03..73f7f02d6534a8de5eceb9e30f4087ecb0d17e5c 100644
Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-match-system-enabled-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-match-system-enabled-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/encryption-details-linux.png b/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/encryption-details-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..e18821b7743825c97218764ec2f39c0db90c5f1f
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/encryption-details-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/reset-cryptographic-identity-linux.png b/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/reset-cryptographic-identity-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e0b105c2463898ecb6236e0a2437d1599c37dc9
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/advanced.spec.ts/reset-cryptographic-identity-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..c999365534f10210fc9281e8e180b66fc7228f13
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/default-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/delete-key-storage-confirm-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/delete-key-storage-confirm-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..237c853a1be434b4e42598ad9917418af7dec8f3
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/delete-key-storage-confirm-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ad192f2c28db2d0795507c5d3ff4cc176ac8650
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/out-of-sync-recovery-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..0bb5fad763f1d7c0b2ce56db862d831a67963a52
Binary files /dev/null and b/playwright/snapshots/settings/encryption-user-tab/encryption-tab.spec.ts/verify-device-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png
index b2083a5dd479ea17d6d8e61c3f2a7771d3fbbd9c..a42cb394d7afad2778cac9e3daaa11429f9f7ca9 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-1-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png
index 02945494593aa14d13852d5b9dda55fdcb1bfa34..ec0207a227d9a0374ce840399c08ffc1e284dab0 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/change-key-2-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png
index 971745c4128843a615132ff18e51eebdbbdaeb17..1f34a3f2e092ee115a3f5ed6265662def234a3e8 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/default-recovery-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/out-of-sync-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/out-of-sync-recovery-linux.png
deleted file mode 100644
index e6664a5f79b1e1a536f38cba312a836273a9f8ed..0000000000000000000000000000000000000000
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/out-of-sync-recovery-linux.png and /dev/null differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png
index 1a413094ae178ad147b7f68cb4270f4e9b011957..3835cfe727437c8ba5e5c8bc717e728c8f08b1c1 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-1-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png
index 099c0c549e0d1f499abdaa9eb2b645a4d249d90a..6c3e9eab2c4943ffabc8d0d374f151cd9cbadb81 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-2-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png
index 6cc32cc4311596a8d37c1353bf6fea12ddf2f01a..cb0bc78e00c96d2237caade1df64c2900d383674 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-key-3-encryption-tab-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png
index 78dcd14aeab2f8fdf5bba4f4f12bd731b0f94435..e26d001a9025118f68440d1d1508966b4891c106 100644
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png and b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/set-up-recovery-linux.png differ
diff --git a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/verify-device-encryption-tab-linux.png b/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/verify-device-encryption-tab-linux.png
deleted file mode 100644
index 643fe46a1dffc6aa090cbb6f81902a04ff68c042..0000000000000000000000000000000000000000
Binary files a/playwright/snapshots/settings/encryption-user-tab/recovery.spec.ts/verify-device-encryption-tab-linux.png and /dev/null differ
diff --git a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png
index c08a36a808c5c0a9e1aa3d4b6cac12b9564bcaa8..c326c3f8e4216aece1d7e7efc439bcbc88b960e7 100644
Binary files a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png
index 62a8c5b8d171dab3bbe6bfb3561ab38411c77fc5..5f92c00fc3ddcfa6912440b21a556279265adb2e 100644
Binary files a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png b/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6605b5b64e8d2d2a174fd191061751d7387aeb7
Binary files /dev/null and b/playwright/snapshots/settings/quick-settings-menu.spec.ts/quick-settings-linux.png differ
diff --git a/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png
index 720cc415480f65881ed45b80c8468f9c94f6b74a..9cd18bee3ed8c4da028b517f3fb2989a48ce49b5 100644
Binary files a/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/spaces/spaces.spec.ts/add-existing-rooms-dialog-linux.png b/playwright/snapshots/spaces/spaces.spec.ts/add-existing-rooms-dialog-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..53ccabf4f2f2ced57260aac147c6d4bff613a9b4
Binary files /dev/null and b/playwright/snapshots/spaces/spaces.spec.ts/add-existing-rooms-dialog-linux.png differ
diff --git a/playwright/snapshots/spaces/spaces.spec.ts/invite-teammates-dialog-linux.png b/playwright/snapshots/spaces/spaces.spec.ts/invite-teammates-dialog-linux.png
index 2244dc7cf91778a96485aa94f8af3eb3a5655656..b69496e5fee832c998a85ff124a6f61e4dccd849 100644
Binary files a/playwright/snapshots/spaces/spaces.spec.ts/invite-teammates-dialog-linux.png and b/playwright/snapshots/spaces/spaces.spec.ts/invite-teammates-dialog-linux.png differ
diff --git a/playwright/snapshots/spaces/spaces.spec.ts/space-room-view-linux.png b/playwright/snapshots/spaces/spaces.spec.ts/space-room-view-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e627ec9f241a68edb718207dfd1005bf7b4a75c
Binary files /dev/null and b/playwright/snapshots/spaces/spaces.spec.ts/space-room-view-linux.png differ
diff --git a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png
index 301201c1f6ae9fd7cdc7c5ca82f0d07bc934d904..f217fe709412b8f6fdba53ea33cb6b86c3921828 100644
Binary files a/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png differ
diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png
index 67414d1e7bc9c0aacb571cd2c4a1e447d6d96a4d..30fa37ab9ed9085d36bba168c866793c052cb7b4 100644
Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png differ
diff --git a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png
index 4b779aa788883c3bce87c6f435e4e7e888b8cc6c..4bad759050dddac80a0b865ddab0070994d3dc9a 100644
Binary files a/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png and b/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-no-avatar-linux.png b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-no-avatar-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..377282a2c8581282c87c0037519cd1bfd94164b3
Binary files /dev/null and b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-no-avatar-linux.png differ
diff --git a/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-no-avatar-linux.png b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-no-avatar-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..29d129f73f318e4de71d402a7003e36635dd6a5e
Binary files /dev/null and b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-no-avatar-linux.png differ
diff --git a/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-with-avatar-linux.png b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-with-avatar-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..452f08d3e2803966e7d61d20ea4263413c3759aa
Binary files /dev/null and b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-room-tree-with-avatar-linux.png differ
diff --git a/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-with-avatar-linux.png b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-with-avatar-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..93459507d01462427b4e919aa8696d0dd9b4b792
Binary files /dev/null and b/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-with-avatar-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/code-block-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/code-block-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..2f62d0dec610527cb4cae18c4a7fc5376195527f
Binary files /dev/null and b/playwright/snapshots/timeline/timeline.spec.ts/code-block-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png
index 00b271004eb96155d45f37a6c97b24a06cf35b17..f8135426c2b409f0ef1d85c1ce132b2a03f7036f 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-and-messages-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png
index 8f11c831dbdf1b84ab312c778c8b31d1871fdfb7..4d75b3b96686b8c1da95f8cc65bb3204ac83f45f 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/collapsed-gels-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png
index 63655439479f564ffc6aeb6ebcdccfe5fd3a0a9f..a6e31b89cd266120105b789819ec8c3204a6e18d 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/configured-room-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/edited-code-block-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/edited-code-block-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..46f5cf1a7ae4c9b4e2b15b189f5697fe33e9cb0e
Binary files /dev/null and b/playwright/snapshots/timeline/timeline.spec.ts/edited-code-block-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png
index d8a5ae40569f10465624e02f942346608ea2b234..8d3d7b09ed1704cd7624937fc7b130b1dee031f5 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png
index 58c844a54d332e6faebfdc613e44e9888c410405..785681b28c0d1bdf7de5e7dc66d5960979c7139e 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png
index d8e6da9f8fead1de813bb88eaea0aa4f7feec82d..e5e312ac7363bd7750a4779190bf17e171f13965 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-compact-modern-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png
index e1a4e6ef06868ee3a20810df0796ef5496b98202..f79934e621c0b1dff6ba5e60715c556e4e6896fa 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png
index 032a8c11188bbe53fc796829e0060cffd0c1ecdd..ff4fa7c1b90401bd9910628a3126a3c83629054c 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-tiles-modern-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png
index b31eae03f6499ea4afee5d9dfbac76b9bfd15fdd..f6840e8daf2216f4e8e00053092a6d9d59ad4c3c 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png
index 1c7265ca62dae2edd656a8c5b98769d8ea40e671..6154a0a268ef3f3fc011f8bf945abcc1480b814d 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png
index 33ef04df3cf83f61e8399ca883941b5ab394b006..06853769d70e61efcc4816d9d660d5e28bcf45c8 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png
index d8a5ae40569f10465624e02f942346608ea2b234..8d3d7b09ed1704cd7624937fc7b130b1dee031f5 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png
index 608b17051dd0da7340be4285b0bbb172a195afba..f1a95a8275c3c84c7a80198bc9e5a3c871c571ef 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png
index 06aa02cdf8c81d853a534f863e4c1f4f7230e637..9d9dacd1bf428d0825076b4a956206603a28ddbd 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png
index efcbba43f95f8e97e6509a8103c1d49c75a10827..e0523d6eec14641cceacf39d0f6ee309e8aafcb9 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-padding-modern-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png
index 00c697f9a61920cb5a3ad17a9e64bc34c2f0aaf5..5c6d7710f69492524db5fa7101b70cc23bfed391 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/hidden-event-line-zero-padding-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/image-in-timeline-default-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/image-in-timeline-default-layout-linux.png
index b8a24fb3a475836f73caeab5b0a701a4633c3d61..b1236c9ea0db14a3d1be8a13b4705d91b6ef881b 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/image-in-timeline-default-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/image-in-timeline-default-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png
index 879e647f5e3488ceccc0ed13531045b20a316821..5d44a1f655a7f46713f6b4bc13f07f62e8e740f2 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-bubble-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png
index b52a56a22e3563c7187c3e29e5bbd1d67370b484..baa75ffaba0cba08508858bf8022111cbc64e0ec 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-irc-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png
index bbaba1dbea7eb98b06c74bb18406f23f9fe1503e..4b54392a217afad31503370bed0233e38c211c70 100644
Binary files a/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/long-strings-with-reply-modern-layout-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/spoiler-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/spoiler-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..529d254dc4b010debb78008de4b4d299e906fbbe
Binary files /dev/null and b/playwright/snapshots/timeline/timeline.spec.ts/spoiler-linux.png differ
diff --git a/playwright/snapshots/timeline/timeline.spec.ts/spoiler-uncovered-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/spoiler-uncovered-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..41d5bc8beecca504ea01941f659c2412998af6c0
Binary files /dev/null and b/playwright/snapshots/timeline/timeline.spec.ts/spoiler-uncovered-linux.png differ
diff --git a/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png b/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png
index e2c6718a930e2b72cd2dfa0af22752b05cc697ac..79335b66aebb32f2c3cbc2bd93d8825e3fc6d2e7 100644
Binary files a/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png and b/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png differ
diff --git a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png
index 30206f1a255fe4bc43a433cce5fb69c2b5045090..a4f6a476f66bd1e29082312aeef7064840399969 100644
Binary files a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png and b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png differ
diff --git a/playwright/testcontainers/HomeserverContainer.ts b/playwright/testcontainers/HomeserverContainer.ts
deleted file mode 100644
index 259ecb7fe0af7f5f61db9d98f98cd07f79b8aebd..0000000000000000000000000000000000000000
--- a/playwright/testcontainers/HomeserverContainer.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { AbstractStartedContainer, GenericContainer } from "testcontainers";
-import { APIRequestContext, TestInfo } from "@playwright/test";
-
-import { HomeserverInstance } from "../plugins/homeserver";
-import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
-
-export interface HomeserverContainer<Config> extends GenericContainer {
-    withConfigField(key: string, value: any): this;
-    withConfig(config: Partial<Config>): this;
-    withMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): this;
-    start(): Promise<StartedHomeserverContainer>;
-}
-
-export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {
-    setRequest(request: APIRequestContext): void;
-    onTestFinished(testInfo: TestInfo): Promise<void>;
-}
diff --git a/playwright/testcontainers/dendrite.ts b/playwright/testcontainers/dendrite.ts
index 58ab844a7cac604afe543d7a317015a89fb9a383..55938778cd0616037508c12de075d70cfc4eb406 100644
--- a/playwright/testcontainers/dendrite.ts
+++ b/playwright/testcontainers/dendrite.ts
@@ -8,12 +8,13 @@ Please see LICENSE files in the repository root for full details.
 import { GenericContainer, Wait } from "testcontainers";
 import * as YAML from "yaml";
 import { set } from "lodash";
-
-import { randB64Bytes } from "../plugins/utils/rand.ts";
-import { StartedSynapseContainer } from "./synapse.ts";
-import { deepCopy } from "../plugins/utils/object.ts";
-import { HomeserverContainer } from "./HomeserverContainer.ts";
-import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
+import { randB64Bytes } from "@element-hq/element-web-playwright-common/lib/utils/rand.js";
+import { deepCopy } from "@element-hq/element-web-playwright-common/lib/utils/object.js";
+import {
+    StartedSynapseContainer,
+    type HomeserverContainer,
+    type StartedMatrixAuthenticationServiceContainer,
+} from "@element-hq/element-web-playwright-common/lib/testcontainers";
 
 const DEFAULT_CONFIG = {
     version: 2,
@@ -223,7 +224,7 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon
             .withWaitStrategy(Wait.forHttp("/_matrix/client/versions", 8008));
     }
 
-    public withConfigField(key: string, value: any): this {
+    public withConfigField(key: string, value: unknown): this {
         set(this.config, key, value);
         return this;
     }
@@ -236,6 +237,11 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon
         return this;
     }
 
+    // Dendrite does not support SMTP at this time - https://github.com/element-hq/dendrite/issues/1298
+    public withSmtpServer(): this {
+        return this;
+    }
+
     // Dendrite does not support MAS at this time
     public withMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): this {
         return this;
diff --git a/playwright/testcontainers/mailhog.ts b/playwright/testcontainers/mailhog.ts
deleted file mode 100644
index c3305607d89bdc84220283a7e9bb185d807aeda3..0000000000000000000000000000000000000000
--- a/playwright/testcontainers/mailhog.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
-import mailhog from "mailhog";
-
-export class MailhogContainer extends GenericContainer {
-    constructor() {
-        super("mailhog/mailhog:latest");
-
-        this.withExposedPorts(8025).withWaitStrategy(Wait.forListeningPorts());
-    }
-
-    public override async start(): Promise<StartedMailhogContainer> {
-        return new StartedMailhogContainer(await super.start());
-    }
-}
-
-export class StartedMailhogContainer extends AbstractStartedContainer {
-    public readonly client: mailhog.API;
-
-    constructor(container: StartedTestContainer) {
-        super(container);
-        this.client = mailhog({ host: container.getHost(), port: container.getMappedPort(8025) });
-    }
-}
diff --git a/playwright/testcontainers/mas.ts b/playwright/testcontainers/mas.ts
deleted file mode 100644
index 9b05b521bacc624392c98bbfb02ae4854760d395..0000000000000000000000000000000000000000
--- a/playwright/testcontainers/mas.ts
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait, ExecResult } from "testcontainers";
-import { StartedPostgreSqlContainer } from "@testcontainers/postgresql";
-import * as YAML from "yaml";
-
-import { getFreePort } from "../plugins/utils/port.ts";
-import { deepCopy } from "../plugins/utils/object.ts";
-import { Credentials } from "../plugins/homeserver";
-
-const DEFAULT_CONFIG = {
-    http: {
-        listeners: [
-            {
-                name: "web",
-                resources: [
-                    { name: "discovery" },
-                    { name: "human" },
-                    { name: "oauth" },
-                    { name: "compat" },
-                    {
-                        name: "graphql",
-                        playground: true,
-                    },
-                    {
-                        name: "assets",
-                        path: "/usr/local/share/mas-cli/assets/",
-                    },
-                ],
-                binds: [
-                    {
-                        address: "[::]:8080",
-                    },
-                ],
-                proxy_protocol: false,
-            },
-            {
-                name: "internal",
-                resources: [
-                    {
-                        name: "health",
-                    },
-                ],
-                binds: [
-                    {
-                        address: "[::]:8081",
-                    },
-                ],
-                proxy_protocol: false,
-            },
-        ],
-        trusted_proxies: ["192.128.0.0/16", "172.16.0.0/12", "10.0.0.0/10", "127.0.0.1/8", "fd00::/8", "::1/128"],
-        public_base: "", // Needs to be set
-        issuer: "", // Needs to be set
-    },
-    database: {
-        host: "postgres",
-        port: 5432,
-        database: "postgres",
-        username: "postgres",
-        password: "p4S5w0rD",
-        max_connections: 10,
-        min_connections: 0,
-        connect_timeout: 30,
-        idle_timeout: 600,
-        max_lifetime: 1800,
-    },
-    telemetry: {
-        tracing: {
-            exporter: "none",
-            propagators: [],
-        },
-        metrics: {
-            exporter: "none",
-        },
-        sentry: {
-            dsn: null,
-        },
-    },
-    templates: {
-        path: "/usr/local/share/mas-cli/templates/",
-        assets_manifest: "/usr/local/share/mas-cli/manifest.json",
-        translations_path: "/usr/local/share/mas-cli/translations/",
-    },
-    email: {
-        from: '"Authentication Service" <root@localhost>',
-        reply_to: '"Authentication Service" <root@localhost>',
-        transport: "smtp",
-        mode: "plain",
-        hostname: "mailhog",
-        port: 1025,
-        username: "username",
-        password: "password",
-    },
-    secrets: {
-        encryption: "984b18e207c55ad5fbb2a49b217481a722917ee87b2308d4cf314c83fed8e3b5",
-        keys: [
-            {
-                kid: "YEAhzrKipJ",
-                key: "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAuIV+AW5vx52I4CuumgSxp6yvKfIAnRdALeZZCoFkIGxUli1B\nS79NJ3ls46oLh1pSD9RrhaMp6HTNoi4K3hnP9Q9v77pD7KwdFKG3UdG1zksIB0s/\n+/Ey/DmX4LPluwBBS7r/LkQ1jk745lENA++oiDqZf2D/uP8jCHlvaSNyVKTqi1ki\nOXPd4T4xBUjzuas9ze5jQVSYtfOidgnv1EzUipbIxgvH1jNt4raRlmP8mOq7xEnW\nR+cF5x6n/g17PdSEfrwO4kz6aKGZuMP5lVlDEEnMHKabFSQDBl7+Mpok6jXutbtA\nuiBnsKEahF9eoj4na4fpbRNPdIVyoaN5eGvm5wIDAQABAoIBAApyFCYEmHNWaa83\nCdVSOrRhRDE9r+c0r79pcNT1ajOjrk4qFa4yEC4R46YntCtfY5Hd1pBkIjU0l4d8\nz8Su9WTMEOwjQUEepS7L0NLi6kXZXYT8L40VpGs+32grBvBFHW0qEtQNrHJ36gMv\nx2rXoFTF7HaXiSJx3wvVxAbRqOE9tBXLsmNHaWaAdWQG5o77V9+zvMri3cAeEg2w\nVkKokb0dza7es7xG3tqS26k69SrwGeeuKo7qCHPH2cfyWmY5Yhv8iOoA59JzzbiK\nUdxyzCHskrPSpRKVkVVwmY3RBt282TmSRG7td7e5ESSj50P2e5BI5uu1Hp/dvU4F\nvYjV7kECgYEA6WqYoUpVsgQiqhvJwJIc/8gRm0mUy8TenI36z4Iim01Nt7fibWH7\nXnsFqLGjXtYNVWvBcCrUl9doEnRbJeG2eRGbGKYAWVrOeFvwM4fYvw9GoOiJdDj4\ncgWDe7eHbHE+UTqR7Nnr/UBfipoNWDh6X68HRBuXowh0Q6tOfxsrRFECgYEAyl/V\n4b8bFp3pKZZCb+KPSYsQf793cRmrBexPcLWcDPYbMZQADEZ/VLjbrNrpTOWxUWJT\nhr8MrWswnHO+l5AFu5CNO+QgV2dHLk+2w8qpdpFRPJCfXfo2D3wZ0c4cv3VCwv1V\n5y7f6XWVjDWZYV4wj6c3shxZJjZ+9Hbhf3/twbcCgYA6fuRRR3fCbRbi2qPtBrEN\nyO3gpMgNaQEA6vP4HPzfPrhDWmn8T5nXS61XYW03zxz4U1De81zj0K/cMBzHmZFJ\nNghQXQmpWwBzWVcREvJWr1Vb7erEnaJlsMwKrSvbGWYspSj82oAxr3hCG+lMOpsw\nb4S6pM+TpAK/EqdRY1WsgQKBgQCGoMaaTRXqL9bC0bEU2XVVCWxKb8c3uEmrwQ7/\n/fD4NmjUzI5TnDps1CVfkqoNe+hAKddDFqmKXHqUOfOaxDbsFje+lf5l5tDVoDYH\nfjTKKdYPIm7CiAeauYY7qpA5Vfq52Opixy4yEwUPp0CII67OggFtPaqY3zwJyWQt\n+57hdQKBgGCXM/KKt7ceUDcNJxSGjvu0zD9D5Sv2ihYlEBT/JLaTCCJdvzREevaJ\n1d+mpUAt0Lq6A8NWOMq8HPaxAik3rMQ0WtM5iG+XgsUqvTSb7NcshArDLuWGnW3m\nMC4rM0UBYAS4QweduUSH1imrwH/1Gu5+PxbiecceRMMggWpzu0Bq\n-----END RSA PRIVATE KEY-----\n",
-            },
-            {
-                kid: "8J1AxrlNZT",
-                key: "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIF1cjfIOEdy3BXJ72x6fKpEB8WP1ddZAUJAaqqr/6CpToAoGCCqGSM49\nAwEHoUQDQgAEfHdNuI1Yeh3/uOq2PlnW2vymloOVpwBYebbw4VVsna9xhnutIdQW\ndE8hkX8Yb0pIDasrDiwllVLzSvsWJAI0Kw==\n-----END EC PRIVATE KEY-----\n",
-            },
-            {
-                kid: "3BW6un1EBi",
-                key: "-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDA+3ZV17r8TsiMdw1cpbTSNbyEd5SMy3VS1Mk/kz6O2Ev/3QZut8GE2\nq3eGtLBoVQigBwYFK4EEACKhZANiAASs8Wxjk/uRimRKXnPr2/wDaXkN9wMDjYQK\nmZULb+0ZP1/cXmuXuri8hUGhQvIU8KWY9PkpV+LMPEdpE54mHPKSLjq5CDXoSZ/P\n9f7cdRaOZ000KQPZfIFR9ujJTtDN7Vs=\n-----END EC PRIVATE KEY-----\n",
-            },
-            {
-                kid: "pkZ0pTKK0X",
-                key: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIHenfsXYPc5yzjZKUfvmydDR1YRwdsfZYvwHf/2wsYxooAcGBSuBBAAK\noUQDQgAEON1x7Vlu+nA0KvC5vYSOHhDUkfLYNZwYSLPFVT02h9E13yFFMIJegIBl\nAer+6PMZpPc8ycyeH9N+U9NAyliBhQ==\n-----END EC PRIVATE KEY-----\n",
-            },
-        ],
-    },
-    passwords: {
-        enabled: true,
-        schemes: [
-            {
-                version: 1,
-                algorithm: "argon2id",
-            },
-        ],
-        minimum_complexity: 0,
-    },
-    policy: {
-        wasm_module: "/usr/local/share/mas-cli/policy.wasm",
-        client_registration_entrypoint: "client_registration/violation",
-        register_entrypoint: "register/violation",
-        authorization_grant_entrypoint: "authorization_grant/violation",
-        password_entrypoint: "password/violation",
-        email_entrypoint: "email/violation",
-        data: {
-            client_registration: {
-                // allow non-SSL and localhost URIs
-                allow_insecure_uris: true,
-                // EW doesn't have contacts at this time
-                allow_missing_contacts: true,
-            },
-        },
-    },
-    upstream_oauth2: {
-        providers: [],
-    },
-    branding: {
-        service_name: null,
-        policy_uri: null,
-        tos_uri: null,
-        imprint: null,
-        logo_uri: null,
-    },
-    account: {
-        password_registration_enabled: true,
-    },
-    experimental: {
-        access_token_ttl: 300,
-        compat_token_ttl: 300,
-    },
-    rate_limiting: {
-        login: {
-            burst: 10,
-            per_second: 1,
-        },
-        registration: {
-            burst: 10,
-            per_second: 1,
-        },
-    },
-};
-
-export class MatrixAuthenticationServiceContainer extends GenericContainer {
-    private config: typeof DEFAULT_CONFIG;
-    private readonly args = ["-c", "/config/config.yaml"];
-
-    constructor(db: StartedPostgreSqlContainer) {
-        // We rely on `mas-cli manage add-email` which isn't in a release yet
-        // https://github.com/element-hq/matrix-authentication-service/pull/3235
-        super("ghcr.io/element-hq/matrix-authentication-service:sha-0b90c33");
-
-        this.config = deepCopy(DEFAULT_CONFIG);
-        this.config.database.username = db.getUsername();
-        this.config.database.password = db.getPassword();
-
-        this.withExposedPorts(8080, 8081)
-            .withWaitStrategy(Wait.forHttp("/health", 8081))
-            .withCommand(["server", ...this.args]);
-    }
-
-    public withConfig(config: object): this {
-        this.config = {
-            ...this.config,
-            ...config,
-        };
-        return this;
-    }
-
-    public override async start(): Promise<StartedMatrixAuthenticationServiceContainer> {
-        // MAS config issuer needs to know what URL it'll be accessed from, so we have to map the port manually
-        const port = await getFreePort();
-
-        this.config.http.public_base = `http://localhost:${port}/`;
-        this.config.http.issuer = `http://localhost:${port}/`;
-
-        this.withExposedPorts({
-            container: 8080,
-            host: port,
-        }).withCopyContentToContainer([
-            {
-                target: "/config/config.yaml",
-                content: YAML.stringify(this.config),
-            },
-        ]);
-
-        return new StartedMatrixAuthenticationServiceContainer(
-            await super.start(),
-            `http://localhost:${port}`,
-            this.args,
-        );
-    }
-}
-
-export class StartedMatrixAuthenticationServiceContainer extends AbstractStartedContainer {
-    private adminTokenPromise?: Promise<string>;
-
-    constructor(
-        container: StartedTestContainer,
-        public readonly baseUrl: string,
-        private readonly args: string[],
-    ) {
-        super(container);
-    }
-
-    public async getAdminToken(): Promise<string> {
-        if (this.adminTokenPromise === undefined) {
-            this.adminTokenPromise = this.registerUserInternal(
-                "admin",
-                "totalyinsecureadminpassword",
-                undefined,
-                true,
-            ).then((res) => res.accessToken);
-        }
-        return this.adminTokenPromise;
-    }
-
-    private async manage(cmd: string, ...args: string[]): Promise<ExecResult> {
-        const result = await this.exec(["mas-cli", "manage", cmd, ...this.args, ...args]);
-        if (result.exitCode !== 0) {
-            throw new Error(`Failed mas-cli manage ${cmd}: ${result.output}`);
-        }
-        return result;
-    }
-
-    private async manageRegisterUser(
-        username: string,
-        password: string,
-        displayName?: string,
-        admin = false,
-    ): Promise<string> {
-        const args: string[] = [];
-        if (admin) args.push("-a");
-        const result = await this.manage(
-            "register-user",
-            ...args,
-            "-y",
-            "-p",
-            password,
-            "-d",
-            displayName ?? "",
-            username,
-        );
-
-        const registerLines = result.output.trim().split("\n");
-        const userId = registerLines
-            .find((line) => line.includes("Matrix ID: "))
-            ?.split(": ")
-            .pop();
-
-        if (!userId) {
-            throw new Error(`Failed to register user: ${result.output}`);
-        }
-
-        return userId;
-    }
-
-    private async manageIssueCompatibilityToken(
-        username: string,
-        admin = false,
-    ): Promise<{ accessToken: string; deviceId: string }> {
-        const args: string[] = [];
-        if (admin) args.push("--yes-i-want-to-grant-synapse-admin-privileges");
-        const result = await this.manage("issue-compatibility-token", ...args, username);
-
-        const parts = result.output.trim().split(/\s+/);
-        const accessToken = parts.find((part) => part.startsWith("mct_"));
-        const deviceId = parts.find((part) => part.startsWith("compat_session.device="))?.split("=")[1];
-
-        if (!accessToken || !deviceId) {
-            throw new Error(`Failed to issue compatibility token: ${result.output}`);
-        }
-
-        return { accessToken, deviceId };
-    }
-
-    private async registerUserInternal(
-        username: string,
-        password: string,
-        displayName?: string,
-        admin = false,
-    ): Promise<Credentials> {
-        const userId = await this.manageRegisterUser(username, password, displayName, admin);
-        const { deviceId, accessToken } = await this.manageIssueCompatibilityToken(username, admin);
-
-        return {
-            userId,
-            accessToken,
-            deviceId,
-            homeServer: userId.slice(1).split(":").slice(1).join(":"),
-            displayName,
-            username,
-            password,
-        };
-    }
-
-    public async registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
-        return this.registerUserInternal(username, password, displayName, false);
-    }
-
-    public async setThreepid(username: string, medium: string, address: string): Promise<void> {
-        if (medium !== "email") {
-            throw new Error("Only email threepids are supported by MAS");
-        }
-
-        await this.manage("add-email", username, address);
-    }
-}
diff --git a/playwright/testcontainers/synapse.ts b/playwright/testcontainers/synapse.ts
index 4307834b3610ed6027c1aaae0c35c16cd09042dd..19d15441960de667297ddf3234d4f181f7da152f 100644
--- a/playwright/testcontainers/synapse.ts
+++ b/playwright/testcontainers/synapse.ts
@@ -1,388 +1,20 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024-2025 New Vector Ltd.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AbstractStartedContainer, GenericContainer, RestartOptions, StartedTestContainer, Wait } from "testcontainers";
-import { APIRequestContext, TestInfo } from "@playwright/test";
-import crypto from "node:crypto";
-import * as YAML from "yaml";
-import { set } from "lodash";
+import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
 
-import { getFreePort } from "../plugins/utils/port.ts";
-import { randB64Bytes } from "../plugins/utils/rand.ts";
-import { Credentials } from "../plugins/homeserver";
-import { deepCopy } from "../plugins/utils/object.ts";
-import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverContainer.ts";
-import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
-import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";
+const TAG = "develop@sha256:66955f34a593cfc3b6e77b8d5510c60c6094f5bade8a17d2feaefbb8662ccf09";
 
-const TAG = "develop@sha256:5d62b61c4373eaca25df6c6bb99fc1be92f8f40b8abebd8897bf5b2af9eb137a";
-
-const DEFAULT_CONFIG = {
-    server_name: "localhost",
-    public_baseurl: "", // set by start method
-    pid_file: "/homeserver.pid",
-    web_client: false,
-    soft_file_limit: 0,
-    // Needs to be configured to log to the console like a good docker process
-    log_config: "/data/log.config",
-    listeners: [
-        {
-            // Listener is always port 8008 (configured in the container)
-            port: 8008,
-            tls: false,
-            bind_addresses: ["::"],
-            type: "http",
-            x_forwarded: true,
-            resources: [
-                {
-                    names: ["client"],
-                    compress: false,
-                },
-            ],
-        },
-    ],
-    database: {
-        // An sqlite in-memory database is fast & automatically wipes each time
-        name: "sqlite3",
-        args: {
-            database: ":memory:",
-        },
-    },
-    rc_messages_per_second: 10000,
-    rc_message_burst_count: 10000,
-    rc_registration: {
-        per_second: 10000,
-        burst_count: 10000,
-    },
-    rc_joins: {
-        local: {
-            per_second: 9999,
-            burst_count: 9999,
-        },
-        remote: {
-            per_second: 9999,
-            burst_count: 9999,
-        },
-    },
-    rc_joins_per_room: {
-        per_second: 9999,
-        burst_count: 9999,
-    },
-    rc_3pid_validation: {
-        per_second: 1000,
-        burst_count: 1000,
-    },
-    rc_invites: {
-        per_room: {
-            per_second: 1000,
-            burst_count: 1000,
-        },
-        per_user: {
-            per_second: 1000,
-            burst_count: 1000,
-        },
-    },
-    rc_login: {
-        address: {
-            per_second: 10000,
-            burst_count: 10000,
-        },
-        account: {
-            per_second: 10000,
-            burst_count: 10000,
-        },
-        failed_attempts: {
-            per_second: 10000,
-            burst_count: 10000,
-        },
-    },
-    media_store_path: "/tmp/media_store",
-    max_upload_size: "50M",
-    max_image_pixels: "32M",
-    dynamic_thumbnails: false,
-    enable_registration: true,
-    enable_registration_without_verification: true,
-    disable_msisdn_registration: false,
-    registrations_require_3pid: [],
-    enable_metrics: false,
-    report_stats: false,
-    // These placeholders will be replaced with values generated at start
-    registration_shared_secret: "secret",
-    macaroon_secret_key: "secret",
-    form_secret: "secret",
-    // Signing key must be here: it will be generated to this file
-    signing_key_path: "/data/localhost.signing.key",
-    trusted_key_servers: [],
-    password_config: {
-        enabled: true,
-    },
-    ui_auth: {},
-    background_updates: {
-        // Inhibit background updates as this Synapse isn't long-lived
-        min_batch_size: 100000,
-        sleep_duration_ms: 100000,
-    },
-    enable_authenticated_media: true,
-    email: undefined,
-    user_consent: undefined,
-    server_notices: undefined,
-    allow_guest_access: false,
-    experimental_features: {},
-    oidc_providers: [],
-    serve_server_wellknown: true,
-    presence: {
-        enabled: true,
-        include_offline_users_on_sync: true,
-    },
-};
-
-export type SynapseConfig = Partial<typeof DEFAULT_CONFIG>;
-
-export class SynapseContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
-    private config: typeof DEFAULT_CONFIG;
-    private mas?: StartedMatrixAuthenticationServiceContainer;
-
-    constructor() {
+/**
+ * SynapseContainer which freezes the docker digest to stabilise tests,
+ * updated periodically by the `playwright-image-updates.yaml` workflow.
+ */
+export class SynapseContainer extends BaseSynapseContainer {
+    public constructor() {
         super(`ghcr.io/element-hq/synapse:${TAG}`);
-
-        this.config = deepCopy(DEFAULT_CONFIG);
-        this.config.registration_shared_secret = randB64Bytes(16);
-        this.config.macaroon_secret_key = randB64Bytes(16);
-        this.config.form_secret = randB64Bytes(16);
-
-        const signingKey = randB64Bytes(32);
-        this.withWaitStrategy(Wait.forHttp("/health", 8008)).withCopyContentToContainer([
-            { target: this.config.signing_key_path, content: `ed25519 x ${signingKey}` },
-            {
-                target: this.config.log_config,
-                content: YAML.stringify({
-                    version: 1,
-                    formatters: {
-                        precise: {
-                            format: "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s",
-                        },
-                    },
-                    handlers: {
-                        console: {
-                            class: "logging.StreamHandler",
-                            formatter: "precise",
-                        },
-                    },
-                    loggers: {
-                        "synapse.storage.SQL": {
-                            level: "DEBUG",
-                        },
-                        "twisted": {
-                            handlers: ["console"],
-                            propagate: false,
-                        },
-                    },
-                    root: {
-                        level: "DEBUG",
-                        handlers: ["console"],
-                    },
-                    disable_existing_loggers: false,
-                }),
-            },
-        ]);
-    }
-
-    public withConfigField(key: string, value: any): this {
-        set(this.config, key, value);
-        return this;
-    }
-
-    public withConfig(config: Partial<typeof DEFAULT_CONFIG>): this {
-        this.config = {
-            ...this.config,
-            ...config,
-        };
-        return this;
-    }
-
-    public withMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): this {
-        this.mas = mas;
-        return this;
-    }
-
-    public override async start(): Promise<StartedSynapseContainer> {
-        // Synapse config public_baseurl needs to know what URL it'll be accessed from, so we have to map the port manually
-        const port = await getFreePort();
-
-        this.withExposedPorts({
-            container: 8008,
-            host: port,
-        })
-            .withConfig({
-                public_baseurl: `http://localhost:${port}`,
-            })
-            .withCopyContentToContainer([
-                {
-                    target: "/data/homeserver.yaml",
-                    content: YAML.stringify(this.config),
-                },
-            ]);
-
-        const container = await super.start();
-        const baseUrl = `http://localhost:${port}`;
-        if (this.mas) {
-            return new StartedSynapseWithMasContainer(
-                container,
-                baseUrl,
-                this.config.registration_shared_secret,
-                this.mas,
-            );
-        }
-
-        return new StartedSynapseContainer(container, baseUrl, this.config.registration_shared_secret);
-    }
-}
-
-export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
-    protected adminTokenPromise?: Promise<string>;
-    protected readonly adminApi: Api;
-    public readonly csApi: ClientServerApi;
-
-    constructor(
-        container: StartedTestContainer,
-        public readonly baseUrl: string,
-        private readonly registrationSharedSecret: string,
-    ) {
-        super(container);
-        this.adminApi = new Api(`${this.baseUrl}/_synapse/admin`);
-        this.csApi = new ClientServerApi(this.baseUrl);
-    }
-
-    public restart(options?: Partial<RestartOptions>): Promise<void> {
-        this.adminTokenPromise = undefined;
-        return super.restart(options);
-    }
-
-    public setRequest(request: APIRequestContext): void {
-        this.csApi.setRequest(request);
-        this.adminApi.setRequest(request);
-    }
-
-    public async onTestFinished(testInfo: TestInfo): Promise<void> {
-        // Clean up the server to prevent rooms leaking between tests
-        await this.deletePublicRooms();
-    }
-
-    protected async deletePublicRooms(): Promise<void> {
-        const token = await this.getAdminToken();
-        // We hide the rooms from the room directory to save time between tests and for portability between homeservers
-        const { chunk: rooms } = await this.csApi.request<{
-            chunk: { room_id: string }[];
-        }>("GET", "/v3/publicRooms", token, {});
-        await Promise.all(
-            rooms.map((room) =>
-                this.csApi.request("PUT", `/v3/directory/list/room/${room.room_id}`, token, { visibility: "private" }),
-            ),
-        );
-    }
-
-    private async registerUserInternal(
-        username: string,
-        password: string,
-        displayName?: string,
-        admin = false,
-    ): Promise<Credentials> {
-        const path = "/v1/register";
-        const { nonce } = await this.adminApi.request<{ nonce: string }>("GET", path, undefined, {});
-        const mac = crypto
-            .createHmac("sha1", this.registrationSharedSecret)
-            .update(`${nonce}\0${username}\0${password}\0${admin ? "" : "not"}admin`)
-            .digest("hex");
-        const data = await this.adminApi.request<{
-            home_server: string;
-            access_token: string;
-            user_id: string;
-            device_id: string;
-        }>("POST", path, undefined, {
-            nonce,
-            username,
-            password,
-            mac,
-            admin,
-            displayname: displayName,
-        });
-
-        return {
-            homeServer: data.home_server || data.user_id.split(":").slice(1).join(":"),
-            accessToken: data.access_token,
-            userId: data.user_id,
-            deviceId: data.device_id,
-            password,
-            displayName,
-            username,
-        };
-    }
-
-    protected async getAdminToken(): Promise<string> {
-        if (this.adminTokenPromise === undefined) {
-            this.adminTokenPromise = this.registerUserInternal(
-                "admin",
-                "totalyinsecureadminpassword",
-                undefined,
-                true,
-            ).then((res) => res.accessToken);
-        }
-        return this.adminTokenPromise;
-    }
-
-    private async adminRequest<R extends {}>(verb: "GET", path: string, data?: never): Promise<R>;
-    private async adminRequest<R extends {}>(verb: Verb, path: string, data?: object): Promise<R>;
-    private async adminRequest<R extends {}>(verb: Verb, path: string, data?: object): Promise<R> {
-        const adminToken = await this.getAdminToken();
-        return this.adminApi.request(verb, path, adminToken, data);
-    }
-
-    public registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
-        return this.registerUserInternal(username, password, displayName, false);
-    }
-
-    public async loginUser(userId: string, password: string): Promise<Credentials> {
-        return this.csApi.loginUser(userId, password);
-    }
-
-    public async setThreepid(userId: string, medium: string, address: string): Promise<void> {
-        await this.adminRequest("PUT", `/v2/users/${userId}`, {
-            threepids: [
-                {
-                    medium,
-                    address,
-                },
-            ],
-        });
-    }
-}
-
-export class StartedSynapseWithMasContainer extends StartedSynapseContainer {
-    constructor(
-        container: StartedTestContainer,
-        baseUrl: string,
-        registrationSharedSecret: string,
-        private readonly mas: StartedMatrixAuthenticationServiceContainer,
-    ) {
-        super(container, baseUrl, registrationSharedSecret);
-    }
-
-    protected async getAdminToken(): Promise<string> {
-        if (this.adminTokenPromise === undefined) {
-            this.adminTokenPromise = this.mas.getAdminToken();
-        }
-        return this.adminTokenPromise;
-    }
-
-    public registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
-        return this.mas.registerUser(username, password, displayName);
-    }
-
-    public async setThreepid(userId: string, medium: string, address: string): Promise<void> {
-        return this.mas.setThreepid(userId, medium, address);
     }
 }
diff --git a/res/apple-app-site-association b/res/apple-app-site-association
index 94869effabb7314d86d458e162f4bd57412e44de..0235e6ada1e254c2a8bc9d485ded34bfbd92315a 100644
--- a/res/apple-app-site-association
+++ b/res/apple-app-site-association
@@ -5,8 +5,7 @@
         "appIDs": [
           "7J4U792NQT.im.vector.app",
           "7J4U792NQT.io.element.elementx",
-          "7J4U792NQT.io.element.elementx.nightly",
-          "7J4U792NQT.io.element.elementx.pr"
+          "7J4U792NQT.io.element.elementx.nightly"
         ],
         "components": [
           {
@@ -28,8 +27,7 @@
     "apps": [
       "7J4U792NQT.im.vector.app",
       "7J4U792NQT.io.element.elementx",
-      "7J4U792NQT.io.element.elementx.nightly",
-      "7J4U792NQT.io.element.elementx.pr"
+      "7J4U792NQT.io.element.elementx.nightly"
     ]
   }
 }
diff --git a/res/css/_common.pcss b/res/css/_common.pcss
index fe8eff22860b651ad0496af01bd08cb28d33f88e..3eed8c93c603e12f670c16f68a6327b436aad12d 100644
--- a/res/css/_common.pcss
+++ b/res/css/_common.pcss
@@ -589,18 +589,23 @@ legend {
  * in the app look the same by being AccessibleButtons, or possibly by having explict button classes.
  * We should go through and have one consistent set of styles for buttons throughout the app.
  * For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons.
- *
- * Elements that should not be styled like a dialog button are mentioned in a :not() pseudo-class.
- * For the widest browser support, we use multiple :not pseudo-classes instead of :not(.a, .b).
  */
 .mx_Dialog
-    button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
-        .mx_UserProfileSettings button
-    ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
-        .mx_EncryptionUserSettingsTab button
+    button:not(
+        .mx_EncryptionUserSettingsTab button,
+        .mx_EncryptionCard button,
+        .mx_UserProfileSettings button,
+        .mx_ShareDialog button,
+        .mx_UnpinAllDialog button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_Dialog_nonDialogButton,
+        .mx_AccessibleButton,
+        .mx_IdentityServerPicker button,
+        .mx_AccessSecretStorageDialog button,
+        [class|="maplibregl"]
     ),
+.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton),
 .mx_Dialog input[type="submit"],
-.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
 .mx_Dialog_buttons input[type="submit"] {
     @mixin mx_DialogButton;
     margin-left: 0px;
@@ -616,32 +621,46 @@ legend {
 }
 
 .mx_Dialog
-    button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
-        .mx_UserProfileSettings button
-    ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
+    button:not(
+        .mx_Dialog_nonDialogButton,
+        [class|="maplibregl"],
+        .mx_AccessibleButton,
+        .mx_UserProfileSettings button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_UnpinAllDialog button,
+        .mx_ShareDialog button,
         .mx_EncryptionUserSettingsTab button
     ):last-child {
     margin-right: 0px;
 }
 
 .mx_Dialog
-    button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
-        .mx_UserProfileSettings button
-    ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
+    button:not(
+        .mx_Dialog_nonDialogButton,
+        [class|="maplibregl"],
+        .mx_AccessibleButton,
+        .mx_UserProfileSettings button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_UnpinAllDialog button,
+        .mx_ShareDialog button,
         .mx_EncryptionUserSettingsTab button
     ):focus,
 .mx_Dialog input[type="submit"]:focus,
-.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
+.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):focus,
 .mx_Dialog_buttons input[type="submit"]:focus {
     filter: brightness($focus-brightness);
 }
 
-.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
+.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
 .mx_Dialog input[type="submit"].mx_Dialog_primary,
 .mx_Dialog_buttons
-    button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
-        .mx_UserProfileSettings button
-    ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
+    button:not(
+        .mx_Dialog_nonDialogButton,
+        .mx_AccessibleButton,
+        .mx_UserProfileSettings button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_UnpinAllDialog button,
+        .mx_ShareDialog button,
         .mx_EncryptionUserSettingsTab button
     ),
 .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
@@ -651,32 +670,43 @@ legend {
     min-width: 156px;
 }
 
-.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
+.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
 .mx_Dialog input[type="submit"].danger,
 .mx_Dialog_buttons
-    button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
-        .mx_ThemeChoicePanel_CustomTheme button
-    ):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
+    button.danger:not(
+        .mx_Dialog_nonDialogButton,
+        .mx_AccessibleButton,
+        .mx_UserProfileSettings button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_UnpinAllDialog button,
+        .mx_ShareDialog button,
+        .mx_EncryptionUserSettingsTab button
+    ),
 .mx_Dialog_buttons input[type="submit"].danger {
     background-color: var(--cpd-color-bg-critical-primary);
     border: solid 1px var(--cpd-color-bg-critical-primary);
     color: var(--cpd-color-text-on-solid-primary);
 }
 
-.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
+.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
 .mx_Dialog input[type="submit"].warning {
     border: solid 1px var(--cpd-color-border-critical-subtle);
     color: var(--cpd-color-text-critical-primary);
 }
 
 .mx_Dialog
-    button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
-        .mx_UserProfileSettings button
-    ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
+    button:not(
+        .mx_Dialog_nonDialogButton,
+        [class|="maplibregl"],
+        .mx_AccessibleButton,
+        .mx_UserProfileSettings button,
+        .mx_ThemeChoicePanel_CustomTheme button,
+        .mx_UnpinAllDialog button,
+        .mx_ShareDialog button,
         .mx_EncryptionUserSettingsTab button
     ):disabled,
 .mx_Dialog input[type="submit"]:disabled,
-.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
+.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):disabled,
 .mx_Dialog_buttons input[type="submit"]:disabled {
     background-color: $light-fg-color;
     border: solid 1px $light-fg-color;
diff --git a/res/css/_components.pcss b/res/css/_components.pcss
index e0f9a5788dee66cb0273aed1fba1021898896110..247dd7edab2e0b693b8e1dabd3a70be073cae0c7 100644
--- a/res/css/_components.pcss
+++ b/res/css/_components.pcss
@@ -48,6 +48,7 @@
 @import "./components/views/settings/devices/_FilteredDeviceListHeader.pcss";
 @import "./components/views/settings/devices/_SecurityRecommendations.pcss";
 @import "./components/views/settings/devices/_SelectableDeviceTile.pcss";
+@import "./components/views/settings/encryption/_KeyStoragePanel.pcss";
 @import "./components/views/settings/shared/_SettingsSubsection.pcss";
 @import "./components/views/settings/shared/_SettingsSubsectionHeading.pcss";
 @import "./components/views/spaces/_QuickThemeSwitcher.pcss";
@@ -115,6 +116,7 @@
 @import "./views/auth/_Welcome.pcss";
 @import "./views/avatars/_BaseAvatar.pcss";
 @import "./views/avatars/_DecoratedRoomAvatar.pcss";
+@import "./views/avatars/_RoomAvatarView.pcss";
 @import "./views/avatars/_WidgetAvatar.pcss";
 @import "./views/avatars/_WithPresenceIndicator.pcss";
 @import "./views/beta/_BetaCard.pcss";
@@ -127,13 +129,14 @@
 @import "./views/dialogs/_AddExistingToSpaceDialog.pcss";
 @import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss";
 @import "./views/dialogs/_BugReportDialog.pcss";
-@import "./views/dialogs/_BulkRedactDialog.pcss";
 @import "./views/dialogs/_ChangelogDialog.pcss";
 @import "./views/dialogs/_CompoundDialog.pcss";
+@import "./views/dialogs/_ConfirmKeyStorageOffDialog.pcss";
 @import "./views/dialogs/_ConfirmSpaceUserActionDialog.pcss";
 @import "./views/dialogs/_ConfirmUserActionDialog.pcss";
 @import "./views/dialogs/_CreateRoomDialog.pcss";
 @import "./views/dialogs/_CreateSubspaceDialog.pcss";
+@import "./views/dialogs/_Crypto.pcss";
 @import "./views/dialogs/_DeactivateAccountDialog.pcss";
 @import "./views/dialogs/_DevtoolsDialog.pcss";
 @import "./views/dialogs/_ExportDialog.pcss";
@@ -151,6 +154,7 @@
 @import "./views/dialogs/_ModalWidgetDialog.pcss";
 @import "./views/dialogs/_PollCreateDialog.pcss";
 @import "./views/dialogs/_RegistrationEmailPromptDialog.pcss";
+@import "./views/dialogs/_ReportRoomDialog.pcss";
 @import "./views/dialogs/_RoomSettingsDialog.pcss";
 @import "./views/dialogs/_RoomSettingsDialogBridges.pcss";
 @import "./views/dialogs/_RoomUpgradeDialog.pcss";
@@ -210,7 +214,6 @@
 @import "./views/elements/_ServerPicker.pcss";
 @import "./views/elements/_SettingsFlag.pcss";
 @import "./views/elements/_Spinner.pcss";
-@import "./views/elements/_StyledCheckbox.pcss";
 @import "./views/elements/_StyledRadioButton.pcss";
 @import "./views/elements/_SyntaxHighlight.pcss";
 @import "./views/elements/_TagComposer.pcss";
@@ -226,6 +229,7 @@
 @import "./views/messages/_DisambiguatedProfile.pcss";
 @import "./views/messages/_EventTileBubble.pcss";
 @import "./views/messages/_HiddenBody.pcss";
+@import "./views/messages/_HiddenMediaPlaceholder.pcss";
 @import "./views/messages/_JumpToDatePicker.pcss";
 @import "./views/messages/_LegacyCallEvent.pcss";
 @import "./views/messages/_MEmoteBody.pcss";
@@ -268,6 +272,16 @@
 @import "./views/right_panel/_VerificationPanel.pcss";
 @import "./views/right_panel/_WidgetCard.pcss";
 @import "./views/room_settings/_AliasSettings.pcss";
+@import "./views/rooms/RoomListPanel/_EmptyRoomList.pcss";
+@import "./views/rooms/RoomListPanel/_RoomList.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListItemMenuView.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss";
+@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss";
 @import "./views/rooms/_AppsDrawer.pcss";
 @import "./views/rooms/_Autocomplete.pcss";
 @import "./views/rooms/_AuxPanel.pcss";
@@ -283,7 +297,10 @@
 @import "./views/rooms/_EventTile.pcss";
 @import "./views/rooms/_HistoryTile.pcss";
 @import "./views/rooms/_IRCLayout.pcss";
+@import "./views/rooms/_InvitedIconView.pcss";
 @import "./views/rooms/_JumpToBottomButton.pcss";
+@import "./views/rooms/_LegacyRoomList.pcss";
+@import "./views/rooms/_LegacyRoomListHeader.pcss";
 @import "./views/rooms/_LinkPreviewGroup.pcss";
 @import "./views/rooms/_LinkPreviewWidget.pcss";
 @import "./views/rooms/_LiveContentSummary.pcss";
@@ -307,8 +324,6 @@
 @import "./views/rooms/_RoomHeader.pcss";
 @import "./views/rooms/_RoomInfoLine.pcss";
 @import "./views/rooms/_RoomKnocksBar.pcss";
-@import "./views/rooms/_RoomList.pcss";
-@import "./views/rooms/_RoomListHeader.pcss";
 @import "./views/rooms/_RoomPreviewBar.pcss";
 @import "./views/rooms/_RoomPreviewCard.pcss";
 @import "./views/rooms/_RoomSearchAuxPanel.pcss";
@@ -329,8 +344,6 @@
 @import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss";
 @import "./views/rooms/wysiwyg_composer/components/_LinkModal.pcss";
 @import "./views/settings/_AvatarSetting.pcss";
-@import "./views/settings/_CrossSigningPanel.pcss";
-@import "./views/settings/_CryptographyPanel.pcss";
 @import "./views/settings/_FontScalingPanel.pcss";
 @import "./views/settings/_ImageSizePanel.pcss";
 @import "./views/settings/_IntegrationManager.pcss";
@@ -343,8 +356,6 @@
 @import "./views/settings/_PhoneNumbers.pcss";
 @import "./views/settings/_PowerLevelSelector.pcss";
 @import "./views/settings/_RoomProfileSettings.pcss";
-@import "./views/settings/_SecureBackupPanel.pcss";
-@import "./views/settings/_SetIdServer.pcss";
 @import "./views/settings/_SetIntegrationManager.pcss";
 @import "./views/settings/_SettingsFieldset.pcss";
 @import "./views/settings/_SettingsHeader.pcss";
@@ -353,8 +364,12 @@
 @import "./views/settings/_ThemeChoicePanel.pcss";
 @import "./views/settings/_UpdateCheckButton.pcss";
 @import "./views/settings/_UserProfileSettings.pcss";
+@import "./views/settings/encryption/_AdvancedPanel.pcss";
 @import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
 @import "./views/settings/encryption/_EncryptionCard.pcss";
+@import "./views/settings/encryption/_EncryptionCardEmphasisedContent.pcss";
+@import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss";
+@import "./views/settings/encryption/_ResetIdentityPanel.pcss";
 @import "./views/settings/tabs/_SettingsBanner.pcss";
 @import "./views/settings/tabs/_SettingsIndent.pcss";
 @import "./views/settings/tabs/_SettingsSection.pcss";
@@ -366,6 +381,7 @@
 @import "./views/settings/tabs/user/_AppearanceUserSettingsTab.pcss";
 @import "./views/settings/tabs/user/_HelpUserSettingsTab.pcss";
 @import "./views/settings/tabs/user/_KeyboardUserSettingsTab.pcss";
+@import "./views/settings/tabs/user/_MediaPreviewAccountSettings.pcss";
 @import "./views/settings/tabs/user/_MjolnirUserSettingsTab.pcss";
 @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.pcss";
 @import "./views/settings/tabs/user/_SecurityUserSettingsTab.pcss";
diff --git a/res/css/components/views/elements/_FilterDropdown.pcss b/res/css/components/views/elements/_FilterDropdown.pcss
index a2682213c6663e8aeaec1dae284267002beef173..22eb5f2ff10b00aa59453635d8f38495507f3205 100644
--- a/res/css/components/views/elements/_FilterDropdown.pcss
+++ b/res/css/components/views/elements/_FilterDropdown.pcss
@@ -16,7 +16,7 @@ Please see LICENSE files in the repository root for full details.
 
         border: 1px solid $quinary-content;
         border-radius: 8px;
-        box-shadow: 0px 1px 3px rgba(23, 25, 28, 0.05);
+        box-shadow: 0px 1px 3px rgb(23, 25, 28, 0.05);
 
         background-color: $system;
 
diff --git a/res/css/components/views/location/_Marker.pcss b/res/css/components/views/location/_Marker.pcss
index 5a8fef919929d2f1f2218a5fefab9f6d236057e9..a3bfc10f52e3b6d800809b6f21a8a5e6f399affe 100644
--- a/res/css/components/views/location/_Marker.pcss
+++ b/res/css/components/views/location/_Marker.pcss
@@ -14,7 +14,7 @@ Please see LICENSE files in the repository root for full details.
     width: 42px;
     height: 42px;
     border-radius: 50%;
-    filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
+    filter: drop-shadow(0px 3px 5px rgb(0, 0, 0, 0.2));
     background-color: currentColor;
 
     display: flex;
diff --git a/res/css/components/views/location/_ZoomButtons.pcss b/res/css/components/views/location/_ZoomButtons.pcss
index f98c7bf1b0b068be1ead1c14d03c534b80018fa2..bb7d72e4e83916b6f2118778566f1024642ac705 100644
--- a/res/css/components/views/location/_ZoomButtons.pcss
+++ b/res/css/components/views/location/_ZoomButtons.pcss
@@ -24,7 +24,7 @@ Please see LICENSE files in the repository root for full details.
         height: $ZoomButtons_button-size;
         width: $ZoomButtons_button-size;
         background: $background;
-        box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
+        box-shadow: 0px 4px 12px rgb(0, 0, 0, 0.25);
 
         .mx_ZoomButtons_icon {
             $ZoomButtons_icon-size: 12px;
diff --git a/res/css/components/views/pips/_WidgetPip.pcss b/res/css/components/views/pips/_WidgetPip.pcss
index e515e3eec08b282fe77d9bf2a419bfda37f7dc34..b9ad791021a3c43f464ee83b9099b90c607dfe97 100644
--- a/res/css/components/views/pips/_WidgetPip.pcss
+++ b/res/css/components/views/pips/_WidgetPip.pcss
@@ -48,7 +48,7 @@ $height: 220px;
     display: flex;
     font-size: $font-12px;
     font-weight: var(--cpd-font-weight-semibold);
-    background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0));
+    background: linear-gradient(rgb(0, 0, 0, 0.9), rgb(0, 0, 0, 0));
 }
 
 .mx_WidgetPip_backButton {
@@ -69,5 +69,5 @@ $height: 220px;
     display: flex;
     justify-content: flex-end;
     align-items: flex-end;
-    background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.9));
+    background: linear-gradient(rgb(0, 0, 0, 0), rgb(0, 0, 0, 0.9));
 }
diff --git a/res/css/components/views/settings/devices/_SelectableDeviceTile.pcss b/res/css/components/views/settings/devices/_SelectableDeviceTile.pcss
index 927a53937f6c12d6eb8818e272cd0c2bb237ed59..6743410f80c7ef34230a69c78314a4cb3c3456d1 100644
--- a/res/css/components/views/settings/devices/_SelectableDeviceTile.pcss
+++ b/res/css/components/views/settings/devices/_SelectableDeviceTile.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -16,9 +16,9 @@ Please see LICENSE files in the repository root for full details.
 .mx_SelectableDeviceTile_checkbox {
     flex: 1 0;
 
-    .mx_Checkbox_background + div {
-        flex: 1 0;
-        /* override more specific selector */
-        margin-left: $spacing-16 !important;
+    > div {
+        margin-top: auto;
+        margin-bottom: auto;
+        margin-right: var(--cpd-space-1x);
     }
 }
diff --git a/res/css/components/views/settings/encryption/_KeyStoragePanel.pcss b/res/css/components/views/settings/encryption/_KeyStoragePanel.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..34a79db9cdb7bc0ecf994be7a51083d060127fa6
--- /dev/null
+++ b/res/css/components/views/settings/encryption/_KeyStoragePanel.pcss
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_KeyStoragePanel_toggleRow {
+    flex-direction: row;
+}
diff --git a/res/css/components/views/utils/_Flex.pcss b/res/css/components/views/utils/_Flex.pcss
index a7f3688466d38ec81348a50f81f4425fd0daefea..9cfa6424f074f7fbff20f1b588834e765ac2046c 100644
--- a/res/css/components/views/utils/_Flex.pcss
+++ b/res/css/components/views/utils/_Flex.pcss
@@ -12,4 +12,5 @@ Please see LICENSE files in the repository root for full details.
     align-items: var(--mx-flex-align, unset);
     justify-content: var(--mx-flex-justify, unset);
     gap: var(--mx-flex-gap, unset);
+    flex-wrap: var(--mx-flex-wrap, unset);
 }
diff --git a/res/css/structures/ErrorView.pcss b/res/css/structures/ErrorView.pcss
index cf09ac02afddba471849c84818b33edf7305ea81..ddc510e18829e5caba5060faac4444a81232c302 100644
--- a/res/css/structures/ErrorView.pcss
+++ b/res/css/structures/ErrorView.pcss
@@ -10,8 +10,9 @@ Please see LICENSE files in the repository root for full details.
     --cpd-separator-inset: calc(50% - (var(--width) / 2));
     --cpd-separator-spacing: var(--cpd-space-8x);
 
-    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
-        "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+    font-family:
+        -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
+        "Segoe UI Emoji", "Segoe UI Symbol";
     text-align: center;
     color: var(--cpd-color-text-primary);
     width: 100%;
diff --git a/res/css/structures/_ContextualMenu.pcss b/res/css/structures/_ContextualMenu.pcss
index 9e6be31ac059ed3c437807a7e16d9c6e1dca0200..3c1363f89636bbe1c7ec0e4959ff4ca785848523 100644
--- a/res/css/structures/_ContextualMenu.pcss
+++ b/res/css/structures/_ContextualMenu.pcss
@@ -23,7 +23,7 @@ Please see LICENSE files in the repository root for full details.
 
 .mx_ContextualMenu {
     border-radius: 12px;
-    box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.1);
+    box-shadow: 0px 4px 24px rgb(0, 0, 0, 0.1);
     background-color: var(--cpd-color-bg-canvas-default);
     border: var(--cpd-border-width-1) solid var(--cpd-color-border-interactive-secondary);
     color: $primary-content;
diff --git a/res/css/structures/_GenericDropdownMenu.pcss b/res/css/structures/_GenericDropdownMenu.pcss
index dd9dc0ade14db37283ba93ae2cbe362423cf113a..1d0fb796488c8abc99ca9bf7c86e85b6cf3a99a9 100644
--- a/res/css/structures/_GenericDropdownMenu.pcss
+++ b/res/css/structures/_GenericDropdownMenu.pcss
@@ -41,7 +41,7 @@ Please see LICENSE files in the repository root for full details.
         padding-bottom: 10px;
 
         border: 1px solid $quinary-content;
-        box-shadow: 0 1px 3px rgba(23, 25, 28, 0.05);
+        box-shadow: 0 1px 3px rgb(23, 25, 28, 0.05);
     }
 
     .mx_ContextualMenu_chevron_top {
diff --git a/res/css/structures/_LeftPanel.pcss b/res/css/structures/_LeftPanel.pcss
index cf2845b173005671cbcbd49c17e87b073436d983..c1886b6b80744425d04314408082f110a495c70b 100644
--- a/res/css/structures/_LeftPanel.pcss
+++ b/res/css/structures/_LeftPanel.pcss
@@ -28,6 +28,12 @@ Please see LICENSE files in the repository root for full details.
     --collapsedWidth: 68px;
 }
 
+.mx_LeftPanel_newRoomList {
+    /* Thew new rooms list is not designed to be collapsed to just icons. */
+    /* 224 + 68(spaces bar) was deemed by design to be a good minimum for the left panel. */
+    --collapsedWidth: 224px;
+}
+
 .mx_LeftPanel_wrapper {
     display: flex;
     flex-direction: row;
@@ -113,7 +119,7 @@ Please see LICENSE files in the repository root for full details.
             display: flex;
             align-items: center;
 
-            & + .mx_RoomListHeader {
+            & + .mx_LegacyRoomListHeader {
                 margin-top: 12px;
             }
 
@@ -180,7 +186,7 @@ Please see LICENSE files in the repository root for full details.
             }
         }
 
-        .mx_RoomListHeader:first-child {
+        .mx_LegacyRoomListHeader:first-child {
             margin-top: 12px;
         }
 
diff --git a/res/css/structures/_QuickSettingsButton.pcss b/res/css/structures/_QuickSettingsButton.pcss
index 44e0ded064c156a2b9fefb48a0e4552554348b7f..f4b881f9551c4c9da8a5ad59b4c3977de48913a3 100644
--- a/res/css/structures/_QuickSettingsButton.pcss
+++ b/res/css/structures/_QuickSettingsButton.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -70,50 +70,39 @@ Please see LICENSE files in the repository root for full details.
         text-transform: uppercase;
         color: var(--cpd-color-text-secondary);
         margin: 20px 0 12px;
-    }
-
-    .mx_QuickSettingsButton_pinToSidebarHeading {
-        padding-left: 24px;
         position: relative;
-    }
-
-    .mx_Checkbox {
-        margin-bottom: 8px;
-    }
-
-    .mx_QuickSettingsButton_favouritesCheckbox,
-    .mx_QuickSettingsButton_peopleCheckbox {
-        .mx_Checkbox_background + div {
-            padding-left: 22px;
-            position: relative;
-            margin-left: 6px;
-            font-size: $font-15px;
-            line-height: $font-24px;
-            color: var(--cpd-color-text-primary);
-        }
+        display: flex;
     }
 
     .mx_QuickSettingsButton_moreOptionsButton {
-        padding-left: 22px;
-        margin-left: 22px;
+        margin-left: var(--cpd-space-7x);
         font-size: $font-15px;
         line-height: $font-24px;
         color: var(--cpd-color-text-primary);
         position: relative;
         margin-bottom: 16px;
     }
+
+    .mx_QuickSettingsButton_option {
+        margin-bottom: var(--cpd-space-3x);
+        label {
+            /* Correctly line up icons and text. */
+            display: flex;
+        }
+    }
 }
 
-.mx_QuickSettingsButton_icon {
-    // TODO remove when all icons have fill=currentColor
-    * {
-        fill: $secondary-content;
+.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list {
+    .mx_QuickThemeSwitcher {
+        margin-top: var(--cpd-space-2x);
     }
+}
+
+.mx_QuickSettingsButton_icon {
+    margin-right: var(--cpd-space-1x);
     color: $secondary-content;
-    width: 16px;
-    height: 16px;
-    position: absolute;
-    left: 0;
-    top: 50%;
-    transform: translateY(-50%);
+    width: 18px;
+    height: 18px;
+    margin-top: auto;
+    margin-bottom: auto;
 }
diff --git a/res/css/structures/_RoomView.pcss b/res/css/structures/_RoomView.pcss
index 478bf548caace31fa3859b6e811bc34c2daac9d3..b7ab171615ec6e7f2918bf1f25a69189cd1aedf6 100644
--- a/res/css/structures/_RoomView.pcss
+++ b/res/css/structures/_RoomView.pcss
@@ -35,6 +35,7 @@ Please see LICENSE files in the repository root for full details.
         width: 100%;
         flex: 0 0 auto;
         margin-right: 2px;
+        padding-bottom: 1em;
     }
 }
 
diff --git a/res/css/structures/_SpaceHierarchy.pcss b/res/css/structures/_SpaceHierarchy.pcss
index 31dad9413f1193747c1f9da97799474a8016fe17..88c40b6fab0a3091b577f9da9d806028c0869e54 100644
--- a/res/css/structures/_SpaceHierarchy.pcss
+++ b/res/css/structures/_SpaceHierarchy.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -77,7 +77,7 @@ Please see LICENSE files in the repository root for full details.
             height: 16px;
             width: 16px;
             left: 0;
-            background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
+            background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
             background-size: cover;
             background-repeat: no-repeat;
         }
@@ -247,15 +247,6 @@ Please see LICENSE files in the repository root for full details.
                     .mx_AccessibleButton_kind_primary_outline {
                         padding: 3px 16px; /* to account for the 1px border */
                     }
-
-                    .mx_Checkbox {
-                        display: inline-flex;
-
-                        label {
-                            width: 16px;
-                            height: 16px;
-                        }
-                    }
                 }
 
                 &:hover,
@@ -302,7 +293,7 @@ Please see LICENSE files in the repository root for full details.
     > hr {
         border: none;
         height: 1px;
-        background-color: rgba(141, 151, 165, 0.2);
+        background-color: rgb(141, 151, 165, 0.2);
         margin: 20px 0;
     }
 
diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss
index 31dab1ee8ddf1672b658d6950b4acb632efb663b..64044c4c5c13c928a36eac640a5c9944644f88b9 100644
--- a/res/css/structures/_SpacePanel.pcss
+++ b/res/css/structures/_SpacePanel.pcss
@@ -30,6 +30,11 @@ Please see LICENSE files in the repository root for full details.
         width: 68px;
     }
 
+    &.newUi {
+        background-color: var(--cpd-color-bg-canvas-default);
+        border-right: 1px solid var(--cpd-color-bg-subtle-primary);
+    }
+
     .mx_SpacePanel_toggleCollapse {
         position: absolute;
         width: 18px;
@@ -352,9 +357,9 @@ Please see LICENSE files in the repository root for full details.
             mask-image: linear-gradient(
                 to top,
                 transparent,
-                rgba(255, 255, 255, 30%) 4px,
-                rgba(255, 255, 255, 55%) 8px,
-                rgba(255, 255, 255, 75%) 12px,
+                rgb(255, 255, 255, 30%) 4px,
+                rgb(255, 255, 255, 55%) 8px,
+                rgb(255, 255, 255, 75%) 12px,
                 black 16px
             );
         }
@@ -365,13 +370,14 @@ Please see LICENSE files in the repository root for full details.
                Note the top fade is much smaller because the spaces start close to the top,
                so otherwise a large gradient suddenly appears when you scroll down.
              */
-            mask-image: linear-gradient(to bottom, transparent, black 16px),
+            mask-image:
+                linear-gradient(to bottom, transparent, black 16px),
                 linear-gradient(
                     to top,
                     transparent,
-                    rgba(255, 255, 255, 30%) 4px,
-                    rgba(255, 255, 255, 55%) 8px,
-                    rgba(255, 255, 255, 75%) 12px,
+                    rgb(255, 255, 255, 30%) 4px,
+                    rgb(255, 255, 255, 55%) 8px,
+                    rgb(255, 255, 255, 75%) 12px,
                     black 16px
                 );
             mask-position:
@@ -398,6 +404,11 @@ Please see LICENSE files in the repository root for full details.
             display: block;
         }
     }
+
+    &.newUi .mx_UserMenu {
+        margin-top: var(--cpd-space-4x);
+        border-bottom: none;
+    }
 }
 
 .mx_SpacePanel_contextMenu {
diff --git a/res/css/structures/_SplashPage.pcss b/res/css/structures/_SplashPage.pcss
index 6f976ba57531fdcf5b9b1bf9a5a11d1a700ca4ed..bd57fbb48d6a7ad1c1c3b7c5c9aa7c1429fff606 100644
--- a/res/css/structures/_SplashPage.pcss
+++ b/res/css/structures/_SplashPage.pcss
@@ -16,14 +16,15 @@ Please see LICENSE files in the repository root for full details.
         position: absolute;
         z-index: -1;
         opacity: 0.6;
-        background-image: radial-gradient(
+        background-image:
+            radial-gradient(
                 53.85% 66.75% at 87.55% 0%,
-                hsla(250deg, 76%, 71%, 0.261) 0%,
-                hsla(250deg, 100%, 88%, 0) 100%
+                hsl(250deg, 76%, 71%, 0.261) 0%,
+                hsl(250deg, 100%, 88%, 0) 100%
             ),
-            radial-gradient(41.93% 41.93% at 0% 0%, hsla(222deg, 29%, 20%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%),
-            radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.174) 0%, hsla(0deg, 100%, 86%, 0) 100%),
-            radial-gradient(106.35% 96.26% at 100% 0%, hsla(250deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%);
+            radial-gradient(41.93% 41.93% at 0% 0%, hsl(222deg, 29%, 20%, 0.28) 0%, hsl(250deg, 100%, 88%, 0) 100%),
+            radial-gradient(100% 100% at 0% 0%, hsl(250deg, 100%, 88%, 0.174) 0%, hsl(0deg, 100%, 86%, 0) 100%),
+            radial-gradient(106.35% 96.26% at 100% 0%, hsl(250deg, 100%, 88%, 0.4) 0%, hsl(167deg, 76%, 82%, 0) 100%);
         /* blur to reduce color banding issues due to alpha-blending multiple gradients */
         filter: blur(8px);
         inset: -9px;
@@ -33,8 +34,8 @@ Please see LICENSE files in the repository root for full details.
             /* gradient to apply different amounts of dithering to different parts of the gradient */
                 linear-gradient(
                     to bottom,
-                    /* 10% dithering at the top */ rgba(0, 0, 0, 0.9) 20%,
-                    /* 80% dithering at the bottom */ rgba(0, 0, 0, 0.2) 100%
+                    /* 10% dithering at the top */ rgb(0, 0, 0, 0.9) 20%,
+                    /* 80% dithering at the bottom */ rgb(0, 0, 0, 0.2) 100%
                 );
     }
 }
diff --git a/res/css/structures/_ToastContainer.pcss b/res/css/structures/_ToastContainer.pcss
index 6022c01c999227ffb0542d9293eeaf091040642d..cf1d7fd4a9d20e24ff0dde489a6e413f1a5229c0 100644
--- a/res/css/structures/_ToastContainer.pcss
+++ b/res/css/structures/_ToastContainer.pcss
@@ -21,7 +21,7 @@ Please see LICENSE files in the repository root for full details.
         grid-row: 2 / 4;
         grid-column: 1;
         background-color: $system;
-        box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
+        box-shadow: 0px 4px 20px rgb(0, 0, 0, 0.5);
         border-radius: 8px;
     }
 
@@ -30,7 +30,7 @@ Please see LICENSE files in the repository root for full details.
         grid-column: 1;
         background-color: var(--cpd-color-bg-canvas-default);
         color: $primary-content;
-        box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.1);
+        box-shadow: 0px 4px 24px rgb(0, 0, 0, 0.1);
         border: var(--cpd-border-width-1) solid var(--cpd-color-border-interactive-secondary);
         border-radius: 12px;
         overflow: hidden;
@@ -79,6 +79,11 @@ Please see LICENSE files in the repository root for full details.
                 background-color: $primary-content;
             }
 
+            &.mx_Toast_icon_key_storage::after {
+                mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg");
+                background-color: $primary-content;
+            }
+
             &.mx_Toast_icon_labs::after {
                 mask-image: url("$(res)/img/element-icons/flask.svg");
                 background-color: $secondary-content;
diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss
index 50e93d236934bf566f8c4c695e369fd3d8c0f030..2f4099c893adb6e67f9c0b40d7b630d70fec7603 100644
--- a/res/css/structures/_UserMenu.pcss
+++ b/res/css/structures/_UserMenu.pcss
@@ -37,27 +37,6 @@ Please see LICENSE files in the repository root for full details.
         line-height: $font-24px;
         margin-left: 10px;
     }
-
-    .mx_UserMenu_dndBadge {
-        position: absolute;
-        bottom: -2px;
-        right: -7px;
-        width: 16px;
-        height: 16px;
-        border-radius: 50%;
-
-        &::before {
-            content: "";
-            width: 16px;
-            height: 16px;
-            position: absolute;
-            mask-position: center;
-            mask-size: contain;
-            mask-repeat: no-repeat;
-            background-color: $alert;
-            mask-image: url("$(res)/img/element-icons/roomlist/dnd.svg");
-        }
-    }
 }
 
 .mx_IconizedContextMenu {
@@ -158,14 +137,6 @@ Please see LICENSE files in the repository root for full details.
         mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg");
     }
 
-    .mx_UserMenu_iconDnd::before {
-        mask-image: url("$(res)/img/element-icons/roomlist/dnd.svg");
-    }
-
-    .mx_UserMenu_iconDndOff::before {
-        mask-image: url("$(res)/img/element-icons/roomlist/dnd-cross.svg");
-    }
-
     .mx_UserMenu_iconBell::before {
         mask-image: url("$(res)/img/element-icons/notifications.svg");
     }
diff --git a/res/css/structures/auth/_Registration.pcss b/res/css/structures/auth/_Registration.pcss
index 409767e1da7071079a3197201111187b96ab1ea2..a1833df1d0346d75212345b8cf765b5ee88b50f9 100644
--- a/res/css/structures/auth/_Registration.pcss
+++ b/res/css/structures/auth/_Registration.pcss
@@ -37,7 +37,7 @@ Please see LICENSE files in the repository root for full details.
     justify-content: space-between;
     padding-top: 16px;
     margin-top: 16px;
-    border-top: 1px solid rgba(141, 151, 165, 0.2);
+    border-top: 1px solid rgb(141, 151, 165, 0.2);
 
     > * {
         flex-basis: content;
diff --git a/res/css/views/auth/_AuthFooter.pcss b/res/css/views/auth/_AuthFooter.pcss
index 96b41406ad3c9b05655c294e5a7174457ad957a1..57545e7691d55a5552db62ffc594d0ab7930da80 100644
--- a/res/css/views/auth/_AuthFooter.pcss
+++ b/res/css/views/auth/_AuthFooter.pcss
@@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
     font: var(--cpd-font-body-md-regular);
     opacity: 0.72;
     padding: 20px 0;
-    background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8));
+    background: linear-gradient(rgb(0, 0, 0, 0), rgb(0, 0, 0, 0.8));
 }
 
 .mx_AuthFooter a:link,
diff --git a/res/css/views/auth/_AuthPage.pcss b/res/css/views/auth/_AuthPage.pcss
index 469bca505393ca384ea4def2e0c3fbd22e370fe9..3ae17122be12fb1cc4ab749de0e55975bb78d2b4 100644
--- a/res/css/views/auth/_AuthPage.pcss
+++ b/res/css/views/auth/_AuthPage.pcss
@@ -19,7 +19,7 @@ Please see LICENSE files in the repository root for full details.
     display: flex;
     margin: 100px auto auto;
     border-radius: 4px;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.33);
+    box-shadow: 0 2px 4px 0 rgb(0, 0, 0, 0.33);
     background-color: $authpage-modal-bg-color;
 
     @media only screen and (max-height: 768px) {
diff --git a/res/css/views/avatars/_RoomAvatarView.pcss b/res/css/views/avatars/_RoomAvatarView.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..0d5523a9f12b58dc35f54ceb690834eb9d58a23c
--- /dev/null
+++ b/res/css/views/avatars/_RoomAvatarView.pcss
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomAvatarView {
+    --room-avatar-size: 32px;
+
+    position: relative;
+
+    /* Keep the container to the same size than the avatar */
+    inline-size: var(--room-avatar-size);
+    block-size: var(--room-avatar-size);
+
+    .mx_RoomAvatarView_RoomAvatar {
+        mask-position: center;
+        mask-size: contain;
+        mask-repeat: no-repeat;
+    }
+
+    .mx_RoomAvatarView_RoomAvatar_icon {
+        mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-icon-mask.svg");
+    }
+
+    .mx_RoomAvatarView_RoomAvatar_presence {
+        mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-presence-mask.svg");
+    }
+
+    .mx_RoomAvatarView_icon {
+        position: absolute;
+
+        /* Place half the icon inside the avatar */
+        /* Avatar size - (icon size (16px) / 2) */
+        left: calc((var(--room-avatar-size) - 8px));
+        bottom: var(--cpd-space-0-5x);
+    }
+
+    .mx_RoomAvatarView_PresenceDecoration {
+        position: absolute;
+
+        /* Place half the icon inside the avatar */
+        /* Avatar size - (icon size (8px) / 2) */
+        left: calc((var(--room-avatar-size) - 4px));
+        bottom: var(--cpd-space-0-5x);
+    }
+}
diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss
index f365c4a293cc079bcb846bec2b1431935be7c47e..9fc454f32861c8c95cd048ab9b4225db26b6879b 100644
--- a/res/css/views/context_menus/_MessageContextMenu.pcss
+++ b/res/css/views/context_menus/_MessageContextMenu.pcss
@@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details.
     }
 
     .mx_MessageContextMenu_iconReport::before {
-        mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
+        mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
     }
 
     .mx_MessageContextMenu_iconLink::before {
diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss
index 7dc25d6811319af4753bc07fb885c5740e7f5ce7..cd5cff94a04f76989cbf135bb9852b8c70e584e7 100644
--- a/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss
+++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -32,6 +32,11 @@ Please see LICENSE files in the repository root for full details.
     .mx_AddExistingToSpace_section {
         margin-right: 12px;
 
+        ul {
+            list-style: none;
+            padding-left: 0;
+        }
+
         // provides space for scrollbar so that checkbox and scrollbar do not collide
 
         &:not(:first-child) {
@@ -214,6 +219,12 @@ Please see LICENSE files in the repository root for full details.
     display: flex;
     margin-top: 12px;
 
+    form {
+        /* Align checkboxes. */
+        margin-top: auto;
+        margin-bottom: auto;
+    }
+
     .mx_DecoratedRoomAvatar, /* we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling */ {
         margin-right: 12px;
     }
@@ -227,8 +238,4 @@ Please see LICENSE files in the repository root for full details.
         text-overflow: ellipsis;
         margin-right: 12px;
     }
-
-    .mx_Checkbox {
-        align-items: center;
-    }
 }
diff --git a/res/css/views/dialogs/_BulkRedactDialog.pcss b/res/css/views/dialogs/_BulkRedactDialog.pcss
deleted file mode 100644
index 3e274798eba064f9f6aca7c303e0de05ecb50de2..0000000000000000000000000000000000000000
--- a/res/css/views/dialogs/_BulkRedactDialog.pcss
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2021 Robin Townsend <robin@robin.town>
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_BulkRedactDialog {
-    .mx_Checkbox,
-    .mx_BulkRedactDialog_checkboxMicrocopy {
-        line-height: $font-20px;
-    }
-
-    .mx_BulkRedactDialog_checkboxMicrocopy {
-        margin-left: 26px;
-        color: $secondary-content;
-    }
-}
diff --git a/res/css/views/dialogs/_CompoundDialog.pcss b/res/css/views/dialogs/_CompoundDialog.pcss
index b43fa1f14e858981d6a1007965678af675b0e0c0..1c3ba01f1c9aaf4cf1dcc576baae2d1cbc8a9a88 100644
--- a/res/css/views/dialogs/_CompoundDialog.pcss
+++ b/res/css/views/dialogs/_CompoundDialog.pcss
@@ -77,7 +77,7 @@ Please see LICENSE files in the repository root for full details.
     max-height: 80%;
 
     .mx_CompoundDialog_footer {
-        box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.05); /* hardcoded colour for both themes */
+        box-shadow: 0px -4px 4px rgb(0, 0, 0, 0.05); /* hardcoded colour for both themes */
         z-index: 1; /* needed to make footer & shadow appear above dialog content */
     }
 }
diff --git a/res/css/views/dialogs/_ConfirmKeyStorageOffDialog.pcss b/res/css/views/dialogs/_ConfirmKeyStorageOffDialog.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..5ac53c7b706fd080c970dece309bc753e4045b18
--- /dev/null
+++ b/res/css/views/dialogs/_ConfirmKeyStorageOffDialog.pcss
@@ -0,0 +1,16 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+.mx_ConfirmKeyStorageOffDialog {
+    .mx_Dialog_border {
+        width: 600px;
+    }
+
+    .mx_EncryptionCard {
+        text-align: center;
+    }
+}
diff --git a/res/css/views/dialogs/_Crypto.pcss b/res/css/views/dialogs/_Crypto.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..12d46cf75b28e9778f246083265130dcd590a05d
--- /dev/null
+++ b/res/css/views/dialogs/_Crypto.pcss
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_Crypto {
+    table {
+        margin: var(--cpd-space-4x) 0;
+        text-align: left;
+        border-spacing: var(--cpd-space-2x) 0;
+
+        thead {
+            font: var(--cpd-font-heading-sm-semibold);
+        }
+    }
+}
diff --git a/res/css/views/dialogs/_ExportDialog.pcss b/res/css/views/dialogs/_ExportDialog.pcss
index 46c0c578c6543ad2f1c5fcf769b7ed28d3206b53..cda2fab55febcbe05ab05d3ed71ce6a9e735685c 100644
--- a/res/css/views/dialogs/_ExportDialog.pcss
+++ b/res/css/views/dialogs/_ExportDialog.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -43,11 +43,6 @@ Please see LICENSE files in the repository root for full details.
         .mx_Field_valid.mx_Field:focus-within {
             border-color: $input-border-color;
         }
-
-        .mx_Checkbox input[type="checkbox"]:checked + label > .mx_Checkbox_background {
-            background: $info-plinth-fg-color;
-            border-color: $info-plinth-fg-color;
-        }
     }
 
     .mx_ExportDialog_progress {
diff --git a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss
index f77ebf7b6b9da712e69c67857933827fdf06e963..ed51968dec7e041358096ec9d303601c6f1f0ee8 100644
--- a/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss
+++ b/res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -74,10 +74,6 @@ Please see LICENSE files in the repository root for full details.
                 line-height: $font-15px;
                 color: $tertiary-content;
             }
-
-            .mx_Checkbox {
-                align-items: center;
-            }
         }
     }
 
diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss
index 8105833830c16b698dae66007bdfb9b38ab1c91a..5d06545ae802816ed5d8466e1041920be96fbaa7 100644
--- a/res/css/views/dialogs/_MessageEditHistoryDialog.pcss
+++ b/res/css/views/dialogs/_MessageEditHistoryDialog.pcss
@@ -34,13 +34,13 @@ Please see LICENSE files in the repository root for full details.
 
     .mx_EditHistoryMessage_deletion {
         color: rgb(255, 76, 85);
-        background-color: rgba(255, 76, 85, 0.1);
+        background-color: rgb(255, 76, 85, 0.1);
         text-decoration: line-through;
     }
 
     .mx_EditHistoryMessage_insertion {
         color: rgb(26, 169, 123);
-        background-color: rgba(26, 169, 123, 0.1);
+        background-color: rgb(26, 169, 123, 0.1);
         text-decoration: underline;
     }
 
diff --git a/res/css/views/dialogs/_ReportRoomDialog.pcss b/res/css/views/dialogs/_ReportRoomDialog.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..b656638d9c3328ebfa0271cf1d48f03e8d10ca19
--- /dev/null
+++ b/res/css/views/dialogs/_ReportRoomDialog.pcss
@@ -0,0 +1,41 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+.mx_ReportRoomDialog,
+.mx_DeclineAndBlockInviteDialog {
+    textarea {
+        font: var(--cpd-font-body-md-regular);
+        border: 1px solid var(--cpd-color-border-interactive-primary);
+        background: var(--cpd-color-bg-canvas-default);
+        border-radius: 0.5rem;
+        padding: var(--cpd-space-3x) var(--cpd-space-4x);
+    }
+
+    /*
+      Workaround to fix labels appearing with the wrong color.
+
+      .mx_Dialog (in res/css/_common.pcss) redefines the body color
+      as $light-fg-color rather than the standard primary color.
+
+      This forces the colour to match the Compound style, but
+      in the future the Dialogs should not force a color.
+    */
+    form label {
+        color: var(--cpd-color-text-primary);
+    }
+}
+
+.mx_DeclineAndBlockInviteDialog {
+    div[aria-disabled="true"] > label {
+        color: var(--cpd-color-text-secondary);
+    }
+
+    .mx_SettingsFlag_label {
+        color: var(--cpd-color-text-primary);
+        font-weight: var(--cpd-font-weight-semibold);
+    }
+}
diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss
index d00acd6786d92f8335d33a0fd4b7d96efd3f7f71..592431c2f19ab930899b5b4f3187431e9887b33d 100644
--- a/res/css/views/dialogs/_SpotlightDialog.pcss
+++ b/res/css/views/dialogs/_SpotlightDialog.pcss
@@ -412,7 +412,8 @@ Please see LICENSE files in the repository root for full details.
             .mx_SpotlightDialog_joinRoomAlias,
             .mx_SpotlightDialog_explorePublicRooms,
             .mx_SpotlightDialog_explorePublicSpaces,
-            .mx_SpotlightDialog_startGroupChat {
+            .mx_SpotlightDialog_startGroupChat,
+            .mx_SpotlightDialog_searchMessages {
                 padding-left: $spacing-32;
                 position: relative;
 
@@ -451,22 +452,14 @@ Please see LICENSE files in the repository root for full details.
                 mask-image: url("$(res)/img/element-icons/group-members.svg");
             }
 
+            .mx_SpotlightDialog_searchMessages::before {
+                mask-image: url("$(res)/img/element-icons/room/search-inset.svg");
+            }
+
             .mx_SpotlightDialog_otherSearches_messageSearchText {
                 font-size: $font-15px;
                 line-height: $font-24px;
             }
-
-            .mx_SpotlightDialog_otherSearches_messageSearchIcon {
-                display: inline-block;
-                width: 24px;
-                height: 24px;
-                background-color: $secondary-content;
-                vertical-align: text-bottom;
-                mask-repeat: no-repeat;
-                mask-position: center;
-                mask-size: contain;
-                mask-image: url("$(res)/img/element-icons/room/search-inset.svg");
-            }
         }
 
         .mx_SpotlightDialog_result_details {
diff --git a/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.pcss b/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.pcss
index 9e1024b9a49aa18abd1c28fa24bcddc5df89395e..62805a24ae53916e9cc2f49624eaefc720568094 100644
--- a/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.pcss
+++ b/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2020 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -19,13 +19,6 @@ Please see LICENSE files in the repository root for full details.
         margin-top: 20px;
         font-size: $font-15px;
         line-height: $font-15px;
-
-        .mx_WidgetCapabilitiesPromptDialog_byline {
-            color: $muted-fg-color;
-            margin-left: 26px;
-            font-size: $font-12px;
-            line-height: $font-12px;
-        }
     }
 
     .mx_Dialog_buttons {
diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss
index da71b4462b044af989106f0ca461d6a4c9feaf53..943ec3a41fe73449d125374180d203de82282e69 100644
--- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss
+++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss
@@ -7,62 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 .mx_AccessSecretStorageDialog {
-    .mx_AccessSecretStorageDialog_titleWithIcon {
-        &::before {
-            content: "";
-            display: inline-block;
-            width: 24px;
-            height: 24px;
-            margin-inline-end: $spacing-8;
-            position: relative;
-            top: 5px;
-            background-color: $primary-content;
-        }
-
-        &.mx_AccessSecretStorageDialog_resetBadge::before {
-            /* The image isn't capable of masking, so we use a background instead. */
-            background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
-            background-size: 24px;
-            background-color: transparent;
-        }
-
-        &.mx_AccessSecretStorageDialog_secureBackupTitle::before {
-            mask-image: url("$(res)/img/feather-customised/secure-backup.svg");
-        }
-
-        &.mx_AccessSecretStorageDialog_securePhraseTitle::before {
-            mask-image: url("$(res)/img/feather-customised/secure-phrase.svg");
-        }
+    &.mx_EncryptionCard {
+        /* override some styles that we don't need */
+        border: 0px none;
+        box-shadow: none;
+        padding: 0px;
     }
 
     .mx_AccessSecretStorageDialog_primaryContainer {
-        .mx_AccessSecretStorageDialog_passPhraseInput {
-            width: 300px;
-            border: 1px solid $accent;
-            border-radius: 5px;
-        }
-
-        .mx_AccessSecretStorageDialog_keyStatus {
-            height: 30px;
-        }
-
-        .mx_AccessSecretStorageDialog_recoveryKeyEntry {
-            display: flex;
-            align-items: center;
-
-            .mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput {
-                flex-grow: 1;
-            }
-
-            .mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText {
-                margin: $spacing-16;
-            }
-
-            .mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput {
-                display: none;
-            }
-        }
-
         .mx_AccessSecretStorageDialog_recoveryKeyFeedback {
             &::before {
                 content: "";
@@ -76,15 +28,6 @@ Please see LICENSE files in the repository root for full details.
                 margin-inline-end: 5px;
             }
 
-            &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--valid {
-                color: $accent;
-
-                &::before {
-                    mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
-                    background-color: $accent;
-                }
-            }
-
             &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid {
                 color: $alert;
 
@@ -94,46 +37,9 @@ Please see LICENSE files in the repository root for full details.
                 }
             }
         }
+    }
 
-        .mx_Dialog_buttons {
-            $spacingStart: $spacing-24; /* 16px icon + 8px padding */
-
-            text-align: initial;
-            display: flex;
-            flex-flow: column;
-            gap: 14px;
-
-            .mx_Dialog_buttons_additive {
-                float: none;
-
-                .mx_AccessSecretStorageDialog_reset {
-                    position: relative;
-                    padding-inline-start: $spacingStart;
-                    /* To avoid bold styling inherent with <strong> elements */
-                    font-weight: inherit;
-
-                    &::before {
-                        content: "";
-                        display: inline-block;
-                        position: absolute;
-                        height: 16px;
-                        width: 16px;
-                        left: 0;
-                        top: 2px; /* alignment */
-                        background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
-                        background-size: contain;
-                    }
-
-                    .mx_AccessSecretStorageDialog_reset_link {
-                        color: $alert;
-                    }
-                }
-            }
-
-            .mx_Dialog_buttons_row {
-                gap: $spacing-16; /* TODO: needs normalization */
-                padding-inline-start: $spacingStart;
-            }
-        }
+    .mx_EncryptionCard_buttons {
+        margin-top: var(--cpd-space-20x);
     }
 }
diff --git a/res/css/views/elements/_InfoTooltip.pcss b/res/css/views/elements/_InfoTooltip.pcss
index 5229b7d9f59b66d4ec09cc977cf0f6c0dd7d041b..a214f0bf835b783a67058f84667c13f89b573b4e 100644
--- a/res/css/views/elements/_InfoTooltip.pcss
+++ b/res/css/views/elements/_InfoTooltip.pcss
@@ -29,5 +29,5 @@ Please see LICENSE files in the repository root for full details.
 }
 
 .mx_InfoTooltip_icon_warning::before {
-    mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
+    mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
 }
diff --git a/res/css/views/elements/_LabelledCheckbox.pcss b/res/css/views/elements/_LabelledCheckbox.pcss
index add8f14998c295d23bcf0c4ccb53579c26186376..ffbf749dabb448776ad2867437ac3dd685ed5c5e 100644
--- a/res/css/views/elements/_LabelledCheckbox.pcss
+++ b/res/css/views/elements/_LabelledCheckbox.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,26 +7,5 @@ Please see LICENSE files in the repository root for full details.
 */
 
 .mx_LabelledCheckbox {
-    display: flex;
-    gap: 8px;
-    flex-direction: row;
-
-    .mx_Checkbox {
-        margin-top: 3px; /* visually align with label text */
-    }
-
-    .mx_LabelledCheckbox_labels {
-        flex: 1;
-
-        .mx_LabelledCheckbox_label {
-            vertical-align: middle;
-        }
-
-        .mx_LabelledCheckbox_byline {
-            display: block;
-            padding-top: $spacing-4;
-            color: $muted-fg-color;
-            font-size: $font-11px;
-        }
-    }
+    margin-top: var(--cpd-space-2x);
 }
diff --git a/res/css/views/elements/_Pill.pcss b/res/css/views/elements/_Pill.pcss
index 055a524c5a157fbb234dfc9a03b65933f604f831..d692f812a4e19ec3830f7450befb25e5277fea12 100644
--- a/res/css/views/elements/_Pill.pcss
+++ b/res/css/views/elements/_Pill.pcss
@@ -26,7 +26,8 @@ Please see LICENSE files in the repository root for full details.
     }
 
     &.mx_UserPill_me,
-    &.mx_AtRoomPill {
+    &.mx_AtRoomPill,
+    &.mx_KeywordPill {
         background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
     }
 
@@ -45,7 +46,8 @@ Please see LICENSE files in the repository root for full details.
     }
 
     /* We don't want to indicate clickability */
-    &.mx_AtRoomPill:hover {
+    &.mx_AtRoomPill:hover,
+    &.mx_KeywordPill:hover {
         background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
         cursor: unset;
     }
diff --git a/res/css/views/elements/_ServerPicker.pcss b/res/css/views/elements/_ServerPicker.pcss
index fa0b46599fae4885eeed835f8f1d347cb70cad87..aced0976ce823dc90cb8b53f7459855cbc1a2326 100644
--- a/res/css/views/elements/_ServerPicker.pcss
+++ b/res/css/views/elements/_ServerPicker.pcss
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 .mx_ServerPicker {
     margin-bottom: 14px;
     padding-bottom: $spacing-16;
-    border-bottom: 1px solid rgba(141, 151, 165, 0.2);
+    border-bottom: 1px solid rgb(141, 151, 165, 0.2);
     display: grid;
     grid-template-columns: auto min-content;
     grid-template-rows: auto auto auto;
diff --git a/res/css/views/elements/_StyledCheckbox.pcss b/res/css/views/elements/_StyledCheckbox.pcss
deleted file mode 100644
index 77382d711c97f09554efe44bd0967402568f9835..0000000000000000000000000000000000000000
--- a/res/css/views/elements/_StyledCheckbox.pcss
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_Checkbox {
-    $size: $font-16px;
-    $border-radius: 0.27rem;
-
-    display: flex;
-    align-items: flex-start;
-
-    input[type="checkbox"] {
-        appearance: none;
-        margin: 0;
-        padding: 0;
-
-        & + label {
-            display: flex;
-            align-items: center;
-
-            flex-grow: 1;
-        }
-
-        & + label > .mx_Checkbox_background {
-            display: inline-flex;
-            position: relative;
-
-            flex-shrink: 0;
-
-            height: $size;
-            width: $size;
-            size: 0.5rem;
-            border: 1px solid var(--cpd-color-border-interactive-primary);
-            box-sizing: border-box;
-            border-radius: $border-radius;
-
-            .mx_Checkbox_checkmark {
-                display: none;
-
-                height: 100%;
-                width: 100%;
-                mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
-                mask-position: center;
-                mask-size: 100%;
-                mask-repeat: no-repeat;
-            }
-        }
-
-        &:checked + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
-            display: block;
-        }
-
-        & + label > *:not(.mx_Checkbox_background) {
-            margin-left: 10px;
-        }
-
-        &:disabled + label {
-            cursor: not-allowed;
-        }
-
-        &:focus-visible {
-            & + label .mx_Checkbox_background {
-                @mixin unreal-focus;
-            }
-        }
-    }
-}
-
-.mx_Checkbox.mx_Checkbox_kind_solid input[type="checkbox"] {
-    & + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
-        background: var(--cpd-color-icon-on-solid-primary);
-    }
-
-    &:checked + label > .mx_Checkbox_background {
-        background: var(--cpd-color-bg-accent-rest);
-        border-color: var(--cpd-color-bg-accent-rest);
-    }
-
-    &:checked:disabled + label > .mx_Checkbox_background {
-        background: var(--cpd-color-bg-action-primary-disabled);
-        border-color: var(--cpd-color-bg-action-primary-disabled);
-    }
-}
-
-.mx_Checkbox.mx_Checkbox_kind_outline input[type="checkbox"] {
-    & + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
-        background: var(--cpd-color-bg-accent-rest);
-    }
-
-    &:checked + label > .mx_Checkbox_background {
-        background: transparent;
-        border-color: var(--cpd-color-bg-accent-rest);
-    }
-}
diff --git a/res/css/views/emojipicker/_EmojiPicker.pcss b/res/css/views/emojipicker/_EmojiPicker.pcss
index c1a666a672e32282f093dd451cdcd5978df0b43d..754242460558e1e593147f57658641b8899eb038 100644
--- a/res/css/views/emojipicker/_EmojiPicker.pcss
+++ b/res/css/views/emojipicker/_EmojiPicker.pcss
@@ -20,7 +20,7 @@ Please see LICENSE files in the repository root for full details.
     flex: 1;
     overflow-y: scroll;
     scrollbar-width: thin;
-    scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
+    scrollbar-color: rgb(0, 0, 0, 0.2) transparent;
 }
 
 .mx_EmojiPicker_header {
@@ -201,7 +201,7 @@ Please see LICENSE files in the repository root for full details.
 }
 
 .mx_EmojiPicker_item_selected {
-    color: rgba(0, 0, 0, 0.5);
+    color: rgb(0, 0, 0, 0.5);
     border: 1px solid $accent;
     padding: 4px;
 }
diff --git a/res/css/views/location/_LocationPicker.pcss b/res/css/views/location/_LocationPicker.pcss
index 9384411cf804aefa94902fc63d89b79065306095..c103f7d68a6748e97455a80b0d77a35003974aeb 100644
--- a/res/css/views/location/_LocationPicker.pcss
+++ b/res/css/views/location/_LocationPicker.pcss
@@ -76,7 +76,7 @@ Please see LICENSE files in the repository root for full details.
     pointer-events: none;
 
     span {
-        box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.15);
+        box-shadow: 0px 4px 15px rgb(0, 0, 0, 0.15);
         border-radius: 8px;
         padding: $spacing-8;
         background-color: $background;
diff --git a/res/css/views/messages/_DisambiguatedProfile.pcss b/res/css/views/messages/_DisambiguatedProfile.pcss
index 3f10c07abe33c788f15f29a30341cb20f54ce35f..4758bb540728fc4bea637c2eead5edc3fcc42f79 100644
--- a/res/css/views/messages/_DisambiguatedProfile.pcss
+++ b/res/css/views/messages/_DisambiguatedProfile.pcss
@@ -35,6 +35,8 @@ Please see LICENSE files in the repository root for full details.
     .mx_DisambiguatedProfile_mxid {
         margin-inline-start: 0;
         font: var(--cpd-font-body-sm-regular);
+        text-overflow: ellipsis;
+        overflow: hidden;
     }
 
     span:not(.mx_DisambiguatedProfile_mxid) {
diff --git a/res/css/views/messages/_HiddenMediaPlaceholder.pcss b/res/css/views/messages/_HiddenMediaPlaceholder.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..c7efe6ec7e12cb42aec7e53a46bc0599d057131d
--- /dev/null
+++ b/res/css/views/messages/_HiddenMediaPlaceholder.pcss
@@ -0,0 +1,29 @@
+.mx_HiddenMediaPlaceholder {
+    border: none;
+    width: 100%;
+    height: 100%;
+    inset: 0;
+
+    /* To center the text in the middle of the frame */
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+
+    cursor: pointer;
+    background-color: $header-panel-bg-color;
+
+    > div {
+        color: $accent;
+        /* Icon alignment */
+        display: flex;
+        > svg {
+            margin-top: auto;
+            margin-bottom: auto;
+        }
+    }
+}
+
+.mx_EventTile:hover .mx_HiddenMediaPlaceholder {
+    background-color: $background;
+}
diff --git a/res/css/views/messages/_MImageBody.pcss b/res/css/views/messages/_MImageBody.pcss
index 5942bb44b3deb3fd7fc4b7fe8685675e444fc442..0e73c1d55c1d7cca53bc6013edc3f7f189d3e38f 100644
--- a/res/css/views/messages/_MImageBody.pcss
+++ b/res/css/views/messages/_MImageBody.pcss
@@ -25,7 +25,7 @@ Please see LICENSE files in the repository root for full details.
     overflow: hidden;
 
     /* Hardcoded colours because it's the same on all themes */
-    background-color: rgba(0, 0, 0, 0.6);
+    background-color: rgb(0, 0, 0, 0.6);
     color: #ffffff;
 }
 
@@ -79,39 +79,3 @@ Please see LICENSE files in the repository root for full details.
     color: $imagebody-giflabel-color;
     pointer-events: none;
 }
-
-.mx_HiddenImagePlaceholder {
-    position: absolute;
-    inset: 0;
-
-    /* To center the text in the middle of the frame */
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    text-align: center;
-
-    cursor: pointer;
-    background-color: $header-panel-bg-color;
-
-    .mx_HiddenImagePlaceholder_button {
-        color: $accent;
-
-        span.mx_HiddenImagePlaceholder_eye {
-            margin-right: 8px;
-
-            background-color: $accent;
-            mask-image: url("$(res)/img/element-icons/eye.svg");
-            display: inline-block;
-            width: 18px;
-            height: 14px;
-        }
-
-        span:not(.mx_HiddenImagePlaceholder_eye) {
-            vertical-align: text-bottom;
-        }
-    }
-}
-
-.mx_EventTile:hover .mx_HiddenImagePlaceholder {
-    background-color: $background;
-}
diff --git a/res/css/views/right_panel/_BaseCard.pcss b/res/css/views/right_panel/_BaseCard.pcss
index 83e1fc28d4889bf9eb1d0e7224b0c67af55c8009..035485428a57c6ef2acac5e1ce64475c2dbb6399 100644
--- a/res/css/views/right_panel/_BaseCard.pcss
+++ b/res/css/views/right_panel/_BaseCard.pcss
@@ -99,7 +99,7 @@ Please see LICENSE files in the repository root for full details.
 
         .mx_AccessibleButton_kind_secondary {
             color: $secondary-content;
-            background-color: rgba(141, 151, 165, 0.2);
+            background-color: rgb(141, 151, 165, 0.2);
             font: var(--cpd-font-body-md-semibold);
         }
 
@@ -125,7 +125,7 @@ Please see LICENSE files in the repository root for full details.
         padding-bottom: 10px;
 
         border: var(--cpd-border-width-1) solid var(--cpd-color-border-interactive-secondary);
-        box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.1);
+        box-shadow: 0px 4px 24px rgb(0, 0, 0, 0.1);
     }
 
     .mx_ContextualMenu_chevron_top {
diff --git a/res/css/views/right_panel/_EmptyState.pcss b/res/css/views/right_panel/_EmptyState.pcss
index 05bc2882b8daec7ebf2c4f93d9e2a1fbb3c91e55..15e78414c9f9eb74bdc8c1a1c9a7656143bcbc3a 100644
--- a/res/css/views/right_panel/_EmptyState.pcss
+++ b/res/css/views/right_panel/_EmptyState.pcss
@@ -30,7 +30,7 @@ Please see LICENSE files in the repository root for full details.
         height: 775px;
         right: -253.77px;
         top: 0;
-        background: radial-gradient(49.95% 49.95% at 50% 50%, rgba(13, 189, 139, 0.12) 0%, rgba(18, 115, 235, 0) 100%);
+        background: radial-gradient(49.95% 49.95% at 50% 50%, rgb(13, 189, 139, 0.12) 0%, rgb(18, 115, 235, 0) 100%);
         transform: rotate(-89.69deg);
         overflow: hidden;
     }
diff --git a/res/css/views/right_panel/_ExtensionsCard.pcss b/res/css/views/right_panel/_ExtensionsCard.pcss
index 65dc23f66715adcf7113905c8002ca2717df65ab..c98fa3e9dcf18028b19bf1b11759f6cbc3c22ed1 100644
--- a/res/css/views/right_panel/_ExtensionsCard.pcss
+++ b/res/css/views/right_panel/_ExtensionsCard.pcss
@@ -7,12 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 .mx_ExtensionsCard {
-    --cpd-separator-inset: var(--cpd-space-4x);
-    --cpd-separator-spacing: var(--cpd-space-4x);
-
+    --cpd-separator-spacing: var(--cpd-space-6x);
+    --AddExtension-overlap: -76px;
     .mx_AutoHideScrollbar {
         padding: 0 var(--cpd-space-4x);
-        margin-top: var(--cpd-space-3x);
+        margin-top: var(--cpd-space-6x);
         box-sizing: border-box;
 
         /* Styling for the "Add extensions" button */
@@ -70,7 +69,7 @@ Please see LICENSE files in the repository root for full details.
                     top: var(--cpd-space-2x); /* equal to padding-top of parent */
                     left: 0;
                     border-radius: 12px;
-                    background-color: rgba(141, 151, 165, 0.1);
+                    background-color: rgb(141, 151, 165, 0.1);
                 }
             }
 
@@ -128,6 +127,11 @@ Please see LICENSE files in the repository root for full details.
 
     .mx_EmptyState::before {
         /* Overlap the Add extensions button */
-        top: -76px;
+        top: var(--AddExtension-overlap);
+    }
+
+    .mx_EmptyState {
+        /* Stop empty state scrolling */
+        height: calc(100% + var(--AddExtension-overlap));
     }
 }
diff --git a/res/css/views/right_panel/_PinnedMessagesCard.pcss b/res/css/views/right_panel/_PinnedMessagesCard.pcss
index 6a15653398067657a0625b625805c30d7e0c7087..1b73787f221860a828810c3c556539111e78d16b 100644
--- a/res/css/views/right_panel/_PinnedMessagesCard.pcss
+++ b/res/css/views/right_panel/_PinnedMessagesCard.pcss
@@ -37,7 +37,7 @@ Please see LICENSE files in the repository root for full details.
         display: flex;
         justify-content: center;
         align-items: center;
-        box-shadow: 0 4px 24px 0 rgba(27, 29, 34, 0.1);
+        box-shadow: 0 4px 24px 0 rgb(27, 29, 34, 0.1);
         background: var(--cpd-color-bg-canvas-default);
     }
 
diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss
index fb1d6f7d9cdc643a0a97d0ed74702912225ebc2e..3c1c07677c7919772b6e1ab198d82b1922278c51 100644
--- a/res/css/views/right_panel/_RoomSummaryCard.pcss
+++ b/res/css/views/right_panel/_RoomSummaryCard.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2020 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -100,3 +100,7 @@ Please see LICENSE files in the repository root for full details.
 .mx_RoomSummaryCard_roomName {
     margin: $spacing-12 0 $spacing-4;
 }
+
+.mx_RoomSummaryCard_bottomOptions {
+    margin: 0 0 var(--cpd-space-8x);
+}
diff --git a/res/css/views/right_panel/_ThreadPanel.pcss b/res/css/views/right_panel/_ThreadPanel.pcss
index b21eb17f03356173750aa64708cbb50e38a46b96..2fe1a8690871177ae3543b3362b806387be7e2a9 100644
--- a/res/css/views/right_panel/_ThreadPanel.pcss
+++ b/res/css/views/right_panel/_ThreadPanel.pcss
@@ -15,41 +15,48 @@ Please see LICENSE files in the repository root for full details.
         flex: unset;
     }
 
-    .mx_BaseCard_header {
-        .mx_BaseCard_header_title {
-            .mx_AccessibleButton {
-                font-size: 12px;
-                color: $secondary-content;
-            }
+    .mx_ThreadPanelHeader {
+        height: 60px;
+        display: flex;
+        box-sizing: border-box;
+        padding: 16px;
+        align-items: center;
+        border-bottom: 1px solid var(--cpd-color-gray-400);
+
+        .mx_AccessibleButton {
+            font-size: 12px;
+            color: $secondary-content;
+        }
 
-            .mx_ThreadPanel_vertical_separator {
-                height: 16px;
-                margin-left: var(--cpd-space-3x);
-                margin-right: var(--cpd-space-1x);
-                border-left: 1px solid var(--cpd-color-gray-400);
+        .mx_ThreadPanel_vertical_separator {
+            height: 28px;
+            margin-left: var(--cpd-space-3x);
+            margin-right: var(--cpd-space-2x);
+            border-left: 1px solid var(--cpd-color-gray-400);
+        }
+
+        .mx_ThreadPanel_dropdown {
+            font: var(--cpd-font-body-sm-regular);
+            padding: 3px $spacing-4 3px $spacing-8;
+            border-radius: 4px;
+            line-height: 1.5;
+            user-select: none;
+
+            &:hover,
+            &[aria-expanded="true"] {
+                background: $quinary-content;
             }
 
-            .mx_ThreadPanel_dropdown {
-                padding: 3px $spacing-4 3px $spacing-8;
-                border-radius: 4px;
-                line-height: 1.5;
-                user-select: none;
-
-                &:hover,
-                &[aria-expanded="true"] {
-                    background: $quinary-content;
-                }
-
-                &::before {
-                    content: "";
-                    width: 18px;
-                    height: 18px;
-                    background: currentColor;
-                    mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
-                    mask-size: 100%;
-                    mask-repeat: no-repeat;
-                    float: right;
-                }
+            &::before {
+                margin-left: 2px;
+                content: "";
+                width: 20px;
+                height: 20px;
+                background: currentColor;
+                mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
+                mask-size: 100%;
+                mask-repeat: no-repeat;
+                float: right;
             }
         }
     }
diff --git a/res/css/views/right_panel/_UserInfo.pcss b/res/css/views/right_panel/_UserInfo.pcss
index 0af5585d1a2b18298bb962cdbbf3ae5c32779b57..3030b93c0307235442923aeb51d93bef78fcea71 100644
--- a/res/css/views/right_panel/_UserInfo.pcss
+++ b/res/css/views/right_panel/_UserInfo.pcss
@@ -34,13 +34,9 @@ Please see LICENSE files in the repository root for full details.
     }
 
     .mx_UserInfo_container {
-        padding: var(--cpd-space-4x) 0;
+        padding: var(--cpd-space-2x) 0 var(--cpd-space-4x);
         margin: 0 var(--cpd-space-4x);
 
-        .mx_UserInfo_container_verifyButton {
-            margin-top: $spacing-8;
-        }
-
         & + .mx_UserInfo_container {
             border-top: 1px solid $separator;
         }
@@ -65,7 +61,7 @@ Please see LICENSE files in the repository root for full details.
     }
 
     .mx_UserInfo_avatar {
-        margin: $spacing-24 $spacing-32 0 $spacing-32;
+        margin: var(--cpd-space-12x) var(--cpd-space-4x) 0 var(--cpd-space-4x);
 
         .mx_UserInfo_avatar_transition {
             max-width: 120px;
@@ -98,11 +94,30 @@ Please see LICENSE files in the repository root for full details.
         margin: 5px 0;
     }
 
+    .mx_UserInfo_header {
+        margin-bottom: var(--cpd-space-8x);
+        padding-bottom: 0;
+    }
+
     .mx_UserInfo_profile {
+        display: flex;
+        flex-direction: column;
+        gap: var(--cpd-space-1x);
+
         h1 {
+            margin: 0;
             font-size: $font-20px;
             line-height: $font-25px;
 
+            /* E2E icon wrapper */
+            .mx_Flex > span {
+                display: inline-block;
+            }
+        }
+
+        .mx_UserInfo_profile_name {
+            min-height: 30px;
+
             /* limit to 2 lines, show an ellipsis if it overflows */
             /* this looks webkit specific but is supported by Firefox 68+ */
             display: -webkit-box;
@@ -112,15 +127,43 @@ Please see LICENSE files in the repository root for full details.
             overflow: hidden;
             word-break: break-all;
             text-overflow: ellipsis;
+        }
 
-            /* E2E icon wrapper */
-            .mx_Flex > span {
-                display: inline-block;
-            }
+        .mx_UserInfo_profile_mxid {
+            color: var(--cpd-color-text-secondary);
+            height: 28px;
         }
 
         .mx_UserInfo_profileStatus {
-            margin: var(--cpd-space-1x) 0;
+            height: 20px;
+        }
+
+        .mx_UserInfo_timezone {
+            height: 20px;
+            margin: 0;
+            display: flex;
+            align-items: center;
+        }
+
+        /** Overrides for the copy to clipboard button **/
+        .mx_CopyableText {
+            align-items: center;
+        }
+
+        .mx_CopyableText_copyButton {
+            width: 28px;
+            height: 28px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            position: unset;
+            padding-left: var(--cpd-space-2x);
+        }
+
+        .mx_CopyableText_copyButton::before {
+            width: 20px;
+            height: 20px;
+            background-color: var(--cpd-color-icon-secondary-alpha);
         }
     }
 
@@ -133,6 +176,28 @@ Please see LICENSE files in the repository root for full details.
         opacity: 1;
     }
 
+    .mx_UserInfo_verification {
+        margin-top: var(--cpd-space-4x);
+        height: 36px;
+
+        .mx_UserInfo_verified_badge {
+            min-width: 68px;
+            height: 20px;
+
+            .mx_UserInfo_verified_icon {
+                flex-shrink: 0;
+            }
+
+            .mx_UserInfo_verified_label {
+                margin: 0;
+            }
+        }
+
+        .mx_UserInfo_verification_unavailable {
+            color: var(--cpd-color-text-secondary);
+        }
+    }
+
     .mx_UserInfo_memberDetails {
         .mx_UserInfo_profileField {
             display: flex;
@@ -179,45 +244,6 @@ Please see LICENSE files in the repository root for full details.
         flex: 1 1 0;
     }
 
-    .mx_UserInfo_devices {
-        .mx_UserInfo_device {
-            display: flex;
-            margin: $spacing-8 0;
-
-            &.mx_UserInfo_device_verified {
-                .mx_UserInfo_device_trusted {
-                    color: $accent;
-                }
-            }
-            &.mx_UserInfo_device_unverified {
-                .mx_UserInfo_device_trusted {
-                    color: $alert;
-                }
-            }
-
-            .mx_UserInfo_device_name {
-                flex: 1;
-                margin: 0 5px;
-                word-break: break-word;
-            }
-        }
-
-        /* both for icon in expand button and device item */
-        .mx_E2EIcon {
-            /* don't squeeze */
-            flex: 0 0 auto;
-            margin: 0;
-            width: 12px;
-            height: 12px;
-        }
-
-        .mx_UserInfo_expand {
-            column-gap: 5px; /* cf: mx_UserInfo_device_name */
-            margin-bottom: 11px;
-            align-items: initial; /* Cancel the default property */
-        }
-    }
-
     &.mx_UserInfo_smallAvatar {
         .mx_UserInfo_avatar {
             .mx_UserInfo_avatar_transition {
diff --git a/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss b/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..a0fbfdaea716e2ba41eafdcea0a61bf682a47464
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_EmptyRoomList_GenericPlaceholder {
+    align-self: center;
+    /** It should take 2/3 of the width **/
+    width: 66%;
+    /** It should be positioned at 1/3 of the height **/
+    padding-top: 33%;
+
+    .mx_EmptyRoomList_GenericPlaceholder_title {
+        font: var(--cpd-font-body-lg-semibold);
+        text-align: center;
+    }
+
+    .mx_EmptyRoomList_GenericPlaceholder_description {
+        font: var(--cpd-font-body-sm-regular);
+        color: var(--cpd-color-text-secondary);
+        text-align: center;
+    }
+
+    .mx_EmptyRoomList_DefaultPlaceholder {
+        margin-top: var(--cpd-space-4x);
+    }
+
+    button {
+        width: 100%;
+    }
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomList.pcss b/res/css/views/rooms/RoomListPanel/_RoomList.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..54798f1ea96dad1636d606336a07455c2929eb4d
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomList.pcss
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomList {
+    height: 100%;
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..9af934c308df7932ddbce0d421596389003db922
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListHeaderView {
+    flex: 0 0 60px;
+    padding: 0 var(--cpd-space-3x);
+
+    .mx_RoomListHeaderView_title {
+        min-width: 0;
+
+        h1 {
+            all: unset;
+            font: var(--cpd-font-heading-sm-semibold);
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+        }
+    }
+
+    .mx_SpaceMenu_button {
+        svg {
+            transition: transform 0.1s linear;
+        }
+    }
+
+    .mx_SpaceMenu_button[aria-expanded="true"] {
+        svg {
+            transform: rotate(180deg);
+        }
+    }
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..cabd9b2d205af7c6b4aa389c91b0ee7baf7ee114
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListItemMenuView {
+    svg {
+        fill: var(--cpd-color-icon-primary);
+    }
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..ac58a69bef7a134cda37f2d9a0df43be75ce3327
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+/**
+ * The RoomListItemView has the following structure:
+ * button--------------------------------------------------|
+ * | <-12px-> container------------------------------------|
+ * |          | room avatar <-8px-> content----------------|
+ * |          |                      | room_name <- 20px ->|
+ * |          |                      | --------------------| <-- border
+ * |-------------------------------------------------------|
+ */
+.mx_RoomListItemView {
+    all: unset;
+    cursor: pointer;
+
+    .mx_RoomListItemView_container {
+        padding-left: var(--cpd-space-3x);
+        font: var(--cpd-font-body-md-regular);
+        height: 100%;
+
+        .mx_RoomListItemView_content {
+            height: 100%;
+            flex: 1;
+            /* The border is only under the room name and the future hover menu  */
+            border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary);
+            box-sizing: border-box;
+            min-width: 0;
+            padding-right: var(--cpd-space-5x);
+
+            .mx_RoomListItemView_text {
+                min-width: 0;
+            }
+
+            .mx_RoomListItemView_roomName {
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+            }
+
+            .mx_RoomListItemView_messagePreview {
+                font: var(--cpd-font-body-sm-regular);
+                color: var(--cpd-color-text-secondary);
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+            }
+        }
+    }
+}
+
+.mx_RoomListItemView_hover {
+    background-color: var(--cpd-color-bg-action-secondary-hovered);
+}
+
+.mx_RoomListItemView_menu_open .mx_RoomListItemView_container .mx_RoomListItemView_content {
+    /**
+     * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331
+     * the icon size of the menu is 18px instead of 20px with a different internal padding
+     * We need to use 18px to align the icon with the others icons
+     * 18px is not available in compound spacing
+     */
+    padding-right: 18px;
+}
+
+.mx_RoomListItemView_selected {
+    background-color: var(--cpd-color-bg-action-secondary-pressed);
+}
+
+.mx_RoomListItemView_bold .mx_RoomListItemView_roomName {
+    font: var(--cpd-font-body-md-semibold);
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..eb1f6e5fe5b9528347c84437eaf6c27d22d51c64
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListPanel {
+    background-color: var(--cpd-color-bg-canvas-default);
+    height: 100%;
+    border-right: 1px solid var(--cpd-color-bg-subtle-primary);
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..ac85782bbd0ac2f0e5f06ad1356dd880640716cc
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListPrimaryFilters {
+    margin: unset;
+    list-style-type: none;
+    padding: var(--cpd-space-2x) var(--cpd-space-3x);
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..f6f98e1efe8852e93d6b51d564adfab6b3f87c4d
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListSearch {
+    /* From figma, this should be aligned with the room header */
+    flex: 0 0 64px;
+    box-sizing: border-box;
+    border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
+    padding: 0 var(--cpd-space-3x);
+
+    svg {
+        fill: var(--cpd-color-icon-secondary);
+    }
+
+    .mx_RoomListSearch_search {
+        /* The search button should take all the remaining space */
+        flex: 1;
+        font: var(--cpd-font-body-md-regular);
+        color: var(--cpd-color-text-secondary);
+        min-width: 0;
+
+        span {
+            flex: 1;
+
+            kbd {
+                font-family: inherit;
+            }
+
+            /* Shrink and truncate the search text */
+            white-space: nowrap;
+            overflow: hidden;
+            .mx_RoomListSearch_search_text {
+                min-width: 0;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                text-align: start;
+            }
+        }
+    }
+
+    .mx_RoomListSearch_button:hover {
+        svg {
+            fill: var(--cpd-color-icon-primary);
+        }
+    }
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..0fa8dc12aef8fb25b1040c2ed915733b98321050
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListSecondaryFilters {
+    font: var(--cpd-font-body-md-medium);
+    margin: var(--cpd-space-2x);
+    margin-left: var(--cpd-space-1x);
+}
diff --git a/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..2e644cbba1e11ddc56166399949f45b6778dd181
--- /dev/null
+++ b/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RoomListSkeleton {
+    position: relative;
+    margin-left: 4px;
+    height: 100%;
+
+    &::before {
+        background-color: var(--cpd-color-bg-subtle-secondary);
+        width: 100%;
+        height: 100%;
+
+        content: "";
+        position: absolute;
+        mask-repeat: repeat-y;
+        mask-size: auto 96px;
+        mask-image: url("$(res)/img/element-icons/roomlist/room-list-item-skeleton.svg");
+    }
+}
diff --git a/res/css/views/rooms/_EventBubbleTile.pcss b/res/css/views/rooms/_EventBubbleTile.pcss
index c25cbfcec410fb4de3c3b7a9a1fe03a3961945ef..1607c45924901e7a0034ab0e8e8c53ce3fd6f12e 100644
--- a/res/css/views/rooms/_EventBubbleTile.pcss
+++ b/res/css/views/rooms/_EventBubbleTile.pcss
@@ -312,7 +312,7 @@ Please see LICENSE files in the repository root for full details.
                 .mx_MessageTimestamp {
                     border-radius: var(--MBody-border-radius);
                     /* Hardcoded colours because it's the same on all themes */
-                    background-color: rgba(0, 0, 0, 0.6);
+                    background-color: rgb(0, 0, 0, 0.6);
                     color: #ffffff;
                     padding: 0px 4px 0px 4px;
                 }
diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss
index 5b86e3f75323c4e8886aacc1c7b08dae95980d07..b4172fc00370d2be3061c7586535926dd007ee27 100644
--- a/res/css/views/rooms/_EventTile.pcss
+++ b/res/css/views/rooms/_EventTile.pcss
@@ -135,12 +135,6 @@ $left-gutter: 64px;
         }
     }
 
-    &.mx_EventTile_highlight,
-    &.mx_EventTile_highlight .markdown-body,
-    &.mx_EventTile_highlight .mx_EventTile_edited {
-        color: $alert;
-    }
-
     &.mx_EventTile_bubbleContainer {
         display: grid;
         grid-template-columns: 1fr 100px;
@@ -689,6 +683,7 @@ $left-gutter: 64px;
         line-height: inherit !important;
         background-color: inherit;
         color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */
+        flex: 1;
 
         pre,
         code {
@@ -821,11 +816,13 @@ $left-gutter: 64px;
     .mx_EventTile_spoiler_content {
         filter: blur(5px) saturate(0.1) sepia(1);
         transition-duration: 0.5s;
+        pointer-events: none;
     }
 
     &.visible > .mx_EventTile_spoiler_content {
         filter: none;
         user-select: auto;
+        pointer-events: auto;
     }
 }
 
diff --git a/res/css/views/rooms/_InvitedIconView.pcss b/res/css/views/rooms/_InvitedIconView.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..504f4498af98c7f5dbcad5a8ddbfc5e9ce76beae
--- /dev/null
+++ b/res/css/views/rooms/_InvitedIconView.pcss
@@ -0,0 +1,10 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+.mx_InvitedIconView {
+    color: var(--cpd-color-icon-tertiary);
+}
diff --git a/res/css/views/rooms/_RoomList.pcss b/res/css/views/rooms/_LegacyRoomList.pcss
similarity index 71%
rename from res/css/views/rooms/_RoomList.pcss
rename to res/css/views/rooms/_LegacyRoomList.pcss
index 74e2e86ed1dfd2429e48bb4a02b12f4dc442df26..acf162b7a2f813422c709be8d8cb822dcc149b43 100644
--- a/res/css/views/rooms/_RoomList.pcss
+++ b/res/css/views/rooms/_LegacyRoomList.pcss
@@ -6,31 +6,31 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-.mx_RoomList {
+.mx_LegacyRoomList {
     padding-right: 7px; /* width of the scrollbar, to line things up */
 }
 
-.mx_RoomList_iconPlus::before {
+.mx_LegacyRoomList_iconPlus::before {
     mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg");
 }
-.mx_RoomList_iconNewRoom::before {
+.mx_LegacyRoomList_iconNewRoom::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
 }
-.mx_RoomList_iconNewVideoRoom::before {
+.mx_LegacyRoomList_iconNewVideoRoom::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
 }
-.mx_RoomList_iconAddExistingRoom::before {
+.mx_LegacyRoomList_iconAddExistingRoom::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
 }
-.mx_RoomList_iconExplore::before {
+.mx_LegacyRoomList_iconExplore::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
 }
-.mx_RoomList_iconDialpad::before {
+.mx_LegacyRoomList_iconDialpad::before {
     mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
 }
-.mx_RoomList_iconStartChat::before {
+.mx_LegacyRoomList_iconStartChat::before {
     mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
 }
-.mx_RoomList_iconInvite::before {
+.mx_LegacyRoomList_iconInvite::before {
     mask-image: url("$(res)/img/element-icons/room/share.svg");
 }
diff --git a/res/css/views/rooms/_RoomListHeader.pcss b/res/css/views/rooms/_LegacyRoomListHeader.pcss
similarity index 83%
rename from res/css/views/rooms/_RoomListHeader.pcss
rename to res/css/views/rooms/_LegacyRoomListHeader.pcss
index 396aa4a61a747994e693f6e6d0bb4932e508f632..c04b56d94af5f8530e80a1af76e8781fc91e6178 100644
--- a/res/css/views/rooms/_RoomListHeader.pcss
+++ b/res/css/views/rooms/_LegacyRoomListHeader.pcss
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-.mx_RoomListHeader {
+.mx_LegacyRoomListHeader {
     display: flex;
     align-items: center;
 
-    .mx_RoomListHeader_contextLessTitle,
-    .mx_RoomListHeader_contextMenuButton {
+    .mx_LegacyRoomListHeader_contextLessTitle,
+    .mx_LegacyRoomListHeader_contextMenuButton {
         font: var(--cpd-font-heading-sm-semibold);
         font-weight: var(--cpd-font-weight-semibold);
         padding: 1px 24px 1px 4px;
@@ -24,7 +24,7 @@ Please see LICENSE files in the repository root for full details.
         user-select: none;
     }
 
-    .mx_RoomListHeader_contextMenuButton {
+    .mx_LegacyRoomListHeader_contextMenuButton {
         border-radius: 6px;
 
         &:hover {
@@ -54,7 +54,7 @@ Please see LICENSE files in the repository root for full details.
         }
     }
 
-    .mx_RoomListHeader_plusButton {
+    .mx_LegacyRoomListHeader_plusButton {
         width: 32px;
         height: 32px;
         border-radius: 8px;
@@ -88,21 +88,21 @@ Please see LICENSE files in the repository root for full details.
     }
 }
 
-.mx_RoomListHeader_iconInvite::before {
+.mx_LegacyRoomListHeader_iconInvite::before {
     mask-image: url("$(res)/img/element-icons/room/invite.svg");
 }
-.mx_RoomListHeader_iconStartChat::before {
+.mx_LegacyRoomListHeader_iconStartChat::before {
     mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
 }
-.mx_RoomListHeader_iconNewRoom::before {
+.mx_LegacyRoomListHeader_iconNewRoom::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
 }
-.mx_RoomListHeader_iconNewVideoRoom::before {
+.mx_LegacyRoomListHeader_iconNewVideoRoom::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
 }
-.mx_RoomListHeader_iconExplore::before {
+.mx_LegacyRoomListHeader_iconExplore::before {
     mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
 }
-.mx_RoomListHeader_iconPlus::before {
+.mx_LegacyRoomListHeader_iconPlus::before {
     mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg");
 }
diff --git a/res/css/views/rooms/_MemberListHeaderView.pcss b/res/css/views/rooms/_MemberListHeaderView.pcss
index 326cf84dd6cdab454dd2440b78da9c97022af92e..314bd740629c1ffeed67dcb8e9584cd317d6caa1 100644
--- a/res/css/views/rooms/_MemberListHeaderView.pcss
+++ b/res/css/views/rooms/_MemberListHeaderView.pcss
@@ -16,6 +16,7 @@ Please see LICENSE files in the repository root for full details.
 
     .mx_MemberListHeaderView_invite_small {
         margin-left: var(--cpd-space-3x);
+        margin-right: var(--cpd-space-4x);
     }
 
     .mx_MemberListHeaderView_invite_large {
@@ -33,5 +34,7 @@ Please see LICENSE files in the repository root for full details.
 
     .mx_MemberListHeaderView_search {
         width: 240px;
+        flex-grow: 1;
+        margin-left: var(--cpd-space-4x);
     }
 }
diff --git a/res/css/views/rooms/_MemberListView.pcss b/res/css/views/rooms/_MemberListView.pcss
index e13b17b226ee2f20432c0f31319254164a036de9..0aee9cb1596c73de964af3e73efdab330cd5c4c9 100644
--- a/res/css/views/rooms/_MemberListView.pcss
+++ b/res/css/views/rooms/_MemberListView.pcss
@@ -14,4 +14,10 @@ Please see LICENSE files in the repository root for full details.
     .mx_MemberListView_container {
         height: 100%;
     }
+
+    .mx_MemberListView_separator {
+        margin: 0;
+        border: none;
+        border-top: 2px solid var(--cpd-color-bg-subtle-primary);
+    }
 }
diff --git a/res/css/views/rooms/_MemberTileView.pcss b/res/css/views/rooms/_MemberTileView.pcss
index 702edd8f9d2b5f5c6d701ba6bbf109ce2c562719..307625d042ac03f8b795ed6da630ed4270b60c71 100644
--- a/res/css/views/rooms/_MemberTileView.pcss
+++ b/res/css/views/rooms/_MemberTileView.pcss
@@ -27,13 +27,13 @@ Please see LICENSE files in the repository root for full details.
 
     .mx_MemberTileView_name {
         font: var(--cpd-font-body-md-medium);
-        font-size: 15px;
         min-width: 0;
     }
 
-    .mx_MemberTileView_user_label {
+    .mx_MemberTileView_userLabel {
         font: var(--cpd-font-body-sm-regular);
-        font-size: 13px;
+        color: var(--cpd-color-text-secondary);
+        margin-left: var(--cpd-space-4x);
     }
 
     .mx_MemberTileView_avatar {
@@ -41,18 +41,4 @@ Please see LICENSE files in the repository root for full details.
         height: 32px;
         width: 32px;
     }
-
-    .mx_E2EIconView {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-    }
-
-    .mx_E2EIconView_warning {
-        color: var(--cpd-color-icon-critical-primary);
-    }
-
-    .mx_E2EIconView_verified {
-        color: var(--cpd-color-icon-success-primary);
-    }
 }
diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss
index da9b75b17d18e05367e4b7428084ddf1d6957aa5..d7f3ddd1fb4aafd6248189e1c71dfcabbf11051c 100644
--- a/res/css/views/rooms/_PinnedMessageBanner.pcss
+++ b/res/css/views/rooms/_PinnedMessageBanner.pcss
@@ -19,7 +19,7 @@
     border-bottom: 1px solid var(--cpd-color-gray-400);
 
     /* From figma */
-    box-shadow: 0 var(--cpd-space-2x) var(--cpd-space-6x) calc(var(--cpd-space-2x) * -1) rgba(27, 29, 34, 0.1);
+    box-shadow: 0 var(--cpd-space-2x) var(--cpd-space-6x) calc(var(--cpd-space-2x) * -1) rgb(27, 29, 34, 0.1);
 
     .mx_PinnedMessageBanner_main {
         background: transparent;
diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss
index 32eb055f07ed6f1e3ded1de32870c963d5bcd9b0..afde8f346473c96b571f4edcfea4c7d6ed1e0054 100644
--- a/res/css/views/rooms/_RoomHeader.pcss
+++ b/res/css/views/rooms/_RoomHeader.pcss
@@ -59,6 +59,7 @@ Please see LICENSE files in the repository root for full details.
 
 .mx_RoomHeader_icon {
     flex-shrink: 0;
+    padding: var(--cpd-space-1x);
 }
 
 .mx_RoomHeader .mx_FacePile {
@@ -71,6 +72,7 @@ Please see LICENSE files in the repository root for full details.
     padding: var(--cpd-space-1-5x);
     cursor: pointer;
     user-select: none;
+    font: var(--cpd-font-body-sm-medium);
 
     /* RoomAvatar doesn't pass classes down to avatar
     So set style here
@@ -83,6 +85,12 @@ Please see LICENSE files in the repository root for full details.
         color: $primary-content;
         background: var(--cpd-color-bg-subtle-primary);
     }
+
+    &.mx_FacePile_toggled {
+        background: var(--cpd-color-bg-success-subtle);
+        color: var(--cpd-color-text-action-accent);
+        font: var(--cpd-font-body-sm-semibold);
+    }
 }
 
 .mx_RoomHeader .mx_BaseAvatar {
@@ -93,3 +101,7 @@ Please see LICENSE files in the repository root for full details.
     /* Workaround for https://github.com/element-hq/compound/issues/331 */
     min-width: 240px;
 }
+
+.mx_RoomHeader .mx_RoomHeader_toggled {
+    color: var(--cpd-color-icon-accent-primary);
+}
diff --git a/res/css/views/rooms/_RoomSublist.pcss b/res/css/views/rooms/_RoomSublist.pcss
index f5d0e4ed9135db7566aa9433132d5a099cb220cd..3361bce4bbf948220e0974c868b45c038c01044e 100644
--- a/res/css/views/rooms/_RoomSublist.pcss
+++ b/res/css/views/rooms/_RoomSublist.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2020 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -393,8 +393,7 @@ Please see LICENSE files in the repository root for full details.
         margin-bottom: 4px;
     }
 
-    .mx_StyledRadioButton,
-    .mx_Checkbox {
+    .mx_StyledRadioButton {
         margin-top: 8px;
     }
 }
@@ -405,8 +404,7 @@ Please see LICENSE files in the repository root for full details.
     height: 240px;
 
     &::before {
-        background: $roomsublist-skeleton-ui-bg;
-
+        background-color: var(--cpd-color-bg-subtle-secondary);
         width: 100%;
         height: 100%;
 
diff --git a/res/css/views/rooms/_UserIdentityWarning.pcss b/res/css/views/rooms/_UserIdentityWarning.pcss
index e5d14eb472661803dac8c4423c827a619ec08872..cf87e47a24a48c96635eac84a8c05b770a91b091 100644
--- a/res/css/views/rooms/_UserIdentityWarning.pcss
+++ b/res/css/views/rooms/_UserIdentityWarning.pcss
@@ -20,8 +20,14 @@ Please see LICENSE files in the repository root for full details.
             margin-left: var(--cpd-space-6x);
             flex-grow: 1;
         }
+        .mx_UserIdentityWarning_main.critical {
+            color: var(--cpd-color-text-critical-primary);
+        }
     }
 }
+.mx_UserIdentityWarning.critical {
+    background: linear-gradient(180deg, var(--cpd-color-red-100) 0%, var(--cpd-color-theme-bg) 100%);
+}
 
 .mx_MessageComposer.mx_MessageComposer--compact > .mx_UserIdentityWarning {
     margin-left: calc(-25px + var(--RoomView_MessageList-padding));
diff --git a/res/css/views/settings/_CrossSigningPanel.pcss b/res/css/views/settings/_CrossSigningPanel.pcss
deleted file mode 100644
index e9b2aa0c293f2f382cdff2e847cd2a51381e484c..0000000000000000000000000000000000000000
--- a/res/css/views/settings/_CrossSigningPanel.pcss
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_CrossSigningPanel_statusList {
-    border-spacing: 0;
-
-    th {
-        text-align: start;
-    }
-
-    td,
-    th {
-        padding: 0;
-
-        &:first-of-type {
-            padding-inline-end: 1em;
-        }
-    }
-}
-
-.mx_CrossSigningPanel_buttonRow {
-    margin: 1em 0;
-
-    :nth-child(n + 1) {
-        margin-inline-end: 10px;
-    }
-}
-
-.mx_CrossSigningPanel_advanced {
-    width: fit-content;
-}
diff --git a/res/css/views/settings/_CryptographyPanel.pcss b/res/css/views/settings/_CryptographyPanel.pcss
deleted file mode 100644
index 9c174ceaab762ee41426057bbd2a4895b7847e4b..0000000000000000000000000000000000000000
--- a/res/css/views/settings/_CryptographyPanel.pcss
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_CryptographyPanel_sessionInfo {
-    padding: 0em;
-    border-spacing: 0px;
-}
-.mx_CryptographyPanel_sessionInfo > tr {
-    vertical-align: baseline;
-    padding: 0em;
-
-    th {
-        text-align: start;
-    }
-
-    td,
-    th {
-        padding: 0 1em 0 0;
-    }
-}
-
-.mx_CryptographyPanel_importExportButtons {
-    display: inline-flex;
-    flex-flow: wrap;
-    row-gap: $spacing-8;
-    column-gap: $spacing-8;
-}
diff --git a/res/css/views/settings/_JoinRuleSettings.pcss b/res/css/views/settings/_JoinRuleSettings.pcss
index 485434f0da5d73be14166aca0d12aab452c1cbe6..fcb21fca9681b5bf0d437d31b17ad3359a7c11a9 100644
--- a/res/css/views/settings/_JoinRuleSettings.pcss
+++ b/res/css/views/settings/_JoinRuleSettings.pcss
@@ -53,6 +53,14 @@ Please see LICENSE files in the repository root for full details.
         display: block;
     }
 
+    &.mx_StyledRadioButton_disabled {
+        opacity: 0.5;
+    }
+
+    &.mx_StyledRadioButton_disabled + span {
+        opacity: 0.5;
+    }
+
     & + span {
         display: inline-block;
         margin-left: 34px;
@@ -71,3 +79,7 @@ Please see LICENSE files in the repository root for full details.
     font: var(--cpd-font-body-md-regular);
     margin-top: var(--cpd-space-2x);
 }
+
+.mx_JoinRuleSettings_recommended {
+    color: $accent-1000;
+}
diff --git a/res/css/views/settings/_SecureBackupPanel.pcss b/res/css/views/settings/_SecureBackupPanel.pcss
deleted file mode 100644
index 6e571af3391245b308ff4d49fb34ed706b238a63..0000000000000000000000000000000000000000
--- a/res/css/views/settings/_SecureBackupPanel.pcss
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
-Copyright 2018 New Vector Ltd
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_SecureBackupPanel_deviceName {
-    font-style: italic;
-}
-
-.mx_SecureBackupPanel_buttonRow {
-    margin: 1em 0;
-    display: inline-flex;
-    flex-flow: wrap;
-    row-gap: 10px;
-
-    :nth-child(n + 1) {
-        margin-inline-end: 10px;
-    }
-}
-
-.mx_SecureBackupPanel_statusList {
-    border-spacing: 0;
-
-    th {
-        text-align: start;
-    }
-
-    td,
-    th {
-        padding: 0;
-
-        &:first-of-type {
-            padding-inline-end: 1em;
-        }
-    }
-}
-
-.mx_SecureBackupPanel_advanced {
-    width: fit-content;
-}
diff --git a/res/css/views/settings/_SetIdServer.pcss b/res/css/views/settings/_SetIdServer.pcss
deleted file mode 100644
index 377292451ffef8814a6e754e454bf68cc9a15899..0000000000000000000000000000000000000000
--- a/res/css/views/settings/_SetIdServer.pcss
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019-2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-.mx_SetIdServer {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: $spacing-8;
-
-    .mx_Field {
-        width: 100%;
-        margin: 0;
-    }
-}
-
-.mx_SetIdServer_tooltip {
-    max-width: var(--SettingsTab_tooltip-max-width);
-}
diff --git a/res/css/views/settings/_SetIntegrationManager.pcss b/res/css/views/settings/_SetIntegrationManager.pcss
index a046ce0fffab138235274dd018ff83916002c5be..f370d06e5eced3030e581f326ffd9d98c0bf608d 100644
--- a/res/css/views/settings/_SetIntegrationManager.pcss
+++ b/res/css/views/settings/_SetIntegrationManager.pcss
@@ -7,19 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 .mx_SetIntegrationManager {
-    .mx_SettingsFlag {
+    .mx_SetIntegrationManager_heading_manager {
+        display: flex;
         align-items: center;
-
-        .mx_SetIntegrationManager_heading_manager {
-            display: flex;
-            align-items: center;
-            flex-wrap: wrap;
-            column-gap: $spacing-4;
-        }
-
-        .mx_ToggleSwitch {
-            align-self: flex-start;
-            min-width: var(--ToggleSwitch-min-width); /* avoid compression */
-        }
+        flex-wrap: wrap;
+        column-gap: $spacing-4;
+    }
+    form {
+        margin-top: var(--cpd-space-3x);
     }
 }
diff --git a/res/css/views/settings/encryption/_AdvancedPanel.pcss b/res/css/views/settings/encryption/_AdvancedPanel.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..fed8fca7ead4c21d357a007f92ff711f2d58d783
--- /dev/null
+++ b/res/css/views/settings/encryption/_AdvancedPanel.pcss
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_EncryptionDetails,
+.mx_OtherSettings {
+    display: flex;
+    flex-direction: column;
+    gap: var(--cpd-space-6x);
+    width: 100%;
+    align-items: start;
+
+    .mx_EncryptionDetails_session_title,
+    .mx_OtherSettings_title {
+        font: var(--cpd-font-body-lg-semibold);
+        padding-bottom: var(--cpd-space-2x);
+        border-bottom: 1px solid var(--cpd-color-gray-400);
+        width: 100%;
+        margin: 0;
+    }
+}
+
+.mx_EncryptionDetails {
+    .mx_EncryptionDetails_session {
+        display: flex;
+        flex-direction: column;
+        gap: var(--cpd-space-4x);
+        width: 100%;
+
+        > div {
+            display: flex;
+
+            > span {
+                width: 50%;
+                word-wrap: break-word;
+            }
+        }
+
+        > div:nth-child(odd) {
+            background-color: var(--cpd-color-gray-200);
+        }
+    }
+
+    .mx_EncryptionDetails_buttons {
+        display: flex;
+        gap: var(--cpd-space-4x);
+    }
+}
diff --git a/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss b/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss
index d6577431404ba96d91327d1c1006590ed99c0e1e..ceacb22c2700a5f67627c2e815fc3a53ea0a0118 100644
--- a/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss
+++ b/res/css/views/settings/encryption/_ChangeRecoveryKey.pcss
@@ -69,11 +69,4 @@
         flex-direction: column;
         gap: var(--cpd-space-8x);
     }
-
-    .mx_ChangeRecoveryKey_footer {
-        display: flex;
-        flex-direction: column;
-        gap: var(--cpd-space-4x);
-        justify-content: center;
-    }
 }
diff --git a/res/css/views/settings/encryption/_EncryptionCard.pcss b/res/css/views/settings/encryption/_EncryptionCard.pcss
index f125aea17645b6d87589b57042a051b6c8784aa8..5598facfa950683fff1944d9666daf594b33a189 100644
--- a/res/css/views/settings/encryption/_EncryptionCard.pcss
+++ b/res/css/views/settings/encryption/_EncryptionCard.pcss
@@ -12,7 +12,7 @@
     padding: var(--cpd-space-10x);
     border-radius: var(--cpd-space-4x);
     /* From figma */
-    box-shadow: 0 1.2px 2.4px 0 rgba(27, 29, 34, 0.15);
+    box-shadow: 0 1.2px 2.4px 0 rgb(27, 29, 34, 0.15);
     border: 1px solid var(--cpd-color-gray-400);
 
     .mx_EncryptionCard_header {
@@ -31,3 +31,10 @@
         }
     }
 }
+
+.mx_EncryptionCard_buttons {
+    display: flex;
+    flex-direction: column;
+    gap: var(--cpd-space-4x);
+    justify-content: center;
+}
diff --git a/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss b/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..6b18fcff65069199f918fa4788d50f4d7e90e2da
--- /dev/null
+++ b/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_EncryptionCard_emphasisedContent {
+    span {
+        font: var(--cpd-font-body-md-medium);
+        text-align: center;
+    }
+}
diff --git a/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss b/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..fc6ba7d95933ca25691da9406bbb3a76294fb052
--- /dev/null
+++ b/res/css/views/settings/encryption/_RecoveryPanelOutOfSync.pcss
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.mx_RecoveryPanelOutOfSync {
+    display: flex;
+    gap: var(--cpd-space-2x);
+}
diff --git a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..8318d6d91cd3a9db1e28378e6134e8af55ffe4c6
--- /dev/null
+++ b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+// Red text for the "Do not close this window" warning
+.mx_ResetIdentityPanel_warning {
+    color: var(--cpd-color-text-critical-primary);
+}
diff --git a/res/css/views/settings/tabs/user/_MediaPreviewAccountSettings.pcss b/res/css/views/settings/tabs/user/_MediaPreviewAccountSettings.pcss
new file mode 100644
index 0000000000000000000000000000000000000000..68187854c1638a45d77e3c344b99433c76691540
--- /dev/null
+++ b/res/css/views/settings/tabs/user/_MediaPreviewAccountSettings.pcss
@@ -0,0 +1,28 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+.mx_MediaPreviewAccountSetting_Radio {
+    margin: var(--cpd-space-1x) 0;
+}
+
+.mx_MediaPreviewAccountSetting {
+    margin-top: var(--cpd-space-1x);
+}
+
+.mx_MediaPreviewAccountSetting_RadioHelp {
+    margin-top: 0;
+    margin-bottom: var(--cpd-space-1x);
+}
+
+.mx_MediaPreviewAccountSetting_Form {
+    width: 100%;
+}
+
+.mx_MediaPreviewAccountSetting_ToggleSwitch {
+    font: var(--cpd-font-body-md-medium);
+    letter-spacing: var(--cpd-font-letter-spacing-body-md);
+}
diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
index cb5d1fbc94ba79fd4e3a531ee382803600fe4c50..82f839042b101bc969233d4d0229cbe5206be83e 100644
--- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
+++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.pcss
@@ -11,6 +11,12 @@ Please see LICENSE files in the repository root for full details.
     column-gap: $spacing-8;
 }
 
+.mx_SecurityUserSettingsTab_ignoredUsers {
+    padding-left: 0;
+    margin: 0;
+    list-style: none;
+}
+
 .mx_SecurityUserSettingsTab_ignoredUser {
     margin-bottom: $spacing-4;
 }
diff --git a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss
index cb360cc898f2c8b3ae28180d6ff6108a7d800799..a8d45f9f77abfc2e33665de702f657d60013fbf3 100644
--- a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss
+++ b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.pcss
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -14,17 +14,12 @@ Please see LICENSE files in the repository root for full details.
     }
 }
 
-.mx_SidebarUserSettingsTab_checkbox {
-    margin-bottom: $spacing-8;
-    /* override checkbox styles */
-    label {
-        align-items: flex-start !important;
-    }
+.mx_SidebarUserSettingsTab_icon {
+    margin-right: var(--cpd-space-2x);
+    margin-top: auto;
+    margin-bottom: auto;
+}
 
-    svg {
-        height: 16px;
-        width: 16px;
-        margin-right: $spacing-8;
-        margin-bottom: -1px;
-    }
+.mx_SidebarUserSettingsTab_checkbox label {
+    display: flex;
 }
diff --git a/res/css/views/verification/_VerificationShowSas.pcss b/res/css/views/verification/_VerificationShowSas.pcss
index 5e007ed2343211204da9ef739220d4c27268f1af..1a24519cbf8db53579b3551b962d1cb8484cb4b2 100644
--- a/res/css/views/verification/_VerificationShowSas.pcss
+++ b/res/css/views/verification/_VerificationShowSas.pcss
@@ -46,10 +46,8 @@ Please see LICENSE files in the repository root for full details.
 }
 
 .mx_VerificationShowSas_emojiSas_label {
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
     font-size: $font-12px;
+    word-break: break-all;
 }
 
 .mx_VerificationShowSas_emojiSas_break {
diff --git a/res/css/views/voip/_LegacyCallView.pcss b/res/css/views/voip/_LegacyCallView.pcss
index 007e8b500051e9dac7e063f94af3068dcd6b2062..93d3e23f8bfb876485d869650502a1367e2ce170 100644
--- a/res/css/views/voip/_LegacyCallView.pcss
+++ b/res/css/views/voip/_LegacyCallView.pcss
@@ -95,7 +95,7 @@ Please see LICENSE files in the repository root for full details.
                     height: 100%;
                     left: 0;
                     right: 0;
-                    background-color: rgba(0, 0, 0, 0.6);
+                    background-color: rgb(0, 0, 0, 0.6);
                 }
             }
 
@@ -144,7 +144,7 @@ Please see LICENSE files in the repository root for full details.
         border-radius: 8px;
 
         background-color: $system;
-        box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.2);
+        box-shadow: 0px 4px 20px rgb(0, 0, 0, 0.2);
 
         .mx_LegacyCallViewButtons {
             bottom: 13px;
diff --git a/res/css/views/voip/_VideoFeed.pcss b/res/css/views/voip/_VideoFeed.pcss
index 6abe3901268a54358b196f493e0576202a2610a4..8522fbb4f0cdafd121a478445331d7658fcb8f83 100644
--- a/res/css/views/voip/_VideoFeed.pcss
+++ b/res/css/views/voip/_VideoFeed.pcss
@@ -56,7 +56,7 @@ Please see LICENSE files in the repository root for full details.
         width: 24px;
         height: 24px;
 
-        background-color: rgba(0, 0, 0, 0.5); /* Same on both themes */
+        background-color: rgb(0, 0, 0, 0.5); /* Same on both themes */
         border-radius: 100%;
 
         &::before {
diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2
index c9ecd7a0dad3e816558ba76dedfd68256fe0e1f0..5bfc425d66b3f158ed26e85674502782dcb928f8 100644
Binary files a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 differ
diff --git a/res/img/element-icons/roomlist/dnd-cross.svg b/res/img/element-icons/roomlist/dnd-cross.svg
deleted file mode 100644
index 2091d5980212706d15cf5411329b8795d6a743b9..0000000000000000000000000000000000000000
--- a/res/img/element-icons/roomlist/dnd-cross.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.3333 7.3335V8.66683H10.5533L13.56 11.6735C14.26 10.6202 14.6667 9.36016 14.6667 8.00016C14.6667 4.32016 11.68 1.3335 8.00001 1.3335C6.64001 1.3335 5.38001 1.74016 4.32668 2.44016L9.22001 7.3335H11.3333ZM0.926682 2.8135L2.44001 4.32683C1.74001 5.38016 1.33335 6.64016 1.33335 8.00016C1.33335 11.6802 4.32001 14.6668 8.00001 14.6668C9.36001 14.6668 10.62 14.2602 11.6733 13.5602L13.1867 15.0735L14.1267 14.1335L1.87335 1.8735L0.926682 2.8135ZM4.66668 7.3335H5.44668L6.78001 8.66683H4.66668V7.3335Z" fill="#737D8C"/>
-</svg>
diff --git a/res/img/element-icons/roomlist/dnd.svg b/res/img/element-icons/roomlist/dnd.svg
deleted file mode 100644
index 8c4a86e51962693f5d9d4338319bec7e4f296147..0000000000000000000000000000000000000000
--- a/res/img/element-icons/roomlist/dnd.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM17 13H7V11H17V13Z" fill="#17191C"/>
-</svg>
diff --git a/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8eaf2f1831832fe33c3a39448df3e4d3089e3943
--- /dev/null
+++ b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg
@@ -0,0 +1,3 @@
+<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+    <path d="M32 0H0V32H32C26.4772 32 22 27.5228 22 22C22 16.4772 26.4772 12 32 12V0Z"/>
+</svg>
diff --git a/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b40f382d4b8d094a6c0305c4966860ddc55775ce
--- /dev/null
+++ b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg
@@ -0,0 +1,3 @@
+<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+    <path d="M36 -4H-4V36H36V30.4722C34.9385 31.4223 33.5367 32 32 32C28.6863 32 26 29.3137 26 26C26 22.6863 28.6863 20 32 20C33.5367 20 34.9385 20.5777 36 21.5278V-4Z" />
+</svg>
diff --git a/res/img/element-icons/roomlist/room-list-item-skeleton.svg b/res/img/element-icons/roomlist/room-list-item-skeleton.svg
new file mode 100644
index 0000000000000000000000000000000000000000..adf56e4ed8af2b8f6fed07d48f591827aa5edcf9
--- /dev/null
+++ b/res/img/element-icons/roomlist/room-list-item-skeleton.svg
@@ -0,0 +1,14 @@
+<svg width="309" height="96" viewBox="0 0 309 96" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="8" y="8" width="32" height="32" rx="16" fill="#E1E6EC"/>
+<mask id="path-2-inside-1_1772_38931" fill="white">
+<path d="M52 0H309V48H52V0Z"/>
+</mask>
+<path d="M309 48V47.5H52V48V48.5H309V48Z" fill="#F0F2F5" mask="url(#path-2-inside-1_1772_38931)"/>
+<path d="M48.7 25.938C48.7 25.3073 48.8467 24.75 49.14 24.266C49.4187 23.7673 49.8073 23.3787 50.306 23.1C50.79 22.8067 51.3473 22.66 51.978 22.66H64.386C65.0167 22.66 65.574 22.8067 66.058 23.1C66.5567 23.3787 66.9453 23.7673 67.224 24.266C67.5173 24.75 67.664 25.3073 67.664 25.938V25.982C67.664 26.6127 67.5173 27.1773 67.224 27.676C66.9453 28.16 66.5567 28.5487 66.058 28.842C65.574 29.1207 65.0167 29.26 64.386 29.26H51.978C51.3473 29.26 50.79 29.1207 50.306 28.842C49.8073 28.5487 49.4187 28.16 49.14 27.676C48.8467 27.1773 48.7 26.6127 48.7 25.982V25.938ZM61.0535 25.938C61.0535 25.3073 61.2002 24.75 61.4935 24.266C61.7722 23.7673 62.1608 23.3787 62.6595 23.1C63.1435 22.8067 63.7008 22.66 64.3315 22.66H76.7395C77.3702 22.66 77.9275 22.8067 78.4115 23.1C78.9102 23.3787 79.2988 23.7673 79.5775 24.266C79.8708 24.75 80.0175 25.3073 80.0175 25.938V25.982C80.0175 26.6127 79.8708 27.1773 79.5775 27.676C79.2988 28.16 78.9102 28.5487 78.4115 28.842C77.9275 29.1207 77.3702 29.26 76.7395 29.26H64.3315C63.7008 29.26 63.1435 29.1207 62.6595 28.842C62.1608 28.5487 61.7722 28.16 61.4935 27.676C61.2002 27.1773 61.0535 26.6127 61.0535 25.982V25.938ZM73.407 25.938C73.407 25.3073 73.5537 24.75 73.847 24.266C74.1257 23.7673 74.5144 23.3787 75.013 23.1C75.497 22.8067 76.0544 22.66 76.685 22.66H89.093C89.7237 22.66 90.281 22.8067 90.765 23.1C91.2637 23.3787 91.6524 23.7673 91.931 24.266C92.2244 24.75 92.371 25.3073 92.371 25.938V25.982C92.371 26.6127 92.2244 27.1773 91.931 27.676C91.6524 28.16 91.2637 28.5487 90.765 28.842C90.281 29.1207 89.7237 29.26 89.093 29.26H76.685C76.0544 29.26 75.497 29.1207 75.013 28.842C74.5144 28.5487 74.1257 28.16 73.847 27.676C73.5537 27.1773 73.407 26.6127 73.407 25.982V25.938ZM85.7605 25.938C85.7605 25.3073 85.9072 24.75 86.2005 24.266C86.4792 23.7673 86.8679 23.3787 87.3665 23.1C87.8505 22.8067 88.4079 22.66 89.0385 22.66H101.447C102.077 22.66 102.635 22.8067 103.119 23.1C103.617 23.3787 104.006 23.7673 104.285 24.266C104.578 24.75 104.725 25.3073 104.725 25.938V25.982C104.725 26.6127 104.578 27.1773 104.285 27.676C104.006 28.16 103.617 28.5487 103.119 28.842C102.635 29.1207 102.077 29.26 101.447 29.26H89.0385C88.4079 29.26 87.8505 29.1207 87.3665 28.842C86.8679 28.5487 86.4792 28.16 86.2005 27.676C85.9072 27.1773 85.7605 26.6127 85.7605 25.982V25.938ZM98.1141 25.938C98.1141 25.3073 98.2607 24.75 98.5541 24.266C98.8327 23.7673 99.2214 23.3787 99.7201 23.1C100.204 22.8067 100.761 22.66 101.392 22.66H113.8C114.431 22.66 114.988 22.8067 115.472 23.1C115.971 23.3787 116.359 23.7673 116.638 24.266C116.931 24.75 117.078 25.3073 117.078 25.938V25.982C117.078 26.6127 116.931 27.1773 116.638 27.676C116.359 28.16 115.971 28.5487 115.472 28.842C114.988 29.1207 114.431 29.26 113.8 29.26H101.392C100.761 29.26 100.204 29.1207 99.7201 28.842C99.2214 28.5487 98.8327 28.16 98.5541 27.676C98.2607 27.1773 98.1141 26.6127 98.1141 25.982V25.938ZM110.468 25.938C110.468 25.3073 110.614 24.75 110.908 24.266C111.186 23.7673 111.575 23.3787 112.074 23.1C112.558 22.8067 113.115 22.66 113.746 22.66H126.154C126.784 22.66 127.342 22.8067 127.826 23.1C128.324 23.3787 128.713 23.7673 128.992 24.266C129.285 24.75 129.432 25.3073 129.432 25.938V25.982C129.432 26.6127 129.285 27.1773 128.992 27.676C128.713 28.16 128.324 28.5487 127.826 28.842C127.342 29.1207 126.784 29.26 126.154 29.26H113.746C113.115 29.26 112.558 29.1207 112.074 28.842C111.575 28.5487 111.186 28.16 110.908 27.676C110.614 27.1773 110.468 26.6127 110.468 25.982V25.938ZM122.821 25.938C122.821 25.3073 122.968 24.75 123.261 24.266C123.54 23.7673 123.928 23.3787 124.427 23.1C124.911 22.8067 125.468 22.66 126.099 22.66H138.507C139.138 22.66 139.695 22.8067 140.179 23.1C140.678 23.3787 141.066 23.7673 141.345 24.266C141.638 24.75 141.785 25.3073 141.785 25.938V25.982C141.785 26.6127 141.638 27.1773 141.345 27.676C141.066 28.16 140.678 28.5487 140.179 28.842C139.695 29.1207 139.138 29.26 138.507 29.26H126.099C125.468 29.26 124.911 29.1207 124.427 28.842C123.928 28.5487 123.54 28.16 123.261 27.676C122.968 27.1773 122.821 26.6127 122.821 25.982V25.938ZM135.175 25.938C135.175 25.3073 135.321 24.75 135.615 24.266C135.893 23.7673 136.282 23.3787 136.781 23.1C137.265 22.8067 137.822 22.66 138.453 22.66H150.861C151.491 22.66 152.049 22.8067 152.533 23.1C153.031 23.3787 153.42 23.7673 153.699 24.266C153.992 24.75 154.139 25.3073 154.139 25.938V25.982C154.139 26.6127 153.992 27.1773 153.699 27.676C153.42 28.16 153.031 28.5487 152.533 28.842C152.049 29.1207 151.491 29.26 150.861 29.26H138.453C137.822 29.26 137.265 29.1207 136.781 28.842C136.282 28.5487 135.893 28.16 135.615 27.676C135.321 27.1773 135.175 26.6127 135.175 25.982V25.938ZM160.72 25.938C160.72 25.3073 160.866 24.75 161.16 24.266C161.438 23.7673 161.827 23.3787 162.326 23.1C162.81 22.8067 163.367 22.66 163.998 22.66H176.406C177.036 22.66 177.594 22.8067 178.078 23.1C178.576 23.3787 178.965 23.7673 179.244 24.266C179.537 24.75 179.684 25.3073 179.684 25.938V25.982C179.684 26.6127 179.537 27.1773 179.244 27.676C178.965 28.16 178.576 28.5487 178.078 28.842C177.594 29.1207 177.036 29.26 176.406 29.26H163.998C163.367 29.26 162.81 29.1207 162.326 28.842C161.827 28.5487 161.438 28.16 161.16 27.676C160.866 27.1773 160.72 26.6127 160.72 25.982V25.938ZM173.073 25.938C173.073 25.3073 173.22 24.75 173.513 24.266C173.792 23.7673 174.18 23.3787 174.679 23.1C175.163 22.8067 175.72 22.66 176.351 22.66H182.687C183.318 22.66 183.875 22.8067 184.359 23.1C184.858 23.3787 185.246 23.7673 185.525 24.266C185.818 24.75 185.965 25.3073 185.965 25.938V25.982C185.965 26.6127 185.818 27.1773 185.525 27.676C185.246 28.16 184.858 28.5487 184.359 28.842C183.875 29.1207 183.318 29.26 182.687 29.26H176.351C175.72 29.26 175.163 29.1207 174.679 28.842C174.18 28.5487 173.792 28.16 173.513 27.676C173.22 27.1773 173.073 26.6127 173.073 25.982V25.938ZM179.368 25.938C179.368 25.3073 179.515 24.75 179.808 24.266C180.087 23.7673 180.475 23.3787 180.974 23.1C181.458 22.8067 182.015 22.66 182.646 22.66H195.054C195.685 22.66 196.242 22.8067 196.726 23.1C197.225 23.3787 197.613 23.7673 197.892 24.266C198.185 24.75 198.332 25.3073 198.332 25.938V25.982C198.332 26.6127 198.185 27.1773 197.892 27.676C197.613 28.16 197.225 28.5487 196.726 28.842C196.242 29.1207 195.685 29.26 195.054 29.26H182.646C182.015 29.26 181.458 29.1207 180.974 28.842C180.475 28.5487 180.087 28.16 179.808 27.676C179.515 27.1773 179.368 26.6127 179.368 25.982V25.938ZM191.721 25.938C191.721 25.3073 191.868 24.75 192.161 24.266C192.44 23.7673 192.829 23.3787 193.327 23.1C193.811 22.8067 194.369 22.66 194.999 22.66H207.407C208.038 22.66 208.595 22.8067 209.079 23.1C209.578 23.3787 209.967 23.7673 210.245 24.266C210.539 24.75 210.685 25.3073 210.685 25.938V25.982C210.685 26.6127 210.539 27.1773 210.245 27.676C209.967 28.16 209.578 28.5487 209.079 28.842C208.595 29.1207 208.038 29.26 207.407 29.26H194.999C194.369 29.26 193.811 29.1207 193.327 28.842C192.829 28.5487 192.44 28.16 192.161 27.676C191.868 27.1773 191.721 26.6127 191.721 25.982V25.938ZM204.075 25.938C204.075 25.3073 204.222 24.75 204.515 24.266C204.794 23.7673 205.182 23.3787 205.681 23.1C206.165 22.8067 206.722 22.66 207.353 22.66H219.761C220.392 22.66 220.949 22.8067 221.433 23.1C221.932 23.3787 222.32 23.7673 222.599 24.266C222.892 24.75 223.039 25.3073 223.039 25.938V25.982C223.039 26.6127 222.892 27.1773 222.599 27.676C222.32 28.16 221.932 28.5487 221.433 28.842C220.949 29.1207 220.392 29.26 219.761 29.26H207.353C206.722 29.26 206.165 29.1207 205.681 28.842C205.182 28.5487 204.794 28.16 204.515 27.676C204.222 27.1773 204.075 26.6127 204.075 25.982V25.938ZM216.429 25.938C216.429 25.3073 216.575 24.75 216.869 24.266C217.147 23.7673 217.536 23.3787 218.035 23.1C218.519 22.8067 219.076 22.66 219.707 22.66H226.043C226.673 22.66 227.231 22.8067 227.715 23.1C228.213 23.3787 228.602 23.7673 228.881 24.266C229.174 24.75 229.321 25.3073 229.321 25.938V25.982C229.321 26.6127 229.174 27.1773 228.881 27.676C228.602 28.16 228.213 28.5487 227.715 28.842C227.231 29.1207 226.673 29.26 226.043 29.26H219.707C219.076 29.26 218.519 29.1207 218.035 28.842C217.536 28.5487 217.147 28.16 216.869 27.676C216.575 27.1773 216.429 26.6127 216.429 25.982V25.938ZM222.723 25.938C222.723 25.3073 222.87 24.75 223.163 24.266C223.442 23.7673 223.831 23.3787 224.329 23.1C224.813 22.8067 225.371 22.66 226.001 22.66H232.337C232.968 22.66 233.525 22.8067 234.009 23.1C234.508 23.3787 234.897 23.7673 235.175 24.266C235.469 24.75 235.615 25.3073 235.615 25.938V25.982C235.615 26.6127 235.469 27.1773 235.175 27.676C234.897 28.16 234.508 28.5487 234.009 28.842C233.525 29.1207 232.968 29.26 232.337 29.26H226.001C225.371 29.26 224.813 29.1207 224.329 28.842C223.831 28.5487 223.442 28.16 223.163 27.676C222.87 27.1773 222.723 26.6127 222.723 25.982V25.938ZM229.018 25.938C229.018 25.3073 229.165 24.75 229.458 24.266C229.737 23.7673 230.126 23.3787 230.624 23.1C231.108 22.8067 231.666 22.66 232.296 22.66H238.632C239.263 22.66 239.82 22.8067 240.304 23.1C240.803 23.3787 241.192 23.7673 241.47 24.266C241.764 24.75 241.91 25.3073 241.91 25.938V25.982C241.91 26.6127 241.764 27.1773 241.47 27.676C241.192 28.16 240.803 28.5487 240.304 28.842C239.82 29.1207 239.263 29.26 238.632 29.26H232.296C231.666 29.26 231.108 29.1207 230.624 28.842C230.126 28.5487 229.737 28.16 229.458 27.676C229.165 27.1773 229.018 26.6127 229.018 25.982V25.938Z" fill="#F0F2F5"/>
+<rect x="8" y="56" width="32" height="32" rx="16" fill="#E1E6EC"/>
+<mask id="path-6-inside-2_1772_38931" fill="white">
+<path d="M52 48H309V96H52V48Z"/>
+</mask>
+<path d="M309 96V95.5H52V96V96.5H309V96Z" fill="#F0F2F5" mask="url(#path-6-inside-2_1772_38931)"/>
+<path d="M48.7 73.938C48.7 73.3073 48.8467 72.75 49.14 72.266C49.4187 71.7673 49.8073 71.3787 50.306 71.1C50.79 70.8067 51.3473 70.66 51.978 70.66H64.386C65.0167 70.66 65.574 70.8067 66.058 71.1C66.5567 71.3787 66.9453 71.7673 67.224 72.266C67.5173 72.75 67.664 73.3073 67.664 73.938V73.982C67.664 74.6127 67.5173 75.1773 67.224 75.676C66.9453 76.16 66.5567 76.5487 66.058 76.842C65.574 77.1207 65.0167 77.26 64.386 77.26H51.978C51.3473 77.26 50.79 77.1207 50.306 76.842C49.8073 76.5487 49.4187 76.16 49.14 75.676C48.8467 75.1773 48.7 74.6127 48.7 73.982V73.938ZM61.0535 73.938C61.0535 73.3073 61.2002 72.75 61.4935 72.266C61.7722 71.7673 62.1608 71.3787 62.6595 71.1C63.1435 70.8067 63.7008 70.66 64.3315 70.66H76.7395C77.3702 70.66 77.9275 70.8067 78.4115 71.1C78.9102 71.3787 79.2988 71.7673 79.5775 72.266C79.8708 72.75 80.0175 73.3073 80.0175 73.938V73.982C80.0175 74.6127 79.8708 75.1773 79.5775 75.676C79.2988 76.16 78.9102 76.5487 78.4115 76.842C77.9275 77.1207 77.3702 77.26 76.7395 77.26H64.3315C63.7008 77.26 63.1435 77.1207 62.6595 76.842C62.1608 76.5487 61.7722 76.16 61.4935 75.676C61.2002 75.1773 61.0535 74.6127 61.0535 73.982V73.938ZM73.407 73.938C73.407 73.3073 73.5537 72.75 73.847 72.266C74.1257 71.7673 74.5144 71.3787 75.013 71.1C75.497 70.8067 76.0544 70.66 76.685 70.66H89.093C89.7237 70.66 90.281 70.8067 90.765 71.1C91.2637 71.3787 91.6524 71.7673 91.931 72.266C92.2244 72.75 92.371 73.3073 92.371 73.938V73.982C92.371 74.6127 92.2244 75.1773 91.931 75.676C91.6524 76.16 91.2637 76.5487 90.765 76.842C90.281 77.1207 89.7237 77.26 89.093 77.26H76.685C76.0544 77.26 75.497 77.1207 75.013 76.842C74.5144 76.5487 74.1257 76.16 73.847 75.676C73.5537 75.1773 73.407 74.6127 73.407 73.982V73.938ZM85.7605 73.938C85.7605 73.3073 85.9072 72.75 86.2005 72.266C86.4792 71.7673 86.8679 71.3787 87.3665 71.1C87.8505 70.8067 88.4079 70.66 89.0385 70.66H101.447C102.077 70.66 102.635 70.8067 103.119 71.1C103.617 71.3787 104.006 71.7673 104.285 72.266C104.578 72.75 104.725 73.3073 104.725 73.938V73.982C104.725 74.6127 104.578 75.1773 104.285 75.676C104.006 76.16 103.617 76.5487 103.119 76.842C102.635 77.1207 102.077 77.26 101.447 77.26H89.0385C88.4079 77.26 87.8505 77.1207 87.3665 76.842C86.8679 76.5487 86.4792 76.16 86.2005 75.676C85.9072 75.1773 85.7605 74.6127 85.7605 73.982V73.938ZM111.305 73.938C111.305 73.3073 111.452 72.75 111.745 72.266C112.024 71.7673 112.413 71.3787 112.911 71.1C113.395 70.8067 113.953 70.66 114.583 70.66H126.991C127.622 70.66 128.179 70.8067 128.663 71.1C129.162 71.3787 129.551 71.7673 129.829 72.266C130.123 72.75 130.269 73.3073 130.269 73.938V73.982C130.269 74.6127 130.123 75.1773 129.829 75.676C129.551 76.16 129.162 76.5487 128.663 76.842C128.179 77.1207 127.622 77.26 126.991 77.26H114.583C113.953 77.26 113.395 77.1207 112.911 76.842C112.413 76.5487 112.024 76.16 111.745 75.676C111.452 75.1773 111.305 74.6127 111.305 73.982V73.938ZM123.659 73.938C123.659 73.3073 123.806 72.75 124.099 72.266C124.378 71.7673 124.766 71.3787 125.265 71.1C125.749 70.8067 126.306 70.66 126.937 70.66H133.273C133.904 70.66 134.461 70.8067 134.945 71.1C135.444 71.3787 135.832 71.7673 136.111 72.266C136.404 72.75 136.551 73.3073 136.551 73.938V73.982C136.551 74.6127 136.404 75.1773 136.111 75.676C135.832 76.16 135.444 76.5487 134.945 76.842C134.461 77.1207 133.904 77.26 133.273 77.26H126.937C126.306 77.26 125.749 77.1207 125.265 76.842C124.766 76.5487 124.378 76.16 124.099 75.676C123.806 75.1773 123.659 74.6127 123.659 73.982V73.938ZM129.954 73.938C129.954 73.3073 130.101 72.75 130.394 72.266C130.673 71.7673 131.061 71.3787 131.56 71.1C132.044 70.8067 132.601 70.66 133.232 70.66H145.64C146.271 70.66 146.828 70.8067 147.312 71.1C147.811 71.3787 148.199 71.7673 148.478 72.266C148.771 72.75 148.918 73.3073 148.918 73.938V73.982C148.918 74.6127 148.771 75.1773 148.478 75.676C148.199 76.16 147.811 76.5487 147.312 76.842C146.828 77.1207 146.271 77.26 145.64 77.26H133.232C132.601 77.26 132.044 77.1207 131.56 76.842C131.061 76.5487 130.673 76.16 130.394 75.676C130.101 75.1773 129.954 74.6127 129.954 73.982V73.938ZM142.307 73.938C142.307 73.3073 142.454 72.75 142.747 72.266C143.026 71.7673 143.415 71.3787 143.913 71.1C144.397 70.8067 144.955 70.66 145.585 70.66H157.993C158.624 70.66 159.181 70.8067 159.665 71.1C160.164 71.3787 160.553 71.7673 160.831 72.266C161.125 72.75 161.271 73.3073 161.271 73.938V73.982C161.271 74.6127 161.125 75.1773 160.831 75.676C160.553 76.16 160.164 76.5487 159.665 76.842C159.181 77.1207 158.624 77.26 157.993 77.26H145.585C144.955 77.26 144.397 77.1207 143.913 76.842C143.415 76.5487 143.026 76.16 142.747 75.676C142.454 75.1773 142.307 74.6127 142.307 73.982V73.938ZM154.661 73.938C154.661 73.3073 154.808 72.75 155.101 72.266C155.38 71.7673 155.768 71.3787 156.267 71.1C156.751 70.8067 157.308 70.66 157.939 70.66H170.347C170.978 70.66 171.535 70.8067 172.019 71.1C172.518 71.3787 172.906 71.7673 173.185 72.266C173.478 72.75 173.625 73.3073 173.625 73.938V73.982C173.625 74.6127 173.478 75.1773 173.185 75.676C172.906 76.16 172.518 76.5487 172.019 76.842C171.535 77.1207 170.978 77.26 170.347 77.26H157.939C157.308 77.26 156.751 77.1207 156.267 76.842C155.768 76.5487 155.38 76.16 155.101 75.676C154.808 75.1773 154.661 74.6127 154.661 73.982V73.938ZM167.014 73.938C167.014 73.3073 167.161 72.75 167.454 72.266C167.733 71.7673 168.122 71.3787 168.62 71.1C169.104 70.8067 169.662 70.66 170.292 70.66H176.628C177.259 70.66 177.816 70.8067 178.3 71.1C178.799 71.3787 179.188 71.7673 179.466 72.266C179.76 72.75 179.906 73.3073 179.906 73.938V73.982C179.906 74.6127 179.76 75.1773 179.466 75.676C179.188 76.16 178.799 76.5487 178.3 76.842C177.816 77.1207 177.259 77.26 176.628 77.26H170.292C169.662 77.26 169.104 77.1207 168.62 76.842C168.122 76.5487 167.733 76.16 167.454 75.676C167.161 75.1773 167.014 74.6127 167.014 73.982V73.938ZM173.309 73.938C173.309 73.3073 173.456 72.75 173.749 72.266C174.028 71.7673 174.417 71.3787 174.915 71.1C175.399 70.8067 175.957 70.66 176.587 70.66H182.923C183.554 70.66 184.111 70.8067 184.595 71.1C185.094 71.3787 185.483 71.7673 185.761 72.266C186.055 72.75 186.201 73.3073 186.201 73.938V73.982C186.201 74.6127 186.055 75.1773 185.761 75.676C185.483 76.16 185.094 76.5487 184.595 76.842C184.111 77.1207 183.554 77.26 182.923 77.26H176.587C175.957 77.26 175.399 77.1207 174.915 76.842C174.417 76.5487 174.028 76.16 173.749 75.676C173.456 75.1773 173.309 74.6127 173.309 73.982V73.938ZM179.604 73.938C179.604 73.3073 179.751 72.75 180.044 72.266C180.323 71.7673 180.712 71.3787 181.21 71.1C181.694 70.8067 182.252 70.66 182.882 70.66H189.218C189.849 70.66 190.406 70.8067 190.89 71.1C191.389 71.3787 191.778 71.7673 192.056 72.266C192.35 72.75 192.496 73.3073 192.496 73.938V73.982C192.496 74.6127 192.35 75.1773 192.056 75.676C191.778 76.16 191.389 76.5487 190.89 76.842C190.406 77.1207 189.849 77.26 189.218 77.26H182.882C182.252 77.26 181.694 77.1207 181.21 76.842C180.712 76.5487 180.323 76.16 180.044 75.676C179.751 75.1773 179.604 74.6127 179.604 73.982V73.938Z" fill="#F0F2F5"/>
+</svg>
diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss
index 2d3ea2e4f423d45714fd8a4d54090ba2fd410c07..c9c86717e679b15b4fffc89e3a591abd097da090 100644
--- a/res/themes/dark/css/_dark.pcss
+++ b/res/themes/dark/css/_dark.pcss
@@ -309,11 +309,8 @@ body {
 
 /* Splash Page Gradient */
 .mx_SplashPage::before {
-    background-image: radial-gradient(
-            53.85% 66.75% at 87.55% 0%,
-            hsla(0deg, 0%, 11%, 0.15) 0%,
-            hsla(250deg, 100%, 88%, 0) 100%
-        ),
+    background-image:
+        radial-gradient(53.85% 66.75% at 87.55% 0%, hsla(0deg, 0%, 11%, 0.15) 0%, hsla(250deg, 100%, 88%, 0) 100%),
         radial-gradient(41.93% 41.93% at 0% 0%, hsla(0deg, 0%, 38%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%),
         radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.3) 0%, hsla(0deg, 100%, 86%, 0) 100%),
         radial-gradient(106.35% 96.26% at 100% 0%, hsla(25deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%) !important;
diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss
index 32ca7d3d1a023b9dae869ef77ad4af33f36c2247..eea7197d9fcc1c0707d35875706b8db44a1738a7 100644
--- a/res/themes/legacy-light/css/_legacy-light.pcss
+++ b/res/themes/legacy-light/css/_legacy-light.pcss
@@ -10,11 +10,13 @@
 /* Noto Color Emoji contains digits, in fixed-width, therefore causing
    digits in flowed text to stand out.
    TODO: Consider putting all emoji fonts to the end rather than the front. */
-$font-family: "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
-    sans-serif, "Noto Color Emoji";
+$font-family:
+    "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
+    "Noto Color Emoji";
 
-$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
-    monospace, "Noto Color Emoji";
+$monospace-font-family:
+    "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
+    "Noto Color Emoji";
 
 /* unified palette */
 /* try to use these colors when possible */
diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.pcss b/res/themes/light-high-contrast/css/_light-high-contrast.pcss
index 213c64144016f6edc9276edf5534642b368be080..94774bc5b8bb28f5f4fdf26715a5af5eda0f86dd 100644
--- a/res/themes/light-high-contrast/css/_light-high-contrast.pcss
+++ b/res/themes/light-high-contrast/css/_light-high-contrast.pcss
@@ -163,7 +163,8 @@ $accent-1400: var(--cpd-color-green-1400);
                     &.mx_SpotlightDialog_startChat::before,
                     &.mx_SpotlightDialog_joinRoomAlias::before,
                     &.mx_SpotlightDialog_explorePublicRooms::before,
-                    &.mx_SpotlightDialog_startGroupChat::before {
+                    &.mx_SpotlightDialog_startGroupChat::before,
+                    &.mx_SpotlightDialog_searchMessages::before {
                         background-color: $background !important;
                     }
 
diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss
index 1a1705a9c15bd5c2cccc3d5929e1884092f1bf0c..a6bb29bac447b3233f28b2477f5f8335b034c390 100644
--- a/res/themes/light/css/_light.pcss
+++ b/res/themes/light/css/_light.pcss
@@ -10,11 +10,13 @@
 /* Noto Color Emoji contains digits, in fixed-width, therefore causing
    digits in flowed text to stand out.
    TODO: Consider putting all emoji fonts to the end rather than the front. */
-$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
-    sans-serif, "Noto Color Emoji";
+$font-family:
+    "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
+    "Noto Color Emoji";
 
-$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
-    monospace, "Noto Color Emoji";
+$monospace-font-family:
+    "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
+    "Noto Color Emoji";
 
 /* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */
 /* ******************** */
diff --git a/res/welcome.html b/res/welcome.html
index ef2d43bd8ffd9c7a94548b0ff35e1037c49f62f7..9fdd60a7c02b93d09d8e4cce0cf2593e788a203f 100644
--- a/res/welcome.html
+++ b/res/welcome.html
@@ -3,7 +3,7 @@
  * voodoo where we have to set display: none by default
  */
 
-    h1::after {
+    .mx_Header_title::after {
         content: "!";
     }
 
diff --git a/scripts/docker-package.sh b/scripts/docker-package.sh
index 092058711704ae4f14306da41b98cc52d2810ab4..14444de556e55a4b475940a77dfdd3d43fc297f9 100755
--- a/scripts/docker-package.sh
+++ b/scripts/docker-package.sh
@@ -3,7 +3,6 @@
 set -ex
 
 BRANCH=$(git rev-parse --abbrev-ref HEAD)
-DIST_VERSION=$(git describe --abbrev=0 --tags)
 
 DIR=$(dirname "$0")
 
@@ -13,6 +12,8 @@ DIR=$(dirname "$0")
 if [[ $BRANCH != HEAD && ! $BRANCH =~ heads/v.+ ]]
 then
     DIST_VERSION=$("$DIR"/get-version-from-git.sh)
+else
+    DIST_VERSION=$(git describe --abbrev=0 --tags)
 fi
 
 DIST_VERSION=$("$DIR"/normalize-version.sh "$DIST_VERSION")
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 666a74ff67c32076ea6aeb430b13960035b35f6d..1299815bbbc114dfce0381f86305809a27d70f47 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -45,10 +45,7 @@ getPRInfo() {
 
 # Some CIs don't give us enough info, so we just get the PR number and ask the
 # GH API for more info - "fork:branch". Some give us this directly.
-if [ -n "$BUILDKITE_BRANCH" ]; then
-    # BuildKite
-    head=$BUILDKITE_BRANCH
-elif [ -n "$PR_NUMBER" ]; then
+if [ -n "$PR_NUMBER" ]; then
     # GitHub
     getPRInfo $PR_NUMBER
 elif [ -n "$REVIEW_ID" ]; then
@@ -72,11 +69,21 @@ if [[ "$head" == *":"* ]]; then
 fi
 clone ${TRY_ORG} $defrepo ${TRY_BRANCH}
 
-# Try the target branch of the push or PR.
-if [ -n "$GITHUB_BASE_REF" ]; then
-    clone $deforg $defrepo $GITHUB_BASE_REF
-elif [ -n "$BUILDKITE_PULL_REQUEST_BASE_BRANCH" ]; then
-    clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
+# For merge queue runs we need to extract the temporary branch name
+# the ref_name will look like `gh-readonly-queue/<branch>/pr-<number>-<sha>`
+if [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then
+    withoutPrefix=${GITHUB_REF_NAME#gh-readonly-queue/}
+    clone $deforg $defrepo ${withoutPrefix%%/pr-*}
+fi
+
+# Try the target branch of the push or PR, or the branch that was pushed to
+# (ie. the 'master' branch should use matching 'master' dependencies)
+base_or_branch=$GITHUB_BASE_REF
+if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
+    base_or_branch=${GITHUB_REF}
+fi
+if [ -n "$base_or_branch" ]; then
+    clone $deforg $defrepo $base_or_branch
 fi
 
 # Try HEAD which is the branch name in Netlify (not BRANCH which is pull/xxxx/head for PR builds)
diff --git a/src/@types/common.ts b/src/@types/common.ts
index cdf969a698a6197c28a9f71906b9e406305072e4..4c8c707c4afc073820b83eaa56683d4dda1923c8 100644
--- a/src/@types/common.ts
+++ b/src/@types/common.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JSXElementConstructor } from "react";
+import { type JSX, type JSXElementConstructor } from "react";
 
 export type { NonEmptyArray, XOR, Writeable } from "matrix-js-sdk/src/matrix";
 
diff --git a/src/@types/commonmark.ts b/src/@types/commonmark.ts
index 2d3be1b2438d1e72fd89fc49e9e9e0da5cf93887..dde24d025199b497d86f015380e53b89398200f6 100644
--- a/src/@types/commonmark.ts
+++ b/src/@types/commonmark.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as commonmark from "commonmark";
+import type * as commonmark from "commonmark";
 
 declare module "commonmark" {
     export type Attr = [key: string, value: string];
diff --git a/src/@types/diff-dom.d.ts b/src/@types/diff-dom.d.ts
index 986a84dc0b122db90eedbd15a1e5d063f5b61552..12587446d0bd51130311f779b8008c5eb16e2ba8 100644
--- a/src/@types/diff-dom.d.ts
+++ b/src/@types/diff-dom.d.ts
@@ -18,6 +18,7 @@ declare module "diff-dom" {
         newValue: HTMLElement | string;
     }
 
+    // eslint-disable-next-line @typescript-eslint/no-empty-object-type
     interface IOpts {}
 
     export class DiffDOM {
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index c76c43f8298bcac2476067676ca0d89b91f95585..f19a8591cb24374cda23864e7e04ac38d81fe9c1 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -10,41 +10,43 @@ Please see LICENSE files in the repository root for full details.
 import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
 import "@types/modernizr";
 
+import type { ModuleLoader } from "@element-hq/element-web-module-api";
 import type { logger } from "matrix-js-sdk/src/logger";
-import ContentMessages from "../ContentMessages";
-import { IMatrixClientPeg } from "../MatrixClientPeg";
-import ToastStore from "../stores/ToastStore";
-import DeviceListener from "../DeviceListener";
-import { RoomListStore } from "../stores/room-list/Interface";
-import { PlatformPeg } from "../PlatformPeg";
-import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
-import { IntegrationManagers } from "../integrations/IntegrationManagers";
-import { ModalManager } from "../Modal";
-import SettingsStore from "../settings/SettingsStore";
-import { Notifier } from "../Notifier";
-import RightPanelStore from "../stores/right-panel/RightPanelStore";
-import WidgetStore from "../stores/WidgetStore";
-import LegacyCallHandler from "../LegacyCallHandler";
-import UserActivity from "../UserActivity";
-import { ModalWidgetStore } from "../stores/ModalWidgetStore";
-import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
-import VoipUserMapper from "../VoipUserMapper";
-import { SpaceStoreClass } from "../stores/spaces/SpaceStore";
-import TypingStore from "../stores/TypingStore";
-import { EventIndexPeg } from "../indexing/EventIndexPeg";
-import { VoiceRecordingStore } from "../stores/VoiceRecordingStore";
-import PerformanceMonitor from "../performance";
-import UIStore from "../stores/UIStore";
-import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
-import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
-import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
-import ActiveWidgetStore from "../stores/ActiveWidgetStore";
-import AutoRageshakeStore from "../stores/AutoRageshakeStore";
-import { IConfigOptions } from "../IConfigOptions";
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
-import { DeepReadonly } from "./common";
-import MatrixChat from "../components/structures/MatrixChat";
-import { InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
+import type ContentMessages from "../ContentMessages";
+import { type IMatrixClientPeg } from "../MatrixClientPeg";
+import type ToastStore from "../stores/ToastStore";
+import type DeviceListener from "../DeviceListener";
+import { type RoomListStore } from "../stores/room-list/Interface";
+import { type PlatformPeg } from "../PlatformPeg";
+import type RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
+import { type IntegrationManagers } from "../integrations/IntegrationManagers";
+import { type ModalManager } from "../Modal";
+import type SettingsStore from "../settings/SettingsStore";
+import { type Notifier } from "../Notifier";
+import type RightPanelStore from "../stores/right-panel/RightPanelStore";
+import type WidgetStore from "../stores/WidgetStore";
+import type LegacyCallHandler from "../LegacyCallHandler";
+import type UserActivity from "../UserActivity";
+import { type ModalWidgetStore } from "../stores/ModalWidgetStore";
+import { type WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
+import { type SpaceStoreClass } from "../stores/spaces/SpaceStore";
+import type TypingStore from "../stores/TypingStore";
+import { type EventIndexPeg } from "../indexing/EventIndexPeg";
+import { type VoiceRecordingStore } from "../stores/VoiceRecordingStore";
+import type PerformanceMonitor from "../performance";
+import type UIStore from "../stores/UIStore";
+import { type SetupEncryptionStore } from "../stores/SetupEncryptionStore";
+import { type RoomScrollStateStore } from "../stores/RoomScrollStateStore";
+import { type ConsoleLogger, type IndexedDBLogStore } from "../rageshake/rageshake";
+import type ActiveWidgetStore from "../stores/ActiveWidgetStore";
+import type AutoRageshakeStore from "../stores/AutoRageshakeStore";
+import { type IConfigOptions } from "../IConfigOptions";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type DeepReadonly } from "./common";
+import type MatrixChat from "../components/structures/MatrixChat";
+import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
+import { type ModuleApiType } from "../modules/Api.ts";
+import type { RoomListStoreV3Class } from "../stores/room-list-v3/RoomListStoreV3.ts";
 
 /* eslint-disable @typescript-eslint/naming-convention */
 
@@ -80,23 +82,15 @@ declare global {
         mxMatrixClientPeg: IMatrixClientPeg;
         mxReactSdkConfig: DeepReadonly<IConfigOptions>;
 
-        // Needed for Safari, unknown to TypeScript
-        webkitAudioContext: typeof AudioContext;
-
         // https://docs.microsoft.com/en-us/previous-versions/hh772328(v=vs.85)
         // we only ever check for its existence, so we can ignore its actual type
         MSStream?: unknown;
 
-        // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029#issuecomment-869224737
-        // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
-        OffscreenCanvas?: {
-            new (width: number, height: number): OffscreenCanvas;
-        };
-
         mxContentMessages: ContentMessages;
         mxToastStore: ToastStore;
         mxDeviceListener: DeviceListener;
         mxRoomListStore: RoomListStore;
+        mxRoomListStoreV3: RoomListStoreV3Class;
         mxRoomListLayoutStore: RoomListLayoutStore;
         mxPlatformPeg: PlatformPeg;
         mxIntegrationManagers: typeof IntegrationManagers;
@@ -109,7 +103,6 @@ declare global {
         mxLegacyCallHandler: LegacyCallHandler;
         mxUserActivity: UserActivity;
         mxModalWidgetStore: ModalWidgetStore;
-        mxVoipUserMapper: VoipUserMapper;
         mxSpaceStore: SpaceStoreClass;
         mxVoiceRecordingStore: VoiceRecordingStore;
         mxTypingStore: TypingStore;
@@ -122,6 +115,8 @@ declare global {
         mxRoomScrollStateStore?: RoomScrollStateStore;
         mxActiveWidgetStore?: ActiveWidgetStore;
         mxOnRecaptchaLoaded?: () => void;
+        mxModuleLoader: ModuleLoader;
+        mxModuleApi: ModuleApiType;
 
         // electron-only
         electron?: Electron;
@@ -135,6 +130,11 @@ declare global {
     interface Electron {
         on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void;
         send(channel: ElectronChannel, ...args: any[]): void;
+        initialise(): Promise<{
+            protocol: string;
+            sessionId: string;
+            config: IConfigOptions;
+        }>;
     }
 
     interface DesktopCapturerSource {
@@ -152,31 +152,10 @@ declare global {
         fetchWindowIcons?: boolean;
     }
 
-    interface Document {
-        // Safari & IE11 only have this prefixed: we used prefixed versions
-        // previously so let's continue to support them for now
-        webkitExitFullscreen(): Promise<void>;
-        msExitFullscreen(): Promise<void>;
-        readonly webkitFullscreenElement: Element | null;
-        readonly msFullscreenElement: Element | null;
-    }
-
-    interface Navigator {
-        userLanguage?: string;
-    }
-
     interface StorageEstimate {
         usageDetails?: { [key: string]: number };
     }
 
-    interface Element {
-        // Safari & IE11 only have this prefixed: we used prefixed versions
-        // previously so let's continue to support them for now
-        webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
-        msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
-        // scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void;
-    }
-
     // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278
     interface AudioWorkletProcessor {
         readonly port: MessagePort;
@@ -235,11 +214,4 @@ declare global {
     var mx_rage_store: IndexedDBLogStore;
 }
 
-// add method which is missing from the node typing
-declare module "url" {
-    interface Url {
-        format(): string;
-    }
-}
-
 /* eslint-enable @typescript-eslint/naming-convention */
diff --git a/src/@types/matrix-js-sdk.d.ts b/src/@types/matrix-js-sdk.d.ts
index 6ffa09dd4f82b3428fd73f1ba914f6843f4ee71a..c81c5377bfc4c80faed591611195667d86a20351 100644
--- a/src/@types/matrix-js-sdk.d.ts
+++ b/src/@types/matrix-js-sdk.d.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2024 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -11,8 +11,10 @@ import type { BLURHASH_FIELD } from "../utils/image-media";
 import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types";
 import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types";
 import type { EncryptedFile } from "matrix-js-sdk/src/types";
+import type { EmptyObject } from "matrix-js-sdk/src/matrix";
 import type { DeviceClientInformation } from "../utils/device/types.ts";
 import type { UserWidget } from "../utils/WidgetUtils-types.ts";
+import { type MediaPreviewConfig } from "./media_preview.ts";
 
 // Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
 declare module "matrix-js-sdk/src/types" {
@@ -35,7 +37,7 @@ declare module "matrix-js-sdk/src/types" {
         [JitsiCallMemberEventType]: JitsiCallMemberContent;
 
         // Unstable widgets state events
-        "im.vector.modular.widgets": IWidget | {};
+        "im.vector.modular.widgets": IWidget | EmptyObject;
         [WIDGET_LAYOUT_EVENT_TYPE]: ILayoutStateEvent;
 
         // Element custom state events
@@ -86,6 +88,8 @@ declare module "matrix-js-sdk/src/types" {
         "m.accepted_terms": {
             accepted: string[];
         };
+
+        "io.element.msc4278.media_preview_config": MediaPreviewConfig;
     }
 
     export interface AudioContent {
@@ -104,6 +108,6 @@ declare module "matrix-js-sdk/src/types" {
             // https://github.com/matrix-org/matrix-doc/pull/3246
             waveform?: number[];
         };
-        "org.matrix.msc3245.voice"?: {};
+        "org.matrix.msc3245.voice"?: EmptyObject;
     }
 }
diff --git a/src/@types/media_preview.ts b/src/@types/media_preview.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d340e64caf593c5d7fcf6f8769e8909fe9372f67
--- /dev/null
+++ b/src/@types/media_preview.ts
@@ -0,0 +1,33 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+export enum MediaPreviewValue {
+    /**
+     * Media previews should be enabled.
+     */
+    On = "on",
+    /**
+     * Media previews should only be enabled for rooms with non-public join rules.
+     */
+    Private = "private",
+    /**
+     * Media previews should be disabled.
+     */
+    Off = "off",
+}
+
+export const MEDIA_PREVIEW_ACCOUNT_DATA_TYPE = "io.element.msc4278.media_preview_config";
+export interface MediaPreviewConfig extends Record<string, unknown> {
+    /**
+     * Media preview setting for thumbnails of media in rooms.
+     */
+    media_previews: MediaPreviewValue;
+    /**
+     * Media preview settings for avatars of rooms we have been invited to.
+     */
+    invite_avatars: MediaPreviewValue.On | MediaPreviewValue.Off;
+}
diff --git a/src/@types/react.d.ts b/src/@types/react.d.ts
index 2573bc0ab94c35bb501c279e6cb6fc0eac2cdeae..61a4c59992a5e913f569daf52518226dcecbd226 100644
--- a/src/@types/react.d.ts
+++ b/src/@types/react.d.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { PropsWithChildren } from "react";
+import { type ComponentType } from "react";
 
 declare module "react" {
-    // Fix forwardRef types for Generic components - https://stackoverflow.com/a/58473012
-    function forwardRef<T, P = {}>(
-        render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement | null,
-    ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
-
     // Fix lazy types - https://stackoverflow.com/a/71017028
     function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
+
+    // Standardize defaultProps for FunctionComponent so we can write generics assuming `defaultProps` exists on ComponentType
+    interface FunctionComponent {
+        defaultProps?: unknown;
+    }
 }
diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts
index 757ea181805e5052bf796bc73eb8ac4f46c6d04f..877a0c5c4a192cc4f40ef24833b7d70eae2a0eb2 100644
--- a/src/AddThreepid.ts
+++ b/src/AddThreepid.ts
@@ -9,21 +9,22 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    IAddThreePidOnlyBody,
-    IRequestMsisdnTokenResponse,
-    IRequestTokenResponse,
-    MatrixClient,
+    type IAddThreePidOnlyBody,
+    type IRequestMsisdnTokenResponse,
+    type IRequestTokenResponse,
+    type MatrixClient,
     MatrixError,
     HTTPError,
-    IThreepid,
-    UIAResponse,
+    type IThreepid,
 } from "matrix-js-sdk/src/matrix";
 
 import Modal from "./Modal";
 import { _t, UserFriendlyError } from "./languageHandler";
 import IdentityAuthClient from "./IdentityAuthClient";
 import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
-import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "./components/views/dialogs/InteractiveAuthDialog";
+import InteractiveAuthDialog, {
+    type InteractiveAuthDialogProps,
+} from "./components/views/dialogs/InteractiveAuthDialog";
 
 function getIdServerDomain(matrixClient: MatrixClient): string {
     const idBaseUrl = matrixClient.getIdentityServerUrl(true);
@@ -78,6 +79,8 @@ export default class AddThreepid {
         } catch (err) {
             if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
                 throw new UserFriendlyError("settings|general|email_address_in_use", { cause: err });
+            } else if (err instanceof MatrixError && err.errcode === "M_THREEPID_MEDIUM_NOT_SUPPORTED") {
+                throw new UserFriendlyError("settings|general|email_adding_unsupported_by_hs", { cause: err });
             }
             // Otherwise, just blurt out the same error
             throw err;
@@ -120,6 +123,8 @@ export default class AddThreepid {
      * @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in
      * @param {string} phoneNumber The national or international formatted phone number to add
      * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
+     *
+     * @throws {UserFriendlyError} An appropriate user-friendly error if the verification code could not be sent.
      */
     public async addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
         try {
@@ -135,6 +140,10 @@ export default class AddThreepid {
         } catch (err) {
             if (err instanceof MatrixError && err.errcode === "M_THREEPID_IN_USE") {
                 throw new UserFriendlyError("settings|general|msisdn_in_use", { cause: err });
+            } else if (err instanceof MatrixError && err.errcode === "M_THREEPID_MEDIUM_NOT_SUPPORTED") {
+                throw new UserFriendlyError("settings|general|msisdn_adding_unsupported_by_hs", { cause: err });
+            } else if (err instanceof MatrixError && err.errcode === "M_INVALID_PARAM") {
+                throw new UserFriendlyError("settings|general|invalid_phone_number", { cause: err });
             }
             // Otherwise, just blurt out the same error
             throw err;
@@ -179,9 +188,7 @@ export default class AddThreepid {
      * with a "message" property which contains a human-readable message detailing why
      * the request failed.
      */
-    public async checkEmailLinkClicked(): Promise<
-        [success?: boolean, result?: UIAResponse<IAddThreePidOnlyBody> | Error | null]
-    > {
+    public async checkEmailLinkClicked(): Promise<[success?: boolean, result?: IAddThreePidOnlyBody | Error | null]> {
         try {
             if (this.bind) {
                 const authClient = new IdentityAuthClient();
@@ -249,6 +256,7 @@ export default class AddThreepid {
      * @param {{type: string, session?: string}} auth UI auth object
      * @return {Promise<Object>} Response from /3pid/add call (in current spec, an empty object)
      */
+    // eslint-disable-next-line @typescript-eslint/no-empty-object-type
     private makeAddThreepidOnlyRequest = (auth?: IAddThreePidOnlyBody["auth"] | null): Promise<{}> => {
         return this.matrixClient.addThreePidOnly({
             sid: this.sessionId!,
@@ -267,7 +275,7 @@ export default class AddThreepid {
      */
     public async haveMsisdnToken(
         msisdnToken: string,
-    ): Promise<[success?: boolean, result?: UIAResponse<IAddThreePidOnlyBody> | Error | null]> {
+    ): Promise<[success?: boolean, result?: IAddThreePidOnlyBody | Error | null]> {
         const authClient = new IdentityAuthClient();
 
         if (this.submitUrl) {
diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx
index e1b80c9d5a24c8aadfa8a18ba0e6b3c90346672d..e3d9c2f5240cf917b4af376e37cb59ea5df53e8a 100644
--- a/src/AsyncWrapper.tsx
+++ b/src/AsyncWrapper.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, Suspense } from "react";
+import React, { type ReactNode, Suspense } from "react";
 
 import { _t } from "./languageHandler";
 import BaseDialog from "./components/views/dialogs/BaseDialog";
diff --git a/src/Avatar.ts b/src/Avatar.ts
index a6725710d1db483cc7ceb09ab6fc737cf6b45c2c..921332c250cb9a7e0140f9e7745d0a36ff8af98a 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomMember, User, Room, ResizeMethod } from "matrix-js-sdk/src/matrix";
+import { type RoomMember, type User, type Room, type ResizeMethod } from "matrix-js-sdk/src/matrix";
 import { useIdColorHash } from "@vector-im/compound-web";
 
 import DMRoomMap from "./utils/DMRoomMap";
@@ -147,11 +147,12 @@ export function avatarUrlForRoom(
     width?: number,
     height?: number,
     resizeMethod?: ResizeMethod,
+    avatarMxcOverride?: string,
 ): string | null {
     if (!room) return null; // null-guard
-
-    if (room.getMxcAvatarUrl()) {
-        const media = mediaFromMxc(room.getMxcAvatarUrl() ?? undefined);
+    const mxc = avatarMxcOverride ?? room.getMxcAvatarUrl();
+    if (mxc) {
+        const media = mediaFromMxc(mxc);
         if (width !== undefined && height !== undefined) {
             return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
         }
diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts
index db4802d4bbad906bb8aacb58cd516ac5f621a54e..87635a421c5fca722dd4e6a3c6404bdf0ec8f77d 100644
--- a/src/BasePlatform.ts
+++ b/src/BasePlatform.ts
@@ -10,25 +10,26 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
-    MatrixEvent,
-    Room,
-    SSOAction,
+    type MatrixClient,
+    type MatrixEvent,
+    type Room,
+    type SSOAction,
     encodeUnpaddedBase64,
-    OidcRegistrationClientMetadata,
+    type OidcRegistrationClientMetadata,
+    MatrixEventEvent,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import dis from "./dispatcher/dispatcher";
-import BaseEventIndexManager from "./indexing/BaseEventIndexManager";
-import { ActionPayload } from "./dispatcher/payloads";
-import { CheckUpdatesPayload } from "./dispatcher/payloads/CheckUpdatesPayload";
+import type BaseEventIndexManager from "./indexing/BaseEventIndexManager";
+import { type ActionPayload } from "./dispatcher/payloads";
+import { type CheckUpdatesPayload } from "./dispatcher/payloads/CheckUpdatesPayload";
 import { Action } from "./dispatcher/actions";
 import { hideToast as hideUpdateToast } from "./toasts/UpdateToast";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import { idbLoad, idbSave, idbDelete } from "./utils/StorageAccess";
-import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
-import { IConfigOptions } from "./IConfigOptions";
+import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { type IConfigOptions } from "./IConfigOptions";
 import SdkConfig from "./SdkConfig";
 import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling";
 import Favicon from "./favicon.ts";
@@ -71,7 +72,7 @@ export default abstract class BasePlatform {
     protected _favicon?: Favicon;
 
     protected constructor() {
-        dis.register(this.onAction);
+        dis.register(this.onAction.bind(this));
         this.startUpdateCheck = this.startUpdateCheck.bind(this);
     }
 
@@ -84,14 +85,14 @@ export default abstract class BasePlatform {
      */
     public abstract getDefaultDeviceDisplayName(): string;
 
-    protected onAction = (payload: ActionPayload): void => {
+    protected onAction(payload: ActionPayload): void {
         switch (payload.action) {
             case "on_client_not_viable":
             case Action.OnLoggedOut:
                 this.setNotificationCount(0);
                 break;
         }
-    };
+    }
 
     // Used primarily for Analytics
     public abstract getHumanReadableName(): string;
@@ -228,6 +229,16 @@ export default abstract class BasePlatform {
             window.focus();
         };
 
+        const closeHandler = (): void => notification.close();
+
+        // Clear a notification from a redacted event.
+        if (ev) {
+            ev.once(MatrixEventEvent.BeforeRedaction, closeHandler);
+            notification.onclose = () => {
+                ev.off(MatrixEventEvent.BeforeRedaction, closeHandler);
+            };
+        }
+
         return notification;
     }
 
diff --git a/src/BlurhashEncoder.ts b/src/BlurhashEncoder.ts
index aef0746dbe0e25c1b4618cdf907759cc530a910e..ab6969d0c8ddee70063c33d3e02a11855ce7d03b 100644
--- a/src/BlurhashEncoder.ts
+++ b/src/BlurhashEncoder.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Request, Response } from "./workers/blurhash.worker.ts";
+import { type Request, type Response } from "./workers/blurhash.worker.ts";
 import { WorkerManager } from "./WorkerManager";
 import blurhashWorkerFactory from "./workers/blurhashWorkerFactory";
 
diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts
index 060c8ab994d3e67a0cf7f8ac08bff87136f172c4..c5e34d7130727368072860129091cb272ae1cb23 100644
--- a/src/ContentMessages.ts
+++ b/src/ContentMessages.ts
@@ -9,23 +9,23 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
+    type MatrixClient,
     MsgType,
     HTTPError,
-    IEventRelation,
-    ISendEventResponse,
-    MatrixEvent,
-    UploadOpts,
-    UploadProgress,
+    type IEventRelation,
+    type ISendEventResponse,
+    type MatrixEvent,
+    type UploadOpts,
+    type UploadProgress,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
 import {
-    ImageInfo,
-    AudioInfo,
-    VideoInfo,
-    EncryptedFile,
-    MediaEventContent,
-    MediaEventInfo,
+    type ImageInfo,
+    type AudioInfo,
+    type VideoInfo,
+    type EncryptedFile,
+    type MediaEventContent,
+    type MediaEventInfo,
 } from "matrix-js-sdk/src/types";
 import encrypt from "matrix-encrypt-attachment";
 import extractPngChunks from "png-chunks-extract";
@@ -38,11 +38,11 @@ import Modal from "./Modal";
 import Spinner from "./components/views/elements/Spinner";
 import { Action } from "./dispatcher/actions";
 import {
-    UploadCanceledPayload,
-    UploadErrorPayload,
-    UploadFinishedPayload,
-    UploadProgressPayload,
-    UploadStartedPayload,
+    type UploadCanceledPayload,
+    type UploadErrorPayload,
+    type UploadFinishedPayload,
+    type UploadProgressPayload,
+    type UploadStartedPayload,
 } from "./dispatcher/payloads/UploadPayload";
 import { RoomUpload } from "./models/RoomUpload";
 import SettingsStore from "./settings/SettingsStore";
diff --git a/src/CreateCrossSigning.ts b/src/CreateCrossSigning.ts
index 0c043d9d2bb95b5185f0ef0b8a2407e30dab3e2b..c8e7aa3e73d3ef2401a09be214c84730ecc11c95 100644
--- a/src/CreateCrossSigning.ts
+++ b/src/CreateCrossSigning.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AuthDict, MatrixClient, MatrixError, UIAResponse } from "matrix-js-sdk/src/matrix";
+import { type AuthDict, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 
 import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
 import Modal from "./Modal";
@@ -31,49 +31,50 @@ export async function createCrossSigning(cli: MatrixClient): Promise<void> {
         throw new Error("No crypto API found!");
     }
 
-    const doBootstrapUIAuth = async (
-        makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
-    ): Promise<void> => {
-        try {
-            await makeRequest({});
-        } catch (error) {
-            if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
-                // Not a UIA response
-                throw error;
-            }
-
-            const dialogAesthetics = {
-                [SSOAuthEntry.PHASE_PREAUTH]: {
-                    title: _t("auth|uia|sso_title"),
-                    body: _t("auth|uia|sso_preauth_body"),
-                    continueText: _t("auth|sso"),
-                    continueKind: "primary",
-                },
-                [SSOAuthEntry.PHASE_POSTAUTH]: {
-                    title: _t("encryption|confirm_encryption_setup_title"),
-                    body: _t("encryption|confirm_encryption_setup_body"),
-                    continueText: _t("action|confirm"),
-                    continueKind: "primary",
-                },
-            };
+    await cryptoApi.bootstrapCrossSigning({
+        authUploadDeviceSigningKeys: (makeRequest) => uiAuthCallback(cli, makeRequest),
+    });
+}
 
-            const { finished } = Modal.createDialog(InteractiveAuthDialog, {
-                title: _t("encryption|bootstrap_title"),
-                matrixClient: cli,
-                makeRequest,
-                aestheticsForStagePhases: {
-                    [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
-                    [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
-                },
-            });
-            const [confirmed] = await finished;
-            if (!confirmed) {
-                throw new Error("Cross-signing key upload auth canceled");
-            }
+export async function uiAuthCallback(
+    matrixClient: MatrixClient,
+    makeRequest: (authData: AuthDict) => Promise<void>,
+): Promise<void> {
+    try {
+        await makeRequest({});
+    } catch (error) {
+        if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
+            // Not a UIA response
+            throw error;
         }
-    };
 
-    await cryptoApi.bootstrapCrossSigning({
-        authUploadDeviceSigningKeys: doBootstrapUIAuth,
-    });
+        const dialogAesthetics = {
+            [SSOAuthEntry.PHASE_PREAUTH]: {
+                title: _t("auth|uia|sso_title"),
+                body: _t("auth|uia|sso_preauth_body"),
+                continueText: _t("auth|sso"),
+                continueKind: "primary",
+            },
+            [SSOAuthEntry.PHASE_POSTAUTH]: {
+                title: _t("encryption|confirm_encryption_setup_title"),
+                body: _t("encryption|confirm_encryption_setup_body"),
+                continueText: _t("action|confirm"),
+                continueKind: "primary",
+            },
+        };
+
+        const { finished } = Modal.createDialog(InteractiveAuthDialog, {
+            title: "",
+            matrixClient,
+            makeRequest,
+            aestheticsForStagePhases: {
+                [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
+                [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+            },
+        });
+        const [confirmed] = await finished;
+        if (!confirmed) {
+            throw new Error("Cross-signing key upload auth canceled");
+        }
+    }
 }
diff --git a/src/DateUtils.ts b/src/DateUtils.ts
index 7c7b7dd7e6cf890b4a97c41fe3844e341fb7a12b..e788ca09bfd7c89d02a4f483e320447bde2ab57b 100644
--- a/src/DateUtils.ts
+++ b/src/DateUtils.ts
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import { _t, getUserLanguage } from "./languageHandler";
 import { getUserTimezone } from "./TimezoneHandler";
@@ -38,7 +38,7 @@ export function getMonthsArray(month: Intl.DateTimeFormatOptions["month"] = "sho
 
 // XXX: Ideally we could just specify `hour12: boolean` but it has issues on Chrome in the `en` locale
 // https://support.google.com/chrome/thread/29828561?hl=en
-function getTwelveHourOptions(showTwelveHour: boolean): Intl.DateTimeFormatOptions {
+export function getTwelveHourOptions(showTwelveHour: boolean): Intl.DateTimeFormatOptions {
     return {
         hourCycle: showTwelveHour ? "h12" : "h23",
     };
diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts
index 6a226ad3fe6ccf3819681e2acaabd4e071916b37..8725a0db0ddaa007fbc0e27cf89935599840bb43 100644
--- a/src/DecryptionFailureTracker.ts
+++ b/src/DecryptionFailureTracker.ts
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import ScalableBloomFilter from "bloom-filters/dist/bloom/scalable-bloom-filter";
-import { HttpApiEvent, MatrixClient, MatrixEventEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error";
+import { HttpApiEvent, type MatrixClient, MatrixEventEvent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error";
 import { DecryptionFailureCode, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
 import { PosthogAnalytics } from "./PosthogAnalytics";
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index e50f0d3f9bc53e50abe66b45b3c40701c2b14280..e13d296bc1558f28bc5805463f51f970bf11ceee 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -7,17 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixEvent,
+    type MatrixEvent,
     ClientEvent,
     EventType,
-    MatrixClient,
+    type MatrixClient,
     RoomStateEvent,
-    SyncState,
+    type SyncState,
     ClientStoppedError,
 } from "matrix-js-sdk/src/matrix";
-import { logger } from "matrix-js-sdk/src/logger";
-import { CryptoEvent, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
-import { CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
+import { logger as baseLogger, LogSpan } from "matrix-js-sdk/src/logger";
+import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 
 import { PosthogAnalytics } from "./PosthogAnalytics";
 import dis from "./dispatcher/dispatcher";
@@ -35,12 +36,12 @@ import {
     showToast as showUnverifiedSessionsToast,
 } from "./toasts/UnverifiedSessionToast";
 import { isSecretStorageBeingAccessed } from "./SecurityManager";
-import { ActionPayload } from "./dispatcher/payloads";
+import { type ActionPayload } from "./dispatcher/payloads";
 import { Action } from "./dispatcher/actions";
 import SdkConfig from "./SdkConfig";
 import PlatformPeg from "./PlatformPeg";
 import { recordClientInformation, removeClientInformation } from "./utils/device/clientInformation";
-import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
+import SettingsStore, { type CallbackFn } from "./settings/SettingsStore";
 import { UIFeature } from "./settings/UIFeature";
 import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
 import { getUserDeviceIds } from "./utils/crypto/deviceInfo";
@@ -48,6 +49,16 @@ import { asyncSomeParallel } from "./utils/arrays.ts";
 
 const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
 
+/**
+ * Unfortunately-named account data key used by Element X to indicate that the user
+ * has chosen to disable server side key backups.
+ *
+ * We need to set and honour this to prevent Element X from automatically turning key backup back on.
+ */
+export const BACKUP_DISABLED_ACCOUNT_DATA_KEY = "m.org.matrix.custom.backup_disabled";
+
+const logger = baseLogger.getChild("DeviceListener:");
+
 export default class DeviceListener {
     private dispatcherRef?: string;
     // device IDs for which the user has dismissed the verify toast ('Later')
@@ -86,9 +97,11 @@ export default class DeviceListener {
         this.client.on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
         this.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
         this.client.on(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged);
+        this.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatusChanged);
         this.client.on(ClientEvent.AccountData, this.onAccountData);
         this.client.on(ClientEvent.Sync, this.onSync);
         this.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
+        this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
         this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn");
         // only configurable in config, so we don't need to watch the value
         this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder);
@@ -111,6 +124,7 @@ export default class DeviceListener {
             this.client.removeListener(ClientEvent.AccountData, this.onAccountData);
             this.client.removeListener(ClientEvent.Sync, this.onSync);
             this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
+            this.client.removeListener(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
         }
         SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
         dis.unregister(this.dispatcherRef);
@@ -119,7 +133,7 @@ export default class DeviceListener {
         this.dismissedThisDeviceToast = false;
         this.keyBackupInfo = null;
         this.keyBackupFetchedAt = null;
-        this.keyBackupStatusChecked = false;
+        this.cachedKeyBackupUploadActive = undefined;
         this.ourDeviceIdsAtStart = null;
         this.displayingToastsForDeviceIds = new Set();
         this.client = undefined;
@@ -131,7 +145,7 @@ export default class DeviceListener {
      * @param {String[]} deviceIds List of device IDs to dismiss notifications for
      */
     public async dismissUnverifiedSessions(deviceIds: Iterable<string>): Promise<void> {
-        logger.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(","));
+        logger.debug("Dismissing unverified sessions: " + Array.from(deviceIds).join(","));
         for (const d of deviceIds) {
             this.dismissed.add(d);
         }
@@ -144,6 +158,13 @@ export default class DeviceListener {
         this.recheck();
     }
 
+    /**
+     * Set the account data "m.org.matrix.custom.backup_disabled" to { "disabled": true }.
+     */
+    public async recordKeyBackupDisabled(): Promise<void> {
+        await this.client?.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
+    }
+
     private async ensureDeviceIdsAtStartPopulated(): Promise<void> {
         if (this.ourDeviceIdsAtStart === null) {
             this.ourDeviceIdsAtStart = await this.getDeviceIds();
@@ -179,6 +200,11 @@ export default class DeviceListener {
         this.recheck();
     };
 
+    private onKeyBackupStatusChanged = (): void => {
+        this.cachedKeyBackupUploadActive = undefined;
+        this.recheck();
+    };
+
     private onCrossSingingKeysChanged = (): void => {
         this.recheck();
     };
@@ -188,11 +214,13 @@ export default class DeviceListener {
         // * migrated SSSS to symmetric
         // * uploaded keys to secret storage
         // * completed secret storage creation
+        // * disabled key backup
         // which result in account data changes affecting checks below.
         if (
             ev.getType().startsWith("m.secret_storage.") ||
             ev.getType().startsWith("m.cross_signing.") ||
-            ev.getType() === "m.megolm_backup.v1"
+            ev.getType() === "m.megolm_backup.v1" ||
+            ev.getType() === BACKUP_DISABLED_ACCOUNT_DATA_KEY
         ) {
             this.recheck();
         }
@@ -218,6 +246,11 @@ export default class DeviceListener {
         this.updateClientInformation();
     };
 
+    private onToDeviceEvent = (event: MatrixEvent): void => {
+        // Receiving a 4S secret can mean we are in sync where we were not before.
+        if (event.getType() === EventType.SecretSend) this.recheck();
+    };
+
     /**
      * Fetch the key backup information from the server.
      *
@@ -266,18 +299,29 @@ export default class DeviceListener {
 
     private async doRecheck(): Promise<void> {
         if (!this.running || !this.client) return; // we have been stopped
+        const logSpan = new LogSpan(logger, "check_" + secureRandomString(4));
+
         const cli = this.client;
 
         // cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1
-        if (!(await cli.isVersionSupported("v1.1"))) return;
+        if (!(await cli.isVersionSupported("v1.1"))) {
+            logSpan.debug("cross-signing not supported");
+            return;
+        }
 
         const crypto = cli.getCrypto();
-        if (!crypto) return;
+        if (!crypto) {
+            logSpan.debug("crypto not enabled");
+            return;
+        }
 
         // don't recheck until the initial sync is complete: lots of account data events will fire
         // while the initial sync is processing and we don't need to recheck on each one of them
         // (we add a listener on sync to do once check after the initial sync is done)
-        if (!cli.isInitialSyncComplete()) return;
+        if (!cli.isInitialSyncComplete()) {
+            logSpan.debug("initial sync not yet complete");
+            return;
+        }
 
         const crossSigningReady = await crypto.isCrossSigningReady();
         const secretStorageReady = await crypto.isSecretStorageReady();
@@ -295,10 +339,20 @@ export default class DeviceListener {
                 (await crypto.getDeviceVerificationStatus(cli.getSafeUserId(), cli.deviceId!))?.crossSigningVerified,
             );
 
-        const allSystemsReady = crossSigningReady && secretStorageReady && allCrossSigningSecretsCached;
+        const keyBackupUploadActive = await this.isKeyBackupUploadActive();
+        const backupDisabled = await this.recheckBackupDisabled(cli);
+
+        // We warn if key backup upload is turned off and we have not explicitly
+        // said we are OK with that.
+        const keyBackupIsOk = keyBackupUploadActive || backupDisabled;
+
+        const allSystemsReady =
+            crossSigningReady && keyBackupIsOk && secretStorageReady && allCrossSigningSecretsCached;
+
         await this.reportCryptoSessionStateToAnalytics(cli);
 
         if (this.dismissedThisDeviceToast || allSystemsReady) {
+            logSpan.info("No toast needed");
             hideSetupEncryptionToast();
 
             this.checkKeyBackupStatus();
@@ -309,21 +363,38 @@ export default class DeviceListener {
             if (!crossSigningReady) {
                 // This account is legacy and doesn't have cross-signing set up at all.
                 // Prompt the user to set it up.
+                logSpan.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast");
                 showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
             } else if (!isCurrentDeviceTrusted) {
                 // cross signing is ready but the current device is not trusted: prompt the user to verify
+                logSpan.info("Current device not verified: showing VERIFY_THIS_SESSION toast");
                 showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
             } else if (!allCrossSigningSecretsCached) {
                 // cross signing ready & device trusted, but we are missing secrets from our local cache.
                 // prompt the user to enter their recovery key.
+                logSpan.info(
+                    "Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast",
+                    crossSigningStatus.privateKeysCachedLocally,
+                );
                 showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC);
+            } else if (!keyBackupIsOk) {
+                logSpan.info("Key backup upload is unexpectedly turned off: showing TURN_ON_KEY_STORAGE toast");
+                showSetupEncryptionToast(SetupKind.TURN_ON_KEY_STORAGE);
             } else if (defaultKeyId === null) {
-                // the user just hasn't set up 4S yet: prompt them to do so
-                showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
+                // The user just hasn't set up 4S yet: if they have key
+                // backup, prompt them to turn on recovery too. (If not, they
+                // have explicitly opted out, so don't hassle them.)
+                if (keyBackupUploadActive) {
+                    logSpan.info("No default 4S key: showing SET_UP_RECOVERY toast");
+                    showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
+                } else {
+                    logSpan.info("No default 4S key but backup disabled: no toast needed");
+                    hideSetupEncryptionToast();
+                }
             } else {
                 // some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did
                 // in 'other' situations. Possibly we should consider prompting for a full reset in this case?
-                logger.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", {
+                logSpan.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", {
                     crossSigningReady,
                     secretStorageReady,
                     allCrossSigningSecretsCached,
@@ -332,6 +403,8 @@ export default class DeviceListener {
                 });
                 showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
             }
+        } else {
+            logSpan.info("Not yet ready, but shouldShowSetupEncryptionToast==false");
         }
 
         // This needs to be done after awaiting on getUserDeviceInfo() above, so
@@ -364,9 +437,9 @@ export default class DeviceListener {
             }
         }
 
-        logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(","));
-        logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(","));
-        logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(","));
+        logSpan.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(","));
+        logSpan.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(","));
+        logSpan.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(","));
 
         const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed();
 
@@ -391,7 +464,7 @@ export default class DeviceListener {
         // ...and hide any we don't need any more
         for (const deviceId of this.displayingToastsForDeviceIds) {
             if (!newUnverifiedDeviceIds.has(deviceId)) {
-                logger.debug("Hiding unverified session toast for " + deviceId);
+                logSpan.debug("Hiding unverified session toast for " + deviceId);
                 hideUnverifiedSessionsToast(deviceId);
             }
         }
@@ -399,6 +472,16 @@ export default class DeviceListener {
         this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
     }
 
+    /**
+     * Fetch the account data for `backup_disabled`. If this is the first time,
+     * fetch it from the server (in case the initial sync has not finished).
+     * Otherwise, fetch it from the store as normal.
+     */
+    private async recheckBackupDisabled(cli: MatrixClient): Promise<boolean> {
+        const backupDisabled = await cli.getAccountDataFromServer(BACKUP_DISABLED_ACCOUNT_DATA_KEY);
+        return !!backupDisabled?.disabled;
+    }
+
     /**
      * Reports current recovery state to analytics.
      * Checks if the session is verified and if the recovery is correctly set up (i.e all secrets known locally and in 4S).
@@ -468,18 +551,42 @@ export default class DeviceListener {
      * trigger an auto-rageshake).
      */
     private checkKeyBackupStatus = async (): Promise<void> => {
-        if (this.keyBackupStatusChecked || !this.client) {
-            return;
+        if (!(await this.isKeyBackupUploadActive())) {
+            dis.dispatch({ action: Action.ReportKeyBackupNotEnabled });
+        }
+    };
+
+    /**
+     * Is key backup enabled? Use a cached answer if we have one.
+     */
+    private isKeyBackupUploadActive = async (): Promise<boolean> => {
+        if (!this.client) {
+            // To preserve existing behaviour, if there is no client, we
+            // pretend key backup upload is on.
+            //
+            // Someone looking to improve this code could try throwing an error
+            // here since we don't expect client to be undefined.
+            return true;
         }
-        const activeKeyBackupVersion = await this.client.getCrypto()?.getActiveSessionBackupVersion();
-        // if key backup is enabled, no need to check this ever again (XXX: why only when it is enabled?)
-        this.keyBackupStatusChecked = !!activeKeyBackupVersion;
 
-        if (!activeKeyBackupVersion) {
-            dis.dispatch({ action: Action.ReportKeyBackupNotEnabled });
+        const crypto = this.client.getCrypto();
+        if (!crypto) {
+            // If there is no crypto, there is no key backup
+            return false;
+        }
+
+        // If we've already cached the answer, return it.
+        if (this.cachedKeyBackupUploadActive !== undefined) {
+            return this.cachedKeyBackupUploadActive;
         }
+
+        // Fetch the answer and cache it
+        const activeKeyBackupVersion = await crypto.getActiveSessionBackupVersion();
+        this.cachedKeyBackupUploadActive = !!activeKeyBackupVersion;
+
+        return this.cachedKeyBackupUploadActive;
     };
-    private keyBackupStatusChecked = false;
+    private cachedKeyBackupUploadActive: boolean | undefined = undefined;
 
     private onRecordClientInformationSettingChange: CallbackFn = (
         _originalSettingName,
diff --git a/src/Editing.ts b/src/Editing.ts
index 3b8d2d393aa35dd651a34d3a9b8c79f481889357..063533f7e56221c9d358a74fb5a7867a56614067 100644
--- a/src/Editing.ts
+++ b/src/Editing.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { TimelineRenderingType } from "./contexts/RoomContext";
+import { type TimelineRenderingType } from "./contexts/RoomContext";
 
 export const editorRoomKey = (roomId: string, context: TimelineRenderingType): string =>
     `mx_edit_room_${roomId}_${context}`;
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index d635b23221f1223b315ed4ee5a337ef7100df816..1d17710dab4906229a70b87335cfae80b3d41ed8 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
 Copyright 2019 The Matrix.org Foundation C.I.C.
 Copyright 2017, 2018 New Vector Ltd
@@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { LegacyRef, ReactNode } from "react";
-import sanitizeHtml, { IOptions } from "sanitize-html";
+import React, { type JSX, type Key, type LegacyRef, type ReactNode } from "react";
+import sanitizeHtml, { type IOptions } from "sanitize-html";
 import classNames from "classnames";
 import katex from "katex";
 import { decode } from "html-entities";
-import { IContent } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type IContent } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
 import escapeHtml from "escape-html";
 import { getEmojiFromUnicode } from "@matrix-org/emojibase-bindings";
 
@@ -25,7 +25,7 @@ import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
 import { sanitizeHtmlParams, transformTags } from "./Linkify";
 import { graphemeSegmenter } from "./utils/strings";
 
-export { Linkify, linkifyElement, linkifyAndSanitizeHtml } from "./Linkify";
+export { Linkify, linkifyAndSanitizeHtml } from "./Linkify";
 
 // Anything outside the basic multilingual plane will be a surrogate pair
 const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
@@ -239,7 +239,7 @@ class HtmlHighlighter extends BaseHighlighter<string> {
 
 const emojiToHtmlSpan = (emoji: string): string =>
     `<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`;
-const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => (
+const emojiToJsxSpan = (emoji: string, key: Key): JSX.Element => (
     <span key={key} className="mx_Emoji" title={unicodeToShortcode(emoji)}>
         {emoji}
     </span>
@@ -294,6 +294,10 @@ export interface EventRenderOpts {
     disableBigEmoji?: boolean;
     stripReplyFallback?: boolean;
     forComposerQuote?: boolean;
+    /**
+     * Should inline media be rendered?
+     */
+    mediaIsVisible?: boolean;
 }
 
 function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: EventRenderOpts = {}): EventAnalysis {
@@ -302,6 +306,20 @@ function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: E
         sanitizeParams = composerSanitizeHtmlParams;
     }
 
+    if (opts.mediaIsVisible === false && sanitizeParams.transformTags?.["img"]) {
+        // Prevent mutating the source of sanitizeParams.
+        sanitizeParams = {
+            ...sanitizeParams,
+            transformTags: {
+                ...sanitizeParams.transformTags,
+                img: (tagName) => {
+                    // Remove element
+                    return { tagName, attribs: {} };
+                },
+            },
+        };
+    }
+
     try {
         const isFormattedBody =
             content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
@@ -365,53 +383,6 @@ function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: E
     }
 }
 
-export function bodyToDiv(
-    content: IContent,
-    highlights: Optional<string[]>,
-    opts: EventRenderOpts = {},
-    ref?: React.Ref<HTMLDivElement>,
-): ReactNode {
-    const { strippedBody, formattedBody, emojiBodyElements, className } = bodyToNode(content, highlights, opts);
-
-    return formattedBody ? (
-        <div
-            key="body"
-            ref={ref}
-            className={className}
-            dangerouslySetInnerHTML={{ __html: formattedBody }}
-            dir="auto"
-        />
-    ) : (
-        <div key="body" ref={ref} className={className} dir="auto">
-            {emojiBodyElements || strippedBody}
-        </div>
-    );
-}
-
-export function bodyToSpan(
-    content: IContent,
-    highlights: Optional<string[]>,
-    opts: EventRenderOpts = {},
-    ref?: React.Ref<HTMLSpanElement>,
-    includeDir = true,
-): ReactNode {
-    const { strippedBody, formattedBody, emojiBodyElements, className } = bodyToNode(content, highlights, opts);
-
-    return formattedBody ? (
-        <span
-            key="body"
-            ref={ref}
-            className={className}
-            dangerouslySetInnerHTML={{ __html: formattedBody }}
-            dir={includeDir ? "auto" : undefined}
-        />
-    ) : (
-        <span key="body" ref={ref} className={className} dir={includeDir ? "auto" : undefined}>
-            {emojiBodyElements || strippedBody}
-        </span>
-    );
-}
-
 interface BodyToNodeReturn {
     strippedBody: string;
     formattedBody?: string;
@@ -419,7 +390,11 @@ interface BodyToNodeReturn {
     className: string;
 }
 
-function bodyToNode(content: IContent, highlights: Optional<string[]>, opts: EventRenderOpts = {}): BodyToNodeReturn {
+export function bodyToNode(
+    content: IContent,
+    highlights: Optional<string[]>,
+    opts: EventRenderOpts = {},
+): BodyToNodeReturn {
     const eventInfo = analyseEvent(content, highlights, opts);
 
     let emojiBody = false;
@@ -505,10 +480,6 @@ export function topicToHtml(
     ref?: LegacyRef<HTMLSpanElement>,
     allowExtendedHtml = false,
 ): ReactNode {
-    if (!SettingsStore.getValue("feature_html_topic")) {
-        htmlTopic = undefined;
-    }
-
     let isFormattedTopic = !!htmlTopic;
     let topicHasEmoji = false;
     let safeTopic = "";
diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts
index bbb377e07b749c0745adde128996e9d38ff81c7a..8b88a18075c6f18e0785202ef5b8bb2c9e82e9ba 100644
--- a/src/IConfigOptions.ts
+++ b/src/IConfigOptions.ts
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IClientWellKnown } from "matrix-js-sdk/src/matrix";
+import { type IClientWellKnown } from "matrix-js-sdk/src/matrix";
 
-import { ValidatedServerConfig } from "./utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "./utils/ValidatedServerConfig";
 
 // Convention decision: All config options are lower_snake_case
 // We use an isolated file for the interface so we can mess around with the eslint options.
@@ -71,7 +71,7 @@ export interface IConfigOptions {
         url: string; // download url
         url_macos?: string;
         url_win64?: string;
-        url_win32?: string;
+        url_win64arm?: string;
         url_linux?: string;
     };
     mobile_builds: {
@@ -117,7 +117,6 @@ export interface IConfigOptions {
         obey_asserted_identity?: boolean; // MSC3086
     };
     element_call: {
-        url?: string;
         guest_spa_url?: string;
         use_exclusively?: boolean;
         participant_limit?: number;
@@ -206,6 +205,8 @@ export interface IConfigOptions {
         policy_uri?: string;
         contacts?: string[];
     };
+
+    modules?: string[];
 }
 
 export interface ISsoRedirectOptions {
diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx
index ce20bc92edb52e3cb35c10d6d422a14bb66298a1..76c151c282f96dddcfc99add449dbf2510b92c0e 100644
--- a/src/IdentityAuthClient.tsx
+++ b/src/IdentityAuthClient.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { SERVICE_TYPES, createClient, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { SERVICE_TYPES, createClient, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "./MatrixClientPeg";
diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts
index 3a4412c5dbbf4eaf2c4bda54a6de789c355d51ec..0ab6acf030ce56fa050e8f638d9bca4726e99219 100644
--- a/src/KeyBindingsDefaults.ts
+++ b/src/KeyBindingsDefaults.ts
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
 import { IS_MAC, Key } from "./Keyboard";
 import SettingsStore from "./settings/SettingsStore";
 import SdkConfig from "./SdkConfig";
-import { IKeyBindingsProvider, KeyBinding } from "./KeyBindingsManager";
+import { type IKeyBindingsProvider, type KeyBinding } from "./KeyBindingsManager";
 import { CATEGORIES, CategoryName, KeyBindingAction } from "./accessibility/KeyboardShortcuts";
 import { getKeyboardShortcuts } from "./accessibility/KeyboardShortcutUtils";
 
diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts
index 5c8bccbfd06f11699f2e8b13fbc19baa745da200..bef954f8feb7beecccdad97003686c523c0076a2 100644
--- a/src/KeyBindingsManager.ts
+++ b/src/KeyBindingsManager.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { KeyBindingAction } from "./accessibility/KeyboardShortcuts";
+import { type KeyBindingAction } from "./accessibility/KeyboardShortcuts";
 import { defaultBindingsProvider } from "./KeyBindingsDefaults";
 import { IS_MAC } from "./Keyboard";
 
diff --git a/src/Keyboard.ts b/src/Keyboard.ts
index 59c6bf77cf3274b1b6a042df7bb85b66b2b79642..de7ab059c6f22e286924b3d9c2ab951c09dcc8ee 100644
--- a/src/Keyboard.ts
+++ b/src/Keyboard.ts
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import type React from "react";
 
 export const Key = {
     HOME: "Home",
diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx
index 8aaa32f47188cc81b7fcd82b9658dae9fe80cc7b..9c3e7073d5c61e95adb9c438505580fe4a167633 100644
--- a/src/LegacyCallHandler.tsx
+++ b/src/LegacyCallHandler.tsx
@@ -12,17 +12,16 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { MatrixError, RuleId, TweakName, SyncState, TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 import {
-    CallError,
+    type CallError,
     CallErrorCode,
     CallEvent,
     CallParty,
     CallState,
     CallType,
     FALLBACK_ICE_SERVER,
-    MatrixCall,
+    type MatrixCall,
 } from "matrix-js-sdk/src/webrtc/call";
 import { logger } from "matrix-js-sdk/src/logger";
-import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
 import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
 
 import { MatrixClientPeg } from "./MatrixClientPeg";
@@ -40,17 +39,15 @@ import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
 import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
 import { UIFeature } from "./settings/UIFeature";
 import { Action } from "./dispatcher/actions";
-import VoipUserMapper from "./VoipUserMapper";
 import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "./widgets/ManagedHybrid";
 import SdkConfig from "./SdkConfig";
 import { ensureDMExists } from "./createRoom";
 import { Container, WidgetLayoutStore } from "./stores/widgets/WidgetLayoutStore";
 import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from "./toasts/IncomingLegacyCallToast";
 import ToastStore from "./stores/ToastStore";
-import Resend from "./Resend";
-import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
 import { InviteKind } from "./components/views/dialogs/InviteDialogTypes";
-import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
+import { type OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
 import { findDMForUser } from "./utils/dm/findDMForUser";
 import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
 import { localNotificationsAreSilenced } from "./utils/notifications";
@@ -60,8 +57,6 @@ import { Jitsi } from "./widgets/Jitsi.ts";
 
 export const PROTOCOL_PSTN = "m.protocol.pstn";
 export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn";
-export const PROTOCOL_SIP_NATIVE = "im.vector.protocol.sip_native";
-export const PROTOCOL_SIP_VIRTUAL = "im.vector.protocol.sip_virtual";
 
 const CHECK_PROTOCOLS_ATTEMPTS = 3;
 
@@ -108,27 +103,9 @@ const debuglog = (...args: any[]): void => {
     }
 };
 
-interface ThirdpartyLookupResponseFields {
-    /* eslint-disable camelcase */
-
-    // im.vector.sip_native
-    virtual_mxid?: string;
-    is_virtual?: boolean;
-
-    // im.vector.sip_virtual
-    native_mxid?: string;
-    is_native?: boolean;
-
-    // common
-    lookup_success?: boolean;
-
-    /* eslint-enable camelcase */
-}
-
 interface ThirdpartyLookupResponse {
     userid: string;
     protocol: string;
-    fields: ThirdpartyLookupResponseFields;
 }
 
 export enum LegacyCallHandlerEvent {
@@ -159,7 +136,6 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
     private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
     private supportsPstnProtocol: boolean | null = null;
     private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
-    private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
 
     // Map of the asserted identity users after we've looked them up using the API.
     // We need to be be able to determine the mapped room synchronously, so we
@@ -180,8 +156,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
     }
 
     /*
-     * Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
-     * if a voip_mxid_translate_pattern is set in the config)
+     * Gets the user-facing room associated with a call
      */
     public roomIdForCall(call?: MatrixCall): string | null {
         if (!call) return null;
@@ -196,7 +171,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
             }
         }
 
-        return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) ?? call.roomId ?? null;
+        return call.roomId ?? null;
     }
 
     public start(): void {
@@ -279,12 +254,6 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
                 this.supportsPstnProtocol = null;
             }
 
-            if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) {
-                this.supportsSipNativeVirtual = Boolean(
-                    protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL],
-                );
-            }
-
             this.emit(LegacyCallHandlerEvent.ProtocolSupport);
         } catch (e) {
             if (maxTries === 1) {
@@ -306,10 +275,6 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
         return this.supportsPstnProtocol ?? false;
     }
 
-    public getSupportsVirtualRooms(): boolean | null {
-        return this.supportsSipNativeVirtual;
-    }
-
     public async pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> {
         try {
             return await MatrixClientPeg.safeGet().getThirdpartyUser(
@@ -324,28 +289,6 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
         }
     }
 
-    public async sipVirtualLookup(nativeMxid: string): Promise<ThirdpartyLookupResponse[]> {
-        try {
-            return await MatrixClientPeg.safeGet().getThirdpartyUser(PROTOCOL_SIP_VIRTUAL, {
-                native_mxid: nativeMxid,
-            });
-        } catch (e) {
-            logger.warn("Failed to query SIP identity for user", e);
-            return Promise.resolve([]);
-        }
-    }
-
-    public async sipNativeLookup(virtualMxid: string): Promise<ThirdpartyLookupResponse[]> {
-        try {
-            return await MatrixClientPeg.safeGet().getThirdpartyUser(PROTOCOL_SIP_NATIVE, {
-                virtual_mxid: virtualMxid,
-            });
-        } catch (e) {
-            logger.warn("Failed to query identity for SIP user", e);
-            return Promise.resolve([]);
-        }
-    }
-
     private onCallIncoming = (call: MatrixCall): void => {
         // if the runtime env doesn't do VoIP, stop here.
         if (!MatrixClientPeg.get()?.supportsVoip()) {
@@ -538,24 +481,16 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
             }
 
             const newAssertedIdentity = call.getRemoteAssertedIdentity()?.id;
-            let newNativeAssertedIdentity = newAssertedIdentity;
-            if (newAssertedIdentity) {
-                const response = await this.sipNativeLookup(newAssertedIdentity);
-                if (response.length && response[0].fields.lookup_success) {
-                    newNativeAssertedIdentity = response[0].userid;
-                }
-            }
-            logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
 
-            if (newNativeAssertedIdentity) {
-                this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity);
+            if (newAssertedIdentity) {
+                this.assertedIdentityNativeUsers.set(call.callId, newAssertedIdentity);
 
                 // If we don't already have a room with this user, make one. This will be slightly odd
                 // if they called us because we'll be inviting them, but there's not much we can do about
                 // this if we want the actual, native room to exist (which we do). This is why it's
                 // important to only obey asserted identity in trusted environments, since anyone you're
                 // on a call with can cause you to send a room invite to someone.
-                await ensureDMExists(MatrixClientPeg.safeGet(), newNativeAssertedIdentity);
+                await ensureDMExists(MatrixClientPeg.safeGet(), newAssertedIdentity);
 
                 const newMappedRoomId = this.roomIdForCall(call);
                 logger.log(`Old room ID: ${mappedRoomId}, new room ID: ${newMappedRoomId}`);
@@ -596,7 +531,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
 
         switch (newState) {
             case CallState.Ringing: {
-                const incomingCallPushRule = new PushProcessor(MatrixClientPeg.safeGet()).getPushRuleById(
+                const incomingCallPushRule = MatrixClientPeg.safeGet().pushProcessor.getPushRuleById(
                     RuleId.IncomingCall,
                 );
                 const pushRuleEnabled = incomingCallPushRule?.enabled;
@@ -744,7 +679,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
 
     private showICEFallbackPrompt(): void {
         const cli = MatrixClientPeg.safeGet();
-        Modal.createDialog(
+        const { finished } = Modal.createDialog(
             QuestionDialog,
             {
                 title: _t("voip|misconfigured_server"),
@@ -768,13 +703,14 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
                     server: new URL(FALLBACK_ICE_SERVER).pathname,
                 }),
                 cancelButton: _t("action|ok"),
-                onFinished: (allow) => {
-                    SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
-                },
             },
             undefined,
             true,
         );
+
+        finished.then(([allow]) => {
+            SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
+        });
     }
 
     private showMediaCaptureError(call: MatrixCall): void {
@@ -811,24 +747,10 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
 
     private async placeMatrixCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> {
         const cli = MatrixClientPeg.safeGet();
-        const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId;
-        logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
-
-        // If we're using a virtual room nd there are any events pending, try to resend them,
-        // otherwise the call will fail and because its a virtual room, the user won't be able
-        // to see it to either retry or clear the pending events. There will only be call events
-        // in this queue, and since we're about to place a new call, they can only be events from
-        // previous calls that are probably stale by now, so just cancel them.
-        if (mappedRoomId !== roomId) {
-            const mappedRoom = cli.getRoom(mappedRoomId);
-            if (mappedRoom?.getPendingEvents().length) {
-                Resend.cancelUnsentEvents(mappedRoom);
-            }
-        }
 
         const timeUntilTurnCresExpire = cli.getTurnServersExpiry() - Date.now();
         logger.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
-        const call = cli.createCall(mappedRoomId)!;
+        const call = cli.createCall(roomId)!;
 
         try {
             this.addCallForRoom(roomId, call);
@@ -979,19 +901,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
         }
         const userId = results[0].userid;
 
-        // Now check to see if this is a virtual user, in which case we should find the
-        // native user
-        let nativeUserId;
-        if (this.getSupportsVirtualRooms()) {
-            const nativeLookupResults = await this.sipNativeLookup(userId);
-            const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success;
-            nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId;
-            logger.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId);
-        } else {
-            nativeUserId = userId;
-        }
-
-        const roomId = await ensureDMExists(MatrixClientPeg.safeGet(), nativeUserId);
+        const roomId = await ensureDMExists(MatrixClientPeg.safeGet(), userId);
         if (!roomId) {
             throw new Error("Failed to ensure DM exists for dialing number");
         }
diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts
index 80b08c8840f4cacbfcb3db41ef6193eb0b70e098..5641f936ae63dd3e213d98d0bffccbcd6a68a55b 100644
--- a/src/Lifecycle.ts
+++ b/src/Lifecycle.ts
@@ -9,13 +9,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactNode } from "react";
-import { createClient, MatrixClient, SSOAction, OidcTokenRefresher, decodeBase64 } from "matrix-js-sdk/src/matrix";
-import { AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types";
-import { QueryDict } from "matrix-js-sdk/src/utils";
+import { type ReactNode } from "react";
+import {
+    createClient,
+    type MatrixClient,
+    SSOAction,
+    type OidcTokenRefresher,
+    decodeBase64,
+} from "matrix-js-sdk/src/matrix";
+import { type AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types";
+import { type QueryDict } from "matrix-js-sdk/src/utils";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { IMatrixClientCreds, MatrixClientPeg, MatrixClientPegAssignOpts } from "./MatrixClientPeg";
+import { type IMatrixClientCreds, MatrixClientPeg, type MatrixClientPegAssignOpts } from "./MatrixClientPeg";
 import { ModuleRunner } from "./modules/ModuleRunner";
 import EventIndexPeg from "./indexing/EventIndexPeg";
 import createMatrixClient from "./utils/createMatrixClient";
@@ -50,12 +56,12 @@ import { setSentryUser } from "./sentry";
 import SdkConfig from "./SdkConfig";
 import { DialogOpener } from "./utils/DialogOpener";
 import { Action } from "./dispatcher/actions";
-import { OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPayload";
+import { type OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPayload";
 import { SdkContextClass } from "./contexts/SDKContext";
 import { messageForLoginError } from "./utils/ErrorUtils";
 import { completeOidcLogin } from "./utils/oidc/authorize";
 import { getOidcErrorMessage } from "./utils/oidc/error";
-import { OidcClientStore } from "./stores/oidc/OidcClientStore";
+import { type OidcClientStore } from "./stores/oidc/OidcClientStore";
 import {
     getStoredOidcClientId,
     getStoredOidcIdTokenClaims,
@@ -143,6 +149,7 @@ interface ILoadSessionOpts {
     ignoreGuest?: boolean;
     defaultDeviceDisplayName?: string;
     fragmentQueryParams?: QueryDict;
+    abortSignal?: AbortSignal;
 }
 
 /**
@@ -190,7 +197,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
 
         if (enableGuest && guestHsUrl && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token) {
             logger.log("Using guest access credentials");
-            return doSetLoggedIn(
+            await doSetLoggedIn(
                 {
                     userId: fragmentQueryParams.guest_user_id as string,
                     accessToken: fragmentQueryParams.guest_access_token as string,
@@ -200,7 +207,8 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
                 },
                 true,
                 false,
-            ).then(() => true);
+            );
+            return true;
         }
         const success = await restoreSessionFromStorage({
             ignoreGuest: Boolean(opts.ignoreGuest),
@@ -219,6 +227,11 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
         // fall back to welcome screen
         return false;
     } catch (e) {
+        // We may be aborted e.g. because our token expired, so don't show an error here
+        if (opts.abortSignal?.aborted) {
+            return false;
+        }
+
         if (e instanceof AbortLoginAndRebuildStorage) {
             // If we're aborting login because of a storage inconsistency, we don't
             // need to show the general failure dialog. Instead, just go back to welcome.
@@ -230,7 +243,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
             return false;
         }
 
-        return handleLoadSessionFailure(e);
+        return handleLoadSessionFailure(e, opts);
     }
 }
 
@@ -308,7 +321,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
     } catch (error) {
         logger.error("Failed to login via OIDC", error);
 
-        await onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
+        onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
         return false;
     }
 }
@@ -400,6 +413,39 @@ export function attemptTokenLogin(
         });
 }
 
+/**
+ * Load the pickle key inside the credentials or create it if it does not exist for this device.
+ *
+ * @param credentials Holds the device to load/store the pickle key
+ *
+ * @returns {Promise} promise which resolves to the loaded or generated pickle key or undefined if
+ *    none was loaded nor generated
+ */
+async function loadOrCreatePickleKey(credentials: IMatrixClientCreds): Promise<string | undefined> {
+    // Try to load the pickle key
+    const userId = credentials.userId;
+    const deviceId = credentials.deviceId;
+    let pickleKey = (await PlatformPeg.get()?.getPickleKey(userId, deviceId ?? "")) ?? undefined;
+    if (!pickleKey) {
+        // Create it if it did not exist
+        pickleKey =
+            userId && deviceId
+                ? ((await PlatformPeg.get()?.createPickleKey(userId, deviceId)) ?? undefined)
+                : undefined;
+        if (pickleKey) {
+            logger.log(`Created pickle key for ${credentials.userId}|${credentials.deviceId}`);
+        } else {
+            logger.log("Pickle key not created");
+        }
+    } else {
+        logger.log(
+            `Pickle key already exists for ${credentials.userId}|${credentials.deviceId} do not create a new one`,
+        );
+    }
+
+    return pickleKey;
+}
+
 /**
  * Called after a successful token login or OIDC authorization.
  * Clear storage then save new credentials in storage
@@ -407,6 +453,8 @@ export function attemptTokenLogin(
  */
 async function onSuccessfulDelegatedAuthLogin(credentials: IMatrixClientCreds): Promise<void> {
     await clearStorage();
+    // SSO does not go through setLoggedIn so we need to load/create the pickle key here too
+    credentials.pickleKey = await loadOrCreatePickleKey(credentials);
     await persistCredentials(credentials);
 
     // remember that we just logged in
@@ -420,13 +468,16 @@ type TryAgainFunction = () => void;
  * @param description error description
  * @param tryAgain OPTIONAL function to call on try again button from error dialog
  */
-async function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): Promise<void> {
-    Modal.createDialog(ErrorDialog, {
+function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): void {
+    const { finished } = Modal.createDialog(ErrorDialog, {
         title: _t("auth|oidc|error_title"),
         description,
         button: _t("action|try_again"),
+    });
+
+    finished.then(([shouldTryAgain]) => {
         // if we have a tryAgain callback, call it the primary 'try again' button was clicked in the dialog
-        onFinished: tryAgain ? (shouldTryAgain?: boolean) => shouldTryAgain && tryAgain() : undefined,
+        if (shouldTryAgain) tryAgain?.();
     });
 }
 
@@ -615,7 +666,7 @@ export async function restoreSessionFromStorage(opts?: { ignoreGuest?: boolean }
     }
 }
 
-async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
+async function handleLoadSessionFailure(e: unknown, loadSessionOpts?: ILoadSessionOpts): Promise<boolean> {
     logger.error("Unable to load session", e);
 
     const modal = Modal.createDialog(SessionRestoreErrorDialog, {
@@ -630,7 +681,7 @@ async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
     }
 
     // try, try again
-    return loadSession();
+    return loadSession(loadSessionOpts);
 }
 
 /**
@@ -649,18 +700,45 @@ async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
 export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<MatrixClient> {
     credentials.freshLogin = true;
     stopMatrixClient();
-    const pickleKey =
-        credentials.userId && credentials.deviceId
-            ? await PlatformPeg.get()?.createPickleKey(credentials.userId, credentials.deviceId)
-            : null;
+    credentials.pickleKey = await loadOrCreatePickleKey(credentials);
+    return doSetLoggedIn(credentials, true, true);
+}
 
-    if (pickleKey) {
-        logger.log(`Created pickle key for ${credentials.userId}|${credentials.deviceId}`);
-    } else {
-        logger.log("Pickle key not created");
+/**
+ * Hydrates an existing session by using the credentials provided. This will
+ * not clear any local storage, unlike setLoggedIn().
+ *
+ * Stops the existing Matrix client (without clearing its data) and starts a
+ * new one in its place. This additionally starts all other react-sdk services
+ * which use the new Matrix client.
+ *
+ * If the credentials belong to a different user from the session already stored,
+ * the old session will be cleared automatically.
+ *
+ * @param {IMatrixClientCreds} credentials The credentials to use
+ *
+ * @returns {Promise} promise which resolves to the new MatrixClient once it has been started
+ */
+export async function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
+    const oldUserId = MatrixClientPeg.safeGet().getUserId();
+    const oldDeviceId = MatrixClientPeg.safeGet().getDeviceId();
+
+    stopMatrixClient(); // unsets MatrixClientPeg.get()
+    localStorage.removeItem("mx_soft_logout");
+    _isLoggingOut = false;
+
+    const overwrite = credentials.userId !== oldUserId || credentials.deviceId !== oldDeviceId;
+    if (overwrite) {
+        logger.warn("Clearing all data: Old session belongs to a different user/session");
     }
 
-    return doSetLoggedIn({ ...credentials, pickleKey: pickleKey ?? undefined }, true, true);
+    if (!credentials.pickleKey && credentials.deviceId !== undefined) {
+        logger.info("Lifecycle#hydrateSession: Pickle key not provided - trying to get one");
+        credentials.pickleKey =
+            (await PlatformPeg.get()?.getPickleKey(credentials.userId, credentials.deviceId)) ?? undefined;
+    }
+
+    return doSetLoggedIn(credentials, overwrite, false);
 }
 
 /**
@@ -1037,7 +1115,9 @@ export async function onLoggedOut(): Promise<void> {
  * @param {object} opts Options for how to clear storage.
  * @returns {Promise} promise which resolves once the stores have been cleared
  */
-async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
+export async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
+    logger.info(`Clearing storage, deleteEverything=${opts?.deleteEverything}`);
+
     if (window.localStorage) {
         // get the currently defined device language, if set, so we can restore it later
         const language = SettingsStore.getValueAt(SettingLevel.DEVICE, "language", null, true, true);
@@ -1118,12 +1198,13 @@ window.mxLoginWithAccessToken = async (hsUrl: string, accessToken: string): Prom
         baseUrl: hsUrl,
         accessToken,
     });
-    const { user_id: userId } = await tempClient.whoami();
+    const { user_id: userId, device_id: deviceId } = await tempClient.whoami();
     await doSetLoggedIn(
         {
             homeserverUrl: hsUrl,
             accessToken,
             userId,
+            deviceId,
         },
         true,
         false,
diff --git a/src/Linkify.tsx b/src/Linkify.tsx
index db4c70228a1b0e48f0857a4631ec7a61004dc5b7..846bf8e82d5ec9269ab9aa7968bedfb203bf392c 100644
--- a/src/Linkify.tsx
+++ b/src/Linkify.tsx
@@ -1,23 +1,17 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2024 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
-import sanitizeHtml, { IOptions } from "sanitize-html";
+import React, { type ReactElement } from "react";
+import sanitizeHtml, { type IOptions } from "sanitize-html";
 import { merge } from "lodash";
 import _Linkify from "linkify-react";
 
-import {
-    _linkifyElement,
-    _linkifyString,
-    ELEMENT_URL_PATTERN,
-    options as linkifyMatrixOptions,
-} from "./linkify-matrix";
-import SettingsStore from "./settings/SettingsStore";
+import { _linkifyString, ELEMENT_URL_PATTERN, options as linkifyMatrixOptions } from "./linkify-matrix";
 import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
 import { mediaFromMxc } from "./customisations/Media";
 import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
@@ -52,10 +46,7 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
         // Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
         // because transformTags is used _before_ we filter by allowedSchemesByTag and
         // we don't want to allow images with `https?` `src`s.
-        // We also drop inline images (as if they were not present at all) when the "show
-        // images" preference is disabled. Future work might expose some UI to reveal them
-        // like standalone image events have.
-        if (!src || !SettingsStore.getValue("showImages")) {
+        if (!src) {
             return { tagName, attribs: {} };
         }
 
@@ -83,7 +74,6 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
         if (requestedHeight) {
             attribs.style += "height: 100%;";
         }
-
         attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
         return { tagName, attribs };
     },
@@ -223,17 +213,6 @@ export function linkifyString(str: string, options = linkifyMatrixOptions): stri
     return _linkifyString(str, options);
 }
 
-/**
- * Linkifies the given DOM element. This is a wrapper around 'linkifyjs/element'.
- *
- * @param {object} element DOM element to linkify
- * @param {object} [options] Options for linkifyElement. Default: linkifyMatrixOptions
- * @returns {object}
- */
-export function linkifyElement(element: HTMLElement, options = linkifyMatrixOptions): HTMLElement {
-    return _linkifyElement(element, options);
-}
-
 /**
  * Linkify the given string and sanitize the HTML afterwards.
  *
diff --git a/src/Livestream.ts b/src/Livestream.ts
index 9cda793385aab16af8fbefa319d88b55006ae531..5fa315b442758036f2df7055d3a8c3c2fdc37292 100644
--- a/src/Livestream.ts
+++ b/src/Livestream.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientWidgetApi } from "matrix-widget-api";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type ClientWidgetApi } from "matrix-widget-api";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "./SdkConfig";
 import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
diff --git a/src/Login.ts b/src/Login.ts
index aa84ad3c86b8f095117fff93973b70b8752da640..cbaab1cb34f7d8cc365adc758b18bcf4f05709eb 100644
--- a/src/Login.ts
+++ b/src/Login.ts
@@ -9,19 +9,19 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     createClient,
-    MatrixClient,
-    LoginFlow,
+    type MatrixClient,
+    type LoginFlow,
     DELEGATED_OIDC_COMPATIBILITY,
-    ILoginFlow,
-    LoginRequest,
-    OidcClientConfig,
+    type ILoginFlow,
+    type LoginRequest,
+    type OidcClientConfig,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { IMatrixClientCreds } from "./MatrixClientPeg";
+import { type IMatrixClientCreds } from "./MatrixClientPeg";
 import { ModuleRunner } from "./modules/ModuleRunner";
 import { getOidcClientId } from "./utils/oidc/registerClient";
-import { IConfigOptions } from "./IConfigOptions";
+import { type IConfigOptions } from "./IConfigOptions";
 import SdkConfig from "./SdkConfig";
 import { isUserRegistrationSupported } from "./utils/oidc/isUserRegistrationSupported";
 
diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts
index ab952c563347994b5cea17ef01d5839691d1eeea..288878cddc555a09b585db3f0cac3ac1a286dc76 100644
--- a/src/MatrixClientPeg.ts
+++ b/src/MatrixClientPeg.ts
@@ -12,14 +12,14 @@ Please see LICENSE files in the repository root for full details.
 import {
     EventTimeline,
     EventTimelineSet,
-    ICreateClientOpts,
-    IStartClientOpts,
-    MatrixClient,
+    type ICreateClientOpts,
+    type IStartClientOpts,
+    type MatrixClient,
     MemoryStore,
     PendingEventOrdering,
-    RoomNameState,
+    type RoomNameState,
     RoomNameType,
-    TokenRefreshFunction,
+    type TokenRefreshFunction,
 } from "matrix-js-sdk/src/matrix";
 import { VerificationMethod } from "matrix-js-sdk/src/types";
 import * as utils from "matrix-js-sdk/src/utils";
@@ -41,6 +41,7 @@ import PlatformPeg from "./PlatformPeg";
 import { formatList } from "./utils/FormattingUtils";
 import SdkConfig from "./SdkConfig";
 import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts";
+import { initialiseDehydrationIfEnabled } from "./utils/device/dehydration";
 
 export interface IMatrixClientCreds {
     homeserverUrl: string;
@@ -298,6 +299,12 @@ class MatrixClientPegClass implements IMatrixClientPeg {
         opts.threadSupport = true;
 
         if (SettingsStore.getValue("feature_sliding_sync")) {
+            throw new UserFriendlyError("sliding_sync_legacy_no_longer_supported");
+        }
+
+        // If the user has enabled the labs feature for sliding sync, set it up
+        // otherwise check if the feature is supported
+        if (SettingsStore.getValue("feature_simplified_sliding_sync")) {
             opts.slidingSync = await SlidingSyncManager.instance.setup(this.matrixClient);
         } else {
             SlidingSyncManager.instance.checkSupport(this.matrixClient);
@@ -340,7 +347,20 @@ class MatrixClientPegClass implements IMatrixClientPeg {
 
         setDeviceIsolationMode(this.matrixClient, SettingsStore.getValue("feature_exclude_insecure_devices"));
 
-        // TODO: device dehydration and whathaveyou
+        // Start dehydration. This code is only for the case where the client
+        // gets restarted, so we only do this if we already have the dehydration
+        // key cached, and we don't have to try to rehydrate a device. If this
+        // is a new login, we will start dehydration after Secret Storage is
+        // unlocked.
+        try {
+            await initialiseDehydrationIfEnabled(this.matrixClient, { onlyIfKeyCached: true, rehydrate: false });
+        } catch (e) {
+            // We may get an error dehydrating, such as if cross-signing and
+            // SSSS are not set up yet.  Just log the error and continue.
+            // If SSSS gets set up later, we will re-try dehydration.
+            console.log("Error starting device dehydration", e);
+        }
+
         return;
     }
 
diff --git a/src/Modal.tsx b/src/Modal.tsx
index 31ad6b9bfcfeff34cd36b958817a93f34076bebc..e2873783ea313e5fa8e59339faa26db19b6b83fc 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -8,16 +8,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { StrictMode } from "react";
-import { createRoot, Root } from "react-dom/client";
+import { createRoot, type Root } from "react-dom/client";
 import classNames from "classnames";
-import { IDeferred, defer } from "matrix-js-sdk/src/utils";
 import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 import { Glass, TooltipProvider } from "@vector-im/compound-web";
 
 import defaultDispatcher from "./dispatcher/dispatcher";
 import AsyncWrapper from "./AsyncWrapper";
-import { Defaultize } from "./@types/common";
-import { ActionPayload } from "./dispatcher/payloads";
+import { type Defaultize } from "./@types/common";
+import { type ActionPayload } from "./dispatcher/payloads";
+import { filterBoolean } from "./utils/arrays.ts";
 
 const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
 const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
@@ -29,12 +29,25 @@ export type ComponentType =
       }>
     | React.ComponentType<any>;
 
-// Generic type which returns the props of the Modal component with the onFinished being optional.
+/**
+ * The parameter types of the `onFinished` callback property exposed by the component which forms the
+ * body of the dialog.
+ *
+ * @typeParam C - The type of the React component which forms the body of the dialog.
+ */
+type OnFinishedParams<C extends ComponentType> = Parameters<React.ComponentProps<C>["onFinished"]>;
+
+/**
+ * The properties exposed by the `props` argument to {@link Modal.createDialog}: the same as
+ * those exposed by the underlying component, with the exception of `onFinished`, which is provided by
+ * `createDialog`.
+ *
+ * @typeParam C - The type of the React component which forms the body of the dialog.
+ */
 export type ComponentProps<C extends ComponentType> = Defaultize<
     Omit<React.ComponentProps<C>, "onFinished">,
     C["defaultProps"]
-> &
-    Partial<Pick<React.ComponentProps<C>, "onFinished">>;
+>;
 
 export interface IModal<C extends ComponentType> {
     elem: React.ReactNode;
@@ -42,15 +55,44 @@ export interface IModal<C extends ComponentType> {
     beforeClosePromise?: Promise<boolean>;
     closeReason?: ModalCloseReason;
     onBeforeClose?(reason?: ModalCloseReason): Promise<boolean>;
-    onFinished: ComponentProps<C>["onFinished"];
-    close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
+
+    /**
+     * Run the {@link deferred} with the given arguments, and close this modal.
+     *
+     * This method is passed as the `onFinished` callback to the underlying component,
+     * as well as being returned by {@link Modal.createDialog} to the caller.
+     */
+    close(...args: OnFinishedParams<C> | []): void;
+
     hidden?: boolean;
-    deferred?: IDeferred<Parameters<ComponentProps<C>["onFinished"]>>;
+
+    /** A deferred to resolve when the dialog closes, with the results as provided by
+     * the call to {@link close} (normally from the `onFinished` callback).
+     */
+    deferred?: PromiseWithResolvers<OnFinishedParams<C> | []>;
 }
 
+/** The result of {@link Modal.createDialog}.
+ *
+ * @typeParam C - The type of the React component which forms the body of the dialog.
+ */
 export interface IHandle<C extends ComponentType> {
-    finished: Promise<Parameters<ComponentProps<C>["onFinished"]>>;
-    close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
+    /**
+     * A promise which will resolve when the dialog closes.
+     *
+     * If the dialog body component calls the `onFinished` property, or the caller calls {@link close},
+     * the promise resolves with an array holding the arguments to that call.
+     *
+     * If the dialog is closed by clicking in the background, the promise resolves with an empty array.
+     */
+    finished: Promise<OnFinishedParams<C> | []>;
+
+    /**
+     * A function which, if called, will close the dialog.
+     *
+     * @param args - Arguments to return to {@link finished}.
+     */
+    close(...args: OnFinishedParams<C>): void;
 }
 
 interface IOptions<C extends ComponentType> {
@@ -160,13 +202,15 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
      * situations like the user logging out of the app.
      */
     public forceCloseAllModals(): void {
-        for (const modal of this.modals) {
+        const modals = filterBoolean([...this.modals, this.staticModal, this.priorityModal]);
+        for (const modal of modals) {
             modal.deferred?.resolve([]);
-            if (modal.onFinished) modal.onFinished.apply(null);
             this.emitClosed();
         }
 
         this.modals = [];
+        this.staticModal = null;
+        this.priorityModal = null;
         this.reRender();
     }
 
@@ -184,7 +228,6 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
         onFinishedProm: IHandle<C>["finished"];
     } {
         const modal = {
-            onFinished: props?.onFinished,
             onBeforeClose: options?.onBeforeClose,
             className,
 
@@ -192,8 +235,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
             elem: null,
         } as IModal<C>;
 
-        // never call this from onFinished() otherwise it will loop
-        const [closeDialog, onFinishedProm] = this.getCloseFn<C>(modal, props);
+        const [closeDialog, onFinishedProm] = this.getCloseFn<C>(modal);
 
         // don't attempt to reuse the same AsyncWrapper for different dialogs,
         // otherwise we'll get confused.
@@ -210,13 +252,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
         return { modal, closeDialog, onFinishedProm };
     }
 
-    private getCloseFn<C extends ComponentType>(
-        modal: IModal<C>,
-        props?: ComponentProps<C>,
-    ): [IHandle<C>["close"], IHandle<C>["finished"]] {
-        modal.deferred = defer<Parameters<ComponentProps<C>["onFinished"]>>();
+    private getCloseFn<C extends ComponentType>(modal: IModal<C>): [IHandle<C>["close"], IHandle<C>["finished"]] {
+        modal.deferred = Promise.withResolvers<OnFinishedParams<C> | []>();
         return [
-            async (...args: Parameters<ComponentProps<C>["onFinished"]>): Promise<void> => {
+            async (...args: OnFinishedParams<C>): Promise<void> => {
                 if (modal.beforeClosePromise) {
                     await modal.beforeClosePromise;
                 } else if (modal.onBeforeClose) {
@@ -228,7 +267,6 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
                     }
                 }
                 modal.deferred?.resolve(args);
-                if (props?.onFinished) props.onFinished.apply(null, args);
                 const i = this.modals.indexOf(modal);
                 if (i >= 0) {
                     this.modals.splice(i, 1);
@@ -276,7 +314,8 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
      *                  using React.lazy to async load the component.
      *                  e.g. `lazy(() => import('./MyComponent'))`
      *
-     * @param props properties to pass to the displayed component. (We will also pass an 'onFinished' property.)
+     * @param props properties to pass to the displayed component. (We will also pass an `onFinished` property; when
+     *     called, that property will close the dialog and return the results to the caller via {@link IHandle.finished}.)
      *
      * @param className CSS class to apply to the modal wrapper
      *
@@ -291,7 +330,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
      *                                 static at a time.
      * @param options? extra options for the dialog
      * @param options.onBeforeClose a callback to decide whether to close the dialog
-     * @returns Object with 'close' parameter being a function that will close the dialog
+     * @returns {@link IHandle} object.
      */
     public createDialog<C extends ComponentType>(
         component: C,
diff --git a/src/NodeAnimator.tsx b/src/NodeAnimator.tsx
index 16c5a716ff76f2bebb71de5c7216b362241c1606..2bcacdfbce1baadb29468a2562972bf7be222420 100644
--- a/src/NodeAnimator.tsx
+++ b/src/NodeAnimator.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { Key, MutableRefObject, ReactElement, RefCallback } from "react";
+import React, { type Key, type RefObject, type ReactElement, type RefCallback, type HTMLAttributes } from "react";
 
 interface IChildProps {
     style: React.CSSProperties;
@@ -20,7 +20,7 @@ interface IProps {
     // a list of state objects to apply to each child node in turn
     startStyles: React.CSSProperties[];
 
-    innerRef?: MutableRefObject<any>;
+    innerRef?: RefObject<any>;
 }
 
 function isReactElement(c: ReturnType<(typeof React.Children)["toArray"]>[number]): c is ReactElement {
@@ -57,7 +57,8 @@ export default class NodeAnimator extends React.Component<IProps> {
      * @param {React.CSSProperties} styles a key/value pair of CSS properties
      * @returns {void}
      */
-    private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
+    private applyStyles(node: HTMLElement, styles?: React.CSSProperties): void {
+        if (!styles) return;
         Object.entries(styles).forEach(([property, value]) => {
             node.style[property as keyof Omit<CSSStyleDeclaration, "length" | "parentRule">] = value;
         });
@@ -68,21 +69,22 @@ export default class NodeAnimator extends React.Component<IProps> {
         this.children = {};
         React.Children.toArray(newChildren).forEach((c) => {
             if (!isReactElement(c)) return;
+            const props = c.props as HTMLAttributes<HTMLElement>;
             if (oldChildren[c.key!]) {
                 const old = oldChildren[c.key!];
                 const oldNode = this.nodes[old.key!];
 
-                if (oldNode && oldNode.style.left !== c.props.style.left) {
-                    this.applyStyles(oldNode, { left: c.props.style.left });
+                if (oldNode && props.style && oldNode.style.left !== props.style.left) {
+                    this.applyStyles(oldNode, { left: props.style.left });
                 }
                 // clone the old element with the props (and children) of the new element
                 // so prop updates are still received by the children.
-                this.children[c.key!] = React.cloneElement(old, c.props, c.props.children);
+                this.children[c.key!] = React.cloneElement(old, props, props.children);
             } else {
                 // new element. If we have a startStyle, use that as the style and go through
                 // the enter animations
                 const newProps: Partial<IChildProps> = {};
-                const restingStyle = c.props.style;
+                const restingStyle = props.style;
 
                 const startStyles = this.props.startStyles;
                 if (startStyles.length > 0) {
@@ -97,7 +99,7 @@ export default class NodeAnimator extends React.Component<IProps> {
         });
     }
 
-    private collectNode(k: Key, domNode: HTMLElement | null, restingStyle: React.CSSProperties): void {
+    private collectNode(k: Key, domNode: HTMLElement | null, restingStyle?: React.CSSProperties): void {
         const key = typeof k === "bigint" ? Number(k) : k;
         if (domNode && this.nodes[key] === undefined && this.props.startStyles.length > 0) {
             const startStyles = this.props.startStyles;
diff --git a/src/Notifier.ts b/src/Notifier.ts
index 377559a6fc7b63933609dc9345df7d44f1b25672..7dce26d6bddbfeea13a97e10c0c0a705ddcca9d6 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -10,21 +10,21 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
-    Room,
+    type Room,
     RoomEvent,
     ClientEvent,
     MsgType,
     SyncState,
-    SyncStateData,
-    IRoomTimelineData,
+    type SyncStateData,
+    type IRoomTimelineData,
     M_LOCATION,
     EventType,
     TypedEventEmitter,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { PermissionChanged as PermissionChangedEvent } from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
+import { type PermissionChanged as PermissionChangedEvent } from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
 import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 
 import { MatrixClientPeg } from "./MatrixClientPeg";
@@ -43,8 +43,6 @@ import { isPushNotifyDisabled } from "./settings/controllers/NotificationControl
 import UserActivity from "./UserActivity";
 import { mediaFromMxc } from "./customisations/Media";
 import ErrorDialog from "./components/views/dialogs/ErrorDialog";
-import LegacyCallHandler from "./LegacyCallHandler";
-import VoipUserMapper from "./VoipUserMapper";
 import { SdkContextClass } from "./contexts/SDKContext";
 import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded } from "./utils/notifications";
 import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
@@ -447,14 +445,7 @@ class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents
 
     // XXX: exported for tests
     public evaluateEvent(ev: MatrixEvent): void {
-        let roomId = ev.getRoomId()!;
-        if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
-            // Attempt to translate a virtual room to a native one
-            const nativeRoomId = VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(roomId);
-            if (nativeRoomId) {
-                roomId = nativeRoomId;
-            }
-        }
+        const roomId = ev.getRoomId()!;
         const room = MatrixClientPeg.safeGet().getRoom(roomId);
         if (!room) {
             // e.g we are in the process of joining a room.
diff --git a/src/PasswordReset.ts b/src/PasswordReset.ts
index f23d14799bc354abf709c457f84d4ba3e3407367..9fb841ed4b22520d2293c70faae4e5f8768c9ebe 100644
--- a/src/PasswordReset.ts
+++ b/src/PasswordReset.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { createClient, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { createClient, type IRequestTokenResponse, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "./languageHandler";
 
diff --git a/src/PlatformPeg.ts b/src/PlatformPeg.ts
index 493fde8f8486bcf50215043ecde1d36563192cbf..9e8852170cc463b34b1b1a4eae5a4938fcdb53e6 100644
--- a/src/PlatformPeg.ts
+++ b/src/PlatformPeg.ts
@@ -7,10 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import BasePlatform from "./BasePlatform";
+import type BasePlatform from "./BasePlatform";
 import defaultDispatcher from "./dispatcher/dispatcher";
 import { Action } from "./dispatcher/actions";
-import { PlatformSetPayload } from "./dispatcher/payloads/PlatformSetPayload";
+import { type PlatformSetPayload } from "./dispatcher/payloads/PlatformSetPayload";
 
 /*
  * Holds the current instance of the `Platform` to use across the codebase.
diff --git a/src/PlaybackEncoder.ts b/src/PlaybackEncoder.ts
index dc212e6a28c14fc062021b1d2fefc78b60c3a559..1cb423dc30aada167409c9f68fa51adf4acc1bf4 100644
--- a/src/PlaybackEncoder.ts
+++ b/src/PlaybackEncoder.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Request, Response } from "./workers/playback.worker";
+import { type Request, type Response } from "./workers/playback.worker";
 import { WorkerManager } from "./WorkerManager";
 import playbackWorkerFactory from "./workers/playbackWorkerFactory";
 
diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts
index 91208e253d5e64952187d0b4fc802782f0540ccb..546674bbcb511e9e0843d15262e6398d43d642b6 100644
--- a/src/PosthogAnalytics.ts
+++ b/src/PosthogAnalytics.ts
@@ -6,20 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import posthog, { CaptureOptions, PostHog, Properties } from "posthog-js";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import posthog, { type CaptureOptions, type PostHog, type Properties } from "posthog-js";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { UserProperties } from "@matrix-org/analytics-events/types/typescript/UserProperties";
-import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
+import { type UserProperties } from "@matrix-org/analytics-events/types/typescript/UserProperties";
+import { type Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
 
 import PlatformPeg from "./PlatformPeg";
 import SdkConfig from "./SdkConfig";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import SettingsStore from "./settings/SettingsStore";
-import { ScreenName } from "./PosthogTrackers";
-import { ActionPayload } from "./dispatcher/payloads";
+import { type ScreenName } from "./PosthogTrackers";
+import { type ActionPayload } from "./dispatcher/payloads";
 import { Action } from "./dispatcher/actions";
-import { SettingUpdatedPayload } from "./dispatcher/payloads/SettingUpdatedPayload";
+import { type SettingUpdatedPayload } from "./dispatcher/payloads/SettingUpdatedPayload";
 import dis from "./dispatcher/dispatcher";
 import { Layout } from "./settings/enums/Layout";
 
diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts
index 02faa6b5ec5b7d52edfdd2eced838e74a976dec1..5eddeecb2ae7099412f953605dc1793b08b49ad5 100644
--- a/src/PosthogTrackers.ts
+++ b/src/PosthogTrackers.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { PureComponent, SyntheticEvent } from "react";
-import { WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen";
-import { Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction";
-import { PinUnpinAction } from "@matrix-org/analytics-events/types/typescript/PinUnpinAction";
+import { PureComponent, type SyntheticEvent } from "react";
+import { type WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen";
+import { type Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction";
+import { type PinUnpinAction } from "@matrix-org/analytics-events/types/typescript/PinUnpinAction";
 
 import PageType from "./PageTypes";
 import Views from "./Views";
diff --git a/src/Presence.ts b/src/Presence.ts
index 8057a0f73781347d491a36425edaf458f5d3672f..7388e2d165bbd5396715019b5def730add8dfea5 100644
--- a/src/Presence.ts
+++ b/src/Presence.ts
@@ -14,7 +14,7 @@ import { SetPresence } from "matrix-js-sdk/src/matrix";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import dis from "./dispatcher/dispatcher";
 import Timer from "./utils/Timer";
-import { ActionPayload } from "./dispatcher/payloads";
+import { type ActionPayload } from "./dispatcher/payloads";
 
 // Time in ms after that a user is considered as unavailable/away
 const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
diff --git a/src/Registration.tsx b/src/Registration.tsx
index ea0264fab35c35640fd4562f94293eeaa3e0501f..22eb6e15ff59046f0288e4d8e144edca7d24fa43 100644
--- a/src/Registration.tsx
+++ b/src/Registration.tsx
@@ -62,14 +62,14 @@ export async function startAnyRegistrationFlow(
                   </button>,
               ]
             : [],
-        onFinished: (proceed) => {
-            if (proceed) {
-                dis.dispatch({ action: "start_login", screenAfterLogin: options.screen_after });
-            } else if (options.go_home_on_cancel) {
-                dis.dispatch({ action: Action.ViewHomePage });
-            } else if (options.go_welcome_on_cancel) {
-                dis.dispatch({ action: "view_welcome_page" });
-            }
-        },
+    });
+    modal.finished.then(([proceed]) => {
+        if (proceed) {
+            dis.dispatch({ action: "start_login", screenAfterLogin: options.screen_after });
+        } else if (options.go_home_on_cancel) {
+            dis.dispatch({ action: Action.ViewHomePage });
+        } else if (options.go_welcome_on_cancel) {
+            dis.dispatch({ action: "view_welcome_page" });
+        }
     });
 }
diff --git a/src/Resend.ts b/src/Resend.ts
index 109aea632fb20b6be90b7387de12864eaf0acb74..e93082f3978bb073788564aca3693a9ef7c2a059 100644
--- a/src/Resend.ts
+++ b/src/Resend.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, EventStatus, Room, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, EventStatus, type Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import dis from "./dispatcher/dispatcher";
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index 91ea459230ed34f9f77f3f6b8fd4f914570284c4..a02530a1cf8c8d9b0cb9a1f66c73d1b9caa863e6 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { Room, MatrixEvent, MatrixClient, User, EventType } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentProps } from "react";
+import { type Room, type MatrixEvent, type MatrixClient, type User, EventType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import MultiInviter, { CompletionStates } from "./utils/MultiInviter";
+import MultiInviter, { type CompletionStates } from "./utils/MultiInviter";
 import Modal from "./Modal";
 import { _t } from "./languageHandler";
 import InviteDialog from "./components/views/dialogs/InviteDialog";
@@ -18,7 +18,7 @@ import BaseAvatar from "./components/views/avatars/BaseAvatar";
 import { mediaFromMxc } from "./customisations/Media";
 import ErrorDialog from "./components/views/dialogs/ErrorDialog";
 import { InviteKind } from "./components/views/dialogs/InviteDialogTypes";
-import { Member } from "./utils/direct-messages";
+import { type Member } from "./utils/direct-messages";
 
 export interface IInviteResult {
     states: CompletionStates;
diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts
index e6064d2691486c70a9605d3b2734f613b0b256f8..d5254d523d06057a7ece83c1ebd168ef68904989 100644
--- a/src/RoomNotifs.ts
+++ b/src/RoomNotifs.ts
@@ -238,25 +238,25 @@ export function determineUnreadState(
     room?: Room,
     threadId?: string,
     includeThreads?: boolean,
-): { level: NotificationLevel; symbol: string | null; count: number } {
+): { level: NotificationLevel; symbol: string | null; count: number; invited: boolean } {
     if (!room) {
-        return { symbol: null, count: 0, level: NotificationLevel.None };
+        return { symbol: null, count: 0, level: NotificationLevel.None, invited: false };
     }
 
     if (getUnsentMessages(room, threadId).length > 0) {
-        return { symbol: "!", count: 1, level: NotificationLevel.Unsent };
+        return { symbol: "!", count: 1, level: NotificationLevel.Unsent, invited: false };
     }
 
     if (getEffectiveMembership(room.getMyMembership()) === EffectiveMembership.Invite) {
-        return { symbol: "!", count: 1, level: NotificationLevel.Highlight };
+        return { symbol: "!", count: 1, level: NotificationLevel.Highlight, invited: true };
     }
 
     if (SettingsStore.getValue("feature_ask_to_join") && isKnockDenied(room)) {
-        return { symbol: "!", count: 1, level: NotificationLevel.Highlight };
+        return { symbol: "!", count: 1, level: NotificationLevel.Highlight, invited: false };
     }
 
     if (getRoomNotifsState(room.client, room.roomId) === RoomNotifState.Mute) {
-        return { symbol: null, count: 0, level: NotificationLevel.None };
+        return { symbol: null, count: 0, level: NotificationLevel.None, invited: false };
     }
 
     const redNotifs = getUnreadNotificationCount(
@@ -269,12 +269,12 @@ export function determineUnreadState(
 
     const trueCount = greyNotifs || redNotifs;
     if (redNotifs > 0) {
-        return { symbol: null, count: trueCount, level: NotificationLevel.Highlight };
+        return { symbol: null, count: trueCount, level: NotificationLevel.Highlight, invited: false };
     }
 
     const markedUnreadState = getMarkedUnreadState(room);
     if (greyNotifs > 0 || markedUnreadState) {
-        return { symbol: null, count: trueCount, level: NotificationLevel.Notification };
+        return { symbol: null, count: trueCount, level: NotificationLevel.Notification, invited: false };
     }
 
     // We don't have any notified messages, but we might have unread messages. Let's find out.
@@ -293,5 +293,6 @@ export function determineUnreadState(
         symbol: null,
         count: trueCount,
         level: hasUnread ? NotificationLevel.Activity : NotificationLevel.None,
+        invited: false,
     };
 }
diff --git a/src/Rooms.ts b/src/Rooms.ts
index 347316c4c9482024ebc6d46ab10c32c2ebf11b5d..b29fa1c6591566548b10568dd5ea454f33bda852 100644
--- a/src/Rooms.ts
+++ b/src/Rooms.ts
@@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, EventType, RoomMember, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Room, EventType, type RoomMember, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import AliasCustomisations from "./customisations/Alias";
+import { filterValidMDirect } from "./utils/dm/filterValidMDirect.ts";
 
 /**
  * Given a room object, return the alias we should use for it,
@@ -56,39 +57,23 @@ export async function setDMRoom(client: MatrixClient, roomId: string, userId: st
     if (client.isGuest()) return;
 
     const mDirectEvent = client.getAccountData(EventType.Direct);
-    const currentContent = mDirectEvent?.getContent() || {};
-
-    const dmRoomMap = new Map(Object.entries(currentContent));
-    let modified = false;
-
-    // remove it from the lists of any others users
-    // (it can only be a DM room for one person)
-    for (const thisUserId of dmRoomMap.keys()) {
-        const roomList = dmRoomMap.get(thisUserId) || [];
-
-        if (thisUserId != userId) {
-            const indexOfRoom = roomList.indexOf(roomId);
-            if (indexOfRoom > -1) {
-                roomList.splice(indexOfRoom, 1);
-                modified = true;
-            }
-        }
+    const { filteredContent } = filterValidMDirect(mDirectEvent?.getContent() ?? {});
+
+    // remove it from the lists of all users (it can only be a DM room for one person)
+    for (const thisUserId in filteredContent) {
+        if (!filteredContent[thisUserId]) continue;
+        filteredContent[thisUserId] = filteredContent[thisUserId].filter((room) => room !== roomId);
     }
 
-    // now add it, if it's not already there
+    // now add it if the caller asked for it to be a DM room
     if (userId) {
-        const roomList = dmRoomMap.get(userId) || [];
-        if (roomList.indexOf(roomId) == -1) {
-            roomList.push(roomId);
-            modified = true;
+        if (!filteredContent[userId]) {
+            filteredContent[userId] = [];
         }
-        dmRoomMap.set(userId, roomList);
+        filteredContent[userId].push(roomId);
     }
 
-    // prevent unnecessary calls to setAccountData
-    if (!modified) return;
-
-    await client.setAccountData(EventType.Direct, Object.fromEntries(dmRoomMap));
+    await client.setAccountData(EventType.Direct, filteredContent);
 }
 
 /**
diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts
index 094bb6fe593da41decdc9d8f425cf8b7d45b6bd8..26ee94de4089397dbd79fde0923327c214fdbaed 100644
--- a/src/ScalarAuthClient.ts
+++ b/src/ScalarAuthClient.ts
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { SERVICE_TYPES, Room, IOpenIDToken } from "matrix-js-sdk/src/matrix";
+import { SERVICE_TYPES, type Room, type IOpenIDToken } from "matrix-js-sdk/src/matrix";
 
 import SettingsStore from "./settings/SettingsStore";
-import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError } from "./Terms";
+import { Service, startTermsFlow, type TermsInteractionCallback, TermsNotSignedError } from "./Terms";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import SdkConfig from "./SdkConfig";
-import { WidgetType } from "./widgets/WidgetType";
+import { type WidgetType } from "./widgets/WidgetType";
 import { parseUrl } from "./utils/UrlUtils";
 
 // The version of the integration manager API we're intending to work with
diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts
index 003d02f2ddb5268bb4741d252d4116b9f64b6ced..840717dead1d5362574e6154eb665d8f14f8c9a1 100644
--- a/src/ScalarMessaging.ts
+++ b/src/ScalarMessaging.ts
@@ -282,7 +282,7 @@ Response:
 
 */
 
-import { IContent, MatrixEvent, IEvent, StateEvents } from "matrix-js-sdk/src/matrix";
+import { type IContent, type MatrixEvent, type IEvent, type StateEvents } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts
index ee1c4eab8a995da911d7dc046f6bb97e46eaaf4d..9576aa209e4d4dac20db5e71b6fcd44f970323f0 100644
--- a/src/SdkConfig.ts
+++ b/src/SdkConfig.ts
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 import { mergeWith } from "lodash";
 
 import { SnakedObject } from "./utils/SnakedObject";
-import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
+import { type IConfigOptions, type ISsoRedirectOptions } from "./IConfigOptions";
 import { isObject, objectClone } from "./utils/objects";
-import { DeepReadonly, Defaultize } from "./@types/common";
+import { type DeepReadonly, type Defaultize } from "./@types/common";
 
 // see element-web config.md for docs, or the IConfigOptions interface for dev docs
 export const DEFAULTS: DeepReadonly<IConfigOptions> = {
@@ -30,7 +30,6 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
         preferred_domain: "meet.element.io",
     },
     element_call: {
-        url: "https://call.element.io",
         use_exclusively: false,
         participant_limit: 8,
         brand: "Element Call",
@@ -59,7 +58,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
         url: "https://element.io/download",
         url_macos: "https://packages.element.io/desktop/install/macos/Element.dmg",
         url_win64: "https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe",
-        url_win32: "https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe",
+        url_win64arm: "https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe",
         url_linux: "https://element.io/download#linux",
     },
     mobile_builds: {
diff --git a/src/Searching.ts b/src/Searching.ts
index dea724fbdf096556edfd45cd01363c4581326239..d507bd10ef9ad6a6fa9fd0233b985f0117cd2d09 100644
--- a/src/Searching.ts
+++ b/src/Searching.ts
@@ -7,19 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    IResultRoomEvents,
-    ISearchRequestBody,
-    ISearchResponse,
-    ISearchResult,
-    ISearchResults,
+    type IResultRoomEvents,
+    type ISearchRequestBody,
+    type ISearchResponse,
+    type ISearchResult,
+    type ISearchResults,
     SearchOrderBy,
-    IRoomEventFilter,
+    type IRoomEventFilter,
     EventType,
-    MatrixClient,
-    SearchResult,
+    type MatrixClient,
+    type SearchResult,
 } from "matrix-js-sdk/src/matrix";
 
-import { ISearchArgs } from "./indexing/BaseEventIndexManager";
+import { type ISearchArgs } from "./indexing/BaseEventIndexManager";
 import EventIndexPeg from "./indexing/EventIndexPeg";
 import { isNotUndefined } from "./Typeguards";
 
@@ -718,4 +718,8 @@ export interface SearchInfo {
      * The total count of matching results as returned by the backend.
      */
     count?: number;
+    /**
+     * Describe the error if any occured.
+     */
+    error?: Error;
 }
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 6af8dd5f18428c5d732599bf8ed6001181f94da6..b497c57a10c11d8c5222be9c4e73faa09117d0d6 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -7,15 +7,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { lazy } from "react";
-import { SecretStorage } from "matrix-js-sdk/src/matrix";
-import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey, CryptoCallbacks } from "matrix-js-sdk/src/crypto-api";
-import { logger } from "matrix-js-sdk/src/logger";
+import { type SecretStorage } from "matrix-js-sdk/src/matrix";
+import { deriveRecoveryKeyFromPassphrase, decodeRecoveryKey, type CryptoCallbacks } from "matrix-js-sdk/src/crypto-api";
+import { logger as rootLogger } from "matrix-js-sdk/src/logger";
 
 import Modal from "./Modal";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import { _t } from "./languageHandler";
 import { isSecureBackupRequired } from "./utils/WellKnownUtils";
-import AccessSecretStorageDialog, { KeyParams } from "./components/views/dialogs/security/AccessSecretStorageDialog";
+import AccessSecretStorageDialog, {
+    type KeyParams,
+} from "./components/views/dialogs/security/AccessSecretStorageDialog";
 import { ModuleRunner } from "./modules/ModuleRunner";
 import QuestionDialog from "./components/views/dialogs/QuestionDialog";
 import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
@@ -29,6 +31,8 @@ let secretStorageKeys: Record<string, Uint8Array> = {};
 let secretStorageKeyInfo: Record<string, SecretStorage.SecretStorageKeyDescription> = {};
 let secretStorageBeingAccessed = false;
 
+const logger = rootLogger.getChild("SecurityManager:");
+
 /**
  * This can be used by other components to check if secret storage access is in
  * progress, so that we can e.g. avoid intermittently showing toasts during
@@ -70,33 +74,34 @@ function makeInputToKey(
     };
 }
 
-async function getSecretStorageKey({
-    keys: keyInfos,
-}: {
-    keys: Record<string, SecretStorage.SecretStorageKeyDescription>;
-}): Promise<[string, Uint8Array]> {
+async function getSecretStorageKey(
+    {
+        keys: keyInfos,
+    }: {
+        keys: Record<string, SecretStorage.SecretStorageKeyDescription>;
+    },
+    secretName: string,
+): Promise<[string, Uint8Array]> {
     const cli = MatrixClientPeg.safeGet();
-    let keyId = await cli.secretStorage.getDefaultKeyId();
-    let keyInfo!: SecretStorage.SecretStorageKeyDescription;
-    if (keyId) {
-        // use the default SSSS key if set
-        keyInfo = keyInfos[keyId];
-        if (!keyInfo) {
-            // if the default key is not available, pretend the default key
-            // isn't set
-            keyId = null;
-        }
-    }
-    if (!keyId) {
-        // if no default SSSS key is set, fall back to a heuristic of using the
+    const defaultKeyId = await cli.secretStorage.getDefaultKeyId();
+
+    let keyId: string;
+    // If the defaultKey is useful, use that
+    if (defaultKeyId && keyInfos[defaultKeyId]) {
+        keyId = defaultKeyId;
+    } else {
+        // Fall back to a heuristic of using the
         // only available key, if only one key is set
-        const keyInfoEntries = Object.entries(keyInfos);
-        if (keyInfoEntries.length > 1) {
+        const usefulKeys = Object.keys(keyInfos);
+        if (usefulKeys.length > 1) {
             throw new Error("Multiple storage key requests not implemented");
         }
-        [keyId, keyInfo] = keyInfoEntries[0];
+        keyId = usefulKeys[0];
     }
-    logger.debug(`getSecretStorageKey: request for 4S keys [${Object.keys(keyInfos)}]: looking for key ${keyId}`);
+    const keyInfo = keyInfos[keyId];
+    logger.debug(
+        `getSecretStorageKey: request for 4S keys [${Object.keys(keyInfos)}] for secret \`${secretName}\`: looking for key ${keyId}`,
+    );
 
     // Check the in-memory cache
     if (secretStorageBeingAccessed && secretStorageKeys[keyId]) {
@@ -106,12 +111,18 @@ async function getSecretStorageKey({
 
     const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
     if (keyFromCustomisations) {
-        logger.log("getSecretStorageKey: Using secret storage key from CryptoSetupExtension");
+        logger.debug("getSecretStorageKey: Using secret storage key from CryptoSetupExtension");
         cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
         return [keyId, keyFromCustomisations];
     }
 
-    logger.debug("getSecretStorageKey: prompting user for key");
+    // We only prompt the user for the default key
+    if (keyId !== defaultKeyId) {
+        logger.debug(`getSecretStorageKey: request for non-default key ${keyId}: not prompting user`);
+        throw new Error("Request for non-default 4S key");
+    }
+
+    logger.debug(`getSecretStorageKey: prompting user for key ${keyId}`);
     const inputToKey = makeInputToKey(keyInfo);
     const { finished } = Modal.createDialog(
         AccessSecretStorageDialog,
@@ -139,7 +150,7 @@ async function getSecretStorageKey({
     if (!keyParams) {
         throw new AccessCancelledError();
     }
-    logger.debug("getSecretStorageKey: got key from user");
+    logger.debug(`getSecretStorageKey: got key ${keyId} from user`);
     const key = await inputToKey(keyParams);
 
     // Save to cache to avoid future prompts in the current session
@@ -154,6 +165,7 @@ function cacheSecretStorageKey(
     key: Uint8Array,
 ): void {
     if (secretStorageBeingAccessed) {
+        logger.debug(`Caching 4S key ${keyId}`);
         secretStorageKeys[keyId] = key;
         secretStorageKeyInfo[keyId] = keyInfo;
     }
@@ -173,13 +185,13 @@ export const crossSigningCallbacks: CryptoCallbacks = {
  * @param func - The operation to be wrapped.
  */
 export async function withSecretStorageKeyCache<T>(func: () => Promise<T>): Promise<T> {
-    logger.debug("SecurityManager: enabling 4S key cache");
+    logger.debug("enabling 4S key cache");
     secretStorageBeingAccessed = true;
     try {
         return await func();
     } finally {
         // Clear secret storage key cache now that work is complete
-        logger.debug("SecurityManager: disabling 4S key cache");
+        logger.debug("disabling 4S key cache");
         secretStorageBeingAccessed = false;
         secretStorageKeys = {};
         secretStorageKeyInfo = {};
diff --git a/src/SendHistoryManager.ts b/src/SendHistoryManager.ts
index 517e1b45e36f9301a8038eb83276ba6776dc0955..149dbe42d2e1fe4674778970c54a96d2471696cb 100644
--- a/src/SendHistoryManager.ts
+++ b/src/SendHistoryManager.ts
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { clamp } from "lodash";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { SerializedPart } from "./editor/parts";
-import EditorModel from "./editor/model";
+import { type SerializedPart } from "./editor/parts";
+import type EditorModel from "./editor/model";
 
 interface IHistoryItem {
     parts: SerializedPart[];
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 08ecd0562b7d106d411c58f60ee367f15e42e75a..afbfeeca03ec60122fe94e270215edd3188653c5 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -9,10 +9,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
-import { ContentHelpers, Direction, EventType, IContent, MRoomTopicEventContent, User } from "matrix-js-sdk/src/matrix";
+import React from "react";
+import {
+    ContentHelpers,
+    Direction,
+    EventType,
+    type IContent,
+    type MRoomTopicEventContent,
+    type User,
+} from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { KnownMembership, RoomMemberEventContent } from "matrix-js-sdk/src/types";
+import { KnownMembership, type RoomMemberEventContent } from "matrix-js-sdk/src/types";
 
 import dis from "./dispatcher/dispatcher";
 import { _t, _td, UserFriendlyError } from "./languageHandler";
@@ -29,7 +36,7 @@ import { WidgetType } from "./widgets/WidgetType";
 import { Jitsi } from "./widgets/Jitsi";
 import BugReportDialog from "./components/views/dialogs/BugReportDialog";
 import { ensureDMExists } from "./createRoom";
-import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
+import { type ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
 import { Action } from "./dispatcher/actions";
 import SdkConfig from "./SdkConfig";
 import SettingsStore from "./settings/SettingsStore";
@@ -44,8 +51,7 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
 import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
 import { shouldShowComponent } from "./customisations/helpers/UIComponents";
 import { TimelineRenderingType } from "./contexts/RoomContext";
-import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
-import VoipUserMapper from "./VoipUserMapper";
+import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
 import { htmlSerializeFromMdIfNeeded } from "./editor/serialize";
 import { leaveRoomBehaviour } from "./utils/leave-behaviour";
 import { MatrixClientPeg } from "./MatrixClientPeg";
@@ -736,28 +742,6 @@ export const Commands = [
         },
         category: CommandCategories.advanced,
     }),
-    new Command({
-        command: "tovirtual",
-        description: _td("slash_command|tovirtual"),
-        category: CommandCategories.advanced,
-        isEnabled(cli): boolean {
-            return !!LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom(cli);
-        },
-        runFn: (cli, roomId) => {
-            return success(
-                (async (): Promise<void> => {
-                    const room = await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(roomId);
-                    if (!room) throw new UserFriendlyError("slash_command|tovirtual_not_found");
-                    dis.dispatch<ViewRoomPayload>({
-                        action: Action.ViewRoom,
-                        room_id: room.roomId,
-                        metricsTrigger: "SlashCommand",
-                        metricsViaKeyboard: true,
-                    });
-                })(),
-            );
-        },
-    }),
     new Command({
         command: "query",
         description: _td("slash_command|query"),
diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts
index b9922e2290f97da4d60706145b18fee167543ea3..815e438da756ebe231b55e83e4341ebfe4ee46bb 100644
--- a/src/SlidingSyncManager.ts
+++ b/src/SlidingSyncManager.ts
@@ -36,45 +36,51 @@ Please see LICENSE files in the repository root for full details.
  *                      list ops)
  */
 
-import { MatrixClient, EventType, AutoDiscovery, Method, timeoutSignal } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, ClientEvent, EventType, type Room } from "matrix-js-sdk/src/matrix";
 import {
-    MSC3575Filter,
-    MSC3575List,
+    type MSC3575Filter,
+    type MSC3575List,
+    type MSC3575SlidingSyncResponse,
     MSC3575_STATE_KEY_LAZY,
     MSC3575_STATE_KEY_ME,
     MSC3575_WILDCARD,
     SlidingSync,
+    SlidingSyncEvent,
+    SlidingSyncState,
 } from "matrix-js-sdk/src/sliding-sync";
 import { logger } from "matrix-js-sdk/src/logger";
-import { defer, sleep } from "matrix-js-sdk/src/utils";
-
-import SettingsStore from "./settings/SettingsStore";
-import SlidingSyncController from "./settings/controllers/SlidingSyncController";
+import { sleep } from "matrix-js-sdk/src/utils";
 
 // how long to long poll for
 const SLIDING_SYNC_TIMEOUT_MS = 20 * 1000;
 
+// The state events we will get for every single room/space/old room/etc
+// This list is only augmented when a direct room subscription is made. (e.g you view a room)
+const REQUIRED_STATE_LIST = [
+    [EventType.RoomJoinRules, ""], // the public icon on the room list
+    [EventType.RoomAvatar, ""], // any room avatar
+    [EventType.RoomCanonicalAlias, ""], // for room name calculations
+    [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
+    [EventType.RoomEncryption, ""], // lets rooms be configured for E2EE correctly
+    [EventType.RoomCreate, ""], // for isSpaceRoom checks
+    [EventType.SpaceChild, MSC3575_WILDCARD], // all space children
+    [EventType.SpaceParent, MSC3575_WILDCARD], // all space parents
+    [EventType.RoomMember, MSC3575_STATE_KEY_ME], // lets the client calculate that we are in fact in the room
+];
+
 // the things to fetch when a user clicks on a room
 const DEFAULT_ROOM_SUBSCRIPTION_INFO = {
     timeline_limit: 50,
     // missing required_state which will change depending on the kind of room
     include_old_rooms: {
         timeline_limit: 0,
-        required_state: [
-            // state needed to handle space navigation and tombstone chains
-            [EventType.RoomCreate, ""],
-            [EventType.RoomTombstone, ""],
-            [EventType.SpaceChild, MSC3575_WILDCARD],
-            [EventType.SpaceParent, MSC3575_WILDCARD],
-            [EventType.RoomMember, MSC3575_STATE_KEY_ME],
-        ],
+        required_state: REQUIRED_STATE_LIST,
     },
 };
 // lazy load room members so rooms like Matrix HQ don't take forever to load
 const UNENCRYPTED_SUBSCRIPTION_NAME = "unencrypted";
 const UNENCRYPTED_SUBSCRIPTION = {
     required_state: [
-        [MSC3575_WILDCARD, MSC3575_WILDCARD], // all events
         [EventType.RoomMember, MSC3575_STATE_KEY_ME], // except for m.room.members, get our own membership
         [EventType.RoomMember, MSC3575_STATE_KEY_LAZY], // ...and lazy load the rest.
     ],
@@ -90,6 +96,72 @@ const ENCRYPTED_SUBSCRIPTION = {
     ...DEFAULT_ROOM_SUBSCRIPTION_INFO,
 };
 
+// the complete set of lists made in SSS. The manager will spider all of these lists depending
+// on the count for each one.
+const sssLists: Record<string, MSC3575List> = {
+    spaces: {
+        ranges: [[0, 10]],
+        timeline_limit: 0, // we don't care about the most recent message for spaces
+        required_state: REQUIRED_STATE_LIST,
+        include_old_rooms: {
+            timeline_limit: 0,
+            required_state: REQUIRED_STATE_LIST,
+        },
+        filters: {
+            room_types: ["m.space"],
+        },
+    },
+    invites: {
+        ranges: [[0, 10]],
+        timeline_limit: 1, // most recent message display
+        required_state: REQUIRED_STATE_LIST,
+        include_old_rooms: {
+            timeline_limit: 0,
+            required_state: REQUIRED_STATE_LIST,
+        },
+        filters: {
+            is_invite: true,
+        },
+    },
+    favourites: {
+        ranges: [[0, 10]],
+        timeline_limit: 1, // most recent message display
+        required_state: REQUIRED_STATE_LIST,
+        include_old_rooms: {
+            timeline_limit: 0,
+            required_state: REQUIRED_STATE_LIST,
+        },
+        filters: {
+            tags: ["m.favourite"],
+        },
+    },
+    dms: {
+        ranges: [[0, 10]],
+        timeline_limit: 1, // most recent message display
+        required_state: REQUIRED_STATE_LIST,
+        include_old_rooms: {
+            timeline_limit: 0,
+            required_state: REQUIRED_STATE_LIST,
+        },
+        filters: {
+            is_dm: true,
+            is_invite: false,
+            // If a DM has a Favourite & Low Prio tag then it'll be shown in those lists instead
+            not_tags: ["m.favourite", "m.lowpriority"],
+        },
+    },
+    untagged: {
+        // SSS will dupe suppress invites/dms from here, so we don't need "not dms, not invites"
+        ranges: [[0, 10]],
+        timeline_limit: 1, // most recent message display
+        required_state: REQUIRED_STATE_LIST,
+        include_old_rooms: {
+            timeline_limit: 0,
+            required_state: REQUIRED_STATE_LIST,
+        },
+    },
+};
+
 export type PartialSlidingSyncRequest = {
     filters?: MSC3575Filter;
     sort?: string[];
@@ -103,6 +175,8 @@ export type PartialSlidingSyncRequest = {
  * sync options and code.
  */
 export class SlidingSyncManager {
+    public static serverSupportsSlidingSync: boolean;
+
     public static readonly ListSpaces = "space_list";
     public static readonly ListSearch = "search_list";
     private static readonly internalInstance = new SlidingSyncManager();
@@ -110,54 +184,23 @@ export class SlidingSyncManager {
     public slidingSync?: SlidingSync;
     private client?: MatrixClient;
 
-    private configureDefer = defer<void>();
+    private configureDefer = Promise.withResolvers<void>();
 
     public static get instance(): SlidingSyncManager {
         return SlidingSyncManager.internalInstance;
     }
 
-    public configure(client: MatrixClient, proxyUrl: string): SlidingSync {
+    private configure(client: MatrixClient, proxyUrl: string): SlidingSync {
         this.client = client;
+        // create the set of lists we will use.
+        const lists = new Map();
+        for (const listName in sssLists) {
+            lists.set(listName, sssLists[listName]);
+        }
         // by default use the encrypted subscription as that gets everything, which is a safer
         // default than potentially missing member events.
-        this.slidingSync = new SlidingSync(
-            proxyUrl,
-            new Map(),
-            ENCRYPTED_SUBSCRIPTION,
-            client,
-            SLIDING_SYNC_TIMEOUT_MS,
-        );
+        this.slidingSync = new SlidingSync(proxyUrl, lists, ENCRYPTED_SUBSCRIPTION, client, SLIDING_SYNC_TIMEOUT_MS);
         this.slidingSync.addCustomSubscription(UNENCRYPTED_SUBSCRIPTION_NAME, UNENCRYPTED_SUBSCRIPTION);
-        // set the space list
-        this.slidingSync.setList(SlidingSyncManager.ListSpaces, {
-            ranges: [[0, 20]],
-            sort: ["by_name"],
-            slow_get_all_rooms: true,
-            timeline_limit: 0,
-            required_state: [
-                [EventType.RoomJoinRules, ""], // the public icon on the room list
-                [EventType.RoomAvatar, ""], // any room avatar
-                [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
-                [EventType.RoomEncryption, ""], // lets rooms be configured for E2EE correctly
-                [EventType.RoomCreate, ""], // for isSpaceRoom checks
-                [EventType.SpaceChild, MSC3575_WILDCARD], // all space children
-                [EventType.SpaceParent, MSC3575_WILDCARD], // all space parents
-                [EventType.RoomMember, MSC3575_STATE_KEY_ME], // lets the client calculate that we are in fact in the room
-            ],
-            include_old_rooms: {
-                timeline_limit: 0,
-                required_state: [
-                    [EventType.RoomCreate, ""],
-                    [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
-                    [EventType.SpaceChild, MSC3575_WILDCARD], // all space children
-                    [EventType.SpaceParent, MSC3575_WILDCARD], // all space parents
-                    [EventType.RoomMember, MSC3575_STATE_KEY_ME], // lets the client calculate that we are in fact in the room
-                ],
-            },
-            filters: {
-                room_types: ["m.space"],
-            },
-        });
         this.configureDefer.resolve();
         return this.slidingSync;
     }
@@ -220,99 +263,113 @@ export class SlidingSyncManager {
         return this.slidingSync!.getListParams(listKey)!;
     }
 
-    public async setRoomVisible(roomId: string, visible: boolean): Promise<string> {
+    /**
+     * Announces that the user has chosen to view the given room and that room will now
+     * be displayed, so it should have more state loaded.
+     * @param roomId The room to set visible
+     */
+    public async setRoomVisible(roomId: string): Promise<void> {
         await this.configureDefer.promise;
         const subscriptions = this.slidingSync!.getRoomSubscriptions();
-        if (visible) {
-            subscriptions.add(roomId);
-        } else {
-            subscriptions.delete(roomId);
-        }
+        if (subscriptions.has(roomId)) return;
+
+        subscriptions.add(roomId);
+
         const room = this.client?.getRoom(roomId);
-        let shouldLazyLoad = !(await this.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
-        if (!room) {
-            // default to safety: request all state if we can't work it out. This can happen if you
-            // refresh the app whilst viewing a room: we call setRoomVisible before we know anything
-            // about the room.
-            shouldLazyLoad = false;
+        // default to safety: request all state if we can't work it out. This can happen if you
+        // refresh the app whilst viewing a room: we call setRoomVisible before we know anything
+        // about the room.
+        let shouldLazyLoad = false;
+        if (room) {
+            // do not lazy load encrypted rooms as we need the entire member list.
+            shouldLazyLoad = !(await this.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
         }
-        logger.log("SlidingSync setRoomVisible:", roomId, visible, "shouldLazyLoad:", shouldLazyLoad);
+        logger.log("SlidingSync setRoomVisible:", roomId, "shouldLazyLoad:", shouldLazyLoad);
         if (shouldLazyLoad) {
             // lazy load this room
             this.slidingSync!.useCustomSubscription(roomId, UNENCRYPTED_SUBSCRIPTION_NAME);
         }
-        const p = this.slidingSync!.modifyRoomSubscriptions(subscriptions);
+        this.slidingSync!.modifyRoomSubscriptions(subscriptions);
         if (room) {
-            return roomId; // we have data already for this room, show immediately e.g it's in a list
+            return; // we have data already for this room, show immediately e.g it's in a list
         }
-        try {
-            // wait until the next sync before returning as RoomView may need to know the current state
-            await p;
-        } catch {
-            logger.warn("SlidingSync setRoomVisible:", roomId, visible, "failed to confirm transaction");
-        }
-        return roomId;
+        // wait until we know about this room. This may take a little while.
+        return new Promise((resolve) => {
+            logger.log(`SlidingSync setRoomVisible room ${roomId} not found, waiting for ClientEvent.Room`);
+            const waitForRoom = (r: Room): void => {
+                if (r.roomId === roomId) {
+                    this.client?.off(ClientEvent.Room, waitForRoom);
+                    logger.log(`SlidingSync room ${roomId} found, resolving setRoomVisible`);
+                    resolve();
+                }
+            };
+            this.client?.on(ClientEvent.Room, waitForRoom);
+        });
     }
 
     /**
-     * Retrieve all rooms on the user's account. Used for pre-populating the local search cache.
-     * Retrieval is gradual over time.
+     * Retrieve all rooms on the user's account. Retrieval is gradual over time.
+     * This function MUST be called BEFORE the first sync request goes out.
      * @param batchSize The number of rooms to return in each request.
      * @param gapBetweenRequestsMs The number of milliseconds to wait between requests.
      */
-    public async startSpidering(batchSize: number, gapBetweenRequestsMs: number): Promise<void> {
-        await sleep(gapBetweenRequestsMs); // wait a bit as this is called on first render so let's let things load
-        let startIndex = batchSize;
-        let hasMore = true;
-        let firstTime = true;
-        while (hasMore) {
-            const endIndex = startIndex + batchSize - 1;
-            try {
-                const ranges = [
-                    [0, batchSize - 1],
-                    [startIndex, endIndex],
-                ];
-                if (firstTime) {
-                    await this.slidingSync!.setList(SlidingSyncManager.ListSearch, {
-                        // e.g [0,19] [20,39] then [0,19] [40,59]. We keep [0,20] constantly to ensure
-                        // any changes to the list whilst spidering are caught.
-                        ranges: ranges,
-                        sort: [
-                            "by_recency", // this list isn't shown on the UI so just sorting by timestamp is enough
-                        ],
-                        timeline_limit: 0, // we only care about the room details, not messages in the room
-                        required_state: [
-                            [EventType.RoomJoinRules, ""], // the public icon on the room list
-                            [EventType.RoomAvatar, ""], // any room avatar
-                            [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead
-                            [EventType.RoomEncryption, ""], // lets rooms be configured for E2EE correctly
-                            [EventType.RoomCreate, ""], // for isSpaceRoom checks
-                            [EventType.RoomMember, MSC3575_STATE_KEY_ME], // lets the client calculate that we are in fact in the room
-                        ],
-                        // we don't include_old_rooms here in an effort to reduce the impact of spidering all rooms
-                        // on the user's account. This means some data in the search dialog results may be inaccurate
-                        // e.g membership of space, but this will be corrected when the user clicks on the room
-                        // as the direct room subscription does include old room iterations.
-                        filters: {
-                            // we get spaces via a different list, so filter them out
-                            not_room_types: ["m.space"],
-                        },
-                    });
-                } else {
-                    await this.slidingSync!.setListRanges(SlidingSyncManager.ListSearch, ranges);
+    private async startSpidering(
+        slidingSync: SlidingSync,
+        batchSize: number,
+        gapBetweenRequestsMs: number,
+    ): Promise<void> {
+        // The manager has created several lists (see `sssLists` in this file), all of which will be spidered simultaneously.
+        // There are multiple lists to ensure that we can populate invites/favourites/DMs sections immediately, rather than
+        // potentially waiting minutes if they are all very old rooms (and hence are returned last by the server). In this
+        // way, the lists are effectively priority requests. We don't actually care which room goes into which list at this
+        // point, as the RoomListStore will calculate this based on the returned data.
+
+        // copy the initial set of list names and ranges, we'll keep this map updated.
+        const listToUpperBound = new Map(
+            Object.keys(sssLists).map((listName) => {
+                return [listName, sssLists[listName].ranges[0][1]];
+            }),
+        );
+        console.log("startSpidering:", listToUpperBound);
+
+        // listen for a response from the server. ANY 200 OK will do here, as we assume that it is ACKing
+        // the request change we have sent out. TODO: this may not be true if you concurrently subscribe to a room :/
+        // but in that case, for spidering at least, it isn't the end of the world as request N+1 includes all indexes
+        // from request N.
+        const lifecycle = async (
+            state: SlidingSyncState,
+            _: MSC3575SlidingSyncResponse | null,
+            err?: Error,
+        ): Promise<void> => {
+            if (state !== SlidingSyncState.Complete) {
+                return;
+            }
+            await sleep(gapBetweenRequestsMs); // don't tightloop; even on errors
+            if (err) {
+                return;
+            }
+
+            // for all lists with total counts > range => increase the range
+            let hasSetRanges = false;
+            listToUpperBound.forEach((currentUpperBound, listName) => {
+                const totalCount = slidingSync.getListData(listName)?.joinedCount || 0;
+                if (currentUpperBound < totalCount) {
+                    // increment the upper bound
+                    const newUpperBound = currentUpperBound + batchSize;
+                    console.log(`startSpidering: ${listName} ${currentUpperBound} => ${newUpperBound}`);
+                    listToUpperBound.set(listName, newUpperBound);
+                    // make the next request. This will only send the request when this callback has finished, so if
+                    // we set all the list ranges at once we will only send 1 new request.
+                    slidingSync.setListRanges(listName, [[0, newUpperBound]]);
+                    hasSetRanges = true;
                 }
-            } catch {
-                // do nothing, as we reject only when we get interrupted but that's fine as the next
-                // request will include our data
-            } finally {
-                // gradually request more over time, even on errors.
-                await sleep(gapBetweenRequestsMs);
+            });
+            if (!hasSetRanges) {
+                // finish spidering
+                slidingSync.off(SlidingSyncEvent.Lifecycle, lifecycle);
             }
-            const listData = this.slidingSync!.getListData(SlidingSyncManager.ListSearch)!;
-            hasMore = endIndex + 1 < listData.joinedCount;
-            startIndex += batchSize;
-            firstTime = false;
-        }
+        };
+        slidingSync.on(SlidingSyncEvent.Lifecycle, lifecycle);
     }
 
     /**
@@ -325,42 +382,10 @@ export class SlidingSyncManager {
      * @returns A working Sliding Sync or undefined
      */
     public async setup(client: MatrixClient): Promise<SlidingSync | undefined> {
-        const baseUrl = client.baseUrl;
-        const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");
-        const wellKnownProxyUrl = await this.getProxyFromWellKnown(client);
-
-        const slidingSyncEndpoint = proxyUrl || wellKnownProxyUrl || baseUrl;
-
-        this.configure(client, slidingSyncEndpoint);
-        logger.info("Sliding sync activated at", slidingSyncEndpoint);
-        this.startSpidering(100, 50); // 100 rooms at a time, 50ms apart
-
-        return this.slidingSync;
-    }
-
-    /**
-     * Get the sliding sync proxy URL from the client well known
-     * @param client The MatrixClient to use
-     * @return The proxy url
-     */
-    public async getProxyFromWellKnown(client: MatrixClient): Promise<string | undefined> {
-        let proxyUrl: string | undefined;
-
-        try {
-            const clientDomain = await client.getDomain();
-            if (clientDomain === null) {
-                throw new RangeError("Homeserver domain is null");
-            }
-            const clientWellKnown = await AutoDiscovery.findClientConfig(clientDomain);
-            proxyUrl = clientWellKnown?.["org.matrix.msc3575.proxy"]?.url;
-        } catch {
-            // Either client.getDomain() is null so we've shorted out, or is invalid so `AutoDiscovery.findClientConfig` has thrown
-        }
-
-        if (proxyUrl != undefined) {
-            logger.log("getProxyFromWellKnown: client well-known declares sliding sync proxy at", proxyUrl);
-        }
-        return proxyUrl;
+        const slidingSync = this.configure(client, client.baseUrl);
+        logger.info("Simplified Sliding Sync activated at", client.baseUrl);
+        this.startSpidering(slidingSync, 50, 50); // 50 rooms at a time, 50ms apart
+        return slidingSync;
     }
 
     /**
@@ -371,9 +396,9 @@ export class SlidingSyncManager {
     public async nativeSlidingSyncSupport(client: MatrixClient): Promise<boolean> {
         // Per https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1589542561
         // `client` can be undefined/null in tests for some reason.
-        const support = await client?.doesServerSupportUnstableFeature("org.matrix.msc3575");
+        const support = await client?.doesServerSupportUnstableFeature("org.matrix.simplified_msc3575");
         if (support) {
-            logger.log("nativeSlidingSyncSupport: sliding sync advertised as unstable");
+            logger.log("nativeSlidingSyncSupport: org.matrix.simplified_msc3575 sliding sync advertised as unstable");
         }
         return support;
     }
@@ -387,20 +412,9 @@ export class SlidingSyncManager {
      */
     public async checkSupport(client: MatrixClient): Promise<void> {
         if (await this.nativeSlidingSyncSupport(client)) {
-            SlidingSyncController.serverSupportsSlidingSync = true;
+            SlidingSyncManager.serverSupportsSlidingSync = true;
             return;
         }
-
-        const proxyUrl = await this.getProxyFromWellKnown(client);
-        if (proxyUrl != undefined) {
-            const response = await fetch(new URL("/client/server.json", proxyUrl), {
-                method: Method.Get,
-                signal: timeoutSignal(10 * 1000), // 10s
-            });
-            if (response.status === 200) {
-                logger.log("checkSupport: well-known sliding sync proxy is up at", proxyUrl);
-                SlidingSyncController.serverSupportsSlidingSync = true;
-            }
-        }
+        SlidingSyncManager.serverSupportsSlidingSync = false;
     }
 }
diff --git a/src/Terms.ts b/src/Terms.ts
index 02b67545da167566c96c41abdd288be6c0b29a6a..002ad0464fa0a5c0544cb85d49ad7516701cff68 100644
--- a/src/Terms.ts
+++ b/src/Terms.ts
@@ -7,11 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import { SERVICE_TYPES, MatrixClient } from "matrix-js-sdk/src/matrix";
+import {
+    type SERVICE_TYPES,
+    type MatrixClient,
+    type Terms,
+    type Policy,
+    type InternationalisedPolicy,
+} from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import Modal from "./Modal";
 import TermsDialog from "./components/views/dialogs/TermsDialog";
+import { pickBestLanguage } from "./languageHandler.tsx";
 
 export class TermsNotSignedError extends Error {}
 
@@ -32,23 +39,8 @@ export class Service {
     ) {}
 }
 
-export interface LocalisedPolicy {
-    name: string;
-    url: string;
-}
-
-export interface Policy {
-    // @ts-ignore: No great way to express indexed types together with other keys
-    version: string;
-    [lang: string]: LocalisedPolicy;
-}
-
-export type Policies = {
-    [policy: string]: Policy;
-};
-
 export type ServicePolicyPair = {
-    policies: Policies;
+    policies: Terms["policies"];
     service: Service;
 };
 
@@ -58,6 +50,11 @@ export type TermsInteractionCallback = (
     extraClassNames?: string,
 ) => Promise<string[]>;
 
+export function pickBestPolicyLanguage(policy: Policy): InternationalisedPolicy | undefined {
+    const termsLang = pickBestLanguage(Object.keys(policy).filter((k) => k !== "version"));
+    return <InternationalisedPolicy>policy[termsLang];
+}
+
 /**
  * Start a flow where the user is presented with terms & conditions for some services
  *
@@ -96,7 +93,7 @@ export async function startTermsFlow(
      * }
      */
 
-    const terms: { policies: Policies }[] = await Promise.all(termsPromises);
+    const terms: Terms[] = await Promise.all(termsPromises);
     const policiesAndServicePairs = terms.map((t, i) => {
         return { service: services[i], policies: t.policies };
     });
@@ -113,11 +110,11 @@ export async function startTermsFlow(
     // things they've not agreed to yet.
     const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
     for (const { service, policies } of policiesAndServicePairs) {
-        const unagreedPolicies: Policies = {};
+        const unagreedPolicies: Terms["policies"] = {};
         for (const [policyName, policy] of Object.entries(policies)) {
             let policyAgreed = false;
             for (const lang of Object.keys(policy)) {
-                if (lang === "version") continue;
+                if (lang === "version" || typeof policy[lang] === "string") continue;
                 if (agreedUrlSet.has(policy[lang].url)) {
                     policyAgreed = true;
                     break;
@@ -154,7 +151,7 @@ export async function startTermsFlow(
         const urlsForService = Array.from(agreedUrlSet).filter((url) => {
             for (const policy of Object.values(policiesAndService.policies)) {
                 for (const lang of Object.keys(policy)) {
-                    if (lang === "version") continue;
+                    if (lang === "version" || typeof policy[lang] === "string") continue;
                     if (policy[lang].url === url) return true;
                 }
             }
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index bdb7e8cbe0e91a83b04c91e6b215da266119fb8e..b63e5b2a00e32e50a940da54428b84f95abd3e29 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import {
-    MatrixEvent,
-    MatrixClient,
+    type MatrixEvent,
+    type MatrixClient,
     GuestAccess,
     HistoryVisibility,
     JoinRule,
@@ -17,11 +17,12 @@ import {
     MsgType,
     M_POLL_START,
     M_POLL_END,
+    ContentHelpers,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 import { removeDirectionOverrideChars } from "matrix-js-sdk/src/utils";
-import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
+import { type PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 
 import { _t } from "./languageHandler";
 import * as Roles from "./Roles";
@@ -190,7 +191,10 @@ function textForMemberEvent(
         case KnownMembership.Leave:
             if (ev.getSender() === ev.getStateKey()) {
                 if (prevContent.membership === KnownMembership.Invite) {
-                    return () => _t("timeline|m.room.member|reject_invite", { targetName });
+                    return () =>
+                        reason
+                            ? _t("timeline|m.room.member|reject_invite_reason", { targetName, reason })
+                            : _t("timeline|m.room.member|reject_invite", { targetName });
                 } else {
                     return () =>
                         reason
@@ -227,11 +231,16 @@ function textForMemberEvent(
 
 function textForTopicEvent(ev: MatrixEvent): (() => string) | null {
     const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
+    const topic = ContentHelpers.parseTopicContent(ev.getContent()).text;
     return () =>
-        _t("timeline|m.room.topic", {
-            senderDisplayName,
-            topic: ev.getContent().topic,
-        });
+        topic
+            ? _t("timeline|m.room.topic|changed", {
+                  senderDisplayName,
+                  topic,
+              })
+            : _t("timeline|m.room.topic|removed", {
+                  senderDisplayName,
+              });
 }
 
 function textForRoomAvatarEvent(ev: MatrixEvent): (() => string) | null {
diff --git a/src/Unread.ts b/src/Unread.ts
index e79a9cb75f92c28a2474f51fbc9aeca1fb7bd136..e8f4769e25f329de38a92849f542aef3c8ba6ec3 100644
--- a/src/Unread.ts
+++ b/src/Unread.ts
@@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { M_BEACON, Room, Thread, MatrixEvent, EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { M_BEACON, type Room, Thread, type MatrixEvent, EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import shouldHideEvent from "./shouldHideEvent";
 import { haveRendererForEvent } from "./events/EventTileFactory";
-import SettingsStore from "./settings/SettingsStore";
 import { RoomNotifState, getRoomNotifsState } from "./RoomNotifs";
 
 /**
@@ -44,12 +43,6 @@ export function eventTriggersUnreadCount(client: MatrixClient, ev: MatrixEvent):
 }
 
 export function doesRoomHaveUnreadMessages(room: Room, includeThreads: boolean): boolean {
-    if (SettingsStore.getValue("feature_sliding_sync")) {
-        // TODO: https://github.com/vector-im/element-web/issues/23207
-        // Sliding Sync doesn't support unread indicator dots (yet...)
-        return false;
-    }
-
     const toCheck: Array<Room | Thread> = [room];
     if (includeThreads) {
         toCheck.push(...room.getThreads());
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
deleted file mode 100644
index a0290a3eb1844032967c64f8a578e6dda25d246c..0000000000000000000000000000000000000000
--- a/src/VoipUserMapper.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2021 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { Room, EventType } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
-import { logger } from "matrix-js-sdk/src/logger";
-
-import { ensureVirtualRoomExists } from "./createRoom";
-import { MatrixClientPeg } from "./MatrixClientPeg";
-import DMRoomMap from "./utils/DMRoomMap";
-import LegacyCallHandler from "./LegacyCallHandler";
-import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
-import { findDMForUser } from "./utils/dm/findDMForUser";
-
-// Functions for mapping virtual users & rooms. Currently the only lookup
-// is sip virtual: there could be others in the future.
-
-export default class VoipUserMapper {
-    // We store mappings of virtual -> native room IDs here until the local echo for the
-    // account data arrives.
-    private virtualToNativeRoomIdCache = new Map<string, string>();
-
-    public static sharedInstance(): VoipUserMapper {
-        if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
-        return window.mxVoipUserMapper;
-    }
-
-    private async userToVirtualUser(userId: string): Promise<string | null> {
-        const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
-        if (results.length === 0 || !results[0].fields.lookup_success) return null;
-        return results[0].userid;
-    }
-
-    private async getVirtualUserForRoom(roomId: string): Promise<string | null> {
-        const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
-        if (!userId) return null;
-
-        const virtualUser = await this.userToVirtualUser(userId);
-        if (!virtualUser) return null;
-
-        return virtualUser;
-    }
-
-    public async getOrCreateVirtualRoomForRoom(roomId: string): Promise<string | null> {
-        const virtualUser = await this.getVirtualUserForRoom(roomId);
-        if (!virtualUser) return null;
-
-        const cli = MatrixClientPeg.safeGet();
-        const virtualRoomId = await ensureVirtualRoomExists(cli, virtualUser, roomId);
-        cli.setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
-            native_room: roomId,
-        });
-
-        this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
-
-        return virtualRoomId;
-    }
-
-    /**
-     * Gets the ID of the virtual room for a room, or null if the room has no
-     * virtual room
-     */
-    public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
-        const virtualUser = await this.getVirtualUserForRoom(roomId);
-        if (!virtualUser) return undefined;
-
-        return findDMForUser(MatrixClientPeg.safeGet(), virtualUser);
-    }
-
-    public nativeRoomForVirtualRoom(roomId: string): string | null {
-        const cachedNativeRoomId = this.virtualToNativeRoomIdCache.get(roomId);
-        if (cachedNativeRoomId) {
-            logger.log(
-                "Returning native room ID " + cachedNativeRoomId + " for virtual room ID " + roomId + " from cache",
-            );
-            return cachedNativeRoomId;
-        }
-
-        const cli = MatrixClientPeg.safeGet();
-        const virtualRoom = cli.getRoom(roomId);
-        if (!virtualRoom) return null;
-        const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
-        if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null;
-        const nativeRoomID = virtualRoomEvent.getContent()["native_room"];
-        const nativeRoom = cli.getRoom(nativeRoomID);
-        if (!nativeRoom || nativeRoom.getMyMembership() !== KnownMembership.Join) return null;
-
-        return nativeRoomID;
-    }
-
-    public isVirtualRoom(room: Room): boolean {
-        if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
-
-        if (this.virtualToNativeRoomIdCache.has(room.roomId)) return true;
-
-        // also look in the create event for the claimed native room ID, which is the only
-        // way we can recognise a virtual room we've created when it first arrives down
-        // our stream. We don't trust this in general though, as it could be faked by an
-        // inviter: our main source of truth is the DM state.
-        const roomCreateEvent = room.currentState.getStateEvents(EventType.RoomCreate, "");
-        if (!roomCreateEvent || !roomCreateEvent.getContent()) return false;
-        // we only look at this for rooms we created (so inviters can't just cause rooms
-        // to be invisible)
-        if (roomCreateEvent.getSender() !== MatrixClientPeg.safeGet().getUserId()) return false;
-        const claimedNativeRoomId = roomCreateEvent.getContent()[VIRTUAL_ROOM_EVENT_TYPE];
-        return Boolean(claimedNativeRoomId);
-    }
-
-    public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
-        if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
-
-        const inviterId = invitedRoom.getDMInviter();
-        if (!inviterId) {
-            logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
-        }
-
-        logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
-        const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
-        if (result.length === 0) {
-            return;
-        }
-
-        if (result[0].fields.is_virtual) {
-            const cli = MatrixClientPeg.safeGet();
-            const nativeUser = result[0].userid;
-            const nativeRoom = findDMForUser(cli, nativeUser);
-            if (nativeRoom) {
-                // It's a virtual room with a matching native room, so set the room account data. This
-                // will make sure we know where how to map calls and also allow us know not to display
-                // it in the future.
-                cli.setRoomAccountData(invitedRoom.roomId, VIRTUAL_ROOM_EVENT_TYPE, {
-                    native_room: nativeRoom.roomId,
-                });
-                // also auto-join the virtual room if we have a matching native room
-                // (possibly we should only join if we've also joined the native room, then we'd also have
-                // to make sure we joined virtual rooms on joining a native one)
-                cli.joinRoom(invitedRoom.roomId);
-
-                // also put this room in the virtual room ID cache so isVirtualRoom return the right answer
-                // in however long it takes for the echo of setAccountData to come down the sync
-                this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
-            }
-        }
-    }
-}
diff --git a/src/WhoIsTyping.ts b/src/WhoIsTyping.ts
index c209253206a3c957e29234d9689cb7f85b4f1e67..000cfa29e5ca36004d72b18150c210fe0cc365b8 100644
--- a/src/WhoIsTyping.ts
+++ b/src/WhoIsTyping.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "./languageHandler";
 
diff --git a/src/WorkerManager.ts b/src/WorkerManager.ts
index 089463dc91539d5cbaa2ae8e7d05242c8dc8f082..1d403be88c2ebae4322cf8aa63536623ee930652 100644
--- a/src/WorkerManager.ts
+++ b/src/WorkerManager.ts
@@ -6,14 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { defer, IDeferred } from "matrix-js-sdk/src/utils";
+import { type WorkerPayload } from "./workers/worker";
 
-import { WorkerPayload } from "./workers/worker";
-
-export class WorkerManager<Request extends {}, Response> {
+export class WorkerManager<Request extends object, Response> {
     private readonly worker: Worker;
     private seq = 0;
-    private pendingDeferredMap = new Map<number, IDeferred<Response>>();
+    private pendingDeferredMap = new Map<number, PromiseWithResolvers<Response>>();
 
     public constructor(worker: Worker) {
         this.worker = worker;
@@ -30,7 +28,7 @@ export class WorkerManager<Request extends {}, Response> {
 
     public call(request: Request): Promise<Response> {
         const seq = this.seq++;
-        const deferred = defer<Response>();
+        const deferred = Promise.withResolvers<Response>();
         this.pendingDeferredMap.set(seq, deferred);
         this.worker.postMessage({ seq, ...request });
         return deferred.promise;
diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts
index 4eafb80f36a74a3509f387c33727b4e0f51e8c44..a261bf361b1131b6100139b25a05524fac4d3725 100644
--- a/src/accessibility/KeyboardShortcutUtils.ts
+++ b/src/accessibility/KeyboardShortcutUtils.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { KeyCombo } from "../KeyBindingsManager";
+import { type KeyCombo } from "../KeyBindingsManager";
 import { IS_MAC, Key } from "../Keyboard";
 import { _t, _td } from "../languageHandler";
 import PlatformPeg from "../PlatformPeg";
@@ -14,10 +14,10 @@ import SettingsStore from "../settings/SettingsStore";
 import {
     DESKTOP_SHORTCUTS,
     DIGITS,
-    IKeyboardShortcuts,
+    type IKeyboardShortcuts,
     KeyBindingAction,
     KEYBOARD_SHORTCUTS,
-    KeyboardShortcutSetting,
+    type KeyboardShortcutSetting,
     MAC_ONLY_SHORTCUTS,
 } from "./KeyboardShortcuts";
 
diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts
index 1e8febb0bbc4fb9cceccd69cc39e674f80938d64..ec5b8312b92caeeeeb605d96bb9b41f554084173 100644
--- a/src/accessibility/KeyboardShortcuts.ts
+++ b/src/accessibility/KeyboardShortcuts.ts
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { _td, TranslationKey } from "../languageHandler";
+import { _td, type TranslationKey } from "../languageHandler";
 import { IS_MAC, IS_ELECTRON, Key } from "../Keyboard";
-import { IBaseSetting } from "../settings/Settings";
-import { KeyCombo } from "../KeyBindingsManager";
+import { type IBaseSetting } from "../settings/Settings";
+import { type KeyCombo } from "../KeyBindingsManager";
 
 export enum KeyBindingAction {
     /** Send a message */
@@ -520,7 +520,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
     },
     [KeyBindingAction.GoToHome]: {
         default: {
-            ctrlOrCmdKey: true,
+            ctrlKey: true,
             altKey: !IS_MAC,
             shiftKey: IS_MAC,
             key: Key.H,
@@ -585,9 +585,9 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
     },
     [KeyBindingAction.ToggleHiddenEventVisibility]: {
         default: {
-            ctrlOrCmdKey: true,
+            ctrlKey: true,
             shiftKey: true,
-            key: Key.H,
+            key: Key.J,
         },
         displayName: _td("keyboard|toggle_hidden_events"),
     },
diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx
index e2227ea42d203c21104bbda14d80ffb1db5c5800..9942b4398c64faecdbca2d59f7a6e1e7736968e6 100644
--- a/src/accessibility/RovingTabIndex.tsx
+++ b/src/accessibility/RovingTabIndex.tsx
@@ -13,16 +13,16 @@ import React, {
     useMemo,
     useRef,
     useReducer,
-    Reducer,
-    Dispatch,
-    RefObject,
-    ReactNode,
-    RefCallback,
+    type Reducer,
+    type Dispatch,
+    type RefObject,
+    type ReactNode,
+    type RefCallback,
 } from "react";
 
 import { getKeyBindingsManager } from "../KeyBindingsManager";
 import { KeyBindingAction } from "./KeyboardShortcuts";
-import { FocusHandler } from "./roving/types";
+import { type FocusHandler } from "./roving/types";
 
 /**
  * Module to simplify implementing the Roving TabIndex accessibility technique
@@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
     scrollIntoView,
     onKeyDown,
 }) => {
-    const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, {
+    const [state, dispatch] = useReducer<IState, [Action]>(reducer, {
         nodes: [],
     });
 
@@ -354,7 +354,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
  * nodeRef = inputRef when inputRef argument is provided.
  */
 export const useRovingTabIndex = <T extends HTMLElement>(
-    inputRef?: RefObject<T>,
+    inputRef?: RefObject<T | null>,
 ): [FocusHandler, boolean, RefCallback<T>, RefObject<T | null>] => {
     const context = useContext(RovingTabIndexContext);
 
diff --git a/src/accessibility/Toolbar.tsx b/src/accessibility/Toolbar.tsx
index 317ff8b9363effba2cd5d562beae22a8d5abf845..5583292e4887c4ad5ffd48eddff949abb561a848 100644
--- a/src/accessibility/Toolbar.tsx
+++ b/src/accessibility/Toolbar.tsx
@@ -6,18 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef } from "react";
+import React, { type Ref, type JSX } from "react";
 
 import { RovingTabIndexProvider } from "./RovingTabIndex";
 import { getKeyBindingsManager } from "../KeyBindingsManager";
 import { KeyBindingAction } from "./KeyboardShortcuts";
 
-interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {}
+interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
+    ref?: Ref<HTMLDivElement>;
+}
 
 // This component implements the Toolbar design pattern from the WAI-ARIA Authoring Practices guidelines.
 // https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
 // All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
-const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref) => {
+const Toolbar = ({ children, ref, ...props }: IProps): JSX.Element => {
     const onKeyDown = (ev: React.KeyboardEvent): void => {
         const target = ev.target as HTMLElement;
         // Don't interfere with input default keydown behaviour
@@ -55,6 +57,6 @@ const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref)
             )}
         </RovingTabIndexProvider>
     );
-});
+};
 
 export default Toolbar;
diff --git a/src/accessibility/context_menu/ContextMenuButton.tsx b/src/accessibility/context_menu/ContextMenuButton.tsx
index e326e087b975d121c8dcf23e9f353f9c16e36374..df16d15821ab822e055c42c1f61758b49010974e 100644
--- a/src/accessibility/context_menu/ContextMenuButton.tsx
+++ b/src/accessibility/context_menu/ContextMenuButton.tsx
@@ -8,21 +8,27 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, Ref } from "react";
+import React, { type Ref, type JSX } from "react";
 
-import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";
 
 type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
     label?: string;
     // whether the context menu is currently open
     isExpanded: boolean;
+    ref?: Ref<HTMLElementTagNameMap[T]>;
 };
 
 // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
-export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
-    { label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
-    ref: Ref<HTMLElementTagNameMap[T]>,
-) {
+export const ContextMenuButton = function <T extends keyof HTMLElementTagNameMap>({
+    label,
+    isExpanded,
+    children,
+    onClick,
+    onContextMenu,
+    ref,
+    ...props
+}: Props<T>): JSX.Element {
     return (
         <AccessibleButton
             {...props}
@@ -36,4 +42,4 @@ export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElemen
             {children}
         </AccessibleButton>
     );
-});
+};
diff --git a/src/accessibility/context_menu/ContextMenuTooltipButton.tsx b/src/accessibility/context_menu/ContextMenuTooltipButton.tsx
index 7a4230ddf78a54744435bbd7dc6f07c8f26fe0a3..b0d0156c0f6dd59f299a6b8ef64c8f176c11a5c6 100644
--- a/src/accessibility/context_menu/ContextMenuTooltipButton.tsx
+++ b/src/accessibility/context_menu/ContextMenuTooltipButton.tsx
@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, Ref } from "react";
+import React, { type JSX } from "react";
 
-import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";
 
 type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
     // whether the context menu is currently open
@@ -18,10 +18,14 @@ type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
 };
 
 // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
-export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
-    { isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
-    ref: Ref<HTMLElementTagNameMap[T]>,
-) {
+export const ContextMenuTooltipButton = function <T extends keyof HTMLElementTagNameMap>({
+    isExpanded,
+    children,
+    onClick,
+    onContextMenu,
+    ref,
+    ...props
+}: Props<T>): JSX.Element {
     return (
         <AccessibleButton
             {...props}
@@ -35,4 +39,4 @@ export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTM
             {children}
         </AccessibleButton>
     );
-});
+};
diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
index 472e7bcae2aab6030d58df4246de377067c59b58..f208eb1091d35cd443c71cd967878070dd074804 100644
--- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
+++ b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2019 The Matrix.org Foundation C.I.C.
 Copyright 2018 New Vector Ltd
 Copyright 2015, 2016 OpenMarket Ltd
@@ -16,13 +16,12 @@ import { KeyBindingAction } from "../KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 
 interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
-    label?: string;
     onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
     onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
 }
 
 // Semantic component for representing a styled role=menuitemcheckbox
-export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onChange, onClose, ...props }) => {
+export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, onChange, onClose, ...props }) => {
     const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
 
     const onKeyDown = (e: React.KeyboardEvent): void => {
@@ -63,7 +62,6 @@ export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onCh
         <StyledCheckbox
             {...props}
             role="menuitemcheckbox"
-            aria-label={label}
             onChange={onChange}
             onKeyDown={onKeyDown}
             onKeyUp={onKeyUp}
diff --git a/src/accessibility/roving/RovingAccessibleButton.tsx b/src/accessibility/roving/RovingAccessibleButton.tsx
index 70e569e335134baf63281920f55749e7eb29426e..e161a44f5450b674bb0c884af2f1a8dc356ce201 100644
--- a/src/accessibility/roving/RovingAccessibleButton.tsx
+++ b/src/accessibility/roving/RovingAccessibleButton.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { RefObject } from "react";
+import React, { type JSX, type RefObject } from "react";
 
-import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";
 import { useRovingTabIndex } from "../RovingTabIndex";
 
 type Props<T extends keyof HTMLElementTagNameMap> = Omit<ButtonProps<T>, "tabIndex"> & {
-    inputRef?: RefObject<HTMLElementTagNameMap[T]>;
+    inputRef?: RefObject<HTMLElementTagNameMap[T] | null>;
     focusOnMouseOver?: boolean;
 };
 
diff --git a/src/accessibility/roving/RovingTabIndexWrapper.tsx b/src/accessibility/roving/RovingTabIndexWrapper.tsx
index a47c1cb3efcc1fe9f0e01833e2b08b62b46c8db4..f2c7c98dd3be2c770c8d68f5c92ae4614830e9b2 100644
--- a/src/accessibility/roving/RovingTabIndexWrapper.tsx
+++ b/src/accessibility/roving/RovingTabIndexWrapper.tsx
@@ -6,13 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, RefCallback } from "react";
+import { type ReactElement, type RefCallback, type RefObject } from "react";
 
+import type React from "react";
 import { useRovingTabIndex } from "../RovingTabIndex";
-import { FocusHandler, Ref } from "./types";
+import { type FocusHandler } from "./types";
 
 interface IProps {
-    inputRef?: Ref;
+    inputRef?: RefObject<HTMLElement | null>;
     children(renderProps: {
         onFocus: FocusHandler;
         isActive: boolean;
diff --git a/src/accessibility/roving/types.ts b/src/accessibility/roving/types.ts
index 3cf8cdf6c0c288c6a428af509d580ef399211c79..f06c15cf7ac4008d17832240a4e2fbde209cb44c 100644
--- a/src/accessibility/roving/types.ts
+++ b/src/accessibility/roving/types.ts
@@ -6,8 +6,4 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RefObject } from "react";
-
-export type Ref = RefObject<HTMLElement>;
-
 export type FocusHandler = () => void;
diff --git a/src/actions/MatrixActionCreators.ts b/src/actions/MatrixActionCreators.ts
index ccda4853f6df562dac63a689065f8f171e94153c..9dc0127bff38401714af0a5d3804d2bccd556665 100644
--- a/src/actions/MatrixActionCreators.ts
+++ b/src/actions/MatrixActionCreators.ts
@@ -8,18 +8,18 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     ClientEvent,
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     MatrixEventEvent,
-    Room,
+    type Room,
     RoomEvent,
-    IRoomTimelineData,
-    RoomState,
+    type IRoomTimelineData,
+    type RoomState,
     RoomStateEvent,
 } from "matrix-js-sdk/src/matrix";
 
 import dis from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 
 /**
  * Create a MatrixActions.sync action that represents a MatrixClient `sync` event,
diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts
index 7418ec2cdd1fcd675695eb9fbdfa90d92386847e..255518a7dcb57d9addf13831b7ee116b02b4b1ce 100644
--- a/src/actions/RoomListActions.ts
+++ b/src/actions/RoomListActions.ts
@@ -7,17 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { asyncAction } from "./actionCreators";
 import Modal from "../Modal";
 import * as Rooms from "../Rooms";
 import { _t } from "../languageHandler";
-import { AsyncActionPayload } from "../dispatcher/payloads";
+import { type AsyncActionPayload } from "../dispatcher/payloads";
 import RoomListStore from "../stores/room-list/RoomListStore";
 import { SortAlgorithm } from "../stores/room-list/algorithms/models";
-import { DefaultTagID, TagID } from "../stores/room-list/models";
+import { DefaultTagID, type TagID } from "../stores/room-list/models";
 import ErrorDialog from "../components/views/dialogs/ErrorDialog";
 
 export default class RoomListActions {
diff --git a/src/actions/actionCreators.ts b/src/actions/actionCreators.ts
index f3a097d06f039fa573c61f898874c9e29fe47eb8..ed8eb6b265e79e26c7d834ec9375a050260ba188 100644
--- a/src/actions/actionCreators.ts
+++ b/src/actions/actionCreators.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AsyncActionFn, AsyncActionPayload } from "../dispatcher/payloads";
+import { type AsyncActionFn, AsyncActionPayload } from "../dispatcher/payloads";
 
 /**
  * Create an action thunk that will dispatch actions indicating the current
diff --git a/src/async-components/structures/ErrorView.tsx b/src/async-components/structures/ErrorView.tsx
index cee6c6345bd4173df81744aaf9d71ab3966864a7..0f9a61781ed45d8f7143fc1d9952e633afe8b536 100644
--- a/src/async-components/structures/ErrorView.tsx
+++ b/src/async-components/structures/ErrorView.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { Text, Heading, Button, Separator } from "@vector-im/compound-web";
 import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
 
@@ -80,9 +80,9 @@ const MobileAppLinks: React.FC<{
 const DesktopAppLinks: React.FC<{
     macOsUrl?: string;
     win64Url?: string;
-    win32Url?: string;
+    win64ArmUrl?: string;
     linuxUrl?: string;
-}> = ({ macOsUrl, win64Url, win32Url, linuxUrl }) => {
+}> = ({ macOsUrl, win64Url, win64ArmUrl, linuxUrl }) => {
     return (
         <Flex gap="var(--cpd-space-4x)">
             {macOsUrl && (
@@ -92,12 +92,12 @@ const DesktopAppLinks: React.FC<{
             )}
             {win64Url && (
                 <Button as="a" href={win64Url} kind="secondary" Icon={MicrosoftIcon}>
-                    {_t("incompatible_browser|windows", { bits: "64" })}
+                    {_t("incompatible_browser|windows_64bit")}
                 </Button>
             )}
-            {win32Url && (
-                <Button as="a" href={win32Url} kind="secondary" Icon={MicrosoftIcon}>
-                    {_t("incompatible_browser|windows", { bits: "32" })}
+            {win64ArmUrl && (
+                <Button as="a" href={win64ArmUrl} kind="secondary" Icon={MicrosoftIcon}>
+                    {_t("incompatible_browser|windows_arm_64bit")}
                 </Button>
             )}
             {linuxUrl && (
@@ -127,7 +127,7 @@ export const UnsupportedBrowserView: React.FC<{
         config.desktop_builds?.available &&
         (config.desktop_builds?.url_macos ||
             config.desktop_builds?.url_win64 ||
-            config.desktop_builds?.url_win32 ||
+            config.desktop_builds?.url_win64arm ||
             config.desktop_builds?.url_linux);
     const hasMobileBuilds = Boolean(
         config.mobile_builds?.ios || config.mobile_builds?.android || config.mobile_builds?.fdroid,
@@ -157,7 +157,7 @@ export const UnsupportedBrowserView: React.FC<{
                             <DesktopAppLinks
                                 macOsUrl={config.desktop_builds?.url_macos}
                                 win64Url={config.desktop_builds?.url_win64}
-                                win32Url={config.desktop_builds?.url_win32}
+                                win64ArmUrl={config.desktop_builds?.url_win64arm}
                                 linuxUrl={config.desktop_builds?.url_linux}
                             />
                         </>
diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
index a7405235490b1b7811d088616ee39b4a2d8fd943..d0680fb4cd48b5955236aac8d09595fcad8feb44 100644
--- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
+++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import SdkConfig from "../../../../SdkConfig";
@@ -19,7 +19,7 @@ import { SettingLevel } from "../../../../settings/SettingLevel";
 import Field from "../../../../components/views/elements/Field";
 import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
 import DialogButtons from "../../../../components/views/elements/DialogButtons";
-import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
+import { type IIndexStats } from "../../../../indexing/BaseEventIndexManager";
 
 interface IProps {
     onFinished(): void;
diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
index 30d04baec1d868211d2cc07986f8681b22aed3cf..0bc6fea2195b5cc08574512946a9aae981f1d31b 100644
--- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../../../../MatrixClientPeg";
diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
index 235f73fc8e69d729547613c8a5728dd7023ab686..0d17ddaa39e4b8ceb391e95424d933642538c836 100644
--- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
+import React, { type JSX, createRef } from "react";
 import FileSaver from "file-saver";
 import { logger } from "matrix-js-sdk/src/logger";
-import { AuthDict, CrossSigningKeys, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix";
-import { GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
+import { type AuthDict } from "matrix-js-sdk/src/matrix";
+import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
 import classNames from "classnames";
 import CheckmarkIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
 
@@ -31,13 +31,13 @@ import {
     SecureBackupSetupMethod,
 } from "../../../../utils/WellKnownUtils";
 import { ModuleRunner } from "../../../../modules/ModuleRunner";
-import Field from "../../../../components/views/elements/Field";
+import type Field from "../../../../components/views/elements/Field";
 import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
 import Spinner from "../../../../components/views/elements/Spinner";
 import InteractiveAuthDialog from "../../../../components/views/dialogs/InteractiveAuthDialog";
-import { IValidationResult } from "../../../../components/views/elements/Validation";
+import { type IValidationResult } from "../../../../components/views/elements/Validation";
 import PassphraseConfirmField from "../../../../components/views/auth/PassphraseConfirmField";
-import { initialiseDehydration } from "../../../../utils/device/dehydration";
+import { initialiseDehydrationIfEnabled } from "../../../../utils/device/dehydration";
 
 // I made a mistake while converting this and it has to be fixed!
 enum Phase {
@@ -55,8 +55,6 @@ enum Phase {
 const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
 
 interface IProps {
-    hasCancel?: boolean;
-    accountPassword?: string;
     forceReset?: boolean;
     resetCrossSigning?: boolean;
     onFinished(ok?: boolean): void;
@@ -71,11 +69,6 @@ interface IState {
     downloaded: boolean;
     setPassphrase: boolean;
 
-    // does the server offer a UI auth flow with just m.login.password
-    // for /keys/device_signing/upload?
-    canUploadKeysWithPasswordOnly: boolean | null;
-    accountPassword: string;
-    accountPasswordCorrect: boolean | null;
     canSkip: boolean;
     passPhraseKeySelected: string;
     error?: boolean;
@@ -90,7 +83,6 @@ interface IState {
  */
 export default class CreateSecretStorageDialog extends React.PureComponent<IProps, IState> {
     public static defaultProps: Partial<IProps> = {
-        hasCancel: true,
         forceReset: false,
         resetCrossSigning: false,
     };
@@ -111,16 +103,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
             passPhraseKeySelected = SecureBackupSetupMethod.Passphrase;
         }
 
-        const accountPassword = props.accountPassword || "";
-        let canUploadKeysWithPasswordOnly: boolean | null = null;
-        if (accountPassword) {
-            // If we have an account password in memory, let's simplify and
-            // assume it means password auth is also supported for device
-            // signing key upload as well. This avoids hitting the server to
-            // test auth flows, which may be slow under high load.
-            canUploadKeysWithPasswordOnly = true;
-        }
-
         const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
         const phase = keyFromCustomisations ? Phase.Loading : Phase.ChooseKeyPassphrase;
 
@@ -132,23 +114,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
             copied: false,
             downloaded: false,
             setPassphrase: false,
-            // does the server offer a UI auth flow with just m.login.password
-            // for /keys/device_signing/upload?
-            accountPasswordCorrect: null,
             canSkip: !isSecureBackupRequired(cli),
-            canUploadKeysWithPasswordOnly,
             passPhraseKeySelected,
-            accountPassword,
         };
     }
 
     public componentDidMount(): void {
         const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
         if (keyFromCustomisations) this.initExtension(keyFromCustomisations);
-
-        if (this.state.canUploadKeysWithPasswordOnly === null) {
-            this.queryKeyUploadAuth();
-        }
     }
 
     private initExtension(keyFromCustomisations: Uint8Array): void {
@@ -159,27 +132,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
         this.bootstrapSecretStorage();
     }
 
-    private async queryKeyUploadAuth(): Promise<void> {
-        try {
-            await MatrixClientPeg.safeGet().uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys);
-            // We should never get here: the server should always require
-            // UI auth to upload device signing keys. If we do, we upload
-            // no keys which would be a no-op.
-            logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
-        } catch (error) {
-            if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
-                logger.log("uploadDeviceSigningKeys advertised no flows!");
-                return;
-            }
-            const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => {
-                return f.stages.length === 1 && f.stages[0] === "m.login.password";
-            });
-            this.setState({
-                canUploadKeysWithPasswordOnly,
-            });
-        }
-    }
-
     private onKeyPassphraseChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
         this.setState({
             passPhraseKeySelected: e.target.value,
@@ -225,47 +177,34 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
         });
     };
 
-    private doBootstrapUIAuth = async (
-        makeRequest: (authData: AuthDict) => Promise<UIAResponse<void>>,
-    ): Promise<void> => {
-        if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
-            await makeRequest({
-                type: "m.login.password",
-                identifier: {
-                    type: "m.id.user",
-                    user: MatrixClientPeg.safeGet().getSafeUserId(),
-                },
-                password: this.state.accountPassword,
-            });
-        } else {
-            const dialogAesthetics = {
-                [SSOAuthEntry.PHASE_PREAUTH]: {
-                    title: _t("auth|uia|sso_title"),
-                    body: _t("auth|uia|sso_preauth_body"),
-                    continueText: _t("auth|sso"),
-                    continueKind: "primary",
-                },
-                [SSOAuthEntry.PHASE_POSTAUTH]: {
-                    title: _t("encryption|confirm_encryption_setup_title"),
-                    body: _t("encryption|confirm_encryption_setup_body"),
-                    continueText: _t("action|confirm"),
-                    continueKind: "primary",
-                },
-            };
-
-            const { finished } = Modal.createDialog(InteractiveAuthDialog, {
-                title: _t("encryption|bootstrap_title"),
-                matrixClient: MatrixClientPeg.safeGet(),
-                makeRequest,
-                aestheticsForStagePhases: {
-                    [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
-                    [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
-                },
-            });
-            const [confirmed] = await finished;
-            if (!confirmed) {
-                throw new Error("Cross-signing key upload auth canceled");
-            }
+    private doBootstrapUIAuth = async (makeRequest: (authData: AuthDict) => Promise<void>): Promise<void> => {
+        const dialogAesthetics = {
+            [SSOAuthEntry.PHASE_PREAUTH]: {
+                title: _t("auth|uia|sso_title"),
+                body: _t("auth|uia|sso_preauth_body"),
+                continueText: _t("auth|sso"),
+                continueKind: "primary",
+            },
+            [SSOAuthEntry.PHASE_POSTAUTH]: {
+                title: _t("encryption|confirm_encryption_setup_title"),
+                body: _t("encryption|confirm_encryption_setup_body"),
+                continueText: _t("action|confirm"),
+                continueKind: "primary",
+            },
+        };
+
+        const { finished } = Modal.createDialog(InteractiveAuthDialog, {
+            title: _t("encryption|bootstrap_title"),
+            matrixClient: MatrixClientPeg.safeGet(),
+            makeRequest,
+            aestheticsForStagePhases: {
+                [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
+                [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+            },
+        });
+        const [confirmed] = await finished;
+        if (!confirmed) {
+            throw new Error("Cross-signing key upload auth canceled");
         }
     };
 
@@ -332,7 +271,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
                     setupNewKeyBackup: !backupInfo,
                 });
             }
-            await initialiseDehydration(true);
+            await initialiseDehydrationIfEnabled(cli, { createNewKey: true });
 
             this.setState({
                 phase: Phase.Stored,
@@ -805,7 +744,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
                 top={this.topComponent}
                 title={this.titleForPhase(this.state.phase)}
                 titleClass={titleClass}
-                hasCancel={this.props.hasCancel && [Phase.Passphrase].includes(this.state.phase)}
+                hasCancel={false}
                 fixedWidth={false}
             >
                 <div>{content}</div>
diff --git a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
index 18dd507b5a5eef820f26cee0ea3f1a5fd2bd935b..31c7c7ca8d8a7389ffa6c9006ab740b8738ab815 100644
--- a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
+++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
@@ -8,17 +8,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import FileSaver from "file-saver";
-import React, { ChangeEvent } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t, _td } from "../../../../languageHandler";
 import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
 import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
-import { KeysStartingWith } from "../../../../@types/common";
+import { type KeysStartingWith } from "../../../../@types/common";
 import PassphraseField from "../../../../components/views/auth/PassphraseField";
 import PassphraseConfirmField from "../../../../components/views/auth/PassphraseConfirmField";
-import Field from "../../../../components/views/elements/Field";
+import type Field from "../../../../components/views/elements/Field";
 
 enum Phase {
     Edit = "edit",
@@ -178,7 +178,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
                                     type="password"
                                     disabled={disableForm}
                                     autoComplete="new-password"
-                                    fieldRef={(field) => (this.fieldPassword = field)}
+                                    fieldRef={(field) => {
+                                        this.fieldPassword = field;
+                                    }}
                                 />
                             </div>
                             <div className="mx_E2eKeysDialog_inputRow">
@@ -195,7 +197,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
                                     type="password"
                                     disabled={disableForm}
                                     autoComplete="new-password"
-                                    fieldRef={(field) => (this.fieldPasswordConfirm = field)}
+                                    fieldRef={(field) => {
+                                        this.fieldPasswordConfirm = field;
+                                    }}
                                 />
                             </div>
                         </div>
diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
index 16d50640f56784bf4138b2b28a022b2d2903efda..fff841d8a67a2b2c9947c23156bd6c7c237f76f7 100644
--- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
+++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { createRef } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
diff --git a/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx
index bec708e6645ce88c344fceb17996018dbde807da..a2fef4e1c92d7cfeac525a2828c1b3210e2ddd9e 100644
--- a/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx
+++ b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { JSX, useEffect, useState } from "react";
+import React, { type JSX, useEffect, useState } from "react";
 
 import dis from "../../../../dispatcher/dispatcher";
 import { _t } from "../../../../languageHandler";
@@ -49,15 +49,8 @@ export default function NewRecoveryMethodDialog({ onFinished }: NewRecoveryMetho
         if (isKeyBackupEnabled) {
             onFinished();
         } else {
-            Modal.createDialog(
-                RestoreKeyBackupDialog,
-                {
-                    onFinished,
-                },
-                undefined,
-                false,
-                true,
-            );
+            const { finished } = Modal.createDialog(RestoreKeyBackupDialog, {}, undefined, false, true);
+            finished.then(onFinished);
         }
     }
 
diff --git a/src/audio/BackgroundAudio.ts b/src/audio/BackgroundAudio.ts
index c90016eef91578a338708e171adc9bd39657c53a..eda10c0904dcadf38acb61a8293db7cd1ccb72d3 100644
--- a/src/audio/BackgroundAudio.ts
+++ b/src/audio/BackgroundAudio.ts
@@ -48,6 +48,12 @@ export class BackgroundAudio {
         source.buffer = this.sounds[url];
         source.loop = loop;
         source.connect(this.audioContext.destination);
+
+        await this.audioContext.resume();
+        source.onended = () => {
+            this.audioContext.suspend();
+        };
+
         source.start();
         return source;
     }
diff --git a/src/audio/ManagedPlayback.ts b/src/audio/ManagedPlayback.ts
index 0f0b420d1ed919b1db96ac2c3e33f23ae51e9435..43cea38f5f09f3512aab578c9f24118e2e9358c3 100644
--- a/src/audio/ManagedPlayback.ts
+++ b/src/audio/ManagedPlayback.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { Playback } from "./Playback";
-import { PlaybackManager } from "./PlaybackManager";
+import { type PlaybackManager } from "./PlaybackManager";
 import { DEFAULT_WAVEFORM } from "./consts";
 
 /**
diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts
index af777a8c0119522c52d7c954bf1a79ca60340daa..54d2c710d0a8e35b44c3473081e9d4337adc57b3 100644
--- a/src/audio/Playback.ts
+++ b/src/audio/Playback.ts
@@ -9,11 +9,10 @@ Please see LICENSE files in the repository root for full details.
 import EventEmitter from "events";
 import { SimpleObservable } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import { UPDATE_EVENT } from "../stores/AsyncStore";
 import { arrayFastResample } from "../utils/arrays";
-import { IDestroyable } from "../utils/IDestroyable";
+import { type IDestroyable } from "../utils/IDestroyable";
 import { PlaybackClock } from "./PlaybackClock";
 import { createAudioContext, decodeOgg } from "./compat";
 import { clamp } from "../utils/numbers";
@@ -158,42 +157,27 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
             // 5mb
             logger.log("Audio file too large: processing through <audio /> element");
             this.element = document.createElement("AUDIO") as HTMLAudioElement;
-            const deferred = defer<unknown>();
+            const deferred = Promise.withResolvers<unknown>();
             this.element.onloadeddata = deferred.resolve;
             this.element.onerror = deferred.reject;
             this.element.src = URL.createObjectURL(new Blob([this.buf]));
             await deferred.promise; // make sure the audio element is ready for us
         } else {
-            // Safari compat: promise API not supported on this function
-            this.audioBuf = await new Promise((resolve, reject) => {
-                this.context.decodeAudioData(
-                    this.buf,
-                    (b) => resolve(b),
-                    async (e): Promise<void> => {
-                        try {
-                            // This error handler is largely for Safari as well, which doesn't support Opus/Ogg
-                            // very well.
-                            logger.error("Error decoding recording: ", e);
-                            logger.warn("Trying to re-encode to WAV instead...");
-
-                            const wav = await decodeOgg(this.buf);
-
-                            // noinspection ES6MissingAwait - not needed when using callbacks
-                            this.context.decodeAudioData(
-                                wav,
-                                (b) => resolve(b),
-                                (e) => {
-                                    logger.error("Still failed to decode recording: ", e);
-                                    reject(e);
-                                },
-                            );
-                        } catch (e) {
-                            logger.error("Caught decoding error:", e);
-                            reject(e);
-                        }
-                    },
-                );
-            });
+            try {
+                this.audioBuf = await this.context.decodeAudioData(this.buf);
+            } catch (e) {
+                logger.error("Error decoding recording:", e);
+                logger.warn("Trying to re-encode to WAV instead...");
+
+                try {
+                    // This error handler is largely for Safari, which doesn't support Opus/Ogg very well.
+                    const wav = await decodeOgg(this.buf);
+                    this.audioBuf = await this.context.decodeAudioData(wav);
+                } catch (e) {
+                    logger.error("Error decoding recording:", e);
+                    throw e;
+                }
+            }
 
             // Update the waveform to the real waveform once we have channel data to use. We don't
             // exactly trust the user-provided waveform to be accurate...
diff --git a/src/audio/PlaybackClock.ts b/src/audio/PlaybackClock.ts
index ceb06987c887c94e2ead5d578fa7baec8630aeb5..4099483c8ef52b9a88cd2cd75376ffa5f229ed20 100644
--- a/src/audio/PlaybackClock.ts
+++ b/src/audio/PlaybackClock.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { SimpleObservable } from "matrix-widget-api";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IDestroyable } from "../utils/IDestroyable";
+import { type IDestroyable } from "../utils/IDestroyable";
 
 /**
  * Tracks accurate human-perceptible time for an audio clip, as informed
diff --git a/src/audio/PlaybackManager.ts b/src/audio/PlaybackManager.ts
index 80d28f119605bcd831100d8efad27bb3b6034b09..85677d68aa76487443ebce9df3e188d4c2007bed 100644
--- a/src/audio/PlaybackManager.ts
+++ b/src/audio/PlaybackManager.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Playback, PlaybackState } from "./Playback";
+import { type Playback, PlaybackState } from "./Playback";
 import { ManagedPlayback } from "./ManagedPlayback";
 import { DEFAULT_WAVEFORM } from "./consts";
 
diff --git a/src/audio/PlaybackQueue.ts b/src/audio/PlaybackQueue.ts
index f84f6add3cc0b883a0a501b161fa946949f9a229..fbca9d787241ccb1c4af748eee2c540bc9697607 100644
--- a/src/audio/PlaybackQueue.ts
+++ b/src/audio/PlaybackQueue.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room, EventType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { Playback, PlaybackState } from "./Playback";
+import { type Playback, PlaybackState } from "./Playback";
 import { UPDATE_EVENT } from "../stores/AsyncStore";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import { arrayFastClone } from "../utils/arrays";
diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts
index acf23f4dcd05bdbfef18703f1a193bca719ec90b..ec4a143c4eed8a1a86813e66940603af1e79ae28 100644
--- a/src/audio/RecorderWorklet.ts
+++ b/src/audio/RecorderWorklet.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAmplitudePayload, ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
+import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
 import { percentageOf } from "../utils/numbers";
 
 // from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope
diff --git a/src/audio/VoiceMessageRecording.ts b/src/audio/VoiceMessageRecording.ts
index 2dac4ac9c3c2fc742cf8e9fa5f3c3f5b12c69e75..1b15896e975c333c63f16b638b4d60a94fcf8ac6 100644
--- a/src/audio/VoiceMessageRecording.ts
+++ b/src/audio/VoiceMessageRecording.ts
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { EncryptedFile } from "matrix-js-sdk/src/types";
-import { SimpleObservable } from "matrix-widget-api";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type EncryptedFile } from "matrix-js-sdk/src/types";
+import { type SimpleObservable } from "matrix-widget-api";
 
 import { uploadFile } from "../ContentMessages";
 import { concat } from "../utils/arrays";
-import { IDestroyable } from "../utils/IDestroyable";
+import { type IDestroyable } from "../utils/IDestroyable";
 import { Singleflight } from "../utils/Singleflight";
 import { Playback } from "./Playback";
-import { IRecordingUpdate, RecordingState, VoiceRecording } from "./VoiceRecording";
+import { type IRecordingUpdate, RecordingState, VoiceRecording } from "./VoiceRecording";
 
 export interface IUpload {
     mxc?: string; // for unencrypted uploads
diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts
index 0a48c27e06a07c808bdb5fd81f1d517eda664dfd..bde86f9dd78b3d59a31973c11ba2ff2f070e408d 100644
--- a/src/audio/VoiceRecording.ts
+++ b/src/audio/VoiceRecording.ts
@@ -13,7 +13,7 @@ import EventEmitter from "events";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import MediaDeviceHandler from "../MediaDeviceHandler";
-import { IDestroyable } from "../utils/IDestroyable";
+import { type IDestroyable } from "../utils/IDestroyable";
 import { Singleflight } from "../utils/Singleflight";
 import { PayloadEvent, WORKLET_NAME } from "./consts";
 import { UPDATE_EVENT } from "../stores/AsyncStore";
diff --git a/src/audio/compat.ts b/src/audio/compat.ts
index 7af6ef68b234f461ce6226a0c92932eb3b95466f..d9675964500ddf69c8702b89981541fbdde582ce 100644
--- a/src/audio/compat.ts
+++ b/src/audio/compat.ts
@@ -16,12 +16,11 @@ import { SAMPLE_RATE } from "./VoiceRecording";
 
 export function createAudioContext(opts?: AudioContextOptions): AudioContext {
     if (window.AudioContext) {
-        return new AudioContext(opts);
-    } else if (window.webkitAudioContext) {
-        // While the linter is correct that "a constructor name should not start with
-        // a lowercase letter", it's also wrong to think that we have control over this.
-        // eslint-disable-next-line new-cap
-        return new window.webkitAudioContext(opts);
+        const ctx = new AudioContext(opts);
+        // Initialize in suspended state, as Firefox starts using
+        // CPU/battery right away, even if we don't play any sound yet.
+        void ctx.suspend();
+        return ctx;
     } else {
         throw new Error("Unsupported browser");
     }
diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx
index 0161a64dce4ab924669e4a321f9bcf8ce2b61937..94614f0ae7235d8d24e81d66dc085f133a654610 100644
--- a/src/autocomplete/AutocompleteProvider.tsx
+++ b/src/autocomplete/AutocompleteProvider.tsx
@@ -7,8 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-
+import type React from "react";
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import type { ICompletion, ISelectionRange } from "./Autocompleter";
 
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index 7ac11e95390cc01a0c04e3f8c9b6c417cf2acea4..2653de1034de680145af17aeae469c617ab2d303 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactElement } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type ReactElement, type RefAttributes, type HTMLAttributes } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import CommandProvider from "./CommandProvider";
 import RoomProvider from "./RoomProvider";
@@ -15,7 +15,8 @@ import UserProvider from "./UserProvider";
 import EmojiProvider from "./EmojiProvider";
 import NotifProvider from "./NotifProvider";
 import { timeout } from "../utils/promise";
-import AutocompleteProvider, { ICommand } from "./AutocompleteProvider";
+import { type ICommand } from "./AutocompleteProvider";
+import type AutocompleteProvider from "./AutocompleteProvider";
 import SpaceProvider from "./SpaceProvider";
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import { filterBoolean } from "../utils/arrays";
@@ -30,7 +31,7 @@ export interface ICompletion {
     type?: "at-room" | "command" | "community" | "room" | "user";
     completion: string;
     completionId?: string;
-    component: ReactElement;
+    component: ReactElement<RefAttributes<HTMLElement> & HTMLAttributes<HTMLElement>>;
     range: ISelectionRange;
     command?: string;
     suffix?: string;
diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx
index d2b9f18f152f5d164d8a4d3525fd3123c104a0e7..76d53bb86552a584f8f9bf97fcfe172fe4f62093 100644
--- a/src/autocomplete/CommandProvider.tsx
+++ b/src/autocomplete/CommandProvider.tsx
@@ -10,15 +10,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../languageHandler";
 import AutocompleteProvider from "./AutocompleteProvider";
 import QueryMatcher from "./QueryMatcher";
 import { TextualCompletion } from "./Components";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
-import { Command, Commands, CommandMap } from "../SlashCommands";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type ICompletion, type ISelectionRange } from "./Autocompleter";
+import { type Command, Commands, CommandMap } from "../SlashCommands";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 
 const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx
index ebbc1732e2f2acd5b0905ffb7c8531239fa6152e..f1493d0c238181def6ea7fd54eaab613d0229a94 100644
--- a/src/autocomplete/Components.tsx
+++ b/src/autocomplete/Components.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef } from "react";
+import React, { type Ref, type JSX } from "react";
 import classNames from "classnames";
 
 /* These were earlier stateless functional components but had to be converted
@@ -16,14 +16,24 @@ presumably wrap them in a <div> before rendering but I think this is the better
  */
 
 interface ITextualCompletionProps {
-    title?: string;
-    subtitle?: string;
-    description?: string;
-    className?: string;
+    "title"?: string;
+    "subtitle"?: string;
+    "description"?: string;
+    "className"?: string;
+    "aria-selected"?: boolean;
+    "ref"?: Ref<HTMLDivElement>;
 }
 
-export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
-    const { title, subtitle, description, className, "aria-selected": ariaSelectedAttribute, ...restProps } = props;
+export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element => {
+    const {
+        title,
+        subtitle,
+        description,
+        className,
+        "aria-selected": ariaSelectedAttribute,
+        ref,
+        ...restProps
+    } = props;
     return (
         <div
             {...restProps}
@@ -37,13 +47,13 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
             <span className="mx_Autocomplete_Completion_description">{description}</span>
         </div>
     );
-});
+};
 
 interface IPillCompletionProps extends ITextualCompletionProps {
     children?: React.ReactNode;
 }
 
-export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref) => {
+export const PillCompletion = (props: IPillCompletionProps): JSX.Element => {
     const {
         title,
         subtitle,
@@ -51,6 +61,7 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
         className,
         children,
         "aria-selected": ariaSelectedAttribute,
+        ref,
         ...restProps
     } = props;
     return (
@@ -67,4 +78,4 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
             <span className="mx_Autocomplete_Completion_description">{description}</span>
         </div>
     );
-});
+};
diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx
index f976fd37db2ef05a21149d0925258f78235de28c..2d31095acd7a03b1b6ba2084f062d24204fe1201 100644
--- a/src/autocomplete/EmojiProvider.tsx
+++ b/src/autocomplete/EmojiProvider.tsx
@@ -11,18 +11,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { uniq, sortBy, uniqBy, ListIteratee } from "lodash";
+import { uniq, sortBy, uniqBy, type ListIteratee } from "lodash";
 import EMOTICON_REGEX from "emojibase-regex/emoticon";
-import { Room } from "matrix-js-sdk/src/matrix";
-import { EMOJI, Emoji, getEmojiFromUnicode } from "@matrix-org/emojibase-bindings";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import { EMOJI, type Emoji, getEmojiFromUnicode } from "@matrix-org/emojibase-bindings";
 
 import { _t } from "../languageHandler";
 import AutocompleteProvider from "./AutocompleteProvider";
 import QueryMatcher from "./QueryMatcher";
 import { PillCompletion } from "./Components";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
+import { type ICompletion, type ISelectionRange } from "./Autocompleter";
 import SettingsStore from "../settings/SettingsStore";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 import * as recent from "../emojipicker/recent";
 import { filterBoolean } from "../utils/arrays";
 
diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx
index 49190455789db6c5431b7e864325d1b6edafa675..2a6e070efa4d1e3e64d07f8fe8612b6b8f686aae 100644
--- a/src/autocomplete/NotifProvider.tsx
+++ b/src/autocomplete/NotifProvider.tsx
@@ -6,15 +6,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import AutocompleteProvider from "./AutocompleteProvider";
 import { _t } from "../languageHandler";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import { PillCompletion } from "./Components";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
+import { type ICompletion, type ISelectionRange } from "./Autocompleter";
 import RoomAvatar from "../components/views/avatars/RoomAvatar";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 
 const AT_ROOM_REGEX = /@\S*/g;
 
diff --git a/src/autocomplete/QueryMatcher.ts b/src/autocomplete/QueryMatcher.ts
index 985ca3a516d39c08a480c2758821c9931a9129a1..0012beacfacc35a1e6590f57952c607c56cdd230 100644
--- a/src/autocomplete/QueryMatcher.ts
+++ b/src/autocomplete/QueryMatcher.ts
@@ -11,10 +11,10 @@ Please see LICENSE files in the repository root for full details.
 import { at, uniq } from "lodash";
 import { removeHiddenChars } from "matrix-js-sdk/src/utils";
 
-import { TimelineRenderingType } from "../contexts/RoomContext";
-import { Leaves } from "../@types/common";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
+import { type Leaves } from "../@types/common";
 
-interface IOptions<T extends {}> {
+interface IOptions<T extends object> {
     keys: Array<Leaves<T>>;
     funcs?: Array<(o: T) => string | string[]>;
     shouldMatchWordsOnly?: boolean;
@@ -37,7 +37,7 @@ interface IOptions<T extends {}> {
  * @param {function[]} options.funcs List of functions that when called with the
  *     object as an arg will return a string to use as an index
  */
-export default class QueryMatcher<T extends {}> {
+export default class QueryMatcher<T extends object> {
     private _options: IOptions<T>;
     private _items = new Map<string, { object: T; keyWeight: number }[]>();
 
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index 84a4a8a023454a0d4b11045ce9dddc4afff1a857..5c06856b08b145cf736a96a16391f5c20b15a9eb 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { sortBy, uniqBy } from "lodash";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../languageHandler";
 import AutocompleteProvider from "./AutocompleteProvider";
@@ -18,9 +18,9 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
 import QueryMatcher from "./QueryMatcher";
 import { PillCompletion } from "./Components";
 import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
+import { type ICompletion, type ISelectionRange } from "./Autocompleter";
 import RoomAvatar from "../components/views/avatars/RoomAvatar";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 import SettingsStore from "../settings/SettingsStore";
 
 const ROOM_REGEX = /\B#\S*/g;
diff --git a/src/autocomplete/SpaceProvider.tsx b/src/autocomplete/SpaceProvider.tsx
index 273d9ace0003e62fec65b588d7fc2aaf1f1399a3..b031f37c865ee9c4ebbc58d6c45eec2950683072 100644
--- a/src/autocomplete/SpaceProvider.tsx
+++ b/src/autocomplete/SpaceProvider.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
 import { _t } from "../languageHandler";
diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx
index 673bee7bf21a429475d43928a80defb6b829b1d3..39accdc8da886a01464ab59406a5d26c7373ffbe 100644
--- a/src/autocomplete/UserProvider.tsx
+++ b/src/autocomplete/UserProvider.tsx
@@ -12,13 +12,13 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { sortBy } from "lodash";
 import {
-    MatrixEvent,
-    Room,
+    type MatrixEvent,
+    type Room,
     RoomEvent,
-    RoomMember,
-    RoomState,
+    type RoomMember,
+    type RoomState,
     RoomStateEvent,
-    IRoomTimelineData,
+    type IRoomTimelineData,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
@@ -28,9 +28,9 @@ import { PillCompletion } from "./Components";
 import AutocompleteProvider from "./AutocompleteProvider";
 import { _t } from "../languageHandler";
 import { makeUserPermalink } from "../utils/permalinks/Permalinks";
-import { ICompletion, ISelectionRange } from "./Autocompleter";
+import { type ICompletion, type ISelectionRange } from "./Autocompleter";
 import MemberAvatar from "../components/views/avatars/MemberAvatar";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 import UserIdentifierCustomisations from "../customisations/UserIdentifier";
 
 const USER_REGEX = /\B@\S*/g;
@@ -127,7 +127,7 @@ export default class UserProvider extends AutocompleteProvider {
                     suffix: selection.beginning && range!.start === 0 ? ": " : " ",
                     href: makeUserPermalink(user.userId),
                     component: (
-                        <PillCompletion title={displayName} description={description}>
+                        <PillCompletion title={displayName} description={description ?? undefined}>
                             <MemberAvatar member={user} size="24px" />
                         </PillCompletion>
                     ),
diff --git a/src/boundThreepids.ts b/src/boundThreepids.ts
index b7c749a0207baf3cde47a3fe529a1109e7b4e5a2..2d8de4817e00664394be4da01f16411c3bd6c792 100644
--- a/src/boundThreepids.ts
+++ b/src/boundThreepids.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IThreepid, ThreepidMedium, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type IThreepid, type ThreepidMedium, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 
 import IdentityAuthClient from "./IdentityAuthClient";
 
diff --git a/src/call-types.ts b/src/call-types.ts
index 2a263174efbf1872d3607d2cf2b707df03b83b86..6586bcf3b9129b366911870d7008de7922a72482 100644
--- a/src/call-types.ts
+++ b/src/call-types.ts
@@ -6,10 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-// Event type for room account data and room creation content used to mark rooms as virtual rooms
-// (and store the ID of their native room)
-export const VIRTUAL_ROOM_EVENT_TYPE = "im.vector.is_virtual_room";
-
 export const JitsiCallMemberEventType = "io.element.video.member";
 
 export interface JitsiCallMemberContent {
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 764a712d442b47102b6ff31efe55b4408f8adb13..9cd68b5c587c4dd5568c58e98bd46663059d3545 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { HTMLAttributes, ReactHTML, ReactNode, WheelEvent } from "react";
+import React, { type HTMLAttributes, type JSX, type ReactNode, type WheelEvent } from "react";
 
 type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
-    JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
+    JSX.IntrinsicElements[T] extends HTMLAttributes<object> ? DynamicElementProps<T> : DynamicElementProps<"div">;
 type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<Omit<JSX.IntrinsicElements[T], "ref">>;
 
 export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElementProps<T>, "onScroll"> & {
@@ -27,10 +27,10 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElem
 
 export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> extends React.Component<IProps<T>> {
     public static defaultProps = {
-        element: "div" as keyof ReactHTML,
+        element: "div" as keyof HTMLElementTagNameMap,
     };
 
-    public readonly containerRef: React.RefObject<HTMLDivElement> = React.createRef();
+    public readonly containerRef = React.createRef<HTMLDivElement>();
 
     public componentDidMount(): void {
         if (this.containerRef.current && this.props.onScroll) {
diff --git a/src/components/structures/AutocompleteInput.tsx b/src/components/structures/AutocompleteInput.tsx
index 25e0d7d1a188c6dafb0eceb18dd2a21fb0df7a2a..9767bd576194484012153fdbd5781c411c20632e 100644
--- a/src/components/structures/AutocompleteInput.tsx
+++ b/src/components/structures/AutocompleteInput.tsx
@@ -6,13 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState, ReactNode, ChangeEvent, KeyboardEvent, useRef, ReactElement } from "react";
+import React, {
+    useState,
+    type ReactNode,
+    type ChangeEvent,
+    type KeyboardEvent,
+    useRef,
+    type ReactElement,
+} from "react";
 import classNames from "classnames";
 import { SearchIcon, CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import Autocompleter from "../../autocomplete/AutocompleteProvider";
+import type Autocompleter from "../../autocomplete/AutocompleteProvider";
 import { Key } from "../../Keyboard";
-import { ICompletion } from "../../autocomplete/Autocompleter";
+import { type ICompletion } from "../../autocomplete/Autocompleter";
 import AccessibleButton from "../../components/views/elements/AccessibleButton";
 import useFocus from "../../hooks/useFocus";
 
diff --git a/src/components/structures/BackdropPanel.tsx b/src/components/structures/BackdropPanel.tsx
index 64b2ab0fce8fc0d248f4b9d4d6ed8081b72eed38..f3a44521fa116214a0f2f9aea665911c2a008dbb 100644
--- a/src/components/structures/BackdropPanel.tsx
+++ b/src/components/structures/BackdropPanel.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { CSSProperties } from "react";
+import React, { type CSSProperties } from "react";
 
 interface IProps {
     backgroundImage?: string;
diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx
index 51aef8f454aaa5c5bec426c1f7e338ba3ff47b85..bdbd8b4a08101890ad70381068271075e8a4c4b1 100644
--- a/src/components/structures/ContextMenu.tsx
+++ b/src/components/structures/ContextMenu.tsx
@@ -8,12 +8,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { CSSProperties, RefObject, SyntheticEvent, useRef, useState } from "react";
+import React, { type JSX, type CSSProperties, type RefObject, type SyntheticEvent, useRef, useState } from "react";
 import ReactDOM from "react-dom";
 import classNames from "classnames";
 import FocusLock from "react-focus-lock";
 
-import { Writeable } from "../../@types/common";
+import { type Writeable } from "../../@types/common";
 import UIStore from "../../stores/UIStore";
 import { checkInputableElement, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
@@ -440,7 +440,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
         );
     }
 
-    public render(): React.ReactChild {
+    public render(): JSX.Element {
         if (this.props.mountAsChild) {
             // Render as a child of the current parent
             return this.renderMenu();
@@ -582,13 +582,15 @@ export const alwaysAboveRightOf = (
 
 type ContextMenuTuple<T> = [
     boolean,
-    RefObject<T>,
+    RefObject<T | null>,
     (ev?: SyntheticEvent) => void,
     (ev?: SyntheticEvent) => void,
     (val: boolean) => void,
 ];
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
-export const useContextMenu = <T extends any = HTMLElement>(inputRef?: RefObject<T>): ContextMenuTuple<T> => {
+export const useContextMenu = <T extends HTMLElement = HTMLElement>(
+    inputRef?: RefObject<T | null>,
+): ContextMenuTuple<T> => {
     let button = useRef<T>(null);
     if (inputRef) {
         // if we are given a ref, use it instead of ours
diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx
index 2833f79626544ec668030e078b400d4880e8746e..f1c7b75b961db579c131b02798945e3e250eb493 100644
--- a/src/components/structures/EmbeddedPage.tsx
+++ b/src/components/structures/EmbeddedPage.tsx
@@ -12,12 +12,12 @@ import sanitizeHtml from "sanitize-html";
 import classnames from "classnames";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { _t, TranslationKey } from "../../languageHandler";
+import { _t, type TranslationKey } from "../../languageHandler";
 import dis from "../../dispatcher/dispatcher";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
 import AutoHideScrollbar from "./AutoHideScrollbar";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 
 interface IProps {
     // URL to request embedded page content from
@@ -40,8 +40,8 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
     private unmounted = false;
     private dispatcherRef?: string;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             page: "",
diff --git a/src/components/structures/ErrorMessage.tsx b/src/components/structures/ErrorMessage.tsx
index ce4788c0fa2c44ed56136dc8c5571ce24ebe3c1f..c721e67fc5d07745c7a6f531e9d4d0d6dafd1bda 100644
--- a/src/components/structures/ErrorMessage.tsx
+++ b/src/components/structures/ErrorMessage.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 interface ErrorMessageProps {
diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx
index c1eb34597f8be9080eb61b082ed20e49d8c8905b..5930490c3498caa9894e6fe3c539c85069db0390 100644
--- a/src/components/structures/FilePanel.tsx
+++ b/src/components/structures/FilePanel.tsx
@@ -10,14 +10,14 @@ Please see LICENSE files in the repository root for full details.
 import React, { createRef } from "react";
 import {
     Filter,
-    EventTimelineSet,
-    IRoomTimelineData,
-    Direction,
-    MatrixEvent,
+    type EventTimelineSet,
+    type IRoomTimelineData,
+    type Direction,
+    type MatrixEvent,
     MatrixEventEvent,
-    Room,
+    type Room,
     RoomEvent,
-    TimelineWindow,
+    type TimelineWindow,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files";
@@ -27,7 +27,7 @@ import EventIndexPeg from "../../indexing/EventIndexPeg";
 import { _t } from "../../languageHandler";
 import SearchWarning, { WarningKind } from "../views/elements/SearchWarning";
 import BaseCard from "../views/right_panel/BaseCard";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import TimelinePanel from "./TimelinePanel";
 import Spinner from "../views/elements/Spinner";
 import { Layout } from "../../settings/enums/Layout";
diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx
index 5c617e8015aae3d6c1a616a2a898fe55887d6e75..78cd3d3ad4655d0b78a9b8c980b6374aec26f7f2 100644
--- a/src/components/structures/GenericDropdownMenu.tsx
+++ b/src/components/structures/GenericDropdownMenu.tsx
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { FunctionComponent, Key, PropsWithChildren, ReactNode } from "react";
+import React, { type JSX, type FunctionComponent, type Key, type PropsWithChildren, type ReactNode } from "react";
 
 import { MenuItemRadio } from "../../accessibility/context_menu/MenuItemRadio";
-import { ButtonEvent } from "../views/elements/AccessibleButton";
+import { type ButtonEvent } from "../views/elements/AccessibleButton";
 import ContextMenu, { aboveLeftOf, ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
 
 export type GenericDropdownMenuOption<T> = {
diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx
index f8dcc7b70d8c3a7d0a11ba1b59f4763a9aae471d..5cbd2858c59897460db562adc29b6cddd5e98bb3 100644
--- a/src/components/structures/HomePage.tsx
+++ b/src/components/structures/HomePage.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React, { type JSX } from "react";
 import { useContext, useState } from "react";
 
 import AutoHideScrollbar from "./AutoHideScrollbar";
@@ -17,7 +17,7 @@ import dis from "../../dispatcher/dispatcher";
 import { Action } from "../../dispatcher/actions";
 import BaseAvatar from "../views/avatars/BaseAvatar";
 import { OwnProfileStore } from "../../stores/OwnProfileStore";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
 import { useEventEmitter } from "../../hooks/useEventEmitter";
 import MatrixClientContext, { useMatrixClientContext } from "../../contexts/MatrixClientContext";
@@ -27,7 +27,7 @@ import EmbeddedPage from "./EmbeddedPage";
 
 const onClickSendDm = (ev: ButtonEvent): void => {
     PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev);
-    dis.dispatch({ action: "view_create_chat" });
+    dis.dispatch({ action: Action.CreateChat });
 };
 
 const onClickExplore = (ev: ButtonEvent): void => {
@@ -37,7 +37,7 @@ const onClickExplore = (ev: ButtonEvent): void => {
 
 const onClickNewRoom = (ev: ButtonEvent): void => {
     PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
-    dis.dispatch({ action: "view_create_room" });
+    dis.dispatch({ action: Action.CreateRoom });
 };
 
 interface IProps {
diff --git a/src/components/structures/IndicatorScrollbar.tsx b/src/components/structures/IndicatorScrollbar.tsx
index 1d0a90a14e1024f78caa0dabc98a92d487bacbbc..f566e2d2566379ed30d2a954fc72f85b5e0a71c0 100644
--- a/src/components/structures/IndicatorScrollbar.tsx
+++ b/src/components/structures/IndicatorScrollbar.tsx
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
+import React, { createRef, type JSX } from "react";
 
-import AutoHideScrollbar, { IProps as AutoHideScrollbarProps } from "./AutoHideScrollbar";
+import AutoHideScrollbar, { type IProps as AutoHideScrollbarProps } from "./AutoHideScrollbar";
 import UIStore, { UI_EVENTS } from "../../stores/UIStore";
 
 export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollbarProps<T>, "onWheel" | "element"> & {
@@ -21,8 +21,6 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollb
     // scroll horizontally rather than vertically. This should only be used on components
     // with no vertical scroll opportunity.
     verticalScrollsHorizontally?: boolean;
-
-    children: React.ReactNode;
 };
 
 interface IState {
diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx
index 08aa4eeafa86381b7085b8373f3ac6c92c89800d..d1c670c27cf1b5e94cd4beb141f335ee43363abb 100644
--- a/src/components/structures/InteractiveAuth.tsx
+++ b/src/components/structures/InteractiveAuth.tsx
@@ -9,31 +9,28 @@ Please see LICENSE files in the repository root for full details.
 import React, { createRef } from "react";
 import {
     AuthType,
-    IAuthData,
-    AuthDict,
-    IInputs,
+    type IAuthData,
+    type AuthDict,
+    type IInputs,
     InteractiveAuth,
-    IStageStatus,
+    type IStageStatus,
 } from "matrix-js-sdk/src/interactive-auth";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import getEntryComponentForLoginType, {
-    ContinueKind,
-    CustomAuthType,
-    IStageComponent,
+    type ContinueKind,
+    type CustomAuthType,
+    type IStageComponent,
 } from "../views/auth/InteractiveAuthEntryComponents";
 import Spinner from "../views/elements/Spinner";
 
 export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
 
-type InteractiveAuthCallbackSuccess<T> = (
-    success: true,
-    response: T,
-    extra?: { emailSid?: string; clientSecret?: string },
-) => Promise<void>;
-type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => Promise<void>;
-export type InteractiveAuthCallback<T> = InteractiveAuthCallbackSuccess<T> & InteractiveAuthCallbackFailure;
+export type InteractiveAuthCallback<T> = {
+    (success: true, response: T, extra?: { emailSid?: string; clientSecret?: string }): Promise<void>;
+    (success: false, response: IAuthData | Error): Promise<void>;
+};
 
 export interface InteractiveAuthProps<T> {
     // matrix client to use for UI auth requests
@@ -49,10 +46,6 @@ export interface InteractiveAuthProps<T> {
     emailSid?: string;
     // If true, poll to see if the auth flow has been completed out-of-band
     poll?: boolean;
-    // If true, components will be told that the 'Continue' button
-    // is managed by some other party and should not be managed by
-    // the component itself.
-    continueIsManaged?: boolean;
     // continueText and continueKind are passed straight through to the AuthEntryComponent.
     continueText?: string;
     continueKind?: ContinueKind;
@@ -288,7 +281,6 @@ export default class InteractiveAuthComponent<T> extends React.Component<Interac
                 stageState={this.state.stageState}
                 fail={this.onAuthStageFailed}
                 setEmailSid={this.setEmailSid}
-                showContinue={!this.props.continueIsManaged}
                 onPhaseChange={this.onPhaseChange}
                 requestEmailToken={this.authLogic.requestEmailToken}
                 continueText={this.props.continueText}
diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx
index de591a6b22790c1723af58434a45cfe5368eb7ba..3bd2518c8ad8ccee2b25d3ee43061220eb4a8888 100644
--- a/src/components/structures/LeftPanel.tsx
+++ b/src/components/structures/LeftPanel.tsx
@@ -6,24 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React, { type JSX } from "react";
 import { createRef } from "react";
 import classNames from "classnames";
 
 import dis from "../../dispatcher/dispatcher";
 import { _t } from "../../languageHandler";
-import RoomList from "../views/rooms/RoomList";
+import LegacyRoomList from "../views/rooms/LegacyRoomList";
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
 import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
 import { Action } from "../../dispatcher/actions";
 import RoomSearch from "./RoomSearch";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import SpaceStore from "../../stores/spaces/SpaceStore";
-import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces";
+import { MetaSpace, type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 import UIStore from "../../stores/UIStore";
-import { IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
-import RoomListHeader from "../views/rooms/RoomListHeader";
+import { type IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
+import LegacyRoomListHeader from "../views/rooms/LegacyRoomListHeader";
 import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
 import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
@@ -32,10 +32,12 @@ import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
 import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../settings/UIFeature";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import PosthogTrackers from "../../PosthogTrackers";
-import PageType from "../../PageTypes";
+import type PageType from "../../PageTypes";
 import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
+import SettingsStore from "../../settings/SettingsStore";
+import { RoomListPanel } from "../views/rooms/RoomListPanel";
 
 interface IProps {
     isMinimized: boolean;
@@ -56,7 +58,7 @@ interface IState {
 
 export default class LeftPanel extends React.Component<IProps, IState> {
     private listContainerRef = createRef<HTMLDivElement>();
-    private roomListRef = createRef<RoomList>();
+    private roomListRef = createRef<LegacyRoomList>();
     private focusedElement: Element | null = null;
     private isDoingStickyHeaders = false;
 
@@ -377,8 +379,26 @@ export default class LeftPanel extends React.Component<IProps, IState> {
     }
 
     public render(): React.ReactNode {
+        const useNewRoomList = SettingsStore.getValue("feature_new_room_list");
+        const containerClasses = classNames({
+            mx_LeftPanel: true,
+            mx_LeftPanel_newRoomList: useNewRoomList,
+            mx_LeftPanel_minimized: this.props.isMinimized,
+        });
+
+        const roomListClasses = classNames("mx_LeftPanel_actualRoomListContainer", "mx_AutoHideScrollbar");
+        if (useNewRoomList) {
+            return (
+                <div className={containerClasses}>
+                    <div className="mx_LeftPanel_roomListContainer">
+                        <RoomListPanel activeSpace={this.state.activeSpace} />
+                    </div>
+                </div>
+            );
+        }
+
         const roomList = (
-            <RoomList
+            <LegacyRoomList
                 onKeyDown={this.onKeyDown}
                 resizeNotifier={this.props.resizeNotifier}
                 onFocus={this.onFocus}
@@ -391,19 +411,12 @@ export default class LeftPanel extends React.Component<IProps, IState> {
             />
         );
 
-        const containerClasses = classNames({
-            mx_LeftPanel: true,
-            mx_LeftPanel_minimized: this.props.isMinimized,
-        });
-
-        const roomListClasses = classNames("mx_LeftPanel_actualRoomListContainer", "mx_AutoHideScrollbar");
-
         return (
             <div className={containerClasses}>
                 <div className="mx_LeftPanel_roomListContainer">
                     {shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()}
                     {this.renderBreadcrumbs()}
-                    {!this.props.isMinimized && <RoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
+                    {!this.props.isMinimized && <LegacyRoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
                     <nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
                         <div
                             className={roomListClasses}
diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts
index 96471589213a85a20f15b1436a8218dd2e68a29c..76caea692e4099fffaec75e84d2d81353aff976b 100644
--- a/src/components/structures/LegacyCallEventGrouper.ts
+++ b/src/components/structures/LegacyCallEventGrouper.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { CallEvent, CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { CallEvent, CallState, CallType, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import { EventEmitter } from "events";
 
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index fa1cb17ebe79e91398b9e9c43bb3cc441847cf8e..26e127f21f9fb7e5431234dc7a58581ae2dec736 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -6,44 +6,44 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ClipboardEvent } from "react";
+import React, { type ClipboardEvent } from "react";
 import {
     ClientEvent,
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     RoomStateEvent,
-    MatrixError,
-    IUsageLimit,
-    SyncStateData,
+    type MatrixError,
+    type IUsageLimit,
+    type SyncStateData,
     SyncState,
     EventType,
 } from "matrix-js-sdk/src/matrix";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import classNames from "classnames";
 
 import { isOnlyCtrlOrCmdKeyEvent, Key } from "../../Keyboard";
 import PageTypes from "../../PageTypes";
 import MediaDeviceHandler from "../../MediaDeviceHandler";
 import dis from "../../dispatcher/dispatcher";
-import { IMatrixClientCreds } from "../../MatrixClientPeg";
+import { type IMatrixClientCreds } from "../../MatrixClientPeg";
 import SettingsStore from "../../settings/SettingsStore";
 import { SettingLevel } from "../../settings/SettingLevel";
 import ResizeHandle from "../views/elements/ResizeHandle";
 import { CollapseDistributor, Resizer } from "../../resizer";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import PlatformPeg from "../../PlatformPeg";
 import { DefaultTagID } from "../../stores/room-list/models";
 import { hideToast as hideServerLimitToast, showToast as showServerLimitToast } from "../../toasts/ServerLimitToast";
 import { Action } from "../../dispatcher/actions";
 import LeftPanel from "./LeftPanel";
-import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
+import { type ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
 import RoomListStore from "../../stores/room-list/RoomListStore";
 import NonUrgentToastContainer from "./NonUrgentToastContainer";
-import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
+import { type IOOBData, type IThreepidInvite } from "../../stores/ThreepidInviteStore";
 import Modal from "../../Modal";
-import { CollapseItem, ICollapseConfig } from "../../resizer/distributors/collapse";
+import { type CollapseItem, type ICollapseConfig } from "../../resizer/distributors/collapse";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
-import { IOpts } from "../../createRoom";
+import { type IOpts } from "../../createRoom";
 import SpacePanel from "../views/spaces/SpacePanel";
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
 import AudioFeedArrayForLegacyCall from "../views/voip/AudioFeedArrayForLegacyCall";
@@ -55,16 +55,16 @@ import UserView from "./UserView";
 import { BackdropPanel } from "./BackdropPanel";
 import { mediaFromMxc } from "../../customisations/Media";
 import { UserTab } from "../views/dialogs/UserTab";
-import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
+import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import { TimelineRenderingType } from "../../contexts/RoomContext";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
-import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
+import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
 import LeftPanelLiveShareWarning from "../views/beacon/LeftPanelLiveShareWarning";
 import HomePage from "./HomePage";
 import { PipContainer } from "./PipContainer";
 import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushRules";
-import { ConfigOptions } from "../../SdkConfig";
+import { type ConfigOptions } from "../../SdkConfig";
 import { MatrixClientContextProvider } from "./MatrixClientContextProvider";
 import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
 
@@ -124,9 +124,9 @@ class LoggedInView extends React.Component<IProps, IState> {
     public static displayName = "LoggedInView";
 
     protected readonly _matrixClient: MatrixClient;
-    protected readonly _roomView: React.RefObject<RoomView>;
-    protected readonly _resizeContainer: React.RefObject<HTMLDivElement>;
-    protected readonly resizeHandler: React.RefObject<HTMLDivElement>;
+    protected readonly _roomView: React.RefObject<RoomView | null>;
+    protected readonly _resizeContainer: React.RefObject<HTMLDivElement | null>;
+    protected readonly resizeHandler: React.RefObject<HTMLDivElement | null>;
     protected layoutWatcherRef?: string;
     protected compactLayoutWatcherRef?: string;
     protected backgroundImageWatcherRef?: string;
@@ -259,9 +259,11 @@ class LoggedInView extends React.Component<IProps, IState> {
     private createResizer(): Resizer<ICollapseConfig, CollapseItem> {
         let panelSize: number | null;
         let panelCollapsed: boolean;
+        const useNewRoomList = SettingsStore.getValue("feature_new_room_list");
+        // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
+        const toggleSize = useNewRoomList ? 224 : 206 - 50;
         const collapseConfig: ICollapseConfig = {
-            // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
-            toggleSize: 206 - 50,
+            toggleSize,
             onCollapsed: (collapsed) => {
                 panelCollapsed = collapsed;
                 if (collapsed) {
@@ -501,9 +503,7 @@ class LoggedInView extends React.Component<IProps, IState> {
                 handled = true;
                 break;
             case KeyBindingAction.FilterRooms:
-                dis.dispatch({
-                    action: "focus_room_filter",
-                });
+                dis.fire(Action.OpenSpotlight);
                 handled = true;
                 break;
             case KeyBindingAction.ToggleUserMenu:
@@ -699,10 +699,18 @@ class LoggedInView extends React.Component<IProps, IState> {
             "mx_MatrixChat--with-avatar": this.state.backgroundImage,
         });
 
+        const useNewRoomList = SettingsStore.getValue("feature_new_room_list");
+
+        const leftPanelWrapperClasses = classNames({
+            mx_LeftPanel_wrapper: true,
+            mx_LeftPanel_newRoomList: useNewRoomList,
+        });
+
         const audioFeedArraysForCalls = this.state.activeCalls.map((call) => {
             return <AudioFeedArrayForLegacyCall call={call} key={call.callId} />;
         });
 
+        const shouldUseMinimizedUI = !useNewRoomList && this.props.collapseLhs;
         return (
             <MatrixClientContextProvider client={this._matrixClient}>
                 <div
@@ -714,19 +722,21 @@ class LoggedInView extends React.Component<IProps, IState> {
                     <ToastContainer />
                     <div className={bodyClasses}>
                         <div className="mx_LeftPanel_outerWrapper">
-                            <LeftPanelLiveShareWarning isMinimized={this.props.collapseLhs || false} />
-                            <div className="mx_LeftPanel_wrapper">
-                                <BackdropPanel blurMultiplier={0.5} backgroundImage={this.state.backgroundImage} />
+                            <LeftPanelLiveShareWarning isMinimized={shouldUseMinimizedUI || false} />
+                            <div className={leftPanelWrapperClasses}>
+                                {!useNewRoomList && (
+                                    <BackdropPanel blurMultiplier={0.5} backgroundImage={this.state.backgroundImage} />
+                                )}
                                 <SpacePanel />
-                                <BackdropPanel backgroundImage={this.state.backgroundImage} />
+                                {!useNewRoomList && <BackdropPanel backgroundImage={this.state.backgroundImage} />}
                                 <div
                                     className="mx_LeftPanel_wrapper--user"
                                     ref={this._resizeContainer}
-                                    data-collapsed={this.props.collapseLhs ? true : undefined}
+                                    data-collapsed={shouldUseMinimizedUI ? true : undefined}
                                 >
                                     <LeftPanel
                                         pageType={this.props.page_type as PageTypes}
-                                        isMinimized={this.props.collapseLhs || false}
+                                        isMinimized={shouldUseMinimizedUI || false}
                                         resizeNotifier={this.props.resizeNotifier}
                                     />
                                 </div>
diff --git a/src/components/structures/MainSplit.tsx b/src/components/structures/MainSplit.tsx
index 540aec251f0dc93dc607b234f24b5c74e81fa778..6b6736d4ecf7bf96bf0386eabe6fff5a0916ef54 100644
--- a/src/components/structures/MainSplit.tsx
+++ b/src/components/structures/MainSplit.tsx
@@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { NumberSize, Resizable } from "re-resizable";
-import { Direction } from "re-resizable/lib/resizer";
-import { WebPanelResize } from "@matrix-org/analytics-events/types/typescript/WebPanelResize";
+import React, { type JSX, type ReactNode } from "react";
+import { type NumberSize, Resizable } from "re-resizable";
+import { type Direction } from "re-resizable/lib/resizer";
+import { type WebPanelResize } from "@matrix-org/analytics-events/types/typescript/WebPanelResize";
 
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import { PosthogAnalytics } from "../../PosthogAnalytics.ts";
 
 interface IProps {
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 9d3114c67cb49d42dd8ed69976d0e4c0acb1aaa2..e61713ca6966cf0edecf95fac084dccfc2b1c625 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -6,33 +6,34 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, lazy } from "react";
+import React, { type JSX, createRef, lazy } from "react";
 import {
     ClientEvent,
     createClient,
     EventType,
     HttpApiEvent,
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
-    RoomType,
+    MsgType,
+    type RoomType,
     SyncState,
-    SyncStateData,
-    TimelineEvents,
+    type SyncStateData,
+    type TimelineEvents,
 } from "matrix-js-sdk/src/matrix";
-import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
+import { type QueryDict } from "matrix-js-sdk/src/utils";
 import { logger } from "matrix-js-sdk/src/logger";
 import { throttle } from "lodash";
-import { CryptoEvent, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
 import { TooltipProvider } from "@vector-im/compound-web";
-
 // what-input helps improve keyboard accessibility
 import "what-input";
+import sanitizeHtml from "sanitize-html";
 
 import PosthogTrackers from "../../PosthogTrackers";
 import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
-import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
+import { type IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
 import PlatformPeg from "../../PlatformPeg";
-import SdkConfig, { ConfigOptions } from "../../SdkConfig";
+import SdkConfig, { type ConfigOptions } from "../../SdkConfig";
 import dis from "../../dispatcher/dispatcher";
 import Notifier from "../../Notifier";
 import Modal from "../../Modal";
@@ -43,14 +44,15 @@ import * as Lifecycle from "../../Lifecycle";
 import "../../stores/LifecycleStore";
 import "../../stores/AutoRageshakeStore";
 import PageType from "../../PageTypes";
-import createRoom, { IOpts } from "../../createRoom";
+import createRoom, { type IOpts } from "../../createRoom";
 import { _t, _td } from "../../languageHandler";
 import SettingsStore from "../../settings/SettingsStore";
 import ThemeController from "../../settings/controllers/ThemeController";
 import { startAnyRegistrationFlow } from "../../Registration";
 import ResizeNotifier from "../../utils/ResizeNotifier";
 import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
-import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
+import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
+import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
 import { FontWatcher } from "../../settings/watchers/FontWatcher";
 import { storeRoomAliasInCache } from "../../RoomAliasCache";
 import ToastStore from "../../stores/ToastStore";
@@ -60,14 +62,17 @@ import LoggedInView from "./LoggedInView";
 import { Action } from "../../dispatcher/actions";
 import { hideToast as hideAnalyticsToast, showToast as showAnalyticsToast } from "../../toasts/AnalyticsToast";
 import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
-import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
+import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
 import ErrorDialog from "../views/dialogs/ErrorDialog";
 import {
     RoomNotificationStateStore,
     UPDATE_STATUS_INDICATOR,
 } from "../../stores/notifications/RoomNotificationStateStore";
 import { SettingLevel } from "../../settings/SettingLevel";
-import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
+import ThreepidInviteStore, {
+    type IThreepidInvite,
+    type IThreepidInviteWireFormat,
+} from "../../stores/ThreepidInviteStore";
 import { UIFeature } from "../../settings/UIFeature";
 import DialPadModal from "../views/voip/DialPadModal";
 import { showToast as showMobileGuideToast } from "../../toasts/MobileGuideToast";
@@ -91,38 +96,38 @@ import VerificationRequestToast from "../views/toasts/VerificationRequestToast";
 import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
 import UIStore, { UI_EVENTS } from "../../stores/UIStore";
 import SoftLogout from "./auth/SoftLogout";
-import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
 import { copyPlaintext } from "../../utils/strings";
 import { PosthogAnalytics } from "../../PosthogAnalytics";
 import { initSentry } from "../../sentry";
 import LegacyCallHandler from "../../LegacyCallHandler";
 import { showSpaceInvite } from "../../utils/space";
-import { ButtonEvent } from "../views/elements/AccessibleButton";
-import { ActionPayload } from "../../dispatcher/payloads";
-import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
+import { type ButtonEvent } from "../views/elements/AccessibleButton";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import { type SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
 import Views from "../../Views";
-import { FocusNextType, ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
-import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
-import { DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
-import { ViewStartChatOrReusePayload } from "../../dispatcher/payloads/ViewStartChatOrReusePayload";
+import { type FocusNextType, type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
+import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
+import { type AfterForgetRoomPayload } from "../../dispatcher/payloads/AfterForgetRoomPayload";
+import { type DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
+import { type ViewStartChatOrReusePayload } from "../../dispatcher/payloads/ViewStartChatOrReusePayload";
 import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
 import { CallStore } from "../../stores/CallStore";
-import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
-import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
+import { type IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
+import { type ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import { TimelineRenderingType } from "../../contexts/RoomContext";
-import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
 import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
 import { SDKContext, SdkContextClass } from "../../contexts/SDKContext";
 import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings";
 import GenericToast from "../views/toasts/GenericToast";
 import RovingSpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog";
 import { findDMForUser } from "../../utils/dm/findDMForUser";
-import { Linkify } from "../../HtmlUtils";
+import { getHtmlText, Linkify } from "../../HtmlUtils";
 import { NotificationLevel } from "../../stores/notifications/NotificationLevel";
-import { UserTab } from "../views/dialogs/UserTab";
+import { type UserTab } from "../views/dialogs/UserTab";
 import { shouldSkipSetupEncryption } from "../../utils/crypto/shouldSkipSetupEncryption";
 import { Filter } from "../views/dialogs/spotlight/Filter";
 import { checkSessionLockFree, getSessionLock } from "../../utils/SessionLock";
@@ -131,6 +136,11 @@ import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"
 import { LoginSplashView } from "./auth/LoginSplashView";
 import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
 import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
+import { setTheme } from "../../theme";
+import { type OpenForwardDialogPayload } from "../../dispatcher/payloads/OpenForwardDialogPayload";
+import { ShareFormat, type SharePayload } from "../../dispatcher/payloads/SharePayload";
+import Markdown from "../../Markdown";
+import { sanitizeHtmlParams } from "../../Linkify";
 
 // legacy export
 export { default as Views } from "../../Views";
@@ -140,7 +150,7 @@ const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password",
 // Actions that are redirected through the onboarding process prior to being
 // re-dispatched. NOTE: some actions are non-trivial and would require
 // re-factoring to be included in this list in future.
-const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, "view_create_chat", "view_create_room"];
+const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, Action.CreateChat, Action.CreateRoom];
 
 interface IScreen {
     screen: string;
@@ -161,12 +171,6 @@ interface IProps {
     initialScreenAfterLogin?: IScreen;
     // displayname, if any, to set on the device when logging in/registering.
     defaultDeviceDisplayName?: string;
-
-    // Used by tests, this function is called when session initialisation starts
-    // with a promise that resolves or rejects once the initialiation process
-    // has finished, so that tests can wait for this to avoid them executing over
-    // each other.
-    initPromiseCallback?: (p: Promise<void>) => void;
 }
 
 interface IState {
@@ -217,7 +221,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
     };
 
     private firstSyncComplete = false;
-    private firstSyncPromise: IDeferred<void>;
+    private firstSyncPromise: PromiseWithResolvers<void>;
 
     private screenAfterLogin?: IScreen;
     private tokenLogin?: boolean;
@@ -231,6 +235,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
     private themeWatcher?: ThemeWatcher;
     private fontWatcher?: FontWatcher;
     private readonly stores: SdkContextClass;
+    private loadSessionAbortController = new AbortController();
 
     public constructor(props: IProps) {
         super(props);
@@ -255,7 +260,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
 
         // Used by _viewRoom before getting state from sync
         this.firstSyncComplete = false;
-        this.firstSyncPromise = defer();
+        this.firstSyncPromise = Promise.withResolvers();
 
         if (this.props.config.sync_timeline_limit) {
             MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@@ -286,9 +291,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
      */
     private startInitSession = (): void => {
         const initProm = this.initSession();
-        if (this.props.initPromiseCallback) {
-            this.props.initPromiseCallback(initProm);
-        }
 
         initProm.catch((err) => {
             // TODO: show an error screen, rather than a spinner of doom
@@ -323,7 +325,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             // When the session loads it'll be detected as soft logged out and a dispatch
             // will be sent out to say that, triggering this MatrixChat to show the soft
             // logout page.
-            Lifecycle.loadSession();
+            Lifecycle.loadSession({ abortSignal: this.loadSessionAbortController.signal });
             return;
         }
 
@@ -463,6 +465,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         this.themeWatcher = new ThemeWatcher();
         this.fontWatcher = new FontWatcher();
         this.themeWatcher.start();
+        this.themeWatcher.on(ThemeWatcherEvent.Change, setTheme);
         this.fontWatcher.start();
 
         initSentry(SdkConfig.get("sentry"));
@@ -495,6 +498,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
     public componentWillUnmount(): void {
         Lifecycle.stopMatrixClient();
         dis.unregister(this.dispatcherRef);
+        this.themeWatcher?.off(ThemeWatcherEvent.Change, setTheme);
         this.themeWatcher?.stop();
         this.fontWatcher?.stop();
         UIStore.destroy();
@@ -546,6 +550,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
                     guestHsUrl: this.getServerProperties().serverConfig.hsUrl,
                     guestIsUrl: this.getServerProperties().serverConfig.isUrl,
                     defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
+                    abortSignal: this.loadSessionAbortController.signal,
                 });
             })
             .then((loadedSession) => {
@@ -610,7 +615,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         }
 
         // Start the onboarding process for certain actions
-        if (MatrixClientPeg.get()?.isGuest() && ONBOARDING_FLOW_STARTERS.includes(payload.action)) {
+        if (
+            MatrixClientPeg.get()?.isGuest() &&
+            ONBOARDING_FLOW_STARTERS.includes(payload.action as unknown as Action)
+        ) {
             // This will cause `payload` to be dispatched later, once a
             // sync has reached the "prepared" state. Setting a matrix ID
             // will cause a full login and sync and finally the deferred
@@ -700,36 +708,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             case "copy_room":
                 this.copyRoom(payload.room_id);
                 break;
-            case "reject_invite":
-                Modal.createDialog(QuestionDialog, {
-                    title: _t("reject_invitation_dialog|title"),
-                    description: _t("reject_invitation_dialog|confirmation"),
-                    onFinished: (confirm) => {
-                        if (confirm) {
-                            // FIXME: controller shouldn't be loading a view :(
-                            const modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
-
-                            MatrixClientPeg.safeGet()
-                                .leave(payload.room_id)
-                                .then(
-                                    () => {
-                                        modal.close();
-                                        if (this.state.currentRoomId === payload.room_id) {
-                                            dis.dispatch({ action: Action.ViewHomePage });
-                                        }
-                                    },
-                                    (err) => {
-                                        modal.close();
-                                        Modal.createDialog(ErrorDialog, {
-                                            title: _t("reject_invitation_dialog|failed"),
-                                            description: err.toString(),
-                                        });
-                                    },
-                                );
-                        }
-                    },
-                });
-                break;
             case "view_user_info":
                 this.viewUser(payload.userId, payload.subAction);
                 break;
@@ -779,7 +757,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
                 this.viewSomethingBehindModal();
                 break;
             }
-            case "view_create_room":
+            case Action.CreateRoom:
                 this.createRoom(payload.public, payload.defaultName, payload.type);
 
                 // View the welcome or home page if we need something to look at
@@ -807,10 +785,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             case Action.ViewHomePage:
                 this.viewHome(payload.justRegistered);
                 break;
+            case Action.Share:
+                this.viewShare(payload.format, payload.msg);
+                break;
             case Action.ViewStartChatOrReuse:
                 this.chatCreateOrReuse(payload.user_id);
                 break;
-            case "view_create_chat":
+            case Action.CreateChat:
                 showStartChatInviteDialog(payload.initialText || "");
 
                 // View the welcome or home page if we need something to look at
@@ -1021,10 +1002,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         // Wait for the first sync to complete so that if a room does have an alias,
         // it would have been retrieved.
         if (!this.firstSyncComplete) {
-            if (!this.firstSyncPromise) {
-                logger.warn("Cannot view a room before first sync. room_id:", roomInfo.room_id);
-                return;
-            }
             await this.firstSyncPromise.promise;
         }
 
@@ -1135,8 +1112,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
     private viewUser(userId: string, subAction: string): void {
         // Wait for the first sync so that `getRoom` gives us a room object if it's
         // in the sync response
-        const waitForSync = this.firstSyncPromise ? this.firstSyncPromise.promise : Promise.resolve();
-        waitForSync.then(() => {
+        this.firstSyncPromise.promise.then(() => {
             if (subAction === "chat") {
                 this.chatCreateOrReuse(userId);
                 return;
@@ -1147,6 +1123,58 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         });
     }
 
+    private viewShare(format: ShareFormat, msg: string): void {
+        // Wait for the first sync so we can present possible rooms to share into
+        this.firstSyncPromise.promise.then(() => {
+            this.notifyNewScreen("share");
+            let rawEvent;
+            switch (format) {
+                case ShareFormat.Html: {
+                    rawEvent = {
+                        type: "m.room.message",
+                        content: {
+                            msgtype: MsgType.Text,
+                            body: getHtmlText(msg),
+                            format: "org.matrix.custom.html",
+                            formatted_body: sanitizeHtml(msg, sanitizeHtmlParams),
+                        },
+                        origin_server_ts: Date.now(),
+                    };
+                    break;
+                }
+                case ShareFormat.Markdown: {
+                    const html = new Markdown(msg).toHTML({ externalLinks: true });
+                    rawEvent = {
+                        type: "m.room.message",
+                        content: {
+                            msgtype: MsgType.Text,
+                            body: msg,
+                            format: "org.matrix.custom.html",
+                            formatted_body: html,
+                        },
+                        origin_server_ts: Date.now(),
+                    };
+                    break;
+                }
+                default:
+                    rawEvent = {
+                        type: "m.room.message",
+                        content: {
+                            msgtype: MsgType.Text,
+                            body: msg,
+                        },
+                        origin_server_ts: Date.now(),
+                    };
+            }
+            const event = new MatrixEvent(rawEvent);
+            dis.dispatch<OpenForwardDialogPayload>({
+                action: Action.OpenForwardDialog,
+                event: event,
+                permalinkCreator: null,
+            });
+        });
+    }
+
     private async createRoom(defaultPublic = false, defaultName?: string, type?: RoomType): Promise<void> {
         const modal = Modal.createDialog(CreateRoomDialog, {
             type,
@@ -1262,7 +1290,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         const warnings = this.leaveRoomWarnings(roomId);
 
         const isSpace = roomToLeave?.isSpaceRoom();
-        Modal.createDialog(QuestionDialog, {
+        const { finished } = Modal.createDialog(QuestionDialog, {
             title: isSpace ? _t("space|leave_dialog_action") : _t("action|leave_room"),
             description: (
                 <span>
@@ -1278,16 +1306,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             ),
             button: _t("action|leave"),
             danger: warnings.length > 0,
-            onFinished: async (shouldLeave) => {
-                if (shouldLeave) {
-                    await leaveRoomBehaviour(cli, roomId);
+        });
 
-                    dis.dispatch<AfterLeaveRoomPayload>({
-                        action: Action.AfterLeaveRoom,
-                        room_id: roomId,
-                    });
-                }
-            },
+        finished.then(async ([shouldLeave]) => {
+            if (shouldLeave) {
+                await leaveRoomBehaviour(cli, roomId);
+
+                dis.dispatch<AfterLeaveRoomPayload>({
+                    action: Action.AfterLeaveRoom,
+                    room_id: roomId,
+                });
+            }
         });
     }
 
@@ -1301,10 +1330,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
                     dis.dispatch({ action: Action.ViewHomePage });
                 }
 
-                // We have to manually update the room list because the forgotten room will not
-                // be notified to us, therefore the room list will have no other way of knowing
-                // the room is forgotten.
-                if (room) RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
+                if (room) {
+                    // Legacy room list store needs to be told to manually remove this room
+                    RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
+                    // New room list store will remove the room on the following dispatch
+                    dis.dispatch<AfterForgetRoomPayload>({ action: Action.AfterForgetRoom, room });
+                }
             })
             .catch((err) => {
                 const errCode = err.errcode || _td("error|unknown_error_code");
@@ -1379,7 +1410,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
                 // so show the homepage.
                 dis.dispatch<ViewHomePagePayload>({ action: Action.ViewHomePage, justRegistered: true });
             }
-        } else {
+        } else if (!(await this.shouldForceVerification())) {
             this.showScreenAfterLogin();
         }
 
@@ -1499,11 +1530,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
      * (useful for setting listeners)
      */
     private onWillStartClient(): void {
-        // reset the 'have completed first sync' flag,
-        // since we're about to start the client and therefore about
-        // to do the first sync
+        // Reset the 'have completed first sync' flag,
+        // since we're about to start the client and therefore about to do the first sync
+        // We resolve the existing promise with the new one to update any existing listeners
+        if (!this.firstSyncComplete) {
+            const firstSyncPromise = Promise.withResolvers<void>();
+            this.firstSyncPromise.resolve(firstSyncPromise.promise);
+            this.firstSyncPromise = firstSyncPromise;
+        } else {
+            this.firstSyncPromise = Promise.withResolvers();
+        }
         this.firstSyncComplete = false;
-        this.firstSyncPromise = defer();
         const cli = MatrixClientPeg.safeGet();
 
         // Allow the JS SDK to reap timeline events. This reduces the amount of
@@ -1556,29 +1593,36 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             dis.fire(Action.FocusSendMessageComposer);
         });
 
-        cli.on(HttpApiEvent.SessionLoggedOut, function (errObj) {
+        cli.on(HttpApiEvent.SessionLoggedOut, (errObj) => {
+            this.loadSessionAbortController.abort(errObj);
+            this.loadSessionAbortController = new AbortController();
+
             if (Lifecycle.isLoggingOut()) return;
 
             // A modal might have been open when we were logged out by the server
             Modal.forceCloseAllModals();
 
-            if (errObj.httpStatus === 401 && errObj.data && errObj.data["soft_logout"]) {
+            if (errObj.httpStatus === 401 && errObj.data?.["soft_logout"]) {
                 logger.warn("Soft logout issued by server - avoiding data deletion");
                 Lifecycle.softLogout();
                 return;
             }
 
+            dis.dispatch(
+                {
+                    action: "logout",
+                },
+                true,
+            );
+
+            // The above dispatch closes all modals, so open the modal after calling it synchronously
             Modal.createDialog(ErrorDialog, {
                 title: _t("auth|session_logged_out_title"),
                 description: _t("auth|session_logged_out_description"),
             });
-
-            dis.dispatch({
-                action: "logout",
-            });
         });
         cli.on(HttpApiEvent.NoConsent, function (message, consentUri) {
-            Modal.createDialog(
+            const { finished } = Modal.createDialog(
                 QuestionDialog,
                 {
                     title: _t("terms|tac_title"),
@@ -1589,16 +1633,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
                     ),
                     button: _t("terms|tac_button"),
                     cancelButton: _t("action|dismiss"),
-                    onFinished: (confirmed) => {
-                        if (confirmed) {
-                            const wnd = window.open(consentUri, "_blank")!;
-                            wnd.opener = null;
-                        }
-                    },
                 },
                 undefined,
                 true,
             );
+            finished.then(([confirmed]) => {
+                if (confirmed) {
+                    const wnd = window.open(consentUri, "_blank")!;
+                    wnd.opener = null;
+                }
+            });
         });
 
         DecryptionFailureTracker.instance
@@ -1695,13 +1739,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
         if (crypto) {
             const blacklistEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, "blacklistUnverifiedDevices");
             crypto.globalBlacklistUnverifiedDevices = blacklistEnabled;
-
-            // With cross-signing enabled, we send to unknown devices
-            // without prompting. Any bad-device status the user should
-            // be aware of will be signalled through the room shield
-            // changing colour. More advanced behaviour will come once
-            // we implement more settings.
-            cli.setGlobalErrorOnUnknownDevices(false);
         }
 
         // Cannot be done in OnLoggedIn as at that point the AccountSettingsHandler doesn't yet have a client
@@ -1759,12 +1796,26 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
             }
         } else if (screen === "new") {
             dis.dispatch({
-                action: "view_create_room",
+                action: Action.CreateRoom,
             });
         } else if (screen === "dm") {
             dis.dispatch({
-                action: "view_create_chat",
+                action: Action.CreateChat,
             });
+        } else if (screen === "share") {
+            if (params && params["msg"] !== undefined) {
+                dis.dispatch<SharePayload>({
+                    action: Action.Share,
+                    msg: params["msg"],
+                    format: params["format"],
+                });
+            }
+            // if we weren't already coming at this from an existing screen
+            // and we're logged in, then explicitly default to home.
+            // if we're not logged in, then the login flow will do the right thing.
+            if (!this.state.currentRoomId && !this.state.currentUserId) {
+                this.viewHome();
+            }
         } else if (screen === "settings") {
             dis.fire(Action.ViewUserSettings);
         } else if (screen === "welcome") {
@@ -2001,9 +2052,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
     };
 
     // complete security / e2e setup has finished
-    private onCompleteSecurityE2eSetupFinished = (): void => {
-        // This is async but we making this function async to wait for it isn't useful
-        this.onShowPostLoginScreen().catch((e) => {
+    private onCompleteSecurityE2eSetupFinished = async (): Promise<void> => {
+        const forceVerify = await this.shouldForceVerification();
+        if (forceVerify) {
+            const isVerified = await MatrixClientPeg.safeGet().getCrypto()?.isCrossSigningReady();
+            if (!isVerified) {
+                // We must verify but we haven't yet verified - don't continue logging in
+                return;
+            }
+        }
+
+        await this.onShowPostLoginScreen().catch((e) => {
             logger.error("Exception showing post-login screen", e);
         });
     };
diff --git a/src/components/structures/MatrixClientContextProvider.tsx b/src/components/structures/MatrixClientContextProvider.tsx
index edd0daaaba87a11842e8be9bc3f3e117228aabb9..7d555f580950f63bc9b04bc301fb972be95beae5 100644
--- a/src/components/structures/MatrixClientContextProvider.tsx
+++ b/src/components/structures/MatrixClientContextProvider.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { PropsWithChildren, useEffect, useState } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type PropsWithChildren, useEffect, useState, type JSX } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -85,7 +85,7 @@ interface Props {
  * A React component which exposes a {@link MatrixClientContext} and a {@link LocalDeviceVerificationStateContext}
  * to its children.
  */
-export function MatrixClientContextProvider(props: PropsWithChildren<Props>): React.JSX.Element {
+export function MatrixClientContextProvider(props: PropsWithChildren<Props>): JSX.Element {
     const verificationState = useLocalVerificationState(props.client);
     return (
         <MatrixClientContext.Provider value={props.client}>
diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx
index 67c1b98d68b61a3f0649915bfe737af41f9c379c..2441182bc8300f441ca01a8fa43a1f71e7539586 100644
--- a/src/components/structures/MessagePanel.tsx
+++ b/src/components/structures/MessagePanel.tsx
@@ -6,9 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode, TransitionEvent } from "react";
+import React, { type JSX, createRef, type ReactNode, type TransitionEvent } from "react";
 import classNames from "classnames";
-import { Room, MatrixClient, RoomStateEvent, EventStatus, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import {
+    type Room,
+    type MatrixClient,
+    RoomStateEvent,
+    EventStatus,
+    type MatrixEvent,
+    EventType,
+} from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
 
@@ -19,30 +26,30 @@ import SettingsStore from "../../settings/SettingsStore";
 import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
 import { Layout } from "../../settings/enums/Layout";
 import EventTile, {
-    GetRelationsForEvent,
-    IReadReceiptProps,
+    type GetRelationsForEvent,
+    type IReadReceiptProps,
     isEligibleForSpecialReceipt,
-    UnwrappedEventTile,
+    type UnwrappedEventTile,
 } from "../views/rooms/EventTile";
 import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import LegacyCallEventGrouper from "./LegacyCallEventGrouper";
+import type LegacyCallEventGrouper from "./LegacyCallEventGrouper";
 import WhoIsTypingTile from "../views/rooms/WhoIsTypingTile";
-import ScrollPanel, { IScrollState } from "./ScrollPanel";
+import ScrollPanel, { type IScrollState } from "./ScrollPanel";
 import DateSeparator from "../views/messages/DateSeparator";
 import TimelineSeparator, { SeparatorKind } from "../views/messages/TimelineSeparator";
 import ErrorBoundary from "../views/elements/ErrorBoundary";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import Spinner from "../views/elements/Spinner";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
-import EditorStateTransfer from "../../utils/EditorStateTransfer";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import type EditorStateTransfer from "../../utils/EditorStateTransfer";
 import { Action } from "../../dispatcher/actions";
 import { getEventDisplayInfo } from "../../utils/EventRenderingUtils";
-import { IReadReceiptPosition } from "../views/rooms/ReadReceiptMarker";
+import { type IReadReceiptPosition } from "../views/rooms/ReadReceiptMarker";
 import { haveRendererForEvent } from "../../events/EventTileFactory";
 import { editorRoomKey } from "../../Editing";
 import { hasThreadSummary } from "../../utils/EventUtils";
-import { BaseGrouper } from "./grouper/BaseGrouper";
+import { type BaseGrouper } from "./grouper/BaseGrouper";
 import { MainGrouper } from "./grouper/MainGrouper";
 import { CreationGrouper } from "./grouper/CreationGrouper";
 import { _t } from "../../languageHandler";
@@ -252,8 +259,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
     // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
     public grouperKeyMap = new WeakMap<MatrixEvent, string>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             // previous positions the read marker has been in, so we can
@@ -285,6 +292,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
         this.props.room?.currentState.off(RoomStateEvent.Update, this.calculateRoomMembersCount);
         SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
         this.readReceiptMap = {};
+        this.resizeObserver.disconnect();
     }
 
     public componentDidUpdate(prevProps: IProps, prevState: IState): void {
@@ -793,7 +801,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
                 isRedacted={mxEv.isRedacted()}
                 replacingEventId={mxEv.replacingEventId()}
                 editState={isEditing ? this.props.editState : undefined}
-                onHeightChanged={this.onHeightChanged}
+                resizeObserver={this.resizeObserver}
                 readReceipts={readReceipts}
                 readReceiptMap={this.readReceiptMap}
                 showUrlPreview={this.props.showUrlPreview}
@@ -946,15 +954,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
         this.eventTiles[eventId] = node;
     };
 
-    // once dynamic content in the events load, make the scrollPanel check the
-    // scroll offsets.
+    // Once dynamic content in the events load, make the scrollPanel check the scroll offsets.
     public onHeightChanged = (): void => {
-        const scrollPanel = this.scrollPanel.current;
-        if (scrollPanel) {
-            scrollPanel.checkScroll();
-        }
+        this.scrollPanel.current?.checkScroll();
     };
 
+    private resizeObserver = new ResizeObserver(this.onHeightChanged);
+
     private onTypingShown = (): void => {
         const scrollPanel = this.scrollPanel.current;
         // this will make the timeline grow, so checkScroll
diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx
index aa445a549827ca6c23b4251f8ca90844bab8bbc3..481c9f0bf987ee24e8404b2dfe5f9505c7b80fdb 100644
--- a/src/components/structures/NonUrgentToastContainer.tsx
+++ b/src/components/structures/NonUrgentToastContainer.tsx
@@ -6,20 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
-import { ComponentClass } from "../../@types/common";
+import { type ComponentClass } from "../../@types/common";
 import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
 
-interface IProps {}
-
 interface IState {
     toasts: ComponentClass[];
 }
 
-export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
-    public constructor(props: IProps) {
+export default class NonUrgentToastContainer extends React.PureComponent<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx
index cd71710782d3f21bb512001f255a787ea9bd582f..b31ed6bab60ea78c719e6a210f3ea09ba5a5035d 100644
--- a/src/components/structures/NotificationPanel.tsx
+++ b/src/components/structures/NotificationPanel.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications";
 
@@ -38,8 +38,8 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
 
     private card = React.createRef<HTMLDivElement>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             narrow: false,
diff --git a/src/components/structures/PictureInPictureDragger.tsx b/src/components/structures/PictureInPictureDragger.tsx
index ffdf1fd7c8814ff94a4133d9c1be74a0a4d622c0..057d600eefe2aa2b24a8983d87f7176e10ac232b 100644
--- a/src/components/structures/PictureInPictureDragger.tsx
+++ b/src/components/structures/PictureInPictureDragger.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
+import React, { type JSX, createRef } from "react";
 
 import UIStore, { UI_EVENTS } from "../../stores/UIStore";
 import { lerp } from "../../utils/AnimationUtils";
diff --git a/src/components/structures/PipContainer.tsx b/src/components/structures/PipContainer.tsx
index c68aa561d836770055d2848379a476c148c627f2..e46267c149e1436badaea684c801a4de2b30d480 100644
--- a/src/components/structures/PipContainer.tsx
+++ b/src/components/structures/PipContainer.tsx
@@ -6,20 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { MutableRefObject, ReactNode, useRef } from "react";
-import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import React, { type RefObject, type ReactNode, useRef } from "react";
+import { CallEvent, CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import LegacyCallView from "../views/voip/LegacyCallView";
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
-import PictureInPictureDragger, { CreatePipChildren } from "./PictureInPictureDragger";
+import PictureInPictureDragger, { type CreatePipChildren } from "./PictureInPictureDragger";
 import dis from "../../dispatcher/dispatcher";
 import { Action } from "../../dispatcher/actions";
 import { WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
 import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../stores/ActiveWidgetStore";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { WidgetPip } from "../views/pips/WidgetPip";
@@ -34,7 +34,7 @@ const SHOW_CALL_IN_STATES = [
 ];
 
 interface IProps {
-    movePersistedElement: MutableRefObject<(() => void) | undefined>;
+    movePersistedElement: RefObject<(() => void) | null>;
 }
 
 interface IState {
@@ -280,7 +280,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
 }
 
 export const PipContainer: React.FC = () => {
-    const movePersistedElement = useRef<() => void>();
+    const movePersistedElement = useRef<() => void>(null);
 
     return <PipContainerInner movePersistedElement={movePersistedElement} />;
 };
diff --git a/src/components/structures/ReleaseAnnouncement.tsx b/src/components/structures/ReleaseAnnouncement.tsx
index cc21407c60544279fb1c11db2b68928d1a9ad0bb..da41b3c1dd2c79b64b7a1a5bb954d4a34da33504 100644
--- a/src/components/structures/ReleaseAnnouncement.tsx
+++ b/src/components/structures/ReleaseAnnouncement.tsx
@@ -6,10 +6,10 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { ComponentProps, JSX, PropsWithChildren } from "react";
+import React, { type ComponentProps, type JSX, type PropsWithChildren } from "react";
 import { ReleaseAnnouncement as ReleaseAnnouncementCompound } from "@vector-im/compound-web";
 
-import { ReleaseAnnouncementStore, Feature } from "../../stores/ReleaseAnnouncementStore";
+import { ReleaseAnnouncementStore, type Feature } from "../../stores/ReleaseAnnouncementStore";
 import { useIsReleaseAnnouncementOpen } from "../../hooks/useIsReleaseAnnouncementOpen";
 
 interface ReleaseAnnouncementProps
diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx
index b5cf0529b5697e94b2dce614dec3abb87e337db1..94a5d34d60d2926fc75c56c9cb70177162979f3b 100644
--- a/src/components/structures/RightPanel.tsx
+++ b/src/components/structures/RightPanel.tsx
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
-import { Room, RoomState, RoomStateEvent, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React from "react";
+import { type Room, type RoomState, RoomStateEvent, RoomMember, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { throttle } from "lodash";
 
 import dis from "../../dispatcher/dispatcher";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
-import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
+import RoomSummaryCardView from "../views/right_panel/RoomSummaryCardView";
 import WidgetCard from "../views/right_panel/WidgetCard";
 import UserInfo from "../views/right_panel/UserInfo";
 import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
@@ -23,15 +23,15 @@ import FilePanel from "./FilePanel";
 import ThreadView from "./ThreadView";
 import ThreadPanel from "./ThreadPanel";
 import NotificationPanel from "./NotificationPanel";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import { PinnedMessagesCard } from "../views/right_panel/PinnedMessagesCard";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
-import { E2EStatus } from "../../utils/ShieldUtils";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type E2EStatus } from "../../utils/ShieldUtils";
 import TimelineCard from "../views/right_panel/TimelineCard";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
-import { IRightPanelCard, IRightPanelCardState } from "../../stores/right-panel/RightPanelStoreIPanelState";
+import { type IRightPanelCard, type IRightPanelCardState } from "../../stores/right-panel/RightPanelStoreIPanelState";
 import { Action } from "../../dispatcher/actions";
-import { XOR } from "../../@types/common";
+import { type XOR } from "../../@types/common";
 import ExtensionsCard from "../views/right_panel/ExtensionsCard";
 import MemberListView from "../views/rooms/MemberList/MemberListView";
 
@@ -49,8 +49,9 @@ interface RoomlessProps extends BaseProps {
 interface RoomProps extends BaseProps {
     room: Room;
     permalinkCreator: RoomPermalinkCreator;
-    onSearchChange?: (e: ChangeEvent) => void;
+    onSearchChange?: (term: string) => void;
     onSearchCancel?: () => void;
+    searchTerm?: string;
 }
 
 type Props = XOR<RoomlessProps, RoomProps>;
@@ -64,8 +65,10 @@ export default class RightPanel extends React.Component<Props, IState> {
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: Props, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: Props) {
+        super(props);
+
+        this.state = RightPanel.getDerivedStateFromProps(props);
     }
 
     private readonly delayedUpdate = throttle(
@@ -252,12 +255,13 @@ export default class RightPanel extends React.Component<Props, IState> {
             case RightPanelPhases.RoomSummary:
                 if (!!this.props.room) {
                     card = (
-                        <RoomSummaryCard
+                        <RoomSummaryCardView
                             room={this.props.room}
                             // whenever RightPanel is passed a room it is passed a permalinkcreator
                             permalinkCreator={this.props.permalinkCreator!}
                             onSearchChange={this.props.onSearchChange}
                             onSearchCancel={this.props.onSearchCancel}
+                            searchTerm={this.props.searchTerm}
                             focusRoomSearch={cardState?.focusRoomSearch}
                         />
                     );
@@ -278,7 +282,7 @@ export default class RightPanel extends React.Component<Props, IState> {
         }
 
         return (
-            <aside className="mx_RightPanel" id="mx_RightPanel">
+            <aside className="mx_RightPanel" id="mx_RightPanel" data-testid="right-panel">
                 {card}
             </aside>
         );
diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx
index 1e36dc84dba5f33f6620ed1c95df8751681f0e5c..2130f4fa216accbaf7acf37ab600903e22e70d39 100644
--- a/src/components/structures/RoomSearch.tsx
+++ b/src/components/structures/RoomSearch.tsx
@@ -7,11 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import * as React from "react";
+import React from "react";
 
 import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
 import { IS_MAC, Key } from "../../Keyboard";
 import { _t } from "../../languageHandler";
 import AccessibleButton from "../views/elements/AccessibleButton";
@@ -22,26 +21,10 @@ interface IProps {
 }
 
 export default class RoomSearch extends React.PureComponent<IProps> {
-    private dispatcherRef?: string;
-
-    public componentDidMount(): void {
-        this.dispatcherRef = defaultDispatcher.register(this.onAction);
-    }
-
-    public componentWillUnmount(): void {
-        defaultDispatcher.unregister(this.dispatcherRef);
-    }
-
     private openSpotlight(): void {
         defaultDispatcher.fire(Action.OpenSpotlight);
     }
 
-    private onAction = (payload: ActionPayload): void => {
-        if (payload.action === "focus_room_filter") {
-            this.openSpotlight();
-        }
-    };
-
     public render(): React.ReactNode {
         const classes = classNames(
             {
diff --git a/src/components/structures/RoomSearchView.tsx b/src/components/structures/RoomSearchView.tsx
index 14f34c9146281474aca1d34e833b36df8b337b4c..c4d4f30b0529af900d1836d8b8aecda0620bb96a 100644
--- a/src/components/structures/RoomSearchView.tsx
+++ b/src/components/structures/RoomSearchView.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
+import React, { type JSX, type Ref, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
 import {
-    ISearchResults,
-    IThreadBundledRelationship,
-    MatrixEvent,
+    type ISearchResults,
+    type IThreadBundledRelationship,
+    type MatrixEvent,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
@@ -21,9 +21,7 @@ import { _t } from "../../languageHandler";
 import { haveRendererForEvent } from "../../events/EventTileFactory";
 import SearchResultTile from "../views/rooms/SearchResultTile";
 import { searchPagination, SearchScope } from "../../Searching";
-import Modal from "../../Modal";
-import ErrorDialog from "../views/dialogs/ErrorDialog";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
 import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
@@ -45,281 +43,276 @@ interface Props {
     abortController?: AbortController;
     resizeNotifier: ResizeNotifier;
     className: string;
-    onUpdate(inProgress: boolean, results: ISearchResults | null): void;
+    onUpdate(inProgress: boolean, results: ISearchResults | null, error: Error | null): void;
+    ref?: Ref<ScrollPanel>;
 }
 
 // XXX: todo: merge overlapping results somehow?
 // XXX: why doesn't searching on name work?
-export const RoomSearchView = forwardRef<ScrollPanel, Props>(
-    ({ term, scope, promise, abortController, resizeNotifier, className, onUpdate, inProgress }: Props, ref) => {
-        const client = useContext(MatrixClientContext);
-        const roomContext = useScopedRoomContext("showHiddenEvents");
-        const [highlights, setHighlights] = useState<string[] | null>(null);
-        const [results, setResults] = useState<ISearchResults | null>(null);
-        const aborted = useRef(false);
-        // A map from room ID to permalink creator
-        const permalinkCreators = useMemo(() => new Map<string, RoomPermalinkCreator>(), []);
-        const innerRef = useRef<ScrollPanel | null>();
-
-        useEffect(() => {
-            return () => {
-                permalinkCreators.forEach((pc) => pc.stop());
-                permalinkCreators.clear();
-            };
-        }, [permalinkCreators]);
-
-        const handleSearchResult = useCallback(
-            (searchPromise: Promise<ISearchResults>): Promise<boolean> => {
-                onUpdate(true, null);
-
-                return searchPromise.then(
-                    async (results): Promise<boolean> => {
-                        debuglog("search complete");
-                        if (aborted.current) {
-                            logger.error("Discarding stale search results");
-                            return false;
-                        }
+export const RoomSearchView = ({
+    term,
+    scope,
+    promise,
+    abortController,
+    resizeNotifier,
+    className,
+    onUpdate,
+    inProgress,
+    ref,
+}: Props): JSX.Element => {
+    const client = useContext(MatrixClientContext);
+    const roomContext = useScopedRoomContext("showHiddenEvents");
+    const [highlights, setHighlights] = useState<string[] | null>(null);
+    const [results, setResults] = useState<ISearchResults | null>(null);
+    const aborted = useRef(false);
+    // A map from room ID to permalink creator
+    const permalinkCreators = useMemo(() => new Map<string, RoomPermalinkCreator>(), []);
+    const innerRef = useRef<ScrollPanel>(null);
+
+    useEffect(() => {
+        return () => {
+            permalinkCreators.forEach((pc) => pc.stop());
+            permalinkCreators.clear();
+        };
+    }, [permalinkCreators]);
 
-                        // postgres on synapse returns us precise details of the strings
-                        // which actually got matched for highlighting.
-                        //
-                        // In either case, we want to highlight the literal search term
-                        // whether it was used by the search engine or not.
+    const handleSearchResult = useCallback(
+        (searchPromise: Promise<ISearchResults>): Promise<boolean> => {
+            onUpdate(true, null, null);
 
-                        let highlights = results.highlights;
-                        if (!highlights.includes(term)) {
-                            highlights = highlights.concat(term);
-                        }
+            return searchPromise.then(
+                async (results): Promise<boolean> => {
+                    debuglog("search complete");
+                    if (aborted.current) {
+                        logger.error("Discarding stale search results");
+                        return false;
+                    }
 
-                        // For overlapping highlights,
-                        // favour longer (more specific) terms first
-                        highlights = highlights.sort(function (a, b) {
-                            return b.length - a.length;
-                        });
-
-                        for (const result of results.results) {
-                            for (const event of result.context.getTimeline()) {
-                                const bundledRelationship =
-                                    event.getServerAggregatedRelation<IThreadBundledRelationship>(
-                                        THREAD_RELATION_TYPE.name,
-                                    );
-                                if (!bundledRelationship || event.getThread()) continue;
-                                const room = client.getRoom(event.getRoomId());
-                                const thread = room?.findThreadForEvent(event);
-                                if (thread) {
-                                    event.setThread(thread);
-                                } else {
-                                    room?.createThread(event.getId()!, event, [], true);
-                                }
+                    // postgres on synapse returns us precise details of the strings
+                    // which actually got matched for highlighting.
+                    //
+                    // In either case, we want to highlight the literal search term
+                    // whether it was used by the search engine or not.
+
+                    let highlights = results.highlights;
+                    if (!highlights.includes(term)) {
+                        highlights = highlights.concat(term);
+                    }
+
+                    // For overlapping highlights,
+                    // favour longer (more specific) terms first
+                    highlights = highlights.sort(function (a, b) {
+                        return b.length - a.length;
+                    });
+
+                    for (const result of results.results) {
+                        for (const event of result.context.getTimeline()) {
+                            const bundledRelationship = event.getServerAggregatedRelation<IThreadBundledRelationship>(
+                                THREAD_RELATION_TYPE.name,
+                            );
+                            if (!bundledRelationship || event.getThread()) continue;
+                            const room = client.getRoom(event.getRoomId());
+                            const thread = room?.findThreadForEvent(event);
+                            if (thread) {
+                                event.setThread(thread);
+                            } else {
+                                room?.createThread(event.getId()!, event, [], true);
                             }
                         }
+                    }
 
-                        setHighlights(highlights);
-                        setResults({ ...results }); // copy to force a refresh
-                        onUpdate(false, results);
+                    setHighlights(highlights);
+                    setResults({ ...results }); // copy to force a refresh
+                    onUpdate(false, results, null);
+                    return false;
+                },
+                (error) => {
+                    if (aborted.current) {
+                        logger.error("Discarding stale search results");
                         return false;
-                    },
-                    (error) => {
-                        if (aborted.current) {
-                            logger.error("Discarding stale search results");
-                            return false;
-                        }
-                        logger.error("Search failed", error);
-                        Modal.createDialog(ErrorDialog, {
-                            title: _t("error_dialog|search_failed|title"),
-                            description: error?.message ?? _t("error_dialog|search_failed|server_unavailable"),
-                        });
-                        onUpdate(false, null);
-                        return false;
-                    },
-                );
-            },
-            [client, term, onUpdate],
+                    }
+                    logger.error("Search failed", error);
+                    onUpdate(false, null, error);
+                    return false;
+                },
+            );
+        },
+        [client, term, onUpdate],
+    );
+
+    // Mount & unmount effect
+    useEffect(() => {
+        aborted.current = false;
+        handleSearchResult(promise);
+        return () => {
+            aborted.current = true;
+            abortController?.abort();
+        };
+    }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+    // show searching spinner
+    if (results === null) {
+        return (
+            <div
+                className="mx_RoomView_messagePanel mx_RoomView_messagePanelSearchSpinner"
+                data-testid="messagePanelSearchSpinner"
+            />
         );
+    }
 
-        // Mount & unmount effect
-        useEffect(() => {
-            aborted.current = false;
-            handleSearchResult(promise);
-            return () => {
-                aborted.current = true;
-                abortController?.abort();
-            };
-        }, []); // eslint-disable-line react-hooks/exhaustive-deps
-
-        // show searching spinner
-        if (results === null) {
-            return (
-                <div
-                    className="mx_RoomView_messagePanel mx_RoomView_messagePanelSearchSpinner"
-                    data-testid="messagePanelSearchSpinner"
-                />
-            );
+    const onSearchResultsFillRequest = async (backwards: boolean): Promise<boolean> => {
+        if (!backwards) {
+            return false;
         }
 
-        const onSearchResultsFillRequest = async (backwards: boolean): Promise<boolean> => {
-            if (!backwards) {
-                return false;
-            }
+        if (!results.next_batch) {
+            debuglog("no more search results");
+            return false;
+        }
 
-            if (!results.next_batch) {
-                debuglog("no more search results");
-                return false;
-            }
+        debuglog("requesting more search results");
+        const searchPromise = searchPagination(client, results);
+        return handleSearchResult(searchPromise);
+    };
 
-            debuglog("requesting more search results");
-            const searchPromise = searchPagination(client, results);
-            return handleSearchResult(searchPromise);
-        };
+    const ret: JSX.Element[] = [];
 
-        const ret: JSX.Element[] = [];
+    if (inProgress) {
+        ret.push(
+            <li key="search-spinner">
+                <Spinner />
+            </li>,
+        );
+    }
 
-        if (inProgress) {
+    if (!results.next_batch) {
+        if (!results?.results?.length) {
+            ret.push(
+                <li key="search-top-marker">
+                    <h2 className="mx_RoomView_topMarker">{_t("common|no_results")}</h2>
+                </li>,
+            );
+        } else {
             ret.push(
-                <li key="search-spinner">
-                    <Spinner />
+                <li key="search-top-marker">
+                    <h2 className="mx_RoomView_topMarker">{_t("no_more_results")}</h2>
                 </li>,
             );
         }
+    }
 
-        if (!results.next_batch) {
-            if (!results?.results?.length) {
-                ret.push(
-                    <li key="search-top-marker">
-                        <h2 className="mx_RoomView_topMarker">{_t("common|no_results")}</h2>
-                    </li>,
-                );
-            } else {
+    const onRef = (e: ScrollPanel | null): void => {
+        if (typeof ref === "function") {
+            ref(e);
+        } else if (!!ref) {
+            ref.current = e;
+        }
+        innerRef.current = e;
+    };
+
+    let lastRoomId: string | undefined;
+    let mergedTimeline: MatrixEvent[] = [];
+    let ourEventsIndexes: number[] = [];
+
+    for (let i = (results?.results?.length || 0) - 1; i >= 0; i--) {
+        const result = results.results[i];
+
+        const mxEv = result.context.getEvent();
+        const roomId = mxEv.getRoomId()!;
+        const room = client.getRoom(roomId);
+        if (!room) {
+            // if we do not have the room in js-sdk stores then hide it as we cannot easily show it
+            // As per the spec, an all rooms search can create this condition,
+            // it happens with Seshat but not Synapse.
+            // It will make the result count not match the displayed count.
+            logger.log("Hiding search result from an unknown room", roomId);
+            continue;
+        }
+
+        if (!haveRendererForEvent(mxEv, client, roomContext.showHiddenEvents)) {
+            // XXX: can this ever happen? It will make the result count
+            // not match the displayed count.
+            continue;
+        }
+
+        if (scope === SearchScope.All) {
+            if (roomId !== lastRoomId) {
                 ret.push(
-                    <li key="search-top-marker">
-                        <h2 className="mx_RoomView_topMarker">{_t("no_more_results")}</h2>
+                    <li key={mxEv.getId() + "-room"}>
+                        <h2>
+                            {_t("common|room")}: {room.name}
+                        </h2>
                     </li>,
                 );
+                lastRoomId = roomId;
             }
         }
 
-        // once dynamic content in the search results load, make the scrollPanel check
-        // the scroll offsets.
-        const onHeightChanged = (): void => {
-            innerRef.current?.checkScroll();
-        };
-
-        const onRef = (e: ScrollPanel | null): void => {
-            if (typeof ref === "function") {
-                ref(e);
-            } else if (!!ref) {
-                ref.current = e;
-            }
-            innerRef.current = e;
-        };
-
-        let lastRoomId: string | undefined;
-        let mergedTimeline: MatrixEvent[] = [];
-        let ourEventsIndexes: number[] = [];
-
-        for (let i = (results?.results?.length || 0) - 1; i >= 0; i--) {
-            const result = results.results[i];
-
-            const mxEv = result.context.getEvent();
-            const roomId = mxEv.getRoomId()!;
-            const room = client.getRoom(roomId);
-            if (!room) {
-                // if we do not have the room in js-sdk stores then hide it as we cannot easily show it
-                // As per the spec, an all rooms search can create this condition,
-                // it happens with Seshat but not Synapse.
-                // It will make the result count not match the displayed count.
-                logger.log("Hiding search result from an unknown room", roomId);
-                continue;
-            }
-
-            if (!haveRendererForEvent(mxEv, client, roomContext.showHiddenEvents)) {
-                // XXX: can this ever happen? It will make the result count
-                // not match the displayed count.
-                continue;
-            }
-
-            if (scope === SearchScope.All) {
-                if (roomId !== lastRoomId) {
-                    ret.push(
-                        <li key={mxEv.getId() + "-room"}>
-                            <h2>
-                                {_t("common|room")}: {room.name}
-                            </h2>
-                        </li>,
-                    );
-                    lastRoomId = roomId;
-                }
-            }
-
-            const resultLink = "#/room/" + roomId + "/" + mxEv.getId();
-
-            // merging two successive search result if the query is present in both of them
-            const currentTimeline = result.context.getTimeline();
-            const nextTimeline = i > 0 ? results.results[i - 1].context.getTimeline() : [];
+        const resultLink = "#/room/" + roomId + "/" + mxEv.getId();
 
-            if (i > 0 && currentTimeline[currentTimeline.length - 1].getId() == nextTimeline[0].getId()) {
-                // if this is the first searchResult we merge then add all values of the current searchResult
-                if (mergedTimeline.length == 0) {
-                    for (let j = mergedTimeline.length == 0 ? 0 : 1; j < result.context.getTimeline().length; j++) {
-                        mergedTimeline.push(currentTimeline[j]);
-                    }
-                    ourEventsIndexes.push(result.context.getOurEventIndex());
-                }
-
-                // merge the events of the next searchResult
-                for (let j = 1; j < nextTimeline.length; j++) {
-                    mergedTimeline.push(nextTimeline[j]);
-                }
-
-                // add the index of the matching event of the next searchResult
-                ourEventsIndexes.push(
-                    ourEventsIndexes[ourEventsIndexes.length - 1] +
-                        results.results[i - 1].context.getOurEventIndex() +
-                        1,
-                );
-
-                continue;
-            }
+        // merging two successive search result if the query is present in both of them
+        const currentTimeline = result.context.getTimeline();
+        const nextTimeline = i > 0 ? results.results[i - 1].context.getTimeline() : [];
 
+        if (i > 0 && currentTimeline[currentTimeline.length - 1].getId() == nextTimeline[0].getId()) {
+            // if this is the first searchResult we merge then add all values of the current searchResult
             if (mergedTimeline.length == 0) {
-                mergedTimeline = result.context.getTimeline();
-                ourEventsIndexes = [];
+                for (let j = mergedTimeline.length == 0 ? 0 : 1; j < result.context.getTimeline().length; j++) {
+                    mergedTimeline.push(currentTimeline[j]);
+                }
                 ourEventsIndexes.push(result.context.getOurEventIndex());
             }
 
-            let permalinkCreator = permalinkCreators.get(roomId);
-            if (!permalinkCreator) {
-                permalinkCreator = new RoomPermalinkCreator(room);
-                permalinkCreator.start();
-                permalinkCreators.set(roomId, permalinkCreator);
+            // merge the events of the next searchResult
+            for (let j = 1; j < nextTimeline.length; j++) {
+                mergedTimeline.push(nextTimeline[j]);
             }
 
-            ret.push(
-                <SearchResultTile
-                    key={mxEv.getId()}
-                    timeline={mergedTimeline}
-                    ourEventsIndexes={ourEventsIndexes}
-                    searchHighlights={highlights ?? []}
-                    resultLink={resultLink}
-                    permalinkCreator={permalinkCreator}
-                    onHeightChanged={onHeightChanged}
-                />,
+            // add the index of the matching event of the next searchResult
+            ourEventsIndexes.push(
+                ourEventsIndexes[ourEventsIndexes.length - 1] + results.results[i - 1].context.getOurEventIndex() + 1,
             );
 
+            continue;
+        }
+
+        if (mergedTimeline.length == 0) {
+            mergedTimeline = result.context.getTimeline();
             ourEventsIndexes = [];
-            mergedTimeline = [];
+            ourEventsIndexes.push(result.context.getOurEventIndex());
         }
 
-        return (
-            <ScrollPanel
-                ref={onRef}
-                className={"mx_RoomView_searchResultsPanel " + className}
-                onFillRequest={onSearchResultsFillRequest}
-                resizeNotifier={resizeNotifier}
-            >
-                <li className="mx_RoomView_scrollheader" />
-                {ret}
-            </ScrollPanel>
+        let permalinkCreator = permalinkCreators.get(roomId);
+        if (!permalinkCreator) {
+            permalinkCreator = new RoomPermalinkCreator(room);
+            permalinkCreator.start();
+            permalinkCreators.set(roomId, permalinkCreator);
+        }
+
+        ret.push(
+            <SearchResultTile
+                key={mxEv.getId()}
+                timeline={mergedTimeline}
+                ourEventsIndexes={ourEventsIndexes}
+                searchHighlights={highlights ?? []}
+                resultLink={resultLink}
+                permalinkCreator={permalinkCreator}
+            />,
         );
-    },
-);
+
+        ourEventsIndexes = [];
+        mergedTimeline = [];
+    }
+
+    return (
+        <ScrollPanel
+            ref={onRef}
+            className={"mx_RoomView_searchResultsPanel " + className}
+            onFillRequest={onSearchResultsFillRequest}
+            resizeNotifier={resizeNotifier}
+        >
+            <li className="mx_RoomView_scrollheader" />
+            {ret}
+        </ScrollPanel>
+    );
+};
diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx
index 095c359566e392e0b2e1414d8d477cf19d327241..4793f1a65b27ef0066b7e7378f9ebe848eeb7487 100644
--- a/src/components/structures/RoomStatusBar.tsx
+++ b/src/components/structures/RoomStatusBar.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import {
     ClientEvent,
     EventStatus,
-    MatrixError,
-    MatrixEvent,
-    Room,
+    type MatrixError,
+    type MatrixEvent,
+    type Room,
     RoomEvent,
-    SyncState,
-    SyncStateData,
+    type SyncState,
+    type SyncStateData,
 } from "matrix-js-sdk/src/matrix";
 import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
diff --git a/src/components/structures/RoomStatusBarUnsentMessages.tsx b/src/components/structures/RoomStatusBarUnsentMessages.tsx
index 8cb80c894c0de5061a7cbc2f6ccd4b704eb7f19d..0845f2550a4b182b0b6516fd9a0e92d0320b4f75 100644
--- a/src/components/structures/RoomStatusBarUnsentMessages.tsx
+++ b/src/components/structures/RoomStatusBarUnsentMessages.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, ReactNode } from "react";
+import React, { type ReactElement, type ReactNode } from "react";
 
-import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
+import { type StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
 import NotificationBadge from "../views/rooms/NotificationBadge";
 
 interface RoomStatusBarUnsentMessagesProps {
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index fe51b60564270d96c77f98a6eb4b9032d5c72346..fbff50bd01d8bc59e9c8ceed12cd56d139e0fb5b 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -9,40 +9,47 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, JSX } from "react";
+import React, {
+    type ComponentProps,
+    createRef,
+    type ReactElement,
+    type ReactNode,
+    type RefObject,
+    type JSX,
+} from "react";
 import classNames from "classnames";
 import {
-    IRecommendedVersion,
+    type IRecommendedVersion,
     NotificationCountType,
-    Room,
+    type Room,
     RoomEvent,
-    RoomState,
+    type RoomState,
     RoomStateEvent,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
-    EventTimeline,
-    IRoomTimelineData,
+    type EventTimeline,
+    type IRoomTimelineData,
     EventType,
     HistoryVisibility,
     JoinRule,
     ClientEvent,
-    MatrixError,
-    ISearchResults,
+    type MatrixError,
+    type ISearchResults,
     THREAD_RELATION_TYPE,
-    MatrixClient,
+    type MatrixClient,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import { debounce, throttle } from "lodash";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
-import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
 
 import shouldHideEvent from "../../shouldHideEvent";
 import { _t } from "../../languageHandler";
 import * as TimezoneHandler from "../../TimezoneHandler";
 import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import ContentMessages from "../../ContentMessages";
 import Modal from "../../Modal";
 import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
@@ -50,15 +57,15 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
 import * as Rooms from "../../Rooms";
 import MainSplit from "./MainSplit";
 import RightPanel from "./RightPanel";
-import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateStore";
+import RoomScrollStateStore, { type ScrollState } from "../../stores/RoomScrollStateStore";
 import WidgetEchoStore from "../../stores/WidgetEchoStore";
 import SettingsStore from "../../settings/SettingsStore";
 import { Layout } from "../../settings/enums/Layout";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import { TimelineRenderingType, MainSplitContentType } from "../../contexts/RoomContext";
 import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
 import { Action } from "../../dispatcher/actions";
-import { IMatrixClientCreds } from "../../MatrixClientPeg";
+import { type IMatrixClientCreds } from "../../MatrixClientPeg";
 import ScrollPanel from "./ScrollPanel";
 import TimelinePanel from "./TimelinePanel";
 import ErrorBoundary from "../views/elements/ErrorBoundary";
@@ -66,8 +73,8 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar";
 import RoomPreviewCard from "../views/rooms/RoomPreviewCard";
 import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
 import AuxPanel from "../views/rooms/AuxPanel";
-import RoomHeader from "../views/rooms/RoomHeader";
-import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
+import RoomHeader from "../views/rooms/RoomHeader/RoomHeader";
+import { type IOOBData, type IThreepidInvite } from "../../stores/ThreepidInviteStore";
 import EffectsOverlay from "../views/elements/EffectsOverlay";
 import { containsEmoji } from "../../effects/utils";
 import { CHAT_EFFECTS } from "../../effects";
@@ -79,7 +86,7 @@ import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutS
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 import { objectHasDiff } from "../../utils/objects";
 import SpaceRoomView from "./SpaceRoomView";
-import { IOpts } from "../../createRoom";
+import { type IOpts } from "../../createRoom";
 import EditorStateTransfer from "../../utils/EditorStateTransfer";
 import ErrorDialog from "../views/dialogs/ErrorDialog";
 import UploadBar from "./UploadBar";
@@ -88,46 +95,44 @@ import MessageComposer from "../views/rooms/MessageComposer";
 import JumpToBottomButton from "../views/rooms/JumpToBottomButton";
 import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
 import { fetchInitialEvent } from "../../utils/EventUtils";
-import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
+import { type ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
 import AppsDrawer from "../views/rooms/AppsDrawer";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-import { JoinRoomPayload } from "../../dispatcher/payloads/JoinRoomPayload";
-import { DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type JoinRoomPayload } from "../../dispatcher/payloads/JoinRoomPayload";
+import { type DoAfterSyncPreparedPayload } from "../../dispatcher/payloads/DoAfterSyncPreparedPayload";
 import FileDropTarget from "./FileDropTarget";
 import Measured from "../views/elements/Measured";
-import { FocusComposerPayload } from "../../dispatcher/payloads/FocusComposerPayload";
+import { type FocusComposerPayload } from "../../dispatcher/payloads/FocusComposerPayload";
 import { LocalRoom, LocalRoomState } from "../../models/LocalRoom";
 import { createRoomFromLocalRoom } from "../../utils/direct-messages";
 import NewRoomIntro from "../views/rooms/NewRoomIntro";
 import EncryptionEvent from "../views/messages/EncryptionEvent";
 import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
 import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
-import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
+import { type ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
 import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages";
 import { LargeLoader } from "./LargeLoader";
 import { isVideoRoom } from "../../utils/video-rooms";
 import { SDKContext } from "../../contexts/SDKContext";
-import { CallStore, CallStoreEvent } from "../../stores/CallStore";
-import { Call } from "../../models/Call";
 import { RoomSearchView } from "./RoomSearchView";
-import eventSearch, { SearchInfo, SearchScope } from "../../Searching";
-import VoipUserMapper from "../../VoipUserMapper";
-import { isCallEvent } from "./LegacyCallEventGrouper";
+import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
 import { WidgetType } from "../../widgets/WidgetType";
 import WidgetUtils from "../../utils/WidgetUtils";
 import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
 import { WaitingForThirdPartyRoomView } from "./WaitingForThirdPartyRoomView";
 import { isNotUndefined } from "../../Typeguards";
-import { CancelAskToJoinPayload } from "../../dispatcher/payloads/CancelAskToJoinPayload";
-import { SubmitAskToJoinPayload } from "../../dispatcher/payloads/SubmitAskToJoinPayload";
+import { type CancelAskToJoinPayload } from "../../dispatcher/payloads/CancelAskToJoinPayload";
+import { type SubmitAskToJoinPayload } from "../../dispatcher/payloads/SubmitAskToJoinPayload";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import { onView3pidInvite } from "../../stores/right-panel/action-handlers";
 import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel";
 import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner";
 import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext";
+import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog";
+import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts";
 
 const DEBUG = false;
 const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
@@ -158,7 +163,6 @@ export { MainSplitContentType };
 
 export interface IRoomState {
     room?: Room;
-    virtualRoom?: Room;
     roomId?: string;
     roomAlias?: string;
     roomLoading: boolean;
@@ -182,7 +186,6 @@ export interface IRoomState {
      */
     search?: SearchInfo;
     callState?: CallState;
-    activeCall: Call | null;
     canPeek: boolean;
     canSelfRedact: boolean;
     showApps: boolean;
@@ -251,7 +254,7 @@ interface LocalRoomViewProps {
     localRoom: LocalRoom;
     resizeNotifier: ResizeNotifier;
     permalinkCreator: RoomPermalinkCreator;
-    roomView: RefObject<HTMLElement>;
+    roomView: RefObject<HTMLElement | null>;
     onFileDrop: (dataTransfer: DataTransfer) => Promise<void>;
     mainSplitContentType: MainSplitContentType;
 }
@@ -393,7 +396,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
             membersLoaded: !llMembers,
             numUnreadMessages: 0,
             callState: undefined,
-            activeCall: null,
             canPeek: false,
             canSelfRedact: false,
             showApps: false,
@@ -569,7 +571,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
             mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
             initialEventId: undefined, // default to clearing this, will get set later in the method if needed
             showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
-            activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
             promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
             viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
         };
@@ -719,23 +720,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         });
     };
 
-    private onConnectedCalls = (): void => {
-        if (this.state.roomId === undefined) return;
-        const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
-        if (activeCall === null) {
-            // We disconnected from the call, so stop viewing it
-            defaultDispatcher.dispatch<ViewRoomPayload>(
-                {
-                    action: Action.ViewRoom,
-                    room_id: this.state.roomId,
-                    view_call: false,
-                    metricsTrigger: undefined,
-                },
-                true,
-            ); // Synchronous so that CallView disappears immediately
-        }
-
-        this.setState({ activeCall });
+    private onCallClose = (): void => {
+        // Stop viewing the call
+        defaultDispatcher.dispatch<ViewRoomPayload>(
+            {
+                action: Action.ViewRoom,
+                room_id: this.state.roomId,
+                view_call: false,
+                metricsTrigger: undefined,
+            },
+            true,
+        ); // Synchronous so that CallView disappears immediately
     };
 
     private getRoomId = (): string | undefined => {
@@ -892,8 +887,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
         this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
 
-        CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
-
         this.props.resizeNotifier.on("isResizing", this.onIsResizing);
 
         this.settingWatchers = [
@@ -1019,7 +1012,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
             );
         }
 
-        CallStore.instance.off(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
         this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
 
         // cancel any pending calls to the throttled updated
@@ -1143,13 +1135,14 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                 break;
             case "MatrixActions.sync":
                 if (!this.state.matrixClientIsReady) {
+                    const isReadyNow = Boolean(this.context.client?.isInitialSyncComplete());
                     this.setState(
                         {
-                            matrixClientIsReady: !!this.context.client?.isInitialSyncComplete(),
+                            matrixClientIsReady: isReadyNow,
                         },
                         () => {
                             // send another "initial" RVS update to trigger peeking if needed
-                            this.onRoomViewStoreUpdate(true);
+                            if (isReadyNow) this.onRoomViewStoreUpdate(true);
                         },
                     );
                 }
@@ -1251,6 +1244,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
             case Action.View3pidInvite:
                 onView3pidInvite(payload, RightPanelStore.instance);
                 break;
+            case Action.FocusMessageSearch:
+                if ((payload as FocusMessageSearchPayload).initialText) {
+                    this.onSearch(payload.initialText);
+                }
+                break;
         }
     };
 
@@ -1348,12 +1346,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         return this.messagePanel.canResetTimeline();
     };
 
-    private loadVirtualRoom = async (room?: Room): Promise<void> => {
-        const virtualRoom = room?.roomId && (await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(room?.roomId));
-
-        this.setState({ virtualRoom: virtualRoom || undefined });
-    };
-
     // called when state.room is first initialised (either at initial load,
     // after a successful peek, or after we join the room).
     private onRoomLoaded = (room: Room): void => {
@@ -1366,7 +1358,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         this.calculateRecommendedVersion(room);
         this.updatePermissions(room);
         this.checkWidgets(room);
-        this.loadVirtualRoom(room);
         this.updateRoomEncrypted(room);
 
         if (
@@ -1720,11 +1711,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         this.onSearch(this.state.search?.term ?? "", scope);
     };
 
-    private onSearchUpdate = (inProgress: boolean, searchResults: ISearchResults | null): void => {
+    private onSearchUpdate = (inProgress: boolean, searchResults: ISearchResults | null, error: Error | null): void => {
         this.setState({
             search: {
                 ...this.state.search!,
                 count: searchResults?.count,
+                error: error ?? undefined,
                 inProgress,
             },
         });
@@ -1737,48 +1729,61 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         });
     };
 
-    private onRejectButtonClicked = (): void => {
-        const roomId = this.getRoomId();
-        if (!roomId) return;
+    private onDeclineAndBlockButtonClicked = async (): Promise<void> => {
+        if (!this.state.room || !this.context.client) return;
+        const [shouldReject, ignoreUser, reportRoom] = await Modal.createDialog(DeclineAndBlockInviteDialog, {
+            roomName: this.state.room.name,
+        }).finished;
+        if (!shouldReject) {
+            return;
+        }
+
         this.setState({
             rejecting: true,
         });
-        this.context.client?.leave(roomId).then(
-            () => {
-                defaultDispatcher.dispatch({ action: Action.ViewHomePage });
-                this.setState({
-                    rejecting: false,
-                });
-            },
-            (error) => {
-                logger.error(`Failed to reject invite: ${error}`);
 
-                const msg = error.message ? error.message : JSON.stringify(error);
-                Modal.createDialog(ErrorDialog, {
-                    title: _t("room|failed_reject_invite"),
-                    description: msg,
-                });
+        const actions: Promise<unknown>[] = [];
 
-                this.setState({
-                    rejecting: false,
-                });
-            },
-        );
-    };
+        if (ignoreUser) {
+            const myMember = this.state.room.getMember(this.context.client!.getSafeUserId());
+            const inviteEvent = myMember!.events.member;
+            const ignoredUsers = this.context.client.getIgnoredUsers();
+            ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk
+            actions.push(this.context.client.setIgnoredUsers(ignoredUsers));
+        }
 
-    private onRejectAndIgnoreClick = async (): Promise<void> => {
-        this.setState({
-            rejecting: true,
-        });
+        if (reportRoom !== false) {
+            actions.push(this.context.client.reportRoom(this.state.room.roomId, reportRoom!));
+        }
 
+        actions.push(this.context.client.leave(this.state.room.roomId));
         try {
-            const myMember = this.state.room!.getMember(this.context.client!.getSafeUserId());
-            const inviteEvent = myMember!.events.member;
-            const ignoredUsers = this.context.client!.getIgnoredUsers();
-            ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk
-            await this.context.client!.setIgnoredUsers(ignoredUsers);
+            await Promise.all(actions);
+            defaultDispatcher.dispatch({ action: Action.ViewHomePage });
+            this.setState({
+                rejecting: false,
+            });
+        } catch (error) {
+            logger.error(`Failed to reject invite: ${error}`);
 
-            await this.context.client!.leave(this.state.roomId!);
+            const msg = error instanceof Error ? error.message : JSON.stringify(error);
+            Modal.createDialog(ErrorDialog, {
+                title: _t("room|failed_reject_invite"),
+                description: msg,
+            });
+
+            this.setState({
+                rejecting: false,
+            });
+        }
+    };
+
+    private onDeclineButtonClicked = async (): Promise<void> => {
+        if (!this.state.room || !this.context.client) {
+            return;
+        }
+        try {
+            await this.context.client.leave(this.state.room.roomId);
             defaultDispatcher.dispatch({ action: Action.ViewHomePage });
             this.setState({
                 rejecting: false,
@@ -1806,8 +1811,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
         defaultDispatcher.fire(Action.ViewRoomDirectory);
     };
 
-    private onSearchChange = debounce((e: ChangeEvent): void => {
-        const term = (e.target as HTMLInputElement).value;
+    private onSearchChange = debounce((term: string): void => {
         this.onSearch(term);
     }, 300);
 
@@ -2131,7 +2135,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                             <RoomPreviewBar
                                 onJoinClick={this.onJoinButtonClicked}
                                 onForgetClick={this.onForgetClick}
-                                onRejectClick={this.onRejectThreepidInviteButtonClicked}
+                                onDeclineClick={this.onRejectThreepidInviteButtonClicked}
                                 canPreview={false}
                                 error={this.state.roomLoadError}
                                 roomAlias={roomAlias}
@@ -2159,7 +2163,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                         <RoomPreviewCard
                             room={this.state.room}
                             onJoinButtonClicked={this.onJoinButtonClicked}
-                            onRejectButtonClicked={this.onRejectButtonClicked}
+                            onRejectButtonClicked={this.onDeclineButtonClicked}
                         />
                     </div>
                     ;
@@ -2201,8 +2205,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                             <RoomPreviewBar
                                 onJoinClick={this.onJoinButtonClicked}
                                 onForgetClick={this.onForgetClick}
-                                onRejectClick={this.onRejectButtonClicked}
-                                onRejectAndIgnoreClick={this.onRejectAndIgnoreClick}
+                                onDeclineClick={this.onDeclineButtonClicked}
+                                onDeclineAndBlockClick={this.onDeclineAndBlockButtonClicked}
+                                promptRejectionOptions={true}
                                 inviterName={inviterName}
                                 canPreview={false}
                                 joining={this.state.joining}
@@ -2317,7 +2322,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                 <RoomPreviewBar
                     onJoinClick={this.onJoinButtonClicked}
                     onForgetClick={this.onForgetClick}
-                    onRejectClick={this.onRejectThreepidInviteButtonClicked}
+                    onDeclineClick={this.onRejectThreepidInviteButtonClicked}
+                    promptRejectionOptions={true}
                     joining={this.state.joining}
                     inviterName={inviterName}
                     invitedEmail={invitedEmail}
@@ -2355,7 +2361,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                     onRejectButtonClicked={
                         this.props.threepidInvite
                             ? this.onRejectThreepidInviteButtonClicked
-                            : this.onRejectButtonClicked
+                            : this.onDeclineButtonClicked
                     }
                 />
             );
@@ -2432,8 +2438,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                 <TimelinePanel
                     ref={this.gatherTimelinePanelRef}
                     timelineSet={this.state.room.getUnfilteredTimelineSet()}
-                    overlayTimelineSet={this.state.virtualRoom?.getUnfilteredTimelineSet()}
-                    overlayTimelineSetFilter={isCallEvent}
                     showReadReceipts={this.state.showReadReceipts}
                     manageReadReceipts={!this.state.isPeeking}
                     sendReadReceiptOnLoad={!this.state.wasContextSwitch}
@@ -2487,6 +2491,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                 e2eStatus={this.state.e2eStatus}
                 onSearchChange={this.onSearchChange}
                 onSearchCancel={this.onCancelSearchClick}
+                searchTerm={this.state.search?.term ?? ""}
             />
         ) : undefined;
 
@@ -2553,9 +2558,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
                         <CallView
                             room={this.state.room}
                             resizing={this.state.resizing}
-                            waitForCall={isVideoRoom(this.state.room)}
                             skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
                             role="main"
+                            onClose={this.onCallClose}
                         />
                         {previewBar}
                     </>
diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx
index 413d081746c64260f57f67e08b23c0430f6f1d43..a92b24fc55d1f890725e7c6c01cd92e667b82579 100644
--- a/src/components/structures/ScrollPanel.tsx
+++ b/src/components/structures/ScrollPanel.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, CSSProperties, ReactNode } from "react";
+import React, { createRef, type CSSProperties, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import SettingsStore from "../../settings/SettingsStore";
 import Timer from "../../utils/Timer";
 import AutoHideScrollbar from "./AutoHideScrollbar";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
 
 // The amount of extra scroll distance to allow prior to unfilling.
diff --git a/src/components/structures/SearchBox.tsx b/src/components/structures/SearchBox.tsx
index a5bbb7865eaa5b1f876b8e8f636a5f470c12d438..ec85314349d6fec85ae84247c0e1c3178f957b32 100644
--- a/src/components/structures/SearchBox.tsx
+++ b/src/components/structures/SearchBox.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, HTMLProps } from "react";
+import React, { createRef, type HTMLProps } from "react";
 import { throttle } from "lodash";
 import classNames from "classnames";
 
diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx
index 362fe82dce130ff26bec1171354b154d49834ba5..0d41e56c9257dc55fb91ead2eebd28fc2aac8fb0 100644
--- a/src/components/structures/SpaceHierarchy.tsx
+++ b/src/components/structures/SpaceHierarchy.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021-2023 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,42 +7,44 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, {
-    Dispatch,
-    KeyboardEvent,
-    KeyboardEventHandler,
-    ReactElement,
-    ReactNode,
-    SetStateAction,
+    type JSX,
+    type Dispatch,
+    type KeyboardEvent,
+    type KeyboardEventHandler,
+    type ReactElement,
+    type ReactNode,
+    type SetStateAction,
     useCallback,
     useContext,
     useEffect,
+    useId,
     useMemo,
     useRef,
     useState,
 } from "react";
 import {
-    Room,
+    type Room,
     RoomEvent,
     ClientEvent,
-    MatrixClient,
+    type MatrixClient,
     MatrixError,
     EventType,
     RoomType,
     GuestAccess,
     HistoryVisibility,
-    HierarchyRelation,
-    HierarchyRoom,
+    type HierarchyRelation,
+    type HierarchyRoom,
     JoinRule,
 } from "matrix-js-sdk/src/matrix";
 import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
 import classNames from "classnames";
 import { sortBy, uniqBy } from "lodash";
 import { logger } from "matrix-js-sdk/src/logger";
-import { KnownMembership, SpaceChildEventContent } from "matrix-js-sdk/src/types";
+import { KnownMembership, type SpaceChildEventContent } from "matrix-js-sdk/src/types";
 
 import defaultDispatcher from "../../dispatcher/dispatcher";
 import { _t } from "../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import Spinner from "../views/elements/Spinner";
 import SearchBox from "./SearchBox";
 import RoomAvatar from "../views/avatars/RoomAvatar";
@@ -56,13 +58,13 @@ import { getChildOrder } from "../../stores/spaces/SpaceStore";
 import { Linkify, topicToHtml } from "../../HtmlUtils";
 import { useDispatcher } from "../../hooks/useDispatcher";
 import { Action } from "../../dispatcher/actions";
-import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
+import { type IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
 import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
-import { IOOBData } from "../../stores/ThreepidInviteStore";
+import { type IOOBData } from "../../stores/ThreepidInviteStore";
 import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 import { getTopic } from "../../hooks/room/useTopic";
@@ -116,6 +118,7 @@ const Tile: React.FC<ITileProps> = ({
     const [showChildren, toggleShowChildren] = useStateToggle(true);
     const [onFocus, isActive, ref, nodeRef] = useRovingTabIndex();
     const [busy, setBusy] = useState(false);
+    const checkboxLabelId = useId();
 
     const onPreviewClick = (ev: ButtonEvent): void => {
         ev.preventDefault();
@@ -172,7 +175,14 @@ const Tile: React.FC<ITileProps> = ({
     let checkbox: ReactElement | undefined;
     if (onToggleClick) {
         if (hasPermissions) {
-            checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} tabIndex={isActive ? 0 : -1} />;
+            checkbox = (
+                <StyledCheckbox
+                    role="presentation"
+                    aria-labelledby={checkboxLabelId}
+                    checked={!!selected}
+                    tabIndex={-1}
+                />
+            );
         } else {
             checkbox = (
                 <TextWithTooltip
@@ -181,7 +191,12 @@ const Tile: React.FC<ITileProps> = ({
                         ev.stopPropagation();
                     }}
                 >
-                    <StyledCheckbox disabled={true} tabIndex={isActive ? 0 : -1} />
+                    <StyledCheckbox
+                        role="presentation"
+                        aria-labelledby={checkboxLabelId}
+                        disabled={true}
+                        tabIndex={-1}
+                    />
                 </TextWithTooltip>
             );
         }
@@ -248,7 +263,7 @@ const Tile: React.FC<ITileProps> = ({
             <div className="mx_SpaceHierarchy_roomTile_item">
                 <div className="mx_SpaceHierarchy_roomTile_avatar">{avatar}</div>
                 <div className="mx_SpaceHierarchy_roomTile_name">
-                    {name}
+                    <span id={checkboxLabelId}>{name}</span>
                     {joinedSection}
                     {suggestedSection}
                 </div>
@@ -330,11 +345,14 @@ const Tile: React.FC<ITileProps> = ({
         };
     }
 
+    const shouldToggle = hasPermissions && onToggleClick;
+
     return (
         <li
             className="mx_SpaceHierarchy_roomTileWrapper"
             role="treeitem"
             aria-selected={selected}
+            aria-labelledby={checkboxLabelId}
             aria-expanded={children ? showChildren : undefined}
         >
             <AccessibleButton
@@ -342,7 +360,7 @@ const Tile: React.FC<ITileProps> = ({
                     mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space,
                     mx_SpaceHierarchy_joining: busy,
                 })}
-                onClick={hasPermissions && onToggleClick ? onToggleClick : onPreviewClick}
+                onClick={shouldToggle ? onToggleClick : onPreviewClick}
                 onKeyDown={onKeyDown}
                 ref={ref}
                 onFocus={onFocus}
@@ -619,7 +637,7 @@ const useIntersectionObserver = (callback: () => void): ((element: HTMLDivElemen
         }
     };
 
-    const observerRef = useRef<IntersectionObserver>();
+    const observerRef = useRef<IntersectionObserver>(undefined);
     return (element: HTMLDivElement) => {
         if (observerRef.current) {
             observerRef.current.disconnect();
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 1424df2c98a80a0f5526d52da397a40a4df7d8c6..59ec657b0242b3fbfd3f7a90b0ccf2f9cb766da1 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, RoomType, JoinRule, Preset, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { EventType, RoomType, JoinRule, Preset, type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import React, { useCallback, useContext, useRef, useState } from "react";
+import React, { type JSX, useCallback, useContext, useRef, useState } from "react";
 
 import MatrixClientContext from "../../contexts/MatrixClientContext";
-import createRoom, { IOpts } from "../../createRoom";
+import createRoom, { type IOpts } from "../../createRoom";
 import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
 import { Action } from "../../dispatcher/actions";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import * as Email from "../../email";
 import { useEventEmitterState } from "../../hooks/useEventEmitter";
 import { useMyRoomMembership } from "../../hooks/useRoomMembers";
@@ -30,7 +30,7 @@ import { UIComponent } from "../../settings/UIFeature";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import {
     shouldShowSpaceInvite,
     shouldShowSpaceSettings,
@@ -51,7 +51,7 @@ import {
     defaultDmsRenderer,
     defaultRoomsRenderer,
 } from "../views/dialogs/AddExistingToSpaceDialog";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import ErrorBoundary from "../views/elements/ErrorBoundary";
 import Field from "../views/elements/Field";
 import RoomFacePile from "../views/elements/RoomFacePile";
@@ -65,7 +65,7 @@ import { ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
 import MainSplit from "./MainSplit";
 import RightPanel from "./RightPanel";
 import SpaceHierarchy, { showRoom } from "./SpaceHierarchy";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 
 interface IProps {
     space: Room;
@@ -117,7 +117,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
                         <>
                             <IconizedContextMenuOption
                                 label={_t("action|new_room")}
-                                iconClassName="mx_RoomList_iconNewRoom"
+                                iconClassName="mx_LegacyRoomList_iconNewRoom"
                                 onClick={async (e): Promise<void> => {
                                     e.preventDefault();
                                     e.stopPropagation();
@@ -132,7 +132,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
                             {videoRoomsEnabled && (
                                 <IconizedContextMenuOption
                                     label={_t("action|new_video_room")}
-                                    iconClassName="mx_RoomList_iconNewVideoRoom"
+                                    iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
                                     onClick={async (e): Promise<void> => {
                                         e.preventDefault();
                                         e.stopPropagation();
@@ -157,7 +157,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
                     )}
                     <IconizedContextMenuOption
                         label={_t("action|add_existing_room")}
-                        iconClassName="mx_RoomList_iconAddExistingRoom"
+                        iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
@@ -168,7 +168,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
                     {canCreateSpace && (
                         <IconizedContextMenuOption
                             label={_t("room_list|add_space_label")}
-                            iconClassName="mx_RoomList_iconPlus"
+                            iconClassName="mx_LegacyRoomList_iconPlus"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
diff --git a/src/components/structures/SplashPage.tsx b/src/components/structures/SplashPage.tsx
index 1ce0724dabe8bc96613009cba043de7bd978b4fa..86d78fcecc866227324e581273a9038aa5d5c386 100644
--- a/src/components/structures/SplashPage.tsx
+++ b/src/components/structures/SplashPage.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
+import React, { type JSX, type DetailedHTMLProps, type HTMLAttributes, type ReactNode } from "react";
 
 interface Props extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
     className?: string;
diff --git a/src/components/structures/TabbedView.tsx b/src/components/structures/TabbedView.tsx
index 71161fec5253799287d831ed50a7734135e0181d..b01f1605519b627676752c4fc970f5fac866315c 100644
--- a/src/components/structures/TabbedView.tsx
+++ b/src/components/structures/TabbedView.tsx
@@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 
-import { _t, TranslationKey } from "../../languageHandler";
+import { _t, type TranslationKey } from "../../languageHandler";
 import AutoHideScrollbar from "./AutoHideScrollbar";
-import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers";
-import { NonEmptyArray } from "../../@types/common";
+import { PosthogScreenTracker, type ScreenName } from "../../PosthogTrackers";
+import { type NonEmptyArray } from "../../@types/common";
 import { RovingAccessibleButton, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex";
 import { useWindowWidth } from "../../hooks/useWindowWidth";
 
@@ -27,14 +27,14 @@ export class Tab<T extends string> {
      * @param {string} id The tab's ID.
      * @param {string} label The untranslated tab label.
      * @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask.
-     * @param {React.ReactNode} body The JSX for the tab container.
+     * @param {JSX.Element} body The JSX for the tab container.
      * @param {string} screenName The screen name to report to Posthog.
      */
     public constructor(
         public readonly id: T,
         public readonly label: TranslationKey,
         public readonly icon: string | JSX.Element | null,
-        public readonly body: React.ReactNode,
+        public readonly body: JSX.Element,
         public readonly screenName?: ScreenName,
     ) {}
 }
diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx
index f6742d8159987d37e3b5a910406d35f0c360ab37..892dc82c75f5e9ed46f40e7162ac27da80c3cd2f 100644
--- a/src/components/structures/ThreadPanel.tsx
+++ b/src/components/structures/ThreadPanel.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 import React, { useContext, useEffect, useRef, useState } from "react";
-import { EventTimelineSet, Room, Thread } from "matrix-js-sdk/src/matrix";
+import { type EventTimelineSet, type Room, Thread } from "matrix-js-sdk/src/matrix";
 import { IconButton, Tooltip } from "@vector-im/compound-web";
 import { logger } from "matrix-js-sdk/src/logger";
 import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads";
 
 import { Icon as MarkAllThreadsReadIcon } from "../../../res/img/element-icons/check-all.svg";
 import BaseCard from "../views/right_panel/BaseCard";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import MatrixClientContext, { useMatrixClientContext } from "../../contexts/MatrixClientContext";
 import { _t } from "../../languageHandler";
 import { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton";
@@ -23,10 +23,10 @@ import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from "./Conte
 import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
 import TimelinePanel from "./TimelinePanel";
 import { Layout } from "../../settings/enums/Layout";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 import Measured from "../views/elements/Measured";
 import PosthogTrackers from "../../PosthogTrackers";
-import { ButtonEvent } from "../views/elements/AccessibleButton";
+import { type ButtonEvent } from "../views/elements/AccessibleButton";
 import Spinner from "../views/elements/Spinner";
 import { clearRoomNotification } from "../../utils/notifications";
 import EmptyState from "../views/right_panel/EmptyState";
@@ -129,10 +129,10 @@ export const ThreadPanelHeader: React.FC<{
     );
 
     return (
-        <div className="mx_BaseCard_header_title">
+        <div className="mx_ThreadPanelHeader">
             <Tooltip label={_t("threads|mark_all_read")}>
-                <IconButton onClick={onMarkAllThreadsReadClick} size="24px">
-                    <MarkAllThreadsReadIcon />
+                <IconButton onClick={onMarkAllThreadsReadClick} size="28px">
+                    <MarkAllThreadsReadIcon height={20} width={20} />
                 </IconButton>
             </Tooltip>
             <div className="mx_ThreadPanel_vertical_separator" />
@@ -192,9 +192,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
             narrow={narrow}
         >
             <BaseCard
-                header={
-                    hasThreads && <ThreadPanelHeader filterOption={filterOption} setFilterOption={setFilterOption} />
-                }
+                header={_t("common|threads")}
                 id="thread-panel"
                 className="mx_ThreadPanel"
                 ariaLabelledBy="thread-panel-tab"
@@ -204,6 +202,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
                 ref={card}
                 closeButtonRef={closeButonRef}
             >
+                {hasThreads && <ThreadPanelHeader filterOption={filterOption} setFilterOption={setFilterOption} />}
                 <Measured sensor={card} onMeasurement={setNarrow} />
                 {timelineSet ? (
                     <TimelinePanel
diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx
index f7c34c41122cd8c3f3a2f929850335c539160d16..c1c4b3ff576d02fd76b66cb35a2d2b0f1cd407ab 100644
--- a/src/components/structures/ThreadView.tsx
+++ b/src/components/structures/ThreadView.tsx
@@ -6,31 +6,31 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, KeyboardEvent } from "react";
+import React, { type JSX, createRef, type KeyboardEvent } from "react";
 import {
-    Thread,
+    type Thread,
     THREAD_RELATION_TYPE,
     ThreadEvent,
-    Room,
+    type Room,
     RoomEvent,
-    IEventRelation,
-    MatrixEvent,
+    type IEventRelation,
+    type MatrixEvent,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import classNames from "classnames";
 
 import BaseCard from "../views/right_panel/BaseCard";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import MessageComposer from "../views/rooms/MessageComposer";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 import { Layout } from "../../settings/enums/Layout";
 import TimelinePanel from "./TimelinePanel";
 import dis from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { Action } from "../../dispatcher/actions";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
-import { E2EStatus } from "../../utils/ShieldUtils";
+import { type E2EStatus } from "../../utils/ShieldUtils";
 import EditorStateTransfer from "../../utils/EditorStateTransfer";
 import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
 import ContentMessages from "../../ContentMessages";
@@ -39,18 +39,18 @@ import { _t } from "../../languageHandler";
 import ThreadListContextMenu from "../views/context_menus/ThreadListContextMenu";
 import RightPanelStore from "../../stores/right-panel/RightPanelStore";
 import SettingsStore from "../../settings/SettingsStore";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import FileDropTarget from "./FileDropTarget";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
 import Measured from "../views/elements/Measured";
 import PosthogTrackers from "../../PosthogTrackers";
-import { ButtonEvent } from "../views/elements/AccessibleButton";
+import { type ButtonEvent } from "../views/elements/AccessibleButton";
 import Spinner from "../views/elements/Spinner";
-import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
+import { type ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
 import Heading from "../views/typography/Heading";
 import { SdkContextClass } from "../../contexts/SDKContext";
-import { ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
+import { type ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
 import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
 
 interface IProps {
@@ -86,8 +86,8 @@ export default class ThreadView extends React.Component<IProps, IState> {
     // Set by setEventId in ctor.
     private eventId!: string;
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.setEventId(this.props.mxEvent);
         const thread = this.props.room.getThread(this.eventId) ?? undefined;
diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx
index 31ce65cfa76b82bfa0376a73265d6527a65b7f66..8346a0ab3195ea4a43b06d1745184de853156f7a 100644
--- a/src/components/structures/TimelinePanel.tsx
+++ b/src/components/structures/TimelinePanel.tsx
@@ -6,35 +6,35 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode } from "react";
+import React, { createRef, type ReactNode } from "react";
 import {
-    Room,
+    type Room,
     RoomEvent,
-    RoomMember,
+    type RoomMember,
     RoomMemberEvent,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
-    EventTimelineSet,
-    IRoomTimelineData,
+    type EventTimelineSet,
+    type IRoomTimelineData,
     Direction,
     EventTimeline,
     EventType,
-    RelationType,
+    type RelationType,
     ClientEvent,
-    MatrixClient,
-    Relations,
-    MatrixError,
-    SyncState,
+    type MatrixClient,
+    type Relations,
+    type MatrixError,
+    type SyncState,
     TimelineWindow,
     Thread,
     ThreadEvent,
     ReceiptType,
 } from "matrix-js-sdk/src/matrix";
-import { debounce, findLastIndex } from "lodash";
+import { debounce } from "lodash";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import SettingsStore from "../../settings/SettingsStore";
-import { Layout } from "../../settings/enums/Layout";
+import { type Layout } from "../../settings/enums/Layout";
 import { _t } from "../../languageHandler";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
@@ -45,15 +45,16 @@ import { Action } from "../../dispatcher/actions";
 import Timer from "../../utils/Timer";
 import shouldHideEvent from "../../shouldHideEvent";
 import MessagePanel from "./MessagePanel";
-import { IScrollState } from "./ScrollPanel";
-import { ActionPayload } from "../../dispatcher/payloads";
-import ResizeNotifier from "../../utils/ResizeNotifier";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type IScrollState } from "./ScrollPanel";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 import Spinner from "../views/elements/Spinner";
-import EditorStateTransfer from "../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../utils/EditorStateTransfer";
 import ErrorDialog from "../views/dialogs/ErrorDialog";
-import LegacyCallEventGrouper, { buildLegacyCallEventGroupers } from "./LegacyCallEventGrouper";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import type LegacyCallEventGrouper from "./LegacyCallEventGrouper";
+import { buildLegacyCallEventGroupers } from "./LegacyCallEventGrouper";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import { getKeyBindingsManager } from "../../KeyBindingsManager";
 import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
 import { haveRendererForEvent } from "../../events/EventTileFactory";
@@ -72,25 +73,12 @@ const debuglog = (...args: any[]): void => {
     }
 };
 
-const overlaysBefore = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
-    overlayEvent.localTimestamp < mainEvent.localTimestamp;
-
-const overlaysAfter = (overlayEvent: MatrixEvent, mainEvent: MatrixEvent): boolean =>
-    overlayEvent.localTimestamp >= mainEvent.localTimestamp;
-
 interface IProps {
     // The js-sdk EventTimelineSet object for the timeline sequence we are
     // representing.  This may or may not have a room, depending on what it's
     // a timeline representing.  If it has a room, we maintain RRs etc for
     // that room.
     timelineSet: EventTimelineSet;
-    // overlay events from a second timelineset on the main timeline
-    // added to support virtual rooms
-    // events from the overlay timeline set will be added by localTimestamp
-    // into the main timeline
-    overlayTimelineSet?: EventTimelineSet;
-    // filter events from overlay timeline
-    overlayTimelineSetFilter?: (event: MatrixEvent) => boolean;
     showReadReceipts?: boolean;
     // Enable managing RRs and RMs. These require the timelineSet to have a room.
     manageReadReceipts?: boolean;
@@ -250,7 +238,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
     private readonly messagePanel = createRef<MessagePanel>();
     private dispatcherRef?: string;
     private timelineWindow?: TimelineWindow;
-    private overlayTimelineWindow?: TimelineWindow;
     private unmounted = false;
     private readReceiptActivityTimer: Timer | null = null;
     private readMarkerActivityTimer: Timer | null = null;
@@ -348,16 +335,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
         const differentEventId = prevProps.eventId != this.props.eventId;
         const differentHighlightedEventId = prevProps.highlightedEventId != this.props.highlightedEventId;
         const differentAvoidJump = prevProps.eventScrollIntoView && !this.props.eventScrollIntoView;
-        const differentOverlayTimeline = prevProps.overlayTimelineSet !== this.props.overlayTimelineSet;
         if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
             logger.log(
                 `TimelinePanel switching to eventId ${this.props.eventId} (was ${prevProps.eventId}), ` +
                     `scrollIntoView: ${this.props.eventScrollIntoView} (was ${prevProps.eventScrollIntoView})`,
             );
             this.initTimeline(this.props);
-        } else if (differentOverlayTimeline) {
-            logger.log(`TimelinePanel updating overlay timeline.`);
-            this.initTimeline(this.props);
         }
     }
 
@@ -508,24 +491,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
         // this particular event should be the first or last to be unpaginated.
         const eventId = scrollToken;
 
-        // The event in question could belong to either the main timeline or
-        // overlay timeline; let's check both
         const mainEvents = this.timelineWindow!.getEvents();
-        const overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
 
-        let marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
-        let overlayMarker: number;
-        if (marker === -1) {
-            // The event must be from the overlay timeline instead
-            overlayMarker = overlayEvents.findIndex((ev) => ev.getId() === eventId);
-            marker = backwards
-                ? findLastIndex(mainEvents, (ev) => overlaysAfter(overlayEvents[overlayMarker], ev))
-                : mainEvents.findIndex((ev) => overlaysBefore(overlayEvents[overlayMarker], ev));
-        } else {
-            overlayMarker = backwards
-                ? findLastIndex(overlayEvents, (ev) => overlaysBefore(ev, mainEvents[marker]))
-                : overlayEvents.findIndex((ev) => overlaysAfter(ev, mainEvents[marker]));
-        }
+        const marker = mainEvents.findIndex((ev) => ev.getId() === eventId);
 
         // The number of events to unpaginate from the main timeline
         let count: number;
@@ -535,24 +503,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
             count = backwards ? marker + 1 : mainEvents.length - marker;
         }
 
-        // The number of events to unpaginate from the overlay timeline
-        let overlayCount: number;
-        if (overlayMarker === -1) {
-            overlayCount = 0;
-        } else {
-            overlayCount = backwards ? overlayMarker + 1 : overlayEvents.length - overlayMarker;
-        }
-
         if (count > 0) {
             debuglog("Unpaginating", count, "in direction", dir);
             this.timelineWindow!.unpaginate(count, backwards);
         }
 
-        if (overlayCount > 0) {
-            debuglog("Unpaginating", count, "from overlay timeline in direction", dir);
-            this.overlayTimelineWindow!.unpaginate(overlayCount, backwards);
-        }
-
         const { events, liveEvents } = this.getEvents();
         this.buildLegacyCallEventGroupers(events);
         this.setState({
@@ -609,10 +564,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
                 return false;
             }
 
-            if (this.overlayTimelineWindow) {
-                await this.extendOverlayWindowToCoverMainWindow();
-            }
-
             debuglog("paginate complete backwards:" + backwards + "; success:" + r);
 
             const { events, liveEvents } = this.getEvents();
@@ -704,10 +655,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
         data: IRoomTimelineData,
     ): void => {
         // ignore events for other timeline sets
-        if (
-            data.timeline.getTimelineSet() !== this.props.timelineSet &&
-            data.timeline.getTimelineSet() !== this.props.overlayTimelineSet
-        ) {
+        if (data.timeline.getTimelineSet() !== this.props.timelineSet) {
             return;
         }
 
@@ -747,69 +695,60 @@ class TimelinePanel extends React.Component<IProps, IState> {
         // timeline window.
         //
         // see https://github.com/vector-im/vector-web/issues/1035
-        this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false)
-            .then(() => {
-                if (this.overlayTimelineWindow) {
-                    return this.overlayTimelineWindow.paginate(EventTimeline.FORWARDS, 1, false);
-                }
-            })
-            .then(() => {
-                if (this.unmounted) {
-                    return;
-                }
-
-                const { events, liveEvents } = this.getEvents();
-                this.buildLegacyCallEventGroupers(events);
-                const lastLiveEvent = liveEvents[liveEvents.length - 1];
+        this.timelineWindow!.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
+            if (this.unmounted) {
+                return;
+            }
 
-                const updatedState: Partial<IState> = {
-                    events,
-                    liveEvents,
-                };
+            const { events, liveEvents } = this.getEvents();
+            this.buildLegacyCallEventGroupers(events);
+            const lastLiveEvent = liveEvents[liveEvents.length - 1];
 
-                let callRMUpdated = false;
-                if (this.props.manageReadMarkers) {
-                    // when a new event arrives when the user is not watching the
-                    // window, but the window is in its auto-scroll mode, make sure the
-                    // read marker is visible.
-                    //
-                    // We ignore events we have sent ourselves; we don't want to see the
-                    // read-marker when a remote echo of an event we have just sent takes
-                    // more than the timeout on userActiveRecently.
-                    //
-                    const myUserId = MatrixClientPeg.safeGet().credentials.userId;
-                    callRMUpdated = false;
-                    if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
-                        updatedState.readMarkerVisible = true;
-                    } else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
-                        // we know we're stuckAtBottom, so we can advance the RM
-                        // immediately, to save a later render cycle
-
-                        this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
-                        updatedState.readMarkerVisible = false;
-                        updatedState.readMarkerEventId = lastLiveEvent.getId();
-                        callRMUpdated = true;
-                    }
+            const updatedState: Partial<IState> = {
+                events,
+                liveEvents,
+            };
+
+            let callRMUpdated = false;
+            if (this.props.manageReadMarkers) {
+                // when a new event arrives when the user is not watching the
+                // window, but the window is in its auto-scroll mode, make sure the
+                // read marker is visible.
+                //
+                // We ignore events we have sent ourselves; we don't want to see the
+                // read-marker when a remote echo of an event we have just sent takes
+                // more than the timeout on userActiveRecently.
+                //
+                const myUserId = MatrixClientPeg.safeGet().credentials.userId;
+                callRMUpdated = false;
+                if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
+                    updatedState.readMarkerVisible = true;
+                } else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
+                    // we know we're stuckAtBottom, so we can advance the RM
+                    // immediately, to save a later render cycle
+
+                    this.setReadMarker(lastLiveEvent.getId() ?? null, lastLiveEvent.getTs(), true);
+                    updatedState.readMarkerVisible = false;
+                    updatedState.readMarkerEventId = lastLiveEvent.getId();
+                    callRMUpdated = true;
                 }
+            }
 
-                this.setState(updatedState as IState, () => {
-                    this.messagePanel.current?.updateTimelineMinHeight();
-                    if (callRMUpdated) {
-                        this.props.onReadMarkerUpdated?.();
-                    }
-                });
+            this.setState(updatedState as IState, () => {
+                this.messagePanel.current?.updateTimelineMinHeight();
+                if (callRMUpdated) {
+                    this.props.onReadMarkerUpdated?.();
+                }
             });
+        });
     };
 
     private hasTimelineSetFor(roomId: string | undefined): boolean {
-        return (
-            (roomId !== undefined && roomId === this.props.timelineSet.room?.roomId) ||
-            roomId === this.props.overlayTimelineSet?.room?.roomId
-        );
+        return roomId !== undefined && roomId === this.props.timelineSet.room?.roomId;
     }
 
     private onRoomTimelineReset = (room: Room | undefined, timelineSet: EventTimelineSet): void => {
-        if (timelineSet !== this.props.timelineSet && timelineSet !== this.props.overlayTimelineSet) return;
+        if (timelineSet !== this.props.timelineSet) return;
 
         if (this.canResetTimeline()) {
             this.loadTimeline();
@@ -1474,48 +1413,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
         });
     }
 
-    private async extendOverlayWindowToCoverMainWindow(): Promise<void> {
-        const mainWindow = this.timelineWindow!;
-        const overlayWindow = this.overlayTimelineWindow!;
-        const mainEvents = mainWindow.getEvents();
-
-        if (mainEvents.length > 0) {
-            let paginationRequests: Promise<unknown>[];
-
-            // Keep paginating until the main window is covered
-            do {
-                paginationRequests = [];
-                const overlayEvents = overlayWindow.getEvents();
-
-                if (
-                    overlayWindow.canPaginate(EventTimeline.BACKWARDS) &&
-                    (overlayEvents.length === 0 ||
-                        overlaysAfter(overlayEvents[0], mainEvents[0]) ||
-                        !mainWindow.canPaginate(EventTimeline.BACKWARDS))
-                ) {
-                    // Paginating backwards could reveal more events to be overlaid in the main window
-                    paginationRequests.push(
-                        this.onPaginationRequest(overlayWindow, EventTimeline.BACKWARDS, PAGINATE_SIZE),
-                    );
-                }
-
-                if (
-                    overlayWindow.canPaginate(EventTimeline.FORWARDS) &&
-                    (overlayEvents.length === 0 ||
-                        overlaysBefore(overlayEvents.at(-1)!, mainEvents.at(-1)!) ||
-                        !mainWindow.canPaginate(EventTimeline.FORWARDS))
-                ) {
-                    // Paginating forwards could reveal more events to be overlaid in the main window
-                    paginationRequests.push(
-                        this.onPaginationRequest(overlayWindow, EventTimeline.FORWARDS, PAGINATE_SIZE),
-                    );
-                }
-
-                await Promise.all(paginationRequests);
-            } while (paginationRequests.length > 0);
-        }
-    }
-
     /**
      * (re)-load the event timeline, and initialise the scroll state, centered
      * around the given event.
@@ -1535,9 +1432,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
     private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number, scrollIntoView = true): void {
         const cli = MatrixClientPeg.safeGet();
         this.timelineWindow = new TimelineWindow(cli, this.props.timelineSet, { windowLimit: this.props.timelineCap });
-        this.overlayTimelineWindow = this.props.overlayTimelineSet
-            ? new TimelineWindow(cli, this.props.overlayTimelineSet, { windowLimit: this.props.timelineCap })
-            : undefined;
 
         const onLoaded = (): void => {
             if (this.unmounted) return;
@@ -1553,14 +1447,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
 
             this.setState(
                 {
-                    canBackPaginate:
-                        (this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ||
-                            this.overlayTimelineWindow?.canPaginate(EventTimeline.BACKWARDS)) ??
-                        false,
-                    canForwardPaginate:
-                        (this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ||
-                            this.overlayTimelineWindow?.canPaginate(EventTimeline.FORWARDS)) ??
-                        false,
+                    canBackPaginate: this.timelineWindow?.canPaginate(EventTimeline.BACKWARDS) ?? false,
+                    canForwardPaginate: this.timelineWindow?.canPaginate(EventTimeline.FORWARDS) ?? false,
                     timelineLoading: false,
                 },
                 () => {
@@ -1617,11 +1505,13 @@ class TimelinePanel extends React.Component<IProps, IState> {
                 description = _t("timeline|load_error|unable_to_find");
             }
 
-            Modal.createDialog(ErrorDialog, {
+            const { finished } = Modal.createDialog(ErrorDialog, {
                 title: _t("timeline|load_error|title"),
                 description,
-                onFinished,
             });
+            if (onFinished) {
+                finished.then(onFinished);
+            }
         };
 
         // if we already have the event in question, TimelineWindow.load
@@ -1635,7 +1525,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
         // This is a hot-path optimization by skipping a promise tick
         // by repeating a no-op sync branch in
         // TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
-        if (this.props.timelineSet.getTimelineForEvent(eventId) && !this.overlayTimelineWindow) {
+        if (this.props.timelineSet.getTimelineForEvent(eventId)) {
             // if we've got an eventId, and the timeline exists, we can skip
             // the promise tick.
             this.timelineWindow.load(eventId, INITIAL_SIZE);
@@ -1644,14 +1534,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
             return;
         }
 
-        const prom = this.timelineWindow.load(eventId, INITIAL_SIZE).then(async (): Promise<void> => {
-            if (this.overlayTimelineWindow) {
-                // TODO: use timestampToEvent to load the overlay timeline
-                // with more correct position when main TL eventId is truthy
-                await this.overlayTimelineWindow.load(undefined, INITIAL_SIZE);
-                await this.extendOverlayWindowToCoverMainWindow();
-            }
-        });
+        const prom = this.timelineWindow.load(eventId, INITIAL_SIZE);
         this.buildLegacyCallEventGroupers();
         this.setState({
             events: [],
@@ -1682,38 +1565,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
         this.reloadEvents();
     }
 
-    // get the list of events from the timeline windows and the pending event list
+    // get the list of events from the timeline window and the pending event list
     private getEvents(): Pick<IState, "events" | "liveEvents"> {
-        const mainEvents = this.timelineWindow!.getEvents();
-        let overlayEvents = this.overlayTimelineWindow?.getEvents() ?? [];
-        if (this.props.overlayTimelineSetFilter !== undefined) {
-            overlayEvents = overlayEvents.filter(this.props.overlayTimelineSetFilter);
-        }
-
-        // maintain the main timeline event order as returned from the HS
-        // merge overlay events at approximately the right position based on local timestamp
-        const events = overlayEvents.reduce(
-            (acc: MatrixEvent[], overlayEvent: MatrixEvent) => {
-                // find the first main tl event with a later timestamp
-                const index = acc.findIndex((event) => overlaysBefore(overlayEvent, event));
-                // insert overlay event into timeline at approximately the right place
-                // if it's beyond the edge of the main window, hide it so that expanding
-                // the main window doesn't cause new events to pop in and change its position
-                if (index === -1) {
-                    if (!this.timelineWindow!.canPaginate(EventTimeline.FORWARDS)) {
-                        acc.push(overlayEvent);
-                    }
-                } else if (index === 0) {
-                    if (!this.timelineWindow!.canPaginate(EventTimeline.BACKWARDS)) {
-                        acc.unshift(overlayEvent);
-                    }
-                } else {
-                    acc.splice(index, 0, overlayEvent);
-                }
-                return acc;
-            },
-            [...mainEvents],
-        );
+        const events = this.timelineWindow!.getEvents();
 
         // We want the last event to be decrypted first
         const client = MatrixClientPeg.safeGet();
diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx
index 02db99a0e05326689d11ddc3620871e77474aca7..649cd1dc59493421287f4910bc1bcc4200b9d735 100644
--- a/src/components/structures/ToastContainer.tsx
+++ b/src/components/structures/ToastContainer.tsx
@@ -6,19 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import classNames from "classnames";
 import { Text } from "@vector-im/compound-web";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
-import ToastStore, { IToast } from "../../stores/ToastStore";
+import ToastStore, { type IToast } from "../../stores/ToastStore";
 
 interface IState {
     toasts: IToast<any>[];
     countSeen: number;
 }
 
-export default class ToastContainer extends React.Component<{}, IState> {
-    public constructor(props: {}) {
+export default class ToastContainer extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
         this.state = {
             toasts: ToastStore.sharedInstance().getToasts(),
diff --git a/src/components/structures/UploadBar.tsx b/src/components/structures/UploadBar.tsx
index 76188627984decec65cfc26b00fd86101b24b733..a28ff2ba44198eb40a5089aa1191a59ea267638c 100644
--- a/src/components/structures/UploadBar.tsx
+++ b/src/components/structures/UploadBar.tsx
@@ -7,18 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room, IEventRelation } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type Room, type IEventRelation } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
 
 import ContentMessages from "../../ContentMessages";
 import dis from "../../dispatcher/dispatcher";
 import { _t } from "../../languageHandler";
 import { Action } from "../../dispatcher/actions";
 import ProgressBar from "../views/elements/ProgressBar";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
-import { RoomUpload } from "../../models/RoomUpload";
-import { ActionPayload } from "../../dispatcher/payloads";
-import { UploadPayload } from "../../dispatcher/payloads/UploadPayload";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
+import { type RoomUpload } from "../../models/RoomUpload";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import { type UploadPayload } from "../../dispatcher/payloads/UploadPayload";
 import { fileSize } from "../../utils/FileUtils";
 
 interface IProps {
diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx
index e2663523935c10cbb070a50012b71d03b86ca597..67af61f7ac62a6cd4593389c0745ec80cfefdff5 100644
--- a/src/components/structures/UserMenu.tsx
+++ b/src/components/structures/UserMenu.tsx
@@ -6,24 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, createRef, type ReactNode } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { Action } from "../../dispatcher/actions";
 import { _t } from "../../languageHandler";
-import { ChevronFace, ContextMenuButton, MenuProps } from "./ContextMenu";
+import { ChevronFace, ContextMenuButton, type MenuProps } from "./ContextMenu";
 import { UserTab } from "../views/dialogs/UserTab";
-import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
+import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
 import FeedbackDialog from "../views/dialogs/FeedbackDialog";
 import Modal from "../../Modal";
 import LogoutDialog, { shouldShowLogoutDialog } from "../views/dialogs/LogoutDialog";
 import SettingsStore from "../../settings/SettingsStore";
 import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme";
 import { RovingAccessibleButton } from "../../accessibility/RovingTabIndex";
-import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
 import SdkConfig from "../../SdkConfig";
 import { getHomePageUrl } from "../../utils/pages";
 import { OwnProfileStore } from "../../stores/OwnProfileStore";
@@ -39,7 +39,7 @@ import SpaceStore from "../../stores/spaces/SpaceStore";
 import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
 import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
 import PosthogTrackers from "../../PosthogTrackers";
-import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
+import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
 import { SDKContext } from "../../contexts/SDKContext";
 import { shouldShowFeedback } from "../../utils/Feedback";
 import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg";
@@ -81,10 +81,10 @@ export default class UserMenu extends React.Component<IProps, IState> {
     private dispatcherRef?: string;
     private themeWatcherRef?: string;
     private readonly dndWatcherRef?: string;
-    private buttonRef: React.RefObject<HTMLButtonElement> = createRef();
+    private buttonRef = createRef<HTMLButtonElement>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             contextMenuPosition: null,
@@ -370,6 +370,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
             ? toRightOf(this.state.contextMenuPosition)
             : below(this.state.contextMenuPosition);
 
+        const userIdentifierString = UserIdentifierCustomisations.getDisplayUserIdentifier(
+            MatrixClientPeg.safeGet().getSafeUserId(),
+            {
+                withDisplayName: true,
+            },
+        );
+
         return (
             <IconizedContextMenu {...position} onFinished={this.onCloseMenu} className="mx_UserMenu_contextMenu">
                 <div className="mx_UserMenu_contextMenu_header">
@@ -377,13 +384,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
                         <span className="mx_UserMenu_contextMenu_displayName">
                             {OwnProfileStore.instance.displayName}
                         </span>
-                        <span className="mx_UserMenu_contextMenu_userId">
-                            {UserIdentifierCustomisations.getDisplayUserIdentifier(
-                                MatrixClientPeg.safeGet().getSafeUserId(),
-                                {
-                                    withDisplayName: true,
-                                },
-                            )}
+                        <span className="mx_UserMenu_contextMenu_userId" title={userIdentifierString || ""}>
+                            {userIdentifierString}
                         </span>
                     </div>
 
diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx
index 7325f607ad8bec605f79d3ba73f76f8cf7b798d3..58aed9932b9e33c4603bb6a2db28c606d9331ff5 100644
--- a/src/components/structures/UserView.tsx
+++ b/src/components/structures/UserView.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, RoomMember, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { MatrixEvent, RoomMember, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import Modal from "../../Modal";
 import { _t } from "../../languageHandler";
@@ -15,7 +15,7 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
 import MainSplit from "./MainSplit";
 import RightPanel from "./RightPanel";
 import Spinner from "../views/elements/Spinner";
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
 import HomePage from "./HomePage.tsx";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
@@ -34,8 +34,8 @@ export default class UserView extends React.Component<IProps, IState> {
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
         this.state = {
             loading: true,
         };
diff --git a/src/components/structures/ViewSource.tsx b/src/components/structures/ViewSource.tsx
index eca37842d7f0d63dd3bb0450597e48dead2d5654..b2e003284bb61c32799799d57d4e4850d13c86b3 100644
--- a/src/components/structures/ViewSource.tsx
+++ b/src/components/structures/ViewSource.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import SyntaxHighlight from "../views/elements/SyntaxHighlight";
 import { _t } from "../../languageHandler";
diff --git a/src/components/structures/WaitingForThirdPartyRoomView.tsx b/src/components/structures/WaitingForThirdPartyRoomView.tsx
index 7787a03bf26729d7f7a5df71863562cbf6bfe927..3983b286f1244c40a67d043f607553a66d11703b 100644
--- a/src/components/structures/WaitingForThirdPartyRoomView.tsx
+++ b/src/components/structures/WaitingForThirdPartyRoomView.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { RefObject } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type RefObject } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import ResizeNotifier from "../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../utils/ResizeNotifier";
 import ErrorBoundary from "../views/elements/ErrorBoundary";
-import RoomHeader from "../views/rooms/RoomHeader";
+import RoomHeader from "../views/rooms/RoomHeader/RoomHeader.tsx";
 import ScrollPanel from "./ScrollPanel";
 import EventTileBubble from "../views/messages/EventTileBubble";
 import NewRoomIntro from "../views/rooms/NewRoomIntro";
@@ -21,7 +21,7 @@ import SdkConfig from "../../SdkConfig";
 import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
 
 interface Props {
-    roomView: RefObject<HTMLElement>;
+    roomView: RefObject<HTMLElement | null>;
     resizeNotifier: ResizeNotifier;
     inviteEvent: MatrixEvent;
 }
diff --git a/src/components/structures/auth/CompleteSecurity.tsx b/src/components/structures/auth/CompleteSecurity.tsx
index a0f2be88367533713ee4dd5ab10447dd7cfb7ce7..5bade7b24a0e660eb81fd782b06bf8f0c2c879b9 100644
--- a/src/components/structures/auth/CompleteSecurity.tsx
+++ b/src/components/structures/auth/CompleteSecurity.tsx
@@ -78,9 +78,6 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
         } else if (phase === Phase.Busy) {
             icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
             title = _t("encryption|verification|after_new_login|verify_this_device");
-        } else if (phase === Phase.ConfirmReset) {
-            icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
-            title = _t("encryption|verification|after_new_login|reset_confirmation");
         } else if (phase === Phase.Finished) {
             // SetupEncryptionBody will take care of calling onFinished, we don't need to do anything
         } else {
@@ -90,7 +87,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
         const forceVerification = SdkConfig.get("force_verification");
 
         let skipButton;
-        if (!forceVerification && (phase === Phase.Intro || phase === Phase.ConfirmReset)) {
+        if (!forceVerification && phase === Phase.Intro) {
             skipButton = (
                 <AccessibleButton
                     onClick={this.onSkipClick}
diff --git a/src/components/structures/auth/ConfirmSessionLockTheftView.tsx b/src/components/structures/auth/ConfirmSessionLockTheftView.tsx
index b71b627a5a611db1a0eabe79cf043023ef043484..097a6aa5b74b1b71a82c052ef9c4bab737a2df8d 100644
--- a/src/components/structures/auth/ConfirmSessionLockTheftView.tsx
+++ b/src/components/structures/auth/ConfirmSessionLockTheftView.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { _t } from "../../../languageHandler";
 import SdkConfig from "../../../SdkConfig";
diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx
index ecf888f6e9d142846ca75cee10a04b0dbee347e7..171bd955a17ec0ef031bfa4d9c5ca6c3c65215ee 100644
--- a/src/components/structures/auth/ForgotPassword.tsx
+++ b/src/components/structures/auth/ForgotPassword.tsx
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import { sleep } from "matrix-js-sdk/src/utils";
 import { LockSolidIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
@@ -23,11 +23,11 @@ import AuthHeader from "../../views/auth/AuthHeader";
 import AuthBody from "../../views/auth/AuthBody";
 import PassphraseConfirmField from "../../views/auth/PassphraseConfirmField";
 import StyledCheckbox from "../../views/elements/StyledCheckbox";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import QuestionDialog from "../../views/dialogs/QuestionDialog";
 import { EnterEmail } from "./forgot-password/EnterEmail";
 import { CheckEmail } from "./forgot-password/CheckEmail";
-import Field from "../../views/elements/Field";
+import type Field from "../../views/elements/Field";
 import { ErrorMessage } from "../ErrorMessage";
 import { VerifyEmailModal } from "./forgot-password/VerifyEmailModal";
 import Spinner from "../../views/elements/Spinner";
@@ -388,7 +388,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
                                 label={_td("auth|change_password_new_label")}
                                 value={this.state.password}
                                 minScore={PASSWORD_MIN_SCORE}
-                                fieldRef={(field) => (this.fieldPassword = field)}
+                                fieldRef={(field) => {
+                                    this.fieldPassword = field;
+                                }}
                                 onChange={this.onInputChanged.bind(this, "password")}
                                 autoComplete="new-password"
                             />
@@ -399,7 +401,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
                                 labelInvalid={_td("auth|reset_password|passwords_mismatch")}
                                 value={this.state.password2}
                                 password={this.state.password}
-                                fieldRef={(field) => (this.fieldPasswordConfirm = field)}
+                                fieldRef={(field) => {
+                                    this.fieldPasswordConfirm = field;
+                                }}
                                 onChange={this.onInputChanged.bind(this, "password2")}
                                 autoComplete="new-password"
                             />
diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx
index b4b41f0515f69160adcff9d1f0e9b4b90ec7e336..9aca0046f2501e76d164299cc36a4ca3649fee93 100644
--- a/src/components/structures/auth/Login.tsx
+++ b/src/components/structures/auth/Login.tsx
@@ -6,20 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import classNames from "classnames";
 import { logger } from "matrix-js-sdk/src/logger";
-import { SSOFlow, SSOAction } from "matrix-js-sdk/src/matrix";
+import { type SSOFlow, SSOAction } from "matrix-js-sdk/src/matrix";
 
 import { _t, UserFriendlyError } from "../../../languageHandler";
-import Login, { ClientLoginFlow, OidcNativeFlow } from "../../../Login";
+import Login, { type ClientLoginFlow, type OidcNativeFlow } from "../../../Login";
 import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils";
 import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
 import AuthPage from "../../views/auth/AuthPage";
 import PlatformPeg from "../../../PlatformPeg";
 import SettingsStore from "../../../settings/SettingsStore";
 import { UIFeature } from "../../../settings/UIFeature";
-import { IMatrixClientCreds } from "../../../MatrixClientPeg";
+import { type IMatrixClientCreds } from "../../../MatrixClientPeg";
 import PasswordLogin from "../../views/auth/PasswordLogin";
 import InlineSpinner from "../../views/elements/InlineSpinner";
 import Spinner from "../../views/elements/Spinner";
@@ -27,8 +27,8 @@ import SSOButtons from "../../views/elements/SSOButtons";
 import ServerPicker from "../../views/elements/ServerPicker";
 import AuthBody from "../../views/auth/AuthBody";
 import AuthHeader from "../../views/auth/AuthHeader";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import { filterBoolean } from "../../../utils/arrays";
 import { startOidcLogin } from "../../../utils/oidc/authorize";
 
diff --git a/src/components/structures/auth/LoginSplashView.tsx b/src/components/structures/auth/LoginSplashView.tsx
index 3d68a12e8d4e59837fbadf86957fa0499b6ed485..aae7980185ffb233ca24cf8b4e27dd18e8839551 100644
--- a/src/components/structures/auth/LoginSplashView.tsx
+++ b/src/components/structures/auth/LoginSplashView.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
 import { messageForSyncError } from "../../../utils/ErrorUtils";
 import Spinner from "../../views/elements/Spinner";
 import ProgressBar from "../../views/elements/ProgressBar";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
 import { _t } from "../../../languageHandler";
 import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
 import SdkConfig from "../../../SdkConfig";
@@ -43,13 +43,13 @@ type MigrationState = {
 /**
  * The view that is displayed after we have logged in, before the first /sync is completed.
  */
-export function LoginSplashView(props: Props): React.JSX.Element {
+export function LoginSplashView(props: Props): JSX.Element {
     const migrationState = useTypedEventEmitterState(
         props.matrixClient,
         CryptoEvent.LegacyCryptoStoreMigrationProgress,
         (progress?: number, total?: number): MigrationState => ({ progress: progress ?? -1, totalSteps: total ?? -1 }),
     );
-    let errorBox: React.JSX.Element | undefined;
+    let errorBox: JSX.Element | undefined;
     if (props.syncError) {
         errorBox = <div className="mx_LoginSplashView_syncError">{messageForSyncError(props.syncError)}</div>;
     }
diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx
index 1dc6f57bc715121995ab798001926a517e991f8d..a5c713b3ea90b0f0788c5eeb9c9f3cf9421847cb 100644
--- a/src/components/structures/auth/Registration.tsx
+++ b/src/components/structures/auth/Registration.tsx
@@ -9,18 +9,18 @@ Please see LICENSE files in the repository root for full details.
 import {
     AuthType,
     createClient,
-    IAuthData,
-    AuthDict,
-    IInputs,
+    type IAuthData,
+    type AuthDict,
+    type IInputs,
     MatrixError,
-    IRegisterRequestParams,
-    IRequestTokenResponse,
-    MatrixClient,
-    SSOFlow,
+    type IRegisterRequestParams,
+    type IRequestTokenResponse,
+    type MatrixClient,
+    type SSOFlow,
     SSOAction,
-    RegisterResponse,
+    type RegisterResponse,
 } from "matrix-js-sdk/src/matrix";
-import React, { Fragment, ReactNode } from "react";
+import React, { type JSX, Fragment, type ReactNode } from "react";
 import classNames from "classnames";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -28,22 +28,22 @@ import { _t } from "../../../languageHandler";
 import { adminContactStrings, messageForResourceLimitError, resourceLimitStrings } from "../../../utils/ErrorUtils";
 import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
 import * as Lifecycle from "../../../Lifecycle";
-import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
+import { type IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
 import AuthPage from "../../views/auth/AuthPage";
-import Login, { OidcNativeFlow } from "../../../Login";
+import Login, { type OidcNativeFlow } from "../../../Login";
 import dis from "../../../dispatcher/dispatcher";
 import SSOButtons from "../../views/elements/SSOButtons";
 import ServerPicker from "../../views/elements/ServerPicker";
 import RegistrationForm from "../../views/auth/RegistrationForm";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
 import AuthBody from "../../views/auth/AuthBody";
 import AuthHeader from "../../views/auth/AuthHeader";
-import InteractiveAuth, { InteractiveAuthCallback } from "../InteractiveAuth";
+import InteractiveAuth, { type InteractiveAuthCallback } from "../InteractiveAuth";
 import Spinner from "../../views/elements/Spinner";
 import { AuthHeaderDisplay } from "./header/AuthHeaderDisplay";
 import { AuthHeaderProvider } from "./header/AuthHeaderProvider";
 import SettingsStore from "../../../settings/SettingsStore";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import { startOidcLogin } from "../../../utils/oidc/authorize";
 
 const debuglog = (...args: any[]): void => {
diff --git a/src/components/structures/auth/SessionLockStolenView.tsx b/src/components/structures/auth/SessionLockStolenView.tsx
index 01193440fc0383d83b1c45dffad242a62c185e98..bb95f1b44a56783f9a615e680b5a100394aad94e 100644
--- a/src/components/structures/auth/SessionLockStolenView.tsx
+++ b/src/components/structures/auth/SessionLockStolenView.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import SplashPage from "../SplashPage";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx
index 1ff0ad412078c127ab4f919586591e8859367df6..b76a623e2c38c2760d06832d2615760b4c487d4a 100644
--- a/src/components/structures/auth/SetupEncryptionBody.tsx
+++ b/src/components/structures/auth/SetupEncryptionBody.tsx
@@ -1,15 +1,15 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { KeyBackupInfo, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
+import React, { type JSX } from "react";
+import { type KeyBackupInfo, type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { SecretStorageKeyDescription } from "matrix-js-sdk/src/secret-storage";
+import { type SecretStorageKeyDescription } from "matrix-js-sdk/src/secret-storage";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -17,8 +17,9 @@ import Modal from "../../../Modal";
 import VerificationRequestDialog from "../../views/dialogs/VerificationRequestDialog";
 import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore";
 import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
 import Spinner from "../../views/elements/Spinner";
+import { ResetIdentityDialog } from "../../views/dialogs/ResetIdentityDialog";
 
 function keyHasPassphrase(keyInfo: SecretStorageKeyDescription): boolean {
     return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations);
@@ -89,14 +90,15 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
         // We need to call onFinished now to close this dialog, and
         // again later to signal that the verification is complete.
         this.props.onFinished();
-        Modal.createDialog(VerificationRequestDialog, {
+        const { finished: verificationFinished } = Modal.createDialog(VerificationRequestDialog, {
             verificationRequestPromise: requestPromise,
             member: cli.getUser(userId) ?? undefined,
-            onFinished: async (): Promise<void> => {
-                const request = await requestPromise;
-                request.cancel();
-                this.props.onFinished();
-            },
+        });
+
+        verificationFinished.then(async () => {
+            const request = await requestPromise;
+            request.cancel();
+            this.props.onFinished();
         });
     };
 
@@ -112,19 +114,15 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
 
     private onResetClick = (ev: ButtonEvent): void => {
         ev.preventDefault();
-        const store = SetupEncryptionStore.sharedInstance();
-        store.reset();
-    };
-
-    private onResetConfirmClick = (): void => {
-        this.props.onFinished();
-        const store = SetupEncryptionStore.sharedInstance();
-        store.resetConfirm();
-    };
-
-    private onResetBackClick = (): void => {
-        const store = SetupEncryptionStore.sharedInstance();
-        store.returnAfterReset();
+        Modal.createDialog(ResetIdentityDialog, {
+            onReset: () => {
+                // The user completed the reset process - close this dialog
+                this.props.onFinished();
+                const store = SetupEncryptionStore.sharedInstance();
+                store.done();
+            },
+            variant: "confirm",
+        });
     };
 
     private onDoneClick = (): void => {
@@ -157,7 +155,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
                         <p>{_t("encryption|verification|no_key_or_device")}</p>
 
                         <div className="mx_CompleteSecurity_actionRow">
-                            <AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
+                            <AccessibleButton kind="primary" onClick={this.onResetClick}>
                                 {_t("encryption|verification|reset_proceed_prompt")}
                             </AccessibleButton>
                         </div>
@@ -246,22 +244,6 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
                     </div>
                 </div>
             );
-        } else if (phase === Phase.ConfirmReset) {
-            return (
-                <div>
-                    <p>{_t("encryption|verification|verify_reset_warning_1")}</p>
-                    <p>{_t("encryption|verification|verify_reset_warning_2")}</p>
-
-                    <div className="mx_CompleteSecurity_actionRow">
-                        <AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
-                            {_t("encryption|verification|reset_proceed_prompt")}
-                        </AccessibleButton>
-                        <AccessibleButton kind="primary" onClick={this.onResetBackClick}>
-                            {_t("action|go_back")}
-                        </AccessibleButton>
-                    </div>
-                </div>
-            );
         } else if (phase === Phase.Busy || phase === Phase.Loading) {
             return <Spinner />;
         } else {
diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx
index 696edc0ad2e5d8731bf3d73a5e606a6b2050a4b6..34fabe46c7e0136e9364276422e694566779edc9 100644
--- a/src/components/structures/auth/SoftLogout.tsx
+++ b/src/components/structures/auth/SoftLogout.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, SyntheticEvent } from "react";
+import React, { type JSX, type ChangeEvent, type SyntheticEvent } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Optional } from "matrix-events-sdk";
-import { LoginFlow, MatrixError, SSOAction, SSOFlow } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
+import { type LoginFlow, MatrixError, SSOAction, type SSOFlow } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
 import * as Lifecycle from "../../../Lifecycle";
 import Modal from "../../../Modal";
-import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
+import { type IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
 import { sendLoginRequest } from "../../../Login";
 import AuthPage from "../../views/auth/AuthPage";
 import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
@@ -66,8 +66,8 @@ export default class SoftLogout extends React.Component<IProps, IState> {
     public static contextType = SDKContext;
     declare public context: React.ContextType<typeof SDKContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             loginView: LoginView.Loading,
@@ -89,13 +89,12 @@ export default class SoftLogout extends React.Component<IProps, IState> {
     }
 
     private onClearAll = (): void => {
-        Modal.createDialog(ConfirmWipeDeviceDialog, {
-            onFinished: (wipeData) => {
-                if (!wipeData) return;
+        const { finished } = Modal.createDialog(ConfirmWipeDeviceDialog);
+        finished.then(([wipeData]) => {
+            if (!wipeData) return;
 
-                logger.log("Clearing data from soft-logged-out session");
-                Lifecycle.logout(this.context.oidcClientStore);
-            },
+            logger.log("Clearing data from soft-logged-out session");
+            Lifecycle.logout(this.context.oidcClientStore);
         });
     };
 
@@ -168,7 +167,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
             return;
         }
 
-        Lifecycle.setLoggedIn(credentials).catch((e) => {
+        Lifecycle.hydrateSession(credentials).catch((e) => {
             logger.error(e);
             this.setState({ busy: false, errorText: _t("auth|failed_soft_logout_auth") });
         });
@@ -204,7 +203,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
             return false;
         }
 
-        return Lifecycle.setLoggedIn(credentials)
+        return Lifecycle.hydrateSession(credentials)
             .then(() => {
                 if (this.props.onTokenLoginCompleted) {
                     this.props.onTokenLoginCompleted();
diff --git a/src/components/structures/auth/forgot-password/CheckEmail.tsx b/src/components/structures/auth/forgot-password/CheckEmail.tsx
index 428bc1702690cef080f8089c012e82a4839c1dfa..64036507de6b654505c6dbbcea4209c6f803575e 100644
--- a/src/components/structures/auth/forgot-password/CheckEmail.tsx
+++ b/src/components/structures/auth/forgot-password/CheckEmail.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
diff --git a/src/components/structures/auth/forgot-password/EnterEmail.tsx b/src/components/structures/auth/forgot-password/EnterEmail.tsx
index d50040552dabd41ebca2d00e5582f8097c217cdd..9e7d6ae5a6d65199bf44776d7b27dab54d8992ec 100644
--- a/src/components/structures/auth/forgot-password/EnterEmail.tsx
+++ b/src/components/structures/auth/forgot-password/EnterEmail.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useRef } from "react";
+import React, { type ReactNode, useRef } from "react";
 import { EmailSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t, _td } from "../../../../languageHandler";
 import EmailField from "../../../views/auth/EmailField";
 import { ErrorMessage } from "../../ErrorMessage";
 import Spinner from "../../../views/elements/Spinner";
-import Field from "../../../views/elements/Field";
-import AccessibleButton, { ButtonEvent } from "../../../views/elements/AccessibleButton";
+import type Field from "../../../views/elements/Field";
+import AccessibleButton, { type ButtonEvent } from "../../../views/elements/AccessibleButton";
 
 interface EnterEmailProps {
     email: string;
diff --git a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx
index cb2c5b3b850f6811614b23c6cd296d873b4d240c..5f57146fe5a8306f598a4d130fbab5059fc01424 100644
--- a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx
+++ b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 import { RestartIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
diff --git a/src/components/structures/auth/header/AuthHeaderContext.tsx b/src/components/structures/auth/header/AuthHeaderContext.tsx
index 4c9d436f0c23f81e922b307af8acfa189ffaefa3..a45233564b9018eb383d926351d6e9a03893c959 100644
--- a/src/components/structures/auth/header/AuthHeaderContext.tsx
+++ b/src/components/structures/auth/header/AuthHeaderContext.tsx
@@ -6,10 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { createContext, Dispatch, ReducerAction, ReducerState } from "react";
+import { createContext, type Dispatch, type Reducer, type ReducerState } from "react";
 
 import type { AuthHeaderReducer } from "./AuthHeaderProvider";
 
+type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
+
 interface AuthHeaderContextType {
     state: ReducerState<AuthHeaderReducer>;
     dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>;
diff --git a/src/components/structures/auth/header/AuthHeaderDisplay.tsx b/src/components/structures/auth/header/AuthHeaderDisplay.tsx
index f1289a1c99945715d5cd7e1b4331667153d44551..515d919db3dd6564457f21bfcff089a820b8f5c4 100644
--- a/src/components/structures/auth/header/AuthHeaderDisplay.tsx
+++ b/src/components/structures/auth/header/AuthHeaderDisplay.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { Fragment, PropsWithChildren, ReactNode, useContext } from "react";
+import React, { type JSX, Fragment, type PropsWithChildren, type ReactNode, useContext } from "react";
 
 import { AuthHeaderContext } from "./AuthHeaderContext";
 
diff --git a/src/components/structures/auth/header/AuthHeaderModifier.tsx b/src/components/structures/auth/header/AuthHeaderModifier.tsx
index d3b3d648e7f04a16377082e2fa5a7003be41687a..afe5a4b7cead72a71992b91b7e50d2eb11685dbd 100644
--- a/src/components/structures/auth/header/AuthHeaderModifier.tsx
+++ b/src/components/structures/auth/header/AuthHeaderModifier.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactNode, useContext, useEffect } from "react";
+import { type ReactNode, useContext, useEffect } from "react";
 
 import { AuthHeaderContext } from "./AuthHeaderContext";
 import { AuthHeaderActionType } from "./AuthHeaderProvider";
diff --git a/src/components/structures/auth/header/AuthHeaderProvider.tsx b/src/components/structures/auth/header/AuthHeaderProvider.tsx
index 0189b69212481de5fa5cc501e4ebbecea2423384..50a53b95c3db8b36ad973b0f6a4abaf69e1835af 100644
--- a/src/components/structures/auth/header/AuthHeaderProvider.tsx
+++ b/src/components/structures/auth/header/AuthHeaderProvider.tsx
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { isEqual } from "lodash";
-import React, { ComponentProps, PropsWithChildren, Reducer, useReducer } from "react";
+import React, { type JSX, type ComponentProps, type PropsWithChildren, type Reducer, useReducer } from "react";
 
 import { AuthHeaderContext } from "./AuthHeaderContext";
-import { AuthHeaderModifier } from "./AuthHeaderModifier";
+import { type AuthHeaderModifier } from "./AuthHeaderModifier";
 
 export enum AuthHeaderActionType {
     Add,
@@ -24,8 +24,8 @@ interface AuthHeaderAction {
 
 export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>;
 
-export function AuthHeaderProvider({ children }: PropsWithChildren<{}>): JSX.Element {
-    const [state, dispatch] = useReducer<AuthHeaderReducer>(
+export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element {
+    const [state, dispatch] = useReducer<ComponentProps<typeof AuthHeaderModifier>[], [AuthHeaderAction]>(
         (state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => {
             switch (action.type) {
                 case AuthHeaderActionType.Add:
diff --git a/src/components/structures/grouper/BaseGrouper.ts b/src/components/structures/grouper/BaseGrouper.ts
index a685582a2c7a0c8d94ca590cc00c80f3a7c3b00a..c0fc83080e6f53479235df15552c2ac0e988cfec 100644
--- a/src/components/structures/grouper/BaseGrouper.ts
+++ b/src/components/structures/grouper/BaseGrouper.ts
@@ -6,10 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactNode } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type ReactNode } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import MessagePanel, { WrappedEvent } from "../MessagePanel";
+import { type WrappedEvent } from "../MessagePanel";
+import type MessagePanel from "../MessagePanel";
 
 /* Grouper classes determine when events can be grouped together in a summary.
  * Groupers should have the following methods:
diff --git a/src/components/structures/grouper/CreationGrouper.tsx b/src/components/structures/grouper/CreationGrouper.tsx
index 5009b14baff81a632cb126278a6dd64cd03b0a0c..009f5bdc265a83f9413f981662e81502f077559f 100644
--- a/src/components/structures/grouper/CreationGrouper.tsx
+++ b/src/components/structures/grouper/CreationGrouper.tsx
@@ -6,12 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { EventType, M_BEACON_INFO, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode } from "react";
+import { EventType, M_BEACON_INFO, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { BaseGrouper } from "./BaseGrouper";
-import MessagePanel, { WrappedEvent } from "../MessagePanel";
+import { type WrappedEvent } from "../MessagePanel";
+import type MessagePanel from "../MessagePanel";
 import DMRoomMap from "../../../utils/DMRoomMap";
 import { _t } from "../../../languageHandler";
 import DateSeparator from "../../views/messages/DateSeparator";
diff --git a/src/components/structures/grouper/LateEventGrouper.ts b/src/components/structures/grouper/LateEventGrouper.ts
index 87cf6549b2e41cdfe847413f1087b491109e26ef..73e858b340f3baf71df97fcac00b0c636db9e463 100644
--- a/src/components/structures/grouper/LateEventGrouper.ts
+++ b/src/components/structures/grouper/LateEventGrouper.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 const UNSIGNED_KEY = "io.element.late_event";
 
diff --git a/src/components/structures/grouper/MainGrouper.tsx b/src/components/structures/grouper/MainGrouper.tsx
index 84d0be26742982df081b7dc8e4aeccfde8848e66..e686f1aa81489fb41c6b550f2227aa46cf1c2503 100644
--- a/src/components/structures/grouper/MainGrouper.tsx
+++ b/src/components/structures/grouper/MainGrouper.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode } from "react";
+import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import type MessagePanel from "../MessagePanel";
 import type { WrappedEvent } from "../MessagePanel";
diff --git a/src/components/utils/Box.tsx b/src/components/utils/Box.tsx
index 2de64ba0759528680bc370f746d9aa08dc5f6adb..2c5dfa56d27989a562ffd595801a8c153f1e9f32 100644
--- a/src/components/utils/Box.tsx
+++ b/src/components/utils/Box.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { useMemo } from "react";
+import React, { type JSX, useMemo } from "react";
 
 type FlexProps = {
     /**
diff --git a/src/components/utils/Flex.tsx b/src/components/utils/Flex.tsx
index 3788e32c4581a317ff96444cb8974c02c8e08a4d..0a8c3c2fa4f33c7f63afd054bba1fc41d6a12144 100644
--- a/src/components/utils/Flex.tsx
+++ b/src/components/utils/Flex.tsx
@@ -7,14 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { useMemo } from "react";
+import React, { type JSX, type ComponentProps, type JSXElementConstructor, useMemo } from "react";
 
-type FlexProps = {
+type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> = {
     /**
      * The type of the HTML element
      * @default div
      */
-    as?: string;
+    as?: T;
     /**
      * The CSS class name.
      */
@@ -30,15 +30,20 @@ type FlexProps = {
      */
     direction?: "row" | "column" | "row-reverse" | "column-reverse";
     /**
-     * The alingment of the flex children
+     * The alignment of the flex children
      * @default start
      */
-    align?: "start" | "center" | "end" | "baseline" | "stretch";
+    align?: "start" | "center" | "end" | "baseline" | "stretch" | "normal";
     /**
      * The justification of the flex children
      * @default start
      */
     justify?: "start" | "center" | "end" | "space-between";
+    /**
+     * The wrapping of the flex children
+     * @default nowrap
+     */
+    wrap?: "wrap" | "nowrap" | "wrap-reverse";
     /**
      * The spacing between the flex children, expressed with the CSS unit
      * @default 0
@@ -48,22 +53,23 @@ type FlexProps = {
      * the on click event callback
      */
     onClick?: (e: React.MouseEvent) => void;
-};
+} & ComponentProps<T>;
 
 /**
  * A flexbox container helper
  */
-export function Flex({
+export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any> = "div">({
     as = "div",
     display = "flex",
     direction = "row",
     align = "start",
     justify = "start",
     gap = "0",
+    wrap = "nowrap",
     className,
     children,
     ...props
-}: React.PropsWithChildren<FlexProps>): JSX.Element {
+}: React.PropsWithChildren<FlexProps<T>>): JSX.Element {
     const style = useMemo(
         () => ({
             "--mx-flex-display": display,
@@ -71,8 +77,9 @@ export function Flex({
             "--mx-flex-align": align,
             "--mx-flex-justify": justify,
             "--mx-flex-gap": gap,
+            "--mx-flex-wrap": wrap,
         }),
-        [align, direction, display, gap, justify],
+        [align, direction, display, gap, justify, wrap],
     );
 
     return React.createElement(as, { ...props, className: classNames("mx_Flex", className), style }, children);
diff --git a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8879f5ae6922e516ba66fe99c4290337e52975ca
--- /dev/null
+++ b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { EventType, JoinRule, type MatrixEvent, type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { useEffect, useState } from "react";
+
+import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
+import { useDmMember, usePresence, type Presence } from "../../views/avatars/WithPresenceIndicator";
+
+export interface RoomAvatarViewState {
+    /**
+     * Whether the room avatar has a decoration.
+     * A decoration can be a public or a video call icon or an indicator of presence.
+     */
+    hasDecoration: boolean;
+    /**
+     * Whether the room is public.
+     */
+    isPublic: boolean;
+    /**
+     * Whether the room is a video room.
+     */
+    isVideoRoom: boolean;
+    /**
+     * The presence of the user in the DM room.
+     * If null, the user is not in a DM room or presence is not enabled.
+     */
+    presence: Presence | null;
+}
+
+/**
+ * Hook to get the state of the room avatar.
+ * @param room
+ */
+export function useRoomAvatarViewModel(room: Room): RoomAvatarViewState {
+    const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom();
+    const roomMember = useDmMember(room);
+    const presence = usePresence(room, roomMember);
+    const isPublic = useIsPublic(room);
+
+    const hasDecoration = isPublic || isVideoRoom || presence !== null;
+
+    return { hasDecoration, isPublic, isVideoRoom, presence };
+}
+
+/**
+ * Hook listening to the room join rules.
+ * Return true if the room is public.
+ * @param room
+ */
+function useIsPublic(room: Room): boolean {
+    const [isPublic, setIsPublic] = useState(isRoomPublic(room));
+    // We don't use `useTypedEventEmitterState` because we don't want to update `isPublic` value at every `RoomEvent.Timeline` event.
+    useTypedEventEmitter(room, RoomEvent.Timeline, (ev: MatrixEvent, _room: Room) => {
+        if (room.roomId !== _room.roomId) return;
+        if (ev.getType() !== EventType.RoomJoinRules && ev.getType() !== EventType.RoomMember) return;
+
+        setIsPublic(isRoomPublic(_room));
+    });
+
+    // Reset the value when the room changes
+    useEffect(() => {
+        setIsPublic(isRoomPublic(room));
+    }, [room]);
+
+    return isPublic;
+}
+
+/**
+ * Whether the room is public.
+ * @param room
+ */
+function isRoomPublic(room: Room): boolean {
+    return room.getJoinRule() === JoinRule.Public;
+}
diff --git a/src/components/viewmodels/memberlist/MemberListViewModel.tsx b/src/components/viewmodels/memberlist/MemberListViewModel.tsx
index 88eacb1b931c75f2464e04a627935c97f452bb05..a03f7035118c8505a881a37343b8a47c9634dc03 100644
--- a/src/components/viewmodels/memberlist/MemberListViewModel.tsx
+++ b/src/components/viewmodels/memberlist/MemberListViewModel.tsx
@@ -8,35 +8,35 @@ Please see LICENSE files in the repository root for full details.
 import {
     ClientEvent,
     EventType,
-    MatrixEvent,
-    Room,
+    type MatrixEvent,
+    type Room,
     RoomEvent,
     RoomMemberEvent,
-    RoomState,
+    type RoomState,
     RoomStateEvent,
-    RoomMember as SdkRoomMember,
-    User,
+    type RoomMember as SdkRoomMember,
+    type User,
     UserEvent,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { useCallback, useContext, useEffect, useMemo, useState } from "react";
 import { throttle } from "lodash";
 
-import { RoomMember } from "../../../models/rooms/RoomMember";
+import { type RoomMember } from "../../../models/rooms/RoomMember";
 import { mediaFromMxc } from "../../../customisations/Media";
 import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../../settings/UIFeature";
-import { PresenceState } from "../../../models/rooms/PresenceState";
+import { type PresenceState } from "../../../models/rooms/PresenceState";
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
 import { SDKContext } from "../../../contexts/SDKContext";
 import PosthogTrackers from "../../../PosthogTrackers";
-import { ButtonEvent } from "../../views/elements/AccessibleButton";
+import { type ButtonEvent } from "../../views/elements/AccessibleButton";
 import { inviteToRoom } from "../../../utils/room/inviteToRoom";
 import { canInviteTo } from "../../../utils/room/canInviteTo";
 import { isValid3pidInvite } from "../../../RoomInvite";
-import { ThreePIDInvite } from "../../../models/rooms/ThreePIDInvite";
-import { XOR } from "../../../@types/common";
+import { type ThreePIDInvite } from "../../../models/rooms/ThreePIDInvite";
+import { type XOR } from "../../../@types/common";
 import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
 
 type Member = XOR<{ member: RoomMember }, { threePidInvite: ThreePIDInvite }>;
@@ -99,8 +99,12 @@ export function sdkRoomMemberToRoomMember(member: SdkRoomMember): Member {
     };
 }
 
+export const SEPARATOR = "SEPARATOR";
+export type MemberWithSeparator = Member | typeof SEPARATOR;
+
 export interface MemberListViewState {
-    members: Member[];
+    members: MemberWithSeparator[];
+    memberCount: number;
     search: (searchQuery: string) => void;
     isPresenceEnabled: boolean;
     shouldShowInvite: boolean;
@@ -118,10 +122,16 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
     }
 
     const sdkContext = useContext(SDKContext);
-    const [memberMap, setMemberMap] = useState<Map<string, Member>>(new Map());
+    const [memberMap, setMemberMap] = useState<Map<string, MemberWithSeparator>>(new Map());
     const [isLoading, setIsLoading] = useState<boolean>(true);
     // This is the last known total number of members in this room.
     const [totalMemberCount, setTotalMemberCount] = useState(0);
+    /**
+     * This is the current number of members in the list.
+     * This number will be less than the total number of members
+     * in the room when the search functionality is used.
+     */
+    const [memberCount, setMemberCount] = useState(0);
 
     const loadMembers = useMemo(
         () =>
@@ -131,24 +141,34 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
                         roomId,
                         searchQuery,
                     );
-                    const newMemberMap = new Map<string, Member>();
-                    // First add the invited room members
+                    const threePidInvited = getPending3PidInvites(room, searchQuery);
+
+                    const newMemberMap = new Map<string, MemberWithSeparator>();
+
+                    // First add the joined room members
+                    for (const member of joinedSdk) {
+                        const roomMember = sdkRoomMemberToRoomMember(member);
+                        newMemberMap.set(member.userId, roomMember);
+                    }
+
+                    // Then a separator if needed
+                    if (joinedSdk.length > 0 && (invitedSdk.length > 0 || threePidInvited.length > 0))
+                        newMemberMap.set(SEPARATOR, SEPARATOR);
+
+                    // Then add the invited room members
                     for (const member of invitedSdk) {
                         const roomMember = sdkRoomMemberToRoomMember(member);
                         newMemberMap.set(member.userId, roomMember);
                     }
-                    // Then add the third party invites
-                    const threePidInvited = getPending3PidInvites(room, searchQuery);
+
+                    // Finally add the third party invites
                     for (const invited of threePidInvited) {
                         const key = invited.threePidInvite!.event.getContent().display_name;
                         newMemberMap.set(key, invited);
                     }
-                    // Finally add the joined room members
-                    for (const member of joinedSdk) {
-                        const roomMember = sdkRoomMemberToRoomMember(member);
-                        newMemberMap.set(member.userId, roomMember);
-                    }
+
                     setMemberMap(newMemberMap);
+                    setMemberCount(joinedSdk.length + invitedSdk.length + threePidInvited.length);
                     if (!searchQuery) {
                         /**
                          * Since searching for members only gives you the relevant
@@ -241,6 +261,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
 
     return {
         members: Array.from(memberMap.values()),
+        memberCount,
         search: loadMembers,
         shouldShowInvite,
         isPresenceEnabled,
diff --git a/src/components/viewmodels/memberlist/tiles/MemberTileViewModel.tsx b/src/components/viewmodels/memberlist/tiles/MemberTileViewModel.tsx
index 367064e3ffd361951ba8b5557a8d79dc46a8b586..4f6814caae81aa66a7399d8d2a697656e2a1a4a5 100644
--- a/src/components/viewmodels/memberlist/tiles/MemberTileViewModel.tsx
+++ b/src/components/viewmodels/memberlist/tiles/MemberTileViewModel.tsx
@@ -6,16 +6,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useEffect, useMemo, useState } from "react";
-import { RoomStateEvent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
-import { UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import { RoomStateEvent, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { type UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
 import dis from "../../../../dispatcher/dispatcher";
 import { MatrixClientPeg } from "../../../../MatrixClientPeg";
 import { Action } from "../../../../dispatcher/actions";
 import { asyncSome } from "../../../../utils/arrays";
 import { getUserDeviceIds } from "../../../../utils/crypto/deviceInfo";
-import { RoomMember } from "../../../../models/rooms/RoomMember";
-import { _t, _td, TranslationKey } from "../../../../languageHandler";
+import { type RoomMember } from "../../../../models/rooms/RoomMember";
+import { _t, _td, type TranslationKey } from "../../../../languageHandler";
 import UserIdentifierCustomisations from "../../../../customisations/UserIdentifier";
 import { E2EStatus } from "../../../../utils/ShieldUtils";
 
@@ -145,7 +145,7 @@ export function useMemberTileViewModel(props: MemberTileViewModelProps): MemberT
         userLabel = _t(PowerLabel[powerStatus]);
     }
     if (props.member.isInvite) {
-        userLabel = `(${_t("member_list|invited_label")})`;
+        userLabel = _t("member_list|invited_label");
     }
 
     return {
diff --git a/src/components/viewmodels/memberlist/tiles/ThreePidTileViewModel.tsx b/src/components/viewmodels/memberlist/tiles/ThreePidTileViewModel.tsx
index daeb8d899f4913a516b2391285da55ac64066ec5..f1e32692c0f8f393fa9498bb74bc1238a964252a 100644
--- a/src/components/viewmodels/memberlist/tiles/ThreePidTileViewModel.tsx
+++ b/src/components/viewmodels/memberlist/tiles/ThreePidTileViewModel.tsx
@@ -7,7 +7,8 @@ Please see LICENSE files in the repository root for full details.
 
 import dis from "../../../../dispatcher/dispatcher";
 import { Action } from "../../../../dispatcher/actions";
-import { ThreePIDInvite } from "../../../../models/rooms/ThreePIDInvite";
+import { type ThreePIDInvite } from "../../../../models/rooms/ThreePIDInvite";
+import { _t } from "../../../../languageHandler";
 
 interface ThreePidTileViewModelProps {
     threePidInvite: ThreePIDInvite;
@@ -16,6 +17,7 @@ interface ThreePidTileViewModelProps {
 export interface ThreePidTileViewState {
     name: string;
     onClick: () => void;
+    userLabel?: string;
 }
 
 export function useThreePidTileViewModel(props: ThreePidTileViewModelProps): ThreePidTileViewState {
@@ -28,8 +30,11 @@ export function useThreePidTileViewModel(props: ThreePidTileViewModelProps): Thr
         });
     };
 
+    const userLabel = _t("member_list|invited_label");
+
     return {
         name,
         onClick,
+        userLabel,
     };
 }
diff --git a/src/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx b/src/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..10cbc5b568b6e7fa935fe8343e4f7f0574ac1127
--- /dev/null
+++ b/src/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx
@@ -0,0 +1,84 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type SyntheticEvent, useState } from "react";
+import { EventType, type Room, type ContentHelpers } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
+
+import { useRoomState } from "../../../hooks/useRoomState";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import { onRoomTopicLinkClick } from "../../views/elements/RoomTopic";
+import { useTopic } from "../../../hooks/room/useTopic";
+
+export interface RoomTopicState {
+    /**
+     * The topic of the room, the value is taken from the room state
+     */
+    topic: Optional<ContentHelpers.TopicState>;
+    /**
+     * Whether the topic is expanded or not
+     */
+    expanded: boolean;
+    /**
+     * Whether the user have the permission to edit the topic
+     */
+    canEditTopic: boolean;
+    /**
+     * The callback when the edit button is clicked
+     */
+    onEditClick: (e: SyntheticEvent) => void;
+    /**
+     * When the expand button is clicked, it changes expanded state
+     */
+    onExpandedClick: (ev: SyntheticEvent) => void;
+    /**
+     * The callback when the topic link is clicked
+     */
+    onTopicLinkClick: React.MouseEventHandler<HTMLElement>;
+}
+
+/**
+ * The view model for the room topic used in the RoomSummaryCard
+ * @param room - the room to get the topic from
+ * @returns the room topic state
+ */
+export function useRoomTopicViewModel(room: Room): RoomTopicState {
+    const [expanded, setExpanded] = useState(true);
+
+    const topic = useTopic(room);
+
+    const canEditTopic = useRoomState(room, (state) =>
+        state.maySendStateEvent(EventType.RoomTopic, room.client.getSafeUserId()),
+    );
+
+    const onEditClick = (e: SyntheticEvent): void => {
+        e.preventDefault();
+        e.stopPropagation();
+        defaultDispatcher.dispatch({ action: "open_room_settings" });
+    };
+
+    const onExpandedClick = (e: SyntheticEvent): void => {
+        e.preventDefault();
+        e.stopPropagation();
+        setExpanded((_expanded) => !_expanded);
+    };
+
+    const onTopicLinkClick = (e: React.MouseEvent): void => {
+        if (e.target instanceof HTMLAnchorElement) {
+            onRoomTopicLinkClick(e);
+            return;
+        }
+    };
+
+    return {
+        topic,
+        expanded,
+        canEditTopic,
+        onEditClick,
+        onExpandedClick,
+        onTopicLinkClick,
+    };
+}
diff --git a/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx b/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..143f3fca18914702e43f11268f839eaa2cb6a222
--- /dev/null
+++ b/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx
@@ -0,0 +1,278 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useEffect, useRef, useState } from "react";
+import { EventType, type JoinRule, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
+import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext";
+import { type E2EStatus } from "../../../utils/ShieldUtils";
+import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
+import { useRoomState } from "../../../hooks/useRoomState";
+import { useAccountData } from "../../../hooks/useAccountData";
+import { useDispatcher } from "../../../hooks/useDispatcher";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import { Action } from "../../../dispatcher/actions";
+import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
+import { canInviteTo } from "../../../utils/room/canInviteTo";
+import { DefaultTagID } from "../../../stores/room-list/models";
+import { useEventEmitterState } from "../../../hooks/useEventEmitter";
+import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
+import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
+import PosthogTrackers from "../../../PosthogTrackers";
+import { PollHistoryDialog } from "../../views/dialogs/PollHistoryDialog";
+import Modal from "../../../Modal";
+import ExportDialog from "../../views/dialogs/ExportDialog";
+import { ShareDialog } from "../../views/dialogs/ShareDialog";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { ReportRoomDialog } from "../../views/dialogs/ReportRoomDialog";
+import { Key } from "../../../Keyboard";
+import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
+import { tagRoom } from "../../../utils/room/tagRoom";
+import { inviteToRoom } from "../../../utils/room/inviteToRoom";
+
+export interface RoomSummaryCardState {
+    isDirectMessage: boolean;
+    /**
+     * Whether the room is encrypted, used to display the correct badge and icon
+     */
+    isRoomEncrypted: boolean;
+    /**
+     * The e2e status of the room, used to display the correct badge and icon
+     */
+    e2eStatus: E2EStatus | undefined;
+    /**
+     * The join rule of the room, used to display the correct badge and icon
+     */
+    roomJoinRule: JoinRule;
+    /**
+     * if it is a video room, it should not display export chat, polls, files, extensions
+     */
+    isVideoRoom: boolean;
+    /**
+     * display the alias of the room, if it exists
+     */
+    alias: string;
+    /**
+     * value to check if the room is a favorite or not
+     */
+    isFavorite: boolean;
+    /**
+     * value to check if we disable invite button or not
+     */
+    canInviteToState: boolean;
+    /**
+     * Getting the number of pinned messages in the room, next to the pin button
+     */
+    pinCount: number;
+    searchInputRef: React.RefObject<HTMLInputElement | null>;
+    /**
+     * The callback when new value is entered in the search input
+     */
+    onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => void;
+    /**
+     * Callbacks to all the actions button in the right panel
+     */
+    onRoomMembersClick: () => void;
+    onRoomThreadsClick: () => void;
+    onRoomFilesClick: () => void;
+    onRoomExtensionsClick: () => void;
+    onRoomPinsClick: () => void;
+    onRoomSettingsClick: (ev: Event) => void;
+    onLeaveRoomClick: () => void;
+    onShareRoomClick: () => void;
+    onRoomExportClick: () => Promise<void>;
+    onRoomPollHistoryClick: () => void;
+    onReportRoomClick: () => Promise<void>;
+    onFavoriteToggleClick: () => void;
+    onInviteToRoomClick: () => void;
+}
+
+/**
+ * Hook to check if the room is a direct message or not
+ * @param room - The room to check
+ * @returns Whether the room is a direct message
+ */
+const useIsDirectMessage = (room: Room): boolean => {
+    const directRoomsList = useAccountData<Record<string, string[]>>(room.client, EventType.Direct);
+    const [isDirectMessage, setDirectMessage] = useState(false);
+
+    useEffect(() => {
+        for (const [, dmRoomList] of Object.entries(directRoomsList)) {
+            if (dmRoomList.includes(room?.roomId ?? "")) {
+                setDirectMessage(true);
+                break;
+            }
+        }
+    }, [room, directRoomsList]);
+
+    return isDirectMessage;
+};
+
+/**
+ * Hook to handle the search input in the right panel
+ * @param onSearchCancel - The callback when the search input is cancelled
+ * @returns The search input ref and the callback when the search input is updated
+ */
+const useSearchInput = (
+    onSearchCancel?: () => void,
+): {
+    searchInputRef: React.RefObject<HTMLInputElement | null>;
+    onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => void;
+} => {
+    const searchInputRef = useRef<HTMLInputElement>(null);
+
+    const onUpdateSearchInput = (e: React.KeyboardEvent<HTMLInputElement>): void => {
+        if (searchInputRef.current && e.key === Key.ESCAPE) {
+            searchInputRef.current.value = "";
+            onSearchCancel?.();
+        }
+    };
+
+    // Focus the search field when the user clicks on the search button component
+    useDispatcher(defaultDispatcher, (payload) => {
+        if (payload.action === Action.FocusMessageSearch) {
+            searchInputRef.current?.focus();
+        }
+    });
+
+    return {
+        searchInputRef,
+        onUpdateSearchInput,
+    };
+};
+
+export function useRoomSummaryCardViewModel(
+    room: Room,
+    permalinkCreator: RoomPermalinkCreator,
+    onSearchCancel?: () => void,
+): RoomSummaryCardState {
+    const cli = useMatrixClientContext();
+
+    const isRoomEncrypted = useIsEncrypted(cli, room) ?? false;
+    const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
+    const e2eStatus = roomContext.e2eStatus;
+    const isVideoRoom = calcIsVideoRoom(room);
+
+    const roomState = useRoomState(room);
+    // used to check if the room is public or not
+    const roomJoinRule = roomState.getJoinRule();
+    const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
+    const pinCount = usePinnedEvents(room).length;
+    // value to check if the user can invite to the room
+    const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
+
+    const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
+        RoomListStore.instance.getTagsForRoom(room),
+    );
+    const isFavorite = roomTags.includes(DefaultTagID.Favourite);
+
+    const isDirectMessage = useIsDirectMessage(room);
+
+    const onRoomMembersClick = (): void => {
+        RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true);
+    };
+
+    const onRoomThreadsClick = (): void => {
+        RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true);
+    };
+
+    const onRoomFilesClick = (): void => {
+        RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
+    };
+
+    const onRoomExtensionsClick = (): void => {
+        RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true);
+    };
+
+    const onRoomPinsClick = (): void => {
+        PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton");
+        RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true);
+    };
+
+    const onRoomSettingsClick = (ev: Event): void => {
+        defaultDispatcher.dispatch({ action: "open_room_settings" });
+        PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
+    };
+
+    const onShareRoomClick = (): void => {
+        Modal.createDialog(ShareDialog, {
+            target: room,
+        });
+    };
+
+    const onRoomExportClick = async (): Promise<void> => {
+        Modal.createDialog(ExportDialog, {
+            room,
+        });
+    };
+
+    const onRoomPollHistoryClick = (): void => {
+        Modal.createDialog(PollHistoryDialog, {
+            room,
+            matrixClient: cli,
+            permalinkCreator,
+        });
+    };
+
+    const onLeaveRoomClick = (): void => {
+        defaultDispatcher.dispatch({
+            action: "leave_room",
+            room_id: room.roomId,
+        });
+    };
+
+    const onReportRoomClick = async (): Promise<void> => {
+        const [leave] = await Modal.createDialog(ReportRoomDialog, {
+            roomId: room.roomId,
+        }).finished;
+        if (leave) {
+            defaultDispatcher.dispatch({
+                action: "leave_room",
+                room_id: room.roomId,
+            });
+        }
+    };
+
+    const onFavoriteToggleClick = (): void => {
+        tagRoom(room, DefaultTagID.Favourite);
+    };
+
+    const onInviteToRoomClick = (): void => {
+        inviteToRoom(room);
+    };
+
+    // Room Search element ref
+    const { searchInputRef, onUpdateSearchInput } = useSearchInput(onSearchCancel);
+
+    return {
+        isDirectMessage,
+        isRoomEncrypted,
+        roomJoinRule,
+        e2eStatus,
+        isVideoRoom,
+        alias,
+        isFavorite,
+        canInviteToState,
+        searchInputRef,
+        pinCount,
+        onRoomMembersClick,
+        onRoomThreadsClick,
+        onRoomFilesClick,
+        onRoomExtensionsClick,
+        onRoomPinsClick,
+        onRoomSettingsClick,
+        onLeaveRoomClick,
+        onShareRoomClick,
+        onRoomExportClick,
+        onRoomPollHistoryClick,
+        onReportRoomClick,
+        onUpdateSearchInput,
+        onFavoriteToggleClick,
+        onInviteToRoomClick,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx b/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9e141c13790d72cc361336990d41077c57a0bd23
--- /dev/null
+++ b/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { useCallback, useEffect, useState } from "react";
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import { type MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
+
+interface MessagePreviewViewState {
+    /**
+     * A string representation of the message preview if available.
+     */
+    message?: string;
+}
+
+/**
+ * View model for rendering a message preview for a given room list item.
+ * @param room The room for which we're rendering the message preview.
+ * @see {@link MessagePreviewViewState} for what this view model returns.
+ */
+export function useMessagePreviewViewModel(room: Room): MessagePreviewViewState {
+    const [messagePreview, setMessagePreview] = useState<MessagePreview | null>(null);
+
+    const updatePreview = useCallback(async (): Promise<void> => {
+        /**
+         * The second argument to getPreviewForRoom is a tag id which doesn't really make
+         * much sense within the context of the new room list. We can pass an empty string
+         * to match all tags for now but we should remember to actually change the implementation
+         * in the store once we remove the legacy room list.
+         */
+        const newPreview = await MessagePreviewStore.instance.getPreviewForRoom(room, "");
+        setMessagePreview(newPreview);
+    }, [room]);
+
+    /**
+     * Update when the message preview has changed for this room.
+     */
+    useEventEmitter(MessagePreviewStore.instance, MessagePreviewStore.getPreviewChangedEventName(room), () => {
+        updatePreview();
+    });
+
+    /**
+     * Do an initial fetch of the message preview.
+     */
+    useEffect(() => {
+        updatePreview();
+    }, [updatePreview]);
+
+    return {
+        message: messagePreview?.text,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d6f54fcae726a7d3da7a3b88742ba9165d410e6f
--- /dev/null
+++ b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { useCallback } from "react";
+import { JoinRule, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
+
+import { useFeatureEnabled } from "../../../hooks/useSettings";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import PosthogTrackers from "../../../PosthogTrackers";
+import { Action } from "../../../dispatcher/actions";
+import { useEventEmitterState, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
+import {
+    getMetaSpaceName,
+    type MetaSpace,
+    type SpaceKey,
+    UPDATE_HOME_BEHAVIOUR,
+    UPDATE_SELECTED_SPACE,
+} from "../../../stores/spaces";
+import SpaceStore from "../../../stores/spaces/SpaceStore";
+import {
+    shouldShowSpaceSettings,
+    showCreateNewRoom,
+    showSpaceInvite,
+    showSpacePreferences,
+    showSpaceSettings,
+} from "../../../utils/space";
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { createRoom, hasCreateRoomRights } from "./utils";
+import { type SortOption, useSorter } from "./useSorter";
+import { useMessagePreviewToggle } from "./useMessagePreviewToggle";
+
+/**
+ * Hook to get the active space and its title.
+ */
+function useSpace(): { activeSpace: Room | null; title: string } {
+    const [spaceKey, activeSpace] = useEventEmitterState<[SpaceKey, Room | null]>(
+        SpaceStore.instance,
+        UPDATE_SELECTED_SPACE,
+        () => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom],
+    );
+    const spaceName = useTypedEventEmitterState(activeSpace ?? undefined, RoomEvent.Name, () => activeSpace?.name);
+    const allRoomsInHome = useEventEmitterState(
+        SpaceStore.instance,
+        UPDATE_HOME_BEHAVIOUR,
+        () => SpaceStore.instance.allRoomsInHome,
+    );
+
+    const title = spaceName ?? getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome);
+
+    return {
+        activeSpace,
+        title,
+    };
+}
+
+export interface RoomListHeaderViewState {
+    /**
+     * The title of the room list
+     */
+    title: string;
+    /**
+     * Whether to display the compose menu
+     * True if the user can create rooms
+     */
+    displayComposeMenu: boolean;
+    /**
+     * Whether to display the space menu
+     * True if there is an active space
+     */
+    displaySpaceMenu: boolean;
+    /**
+     * Whether the user can create rooms
+     */
+    canCreateRoom: boolean;
+    /**
+     * Whether the user can create video rooms
+     */
+    canCreateVideoRoom: boolean;
+    /**
+     * Whether the user can invite in the active space
+     */
+    canInviteInSpace: boolean;
+    /**
+     * Whether the user can access space settings
+     */
+    canAccessSpaceSettings: boolean;
+    /**
+     * Create a chat room
+     * @param e - The click event
+     */
+    createChatRoom: (e: Event) => void;
+    /**
+     * Create a room
+     * @param e - The click event
+     */
+    createRoom: (e: Event) => void;
+    /**
+     * Create a video room
+     */
+    createVideoRoom: () => void;
+    /**
+     * Open the active space home
+     */
+    openSpaceHome: () => void;
+    /**
+     * Display the space invite dialog
+     */
+    inviteInSpace: () => void;
+    /**
+     * Open the space preferences
+     */
+    openSpacePreferences: () => void;
+    /**
+     * Open the space settings
+     */
+    openSpaceSettings: () => void;
+    /**
+     * Change the sort order of the room-list.
+     */
+    sort: (option: SortOption) => void;
+    /**
+     * The currently active sort option.
+     */
+    activeSortOption: SortOption;
+    /**
+     * Whether message previews must be shown or not.
+     */
+    shouldShowMessagePreview: boolean;
+    /**
+     * A function to turn on/off message previews.
+     */
+    toggleMessagePreview: () => void;
+}
+
+/**
+ * View model for the RoomListHeader.
+ */
+export function useRoomListHeaderViewModel(): RoomListHeaderViewState {
+    const matrixClient = useMatrixClientContext();
+    const { activeSpace, title } = useSpace();
+    const isSpaceRoom = Boolean(activeSpace);
+
+    const canCreateRoom = hasCreateRoomRights(matrixClient, activeSpace);
+    const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms") && canCreateRoom;
+    const displayComposeMenu = canCreateRoom;
+    const displaySpaceMenu = isSpaceRoom;
+    const canInviteInSpace = Boolean(
+        activeSpace?.getJoinRule() === JoinRule.Public || activeSpace?.canInvite(matrixClient.getSafeUserId()),
+    );
+    const canAccessSpaceSettings = Boolean(activeSpace && shouldShowSpaceSettings(activeSpace));
+
+    /* Actions */
+
+    const { activeSortOption, sort } = useSorter();
+    const { shouldShowMessagePreview, toggleMessagePreview } = useMessagePreviewToggle();
+
+    const createChatRoom = useCallback((e: Event) => {
+        defaultDispatcher.fire(Action.CreateChat);
+        PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
+    }, []);
+
+    const createRoomMemoized = useCallback(
+        (e: Event) => {
+            createRoom(activeSpace);
+            PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
+        },
+        [activeSpace],
+    );
+
+    const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
+    const createVideoRoom = useCallback(() => {
+        const type = elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo;
+        if (activeSpace) {
+            showCreateNewRoom(activeSpace, type);
+        } else {
+            defaultDispatcher.dispatch({
+                action: Action.CreateRoom,
+                type,
+            });
+        }
+    }, [activeSpace, elementCallVideoRoomsEnabled]);
+
+    const openSpaceHome = useCallback(() => {
+        // openSpaceHome is only available when there is an active space
+        if (!activeSpace) return;
+        defaultDispatcher.dispatch<ViewRoomPayload>({
+            action: Action.ViewRoom,
+            room_id: activeSpace.roomId,
+            metricsTrigger: undefined,
+        });
+    }, [activeSpace]);
+
+    const inviteInSpace = useCallback(() => {
+        // inviteInSpace is only available when there is an active space
+        if (!activeSpace) return;
+        showSpaceInvite(activeSpace);
+    }, [activeSpace]);
+
+    const openSpacePreferences = useCallback(() => {
+        // openSpacePreferences is only available when there is an active space
+        if (!activeSpace) return;
+        showSpacePreferences(activeSpace);
+    }, [activeSpace]);
+
+    const openSpaceSettings = useCallback(() => {
+        // openSpaceSettings is only available when there is an active space
+        if (!activeSpace) return;
+        showSpaceSettings(activeSpace);
+    }, [activeSpace]);
+
+    return {
+        title,
+        displayComposeMenu,
+        displaySpaceMenu,
+        canCreateRoom,
+        canCreateVideoRoom,
+        canInviteInSpace,
+        canAccessSpaceSettings,
+        createChatRoom,
+        createRoom: createRoomMemoized,
+        createVideoRoom,
+        openSpaceHome,
+        inviteInSpace,
+        openSpacePreferences,
+        openSpaceSettings,
+        activeSortOption,
+        sort,
+        shouldShowMessagePreview,
+        toggleMessagePreview,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..997b515f276d31eaa6e7ec1ab657ff26c485e598
--- /dev/null
+++ b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { useCallback } from "react";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import { useEventEmitterState } from "../../../hooks/useEventEmitter";
+import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
+import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import { DefaultTagID } from "../../../stores/room-list/models";
+import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
+import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
+import { UIComponent } from "../../../settings/UIFeature";
+import dispatcher from "../../../dispatcher/dispatcher";
+import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications";
+import PosthogTrackers from "../../../PosthogTrackers";
+import { tagRoom } from "../../../utils/room/tagRoom";
+import { RoomNotifState } from "../../../RoomNotifs";
+import { useNotificationState } from "../../../hooks/useRoomNotificationState";
+
+export interface RoomListItemMenuViewState {
+    /**
+     * Whether the more options menu should be shown.
+     */
+    showMoreOptionsMenu: boolean;
+    /**
+     * Whether the notification menu should be shown.
+     */
+    showNotificationMenu: boolean;
+    /**
+     * Whether the room is a favourite room.
+     */
+    isFavourite: boolean;
+    /**
+     * Can invite other user's in the room.
+     */
+    canInvite: boolean;
+    /**
+     * Can copy the room link.
+     */
+    canCopyRoomLink: boolean;
+    /**
+     * Can mark the room as read.
+     */
+    canMarkAsRead: boolean;
+    /**
+     * Can mark the room as unread.
+     */
+    canMarkAsUnread: boolean;
+    /**
+     * Whether the notification is set to all messages.
+     */
+    isNotificationAllMessage: boolean;
+    /**
+     * Whether the notification is set to all messages loud.
+     */
+    isNotificationAllMessageLoud: boolean;
+    /**
+     * Whether the notification is set to mentions and keywords only.
+     */
+    isNotificationMentionOnly: boolean;
+    /**
+     * Whether the notification is muted.
+     */
+    isNotificationMute: boolean;
+    /**
+     * Mark the room as read.
+     * @param evt
+     */
+    markAsRead: (evt: Event) => void;
+    /**
+     * Mark the room as unread.
+     * @param evt
+     */
+    markAsUnread: (evt: Event) => void;
+    /**
+     * Toggle the room as favourite.
+     * @param evt
+     */
+    toggleFavorite: (evt: Event) => void;
+    /**
+     * Toggle the room as low priority.
+     */
+    toggleLowPriority: () => void;
+    /**
+     * Invite other users in the room.
+     * @param evt
+     */
+    invite: (evt: Event) => void;
+    /**
+     * Copy the room link in the clipboard.
+     * @param evt
+     */
+    copyRoomLink: (evt: Event) => void;
+    /**
+     * Leave the room.
+     * @param evt
+     */
+    leaveRoom: (evt: Event) => void;
+    /**
+     * Set the room notification state.
+     * @param state
+     */
+    setRoomNotifState: (state: RoomNotifState) => void;
+}
+
+export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewState {
+    const matrixClient = useMatrixClientContext();
+    const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => room.tags);
+    const { level: notificationLevel } = useUnreadNotifications(room);
+
+    const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
+    const isFavourite = Boolean(roomTags[DefaultTagID.Favourite]);
+    const isArchived = Boolean(roomTags[DefaultTagID.Archived]);
+
+    const showMoreOptionsMenu = hasAccessToOptionsMenu(room);
+    const showNotificationMenu = hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived);
+
+    const canMarkAsRead = notificationLevel > NotificationLevel.None;
+    const canMarkAsUnread = !canMarkAsRead && !isArchived;
+
+    const canInvite =
+        room.canInvite(matrixClient.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers);
+    const canCopyRoomLink = !isDm;
+
+    const [roomNotifState, setRoomNotifState] = useNotificationState(room);
+    const isNotificationAllMessage = roomNotifState === RoomNotifState.AllMessages;
+    const isNotificationAllMessageLoud = roomNotifState === RoomNotifState.AllMessagesLoud;
+    const isNotificationMentionOnly = roomNotifState === RoomNotifState.MentionsOnly;
+    const isNotificationMute = roomNotifState === RoomNotifState.Mute;
+
+    // Actions
+
+    const markAsRead = useCallback(
+        async (evt: Event): Promise<void> => {
+            await clearRoomNotification(room, matrixClient);
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkRead", evt);
+        },
+        [room, matrixClient],
+    );
+
+    const markAsUnread = useCallback(
+        async (evt: Event): Promise<void> => {
+            await setMarkedUnreadState(room, matrixClient, true);
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkUnread", evt);
+        },
+        [room, matrixClient],
+    );
+
+    const toggleFavorite = useCallback(
+        (evt: Event): void => {
+            tagRoom(room, DefaultTagID.Favourite);
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", evt);
+        },
+        [room],
+    );
+
+    const toggleLowPriority = useCallback((): void => tagRoom(room, DefaultTagID.LowPriority), [room]);
+
+    const invite = useCallback(
+        (evt: Event): void => {
+            dispatcher.dispatch({
+                action: "view_invite",
+                roomId: room.roomId,
+            });
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", evt);
+        },
+        [room],
+    );
+
+    const copyRoomLink = useCallback(
+        (evt: Event): void => {
+            dispatcher.dispatch({
+                action: "copy_room",
+                room_id: room.roomId,
+            });
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", evt);
+        },
+        [room],
+    );
+
+    const leaveRoom = useCallback(
+        (evt: Event): void => {
+            dispatcher.dispatch({
+                action: isArchived ? "forget_room" : "leave_room",
+                room_id: room.roomId,
+            });
+            PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", evt);
+        },
+        [room, isArchived],
+    );
+
+    return {
+        showMoreOptionsMenu,
+        showNotificationMenu,
+        isFavourite,
+        canInvite,
+        canCopyRoomLink,
+        canMarkAsRead,
+        canMarkAsUnread,
+        isNotificationAllMessage,
+        isNotificationAllMessageLoud,
+        isNotificationMentionOnly,
+        isNotificationMute,
+        markAsRead,
+        markAsUnread,
+        toggleFavorite,
+        toggleLowPriority,
+        invite,
+        copyRoomLink,
+        leaveRoom,
+        setRoomNotifState,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e009033875530de793853e6ba953ff82be000798
--- /dev/null
+++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+
+import dispatcher from "../../../dispatcher/dispatcher";
+import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { Action } from "../../../dispatcher/actions";
+import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils";
+import { _t } from "../../../languageHandler";
+import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
+import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import { useEventEmitter, useEventEmitterState, useTypedEventEmitter } from "../../../hooks/useEventEmitter";
+import { DefaultTagID } from "../../../stores/room-list/models";
+import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall";
+import { type ConnectionState } from "../../../models/Call";
+import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
+import { useMessagePreviewToggle } from "./useMessagePreviewToggle";
+
+export interface RoomListItemViewState {
+    /**
+     * The name of the room.
+     */
+    name: string;
+    /**
+     * Whether the hover menu should be shown.
+     */
+    showHoverMenu: boolean;
+    /**
+     * Open the room having given roomId.
+     */
+    openRoom: () => void;
+    /**
+     * The a11y label for the room list item.
+     */
+    a11yLabel: string;
+    /**
+     * The notification state of the room.
+     */
+    notificationState: RoomNotificationState;
+    /**
+     * Whether the room should be bolded.
+     */
+    isBold: boolean;
+    /**
+     * Whether the room is a video room
+     */
+    isVideoRoom: boolean;
+    /**
+     * The connection state of the call.
+     * `null` if there is no call in the room.
+     */
+    callConnectionState: ConnectionState | null;
+    /**
+     * Whether there are participants in the call.
+     */
+    hasParticipantInCall: boolean;
+    /**
+     * Pre-rendered and translated preview for the latest message in the room, or undefined
+     * if no preview should be shown.
+     */
+    messagePreview: string | undefined;
+    /**
+     * Whether the notification decoration should be shown.
+     */
+    showNotificationDecoration: boolean;
+}
+
+/**
+ * View model for the room list item
+ * @see {@link RoomListItemViewState} for more information about what this view model returns.
+ */
+export function useRoomListItemViewModel(room: Room): RoomListItemViewState {
+    const matrixClient = useMatrixClientContext();
+    const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => room.tags);
+    const isArchived = Boolean(roomTags[DefaultTagID.Archived]);
+    const name = useEventEmitterState(room, RoomEvent.Name, () => room.name);
+
+    const notificationState = useMemo(() => RoomNotificationStateStore.instance.getRoomState(room), [room]);
+
+    const [a11yLabel, setA11yLabel] = useState(getA11yLabel(name, notificationState));
+    const [{ isBold, invited, hasVisibleNotification }, setNotificationValues] = useState(
+        getNotificationValues(notificationState),
+    );
+    useEffect(() => {
+        setA11yLabel(getA11yLabel(name, notificationState));
+    }, [name, notificationState]);
+
+    // Listen to changes in the notification state and update the values
+    useTypedEventEmitter(notificationState, NotificationStateEvents.Update, () => {
+        setA11yLabel(getA11yLabel(name, notificationState));
+        setNotificationValues(getNotificationValues(notificationState));
+    });
+
+    // If the notification reference change due to room change, update the values
+    useEffect(() => {
+        setNotificationValues(getNotificationValues(notificationState));
+    }, [notificationState]);
+
+    // We don't want to show the hover menu if
+    // - there is an invitation for this room
+    // - the user doesn't have access to both notification and more options menus
+    const showHoverMenu =
+        !invited &&
+        (hasAccessToOptionsMenu(room) || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived));
+
+    const messagePreview = useRoomMessagePreview(room);
+
+    // Video room
+    const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom();
+    // EC video call or video room
+    const call = useCall(room.roomId);
+    const connectionState = useConnectionState(call);
+    const hasParticipantInCall = useParticipantCount(call) > 0;
+    const callConnectionState = call ? connectionState : null;
+
+    const showNotificationDecoration = hasVisibleNotification || hasParticipantInCall;
+
+    // Actions
+
+    const openRoom = useCallback((): void => {
+        dispatcher.dispatch<ViewRoomPayload>({
+            action: Action.ViewRoom,
+            room_id: room.roomId,
+            metricsTrigger: "RoomList",
+        });
+    }, [room]);
+
+    return {
+        name,
+        notificationState,
+        showHoverMenu,
+        openRoom,
+        a11yLabel,
+        isBold,
+        isVideoRoom,
+        callConnectionState,
+        hasParticipantInCall,
+        messagePreview,
+        showNotificationDecoration,
+    };
+}
+
+/**
+ * Calculate the values from the notification state
+ * @param notificationState
+ */
+function getNotificationValues(notificationState: RoomNotificationState): {
+    computeA11yLabel: (name: string) => string;
+    isBold: boolean;
+    invited: boolean;
+    hasVisibleNotification: boolean;
+} {
+    const invited = notificationState.invited;
+    const computeA11yLabel = (name: string): string => getA11yLabel(name, notificationState);
+    const isBold = notificationState.hasAnyNotificationOrActivity;
+
+    const hasVisibleNotification = notificationState.hasAnyNotificationOrActivity || notificationState.muted;
+
+    return {
+        computeA11yLabel,
+        isBold,
+        invited,
+        hasVisibleNotification,
+    };
+}
+
+/**
+ * Get the a11y label for the room list item
+ * @param roomName
+ * @param notificationState
+ */
+function getA11yLabel(roomName: string, notificationState: RoomNotificationState): string {
+    if (notificationState.isUnsentMessage) {
+        return _t("a11y|room_messsage_not_sent", {
+            roomName,
+        });
+    } else if (notificationState.invited) {
+        return _t("a11y|room_n_unread_invite", {
+            roomName,
+        });
+    } else if (notificationState.isMention) {
+        return _t("a11y|room_n_unread_messages_mentions", {
+            roomName,
+            count: notificationState.count,
+        });
+    } else if (notificationState.hasUnreadCount) {
+        return _t("a11y|room_n_unread_messages", {
+            roomName,
+            count: notificationState.count,
+        });
+    } else {
+        return _t("room_list|room|open_room", { roomName });
+    }
+}
+
+function useRoomMessagePreview(room: Room): string | undefined {
+    const { shouldShowMessagePreview } = useMessagePreviewToggle();
+    const [previewText, setPreviewText] = useState<string | undefined>(undefined);
+
+    const updatePreview = useCallback(async () => {
+        if (!shouldShowMessagePreview) {
+            setPreviewText(undefined);
+            return;
+        }
+
+        const roomIsDM = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
+        // For the tag, we only care about whether the room is a DM or not as we don't show
+        // display names in previewsd for DMs, so anything else we just say is 'untagged'
+        // (even though it could actually be have other tags: we don't care about them).
+        const messagePreview = await MessagePreviewStore.instance.getPreviewForRoom(
+            room,
+            roomIsDM ? DefaultTagID.DM : DefaultTagID.Untagged,
+        );
+        setPreviewText(messagePreview?.text);
+    }, [room, shouldShowMessagePreview]);
+
+    // MessagePreviewStore and the other AsyncStores need to be converted to TypedEventEmitter
+    useEventEmitter(MessagePreviewStore.instance, MessagePreviewStore.getPreviewChangedEventName(room), () => {
+        updatePreview();
+    });
+
+    useEffect(() => {
+        updatePreview();
+    }, [updatePreview]);
+
+    return previewText;
+}
diff --git a/src/components/viewmodels/roomlist/RoomListViewModel.tsx b/src/components/viewmodels/roomlist/RoomListViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e4e509378280bd253651f84f8af11e59bdb8863a
--- /dev/null
+++ b/src/components/viewmodels/roomlist/RoomListViewModel.tsx
@@ -0,0 +1,99 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useCallback } from "react";
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import { type PrimaryFilter, useFilteredRooms } from "./useFilteredRooms";
+import { createRoom as createRoomFunc, hasCreateRoomRights } from "./utils";
+import { useEventEmitterState } from "../../../hooks/useEventEmitter";
+import { UPDATE_SELECTED_SPACE } from "../../../stores/spaces";
+import SpaceStore from "../../../stores/spaces/SpaceStore";
+import dispatcher from "../../../dispatcher/dispatcher";
+import { Action } from "../../../dispatcher/actions";
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import { useStickyRoomList } from "./useStickyRoomList";
+import { useRoomListNavigation } from "./useRoomListNavigation";
+
+export interface RoomListViewState {
+    /**
+     * Whether the list of rooms is being loaded.
+     */
+    isLoadingRooms: boolean;
+
+    /**
+     * A list of rooms to be displayed in the left panel.
+     */
+    rooms: Room[];
+
+    /**
+     * Create a chat room
+     * @param e - The click event
+     */
+    createChatRoom: () => void;
+
+    /**
+     * Whether the user can create a room in the current space
+     */
+    canCreateRoom: boolean;
+
+    /**
+     * Create a room
+     * @param e - The click event
+     */
+    createRoom: () => void;
+
+    /**
+     * A list of objects that provide the view enough information
+     * to render primary room filters.
+     */
+    primaryFilters: PrimaryFilter[];
+
+    /**
+     * The currently active primary filter.
+     * If no primary filter is active, this will be undefined.
+     */
+    activePrimaryFilter?: PrimaryFilter;
+
+    /**
+     * The index of the active room in the room list.
+     */
+    activeIndex: number | undefined;
+}
+
+/**
+ * View model for the new room list
+ * @see {@link RoomListViewState} for more information about what this view model returns.
+ */
+export function useRoomListViewModel(): RoomListViewState {
+    const matrixClient = useMatrixClientContext();
+    const { isLoadingRooms, primaryFilters, activePrimaryFilter, rooms: filteredRooms } = useFilteredRooms();
+    const { activeIndex, rooms } = useStickyRoomList(filteredRooms);
+
+    useRoomListNavigation(rooms);
+
+    const currentSpace = useEventEmitterState<Room | null>(
+        SpaceStore.instance,
+        UPDATE_SELECTED_SPACE,
+        () => SpaceStore.instance.activeSpaceRoom,
+    );
+    const canCreateRoom = hasCreateRoomRights(matrixClient, currentSpace);
+
+    const createChatRoom = useCallback(() => dispatcher.fire(Action.CreateChat), []);
+    const createRoom = useCallback(() => createRoomFunc(currentSpace), [currentSpace]);
+
+    return {
+        isLoadingRooms,
+        rooms,
+        canCreateRoom,
+        createRoom,
+        createChatRoom,
+        primaryFilters,
+        activePrimaryFilter,
+        activeIndex,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/useFilteredRooms.tsx b/src/components/viewmodels/roomlist/useFilteredRooms.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c3ed4df2ae0a01d9661a82b696a222bac752c75c
--- /dev/null
+++ b/src/components/viewmodels/roomlist/useFilteredRooms.tsx
@@ -0,0 +1,126 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useCallback, useMemo, useState } from "react";
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
+import RoomListStoreV3, { LISTS_LOADED_EVENT, LISTS_UPDATE_EVENT } from "../../../stores/room-list-v3/RoomListStoreV3";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
+import SpaceStore from "../../../stores/spaces/SpaceStore";
+import { UPDATE_SELECTED_SPACE } from "../../../stores/spaces";
+
+/**
+ * Provides information about a primary filter.
+ * A primary filter is a commonly used filter that is given
+ * more precedence in the UI. For eg, primary filters may be
+ * rendered as pills above the room list.
+ */
+export interface PrimaryFilter {
+    // A function to toggle this filter on and off.
+    toggle: () => void;
+    // Whether this filter is currently applied
+    active: boolean;
+    // Text that can be used in the UI to represent this filter.
+    name: string;
+    // The key of the filter
+    key: FilterKey;
+}
+
+interface FilteredRooms {
+    primaryFilters: PrimaryFilter[];
+    isLoadingRooms: boolean;
+    rooms: Room[];
+    /**
+     * The currently active primary filter.
+     * If no primary filter is active, this will be undefined.
+     */
+    activePrimaryFilter?: PrimaryFilter;
+}
+
+const filterKeyToNameMap: Map<FilterKey, TranslationKey> = new Map([
+    [FilterKey.UnreadFilter, _td("room_list|filters|unread")],
+    [FilterKey.PeopleFilter, _td("room_list|filters|people")],
+    [FilterKey.RoomsFilter, _td("room_list|filters|rooms")],
+    [FilterKey.MentionsFilter, _td("room_list|filters|mentions")],
+    [FilterKey.InvitesFilter, _td("room_list|filters|invites")],
+    [FilterKey.FavouriteFilter, _td("room_list|filters|favourite")],
+]);
+
+/**
+ * Track available filters and provide a filtered list of rooms.
+ */
+export function useFilteredRooms(): FilteredRooms {
+    /**
+     * Primary filter refers to the pill based filters
+     * rendered above the room list.
+     */
+    const [primaryFilter, setPrimaryFilter] = useState<FilterKey | undefined>();
+
+    const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
+    const [isLoadingRooms, setIsLoadingRooms] = useState(() => RoomListStoreV3.instance.isLoadingRooms);
+
+    const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => {
+        const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
+        setRooms(newRooms);
+    }, []);
+
+    // Reset filters when active space changes
+    useEventEmitter(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => setPrimaryFilter(undefined));
+
+    const filterUndefined = (array: (FilterKey | undefined)[]): FilterKey[] =>
+        array.filter((f) => f !== undefined) as FilterKey[];
+
+    const getAppliedFilters = (): FilterKey[] => {
+        return filterUndefined([primaryFilter]);
+    };
+
+    useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
+        const filters = getAppliedFilters();
+        updateRoomsFromStore(filters);
+    });
+
+    useEventEmitter(RoomListStoreV3.instance, LISTS_LOADED_EVENT, () => {
+        setIsLoadingRooms(false);
+    });
+
+    /**
+     * This tells the view which primary filters are available, how to toggle them
+     * and whether a given primary filter is active. @see {@link PrimaryFilter}
+     */
+    const primaryFilters = useMemo(() => {
+        const createPrimaryFilter = (key: FilterKey, name: string): PrimaryFilter => {
+            return {
+                toggle: () => {
+                    setPrimaryFilter((currentFilter) => {
+                        const filter = currentFilter === key ? undefined : key;
+                        updateRoomsFromStore(filterUndefined([filter]));
+                        return filter;
+                    });
+                },
+                active: primaryFilter === key,
+                name,
+                key,
+            };
+        };
+        const filters: PrimaryFilter[] = [];
+        for (const [key, name] of filterKeyToNameMap.entries()) {
+            filters.push(createPrimaryFilter(key, _t(name)));
+        }
+        return filters;
+    }, [primaryFilter, updateRoomsFromStore]);
+
+    const activePrimaryFilter = useMemo(() => primaryFilters.find((filter) => filter.active), [primaryFilters]);
+
+    return {
+        isLoadingRooms,
+        primaryFilters,
+        activePrimaryFilter,
+        rooms,
+    };
+}
diff --git a/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx b/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..efb58b3e0493acb99d4144b87231fdd3d30d60da
--- /dev/null
+++ b/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+import { useCallback } from "react";
+
+import SettingsStore from "../../../settings/SettingsStore";
+import { SettingLevel } from "../../../settings/SettingLevel";
+import { useSettingValue } from "../../../hooks/useSettings";
+
+interface MessagePreviewToggleState {
+    shouldShowMessagePreview: boolean;
+    toggleMessagePreview: () => void;
+}
+
+/**
+ * This hook:
+ * - Provides a state that tracks whether message previews are turned on or off.
+ * - Provides a function to toggle message previews.
+ */
+export function useMessagePreviewToggle(): MessagePreviewToggleState {
+    const shouldShowMessagePreview = useSettingValue("RoomList.showMessagePreview");
+
+    const toggleMessagePreview = useCallback((): void => {
+        const toggled = !shouldShowMessagePreview;
+        SettingsStore.setValue("RoomList.showMessagePreview", null, SettingLevel.DEVICE, toggled);
+    }, [shouldShowMessagePreview]);
+
+    return { toggleMessagePreview, shouldShowMessagePreview };
+}
diff --git a/src/components/viewmodels/roomlist/useRoomListNavigation.ts b/src/components/viewmodels/roomlist/useRoomListNavigation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ef979e79c8fbc46b6ad7cd03a664514f10f82c6
--- /dev/null
+++ b/src/components/viewmodels/roomlist/useRoomListNavigation.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Room } from "matrix-js-sdk/src/matrix";
+
+import dispatcher from "../../../dispatcher/dispatcher";
+import { useDispatcher } from "../../../hooks/useDispatcher";
+import { Action } from "../../../dispatcher/actions";
+import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload";
+import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { SdkContextClass } from "../../../contexts/SDKContext";
+import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+
+/**
+ * Hook to navigate the room list using keyboard shortcuts.
+ * It listens to the ViewRoomDelta action and updates the room list accordingly.
+ * @param rooms
+ */
+export function useRoomListNavigation(rooms: Room[]): void {
+    useDispatcher(dispatcher, (payload) => {
+        if (payload.action !== Action.ViewRoomDelta) return;
+        const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
+        if (!roomId) return;
+
+        const { delta, unread } = payload as ViewRoomDeltaPayload;
+        const filteredRooms = unread
+            ? // Filter the rooms to only include unread ones and the active room
+              rooms.filter((room) => {
+                  const state = RoomNotificationStateStore.instance.getRoomState(room);
+                  return room.roomId === roomId || state.isUnread;
+              })
+            : rooms;
+
+        const currentIndex = filteredRooms.findIndex((room) => room.roomId === roomId);
+        if (currentIndex === -1) return;
+
+        // Get the next/previous new room according to the delta
+        // Use slice to loop on the list
+        // If delta is -1 at the start of the list, it will go to the end
+        // If delta is 1 at the end of the list, it will go to the start
+        const [newRoom] = filteredRooms.slice((currentIndex + delta) % filteredRooms.length);
+        if (!newRoom) return;
+
+        dispatcher.dispatch<ViewRoomPayload>({
+            action: Action.ViewRoom,
+            room_id: newRoom.roomId,
+            show_room_tile: true, // to make sure the room gets scrolled into view
+            metricsTrigger: "WebKeyboardShortcut",
+            metricsViaKeyboard: true,
+        });
+    });
+}
diff --git a/src/components/viewmodels/roomlist/useSorter.ts b/src/components/viewmodels/roomlist/useSorter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7a880d430f0c00624fff9beeaa3bf53da848471
--- /dev/null
+++ b/src/components/viewmodels/roomlist/useSorter.ts
@@ -0,0 +1,62 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+import { useState } from "react";
+
+import RoomListStoreV3 from "../../../stores/room-list-v3/RoomListStoreV3";
+import { SortingAlgorithm } from "../../../stores/room-list-v3/skip-list/sorters";
+import SettingsStore from "../../../settings/SettingsStore";
+
+/**
+ * Sorting options made available to the view.
+ */
+export const enum SortOption {
+    Activity = SortingAlgorithm.Recency,
+    AToZ = SortingAlgorithm.Alphabetic,
+}
+
+/**
+ * {@link SortOption} holds almost the same information as
+ * {@link SortingAlgorithm}. This is done intentionally to
+ * prevent the view from having a dependence on the
+ * model (which is the store in this case).
+ */
+const sortingAlgorithmToSortingOption = {
+    [SortingAlgorithm.Alphabetic]: SortOption.AToZ,
+    [SortingAlgorithm.Recency]: SortOption.Activity,
+};
+
+const sortOptionToSortingAlgorithm = {
+    [SortOption.AToZ]: SortingAlgorithm.Alphabetic,
+    [SortOption.Activity]: SortingAlgorithm.Recency,
+};
+
+interface SortState {
+    sort: (option: SortOption) => void;
+    activeSortOption: SortOption;
+}
+
+/**
+ * This hook does two things:
+ * - Provides a way to track the currently active sort option.
+ * - Provides a function to resort the room list.
+ */
+export function useSorter(): SortState {
+    const [activeSortingAlgorithm, setActiveSortingAlgorithm] = useState(() =>
+        SettingsStore.getValue("RoomList.preferredSorting"),
+    );
+
+    const sort = (option: SortOption): void => {
+        const sortingAlgorithm = sortOptionToSortingAlgorithm[option];
+        RoomListStoreV3.instance.resort(sortingAlgorithm);
+        setActiveSortingAlgorithm(sortingAlgorithm);
+    };
+
+    return {
+        sort,
+        activeSortOption: sortingAlgorithmToSortingOption[activeSortingAlgorithm!],
+    };
+}
diff --git a/src/components/viewmodels/roomlist/useStickyRoomList.tsx b/src/components/viewmodels/roomlist/useStickyRoomList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..06feb58581586f6daeac4030d3a1974f7ca1642a
--- /dev/null
+++ b/src/components/viewmodels/roomlist/useStickyRoomList.tsx
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { useCallback, useEffect, useRef, useState } from "react";
+
+import { SdkContextClass } from "../../../contexts/SDKContext";
+import { useDispatcher } from "../../../hooks/useDispatcher";
+import dispatcher from "../../../dispatcher/dispatcher";
+import { Action } from "../../../dispatcher/actions";
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Optional } from "matrix-events-sdk";
+import SpaceStore from "../../../stores/spaces/SpaceStore";
+
+function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
+    const index = rooms.findIndex((room) => room.roomId === roomId);
+    return index === -1 ? undefined : index;
+}
+
+function getRoomsWithStickyRoom(
+    rooms: Room[],
+    oldIndex: number | undefined,
+    newIndex: number | undefined,
+    isRoomChange: boolean,
+): { newRooms: Room[]; newIndex: number | undefined } {
+    const updated = { newIndex, newRooms: rooms };
+    if (isRoomChange) {
+        /*
+         * When opening another room, the index should obviously change.
+         */
+        return updated;
+    }
+    if (newIndex === undefined || oldIndex === undefined) {
+        /*
+         * If oldIndex is undefined, then there was no active room before.
+         * So nothing to do in regards to sticky room.
+         * Similarly, if newIndex is undefined, there's no active room anymore.
+         */
+        return updated;
+    }
+    if (newIndex === oldIndex) {
+        /*
+         * If the index hasn't changed, we have nothing to do.
+         */
+        return updated;
+    }
+    if (oldIndex > rooms.length - 1) {
+        /*
+         * If the old index falls out of the bounds of the rooms array
+         * (usually because rooms were removed), we can no longer place
+         * the active room in the same old index.
+         */
+        return updated;
+    }
+
+    /*
+     * Making the active room sticky is as simple as removing it from
+     * its new index and placing it in the old index.
+     */
+    const newRooms = [...rooms];
+    const [newRoom] = newRooms.splice(newIndex, 1);
+    newRooms.splice(oldIndex, 0, newRoom);
+
+    return { newIndex: oldIndex, newRooms };
+}
+
+interface StickyRoomListResult {
+    /**
+     * List of rooms with sticky active room.
+     */
+    rooms: Room[];
+    /**
+     * Index of the active room in the room list.
+     */
+    activeIndex: number | undefined;
+}
+
+/**
+ * - Provides a list of rooms such that the active room is sticky i.e the active room is kept
+ * in the same index even when the order of rooms in the list changes.
+ * - Provides the index of the active room.
+ * @param rooms list of rooms
+ * @see {@link StickyRoomListResult} details what this hook returns..
+ */
+export function useStickyRoomList(rooms: Room[]): StickyRoomListResult {
+    const [listState, setListState] = useState<{ index: number | undefined; roomsWithStickyRoom: Room[] }>({
+        index: undefined,
+        roomsWithStickyRoom: rooms,
+    });
+
+    const currentSpaceRef = useRef(SpaceStore.instance.activeSpace);
+
+    const updateRoomsAndIndex = useCallback(
+        (newRoomId: string | null, isRoomChange: boolean = false) => {
+            setListState((current) => {
+                const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId();
+                const newActiveIndex = getIndexByRoomId(rooms, activeRoomId);
+                const oldIndex = current.index;
+                const { newIndex, newRooms } = getRoomsWithStickyRoom(rooms, oldIndex, newActiveIndex, isRoomChange);
+                return { index: newIndex, roomsWithStickyRoom: newRooms };
+            });
+        },
+        [rooms],
+    );
+
+    // Re-calculate the index when the active room has changed.
+    useDispatcher(dispatcher, (payload) => {
+        if (payload.action === Action.ActiveRoomChanged) updateRoomsAndIndex(payload.newRoomId, true);
+    });
+
+    // Re-calculate the index when the list of rooms has changed.
+    useEffect(() => {
+        let newRoomId: string | null = null;
+        let isRoomChange = false;
+        const newSpace = SpaceStore.instance.activeSpace;
+        if (currentSpaceRef.current !== newSpace) {
+            /*
+            If the space has changed, we check if we can immediately set the active
+            index to the last opened room in that space. Otherwise, we might see a
+            flicker because of the delay between the space change event and
+            active room change dispatch.
+            */
+            newRoomId = SpaceStore.instance.getLastSelectedRoomIdForSpace(newSpace);
+            isRoomChange = true;
+            currentSpaceRef.current = newSpace;
+        }
+        updateRoomsAndIndex(newRoomId, isRoomChange);
+    }, [rooms, updateRoomsAndIndex]);
+
+    return { activeIndex: listState.index, rooms: listState.roomsWithStickyRoom };
+}
diff --git a/src/components/viewmodels/roomlist/utils.ts b/src/components/viewmodels/roomlist/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dfa20e0d1cb50c55aa890a84e7659a2702acb17c
--- /dev/null
+++ b/src/components/viewmodels/roomlist/utils.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Room, KnownMembership, EventTimeline, EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
+
+import { isKnockDenied } from "../../../utils/membership";
+import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
+import { UIComponent } from "../../../settings/UIFeature";
+import { showCreateNewRoom } from "../../../utils/space";
+import dispatcher from "../../../dispatcher/dispatcher";
+import { Action } from "../../../dispatcher/actions";
+
+/**
+ * Check if the user has access to the options menu.
+ * @param room
+ */
+export function hasAccessToOptionsMenu(room: Room): boolean {
+    return (
+        room.getMyMembership() === KnownMembership.Invite ||
+        (room.getMyMembership() !== KnownMembership.Knock &&
+            !isKnockDenied(room) &&
+            shouldShowComponent(UIComponent.RoomOptionsMenu))
+    );
+}
+
+/**
+ * Check if the user has access to the notification menu.
+ * @param room
+ * @param isGuest
+ * @param isArchived
+ */
+export function hasAccessToNotificationMenu(room: Room, isGuest: boolean, isArchived: boolean): boolean {
+    return !isGuest && !isArchived && hasAccessToOptionsMenu(room);
+}
+
+/**
+ * Create a room
+ * @param space - The space to create the room in
+ */
+export async function createRoom(space?: Room | null): Promise<void> {
+    if (space) {
+        await showCreateNewRoom(space);
+    } else {
+        dispatcher.fire(Action.CreateRoom);
+    }
+}
+
+/**
+ * Check if the user has the rights to create a room in the given space
+ * If the space is not provided, it will check if the user has the rights to create a room in general
+ * @param matrixClient
+ * @param space
+ */
+export function hasCreateRoomRights(matrixClient: MatrixClient, space?: Room | null): boolean {
+    const hasUIRight = shouldShowComponent(UIComponent.CreateRooms);
+    if (!space || !hasUIRight) return hasUIRight;
+
+    return Boolean(
+        space
+            ?.getLiveTimeline()
+            .getState(EventTimeline.FORWARDS)
+            ?.maySendStateEvent(EventType.RoomAvatar, matrixClient.getSafeUserId()),
+    );
+}
diff --git a/src/components/viewmodels/rooms/UserIdentityWarningViewModel.tsx b/src/components/viewmodels/rooms/UserIdentityWarningViewModel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9e9f0f7b3a946cebcf5f08554c279ab6fbe3da32
--- /dev/null
+++ b/src/components/viewmodels/rooms/UserIdentityWarningViewModel.tsx
@@ -0,0 +1,192 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { EventType, type MatrixEvent, type Room, type RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import { throttle } from "lodash";
+import { logger } from "matrix-js-sdk/src/logger";
+
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
+import { useTypedEventEmitter } from "../../../hooks/useEventEmitter.ts";
+
+export type ViolationType = "PinViolation" | "VerificationViolation";
+
+/**
+ * Represents a prompt to the user about a violation in the room.
+ * The type of violation and the member it relates to are included.
+ * If the type is "VerificationViolation", the warning is critical and should be reported with more urgency.
+ */
+export type ViolationPrompt = {
+    member: RoomMember;
+    type: ViolationType;
+};
+
+/**
+ * The state of the UserIdentityWarningViewModel.
+ * This includes the current prompt to show to the user and a callback to handle button clicks.
+ * If currentPrompt is undefined, there are no violations to show.
+ */
+export interface UserIdentityWarningState {
+    currentPrompt?: ViolationPrompt;
+    dispatchAction: (action: UserIdentityWarningViewModelAction) => void;
+}
+
+/**
+ * List of actions that can be dispatched to the UserIdentityWarningViewModel.
+ */
+export type UserIdentityWarningViewModelAction =
+    | { type: "PinUserIdentity"; userId: string }
+    | { type: "WithdrawVerification"; userId: string };
+
+/**
+ * Maps a list of room members to a list of violations.
+ * Checks for all members in the room to see if they have any violations.
+ * If no violations are found, an empty list is returned.
+ *
+ * @param cryptoApi
+ * @param members - The list of room members to check for violations.
+ */
+async function mapToViolations(cryptoApi: CryptoApi, members: RoomMember[]): Promise<ViolationPrompt[]> {
+    const violationList = new Array<ViolationPrompt>();
+    for (const member of members) {
+        const verificationStatus = await cryptoApi.getUserVerificationStatus(member.userId);
+        if (verificationStatus.wasCrossSigningVerified() && !verificationStatus.isCrossSigningVerified()) {
+            violationList.push({ member, type: "VerificationViolation" });
+        } else if (verificationStatus.needsUserApproval) {
+            violationList.push({ member, type: "PinViolation" });
+        }
+    }
+    return violationList;
+}
+
+export function useUserIdentityWarningViewModel(room: Room, key: string): UserIdentityWarningState {
+    const cli = useMatrixClientContext();
+    const crypto = cli.getCrypto();
+
+    const [members, setMembers] = useState<RoomMember[]>([]);
+    const [currentPrompt, setCurrentPrompt] = useState<ViolationPrompt | undefined>(undefined);
+
+    const loadViolations = useMemo(
+        () =>
+            throttle(async (): Promise<void> => {
+                const isEncrypted = crypto && (await crypto.isEncryptionEnabledInRoom(room.roomId));
+                if (!isEncrypted) {
+                    setMembers([]);
+                    setCurrentPrompt(undefined);
+                    return;
+                }
+
+                const targetMembers = await room.getEncryptionTargetMembers();
+                setMembers(targetMembers);
+                const violations = await mapToViolations(crypto, targetMembers);
+
+                let candidatePrompt: ViolationPrompt | undefined;
+                if (violations.length > 0) {
+                    // sort by user ID to ensure consistent ordering
+                    const sortedViolations = violations.sort((a, b) => a.member.userId.localeCompare(b.member.userId));
+                    candidatePrompt = sortedViolations[0];
+                } else {
+                    candidatePrompt = undefined;
+                }
+
+                // is the current prompt still valid?
+                setCurrentPrompt((existingPrompt): ViolationPrompt | undefined => {
+                    if (existingPrompt && violations.includes(existingPrompt)) {
+                        return existingPrompt;
+                    } else if (candidatePrompt) {
+                        return candidatePrompt;
+                    } else {
+                        return undefined;
+                    }
+                });
+            }),
+        [crypto, room],
+    );
+
+    // We need to listen for changes to the members list
+    useTypedEventEmitter(
+        cli,
+        RoomStateEvent.Events,
+        useCallback(
+            async (event: MatrixEvent): Promise<void> => {
+                if (!crypto || event.getRoomId() !== room.roomId) {
+                    return;
+                }
+                let shouldRefresh = false;
+
+                const eventType = event.getType();
+
+                if (eventType === EventType.RoomEncryption && event.getStateKey() === "") {
+                    // Room is now encrypted, so we can initialise the component.
+                    shouldRefresh = true;
+                } else if (eventType == EventType.RoomMember) {
+                    // We're processing an m.room.member event
+                    // Something has changed in membership, someone joined or someone left or
+                    // someone changed their display name. Anyhow let's refresh.
+                    const userId = event.getStateKey();
+                    shouldRefresh = !!userId;
+                }
+
+                if (shouldRefresh) {
+                    loadViolations().catch((e) => {
+                        logger.error("Error refreshing UserIdentityWarningViewModel:", e);
+                    });
+                }
+            },
+            [crypto, room, loadViolations],
+        ),
+    );
+
+    // We need to listen for changes to the verification status of the members to refresh violations
+    useTypedEventEmitter(
+        cli,
+        CryptoEvent.UserTrustStatusChanged,
+        useCallback(
+            (userId: string): void => {
+                if (members.find((m) => m.userId == userId)) {
+                    // This member is tracked, we need to refresh.
+                    // refresh all for now?
+                    // As a later optimisation we could store the current violations and only update the relevant one.
+                    loadViolations().catch((e) => {
+                        logger.error("Error refreshing UserIdentityWarning:", e);
+                    });
+                }
+            },
+            [loadViolations, members],
+        ),
+    );
+
+    useEffect(() => {
+        loadViolations().catch((e) => {
+            logger.error("Error initialising UserIdentityWarning:", e);
+        });
+    }, [loadViolations]);
+
+    const dispatchAction = useCallback(
+        (action: UserIdentityWarningViewModelAction): void => {
+            if (!crypto) {
+                return;
+            }
+            if (action.type === "PinUserIdentity") {
+                crypto.pinCurrentUserIdentity(action.userId).catch((e) => {
+                    logger.error("Error pinning user identity:", e);
+                });
+            } else if (action.type === "WithdrawVerification") {
+                crypto.withdrawVerificationRequirement(action.userId).catch((e) => {
+                    logger.error("Error withdrawing verification requirement:", e);
+                });
+            }
+        },
+        [crypto],
+    );
+
+    return {
+        currentPrompt,
+        dispatchAction,
+    };
+}
diff --git a/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts b/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee301bd27fce300b94db2e482e6f2ab909eed9be
--- /dev/null
+++ b/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts
@@ -0,0 +1,116 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useCallback, useEffect, useState } from "react";
+import { logger } from "matrix-js-sdk/src/logger";
+
+import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
+import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
+
+interface KeyStoragePanelState {
+    /**
+     * Whether the app's "key storage" option should show as enabled to the user,
+     * or 'undefined' if the state is still loading.
+     */
+    isEnabled: boolean | undefined;
+
+    /**
+     * A function that can be called to enable or disable key storage.
+     * @param enable True to turn key storage on or false to turn it off
+     */
+    setEnabled: (enable: boolean) => void;
+
+    /**
+     * True if the state is still loading for the first time
+     */
+    loading: boolean;
+
+    /**
+     * True if the status is in the process of being changed
+     */
+    busy: boolean;
+}
+
+/** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */
+export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
+    const [isEnabled, setIsEnabled] = useState<boolean | undefined>(undefined);
+    const [loading, setLoading] = useState(true);
+    // Whilst the change is being made, the toggle will reflect the pending value rather than the actual state
+    const [pendingValue, setPendingValue] = useState<boolean | undefined>(undefined);
+
+    const matrixClient = useMatrixClientContext();
+
+    const checkStatus = useCallback(async () => {
+        const crypto = matrixClient.getCrypto();
+        if (!crypto) {
+            logger.error("Can't check key backup status: no crypto module available");
+            return;
+        }
+        // The toggle is enabled only if this device will upload megolm keys to the backup.
+        // This is consistent with EX.
+        const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
+        setIsEnabled(activeBackupVersion !== null);
+    }, [matrixClient]);
+
+    useEffect(() => {
+        (async () => {
+            await checkStatus();
+            setLoading(false);
+        })();
+    }, [checkStatus]);
+
+    const setEnabled = useCallback(
+        async (enable: boolean) => {
+            setPendingValue(enable);
+            try {
+                // stop the device listener since enabling or (especially) disabling key storage must be
+                // done with a sequence of API calls that will put the account in a slightly different
+                // state each time, so suppress any warning toasts until the process is finished (when
+                // we'll turn it back on again.)
+                DeviceListener.sharedInstance().stop();
+
+                const crypto = matrixClient.getCrypto();
+                if (!crypto) {
+                    logger.error("Can't change key backup status: no crypto module available");
+                    return;
+                }
+                if (enable) {
+                    // If there is no existing key backup on the server, create one.
+                    // `resetKeyBackup` will delete any existing backup, so we only do this if there is no existing backup.
+                    const currentKeyBackup = await crypto.checkKeyBackupAndEnable();
+                    if (currentKeyBackup === null) {
+                        await crypto.resetKeyBackup();
+
+                        // resetKeyBackup fires this off in the background without waiting, so we need to do it
+                        // explicitly and wait for it, otherwise it won't be enabled yet when we check again.
+                        await crypto.checkKeyBackupAndEnable();
+                    }
+
+                    // Set the flag so that EX no longer thinks the user wants backup disabled
+                    await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: false });
+                } else {
+                    // This method will delete the key backup as well as server side recovery keys and other
+                    // server-side crypto data.
+                    await crypto.disableKeyStorage();
+
+                    // Set a flag to say that the user doesn't want key backup.
+                    // Element X uses this to determine whether to set up automatically,
+                    // so this will stop EX turning it back on spontaneously.
+                    await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
+                }
+
+                await checkStatus();
+            } finally {
+                setPendingValue(undefined);
+                DeviceListener.sharedInstance().start(matrixClient);
+            }
+        },
+        [setPendingValue, checkStatus, matrixClient],
+    );
+
+    return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };
+}
diff --git a/src/components/views/audio_messages/AudioPlayer.tsx b/src/components/views/audio_messages/AudioPlayer.tsx
index 63c77108e37421c60a9f5820ad3d8ab12067ea75..6f674e504d1ea3fc42c7f73fea5ea49e7d10bca3 100644
--- a/src/components/views/audio_messages/AudioPlayer.tsx
+++ b/src/components/views/audio_messages/AudioPlayer.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 import PlayPauseButton from "./PlayPauseButton";
 import { formatBytes } from "../../../utils/FormattingUtils";
diff --git a/src/components/views/audio_messages/AudioPlayerBase.tsx b/src/components/views/audio_messages/AudioPlayerBase.tsx
index 97cbad0fc2e9b4afd441888758a68ef09f65d6f7..71bea008a240c406e7b163692de3ac673a010172 100644
--- a/src/components/views/audio_messages/AudioPlayerBase.tsx
+++ b/src/components/views/audio_messages/AudioPlayerBase.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode, RefObject } from "react";
+import React, { createRef, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { Playback, PlaybackState } from "../../../audio/Playback";
+import { type Playback, type PlaybackState } from "../../../audio/Playback";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 import { _t } from "../../../languageHandler";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
-import SeekBar from "./SeekBar";
-import PlayPauseButton from "./PlayPauseButton";
+import type SeekBar from "./SeekBar";
+import type PlayPauseButton from "./PlayPauseButton";
 
 export interface IProps {
     // Playback instance to render. Cannot change during component lifecycle: create
@@ -31,8 +31,8 @@ interface IState {
 }
 
 export default abstract class AudioPlayerBase<T extends IProps = IProps> extends React.PureComponent<T, IState> {
-    protected seekRef: RefObject<SeekBar> = createRef();
-    protected playPauseRef: RefObject<PlayPauseButton> = createRef();
+    protected seekRef = createRef<SeekBar>();
+    protected playPauseRef = createRef<PlayPauseButton>();
 
     public constructor(props: T) {
         super(props);
diff --git a/src/components/views/audio_messages/Clock.tsx b/src/components/views/audio_messages/Clock.tsx
index c8f27c3f9cadb295f645120de730afc4de08118c..d9f1a3d6c51cdaaf7b981810de0bccf7d666b23b 100644
--- a/src/components/views/audio_messages/Clock.tsx
+++ b/src/components/views/audio_messages/Clock.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLProps } from "react";
+import React, { type HTMLProps } from "react";
 import { Temporal } from "temporal-polyfill";
 
 import { formatSeconds } from "../../../DateUtils";
diff --git a/src/components/views/audio_messages/DurationClock.tsx b/src/components/views/audio_messages/DurationClock.tsx
index 1a84a1595535b460d282fb512904cec78057d67d..920baa99bedeb7653124994a6fb7a9c5f1c6c4d4 100644
--- a/src/components/views/audio_messages/DurationClock.tsx
+++ b/src/components/views/audio_messages/DurationClock.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import Clock from "./Clock";
-import { Playback } from "../../../audio/Playback";
+import { type Playback } from "../../../audio/Playback";
 
 interface IProps {
     playback: Playback;
diff --git a/src/components/views/audio_messages/LiveRecordingClock.tsx b/src/components/views/audio_messages/LiveRecordingClock.tsx
index bd8b8d5d2350e1e3356e1c5a91e2c579bb35dfff..74415566a6be8a5b050d1c8000cb278f86430893 100644
--- a/src/components/views/audio_messages/LiveRecordingClock.tsx
+++ b/src/components/views/audio_messages/LiveRecordingClock.tsx
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 
-import { IRecordingUpdate } from "../../../audio/VoiceRecording";
+import { type IRecordingUpdate } from "../../../audio/VoiceRecording";
 import Clock from "./Clock";
 import { MarkedExecution } from "../../../utils/MarkedExecution";
-import { VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
+import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
 
 interface IProps {
     recorder: VoiceMessageRecording;
diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx
index 18c1ca1aa25e4f29b97f4918a010a51c0bff5933..2c388fe8678229d728186125220db5aad8808472 100644
--- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx
+++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx
@@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 
-import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES } from "../../../audio/VoiceRecording";
+import { type IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES } from "../../../audio/VoiceRecording";
 import { arrayFastResample, arraySeed } from "../../../utils/arrays";
 import Waveform from "./Waveform";
 import { MarkedExecution } from "../../../utils/MarkedExecution";
-import { VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
+import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
 
 interface IProps {
     recorder: VoiceMessageRecording;
diff --git a/src/components/views/audio_messages/PlayPauseButton.tsx b/src/components/views/audio_messages/PlayPauseButton.tsx
index 1b197c6bad757208211518a49ac4bdad1b1f57a7..a4457b22305ea4e536d3d0d0ed682fbbf59e3e9e 100644
--- a/src/components/views/audio_messages/PlayPauseButton.tsx
+++ b/src/components/views/audio_messages/PlayPauseButton.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
-import { Playback, PlaybackState } from "../../../audio/Playback";
-import AccessibleButton, { ButtonProps } from "../elements/AccessibleButton";
+import { type Playback, PlaybackState } from "../../../audio/Playback";
+import AccessibleButton, { type ButtonProps } from "../elements/AccessibleButton";
 
 type Props = Omit<ButtonProps<"div">, "title" | "onClick" | "disabled" | "element" | "ref"> & {
     // Playback instance to manipulate. Cannot change during the component lifecycle.
diff --git a/src/components/views/audio_messages/PlaybackClock.tsx b/src/components/views/audio_messages/PlaybackClock.tsx
index 999b5398b1523a6ac494c91721b0874eae4e6c9f..0d697933794de5f9d505d237918bbf653bf0990b 100644
--- a/src/components/views/audio_messages/PlaybackClock.tsx
+++ b/src/components/views/audio_messages/PlaybackClock.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import Clock from "./Clock";
-import { Playback, PlaybackState } from "../../../audio/Playback";
+import { type Playback, PlaybackState } from "../../../audio/Playback";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 
 interface IProps {
diff --git a/src/components/views/audio_messages/PlaybackWaveform.tsx b/src/components/views/audio_messages/PlaybackWaveform.tsx
index a5113dd042d4e30f9d481cf6f5139f686ae87060..c1f470f4b1ad4728934866897d64662a79674cbb 100644
--- a/src/components/views/audio_messages/PlaybackWaveform.tsx
+++ b/src/components/views/audio_messages/PlaybackWaveform.tsx
@@ -10,7 +10,7 @@ import React from "react";
 
 import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
 import Waveform from "./Waveform";
-import { Playback } from "../../../audio/Playback";
+import { type Playback } from "../../../audio/Playback";
 import { percentageOf } from "../../../utils/numbers";
 import { PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/consts";
 
diff --git a/src/components/views/audio_messages/RecordingPlayback.tsx b/src/components/views/audio_messages/RecordingPlayback.tsx
index 4c030f81efcd3a114931fcc602ddf2707c345cca..c0e3337787731ec63258dc22c598a23aae7d3256 100644
--- a/src/components/views/audio_messages/RecordingPlayback.tsx
+++ b/src/components/views/audio_messages/RecordingPlayback.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 import PlayPauseButton from "./PlayPauseButton";
 import PlaybackClock from "./PlaybackClock";
-import AudioPlayerBase, { IProps as IAudioPlayerBaseProps } from "./AudioPlayerBase";
+import AudioPlayerBase, { type IProps as IAudioPlayerBaseProps } from "./AudioPlayerBase";
 import SeekBar from "./SeekBar";
 import PlaybackWaveform from "./PlaybackWaveform";
 import { PlaybackState } from "../../../audio/Playback";
diff --git a/src/components/views/audio_messages/SeekBar.tsx b/src/components/views/audio_messages/SeekBar.tsx
index 1a79f5be069be5d5937917e6045a68bf22a3abd2..587975ce1b9e2909a409d5c2b36b81b89c765441 100644
--- a/src/components/views/audio_messages/SeekBar.tsx
+++ b/src/components/views/audio_messages/SeekBar.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
+import React, { type ChangeEvent, type CSSProperties, type ReactNode } from "react";
 
-import { PlaybackInterface } from "../../../audio/Playback";
+import { type PlaybackInterface } from "../../../audio/Playback";
 import { MarkedExecution } from "../../../utils/MarkedExecution";
 import { percentageOf } from "../../../utils/numbers";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/audio_messages/Waveform.tsx b/src/components/views/audio_messages/Waveform.tsx
index 83d02b81fd0132f4ca9325d5a0cf9c4e88ca157c..115b310f4ca13d36e724d2dcd7eca1ff20d2ef6d 100644
--- a/src/components/views/audio_messages/Waveform.tsx
+++ b/src/components/views/audio_messages/Waveform.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { CSSProperties } from "react";
+import React, { type CSSProperties } from "react";
 import classNames from "classnames";
 
 interface WaveformCSSProperties extends CSSProperties {
@@ -18,8 +18,6 @@ interface IProps {
     progress: number; // percent complete, 0-1, default 100%
 }
 
-interface IState {}
-
 /**
  * A simple waveform component. This renders bars (centered vertically) for each
  * height provided in the component properties. Updating the properties will update
@@ -28,7 +26,7 @@ interface IState {}
  * For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
  * "filled", as a demonstration of the progress property.
  */
-export default class Waveform extends React.PureComponent<IProps, IState> {
+export default class Waveform extends React.PureComponent<IProps> {
     public static defaultProps = {
         progress: 1,
     };
diff --git a/src/components/views/auth/AuthBody.tsx b/src/components/views/auth/AuthBody.tsx
index b83955fcd99c4ad0ff44c2a59105f42e4999d5ea..98ed54858438a74450dd0ef0a2b075359a791703 100644
--- a/src/components/views/auth/AuthBody.tsx
+++ b/src/components/views/auth/AuthBody.tsx
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { PropsWithChildren } from "react";
+import React, { type JSX, type PropsWithChildren } from "react";
 
 interface Props {
     className?: string;
diff --git a/src/components/views/auth/AuthFooter.tsx b/src/components/views/auth/AuthFooter.tsx
index 472ff53f09fc9c17902523e5a2077d129ffd7c38..1942bf04318304c23795cc99e4f64277f78e6794 100644
--- a/src/components/views/auth/AuthFooter.tsx
+++ b/src/components/views/auth/AuthFooter.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type JSX, type ReactElement } from "react";
 
 import SdkConfig from "../../../SdkConfig";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/auth/CaptchaForm.tsx b/src/components/views/auth/CaptchaForm.tsx
index d019ba234cf6755b2fb2618623773428012a1765..ca25ae180de6ee63b5e0d95e0e1e0f551af9861f 100644
--- a/src/components/views/auth/CaptchaForm.tsx
+++ b/src/components/views/auth/CaptchaForm.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
+import React, { type JSX, createRef } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/auth/CompleteSecurityBody.tsx b/src/components/views/auth/CompleteSecurityBody.tsx
index 77808ec71c7378eeb1d94b21f43ad4b185b0036d..01eb0ec6f5a27f5a24d3f7da417d12edd5d207a7 100644
--- a/src/components/views/auth/CompleteSecurityBody.tsx
+++ b/src/components/views/auth/CompleteSecurityBody.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 export default class CompleteSecurityBody extends React.PureComponent<{ children: ReactNode }> {
     public render(): React.ReactNode {
diff --git a/src/components/views/auth/CountryDropdown.tsx b/src/components/views/auth/CountryDropdown.tsx
index 7e8d669d15d7b7cf8f42cb56be0ecfa65eea2f9c..f25ed95e968201cb76981fbbdb0f0bedf46de067 100644
--- a/src/components/views/auth/CountryDropdown.tsx
+++ b/src/components/views/auth/CountryDropdown.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 
-import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from "../../../phonenumber";
+import { COUNTRIES, getEmojiFlag, type PhoneNumberCountryDefinition } from "../../../phonenumber";
 import SdkConfig from "../../../SdkConfig";
 import { _t, getUserLanguage } from "../../../languageHandler";
 import Dropdown from "../elements/Dropdown";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 interface InternationalisedCountry extends PhoneNumberCountryDefinition {
     name: string; // already translated to the user's locale
diff --git a/src/components/views/auth/EmailField.tsx b/src/components/views/auth/EmailField.tsx
index ddc20c0bd1dfd1cd6ad8666de44d56957c597f7d..fb420ed459ba6e6408118db96a29f6bd9e32e8ab 100644
--- a/src/components/views/auth/EmailField.tsx
+++ b/src/components/views/auth/EmailField.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, PureComponent, RefCallback, RefObject } from "react";
+import React, { type ComponentProps, PureComponent, type Ref } from "react";
 
-import Field, { IInputProps } from "../elements/Field";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
+import Field, { type IInputProps } from "../elements/Field";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
 import * as Email from "../../../email";
 
 interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
     id?: string;
-    fieldRef?: RefCallback<Field> | RefObject<Field>;
+    fieldRef?: Ref<Field>;
     value: string;
     autoFocus?: boolean;
 
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
index d493e5c3cac2b0aa208a93a2fbc631fc422a01d0..a9bf8b7597017fe77ea985aa16f2b6e046c57b43 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx
@@ -7,23 +7,24 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { AuthType, AuthDict, IInputs, IStageStatus } from "matrix-js-sdk/src/interactive-auth";
+import { type InternationalisedPolicy, type Terms, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { AuthType, type AuthDict, type IInputs, type IStageStatus } from "matrix-js-sdk/src/interactive-auth";
 import { logger } from "matrix-js-sdk/src/logger";
-import React, { ChangeEvent, createRef, FormEvent, Fragment } from "react";
-import { Button, Text } from "@vector-im/compound-web";
+import React, { type JSX, type ChangeEvent, createRef, type FormEvent, Fragment } from "react";
+import { Button } from "@vector-im/compound-web";
 import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
+import UserProfileSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile-solid";
 
 import EmailPromptIcon from "../../../../res/img/element-icons/email-prompt.svg";
 import { _t } from "../../../languageHandler";
-import SettingsStore from "../../../settings/SettingsStore";
-import { LocalisedPolicy, Policies } from "../../../Terms";
 import { AuthHeaderModifier } from "../../structures/auth/header/AuthHeaderModifier";
-import AccessibleButton, { AccessibleButtonKind, ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type AccessibleButtonKind, type ButtonEvent } from "../elements/AccessibleButton";
 import Field from "../elements/Field";
 import Spinner from "../elements/Spinner";
 import CaptchaForm from "./CaptchaForm";
-import { Flex } from "../../utils/Flex";
+import { pickBestPolicyLanguage } from "../../../Terms.ts";
+import { EncryptionCardButtons } from "../settings/encryption/EncryptionCardButtons.tsx";
+import { EncryptionCard } from "../settings/encryption/EncryptionCard.tsx";
 
 /* This file contains a collection of components which are used by the
  * InteractiveAuth to prompt the user to enter the information needed
@@ -86,7 +87,6 @@ interface IAuthEntryProps {
     requestEmailToken?: () => Promise<void>;
     fail: (error: Error) => void;
     clientSecret: string;
-    showContinue: boolean;
 }
 
 interface IPasswordAuthEntryState {
@@ -235,12 +235,10 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
 }
 
 interface ITermsAuthEntryProps extends IAuthEntryProps {
-    stageParams?: {
-        policies?: Policies;
-    };
+    stageParams?: Partial<Terms>;
 }
 
-interface LocalisedPolicyWithId extends LocalisedPolicy {
+interface LocalisedPolicyWithId extends InternationalisedPolicy {
     id: string;
 }
 
@@ -278,7 +276,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
         // }
 
         const allPolicies = this.props.stageParams?.policies || {};
-        const prefLang = SettingsStore.getValue("language");
         const initToggles: Record<string, boolean> = {};
         const pickedPolicies: {
             id: string;
@@ -287,17 +284,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
         }[] = [];
         for (const policyId of Object.keys(allPolicies)) {
             const policy = allPolicies[policyId];
-
-            // Pick a language based on the user's language, falling back to english,
-            // and finally to the first language available. If there's still no policy
-            // available then the homeserver isn't respecting the spec.
-            let langPolicy: LocalisedPolicy | undefined = policy[prefLang];
-            if (!langPolicy) langPolicy = policy["en"];
-            if (!langPolicy) {
-                // last resort
-                const firstLang = Object.keys(policy).find((e) => e !== "version");
-                langPolicy = firstLang ? policy[firstLang] : undefined;
-            }
+            const langPolicy = pickBestPolicyLanguage(policy);
             if (!langPolicy) throw new Error("Failed to find a policy to show the user");
 
             initToggles[policyId] = false;
@@ -375,9 +362,11 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
             );
         }
 
-        let submitButton: JSX.Element | undefined;
-        if (this.props.showContinue !== false) {
-            submitButton = (
+        return (
+            <div className="mx_InteractiveAuthEntryComponents">
+                <p>{_t("auth|uia|terms")}</p>
+                {checkboxes}
+                {errorSection}
                 <AccessibleButton
                     kind="primary"
                     className="mx_InteractiveAuthEntryComponents_termsSubmit"
@@ -386,15 +375,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
                 >
                     {_t("action|accept")}
                 </AccessibleButton>
-            );
-        }
-
-        return (
-            <div className="mx_InteractiveAuthEntryComponents">
-                <p>{_t("auth|uia|terms")}</p>
-                {checkboxes}
-                {errorSection}
-                {submitButton}
             </div>
         );
     }
@@ -908,7 +888,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
     }
 }
 
-export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> {
+export class FallbackAuthEntry<T extends object> extends React.Component<IAuthEntryProps & T> {
     protected popupWindow: Window | null;
     protected fallbackButton = createRef<HTMLDivElement>();
 
@@ -993,9 +973,14 @@ export class MasUnlockCrossSigningAuthEntry extends FallbackAuthEntry<{
 
     public render(): React.ReactNode {
         return (
-            <div>
-                <Text>{_t("auth|uia|mas_cross_signing_reset_description")}</Text>
-                <Flex gap="var(--cpd-space-4x)">
+            <EncryptionCard
+                Icon={UserProfileSolidIcon}
+                title={_t("auth|uia|mas_cross_signing_reset_title")}
+                description={_t("auth|uia|mas_cross_signing_reset_description", {
+                    serverName: this.props.matrixClient.getDomain(),
+                })}
+            >
+                <EncryptionCardButtons>
                     <Button
                         Icon={PopOutIcon}
                         onClick={this.onGoToAccountClick}
@@ -1005,11 +990,11 @@ export class MasUnlockCrossSigningAuthEntry extends FallbackAuthEntry<{
                     >
                         {_t("auth|uia|mas_cross_signing_reset_cta")}
                     </Button>
-                    <Button onClick={this.onRetryClick} kind="secondary" className="mx_Dialog_nonDialogButton">
+                    <Button onClick={this.onRetryClick} kind="tertiary" className="mx_Dialog_nonDialogButton">
                         {_t("action|retry")}
                     </Button>
-                </Flex>
-            </div>
+                </EncryptionCardButtons>
+            </EncryptionCard>
         );
     }
 }
diff --git a/src/components/views/auth/LanguageSelector.tsx b/src/components/views/auth/LanguageSelector.tsx
index a3be10eeecce0c864f0cdeb9e944e2382a548bd5..e8dbf0cf4e2d26025e9963dad8b66b4c8b9b17e8 100644
--- a/src/components/views/auth/LanguageSelector.tsx
+++ b/src/components/views/auth/LanguageSelector.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import SdkConfig from "../../../SdkConfig";
 import { getCurrentLanguage } from "../../../languageHandler";
diff --git a/src/components/views/auth/LoginWithQR.tsx b/src/components/views/auth/LoginWithQR.tsx
index ecf107cfbd3af7995c6c708422491e1d3f91a4ca..1519a1fb45304c5504b567ae45f67fef1f744b85 100644
--- a/src/components/views/auth/LoginWithQR.tsx
+++ b/src/components/views/auth/LoginWithQR.tsx
@@ -14,11 +14,11 @@ import {
     MSC4108SecureChannel,
     MSC4108SignInWithQR,
     RendezvousError,
-    RendezvousFailureReason,
+    type RendezvousFailureReason,
     RendezvousIntent,
 } from "matrix-js-sdk/src/rendezvous";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { Click, Mode, Phase } from "./LoginWithQR-types";
 import LoginWithQRFlow from "./LoginWithQRFlow";
diff --git a/src/components/views/auth/LoginWithQRFlow.tsx b/src/components/views/auth/LoginWithQRFlow.tsx
index 663dc1acfff8319c422d8e70bbc388d1f9bb055e..3a5a88f78d413da9b421e6c371b3fa3d17acea9c 100644
--- a/src/components/views/auth/LoginWithQRFlow.tsx
+++ b/src/components/views/auth/LoginWithQRFlow.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode } from "react";
+import React, { type JSX, createRef, type ReactNode } from "react";
 import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
 import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
 import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
-import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
 import { Heading, MFAInput, Text } from "@vector-im/compound-web";
 import classNames from "classnames";
 import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
@@ -21,7 +21,7 @@ import QRCode from "../elements/QRCode";
 import Spinner from "../elements/Spinner";
 import { Click, Phase } from "./LoginWithQR-types";
 import SdkConfig from "../../../SdkConfig";
-import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
+import { type FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
 import { ErrorMessage } from "../../structures/ErrorMessage";
 
 interface Props {
diff --git a/src/components/views/auth/PassphraseConfirmField.tsx b/src/components/views/auth/PassphraseConfirmField.tsx
index 0337c803593af315cdf7f504b86990139a48f618..4dc720e8aff96ba59076348be0fe66908e88e267 100644
--- a/src/components/views/auth/PassphraseConfirmField.tsx
+++ b/src/components/views/auth/PassphraseConfirmField.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, PureComponent, RefCallback, RefObject } from "react";
+import React, { type ComponentProps, PureComponent, type Ref } from "react";
 
-import Field, { IInputProps } from "../elements/Field";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import Field, { type IInputProps } from "../elements/Field";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 
 interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
     id?: string;
-    fieldRef?: RefCallback<Field> | RefObject<Field>;
+    fieldRef?: Ref<Field>;
     autoComplete?: string;
     value: string;
     password: string; // The password we're confirming
diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx
index 8fc56cc68cf5b6da73ea83362ebca9a99b663729..938e559fd7157f22c4633ed39bcf260c646db09e 100644
--- a/src/components/views/auth/PassphraseField.tsx
+++ b/src/components/views/auth/PassphraseField.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, PureComponent, RefCallback, RefObject } from "react";
+import React, { type ComponentProps, PureComponent, type Ref } from "react";
 import classNames from "classnames";
 
 import type { ZxcvbnResult } from "@zxcvbn-ts/core";
 import SdkConfig from "../../../SdkConfig";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
-import Field, { IInputProps } from "../elements/Field";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
+import Field, { type IInputProps } from "../elements/Field";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 
 interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
@@ -22,7 +22,7 @@ interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
     className?: string;
     minScore: 0 | 1 | 2 | 3 | 4;
     value: string;
-    fieldRef?: RefCallback<Field> | RefObject<Field>;
+    fieldRef?: Ref<Field>;
     // Additional strings such as a username used to catch bad passwords
     userInputs?: string[];
 
diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx
index e13af99f6f0c265f7f6fd74a9477d92f6c935f42..a3b77c60f420e49ca645df4629cde746892a9326 100644
--- a/src/components/views/auth/PasswordLogin.tsx
+++ b/src/components/views/auth/PasswordLogin.tsx
@@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { SyntheticEvent } from "react";
+import React, { type JSX, type SyntheticEvent } from "react";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
 import SdkConfig from "../../../SdkConfig";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
 import Field from "../elements/Field";
 import CountryDropdown from "./CountryDropdown";
 import EmailField from "./EmailField";
-import { PhoneNumberCountryDefinition } from "../../../phonenumber";
+import { type PhoneNumberCountryDefinition } from "../../../phonenumber";
 
 // For validating phone numbers without country codes
 const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
@@ -427,7 +427,9 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
                         disabled={this.props.busy}
                         autoFocus={autoFocusPassword}
                         onValidate={this.onPasswordValidate}
-                        ref={(field) => (this[LoginField.Password] = field)}
+                        ref={(field) => {
+                            this[LoginField.Password] = field;
+                        }}
                     />
                     {forgotPasswordJsx}
                     {!this.props.busy && (
diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx
index ef2c83d3bef4bf35dff56438022ac27d14da0762..88e9aa061578f1d71a425d0ec6d6a2f1b1301249 100644
--- a/src/components/views/auth/RegistrationForm.tsx
+++ b/src/components/views/auth/RegistrationForm.tsx
@@ -7,18 +7,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { BaseSyntheticEvent, ComponentProps, ReactNode } from "react";
-import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type BaseSyntheticEvent, type ComponentProps, type ReactNode } from "react";
+import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import * as Email from "../../../email";
-import { looksValid as phoneNumberLooksValid, PhoneNumberCountryDefinition } from "../../../phonenumber";
+import { looksValid as phoneNumberLooksValid, type PhoneNumberCountryDefinition } from "../../../phonenumber";
 import Modal from "../../../Modal";
 import { _t, _td } from "../../../languageHandler";
 import SdkConfig from "../../../SdkConfig";
 import { SAFE_LOCALPART_REGEX } from "../../../Registration";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import EmailField from "./EmailField";
 import PassphraseField from "./PassphraseField";
 import Field from "../elements/Field";
@@ -127,19 +127,11 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
 
         if (this.state.email === "") {
             if (this.showEmail()) {
-                Modal.createDialog(RegistrationEmailPromptDialog, {
-                    onFinished: async (confirmed: boolean, email?: string): Promise<void> => {
-                        if (confirmed && email !== undefined) {
-                            this.setState(
-                                {
-                                    email,
-                                },
-                                () => {
-                                    this.doSubmit(ev);
-                                },
-                            );
-                        }
-                    },
+                const { finished } = Modal.createDialog(RegistrationEmailPromptDialog);
+                finished.then(async ([confirmed, email]) => {
+                    if (confirmed && email !== undefined) {
+                        this.setState({ email }, () => this.doSubmit(ev));
+                    }
                 });
             } else {
                 // user can't set an e-mail so don't prompt them to
@@ -456,7 +448,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
             : _td("auth|registration|continue_without_email_field_label");
         return (
             <EmailField
-                fieldRef={(field) => (this[RegistrationField.Email] = field)}
+                fieldRef={(field) => {
+                    this[RegistrationField.Email] = field;
+                }}
                 label={emailLabel}
                 value={this.state.email}
                 validationRules={this.validateEmailRules.bind(this)}
@@ -471,7 +465,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
         return (
             <PassphraseField
                 id="mx_RegistrationForm_password"
-                fieldRef={(field) => (this[RegistrationField.Password] = field)}
+                fieldRef={(field) => {
+                    this[RegistrationField.Password] = field;
+                }}
                 minScore={PASSWORD_MIN_SCORE}
                 value={this.state.password}
                 onChange={this.onPasswordChange}
@@ -486,7 +482,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
         return (
             <PassphraseConfirmField
                 id="mx_RegistrationForm_passwordConfirm"
-                fieldRef={(field) => (this[RegistrationField.PasswordConfirm] = field)}
+                fieldRef={(field) => {
+                    this[RegistrationField.PasswordConfirm] = field;
+                }}
                 autoComplete="new-password"
                 value={this.state.passwordConfirm}
                 password={this.state.password}
@@ -514,7 +512,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
         );
         return (
             <Field
-                ref={(field) => (this[RegistrationField.PhoneNumber] = field)}
+                ref={(field) => {
+                    this[RegistrationField.PhoneNumber] = field;
+                }}
                 type="text"
                 label={phoneLabel}
                 value={this.state.phoneNumber}
@@ -529,7 +529,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
         return (
             <Field
                 id="mx_RegistrationForm_username"
-                ref={(field) => (this[RegistrationField.Username] = field)}
+                ref={(field) => {
+                    this[RegistrationField.Username] = field;
+                }}
                 type="text"
                 autoFocus={true}
                 label={_t("common|username")}
diff --git a/src/components/views/auth/Welcome.tsx b/src/components/views/auth/Welcome.tsx
index 39d5f95a8d194a01e8abccc6e19f9e990c892140..59fd1533e3655c5dd94fd04104d3f04f3baf0e0c 100644
--- a/src/components/views/auth/Welcome.tsx
+++ b/src/components/views/auth/Welcome.tsx
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import classNames from "classnames";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../../../SdkConfig";
 import AuthPage from "./AuthPage";
@@ -16,9 +17,7 @@ import LanguageSelector from "./LanguageSelector";
 import EmbeddedPage from "../../structures/EmbeddedPage";
 import { MATRIX_LOGO_HTML } from "../../structures/static-page-vars";
 
-interface IProps {}
-
-export default class Welcome extends React.PureComponent<IProps> {
+export default class Welcome extends React.PureComponent<EmptyObject> {
     public render(): React.ReactNode {
         const pagesConfig = SdkConfig.getObject("embedded_pages");
         let pageUrl: string | undefined;
diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx
index 942650c65a969d951c86898432d1a2ce29b53d4e..0837bf1dc8f1c407ca470a7120c841c6e78fcae9 100644
--- a/src/components/views/avatars/BaseAvatar.tsx
+++ b/src/components/views/avatars/BaseAvatar.tsx
@@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { AriaRole, forwardRef, useCallback, useContext, useEffect, useState } from "react";
+import React, { type AriaRole, type JSX, type Ref, useCallback, useContext, useEffect, useState } from "react";
 import classNames from "classnames";
-import { ClientEvent, SyncState } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type SyncState } from "matrix-js-sdk/src/matrix";
 import { Avatar } from "@vector-im/compound-web";
 
 import SettingsStore from "../../../settings/SettingsStore";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
 import { _t } from "../../../languageHandler";
@@ -34,6 +34,7 @@ interface IProps {
     tabIndex?: number;
     altText?: string;
     role?: AriaRole;
+    ref?: Ref<HTMLElement>;
 }
 
 const calculateUrls = (url?: string | null, urls?: string[], lowBandwidth = false): string[] => {
@@ -87,7 +88,7 @@ const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [
     return [imageUrl, onError];
 };
 
-const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
+const BaseAvatar = (props: IProps): JSX.Element => {
     const {
         name,
         idName,
@@ -99,6 +100,7 @@ const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
         className,
         type = "round",
         altText = _t("common|avatar"),
+        ref,
         ...otherProps
     } = props;
 
@@ -134,7 +136,7 @@ const BaseAvatar = forwardRef<HTMLElement, IProps>((props, ref) => {
             data-testid="avatar-img"
         />
     );
-});
+};
 
 export default BaseAvatar;
 export type BaseAvatarType = React.FC<IProps>;
diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx
index 5a11e0f6b27df887af0e05ffc21d5e31eaa9b8ee..321c0501dc65537700f70efbb454660b44c9f31d 100644
--- a/src/components/views/avatars/DecoratedRoomAvatar.tsx
+++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx
@@ -6,21 +6,29 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
-import { EventType, JoinRule, MatrixEvent, Room, RoomEvent, User, UserEvent } from "matrix-js-sdk/src/matrix";
+import {
+    EventType,
+    JoinRule,
+    type MatrixEvent,
+    type Room,
+    RoomEvent,
+    type User,
+    UserEvent,
+} from "matrix-js-sdk/src/matrix";
 import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
 import { Tooltip } from "@vector-im/compound-web";
 
 import RoomAvatar from "./RoomAvatar";
 import NotificationBadge from "../rooms/NotificationBadge";
 import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
-import { NotificationState } from "../../../stores/notifications/NotificationState";
+import { type NotificationState } from "../../../stores/notifications/NotificationState";
 import { isPresenceEnabled } from "../../../utils/presence";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
 import DMRoomMap from "../../../utils/DMRoomMap";
-import { IOOBData } from "../../../stores/ThreepidInviteStore";
+import { type IOOBData } from "../../../stores/ThreepidInviteStore";
 import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers";
 
 interface IProps {
@@ -71,6 +79,9 @@ function tooltipText(variant: Icon): string | undefined {
     }
 }
 
+/**
+ * @deprecated Use {@link RoomAvatarView} instead.
+ */
 export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
     private _dmUser: User | null = null;
     private isUnmounted = false;
diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx
index 641fe4a78382d3bc8eebe99b4c0c6f227095d47e..a62594f03893c5b2281de4f1ea4b782ef596b37b 100644
--- a/src/components/views/avatars/MemberAvatar.tsx
+++ b/src/components/views/avatars/MemberAvatar.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, ReactNode, Ref, useContext } from "react";
-import { RoomMember, ResizeMethod } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ReactNode, type Ref, useContext } from "react";
+import { type RoomMember, type ResizeMethod } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
@@ -18,6 +18,7 @@ import { CardContext } from "../right_panel/context";
 import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
 import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
 import { _t } from "../../../languageHandler";
+import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
 
 interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
     member: RoomMember | null;
@@ -32,21 +33,21 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
     forceHistorical?: boolean; // true to deny `useOnlyCurrentProfiles` usage. Default false.
     hideTitle?: boolean;
     children?: ReactNode;
+    ref?: Ref<HTMLElement>;
 }
 
-function MemberAvatar(
-    {
-        size,
-        resizeMethod = "crop",
-        viewUserOnClick,
-        forceHistorical,
-        fallbackUserId,
-        hideTitle,
-        member: propsMember,
-        ...props
-    }: IProps,
-    ref: Ref<HTMLElement>,
-): JSX.Element {
+export default function MemberAvatar({
+    size,
+    resizeMethod = "crop",
+    viewUserOnClick,
+    forceHistorical,
+    fallbackUserId,
+    hideTitle,
+    member: propsMember,
+    ref,
+    ...props
+}: IProps): JSX.Element {
+    const cli = useContext(MatrixClientContext);
     const card = useContext(CardContext);
 
     const member = useRoomMemberProfile({
@@ -60,7 +61,7 @@ function MemberAvatar(
     let imageUrl: string | null | undefined;
     if (member?.name) {
         if (member.getMxcAvatarUrl()) {
-            imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
+            imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "", cli).getThumbnailOfSourceHttp(
                 parseInt(size, 10),
                 parseInt(size, 10),
                 resizeMethod,
@@ -99,5 +100,3 @@ function MemberAvatar(
         />
     );
 }
-
-export default forwardRef(MemberAvatar);
diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx
index e1d71ac1aa7feb5cf0a5ce22044ddde9637458c9..64903787318b760e077d917de530f0f4b0265c64 100644
--- a/src/components/views/avatars/RoomAvatar.tsx
+++ b/src/components/views/avatars/RoomAvatar.tsx
@@ -1,146 +1,97 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2015, 2016 OpenMarket Ltd
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { Room, RoomStateEvent, MatrixEvent, EventType, RoomType } from "matrix-js-sdk/src/matrix";
+import React, { useCallback, useMemo, type ComponentProps } from "react";
+import { type Room, RoomType, KnownMembership, EventType } from "matrix-js-sdk/src/matrix";
+import { type RoomAvatarEventContent } from "matrix-js-sdk/src/types";
 
 import BaseAvatar from "./BaseAvatar";
 import ImageView from "../elements/ImageView";
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import Modal from "../../../Modal";
 import * as Avatar from "../../../Avatar";
-import DMRoomMap from "../../../utils/DMRoomMap";
 import { mediaFromMxc } from "../../../customisations/Media";
-import { IOOBData } from "../../../stores/ThreepidInviteStore";
-import { LocalRoom } from "../../../models/LocalRoom";
+import { type IOOBData } from "../../../stores/ThreepidInviteStore";
 import { filterBoolean } from "../../../utils/arrays";
+import { useSettingValue } from "../../../hooks/useSettings";
+import { useRoomState } from "../../../hooks/useRoomState";
+import { useRoomIdName } from "../../../hooks/room/useRoomIdName";
+import { MediaPreviewValue } from "../../../@types/media_preview";
 
-interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
+interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick" | "size"> {
     // Room may be left unset here, but if it is,
     // oobData.avatarUrl should be set (else there
     // would be nowhere to get the avatar from)
     room?: Room;
-    oobData: IOOBData & {
+    // Optional here.
+    size?: ComponentProps<typeof BaseAvatar>["size"];
+    oobData?: IOOBData & {
         roomId?: string;
     };
     viewAvatarOnClick?: boolean;
     onClick?(): void;
 }
 
-interface IState {
-    urls: string[];
-}
-
-export function idNameForRoom(room: Room): string {
-    const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
-    // If the room is a DM, we use the other user's ID for the color hash
-    // in order to match the room avatar with their avatar
-    if (dmMapUserId) return dmMapUserId;
-
-    if (room instanceof LocalRoom && room.targets.length === 1) {
-        return room.targets[0].userId;
-    }
-
-    return room.roomId;
-}
-
-export default class RoomAvatar extends React.Component<IProps, IState> {
-    public static defaultProps = {
-        size: "36px",
-        oobData: {},
-    };
-
-    public constructor(props: IProps) {
-        super(props);
-
-        this.state = {
-            urls: RoomAvatar.getImageUrls(this.props),
-        };
-    }
-
-    public componentDidMount(): void {
-        MatrixClientPeg.safeGet().on(RoomStateEvent.Events, this.onRoomStateEvents);
-    }
-
-    public componentWillUnmount(): void {
-        MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
-    }
-
-    public static getDerivedStateFromProps(nextProps: IProps): IState {
-        return {
-            urls: RoomAvatar.getImageUrls(nextProps),
-        };
-    }
+const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobData, size = "36px", ...otherProps }) => {
+    const roomName = room?.name ?? oobData?.name ?? "?";
+    const avatarEvent = useRoomState(room, (state) => state.getStateEvents(EventType.RoomAvatar, ""));
+    const roomIdName = useRoomIdName(room, oobData);
 
-    private onRoomStateEvents = (ev: MatrixEvent): void => {
-        if (ev.getRoomId() !== this.props.room?.roomId || ev.getType() !== EventType.RoomAvatar) return;
+    const showAvatarsOnInvites =
+        useSettingValue("mediaPreviewConfig", room?.roomId).invite_avatars === MediaPreviewValue.On;
 
-        this.setState({
-            urls: RoomAvatar.getImageUrls(this.props),
-        });
-    };
-
-    private static getImageUrls(props: IProps): string[] {
-        let oobAvatar: string | null = null;
-        if (props.oobData.avatarUrl) {
-            oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(
-                parseInt(props.size, 10),
-                parseInt(props.size, 10),
-                "crop",
-            );
-        }
-
-        return filterBoolean([
-            oobAvatar, // highest priority
-            RoomAvatar.getRoomAvatarUrl(props),
-        ]);
-    }
-
-    private static getRoomAvatarUrl(props: IProps): string | null {
-        if (!props.room) return null;
-
-        return Avatar.avatarUrlForRoom(props.room, parseInt(props.size, 10), parseInt(props.size, 10), "crop");
-    }
-
-    private onRoomAvatarClick = (): void => {
-        const avatarUrl = Avatar.avatarUrlForRoom(this.props.room ?? null, undefined, undefined, undefined);
+    const onRoomAvatarClick = useCallback(() => {
+        const avatarUrl = Avatar.avatarUrlForRoom(room ?? null);
         if (!avatarUrl) return;
         const params = {
             src: avatarUrl,
-            name: this.props.room?.name,
+            name: room?.name,
         };
 
         Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
-    };
+    }, [room]);
 
-    private get roomIdName(): string | undefined {
-        const room = this.props.room;
-
-        if (room) {
-            return idNameForRoom(room);
-        } else {
-            return this.props.oobData?.roomId;
+    const urls = useMemo(() => {
+        const myMembership = room?.getMyMembership();
+        if (!showAvatarsOnInvites && (myMembership === KnownMembership.Invite || !myMembership)) {
+            // The user has opted out of showing avatars, so return no urls here.
+            return [];
         }
-    }
 
-    public render(): React.ReactNode {
-        const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
-        const roomName = room?.name ?? oobData.name ?? "?";
+        // parseInt ignores suffixes.
+        const sizeInt = parseInt(size, 10);
+        let oobAvatar: string | null = null;
+        if (oobData?.avatarUrl) {
+            oobAvatar = mediaFromMxc(oobData?.avatarUrl).getThumbnailOfSourceHttp(sizeInt, sizeInt, "crop");
+        }
 
-        return (
-            <BaseAvatar
-                {...otherProps}
-                type={(room?.getType() ?? this.props.oobData?.roomType) === RoomType.Space ? "square" : "round"}
-                name={roomName}
-                idName={this.roomIdName}
-                urls={this.state.urls}
-                onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
-            />
-        );
-    }
-}
+        return filterBoolean([
+            oobAvatar, // highest priority
+            Avatar.avatarUrlForRoom(
+                room ?? null,
+                sizeInt,
+                sizeInt,
+                "crop",
+                avatarEvent?.getContent<RoomAvatarEventContent>().url,
+            ),
+        ]);
+    }, [showAvatarsOnInvites, room, size, avatarEvent, oobData]);
+
+    return (
+        <BaseAvatar
+            {...otherProps}
+            size={size}
+            type={(room?.getType() ?? oobData?.roomType) === RoomType.Space ? "square" : "round"}
+            name={roomName}
+            idName={roomIdName}
+            urls={urls}
+            onClick={viewAvatarOnClick && urls[0] ? onRoomAvatarClick : onClick}
+        />
+    );
+};
+
+export default RoomAvatar;
diff --git a/src/components/views/avatars/RoomAvatarView.tsx b/src/components/views/avatars/RoomAvatarView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8810d073c516ec3e0dac8bd8b427925a2283922a
--- /dev/null
+++ b/src/components/views/avatars/RoomAvatarView.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
+import VideoIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
+import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8";
+import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8";
+import BusyIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8";
+import classNames from "classnames";
+
+import RoomAvatar from "./RoomAvatar";
+import { useRoomAvatarViewModel } from "../../viewmodels/avatars/RoomAvatarViewModel";
+import { _t } from "../../../languageHandler";
+import { Presence } from "./WithPresenceIndicator";
+
+interface RoomAvatarViewProps {
+    /**
+     * The room to display the avatar for.
+     */
+    room: Room;
+}
+
+/**
+ * Component to display the avatar of a room.
+ * Currently only 32px size is supported.
+ */
+export function RoomAvatarView({ room }: RoomAvatarViewProps): JSX.Element {
+    const vm = useRoomAvatarViewModel(room);
+    // No decoration, we just show the avatar
+    if (!vm.hasDecoration) return <RoomAvatar size="32px" room={room} />;
+
+    return (
+        <div className="mx_RoomAvatarView">
+            <RoomAvatar
+                className={classNames("mx_RoomAvatarView_RoomAvatar", {
+                    // Presence indicator and video/public icons don't have the same size
+                    // We use different masks
+                    mx_RoomAvatarView_RoomAvatar_icon: vm.isVideoRoom || vm.isPublic,
+                    mx_RoomAvatarView_RoomAvatar_presence: Boolean(vm.presence),
+                })}
+                size="32px"
+                room={room}
+            />
+
+            {/* If the room is a public video room, we prefer to display only the video icon */}
+            {vm.isPublic && !vm.isVideoRoom && (
+                <PublicIcon
+                    width="16px"
+                    height="16px"
+                    className="mx_RoomAvatarView_icon"
+                    color="var(--cpd-color-icon-tertiary)"
+                    aria-label={_t("room|header|room_is_public")}
+                />
+            )}
+            {vm.isVideoRoom && (
+                <VideoIcon
+                    width="16px"
+                    height="16px"
+                    className="mx_RoomAvatarView_icon"
+                    color="var(--cpd-color-icon-tertiary)"
+                    aria-label={_t("room|video_room")}
+                />
+            )}
+            {vm.presence && <PresenceDecoration presence={vm.presence} />}
+        </div>
+    );
+}
+
+type PresenceDecorationProps = {
+    /**
+     * The presence of the user in the DM room.
+     */
+    presence: NonNullable<Presence>;
+};
+
+/**
+ * Component to display the presence of a user in a DM room.
+ */
+function PresenceDecoration({ presence }: PresenceDecorationProps): JSX.Element {
+    switch (presence) {
+        case Presence.Online:
+            return (
+                <OnlineOrUnavailableIcon
+                    width="8px"
+                    height="8px"
+                    className="mx_RoomAvatarView_PresenceDecoration"
+                    color="var(--cpd-color-icon-accent-primary)"
+                    aria-label={_t("presence|online")}
+                />
+            );
+        case Presence.Away:
+            return (
+                <OnlineOrUnavailableIcon
+                    width="8px"
+                    height="8px"
+                    className="mx_RoomAvatarView_PresenceDecoration"
+                    color="var(--cpd-color-icon-quaternary)"
+                    aria-label={_t("presence|away")}
+                />
+            );
+        case Presence.Offline:
+            return (
+                <OfflineIcon
+                    width="8px"
+                    height="8px"
+                    className="mx_RoomAvatarView_PresenceDecoration"
+                    color="var(--cpd-color-icon-tertiary)"
+                    aria-label={_t("presence|offline")}
+                />
+            );
+        case Presence.Busy:
+            return (
+                <BusyIcon
+                    width="8px"
+                    height="8px"
+                    className="mx_RoomAvatarView_PresenceDecoration"
+                    color="var(--cpd-color-icon-tertiary)"
+                    aria-label={_t("presence|busy")}
+                />
+            );
+    }
+}
diff --git a/src/components/views/avatars/SearchResultAvatar.tsx b/src/components/views/avatars/SearchResultAvatar.tsx
index c50c4d81b2ad974d57c8d0879eca6d7e2c8c66b1..45c85717d2f57528218167f8f68f21a16b700e30 100644
--- a/src/components/views/avatars/SearchResultAvatar.tsx
+++ b/src/components/views/avatars/SearchResultAvatar.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 
 import emailPillAvatar from "../../../../res/img/icon-email-pill-avatar.svg";
 import { mediaFromMxc } from "../../../customisations/Media";
-import { Member, ThreepidMember } from "../../../utils/direct-messages";
+import { type Member, type ThreepidMember } from "../../../utils/direct-messages";
 import BaseAvatar from "./BaseAvatar";
 
 interface SearchResultAvatarProps {
diff --git a/src/components/views/avatars/WidgetAvatar.tsx b/src/components/views/avatars/WidgetAvatar.tsx
index f6d73e7d1cbc81568249fe741c6f9f00ff7a0d79..c43cd98216908f1271586943f579e5e494410f53 100644
--- a/src/components/views/avatars/WidgetAvatar.tsx
+++ b/src/components/views/avatars/WidgetAvatar.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { IWidget } from "matrix-widget-api";
+import React, { type ComponentProps } from "react";
+import { type IWidget } from "matrix-widget-api";
 import classNames from "classnames";
 
-import { IApp, isAppWidget } from "../../../stores/WidgetStore";
-import BaseAvatar, { BaseAvatarType } from "./BaseAvatar";
+import { type IApp, isAppWidget } from "../../../stores/WidgetStore";
+import BaseAvatar, { type BaseAvatarType } from "./BaseAvatar";
 import { mediaFromMxc } from "../../../customisations/Media";
 
 interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls"> {
diff --git a/src/components/views/avatars/WithPresenceIndicator.tsx b/src/components/views/avatars/WithPresenceIndicator.tsx
index 9d10f8dce6edf12114368de437b1c1c75ed283c5..4c4caecd6934c0b139fd6b3488bbc35dff31ed20 100644
--- a/src/components/views/avatars/WithPresenceIndicator.tsx
+++ b/src/components/views/avatars/WithPresenceIndicator.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useEffect, useState } from "react";
-import { ClientEvent, Room, RoomMember, RoomStateEvent, UserEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ReactNode, useEffect, useState } from "react";
+import { ClientEvent, type Room, type RoomMember, RoomStateEvent, UserEvent } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { isPresenceEnabled } from "../../../utils/presence";
@@ -26,7 +26,7 @@ interface Props {
     children: ReactNode;
 }
 
-enum Presence {
+export enum Presence {
     // Note: the names here are used in CSS class names
     Online = "ONLINE",
     Away = "AWAY",
@@ -52,14 +52,14 @@ function getDmMember(room: Room): RoomMember | null {
     return otherUserId ? room.getMember(otherUserId) : null;
 }
 
-export const useDmMember = (room: Room): RoomMember | null => {
-    const [dmMember, setDmMember] = useState<RoomMember | null>(getDmMember(room));
+export const useDmMember = (room?: Room): RoomMember | null => {
+    const [dmMember, setDmMember] = useState<RoomMember | null>(room ? getDmMember(room) : null);
     const updateDmMember = (): void => {
-        setDmMember(getDmMember(room));
+        setDmMember(room ? getDmMember(room) : null);
     };
 
-    useEventEmitter(room.currentState, RoomStateEvent.Members, updateDmMember);
-    useEventEmitter(room.client, ClientEvent.AccountData, updateDmMember);
+    useEventEmitter(room?.currentState, RoomStateEvent.Members, updateDmMember);
+    useEventEmitter(room?.client, ClientEvent.AccountData, updateDmMember);
     useEffect(updateDmMember, [room]);
 
     return dmMember;
@@ -86,7 +86,7 @@ function getPresence(member: RoomMember | null): Presence | null {
     return null;
 }
 
-const usePresence = (room: Room, member: RoomMember | null): Presence | null => {
+export const usePresence = (room: Room, member: RoomMember | null): Presence | null => {
     const [presence, setPresence] = useState<Presence | null>(getPresence(member));
     const updatePresence = (): void => {
         setPresence(getPresence(member));
diff --git a/src/components/views/beacon/BeaconListItem.tsx b/src/components/views/beacon/BeaconListItem.tsx
index 01a7f9364aa24223f4f8cbdca08ede5fdd7b4317..ceedbf05fd3b89292bf7bcaac9cd94d90cfcfeb9 100644
--- a/src/components/views/beacon/BeaconListItem.tsx
+++ b/src/components/views/beacon/BeaconListItem.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLProps, useContext } from "react";
-import { Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import React, { type HTMLProps, useContext } from "react";
+import { type Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
diff --git a/src/components/views/beacon/BeaconMarker.tsx b/src/components/views/beacon/BeaconMarker.tsx
index 01d74e72b1356e252d18bbce93a5c797e0118157..262901233b2c6aa571b8c5d6827e2d002132b169 100644
--- a/src/components/views/beacon/BeaconMarker.tsx
+++ b/src/components/views/beacon/BeaconMarker.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useContext } from "react";
-import * as maplibregl from "maplibre-gl";
-import { Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode, useContext } from "react";
+import { type Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
+import type * as maplibregl from "maplibre-gl";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
 import { SmartMarker } from "../location";
diff --git a/src/components/views/beacon/BeaconStatus.tsx b/src/components/views/beacon/BeaconStatus.tsx
index 1415dc229cd842de97c14e57937ca349a74aea41..0ed7ceb4b08584a5dd16509442c9db8ece1efab2 100644
--- a/src/components/views/beacon/BeaconStatus.tsx
+++ b/src/components/views/beacon/BeaconStatus.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLProps } from "react";
+import React, { type HTMLProps } from "react";
 import classNames from "classnames";
-import { Beacon } from "matrix-js-sdk/src/matrix";
+import { type Beacon } from "matrix-js-sdk/src/matrix";
 
 import StyledLiveBeaconIcon from "./StyledLiveBeaconIcon";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/beacon/BeaconStatusTooltip.tsx b/src/components/views/beacon/BeaconStatusTooltip.tsx
index 1dc1b05e6112a312293f00d7bc85dcb7008946dd..eb8a76f1c1f52dad8fe4e2e89d57433a3fcec23d 100644
--- a/src/components/views/beacon/BeaconStatusTooltip.tsx
+++ b/src/components/views/beacon/BeaconStatusTooltip.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext } from "react";
-import { Beacon, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import { type Beacon, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import BeaconStatus from "./BeaconStatus";
diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx
index 27f9f2e520e935ba6fc1a3f70e5c3a1638543eef..d779ebbf2ec04340c37317f68cb63efce38a2bdb 100644
--- a/src/components/views/beacon/BeaconViewDialog.tsx
+++ b/src/components/views/beacon/BeaconViewDialog.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState, useEffect } from "react";
-import { MatrixClient, Beacon, Room } from "matrix-js-sdk/src/matrix";
-import * as maplibregl from "maplibre-gl";
+import { type MatrixClient, type Beacon, type Room } from "matrix-js-sdk/src/matrix";
 
+import type * as maplibregl from "maplibre-gl";
 import { Icon as LiveLocationIcon } from "../../../../res/img/location/live-location.svg";
 import { useLiveBeacons } from "../../../utils/beacon/useLiveBeacons";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -17,7 +17,7 @@ import BaseDialog from "../dialogs/BaseDialog";
 import Map from "../location/Map";
 import ZoomButtons from "../location/ZoomButtons";
 import BeaconMarker from "./BeaconMarker";
-import { Bounds, getBeaconBounds } from "../../../utils/beacon/bounds";
+import { type Bounds, getBeaconBounds } from "../../../utils/beacon/bounds";
 import { getGeoUri } from "../../../utils/beacon";
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
@@ -26,7 +26,7 @@ import DialogOwnBeaconStatus from "./DialogOwnBeaconStatus";
 import BeaconStatusTooltip from "./BeaconStatusTooltip";
 import MapFallback from "../location/MapFallback";
 import { MapError } from "../location/MapError";
-import { LocationShareError } from "../../../utils/location";
+import { type LocationShareError } from "../../../utils/location";
 
 export interface IProps {
     roomId: Room["roomId"];
diff --git a/src/components/views/beacon/DialogOwnBeaconStatus.tsx b/src/components/views/beacon/DialogOwnBeaconStatus.tsx
index e6b35d924770734eba69a7bed785c489ff4ade88..fff6b0db4b71cad17b001c8e86a9bb3f945ddfd2 100644
--- a/src/components/views/beacon/DialogOwnBeaconStatus.tsx
+++ b/src/components/views/beacon/DialogOwnBeaconStatus.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext } from "react";
-import { Room, Beacon, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import { type Room, type Beacon, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../stores/OwnBeaconStore";
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
diff --git a/src/components/views/beacon/DialogSidebar.tsx b/src/components/views/beacon/DialogSidebar.tsx
index 9fcca26dde42866edf83b3105463d67958b26003..ee86e6bcf0e2a0a3440561cabd81517c4bf711df 100644
--- a/src/components/views/beacon/DialogSidebar.tsx
+++ b/src/components/views/beacon/DialogSidebar.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Beacon } from "matrix-js-sdk/src/matrix";
+import { type Beacon } from "matrix-js-sdk/src/matrix";
 import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/beacon/LeftPanelLiveShareWarning.tsx b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx
index 0c3f26f91ef1cdc5b038e91d04a8aa72bc38ea49..d5338cf28f7d0228dd3638ad786faa8dc223d3a7 100644
--- a/src/components/views/beacon/LeftPanelLiveShareWarning.tsx
+++ b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx
@@ -8,16 +8,16 @@ Please see LICENSE files in the repository root for full details.
 
 import classNames from "classnames";
 import React, { useEffect } from "react";
-import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
+import { type Beacon, type BeaconIdentifier } from "matrix-js-sdk/src/matrix";
 
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
 import { _t } from "../../../languageHandler";
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../stores/OwnBeaconStore";
 import { Icon as LiveLocationIcon } from "../../../../res/img/location/live-location.svg";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../dispatcher/actions";
 import dispatcher from "../../../dispatcher/dispatcher";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface Props {
     isMinimized?: boolean;
diff --git a/src/components/views/beacon/LiveTimeRemaining.tsx b/src/components/views/beacon/LiveTimeRemaining.tsx
index c0730938ee97809e908b30c50807af744df45c9e..d4d7ca262e8147c54219dca765c7b7213a6c3e43 100644
--- a/src/components/views/beacon/LiveTimeRemaining.tsx
+++ b/src/components/views/beacon/LiveTimeRemaining.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useCallback, useEffect, useState } from "react";
-import { BeaconEvent, Beacon } from "matrix-js-sdk/src/matrix";
+import { BeaconEvent, type Beacon } from "matrix-js-sdk/src/matrix";
 
 import { formatDuration } from "../../../DateUtils";
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
diff --git a/src/components/views/beacon/OwnBeaconStatus.tsx b/src/components/views/beacon/OwnBeaconStatus.tsx
index 7d0f277786de1fbc86b04a6cb66cce1a15a3356f..8ece201cbe959b3e2ed884e0146f3f376df9d5ab 100644
--- a/src/components/views/beacon/OwnBeaconStatus.tsx
+++ b/src/components/views/beacon/OwnBeaconStatus.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Beacon } from "matrix-js-sdk/src/matrix";
-import React, { HTMLProps } from "react";
+import { type Beacon } from "matrix-js-sdk/src/matrix";
+import React, { type HTMLProps } from "react";
 
 import { _t } from "../../../languageHandler";
 import { useOwnLiveBeacons } from "../../../utils/beacon";
 import { preventDefaultWrapper } from "../../../utils/NativeEventUtils";
 import BeaconStatus from "./BeaconStatus";
 import { BeaconDisplayStatus } from "./displayStatus";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface Props {
     displayStatus: BeaconDisplayStatus;
diff --git a/src/components/views/beacon/RoomCallBanner.tsx b/src/components/views/beacon/RoomCallBanner.tsx
index a9f25e7e6c404154b6312d8e58c5d5fb20bada17..e4f0dfa6086cd92c12cc36300cc5c0533c904aa6 100644
--- a/src/components/views/beacon/RoomCallBanner.tsx
+++ b/src/components/views/beacon/RoomCallBanner.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useCallback } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../dispatcher/actions";
-import { ConnectionState, ElementCall } from "../../../models/Call";
+import { ConnectionState, type ElementCall } from "../../../models/Call";
 import { useCall } from "../../../hooks/useCall";
 import { useEventEmitterState } from "../../../hooks/useEventEmitter";
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../stores/OwnBeaconStore";
diff --git a/src/components/views/beacon/ShareLatestLocation.tsx b/src/components/views/beacon/ShareLatestLocation.tsx
index 5300a8190006c23a468006c43b77bd2f54cdff59..b18cdff83947b2750d7e2868ad7dd312d198bcbb 100644
--- a/src/components/views/beacon/ShareLatestLocation.tsx
+++ b/src/components/views/beacon/ShareLatestLocation.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useEffect, useState } from "react";
-import { ContentHelpers } from "matrix-js-sdk/src/matrix";
+import { type ContentHelpers } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { Icon as ExternalLinkIcon } from "../../../../res/img/external-link.svg";
diff --git a/src/components/views/beacon/displayStatus.ts b/src/components/views/beacon/displayStatus.ts
index e11b0018ba074edc4bfd863ebe0426fc0ac56560..15320c2fa57e852b0502f0aab00b5125e4bca542 100644
--- a/src/components/views/beacon/displayStatus.ts
+++ b/src/components/views/beacon/displayStatus.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ContentHelpers } from "matrix-js-sdk/src/matrix";
+import { type ContentHelpers } from "matrix-js-sdk/src/matrix";
 
 export enum BeaconDisplayStatus {
     Loading = "Loading",
diff --git a/src/components/views/beacon/index.tsx b/src/components/views/beacon/index.tsx
index 871e7cb07e4e5903275469251a5b666bbfaf02c6..313e1145a97e3d4210386573c7f98310020c1915 100644
--- a/src/components/views/beacon/index.tsx
+++ b/src/components/views/beacon/index.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 // Exports beacon components which touch maplibre-gs wrapped in React Suspense to enable code splitting
 
-import React, { ComponentProps, lazy, Suspense } from "react";
+import React, { type JSX, type ComponentProps, lazy, Suspense } from "react";
 
 import Spinner from "../elements/Spinner";
 
diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx
index 93d69dffce5989b7beaa518f8dde9c00fec9ff7d..4421a9e50b107d05602938a6327ec12e29f3363c 100644
--- a/src/components/views/beta/BetaCard.tsx
+++ b/src/components/views/beta/BetaCard.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useState } from "react";
+import React, { type ReactNode, useState } from "react";
 import { sleep } from "matrix-js-sdk/src/utils";
 
 import { _t } from "../../../languageHandler";
@@ -20,7 +20,7 @@ import SettingsFlag from "../elements/SettingsFlag";
 import { useFeatureEnabled } from "../../../hooks/useSettings";
 import InlineSpinner from "../elements/InlineSpinner";
 import { shouldShowFeedback } from "../../../utils/Feedback";
-import { FeatureSettingKey } from "../../../settings/Settings.tsx";
+import { type FeatureSettingKey } from "../../../settings/Settings.tsx";
 
 // XXX: Keep this around for re-use in future Betas
 
diff --git a/src/components/views/context_menus/DeveloperToolsOption.tsx b/src/components/views/context_menus/DeveloperToolsOption.tsx
index fc77a2e284ec04b063e8e1e0730aba2a13fbbbee..53a5d7283d95f17810b659ad28c9eae2f6b93fd3 100644
--- a/src/components/views/context_menus/DeveloperToolsOption.tsx
+++ b/src/components/views/context_menus/DeveloperToolsOption.tsx
@@ -25,7 +25,6 @@ export const DeveloperToolsOption: React.FC<Props> = ({ onFinished, roomId }) =>
                 Modal.createDialog(
                     DevtoolsDialog,
                     {
-                        onFinished: () => {},
                         roomId: roomId,
                     },
                     "mx_DevtoolsDialog_wrapper",
diff --git a/src/components/views/context_menus/DeviceContextMenu.tsx b/src/components/views/context_menus/DeviceContextMenu.tsx
index 5d71049fb400b97b3349c285ff9aab6d84ec5424..b6646c05eca9731d232d53f230a79f92652dcccd 100644
--- a/src/components/views/context_menus/DeviceContextMenu.tsx
+++ b/src/components/views/context_menus/DeviceContextMenu.tsx
@@ -10,8 +10,8 @@ import React, { useEffect, useState } from "react";
 
 import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
 import IconizedContextMenu, { IconizedContextMenuOptionList, IconizedContextMenuRadio } from "./IconizedContextMenu";
-import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 
 const SECTION_NAMES: Record<MediaDeviceKindEnum, TranslationKey> = {
     [MediaDeviceKindEnum.AudioInput]: _td("voip|input_devices"),
diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx
index c4d0d73786611f0148fd52fbededf6662e301c73..004c86ff72d0abcd8ed0ac59f4597d8a2180a7d0 100644
--- a/src/components/views/context_menus/DialpadContextMenu.tsx
+++ b/src/components/views/context_menus/DialpadContextMenu.tsx
@@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
-import { createRef } from "react";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import React, { createRef } from "react";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
-import ContextMenu, { IProps as IContextMenuProps } from "../../structures/ContextMenu";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
+import ContextMenu, { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
 import Field from "../elements/Field";
 import DialPad from "../voip/DialPad";
 
@@ -24,7 +23,7 @@ interface IState {
 }
 
 export default class DialpadContextMenu extends React.Component<IProps, IState> {
-    private numberEntryFieldRef: React.RefObject<Field> = createRef();
+    private numberEntryFieldRef = createRef<Field>();
 
     public constructor(props: IProps) {
         super(props);
diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx
index 17a38055132c7136c76512981ec116a219ef9078..b6372a96c665bd33b0fcf532a84e23c5fff77fc7 100644
--- a/src/components/views/context_menus/IconizedContextMenu.tsx
+++ b/src/components/views/context_menus/IconizedContextMenu.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import classNames from "classnames";
 
 import ContextMenu, {
     ChevronFace,
-    IProps as IContextMenuProps,
+    type IProps as IContextMenuProps,
     MenuItem,
     MenuItemCheckbox,
     MenuItemRadio,
diff --git a/src/components/views/context_menus/KebabContextMenu.tsx b/src/components/views/context_menus/KebabContextMenu.tsx
index e65f0389ffe4ba13275a800dd41e8a6df287da90..f6fc9972a0b8f9cf06d06436a86e8cfb019ca747 100644
--- a/src/components/views/context_menus/KebabContextMenu.tsx
+++ b/src/components/views/context_menus/KebabContextMenu.tsx
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import ContextMenuIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
 
-import { ChevronFace, ContextMenuButton, MenuProps, useContextMenu } from "../../structures/ContextMenu";
-import { ButtonProps } from "../elements/AccessibleButton";
+import { ChevronFace, ContextMenuButton, type MenuProps, useContextMenu } from "../../structures/ContextMenu";
+import { type ButtonProps } from "../elements/AccessibleButton";
 import IconizedContextMenu, { IconizedContextMenuOptionList } from "./IconizedContextMenu";
 
 const contextMenuBelow = (elementRect: DOMRect): MenuProps => {
diff --git a/src/components/views/context_menus/LegacyCallContextMenu.tsx b/src/components/views/context_menus/LegacyCallContextMenu.tsx
index bc3deab7a1de39f710af01267129e53b5210e4e6..3427ed183be1d57e8405152e39c0b311a8f64443 100644
--- a/src/components/views/context_menus/LegacyCallContextMenu.tsx
+++ b/src/components/views/context_menus/LegacyCallContextMenu.tsx
@@ -6,10 +6,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 
 import { _t } from "../../../languageHandler";
-import ContextMenu, { IProps as IContextMenuProps, MenuItem } from "../../structures/ContextMenu";
+import ContextMenu, { type IProps as IContextMenuProps, MenuItem } from "../../structures/ContextMenu";
 import LegacyCallHandler from "../../../LegacyCallHandler";
 
 interface IProps extends IContextMenuProps {
diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx
index 0af3604fcae14fee0f13273631edbfb1d85ad875..76f6b3198941890b3a644c4a10b7b2b8e34c9990 100644
--- a/src/components/views/context_menus/MessageContextMenu.tsx
+++ b/src/components/views/context_menus/MessageContextMenu.tsx
@@ -8,15 +8,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, useContext } from "react";
+import React, { type JSX, createRef, useContext } from "react";
 import {
     EventStatus,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
     RoomMemberEvent,
     EventType,
     RelationType,
-    Relations,
+    type Relations,
     Thread,
     M_POLL_START,
 } from "matrix-js-sdk/src/matrix";
@@ -31,10 +31,10 @@ import { isUrlPermitted } from "../../../HtmlUtils";
 import { canEditContent, editEvent, isContentActionable } from "../../../utils/EventUtils";
 import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
 import { Action } from "../../../dispatcher/actions";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import { copyPlaintext, getSelectedText } from "../../../utils/strings";
-import ContextMenu, { toRightOf, MenuProps } from "../../structures/ContextMenu";
+import ContextMenu, { toRightOf, type MenuProps } from "../../structures/ContextMenu";
 import ReactionPicker from "../emojipicker/ReactionPicker";
 import ViewSource from "../../structures/ViewSource";
 import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
@@ -42,14 +42,14 @@ import { ShareDialog } from "../dialogs/ShareDialog";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import EndPollDialog from "../dialogs/EndPollDialog";
 import { isPollEnded } from "../messages/MPollBody";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
-import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
-import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
-import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
+import { type OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
+import { type OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
 import { createMapSiteLinkFromEvent } from "../../../utils/location";
 import { getForwardableEvent } from "../../../events/forward/getForwardableEvent";
 import { getShareableLocationEvent } from "../../../events/location/getShareableLocationEvent";
-import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
+import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
 import { CardContext } from "../right_panel/context";
 import PinningUtils from "../../../utils/PinningUtils";
 import PosthogTrackers from "../../../PosthogTrackers.ts";
@@ -130,8 +130,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
 
     private reactButtonRef = createRef<any>(); // XXX Ref to a functional component
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             canRedact: false,
diff --git a/src/components/views/context_menus/RoomGeneralContextMenu.tsx b/src/components/views/context_menus/RoomGeneralContextMenu.tsx
index c54aa1e465282e7ba1c6eb5297d7f3cb6e4f0184..7c21d0985303a3a2dd5cb1f96c0f463e170ce6f5 100644
--- a/src/components/views/context_menus/RoomGeneralContextMenu.tsx
+++ b/src/components/views/context_menus/RoomGeneralContextMenu.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { Room } from "matrix-js-sdk/src/matrix";
-import React, { useContext } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
 
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import RoomListActions from "../../../actions/RoomListActions";
@@ -19,17 +19,17 @@ import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { _t } from "../../../languageHandler";
 import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
-import { DefaultTagID, TagID } from "../../../stores/room-list/models";
+import { DefaultTagID, type TagID } from "../../../stores/room-list/models";
 import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
 import DMRoomMap from "../../../utils/DMRoomMap";
 import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications";
-import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
+import { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
 import IconizedContextMenu, {
     IconizedContextMenuCheckbox,
     IconizedContextMenuOption,
     IconizedContextMenuOptionList,
 } from "../context_menus/IconizedContextMenu";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../../settings/UIFeature";
 import { DeveloperToolsOption } from "./DeveloperToolsOption";
diff --git a/src/components/views/context_menus/RoomNotificationContextMenu.tsx b/src/components/views/context_menus/RoomNotificationContextMenu.tsx
index ea638807625dfa5bc202e84397908ad6c08dbf04..9844f2769537bd2b318bb941d357e02fdce368d8 100644
--- a/src/components/views/context_menus/RoomNotificationContextMenu.tsx
+++ b/src/components/views/context_menus/RoomNotificationContextMenu.tsx
@@ -6,20 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
-import React from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
 
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { useNotificationState } from "../../../hooks/useRoomNotificationState";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { _t } from "../../../languageHandler";
 import { RoomNotifState } from "../../../RoomNotifs";
-import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
+import { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
 import IconizedContextMenu, {
     IconizedContextMenuOptionList,
     IconizedContextMenuRadio,
 } from "../context_menus/IconizedContextMenu";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IProps extends IContextMenuProps {
     room: Room;
diff --git a/src/components/views/context_menus/SpaceContextMenu.tsx b/src/components/views/context_menus/SpaceContextMenu.tsx
index 11a1364d6d39a941c29f2f5ea8738bf04311c21a..eab9c1d011425a8363746f20e210a46c10d9832b 100644
--- a/src/components/views/context_menus/SpaceContextMenu.tsx
+++ b/src/components/views/context_menus/SpaceContextMenu.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext } from "react";
-import { Room, EventType, RoomType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { type Room, EventType, RoomType } from "matrix-js-sdk/src/matrix";
 
-import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
+import { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
 import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
 import { _t } from "../../../languageHandler";
 import {
@@ -22,7 +22,7 @@ import {
 } from "../../../utils/space";
 import { leaveSpace } from "../../../utils/leave-behaviour";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { BetaPill } from "../beta/BetaCard";
 import SettingsStore from "../../../settings/SettingsStore";
@@ -31,7 +31,7 @@ import { Action } from "../../../dispatcher/actions";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../../settings/UIFeature";
 import PosthogTrackers from "../../../PosthogTrackers";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 
 interface IProps extends IContextMenuProps {
     space?: Room;
diff --git a/src/components/views/context_menus/ThreadListContextMenu.tsx b/src/components/views/context_menus/ThreadListContextMenu.tsx
index eea98159546d53f23f2a42474799bc07f3013961..ef32d6228288f84bd72009ca6d583fbdced37075 100644
--- a/src/components/views/context_menus/ThreadListContextMenu.tsx
+++ b/src/components/views/context_menus/ThreadListContextMenu.tsx
@@ -7,19 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useCallback, useEffect } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { copyPlaintext } from "../../../utils/strings";
-import { ChevronFace, ContextMenuTooltipButton, MenuProps, useContextMenu } from "../../structures/ContextMenu";
+import { ChevronFace, ContextMenuTooltipButton, type MenuProps, useContextMenu } from "../../structures/ContextMenu";
 import { _t } from "../../../languageHandler";
 import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
 import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 
 export interface ThreadListContextMenuProps {
     mxEvent: MatrixEvent;
diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx
index 894e8f8c7d4be311c25bf603ec7af3aeecf8166b..5b03d54e1758f36b00ca4c0cb5b2b15b2c025858 100644
--- a/src/components/views/context_menus/WidgetContextMenu.tsx
+++ b/src/components/views/context_menus/WidgetContextMenu.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, useContext } from "react";
-import { ClientWidgetApi, IWidget, MatrixCapabilities } from "matrix-widget-api";
+import React, { type JSX, type ComponentProps, useContext } from "react";
+import { type ClientWidgetApi, type IWidget, MatrixCapabilities } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
 import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
 import { ChevronFace } from "../../structures/ContextMenu";
@@ -187,14 +187,15 @@ export const WidgetContextMenu: React.FC<IProps> = ({
                 onDeleteClick();
             } else if (roomId) {
                 // Show delete confirmation dialog
-                Modal.createDialog(QuestionDialog, {
+                const { finished } = Modal.createDialog(QuestionDialog, {
                     title: _t("widget|context_menu|delete"),
                     description: _t("widget|context_menu|delete_warning"),
                     button: _t("widget|context_menu|delete"),
-                    onFinished: (confirmed) => {
-                        if (!confirmed) return;
-                        WidgetUtils.setRoomWidget(cli, roomId, app.id);
-                    },
+                });
+
+                finished.then(([confirmed]) => {
+                    if (!confirmed) return;
+                    WidgetUtils.setRoomWidget(cli, roomId, app.id);
                 });
             }
 
diff --git a/src/components/views/dialogs/AddExistingSubspaceDialog.tsx b/src/components/views/dialogs/AddExistingSubspaceDialog.tsx
index d7311244b7f21c9637193f79a9a21687f2b51707..09a828194af6a638b9695550db3627223e57bb71 100644
--- a/src/components/views/dialogs/AddExistingSubspaceDialog.tsx
+++ b/src/components/views/dialogs/AddExistingSubspaceDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import BaseDialog from "./BaseDialog";
diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
index 3cc62f4155441049ea1f294735e83a159f97a5b3..c247c3aea97386bac5fe373efe7de8c54e0b6c19 100644
--- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
+++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
@@ -1,27 +1,27 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, ReactNode, useContext, useMemo, useRef, useState } from "react";
+import React, { type ReactElement, type ReactNode, useContext, useId, useMemo, useRef, useState } from "react";
 import classNames from "classnames";
-import { Room, EventType } from "matrix-js-sdk/src/matrix";
+import { type Room, EventType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { sleep } from "matrix-js-sdk/src/utils";
 import { logger } from "matrix-js-sdk/src/logger";
 import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import BaseDialog from "./BaseDialog";
 import Dropdown from "../elements/Dropdown";
 import SearchBox from "../../structures/SearchBox";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
 import RoomAvatar from "../avatars/RoomAvatar";
 import { getDisplayAliasForRoom } from "../../../Rooms";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
 import DMRoomMap from "../../../utils/DMRoomMap";
 import { calculateRoomVia } from "../../../utils/permalinks/Permalinks";
@@ -34,7 +34,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher";
 import LazyRenderList from "../elements/LazyRenderList";
 import { useSettingValue } from "../../../hooks/useSettings";
 import { filterBoolean } from "../../../utils/arrays";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 // These values match CSS
 const ROW_HEIGHT = 32 + 12;
@@ -53,8 +53,9 @@ export const Entry: React.FC<{
     checked: boolean;
     onChange?(value: boolean): void;
 }> = ({ room, checked, onChange }) => {
+    const id = useId();
     return (
-        <label className="mx_AddExistingToSpace_entry">
+        <li id={id} className="mx_AddExistingToSpace_entry" aria-label={room.name}>
             {room?.isSpaceRoom() ? (
                 <RoomAvatar room={room} size="32px" />
             ) : (
@@ -62,11 +63,12 @@ export const Entry: React.FC<{
             )}
             <span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
             <StyledCheckbox
+                aria-labelledby={id}
                 onChange={onChange ? (e) => onChange(e.currentTarget.checked) : undefined}
                 checked={checked}
                 disabled={!onChange}
             />
-        </label>
+        </li>
     );
 };
 
@@ -357,6 +359,7 @@ const defaultRendererFactory =
         <div className="mx_AddExistingToSpace_section">
             <h3>{_t(title)}</h3>
             <LazyRenderList
+                element="ul"
                 itemHeight={ROW_HEIGHT}
                 items={rooms}
                 scrollTop={scrollTop}
diff --git a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx
index 5396d1e9a7807fd791dfe63c2a7bda54a857c5e2..d80f03b7d40378d8bb2bc80fcbb20a592c214b31 100644
--- a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx
+++ b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx
@@ -11,7 +11,7 @@ import React from "react";
 import BaseDialog from "./BaseDialog";
 import { _t } from "../../../languageHandler";
 import DialogButtons from "../elements/DialogButtons";
-import Modal, { ComponentProps } from "../../../Modal";
+import Modal, { type ComponentProps, type IHandle } from "../../../Modal";
 import SdkConfig from "../../../SdkConfig";
 import { getPolicyUrl } from "../../../toasts/AnalyticsToast";
 import ExternalLink from "../elements/ExternalLink";
@@ -91,10 +91,10 @@ export const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
 
 export const showDialog = (
     props: Omit<ComponentProps<typeof AnalyticsLearnMoreDialog>, "cookiePolicyUrl" | "analyticsOwner">,
-): void => {
+): IHandle<typeof AnalyticsLearnMoreDialog> => {
     const privacyPolicyUrl = getPolicyUrl();
     const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand");
-    Modal.createDialog(
+    return Modal.createDialog(
         AnalyticsLearnMoreDialog,
         {
             privacyPolicyUrl,
diff --git a/src/components/views/dialogs/AskInviteAnywayDialog.tsx b/src/components/views/dialogs/AskInviteAnywayDialog.tsx
index b18d70df11252bf5006c86139ca7167b410e38cf..ddc4c04d4abfa1a43512db4119905da025678a5f 100644
--- a/src/components/views/dialogs/AskInviteAnywayDialog.tsx
+++ b/src/components/views/dialogs/AskInviteAnywayDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback } from "react";
+import React, { type JSX, useCallback } from "react";
 
 import { _t } from "../../../languageHandler";
 import SettingsStore from "../../../settings/SettingsStore";
diff --git a/src/components/views/dialogs/BaseDialog.tsx b/src/components/views/dialogs/BaseDialog.tsx
index 39afb2e6213ec8fa92f32daac035642ca6029ac7..bf23919771cc0a1dc952a7d61a8dfec10531a9a0 100644
--- a/src/components/views/dialogs/BaseDialog.tsx
+++ b/src/components/views/dialogs/BaseDialog.tsx
@@ -8,17 +8,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import FocusLock from "react-focus-lock";
 import classNames from "classnames";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import AccessibleButton from "../elements/AccessibleButton";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import Heading from "../typography/Heading";
-import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
+import { PosthogScreenTracker, type ScreenName } from "../../../PosthogTrackers";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 
diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx
index 2c13ae000601ffcddfeaca32f19ad0cd5dd88a5a..081f8beaca346a63e8ca059d3a7941db33fdcd90 100644
--- a/src/components/views/dialogs/BetaFeedbackDialog.tsx
+++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx
@@ -15,7 +15,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { UserTab } from "./UserTab";
 import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
-import { SettingKey } from "../../../settings/Settings.tsx";
+import { type SettingKey } from "../../../settings/Settings.tsx";
 
 // XXX: Keep this around for re-use in future Betas
 
diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx
index 013f9ecb0613ff0e1ac4127c749817cd275ec53f..83e6da8dfe9dd3367c14b48f72f94c4e770aad58 100644
--- a/src/components/views/dialogs/BugReportDialog.tsx
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -9,12 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX, type ReactNode } from "react";
+import { Link } from "@vector-im/compound-web";
 
 import SdkConfig from "../../../SdkConfig";
 import Modal from "../../../Modal";
 import { _t } from "../../../languageHandler";
-import sendBugReport, { downloadBugReport } from "../../../rageshake/submit-rageshake";
+import sendBugReport, { downloadBugReport, RageshakeError } from "../../../rageshake/submit-rageshake";
 import AccessibleButton from "../elements/AccessibleButton";
 import QuestionDialog from "./QuestionDialog";
 import BaseDialog from "./BaseDialog";
@@ -26,7 +27,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { getBrowserSupport } from "../../../SupportedBrowser";
 
-interface IProps {
+export interface BugReportDialogProps {
     onFinished: (success: boolean) => void;
     initialText?: string;
     label?: string;
@@ -36,7 +37,7 @@ interface IProps {
 interface IState {
     sendLogs: boolean;
     busy: boolean;
-    err: string | null;
+    err: ReactNode | null;
     issueUrl: string;
     text: string;
     progress: string | null;
@@ -44,11 +45,11 @@ interface IState {
     downloadProgress: string | null;
 }
 
-export default class BugReportDialog extends React.Component<IProps, IState> {
+export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
     private unmounted: boolean;
-    private issueRef: React.RefObject<Field>;
+    private issueRef: React.RefObject<Field | null>;
 
-    public constructor(props: IProps) {
+    public constructor(props: BugReportDialogProps) {
         super(props);
 
         this.state = {
@@ -89,6 +90,42 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
         this.props.onFinished(false);
     };
 
+    private getErrorText(error: Error | RageshakeError): ReactNode {
+        if (error instanceof RageshakeError) {
+            let errorText;
+            switch (error.errorcode) {
+                case "DISALLOWED_APP":
+                    errorText = _t("bug_reporting|failed_send_logs_causes|disallowed_app");
+                    break;
+                case "REJECTED_BAD_VERSION":
+                    errorText = _t("bug_reporting|failed_send_logs_causes|rejected_version");
+                    break;
+                case "REJECTED_UNEXPECTED_RECOVERY_KEY":
+                    errorText = _t("bug_reporting|failed_send_logs_causes|rejected_recovery_key");
+                    break;
+                default:
+                    if (error.errorcode?.startsWith("REJECTED")) {
+                        errorText = _t("bug_reporting|failed_send_logs_causes|rejected_generic");
+                    } else {
+                        errorText = _t("bug_reporting|failed_send_logs_causes|server_unknown_error");
+                    }
+                    break;
+            }
+            return (
+                <>
+                    <p>{errorText}</p>
+                    {error.policyURL && (
+                        <Link size="medium" target="_blank" href={error.policyURL}>
+                            {_t("action|learn_more")}
+                        </Link>
+                    )}
+                </>
+            );
+        } else {
+            return <p>{_t("bug_reporting|failed_send_logs_causes|unknown_error")}</p>;
+        }
+    }
+
     private onSubmit = (): void => {
         if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
             this.setState({
@@ -126,7 +163,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
                     this.setState({
                         busy: false,
                         progress: null,
-                        err: _t("bug_reporting|failed_send_logs") + `${err.message}`,
+                        err: this.getErrorText(err),
                     });
                 }
             },
@@ -155,7 +192,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
                 this.setState({
                     downloadBusy: false,
                     downloadProgress:
-                        _t("bug_reporting|failed_send_logs") + `${err instanceof Error ? err.message : ""}`,
+                        _t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`,
                 });
             }
         }
diff --git a/src/components/views/dialogs/BulkRedactDialog.tsx b/src/components/views/dialogs/BulkRedactDialog.tsx
index d766c6c9732c316984590073296fabaea1349f99..1b8bf9b07ecee252a92d448db9677fa4bd71306a 100644
--- a/src/components/views/dialogs/BulkRedactDialog.tsx
+++ b/src/components/views/dialogs/BulkRedactDialog.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -8,7 +8,14 @@ Please see LICENSE files in the repository root for full details.
 
 import React, { useState } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient, RoomMember, Room, MatrixEvent, EventTimeline, EventType } from "matrix-js-sdk/src/matrix";
+import {
+    type MatrixClient,
+    type RoomMember,
+    type Room,
+    type MatrixEvent,
+    EventTimeline,
+    EventType,
+} from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
@@ -106,12 +113,13 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
                 <div className="mx_Dialog_content" id="mx_Dialog_content">
                     <p>{_t("user_info|redact|confirm_description_1", { count, user })}</p>
                     <p>{_t("user_info|redact|confirm_description_2")}</p>
-                    <StyledCheckbox checked={keepStateEvents} onChange={(e) => setKeepStateEvents(e.target.checked)}>
+                    <StyledCheckbox
+                        description={_t("user_info|redact|confirm_keep_state_explainer")}
+                        checked={keepStateEvents}
+                        onChange={(e) => setKeepStateEvents(e.target.checked)}
+                    >
                         {_t("user_info|redact|confirm_keep_state_label")}
                     </StyledCheckbox>
-                    <div className="mx_BulkRedactDialog_checkboxMicrocopy">
-                        {_t("user_info|redact|confirm_keep_state_explainer")}
-                    </div>
                 </div>
                 <DialogButtons
                     primaryButton={_t("user_info|redact|confirm_button", { count })}
diff --git a/src/components/views/dialogs/ChangelogDialog.tsx b/src/components/views/dialogs/ChangelogDialog.tsx
index 087beaf6a826969aa259b1d937eea5239398e685..6e12674653797f9b18a88c2b0c84aec5909a8440 100644
--- a/src/components/views/dialogs/ChangelogDialog.tsx
+++ b/src/components/views/dialogs/ChangelogDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { _t } from "../../../languageHandler";
 import QuestionDialog from "./QuestionDialog";
diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
index 2d7500e72c5d899c724c2af7e4b8d394860214da..8fc7f6a256d771e1df51371116092bf021b57c74 100644
--- a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
+++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import ConfirmRedactDialog from "./ConfirmRedactDialog";
diff --git a/src/components/views/dialogs/ConfirmKeyStorageOffDialog.tsx b/src/components/views/dialogs/ConfirmKeyStorageOffDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..49e7cad17bac233f0c3fe4a5fc7b9775ae1d5ef6
--- /dev/null
+++ b/src/components/views/dialogs/ConfirmKeyStorageOffDialog.tsx
@@ -0,0 +1,80 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
+import { Button } from "@vector-im/compound-web";
+import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+
+import { _t } from "../../../languageHandler";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import { EncryptionCard } from "../settings/encryption/EncryptionCard";
+import { EncryptionCardButtons } from "../settings/encryption/EncryptionCardButtons";
+import { type OpenToTabPayload } from "../../../dispatcher/payloads/OpenToTabPayload";
+import { Action } from "../../../dispatcher/actions";
+import { UserTab } from "./UserTab";
+
+interface Props {
+    onFinished: (dismissed: boolean) => void;
+}
+
+/**
+ * Ask the user whether they really want to dismiss the toast about key storage.
+ *
+ * Launched from the {@link SetupEncryptionToast} in mode `TURN_ON_KEY_STORAGE`,
+ * when the user clicks "Dismiss". The caller handles any action via the
+ * `onFinished` prop which takes a boolean that is true if the user clicked
+ * "Yes, dismiss".
+ */
+export default class ConfirmKeyStorageOffDialog extends React.Component<Props> {
+    public constructor(props: Props) {
+        super(props);
+    }
+
+    private onGoToSettingsClick = (): void => {
+        // Open Settings at the Encryption tab
+        const payload: OpenToTabPayload = {
+            action: Action.ViewUserSettings,
+            initialTabId: UserTab.Encryption,
+        };
+        defaultDispatcher.dispatch(payload);
+        this.props.onFinished(false);
+    };
+
+    private onDismissClick = (): void => {
+        this.props.onFinished(true);
+    };
+
+    public render(): React.ReactNode {
+        return (
+            <EncryptionCard
+                Icon={ErrorIcon}
+                destructive={true}
+                title={_t("settings|encryption|confirm_key_storage_off")}
+            >
+                {_t("settings|encryption|confirm_key_storage_off_description", undefined, {
+                    a: (sub) => (
+                        <>
+                            <br />
+                            <a href="https://element.io/help#encryption5" target="_blank" rel="noreferrer noopener">
+                                {sub} <PopOutIcon />
+                            </a>
+                        </>
+                    ),
+                })}
+                <EncryptionCardButtons>
+                    <Button onClick={this.onGoToSettingsClick} autoFocus kind="primary" className="">
+                        {_t("common|go_to_settings")}
+                    </Button>
+                    <Button onClick={this.onDismissClick} kind="secondary">
+                        {_t("action|yes_dismiss")}
+                    </Button>
+                </EncryptionCardButtons>
+            </EncryptionCard>
+        );
+    }
+}
diff --git a/src/components/views/dialogs/ConfirmRedactDialog.tsx b/src/components/views/dialogs/ConfirmRedactDialog.tsx
index 7733d83585bf6149ff9ca39344025faac64ea5e9..17bf75ccadb15a82ec49f19e1f42653db767c5bb 100644
--- a/src/components/views/dialogs/ConfirmRedactDialog.tsx
+++ b/src/components/views/dialogs/ConfirmRedactDialog.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IRedactOpts, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type IRedactOpts, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
 import { _t } from "../../../languageHandler";
@@ -58,37 +58,32 @@ export function createRedactEventDialog({
     const roomId = mxEvent.getRoomId();
 
     if (!roomId) throw new Error(`cannot redact event ${mxEvent.getId()} without room ID`);
-    Modal.createDialog(
-        ConfirmRedactDialog,
-        {
-            event: mxEvent,
-            onFinished: async (proceed, reason): Promise<void> => {
-                if (!proceed) return;
+    const { finished } = Modal.createDialog(ConfirmRedactDialog, { event: mxEvent }, "mx_Dialog_confirmredact");
 
-                const cli = MatrixClientPeg.safeGet();
-                const withRelTypes: Pick<IRedactOpts, "with_rel_types"> = {};
+    finished.then(async ([proceed, reason]) => {
+        if (!proceed) return;
 
-                try {
-                    onCloseDialog?.();
-                    await cli.redactEvent(roomId, eventId, undefined, {
-                        ...(reason ? { reason } : {}),
-                        ...withRelTypes,
-                    });
-                } catch (e: any) {
-                    const code = e.errcode || e.statusCode;
-                    // only show the dialog if failing for something other than a network error
-                    // (e.g. no errcode or statusCode) as in that case the redactions end up in the
-                    // detached queue and we show the room status bar to allow retry
-                    if (typeof code !== "undefined") {
-                        // display error message stating you couldn't delete this.
-                        Modal.createDialog(ErrorDialog, {
-                            title: _t("common|error"),
-                            description: _t("redact|error", { code }),
-                        });
-                    }
-                }
-            },
-        },
-        "mx_Dialog_confirmredact",
-    );
+        const cli = MatrixClientPeg.safeGet();
+        const withRelTypes: Pick<IRedactOpts, "with_rel_types"> = {};
+
+        try {
+            onCloseDialog?.();
+            await cli.redactEvent(roomId, eventId, undefined, {
+                ...(reason ? { reason } : {}),
+                ...withRelTypes,
+            });
+        } catch (e: any) {
+            const code = e.errcode || e.statusCode;
+            // only show the dialog if failing for something other than a network error
+            // (e.g. no errcode or statusCode) as in that case the redactions end up in the
+            // detached queue and we show the room status bar to allow retry
+            if (typeof code !== "undefined") {
+                // display error message stating you couldn't delete this.
+                Modal.createDialog(ErrorDialog, {
+                    title: _t("common|error"),
+                    description: _t("redact|error", { code }),
+                });
+            }
+        }
+    });
 }
diff --git a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx
index 9c21b469e4ae2c9d42a6e80bed38edac5bfd2c27..3851460bdd75fc66c966052c332cdf7cb3b93fb8 100644
--- a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx
+++ b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, useMemo, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ComponentProps, useMemo, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import ConfirmUserActionDialog from "./ConfirmUserActionDialog";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.tsx b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
index f79601f855efe334d41c8bb0d8cd902f44506b41..3fc7b25aec80b87ff9179f294b481da7cefc5058 100644
--- a/src/components/views/dialogs/ConfirmUserActionDialog.tsx
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, FormEvent, ReactNode } from "react";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent, type FormEvent, type ReactNode } from "react";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx
index d77ec145787c198c9a538df74335d03d2e020f53..3ff42cde2276b6323ce85cceabe157a4c456e550 100644
--- a/src/components/views/dialogs/CreateRoomDialog.tsx
+++ b/src/components/views/dialogs/CreateRoomDialog.tsx
@@ -7,14 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef, KeyboardEvent, SyntheticEvent } from "react";
-import { Room, RoomType, JoinRule, Preset, Visibility } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ChangeEvent, createRef, type KeyboardEvent, type SyntheticEvent } from "react";
+import { type Room, RoomType, JoinRule, Preset, Visibility } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../../../SdkConfig";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { checkUserIsAllowedToChangeEncryption, IOpts } from "../../../createRoom";
+import { checkUserIsAllowedToChangeEncryption, type IOpts } from "../../../createRoom";
 import Field from "../elements/Field";
 import RoomAliasField from "../elements/RoomAliasField";
 import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx
index 6af128ef646dfadd3c86b02b9ac5b714a0d2fb16..290c60673112957ea61b025d8876d233dbef9444 100644
--- a/src/components/views/dialogs/CreateSubspaceDialog.tsx
+++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx
@@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useRef, useState } from "react";
-import { Room, JoinRule } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useRef, useState } from "react";
+import { type Room, JoinRule } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
 import BaseDialog from "./BaseDialog";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { BetaPill } from "../beta/BetaCard";
-import Field from "../elements/Field";
-import RoomAliasField from "../elements/RoomAliasField";
+import type Field from "../elements/Field";
+import type RoomAliasField from "../elements/RoomAliasField";
 import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu";
 import { SubspaceSelector } from "./AddExistingToSpaceDialog";
 import JoinRuleDropdown from "../elements/JoinRuleDropdown";
diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx
index 52140b2967b0e7b0f554210d19ebbf3fae56dc87..0622cb25ebd4e40b333700ca2ac5d4e852f0c6b0 100644
--- a/src/components/views/dialogs/DeactivateAccountDialog.tsx
+++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth";
+import React, { type JSX } from "react";
+import { type AuthType, type IAuthData } from "matrix-js-sdk/src/interactive-auth";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
-import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
-import { ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
+import InteractiveAuth, { ERROR_USER_CANCELLED, type InteractiveAuthCallback } from "../../structures/InteractiveAuth";
+import { type ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
 import StyledCheckbox from "../elements/StyledCheckbox";
 import BaseDialog from "./BaseDialog";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
diff --git a/src/components/views/dialogs/DeclineAndBlockInviteDialog.tsx b/src/components/views/dialogs/DeclineAndBlockInviteDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a10dfbe42b2105ba8b42793172afbebd9f4819ef
--- /dev/null
+++ b/src/components/views/dialogs/DeclineAndBlockInviteDialog.tsx
@@ -0,0 +1,82 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type ChangeEventHandler, useCallback, useState } from "react";
+import { Field, Label, Root } from "@vector-im/compound-web";
+
+import { _t } from "../../../languageHandler";
+import BaseDialog from "./BaseDialog";
+import DialogButtons from "../elements/DialogButtons";
+import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
+
+interface IProps {
+    onFinished: (shouldReject: boolean, ignoreUser: boolean, reportRoom: false | string) => void;
+    roomName: string;
+}
+
+export const DeclineAndBlockInviteDialog: React.FunctionComponent<IProps> = ({ onFinished, roomName }) => {
+    const [shouldReport, setShouldReport] = useState<boolean>(false);
+    const [ignoreUser, setIgnoreUser] = useState<boolean>(false);
+
+    const [reportReason, setReportReason] = useState<string>("");
+    const reportReasonChanged = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
+        (e) => setReportReason(e.target.value),
+        [setReportReason],
+    );
+
+    const onCancel = useCallback(() => onFinished(false, false, false), [onFinished]);
+    const onOk = useCallback(
+        () => onFinished(true, ignoreUser, shouldReport ? reportReason : false),
+        [onFinished, ignoreUser, shouldReport, reportReason],
+    );
+
+    return (
+        <BaseDialog
+            className="mx_DeclineAndBlockInviteDialog"
+            onFinished={onCancel}
+            title={_t("decline_invitation_dialog|title")}
+            contentId="mx_Dialog_content"
+        >
+            <Root>
+                <p>{_t("decline_invitation_dialog|confirm", { roomName })}</p>
+                <LabelledToggleSwitch
+                    label={_t("report_content|ignore_user")}
+                    onChange={setIgnoreUser}
+                    caption={_t("decline_invitation_dialog|ignore_user_help")}
+                    value={ignoreUser}
+                />
+                <LabelledToggleSwitch
+                    label={_t("action|report_room")}
+                    onChange={setShouldReport}
+                    caption={_t("decline_invitation_dialog|report_room_description")}
+                    value={shouldReport}
+                />
+                <Field name="report-reason" aria-disabled={!shouldReport}>
+                    <Label htmlFor="mx_DeclineAndBlockInviteDialog_reason">
+                        {_t("room_settings|permissions|ban_reason")}
+                    </Label>
+                    <textarea
+                        id="mx_DeclineAndBlockInviteDialog_reason"
+                        className="mx_RoomReportTextArea"
+                        placeholder={_t("decline_invitation_dialog|reason_description")}
+                        rows={5}
+                        onChange={reportReasonChanged}
+                        value={shouldReport ? reportReason : ""}
+                        disabled={!shouldReport}
+                    />
+                </Field>
+                <DialogButtons
+                    primaryButton={_t("action|decline_invite")}
+                    primaryButtonClass="danger"
+                    cancelButton={_t("action|cancel")}
+                    onPrimaryButtonClick={onOk}
+                    onCancel={onCancel}
+                />
+            </Root>
+        </BaseDialog>
+    );
+};
diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index e9e2c9a33447d264f17a6cdf0822cf97976cb283..9b8b28ea7fefc0d3d5571ddd2831cf529db50e49 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -7,16 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState } from "react";
+import React, { type JSX, useState } from "react";
 
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import BaseDialog from "./BaseDialog";
 import { TimelineEventEditor } from "./devtools/Event";
 import ServersInRoom from "./devtools/ServersInRoom";
 import SettingExplorer from "./devtools/SettingExplorer";
 import { RoomStateExplorer } from "./devtools/RoomState";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./devtools/BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./devtools/BaseTool";
 import WidgetExplorer from "./devtools/WidgetExplorer";
 import { AccountDataExplorer, RoomAccountDataExplorer } from "./devtools/AccountData";
 import SettingsFlag from "../elements/SettingsFlag";
@@ -24,6 +24,8 @@ import { SettingLevel } from "../../../settings/SettingLevel";
 import ServerInfo from "./devtools/ServerInfo";
 import CopyableText from "../elements/CopyableText";
 import RoomNotifications from "./devtools/RoomNotifications";
+import { Crypto } from "./devtools/Crypto";
+import SettingsField from "../elements/SettingsField.tsx";
 
 enum Category {
     Room,
@@ -49,6 +51,7 @@ const Tools: Record<Category, [label: TranslationKey, tool: Tool][]> = {
         [_td("devtools|explore_account_data"), AccountDataExplorer],
         [_td("devtools|settings_explorer"), SettingExplorer],
         [_td("devtools|server_info"), ServerInfo],
+        [_td("devtools|crypto|title"), Crypto],
     ],
 };
 
@@ -99,6 +102,7 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished })
                     <SettingsFlag name="developerMode" level={SettingLevel.ACCOUNT} />
                     <SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
                     <SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
+                    <SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} />
                 </div>
             </BaseTool>
         );
diff --git a/src/components/views/dialogs/EndPollDialog.tsx b/src/components/views/dialogs/EndPollDialog.tsx
index 03d9c7f7f5eb0673f716ceaf3375b0a78c720780..bb654fe6071b6dc60dcf0a4382108ed9b86cd879 100644
--- a/src/components/views/dialogs/EndPollDialog.tsx
+++ b/src/components/views/dialogs/EndPollDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, MatrixClient, TimelineEvents } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type MatrixClient, type TimelineEvents } from "matrix-js-sdk/src/matrix";
 import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
 
 import { _t } from "../../../languageHandler";
@@ -15,7 +15,7 @@ import QuestionDialog from "./QuestionDialog";
 import { findTopAnswer } from "../messages/MPollBody";
 import Modal from "../../../Modal";
 import ErrorDialog from "./ErrorDialog";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 
 interface IProps {
     matrixClient: MatrixClient;
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index bd7ad24bca39fe4708b2a7220d597f1047f0c74e..a1b6daa7219a3d8dfd4bccccce383509d6336edc 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useRef, useState, Dispatch, SetStateAction } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useRef, useState, type Dispatch, type SetStateAction } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
@@ -18,18 +18,18 @@ import StyledRadioGroup from "../elements/StyledRadioGroup";
 import StyledCheckbox from "../elements/StyledCheckbox";
 import {
     ExportFormat,
-    ExportFormatKey,
+    type ExportFormatKey,
     ExportType,
-    ExportTypeKey,
+    type ExportTypeKey,
     textForFormat,
     textForType,
 } from "../../../utils/exportUtils/exportUtils";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
 import HTMLExporter from "../../../utils/exportUtils/HtmlExport";
 import JSONExporter from "../../../utils/exportUtils/JSONExport";
 import PlainTextExporter from "../../../utils/exportUtils/PlainTextExport";
 import { useStateCallback } from "../../../hooks/useStateCallback";
-import Exporter from "../../../utils/exportUtils/Exporter";
+import type Exporter from "../../../utils/exportUtils/Exporter";
 import Spinner from "../elements/Spinner";
 import InfoDialog from "./InfoDialog";
 import ChatExport from "../../../customisations/ChatExport";
diff --git a/src/components/views/dialogs/FeedbackDialog.tsx b/src/components/views/dialogs/FeedbackDialog.tsx
index 27de6259209a02077e642c733ca76010136b10b2..5e1efba38f2cc46de3e0307eba706f7e9d4c880a 100644
--- a/src/components/views/dialogs/FeedbackDialog.tsx
+++ b/src/components/views/dialogs/FeedbackDialog.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useEffect, useRef, useState } from "react";
+import React, { type JSX, useEffect, useRef, useState } from "react";
 
 import QuestionDialog from "./QuestionDialog";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index a831acc5d18456e29174adb2408cd04c620d8422..512f0ad697932ff117219420c05457857cd4c1de 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -6,21 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useEffect, useMemo, useState } from "react";
+import React, { type JSX, useEffect, useMemo, useState } from "react";
 import classnames from "classnames";
 import {
-    IContent,
+    type IContent,
     MatrixEvent,
-    Room,
-    RoomMember,
+    type Room,
+    type RoomMember,
     EventType,
-    MatrixClient,
+    type MatrixClient,
     ContentHelpers,
-    ILocationContent,
+    type ILocationContent,
     LocationAssetType,
     M_TIMESTAMP,
     M_BEACON,
-    TimelineEvents,
+    type TimelineEvents,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
@@ -36,19 +36,19 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
 import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
 import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
 import NotificationBadge from "../rooms/NotificationBadge";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
 import QueryMatcher from "../../../autocomplete/QueryMatcher";
 import TruncatedList from "../elements/TruncatedList";
 import { Action } from "../../../dispatcher/actions";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { isLocationEvent } from "../../../utils/EventUtils";
 import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
 import { RoomContextDetails } from "../rooms/RoomContextDetails";
 import { filterBoolean } from "../../../utils/arrays";
 import {
-    IState,
+    type IState,
     RovingTabIndexContext,
     RovingTabIndexProvider,
     Type,
diff --git a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
index 1ea187361eb5de251cd97d253e60a1fd84bacc5e..3967de7c4d837e7c16940b8d35576fd775466f3a 100644
--- a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
+++ b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
@@ -1,12 +1,12 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useState } from "react";
+import React, { type ReactNode, useState } from "react";
 
 import QuestionDialog from "./QuestionDialog";
 import { _t } from "../../../languageHandler";
@@ -78,7 +78,6 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
                         }}
                         autoFocus={true}
                     />
-
                     <StyledCheckbox
                         checked={canContact}
                         onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}
diff --git a/src/components/views/dialogs/IncomingSasDialog.tsx b/src/components/views/dialogs/IncomingSasDialog.tsx
index e5df3a35b1f9a36f1989acbd6e62da105902815f..bb210d0d2309e1ff1540175bb709394c6f8c54b8 100644
--- a/src/components/views/dialogs/IncomingSasDialog.tsx
+++ b/src/components/views/dialogs/IncomingSasDialog.tsx
@@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { GeneratedSas, ShowSasCallbacks, Verifier, VerifierEvent } from "matrix-js-sdk/src/crypto-api";
+import React, { type ReactNode } from "react";
+import { type GeneratedSas, type ShowSasCallbacks, type Verifier, VerifierEvent } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
diff --git a/src/components/views/dialogs/InfoDialog.tsx b/src/components/views/dialogs/InfoDialog.tsx
index 20fb51a7d091e7b013f35ca103d4655427ed1aef..febf6a34196afffeace9715a6252adcb68638ee7 100644
--- a/src/components/views/dialogs/InfoDialog.tsx
+++ b/src/components/views/dialogs/InfoDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/InteractiveAuthDialog.tsx b/src/components/views/dialogs/InteractiveAuthDialog.tsx
index beb27573c6b47967ac638ba2cb937a79814cedbc..d4ea7218c7a44bdb432b6843c0cf353691e6b995 100644
--- a/src/components/views/dialogs/InteractiveAuthDialog.tsx
+++ b/src/components/views/dialogs/InteractiveAuthDialog.tsx
@@ -8,18 +8,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixClient, UIAResponse } from "matrix-js-sdk/src/matrix";
-import { AuthType } from "matrix-js-sdk/src/interactive-auth";
+import React, { type JSX } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type AuthType } from "matrix-js-sdk/src/interactive-auth";
 
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
 import InteractiveAuth, {
     ERROR_USER_CANCELLED,
-    InteractiveAuthCallback,
-    InteractiveAuthProps,
+    type InteractiveAuthCallback,
+    type InteractiveAuthProps,
 } from "../../structures/InteractiveAuth";
-import { ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
+import { type ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
 import BaseDialog from "./BaseDialog";
 import { Linkify } from "../../../Linkify";
 
@@ -63,7 +63,7 @@ export interface InteractiveAuthDialogProps<T = unknown>
     // Default is defined in _getDefaultDialogAesthetics()
     aestheticsForStagePhases?: DialogAesthetics;
 
-    onFinished(success?: boolean, result?: UIAResponse<T> | Error | null): void;
+    onFinished(success?: boolean, result?: T | Error | null): void;
 }
 
 interface IState {
@@ -111,7 +111,7 @@ export default class InteractiveAuthDialog<T> extends React.Component<Interactiv
 
     private onAuthFinished: InteractiveAuthCallback<T> = async (success, result): Promise<void> => {
         if (success) {
-            this.props.onFinished(true, result);
+            this.props.onFinished(true, result as T);
         } else {
             if (result === ERROR_USER_CANCELLED) {
                 this.props.onFinished(false, null);
@@ -136,7 +136,7 @@ export default class InteractiveAuthDialog<T> extends React.Component<Interactiv
         // Let's pick a title, body, and other params text that we'll show to the user. The order
         // is most specific first, so stagePhase > our props > defaults.
 
-        let title = this.state.authError ? "Error" : this.props.title || _t("common|authentication");
+        let title = this.state.authError ? "Error" : (this.props.title ?? _t("common|authentication"));
         let body = this.state.authError ? null : this.props.body;
         let continueText: string | undefined;
         let continueKind: ContinueKind | undefined;
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index d72baae5235ffbec18d2a9293635229751e45b7a..62536acfd69b0f7054dfa6f6baf167235460c70f 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode, SyntheticEvent } from "react";
+import React, { type JSX, createRef, type ReactNode, type SyntheticEvent } from "react";
 import classNames from "classnames";
-import { RoomMember, Room, MatrixError, EventType } from "matrix-js-sdk/src/matrix";
+import { RoomMember, type Room, MatrixError, EventType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import { logger } from "matrix-js-sdk/src/logger";
 import { uniqBy } from "lodash";
 import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
@@ -26,7 +26,7 @@ import { buildActivityScores, buildMemberScores, compareMembers } from "../../..
 import { abbreviateUrl } from "../../../utils/UrlUtils";
 import IdentityAuthClient from "../../../IdentityAuthClient";
 import { humanizeTime } from "../../../utils/humanize";
-import { IInviteResult, inviteMultipleToRoom, showAnyInviteErrors } from "../../../RoomInvite";
+import { type IInviteResult, inviteMultipleToRoom, showAnyInviteErrors } from "../../../RoomInvite";
 import { Action } from "../../../dispatcher/actions";
 import { DefaultTagID } from "../../../stores/room-list/models";
 import RoomListStore from "../../../stores/room-list/RoomListStore";
@@ -35,7 +35,7 @@ import { UIFeature } from "../../../settings/UIFeature";
 import { mediaFromMxc } from "../../../customisations/Media";
 import BaseAvatar from "../avatars/BaseAvatar";
 import { SearchResultAvatar } from "../avatars/SearchResultAvatar";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { selectText } from "../../../utils/strings";
 import Field from "../elements/Field";
 import TabbedView, { Tab, TabLocation } from "../../structures/TabbedView";
@@ -47,13 +47,13 @@ import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
 import LegacyCallHandler from "../../../LegacyCallHandler";
 import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
 import CopyableText from "../elements/CopyableText";
-import { ScreenName } from "../../../PosthogTrackers";
+import { type ScreenName } from "../../../PosthogTrackers";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import {
     DirectoryMember,
-    IDMUserTileProps,
-    Member,
+    type IDMUserTileProps,
+    type Member,
     startDmOnFirstMessage,
     ThreepidMember,
 } from "../../../utils/direct-messages";
@@ -61,11 +61,11 @@ import { InviteKind } from "./InviteDialogTypes";
 import Modal from "../../../Modal";
 import dis from "../../../dispatcher/dispatcher";
 import { privateShouldBeEncrypted } from "../../../utils/rooms";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 import { UNKNOWN_PROFILE_ERRORS } from "../../../utils/MultiInviter";
-import AskInviteAnywayDialog, { UnknownProfiles } from "./AskInviteAnywayDialog";
+import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDialog";
 import { SdkContextClass } from "../../../contexts/SDKContext";
-import { UserProfilesStore } from "../../../stores/UserProfilesStore";
+import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
 
 // we have a number of types defined from the Matrix spec which can't reasonably be altered here.
 /* eslint-disable camelcase */
@@ -343,7 +343,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
 
     private debounceTimer: number | null = null; // actually number because we're in the browser
     private editorRef = createRef<HTMLInputElement>();
-    private numberEntryFieldRef: React.RefObject<Field> = createRef();
+    private numberEntryFieldRef = createRef<Field>();
     private unmounted = false;
     private encryptionByDefault = false;
     private profilesStore: UserProfilesStore;
diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx
index 72ad5f478ffb8b69b9390a9a3e9d59d1b6a5197d..e81606db7977d7f217640590e1cf6098fe7e5a32 100644
--- a/src/components/views/dialogs/LeaveSpaceDialog.tsx
+++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useMemo, useState } from "react";
-import { Room, JoinRule } from "matrix-js-sdk/src/matrix";
+import { type Room, JoinRule } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import DialogButtons from "../elements/DialogButtons";
diff --git a/src/components/views/dialogs/LogoutDialog.tsx b/src/components/views/dialogs/LogoutDialog.tsx
index 460871fb367125eaf6361a282671846c4e717992..6e000ef631105318feeb3eb6b7651034463fa0fe 100644
--- a/src/components/views/dialogs/LogoutDialog.tsx
+++ b/src/components/views/dialogs/LogoutDialog.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React, { lazy } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import Modal from "../../../Modal";
 import dis from "../../../dispatcher/dispatcher";
diff --git a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
index 7a2e3b35ef0f575472d9fb0de07f4d5b4ab25ffd..0f16d3d6f6ef3aa86dfbb1ad8b127efca02a67d5 100644
--- a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
+++ b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -44,22 +44,23 @@ const Entry: React.FC<{
     }
 
     return (
-        <label className="mx_ManageRestrictedJoinRuleDialog_entry">
-            <div>
-                <div>
-                    {localRoom ? <RoomAvatar room={room} size="20px" /> : <RoomAvatar oobData={room} size="20px" />}
-                    <span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
-                </div>
-                {description && (
-                    <div className="mx_ManageRestrictedJoinRuleDialog_entry_description">{description}</div>
-                )}
-            </div>
+        <div className="mx_ManageRestrictedJoinRuleDialog_entry">
             <StyledCheckbox
                 onChange={onChange ? (e) => onChange(e.target.checked) : undefined}
                 checked={checked}
                 disabled={!onChange}
-            />
-        </label>
+                description={description}
+            >
+                <div>
+                    {localRoom ? (
+                        <RoomAvatar role="none" room={room} size="20px" />
+                    ) : (
+                        <RoomAvatar oobData={room} size="20px" />
+                    )}
+                    <span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
+                </div>
+            </StyledCheckbox>
+        </div>
     );
 };
 
diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/src/components/views/dialogs/MessageEditHistoryDialog.tsx
index 3008e2d16cb4846d6ddb30e737e38f26304dd30e..72b11bc4e135baa3a73c41bbfc5d4375b3fa26cb 100644
--- a/src/components/views/dialogs/MessageEditHistoryDialog.tsx
+++ b/src/components/views/dialogs/MessageEditHistoryDialog.tsx
@@ -6,9 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent, EventType, RelationType, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
+import React, { type JSX } from "react";
+import { type MatrixEvent, EventType, RelationType, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -58,7 +57,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
         const eventId = this.props.mxEvent.getId()!;
         const client = MatrixClientPeg.safeGet();
 
-        const { resolve, reject, promise } = defer<boolean>();
+        const { resolve, reject, promise } = Promise.withResolvers<boolean>();
         let result: Awaited<ReturnType<MatrixClient["relations"]>>;
 
         try {
diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx
index 58c6c92a5e0aac9230e4c86e39a3f4467131dde3..250a438c1354f7fcb2f58d34525a28667494a577 100644
--- a/src/components/views/dialogs/ModalWidgetDialog.tsx
+++ b/src/components/views/dialogs/ModalWidgetDialog.tsx
@@ -6,19 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import {
     ClientWidgetApi,
-    IModalWidgetCloseRequest,
-    IModalWidgetOpenRequestData,
-    IModalWidgetReturnData,
-    ISetModalButtonEnabledActionRequest,
-    IWidgetApiAcknowledgeResponseData,
-    IWidgetApiErrorResponseData,
+    type IModalWidgetCloseRequest,
+    type IModalWidgetOpenRequestData,
+    type IModalWidgetReturnData,
+    type ISetModalButtonEnabledActionRequest,
+    type IWidgetApiAcknowledgeResponseData,
+    type IWidgetApiErrorResponseData,
     BuiltInModalButtonID,
-    ModalButtonID,
+    type ModalButtonID,
     ModalButtonKind,
-    Widget,
+    type Widget,
     WidgetApiFromWidgetAction,
     WidgetKind,
 } from "matrix-widget-api";
@@ -26,14 +26,14 @@ import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import BaseDialog from "./BaseDialog";
 import { _t, getUserLanguage } from "../../../languageHandler";
-import AccessibleButton, { AccessibleButtonKind } from "../elements/AccessibleButton";
+import AccessibleButton, { type AccessibleButtonKind } from "../elements/AccessibleButton";
 import { StopGapWidgetDriver } from "../../../stores/widgets/StopGapWidgetDriver";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { OwnProfileStore } from "../../../stores/OwnProfileStore";
 import { arrayFastClone } from "../../../utils/arrays";
 import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
 import { ELEMENT_CLIENT_ID } from "../../../identifiers";
-import SettingsStore from "../../../settings/SettingsStore";
+import ThemeWatcher, { ThemeWatcherEvent } from "../../../settings/watchers/ThemeWatcher";
 
 interface IProps {
     widgetDefinition: IModalWidgetOpenRequestData;
@@ -53,7 +53,8 @@ const MAX_BUTTONS = 3;
 export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> {
     private readonly widget: Widget;
     private readonly possibleButtons: ModalButtonID[];
-    private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
+    private appFrame = React.createRef<HTMLIFrameElement>();
+    private readonly themeWatcher = new ThemeWatcher();
 
     public state: IState = {
         disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
@@ -77,6 +78,8 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
     }
 
     public componentWillUnmount(): void {
+        this.themeWatcher.off(ThemeWatcherEvent.Change, this.onThemeChange);
+        this.themeWatcher.stop();
         if (!this.state.messaging) return;
         this.state.messaging.off("ready", this.onReady);
         this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
@@ -84,6 +87,10 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
     }
 
     private onReady = (): void => {
+        this.themeWatcher.start();
+        this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
+        // Theme may have changed while messaging was starting
+        this.onThemeChange(this.themeWatcher.getEffectiveTheme());
         this.state.messaging?.sendWidgetConfig(this.props.widgetDefinition);
     };
 
@@ -94,6 +101,10 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
         this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
     };
 
+    private onThemeChange = (theme: string): void => {
+        this.state.messaging?.updateTheme({ name: theme });
+    };
+
     private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>): void => {
         this.props.onFinished(true, ev.detail.data);
     };
@@ -127,7 +138,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
             userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
             userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
             clientId: ELEMENT_CLIENT_ID,
-            clientTheme: SettingsStore.getValue("theme"),
+            clientTheme: this.themeWatcher.getEffectiveTheme(),
             clientLanguage: getUserLanguage(),
             baseUrl: MatrixClientPeg.safeGet().baseUrl,
         });
diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx
index 89ef4ad0ea7789b0e050b85a9daf09cbe6842f7e..533b24306f7b9d029ddc899d0b7ed7bbee489602 100644
--- a/src/components/views/dialogs/ModuleUiDialog.tsx
+++ b/src/components/views/dialogs/ModuleUiDialog.tsx
@@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
-import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
+import React, { createRef, type RefObject } from "react";
+import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
 import { logger } from "matrix-js-sdk/src/logger";
-import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
-import { ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions";
+import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
+import { type ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions";
 
-import ScrollableBaseModal, { IScrollableBaseState } from "./ScrollableBaseModal";
+import ScrollableBaseModal, { type IScrollableBaseState } from "./ScrollableBaseModal";
 import { _t } from "../../../languageHandler";
 
 interface IProps<P extends DialogProps, C extends DialogContent<P>> {
-    contentFactory: (props: P, ref: React.RefObject<C>) => React.ReactNode;
+    contentFactory: (props: P, ref: React.RefObject<C | null>) => React.ReactNode;
     additionalContentProps: Omit<P, keyof DialogProps> | undefined;
     initialOptions: ModuleUiDialogOptions;
     moduleApi: ModuleApi;
@@ -27,10 +27,10 @@ interface IState extends IScrollableBaseState {
     // nothing special
 }
 
-export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> extends ScrollableBaseModal<
-    IProps<P, C>,
-    IState
-> {
+export class ModuleUiDialog<
+    P extends DialogProps = DialogProps,
+    C extends DialogContent<P> = DialogContent<P>,
+> extends ScrollableBaseModal<IProps<P, C>, IState> {
     private contentRef = createRef<C>();
 
     public constructor(props: IProps<P, C>) {
@@ -74,6 +74,11 @@ export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> e
             ...dialogProps,
         } as unknown as P;
 
-        return <div className="mx_ModuleUiDialog">{this.props.contentFactory(contentProps, this.contentRef)}</div>;
+        // XXX: we have to fudge the types here a little as the react-sdk-module-api lacks React 19 support
+        return (
+            <div className="mx_ModuleUiDialog">
+                {this.props.contentFactory(contentProps, this.contentRef as RefObject<C>)}
+            </div>
+        );
     }
 }
diff --git a/src/components/views/dialogs/PollHistoryDialog.tsx b/src/components/views/dialogs/PollHistoryDialog.tsx
index 528b44f33226a5f9a1c5868d51b17640ae98a3ab..bcc92932e0e63af142ba4cc72636bc92d02d1920 100644
--- a/src/components/views/dialogs/PollHistoryDialog.tsx
+++ b/src/components/views/dialogs/PollHistoryDialog.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { PollHistory } from "../polls/pollHistory/PollHistory";
 import BaseDialog from "./BaseDialog";
 
diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
index a7748e3d69c3704241dcaa63fef3cc7941b72193..b798035ab942b92506c355e39ec0c16b17390b64 100644
--- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
+++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
@@ -6,11 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
-import { SyntheticEvent, useRef, useState } from "react";
+import React, { type SyntheticEvent, useRef, useState } from "react";
 
 import { _t, _td } from "../../../languageHandler";
-import Field from "../elements/Field";
+import type Field from "../elements/Field";
 import BaseDialog from "./BaseDialog";
 import DialogButtons from "../elements/DialogButtons";
 import EmailField from "../auth/EmailField";
diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx
index 5c1e409ca5af2aa6e70f8bf49e7bf80287eb0a03..7c840c56ea406f08d0b9b5dc5b339297995fb551 100644
--- a/src/components/views/dialogs/ReportEventDialog.tsx
+++ b/src/components/views/dialogs/ReportEventDialog.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ChangeEvent } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t, UserFriendlyError } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/ReportRoomDialog.tsx b/src/components/views/dialogs/ReportRoomDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2204153c0e0d9432ef237fe5e2764356f0cc058d
--- /dev/null
+++ b/src/components/views/dialogs/ReportRoomDialog.tsx
@@ -0,0 +1,97 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type JSX, type ChangeEventHandler, useCallback, useState } from "react";
+import { Root, Field, Label, InlineSpinner, ErrorMessage, HelpMessage } from "@vector-im/compound-web";
+
+import { _t } from "../../../languageHandler";
+import SdkConfig from "../../../SdkConfig";
+import Markdown from "../../../Markdown";
+import BaseDialog from "./BaseDialog";
+import DialogButtons from "../elements/DialogButtons";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
+
+interface IProps {
+    roomId: string;
+    onFinished(leave: boolean): void;
+}
+
+/*
+ * A dialog for reporting a room.
+ */
+
+export const ReportRoomDialog: React.FC<IProps> = function ({ roomId, onFinished }) {
+    const [error, setErr] = useState<string>();
+    const [busy, setBusy] = useState(false);
+    const [reason, setReason] = useState("");
+    const [leaveRoom, setLeaveRoom] = useState(false);
+    const client = MatrixClientPeg.safeGet();
+
+    const onReasonChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => setReason(e.target.value), []);
+    const onCancel = useCallback(() => onFinished(false), [onFinished]);
+    const onSubmit = useCallback(async () => {
+        setBusy(true);
+        try {
+            await client.reportRoom(roomId, reason);
+            onFinished(leaveRoom);
+        } catch (ex) {
+            setBusy(false);
+            if (ex instanceof Error) {
+                setErr(ex.message);
+            } else {
+                setErr("Unknown error");
+            }
+        }
+    }, [roomId, reason, client, leaveRoom, onFinished]);
+
+    const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
+    let adminMessage: JSX.Element | undefined;
+    if (adminMessageMD) {
+        const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
+        adminMessage = <p dangerouslySetInnerHTML={{ __html: html }} />;
+    }
+
+    return (
+        <BaseDialog
+            className="mx_ReportRoomDialog"
+            onFinished={onCancel}
+            title={_t("action|report_room")}
+            contentId="mx_ReportEventDialog"
+        >
+            <Root id="mx_ReportEventDialog" onSubmit={onSubmit}>
+                <Field name="reason">
+                    <Label htmlFor="mx_ReportRoomDialog_reason">{_t("report_room|reason_label")}</Label>
+                    <textarea
+                        id="mx_ReportRoomDialog_reason"
+                        rows={5}
+                        onChange={onReasonChange}
+                        value={reason}
+                        disabled={busy}
+                    />
+                    {error ? <ErrorMessage>{error}</ErrorMessage> : null}
+                    <HelpMessage>{_t("report_room|description")}</HelpMessage>
+                </Field>
+                {adminMessage}
+                {busy ? <InlineSpinner /> : null}
+                <LabelledToggleSwitch
+                    label={_t("room_list|more_options|leave_room")}
+                    value={leaveRoom}
+                    onChange={setLeaveRoom}
+                />
+                <DialogButtons
+                    primaryButton={_t("action|send_report")}
+                    onPrimaryButtonClick={onSubmit}
+                    focus={true}
+                    onCancel={onCancel}
+                    primaryButtonClass="danger"
+                    primaryDisabled={busy || !reason}
+                />
+            </Root>
+        </BaseDialog>
+    );
+};
diff --git a/src/components/views/dialogs/ResetIdentityDialog.tsx b/src/components/views/dialogs/ResetIdentityDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b946ab1d79a75d3cf02eac1f53dfa293dd9abadb
--- /dev/null
+++ b/src/components/views/dialogs/ResetIdentityDialog.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import { ResetIdentityBody, type ResetIdentityBodyVariant } from "../settings/encryption/ResetIdentityBody";
+
+interface ResetIdentityDialogProps {
+    /**
+     * Called when the dialog is complete.
+     *
+     * `ResetIdentityDialog` expects this to be provided by `Modal.createDialog`, and that it will close the dialog.
+     */
+    onFinished: () => void;
+
+    /**
+     * Called when the identity is reset (before onFinished is called).
+     */
+    onReset: () => void;
+
+    /**
+     * Which variant of this dialog to show.
+     */
+    variant: ResetIdentityBodyVariant;
+}
+
+/**
+ * The dialog for resetting the identity of the current user.
+ */
+export function ResetIdentityDialog({ onFinished, onReset, variant }: ResetIdentityDialogProps): JSX.Element {
+    const matrixClient = MatrixClientPeg.safeGet();
+
+    const onResetWrapper: () => void = () => {
+        onReset();
+        // Close the dialog
+        onFinished();
+    };
+    return (
+        <MatrixClientContext.Provider value={matrixClient}>
+            <ResetIdentityBody onReset={onResetWrapper} onCancelClick={onFinished} variant={variant} />
+        </MatrixClientContext.Provider>
+    );
+}
diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx
index a4c8dd91b2e5cd7dac0e8d82a696be4c103004f9..8c90a356d2e35d60ce0c9b7c472c59e026f1fec4 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.tsx
+++ b/src/components/views/dialogs/RoomSettingsDialog.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { RoomEvent, Room, RoomStateEvent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { RoomEvent, type Room, RoomStateEvent, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
 
 import TabbedView, { Tab } from "../../structures/TabbedView";
 import { _t, _td } from "../../../languageHandler";
@@ -26,8 +26,8 @@ import { UIFeature } from "../../../settings/UIFeature";
 import BaseDialog from "./BaseDialog";
 import { Action } from "../../../dispatcher/actions";
 import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab";
-import { ActionPayload } from "../../../dispatcher/payloads";
-import { NonEmptyArray } from "../../../@types/common";
+import { type ActionPayload } from "../../../dispatcher/payloads";
+import { type NonEmptyArray } from "../../../@types/common";
 import { PollHistoryTab } from "../settings/tabs/room/PollHistoryTab";
 import ErrorBoundary from "../elements/ErrorBoundary";
 import { PeopleRoomSettingsTab } from "../settings/tabs/room/PeopleRoomSettingsTab";
diff --git a/src/components/views/dialogs/RoomUpgradeDialog.tsx b/src/components/views/dialogs/RoomUpgradeDialog.tsx
index fdb0a9f0a678658872a550cf25548d901e7a167d..45485043292259b61a6023a70d9694c3df0e5da1 100644
--- a/src/components/views/dialogs/RoomUpgradeDialog.tsx
+++ b/src/components/views/dialogs/RoomUpgradeDialog.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import Modal from "../../../Modal";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx
index c15db4a5df1df769ae03907f0c5a474cac8d2643..4eeb6677f14da71f69d159f3e4c4e525debdaa24 100644
--- a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx
+++ b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, SyntheticEvent } from "react";
+import React, { type JSX, type ReactNode, type SyntheticEvent } from "react";
 import { EventType, JoinRule } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/ScrollableBaseModal.tsx b/src/components/views/dialogs/ScrollableBaseModal.tsx
index 2f9c94f66fbd7ad334171099865a23987cdfa554..a379b90d5fac25eeb5a50d54f7968826cd436195 100644
--- a/src/components/views/dialogs/ScrollableBaseModal.tsx
+++ b/src/components/views/dialogs/ScrollableBaseModal.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FormEvent } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type FormEvent } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import FocusLock from "react-focus-lock";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
diff --git a/src/components/views/dialogs/ServerOfflineDialog.tsx b/src/components/views/dialogs/ServerOfflineDialog.tsx
index b4d03c4c12658cda1e5046897517a14801c80e72..45a1e80714ef690aa8ca40237c6320803fcc990b 100644
--- a/src/components/views/dialogs/ServerOfflineDialog.tsx
+++ b/src/components/views/dialogs/ServerOfflineDialog.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 import BaseDialog from "./BaseDialog";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx
index 9fd5002138c5cf83b5d8f4d5c0844626856f33c0..e62e3e90c7e4a18a6452d9c39ef4a56330df6a4d 100644
--- a/src/components/views/dialogs/ServerPickerDialog.tsx
+++ b/src/components/views/dialogs/ServerPickerDialog.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef, SyntheticEvent } from "react";
+import React, { type ChangeEvent, createRef, type SyntheticEvent } from "react";
 import { AutoDiscovery } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -18,8 +18,8 @@ import SdkConfig from "../../../SdkConfig";
 import Field from "../elements/Field";
 import StyledRadioButton from "../elements/StyledRadioButton";
 import TextWithTooltip from "../elements/TextWithTooltip";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import ExternalLink from "../elements/ExternalLink";
 
 interface IProps {
diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.tsx b/src/components/views/dialogs/SessionRestoreErrorDialog.tsx
index 26baef9f2a72d07beb25fd3dff489cebeb5b6027..edf93558ff06c443f4ebf13d7429f1f3e0d653a3 100644
--- a/src/components/views/dialogs/SessionRestoreErrorDialog.tsx
+++ b/src/components/views/dialogs/SessionRestoreErrorDialog.tsx
@@ -31,13 +31,13 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
     };
 
     private onClearStorageClick = (): void => {
-        Modal.createDialog(QuestionDialog, {
+        const { finished } = Modal.createDialog(QuestionDialog, {
             title: _t("action|sign_out"),
             description: <div>{_t("error|session_restore|clear_storage_description")}</div>,
             button: _t("action|sign_out"),
             danger: true,
-            onFinished: this.props.onFinished,
         });
+        finished.then(([ok]) => this.props.onFinished(ok));
     };
 
     private onRefreshClick = (): void => {
diff --git a/src/components/views/dialogs/SetEmailDialog.tsx b/src/components/views/dialogs/SetEmailDialog.tsx
index 57d8ed12d44c9bce01b8e29848e0f3361d6cb6cf..5628df33ab62bb4b84c2197382421ca69b28d8d6 100644
--- a/src/components/views/dialogs/SetEmailDialog.tsx
+++ b/src/components/views/dialogs/SetEmailDialog.tsx
@@ -66,12 +66,12 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
         this.addThreepid = new AddThreepid(MatrixClientPeg.safeGet());
         this.addThreepid.addEmailAddress(emailAddress).then(
             () => {
-                Modal.createDialog(QuestionDialog, {
+                const { finished } = Modal.createDialog(QuestionDialog, {
                     title: _t("auth|set_email|verification_pending_title"),
                     description: _t("auth|set_email|verification_pending_description"),
                     button: _t("action|continue"),
-                    onFinished: this.onEmailDialogFinished,
                 });
+                finished.then(([ok]) => this.onEmailDialogFinished(ok));
             },
             (err) => {
                 this.setState({ emailBusy: false });
@@ -89,7 +89,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
         this.props.onFinished(false);
     };
 
-    private onEmailDialogFinished = (ok: boolean): void => {
+    private onEmailDialogFinished = (ok?: boolean): void => {
         if (ok) {
             this.verifyEmailAddress();
         } else {
@@ -115,12 +115,12 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
                         _t("settings|general|error_email_verification") +
                         " " +
                         _t("auth|set_email|verification_pending_description");
-                    Modal.createDialog(QuestionDialog, {
+                    const { finished } = Modal.createDialog(QuestionDialog, {
                         title: _t("auth|set_email|verification_pending_title"),
                         description: message,
                         button: _t("action|continue"),
-                        onFinished: this.onEmailDialogFinished,
                     });
+                    finished.then(([ok]) => this.onEmailDialogFinished(ok));
                 } else {
                     logger.error("Unable to verify email address: " + err);
                     Modal.createDialog(ErrorDialog, {
diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx
index 3feff26f8f73dd0758b860aea27d19655dae76e7..dd909376a013fa6c59447dede30bcff4d8f859d8 100644
--- a/src/components/views/dialogs/ShareDialog.tsx
+++ b/src/components/views/dialogs/ShareDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { JSX, useMemo, useRef, useState } from "react";
+import React, { type JSX, useMemo, useRef, useState } from "react";
 import { Room, RoomMember, MatrixEvent, User } from "matrix-js-sdk/src/matrix";
 import { Checkbox, Button } from "@vector-im/compound-web";
 import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
@@ -19,7 +19,7 @@ import { RoomPermalinkCreator, makeUserPermalink } from "../../../utils/permalin
 import { copyPlaintext } from "../../../utils/strings";
 import { UIFeature } from "../../../settings/UIFeature";
 import BaseDialog from "./BaseDialog";
-import { XOR } from "../../../@types/common";
+import { type XOR } from "../../../@types/common";
 import { useSettingValue } from "../../../hooks/useSettings.ts";
 
 /* eslint-disable @typescript-eslint/no-require-imports */
@@ -103,7 +103,7 @@ export function ShareDialog({ target, customTitle, onFinished, permalinkCreator
     const showQrCode = useSettingValue(UIFeature.ShareQRCode);
     const showSocials = useSettingValue(UIFeature.ShareSocial);
 
-    const timeoutIdRef = useRef<number>();
+    const timeoutIdRef = useRef<number>(undefined);
     const [isCopied, setIsCopied] = useState(false);
 
     const [linkToSpecificEvent, setLinkToSpecificEvent] = useState(target instanceof MatrixEvent);
diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.tsx b/src/components/views/dialogs/SlashCommandHelpDialog.tsx
index 1c087567c36a784eb95c49c8280633ec8c35ebdb..819d28513efa112dae58748a2ee8af03e1c7bfe3 100644
--- a/src/components/views/dialogs/SlashCommandHelpDialog.tsx
+++ b/src/components/views/dialogs/SlashCommandHelpDialog.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import { _t } from "../../../languageHandler";
-import { Command, CommandCategories, Commands } from "../../../SlashCommands";
+import { type Command, CommandCategories, Commands } from "../../../SlashCommands";
 import InfoDialog from "./InfoDialog";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 
diff --git a/src/components/views/dialogs/SpacePreferencesDialog.tsx b/src/components/views/dialogs/SpacePreferencesDialog.tsx
index a5f30f0d641df5f4c61c33a94d7d7fb65acc5509..3bb341652432f177555b669ebc6cf753afc72fd6 100644
--- a/src/components/views/dialogs/SpacePreferencesDialog.tsx
+++ b/src/components/views/dialogs/SpacePreferencesDialog.tsx
@@ -1,13 +1,13 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t, _td } from "../../../languageHandler";
 import BaseDialog from "../dialogs/BaseDialog";
@@ -18,7 +18,7 @@ import SettingsStore from "../../../settings/SettingsStore";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import RoomName from "../elements/RoomName";
 import { SpacePreferenceTab } from "../../../dispatcher/payloads/OpenSpacePreferencesPayload";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 import SettingsTab from "../settings/tabs/SettingsTab";
 import { SettingsSection } from "../settings/shared/SettingsSection";
 import { SettingsSubsection, SettingsSubsectionText } from "../settings/shared/SettingsSubsection";
@@ -45,14 +45,13 @@ const SpacePreferencesAppearanceTab: React.FC<Pick<IProps, "space">> = ({ space
                                 !showPeople,
                             );
                         }}
+                        description={_t("space|preferences|show_people_in_space", {
+                            spaceName: space.name,
+                        })}
                     >
                         {_t("common|people")}
                     </StyledCheckbox>
-                    <SettingsSubsectionText>
-                        {_t("space|preferences|show_people_in_space", {
-                            spaceName: space.name,
-                        })}
-                    </SettingsSubsectionText>
+                    <SettingsSubsectionText />
                 </SettingsSubsection>
             </SettingsSection>
         </SettingsTab>
diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx
index 5ea4ea3418e0d509f5effe11221661209ffd12eb..797622a2714a3ce95f2cfb1baf4ff8316d5cfacc 100644
--- a/src/components/views/dialogs/SpaceSettingsDialog.tsx
+++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useMemo } from "react";
-import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t, _td } from "../../../languageHandler";
 import BaseDialog from "./BaseDialog";
@@ -21,7 +21,7 @@ import { UIFeature } from "../../../settings/UIFeature";
 import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
 import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
 import { Action } from "../../../dispatcher/actions";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 export enum SpaceSettingsTab {
     General = "SPACE_GENERAL_TAB",
diff --git a/src/components/views/dialogs/StorageEvictedDialog.tsx b/src/components/views/dialogs/StorageEvictedDialog.tsx
index bdacbcbb51407b43c0e00a022965124cac5aedc4..86bf3eda8f0c0b679e2371773c3d5c6066cd8c61 100644
--- a/src/components/views/dialogs/StorageEvictedDialog.tsx
+++ b/src/components/views/dialogs/StorageEvictedDialog.tsx
@@ -13,7 +13,7 @@ import { _t } from "../../../languageHandler";
 import BaseDialog from "./BaseDialog";
 import DialogButtons from "../elements/DialogButtons";
 import BugReportDialog from "./BugReportDialog";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IProps {
     onFinished(signOut?: boolean): void;
diff --git a/src/components/views/dialogs/TermsDialog.tsx b/src/components/views/dialogs/TermsDialog.tsx
index c71a77c85b1c49e9560fe7202a36d4ee6a1420c1..62a0bccee6980e0784b0e477272a5306f5df2820 100644
--- a/src/components/views/dialogs/TermsDialog.tsx
+++ b/src/components/views/dialogs/TermsDialog.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { SERVICE_TYPES } from "matrix-js-sdk/src/matrix";
 
-import { _t, pickBestLanguage } from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
 import DialogButtons from "../elements/DialogButtons";
 import BaseDialog from "./BaseDialog";
-import { ServicePolicyPair } from "../../../Terms";
+import { pickBestPolicyLanguage, type ServicePolicyPair } from "../../../Terms";
 import ExternalLink from "../elements/ExternalLink";
 import { parseUrl } from "../../../utils/UrlUtils";
 
@@ -126,8 +126,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
 
             const policyValues = Object.values(policiesAndService.policies);
             for (let i = 0; i < policyValues.length; ++i) {
-                const termDoc = policyValues[i];
-                const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
+                const internationalisedPolicy = pickBestPolicyLanguage(policyValues[i]);
+                if (!internationalisedPolicy) continue;
                 let serviceName: JSX.Element | undefined;
                 let summary: JSX.Element | undefined;
                 if (i === 0) {
@@ -136,19 +136,19 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
                 }
 
                 rows.push(
-                    <tr key={termDoc[termsLang].url}>
+                    <tr key={internationalisedPolicy.url}>
                         <td className="mx_TermsDialog_service">{serviceName}</td>
                         <td className="mx_TermsDialog_summary">{summary}</td>
                         <td>
-                            <ExternalLink rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
-                                {termDoc[termsLang].name}
+                            <ExternalLink rel="noreferrer noopener" target="_blank" href={internationalisedPolicy.url}>
+                                {internationalisedPolicy.name}
                             </ExternalLink>
                         </td>
                         <td>
                             <TermsCheckbox
-                                url={termDoc[termsLang].url}
+                                url={internationalisedPolicy.url}
                                 onChange={this.onTermsCheckboxChange}
-                                checked={Boolean(this.state.agreedUrls[termDoc[termsLang].url])}
+                                checked={Boolean(this.state.agreedUrls[internationalisedPolicy.url])}
                             />
                         </td>
                     </tr>,
@@ -164,7 +164,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
             for (const terms of Object.values(policiesAndService.policies)) {
                 let docAgreed = false;
                 for (const lang of Object.keys(terms)) {
-                    if (lang === "version") continue;
+                    if (lang === "version" || typeof terms[lang] === "string") continue;
                     if (this.state.agreedUrls[terms[lang].url]) {
                         docAgreed = true;
                         break;
diff --git a/src/components/views/dialogs/TextInputDialog.tsx b/src/components/views/dialogs/TextInputDialog.tsx
index 9d68668413f73ad7b865d42b6e0f2281f3ecc184..1a86a40d144a431e154b4dfcde9775cd5f706dc7 100644
--- a/src/components/views/dialogs/TextInputDialog.tsx
+++ b/src/components/views/dialogs/TextInputDialog.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef } from "react";
+import React, { type ChangeEvent, createRef } from "react";
 
 import Field from "../elements/Field";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
-import { IFieldState, IValidationResult } from "../elements/Validation";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
+import { type IFieldState, type IValidationResult } from "../elements/Validation";
 import BaseDialog from "./BaseDialog";
 import DialogButtons from "../elements/DialogButtons";
 
diff --git a/src/components/views/dialogs/UnpinAllDialog.tsx b/src/components/views/dialogs/UnpinAllDialog.tsx
index f2390a978ccd735fb3ab9de693a1252beafc0a8c..3812f0777263892b4758d9701bf4e8452153c99d 100644
--- a/src/components/views/dialogs/UnpinAllDialog.tsx
+++ b/src/components/views/dialogs/UnpinAllDialog.tsx
@@ -6,9 +6,9 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX } from "react";
+import React, { type JSX } from "react";
 import { Button, Text } from "@vector-im/compound-web";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import BaseDialog from "../dialogs/BaseDialog";
diff --git a/src/components/views/dialogs/UntrustedDeviceDialog.tsx b/src/components/views/dialogs/UntrustedDeviceDialog.tsx
index ad1b2df7dc70de6b5b36f24374fd0b85c7e02819..5a076a57b41ba15aeb7c6a8911b62aaa7563bf5f 100644
--- a/src/components/views/dialogs/UntrustedDeviceDialog.tsx
+++ b/src/components/views/dialogs/UntrustedDeviceDialog.tsx
@@ -7,14 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { User } from "matrix-js-sdk/src/matrix";
+import { type User } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import E2EIcon from "../rooms/E2EIcon";
 import AccessibleButton from "../elements/AccessibleButton";
 import BaseDialog from "./BaseDialog";
-import { IDevice } from "../right_panel/UserInfo";
+import { type IDevice } from "../right_panel/UserInfo";
 import { E2EStatus } from "../../../utils/ShieldUtils";
 
 interface IProps {
diff --git a/src/components/views/dialogs/UploadConfirmDialog.tsx b/src/components/views/dialogs/UploadConfirmDialog.tsx
index 83459c7ed73cfdda3f72e8da133b0651740441de..36a05733a377b72492682b0b3792e7ba49564a09 100644
--- a/src/components/views/dialogs/UploadConfirmDialog.tsx
+++ b/src/components/views/dialogs/UploadConfirmDialog.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { FilesIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/UploadFailureDialog.tsx b/src/components/views/dialogs/UploadFailureDialog.tsx
index 7e98fb24b32a4f235824a94435f961ba7810ee7b..75ee17fda6352eb2581b63274c996daba502a26f 100644
--- a/src/components/views/dialogs/UploadFailureDialog.tsx
+++ b/src/components/views/dialogs/UploadFailureDialog.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import { _t } from "../../../languageHandler";
-import ContentMessages from "../../../ContentMessages";
+import type ContentMessages from "../../../ContentMessages";
 import BaseDialog from "./BaseDialog";
 import DialogButtons from "../elements/DialogButtons";
 import { fileSize } from "../../../utils/FileUtils";
diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx
index 75739a7f45437c2a43518ee1ef129711ff2f908e..bb68051dfc420a678b3b455ea2795c3b5b6b87e7 100644
--- a/src/components/views/dialogs/UserSettingsDialog.tsx
+++ b/src/components/views/dialogs/UserSettingsDialog.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { Toast } from "@vector-im/compound-web";
-import React, { useState } from "react";
+import React, { type JSX, useState } from "react";
 import UserProfileIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
 import DevicesIcon from "@vector-im/compound-design-tokens/assets/web/icons/devices";
 import VisibilityOnIcon from "@vector-im/compound-design-tokens/assets/web/icons/visibility-on";
@@ -41,15 +41,20 @@ import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab
 import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
 import SessionManagerTab from "../settings/tabs/user/SessionManagerTab";
 import { UserTab } from "./UserTab";
-import { NonEmptyArray } from "../../../@types/common";
-import { SDKContext, SdkContextClass } from "../../../contexts/SDKContext";
+import { type NonEmptyArray } from "../../../@types/common";
+import { SDKContext, type SdkContextClass } from "../../../contexts/SDKContext";
 import { useSettingValue } from "../../../hooks/useSettings";
 import { ToastContext, useActiveToast } from "../../../contexts/ToastContext";
-import { EncryptionUserSettingsTab } from "../settings/tabs/user/EncryptionUserSettingsTab";
+import { EncryptionUserSettingsTab, type State } from "../settings/tabs/user/EncryptionUserSettingsTab";
 
 interface IProps {
     initialTabId?: UserTab;
     showMsc4108QrCode?: boolean;
+    /*
+     * The initial state of the Encryption tab.
+     * If undefined, the default state is used ("loading").
+     */
+    initialEncryptionState?: State;
     sdkContext: SdkContextClass;
     onFinished(): void;
 }
@@ -91,8 +96,9 @@ function titleForTabID(tabId: UserTab): React.ReactNode {
 export default function UserSettingsDialog(props: IProps): JSX.Element {
     const voipEnabled = useSettingValue(UIFeature.Voip);
     const mjolnirEnabled = useSettingValue("feature_mjolnir");
-    // store this prop in state as changing tabs back and forth should clear it
+    // store these props in state as changing tabs back and forth should clear them
     const [showMsc4108QrCode, setShowMsc4108QrCode] = useState(props.showMsc4108QrCode);
+    const [initialEncryptionState, setInitialEncryptionState] = useState(props.initialEncryptionState);
 
     const getTabs = (): NonEmptyArray<Tab<UserTab>> => {
         const tabs: Tab<UserTab>[] = [];
@@ -184,7 +190,13 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
         );
 
         tabs.push(
-            new Tab(UserTab.Encryption, _td("settings|encryption|title"), <KeyIcon />, <EncryptionUserSettingsTab />),
+            new Tab(
+                UserTab.Encryption,
+                _td("settings|encryption|title"),
+                <KeyIcon />,
+                <EncryptionUserSettingsTab initialState={initialEncryptionState} />,
+                "UserSettingsEncryption",
+            ),
         );
 
         if (showLabsFlags() || SettingsStore.getFeatureSettingNames().some((k) => SettingsStore.getBetaInfo(k))) {
@@ -219,8 +231,9 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
     const [activeTabId, _setActiveTabId] = useActiveTabWithDefault(getTabs(), UserTab.Account, props.initialTabId);
     const setActiveTabId = (tabId: UserTab): void => {
         _setActiveTabId(tabId);
-        // Clear this so switching away from the tab and back to it will not show the QR code again
+        // Clear these so switching away from the tab and back to it will not show the QR code again
         setShowMsc4108QrCode(false);
+        setInitialEncryptionState(undefined);
     };
 
     const [activeToast, toastRack] = useActiveToast();
diff --git a/src/components/views/dialogs/VerificationRequestDialog.tsx b/src/components/views/dialogs/VerificationRequestDialog.tsx
index bb49abbf15a931e2f6927e1ae9cc63c69f1de816..d8cef61036dbf477394781505d2ba7bdebc65d69 100644
--- a/src/components/views/dialogs/VerificationRequestDialog.tsx
+++ b/src/components/views/dialogs/VerificationRequestDialog.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
-import { User } from "matrix-js-sdk/src/matrix";
+import { type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
+import { type User } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
index 7e57d9570863cbb0cf39bed3939ac7fb2f3c5401..2837701673e7c7942e86c8da17ab24a2fe3b2313 100644
--- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
+++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,7 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Capability, isTimelineCapability, Widget, WidgetEventCapability, WidgetKind } from "matrix-widget-api";
+import {
+    type Capability,
+    isTimelineCapability,
+    type Widget,
+    WidgetEventCapability,
+    type WidgetKind,
+} from "matrix-widget-api";
 import { lexicographicCompare } from "matrix-js-sdk/src/utils";
 
 import BaseDialog from "./BaseDialog";
@@ -94,16 +100,12 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
         });
         const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => {
             const text = CapabilityText.for(cap, this.props.widgetKind);
-            const byline = text.byline ? (
-                <span className="mx_WidgetCapabilitiesPromptDialog_byline">{text.byline}</span>
-            ) : null;
 
             return (
                 <div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}>
-                    <StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)}>
+                    <StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)} description={text.byline}>
                         {text.primary}
                     </StyledCheckbox>
-                    {byline}
                 </div>
             );
         });
diff --git a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx
index 4c1e8a9d3260c39bebf954c93a04f9b6d9cb6e9e..f9729a695e3205b5b1d476e4d802201f513df5b7 100644
--- a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx
+++ b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Widget, WidgetKind } from "matrix-widget-api";
+import { type Widget, type WidgetKind } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/dialogs/devtools/AccountData.tsx b/src/components/views/dialogs/devtools/AccountData.tsx
index 402aa396d155799021a31e0b813a1a402ccbc11a..d4dfe039fd3efacf3faf93fb27c119e63e5fabbb 100644
--- a/src/components/views/dialogs/devtools/AccountData.tsx
+++ b/src/components/views/dialogs/devtools/AccountData.tsx
@@ -8,13 +8,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext, useMemo, useState } from "react";
-import { AccountDataEvents, IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type AccountDataEvents, type IContent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import MatrixClientContext from "../../../../contexts/MatrixClientContext";
-import { EventEditor, EventViewer, eventTypeField, IEditorProps, stringify } from "./Event";
+import { EventEditor, EventViewer, eventTypeField, type IEditorProps, stringify } from "./Event";
 import FilteredList from "./FilteredList";
-import { _td, TranslationKey } from "../../../../languageHandler";
+import { _td, type TranslationKey } from "../../../../languageHandler";
 
 export const AccountDataEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack }) => {
     const cli = useContext(MatrixClientContext);
diff --git a/src/components/views/dialogs/devtools/BaseTool.tsx b/src/components/views/dialogs/devtools/BaseTool.tsx
index adc13d2eab8b53dd5eaaa3599f0b1abe060b8868..8a923e26235b9c1e9a9e734dcf8c8bd02f7c0284 100644
--- a/src/components/views/dialogs/devtools/BaseTool.tsx
+++ b/src/components/views/dialogs/devtools/BaseTool.tsx
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createContext, ReactNode, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { createContext, type ReactNode, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
-import { _t, TranslationKey } from "../../../../languageHandler";
-import { XOR } from "../../../../@types/common";
-import { Tool } from "../DevtoolsDialog";
+import { _t, type TranslationKey } from "../../../../languageHandler";
+import { type XOR } from "../../../../@types/common";
+import { type Tool } from "../DevtoolsDialog";
 
 export interface IDevtoolsProps {
     onBack(): void;
diff --git a/src/components/views/dialogs/devtools/Crypto.tsx b/src/components/views/dialogs/devtools/Crypto.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a05c415f00e9c2c362a2a93cc4cd5a432f023840
--- /dev/null
+++ b/src/components/views/dialogs/devtools/Crypto.tsx
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import { InlineSpinner } from "@vector-im/compound-web";
+
+import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
+import BaseTool from "./BaseTool";
+import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
+import { _t } from "../../../../languageHandler";
+
+interface KeyBackupProps {
+    /**
+     * Callback to invoke when the back button is clicked.
+     */
+    onBack(): void;
+}
+
+/**
+ * A component that displays information about the key storage and cross-signing.
+ */
+export function Crypto({ onBack }: KeyBackupProps): JSX.Element {
+    const matrixClient = useMatrixClientContext();
+    return (
+        <BaseTool onBack={onBack} className="mx_Crypto">
+            {matrixClient.getCrypto() ? (
+                <>
+                    <KeyStorage />
+                    <CrossSigning />
+                </>
+            ) : (
+                <span>{_t("devtools|crypto|crypto_not_available")}</span>
+            )}
+        </BaseTool>
+    );
+}
+
+/**
+ * A component that displays information about the key storage.
+ */
+function KeyStorage(): JSX.Element {
+    const matrixClient = useMatrixClientContext();
+    const keyStorageData = useAsyncMemo(async () => {
+        const crypto = matrixClient.getCrypto()!;
+
+        // Get all the key storage data that we will display
+        const backupInfo = await crypto.getKeyBackupInfo();
+        const backupKeyStored = Boolean(await matrixClient.isKeyBackupKeyStored());
+        const backupKeyFromCache = await crypto.getSessionBackupPrivateKey();
+        const backupKeyCached = Boolean(backupKeyFromCache);
+        const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
+        const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
+        const secretStorageKeyInAccount = await matrixClient.secretStorage.hasKey();
+        const secretStorageReady = await crypto.isSecretStorageReady();
+
+        return {
+            backupInfo,
+            backupKeyStored,
+            backupKeyCached,
+            backupKeyWellFormed,
+            activeBackupVersion,
+            secretStorageKeyInAccount,
+            secretStorageReady,
+        };
+    }, [matrixClient]);
+
+    // Show a spinner while loading
+    if (keyStorageData === undefined) return <InlineSpinner aria-label={_t("common|loading")} />;
+
+    const {
+        backupInfo,
+        backupKeyStored,
+        backupKeyCached,
+        backupKeyWellFormed,
+        activeBackupVersion,
+        secretStorageKeyInAccount,
+        secretStorageReady,
+    } = keyStorageData;
+
+    return (
+        <table aria-label={_t("devtools|crypto|key_storage")}>
+            <thead>{_t("devtools|crypto|key_storage")}</thead>
+            <tbody>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|key_backup_latest_version")}</th>
+                    <td>
+                        {backupInfo
+                            ? `${backupInfo.version} (${_t("settings|security|key_backup_algorithm")} ${backupInfo.algorithm})`
+                            : _t("devtools|crypto|key_backup_inactive_warning")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|backup_key_stored_status")}</th>
+                    <td>
+                        {backupKeyStored
+                            ? _t("devtools|crypto|backup_key_stored")
+                            : _t("devtools|crypto|backup_key_not_stored")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|key_backup_active_version")}</th>
+                    <td>
+                        {activeBackupVersion === null
+                            ? _t("devtools|crypto|key_backup_active_version_none")
+                            : activeBackupVersion}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|backup_key_cached_status")}</th>
+                    <td>
+                        {`${
+                            backupKeyCached
+                                ? _t("devtools|crypto|backup_key_cached")
+                                : _t("devtools|crypto|not_found_locally")
+                        }, ${
+                            backupKeyWellFormed
+                                ? _t("devtools|crypto|backup_key_well_formed")
+                                : _t("devtools|crypto|backup_key_unexpected_type")
+                        }`}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|4s_public_key_status")}</th>
+                    <td>
+                        {secretStorageKeyInAccount
+                            ? _t("devtools|crypto|4s_public_key_in_account_data")
+                            : _t("devtools|crypto|4s_public_key_not_in_account_data")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|secret_storage_status")}</th>
+                    <td>
+                        {secretStorageReady
+                            ? _t("devtools|crypto|secret_storage_ready")
+                            : _t("devtools|crypto|secret_storage_not_ready")}
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+    );
+}
+
+/**
+ * A component that displays information about cross-signing.
+ */
+function CrossSigning(): JSX.Element {
+    const matrixClient = useMatrixClientContext();
+    const crossSigningData = useAsyncMemo(async () => {
+        const crypto = matrixClient.getCrypto()!;
+
+        // Get all the cross-signing data that we will display
+        const crossSigningStatus = await crypto.getCrossSigningStatus();
+        const crossSigningPublicKeysOnDevice = crossSigningStatus.publicKeysOnDevice;
+        const crossSigningPrivateKeysInStorage = crossSigningStatus.privateKeysInSecretStorage;
+        const masterPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.masterKey;
+        const selfSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.selfSigningKey;
+        const userSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.userSigningKey;
+        const crossSigningReady = await crypto.isCrossSigningReady();
+
+        return {
+            crossSigningPublicKeysOnDevice,
+            crossSigningPrivateKeysInStorage,
+            masterPrivateKeyCached,
+            selfSigningPrivateKeyCached,
+            userSigningPrivateKeyCached,
+            crossSigningReady,
+        };
+    }, [matrixClient]);
+
+    // Show a spinner while loading
+    if (crossSigningData === undefined) return <InlineSpinner aria-label={_t("common|loading")} />;
+
+    const {
+        crossSigningPublicKeysOnDevice,
+        crossSigningPrivateKeysInStorage,
+        masterPrivateKeyCached,
+        selfSigningPrivateKeyCached,
+        userSigningPrivateKeyCached,
+        crossSigningReady,
+    } = crossSigningData;
+
+    return (
+        <table aria-label={_t("devtools|crypto|cross_signing")}>
+            <thead>{_t("devtools|crypto|cross_signing")}</thead>
+            <tbody>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|cross_signing_status")}</th>
+                    <td>{getCrossSigningStatus(crossSigningReady, crossSigningPrivateKeysInStorage)}</td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|cross_signing_public_keys_on_device_status")}</th>
+                    <td>
+                        {crossSigningPublicKeysOnDevice
+                            ? _t("devtools|crypto|cross_signing_public_keys_on_device")
+                            : _t("devtools|crypto|not_found")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|cross_signing_private_keys_in_storage_status")}</th>
+                    <td>
+                        {crossSigningPrivateKeysInStorage
+                            ? _t("devtools|crypto|cross_signing_private_keys_in_storage")
+                            : _t("devtools|crypto|cross_signing_private_keys_not_in_storage")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|master_private_key_cached_status")}</th>
+                    <td>
+                        {masterPrivateKeyCached
+                            ? _t("devtools|crypto|cross_signing_cached")
+                            : _t("devtools|crypto|not_found_locally")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|self_signing_private_key_cached_status")}</th>
+                    <td>
+                        {selfSigningPrivateKeyCached
+                            ? _t("devtools|crypto|cross_signing_cached")
+                            : _t("devtools|crypto|not_found_locally")}
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">{_t("devtools|crypto|user_signing_private_key_cached_status")}</th>
+                    <td>
+                        {userSigningPrivateKeyCached
+                            ? _t("devtools|crypto|cross_signing_cached")
+                            : _t("devtools|crypto|not_found_locally")}
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+    );
+}
+
+/**
+ * Get the cross-signing status.
+ * @param crossSigningReady Whether cross-signing is ready.
+ * @param crossSigningPrivateKeysInStorage Whether cross-signing private keys are in secret storage.
+ */
+function getCrossSigningStatus(crossSigningReady: boolean, crossSigningPrivateKeysInStorage: boolean): string {
+    if (crossSigningReady) {
+        return crossSigningPrivateKeysInStorage
+            ? _t("devtools|crypto|cross_signing_ready")
+            : _t("devtools|crypto|cross_signing_untrusted");
+    }
+
+    if (crossSigningPrivateKeysInStorage) {
+        return _t("devtools|crypto|cross_signing_not_ready");
+    }
+
+    return _t("devtools|crypto|cross_signing_not_ready");
+}
diff --git a/src/components/views/dialogs/devtools/Event.tsx b/src/components/views/dialogs/devtools/Event.tsx
index a0f7bfece54afd3d89a4e4176a220732c8db7fa9..00669cd6143c3327b465f78b15a434457aab71c2 100644
--- a/src/components/views/dialogs/devtools/Event.tsx
+++ b/src/components/views/dialogs/devtools/Event.tsx
@@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ReactNode, useContext, useMemo, useRef, useState } from "react";
-import { IContent, MatrixEvent, TimelineEvents } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent, type ReactNode, useContext, useMemo, useRef, useState } from "react";
+import { type IContent, type MatrixEvent, type TimelineEvents } from "matrix-js-sdk/src/matrix";
 
-import { _t, _td, TranslationKey } from "../../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../../languageHandler";
 import Field from "../../elements/Field";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import MatrixClientContext from "../../../../contexts/MatrixClientContext";
 import withValidation from "../../elements/Validation";
 import SyntaxHighlight from "../../elements/SyntaxHighlight";
diff --git a/src/components/views/dialogs/devtools/FilteredList.tsx b/src/components/views/dialogs/devtools/FilteredList.tsx
index 0f0c52df94fc4eb5281ea76dd3ca9296a16dfe45..2b7ed027dbaae6bd176d05ae5b816246d49c5822 100644
--- a/src/components/views/dialogs/devtools/FilteredList.tsx
+++ b/src/components/views/dialogs/devtools/FilteredList.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, useEffect, useState } from "react";
+import React, { type JSX, type ChangeEvent, useEffect, useState } from "react";
 
 import { _t } from "../../../../languageHandler";
 import Field from "../../elements/Field";
diff --git a/src/components/views/dialogs/devtools/RoomNotifications.tsx b/src/components/views/dialogs/devtools/RoomNotifications.tsx
index a7c8bb7dde811e1fbe854411995271e6f3f383c9..64c2aef646641ccdf45f18acff98236f2f29a866 100644
--- a/src/components/views/dialogs/devtools/RoomNotifications.tsx
+++ b/src/components/views/dialogs/devtools/RoomNotifications.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { NotificationCountType, Room, Thread, ReceiptType } from "matrix-js-sdk/src/matrix";
-import React, { useContext } from "react";
-import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
+import { NotificationCountType, type Room, type Thread, ReceiptType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { type ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
 
 import MatrixClientContext from "../../../../contexts/MatrixClientContext";
 import { useNotificationState } from "../../../../hooks/useRoomNotificationState";
@@ -16,7 +16,7 @@ import { _t, _td } from "../../../../languageHandler";
 import { determineUnreadState } from "../../../../RoomNotifs";
 import { humanReadableNotificationLevel } from "../../../../stores/notifications/NotificationLevel";
 import { doesRoomOrThreadHaveUnreadMessages } from "../../../../Unread";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
 
 function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Element {
diff --git a/src/components/views/dialogs/devtools/RoomState.tsx b/src/components/views/dialogs/devtools/RoomState.tsx
index 8831ccc8056ba39da4cee136f25778db5440ca2a..0a41743372eed5b3f305eaaadbd6ee8e4504f0bb 100644
--- a/src/components/views/dialogs/devtools/RoomState.tsx
+++ b/src/components/views/dialogs/devtools/RoomState.tsx
@@ -8,13 +8,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext, useEffect, useMemo, useState } from "react";
-import { IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type IContent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
 import { _t, _td } from "../../../../languageHandler";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import MatrixClientContext from "../../../../contexts/MatrixClientContext";
-import { EventEditor, EventViewer, eventTypeField, stateKeyField, IEditorProps, stringify } from "./Event";
+import { EventEditor, EventViewer, eventTypeField, stateKeyField, type IEditorProps, stringify } from "./Event";
 import FilteredList from "./FilteredList";
 import Spinner from "../../elements/Spinner";
 import SyntaxHighlight from "../../elements/SyntaxHighlight";
diff --git a/src/components/views/dialogs/devtools/ServerInfo.tsx b/src/components/views/dialogs/devtools/ServerInfo.tsx
index 85780b8fd93aaa0836c172ccf601ded03f18a886..89bf36dba4049272908d160f4d69f43f3a4c59d8 100644
--- a/src/components/views/dialogs/devtools/ServerInfo.tsx
+++ b/src/components/views/dialogs/devtools/ServerInfo.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import BaseTool, { IDevtoolsProps } from "./BaseTool";
+import BaseTool, { type IDevtoolsProps } from "./BaseTool";
 import { _t } from "../../../../languageHandler";
 import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
 import MatrixClientContext from "../../../../contexts/MatrixClientContext";
diff --git a/src/components/views/dialogs/devtools/ServersInRoom.tsx b/src/components/views/dialogs/devtools/ServersInRoom.tsx
index e7b90c97944be3a96d4a802921cb1384f2ae3d85..ebdd8b526c693df603004362c5f51f70ccbc36ea 100644
--- a/src/components/views/dialogs/devtools/ServersInRoom.tsx
+++ b/src/components/views/dialogs/devtools/ServersInRoom.tsx
@@ -11,7 +11,7 @@ import React, { useContext, useMemo } from "react";
 import { EventType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import { _t } from "../../../../languageHandler";
 
 const ServersInRoom: React.FC<IDevtoolsProps> = ({ onBack }) => {
diff --git a/src/components/views/dialogs/devtools/SettingExplorer.tsx b/src/components/views/dialogs/devtools/SettingExplorer.tsx
index b9919973ebf7c21be7b19087300ee60169573e07..7fedcd2fa5cbbe5fc1810fd488168c4b63d26e22 100644
--- a/src/components/views/dialogs/devtools/SettingExplorer.tsx
+++ b/src/components/views/dialogs/devtools/SettingExplorer.tsx
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, useContext, useMemo, useState } from "react";
+import React, { type ChangeEvent, useContext, useMemo, useState } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t, _td } from "../../../../languageHandler";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
 import AccessibleButton from "../../elements/AccessibleButton";
 import SettingsStore, { LEVEL_ORDER } from "../../../../settings/SettingsStore";
-import { SettingLevel } from "../../../../settings/SettingLevel";
-import { SettingKey, SETTINGS, SettingValueType } from "../../../../settings/Settings";
+import { type SettingLevel } from "../../../../settings/SettingLevel";
+import { type SettingKey, SETTINGS, type SettingValueType } from "../../../../settings/Settings";
 import Field from "../../elements/Field";
 
 const SettingExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
diff --git a/src/components/views/dialogs/devtools/WidgetExplorer.tsx b/src/components/views/dialogs/devtools/WidgetExplorer.tsx
index 5bcae19141712b13244d18767d1f4a7d5b260dc3..d899c46c7c73fff5cebe57b7eb7bf98e5eab83b3 100644
--- a/src/components/views/dialogs/devtools/WidgetExplorer.tsx
+++ b/src/components/views/dialogs/devtools/WidgetExplorer.tsx
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext, useState } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
 import { _t } from "../../../../languageHandler";
-import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
-import WidgetStore, { IApp } from "../../../../stores/WidgetStore";
+import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool";
+import WidgetStore, { type IApp } from "../../../../stores/WidgetStore";
 import { UPDATE_EVENT } from "../../../../stores/AsyncStore";
 import FilteredList from "./FilteredList";
 import { StateEventEditor } from "./RoomState";
diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx
index a6d81ae2cef9e29b5ab906a99683189c25241502..5b1fb3e142de5bfec6b86151f9c6794dc23cc29a 100644
--- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx
+++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx
@@ -6,27 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
+import { Button } from "@vector-im/compound-web";
+import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
 import { debounce } from "lodash";
 import classNames from "classnames";
-import React, { ChangeEvent, FormEvent } from "react";
-import { logger } from "matrix-js-sdk/src/logger";
-import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto-api";
-import { SecretStorage } from "matrix-js-sdk/src/matrix";
+import React, { type ChangeEvent, type FormEvent } from "react";
+import { type SecretStorage } from "matrix-js-sdk/src/matrix";
 
-import { MatrixClientPeg } from "../../../../MatrixClientPeg";
 import Field from "../../elements/Field";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
 import { _t } from "../../../../languageHandler";
-import { accessSecretStorage } from "../../../../SecurityManager";
-import Modal from "../../../../Modal";
-import DialogButtons from "../../elements/DialogButtons";
-import BaseDialog from "../BaseDialog";
-import { chromeFileInputFix } from "../../../../utils/BrowserWorkarounds";
-
-// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode,
-// so this should be plenty and allow for people putting extra whitespace in the file because
-// maybe that's a thing people would do?
-const KEY_FILE_MAX_SIZE = 128;
+import { EncryptionCard } from "../../settings/encryption/EncryptionCard";
+import { EncryptionCardButtons } from "../../settings/encryption/EncryptionCardButtons";
 
 // Don't shout at the user that their key is invalid every time they type a key: wait a short time
 const VALIDATION_THROTTLE_MS = 200;
@@ -34,400 +24,196 @@ const VALIDATION_THROTTLE_MS = 200;
 export type KeyParams = { passphrase?: string; recoveryKey?: string };
 
 interface IProps {
+    /**
+     * Information about the Secret Storage key that we want to get.
+     */
     keyInfo: SecretStorage.SecretStorageKeyDescription;
+    /**
+     * Callback to check whether the given key is correct.
+     */
     checkPrivateKey: (k: KeyParams) => Promise<boolean>;
+    /**
+     * Callback for when the user is done with this dialog.  `result` will
+     * contain information about the key that was entered, or will be `false` if
+     * the user cancelled.
+     */
     onFinished(result?: false | KeyParams): void;
 }
 
 interface IState {
+    //! The recovery key/phrase that the user entered
     recoveryKey: string;
-    recoveryKeyValid: boolean | null;
+    //! Is the recovery key/phrase correct?  `null` means no key/phrase has been entered
     recoveryKeyCorrect: boolean | null;
-    recoveryKeyFileError: boolean | null;
-    forceRecoveryKey: boolean;
-    passPhrase: string;
-    keyMatches: boolean | null;
-    resetting: boolean;
 }
 
 /*
  * Access Secure Secret Storage by requesting the user's passphrase.
  */
 export default class AccessSecretStorageDialog extends React.PureComponent<IProps, IState> {
-    private fileUpload = React.createRef<HTMLInputElement>();
-    private inputRef = React.createRef<HTMLInputElement>();
+    private inputRef = React.createRef<HTMLTextAreaElement>();
 
     public constructor(props: IProps) {
         super(props);
 
         this.state = {
             recoveryKey: "",
-            recoveryKeyValid: null,
             recoveryKeyCorrect: null,
-            recoveryKeyFileError: null,
-            forceRecoveryKey: false,
-            passPhrase: "",
-            keyMatches: null,
-            resetting: false,
         };
     }
 
     private onCancel = (): void => {
-        if (this.state.resetting) {
-            this.setState({ resetting: false });
-        }
         this.props.onFinished(false);
     };
 
-    private onUseRecoveryKeyClick = (): void => {
-        this.setState({
-            forceRecoveryKey: true,
-        });
-    };
-
     private validateRecoveryKeyOnChange = debounce(async (): Promise<void> => {
-        await this.validateRecoveryKey();
+        await this.validateRecoveryKey(this.state.recoveryKey);
     }, VALIDATION_THROTTLE_MS);
 
-    private async validateRecoveryKey(): Promise<void> {
-        if (this.state.recoveryKey === "") {
+    /**
+     * Checks whether the security key/phrase is correct.
+     *
+     * Sets `state.recoveryKeyCorrect` accordingly, and if the key/phrase is
+     * correct, returns a `KeyParams` structure.
+     */
+    private async validateRecoveryKey(recoveryKey: string): Promise<KeyParams | undefined> {
+        recoveryKey = recoveryKey.trim();
+
+        if (recoveryKey === "") {
             this.setState({
-                recoveryKeyValid: null,
                 recoveryKeyCorrect: null,
             });
-            return;
         }
 
+        const hasPassphrase = this.props.keyInfo?.passphrase?.salt && this.props.keyInfo?.passphrase?.iterations;
+
+        // If the user has a passphrase, we want to try validating it both as a
+        // key and as a passphrase.  We first try to validate it as a key, since
+        // that check is faster.
+
         try {
-            const cli = MatrixClientPeg.safeGet();
-            const decodedKey = decodeRecoveryKey(this.state.recoveryKey);
-            const correct = await cli.secretStorage.checkKey(decodedKey, this.props.keyInfo);
-            this.setState({
-                recoveryKeyValid: true,
-                recoveryKeyCorrect: correct,
-            });
-        } catch {
-            this.setState({
-                recoveryKeyValid: false,
-                recoveryKeyCorrect: false,
-            });
+            const input = { recoveryKey };
+            const recoveryKeyCorrect = await this.props.checkPrivateKey(input);
+            if (recoveryKeyCorrect) {
+                this.setState({ recoveryKeyCorrect });
+                return input;
+            }
+        } catch {}
+
+        if (hasPassphrase) {
+            try {
+                const input = { passphrase: recoveryKey };
+                const recoveryKeyCorrect = await this.props.checkPrivateKey(input);
+                if (recoveryKeyCorrect) {
+                    this.setState({ recoveryKeyCorrect });
+                    return input;
+                }
+            } catch {}
         }
+
+        this.setState({
+            recoveryKeyCorrect: false,
+        });
     }
 
-    private onRecoveryKeyChange = (ev: ChangeEvent<HTMLInputElement>): void => {
+    private onRecoveryKeyChange = (ev: ChangeEvent<HTMLTextAreaElement>): void => {
         this.setState({
             recoveryKey: ev.target.value,
-            recoveryKeyFileError: null,
         });
 
-        // also clear the file upload control so that the user can upload the same file
-        // the did before (otherwise the onchange wouldn't fire)
-        if (this.fileUpload.current) this.fileUpload.current.value = "";
-
-        // We don't use Field's validation here because a) we want it in a separate place rather
-        // than in a tooltip and b) we want it to display feedback based on the uploaded file
-        // as well as the text box. Ideally we would refactor Field's validation logic so we could
+        // We don't use Field's validation here because we want it in a separate place rather
+        // than in a tooltip. Ideally we would refactor Field's validation logic so we could
         // re-use some of it.
         this.validateRecoveryKeyOnChange();
     };
 
-    private onRecoveryKeyFileChange = async (ev: ChangeEvent<HTMLInputElement>): Promise<void> => {
-        if (!ev.target.files?.length) return;
-
-        const f = ev.target.files[0];
-
-        if (f.size > KEY_FILE_MAX_SIZE) {
-            this.setState({
-                recoveryKeyFileError: true,
-                recoveryKeyCorrect: false,
-                recoveryKeyValid: false,
-            });
-        } else {
-            const contents = await f.text();
-            // test it's within the base58 alphabet. We could be more strict here, eg. require the
-            // right number of characters, but it's really just to make sure that what we're reading is
-            // text because we'll put it in the text field.
-            if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) {
-                this.setState({
-                    recoveryKeyFileError: null,
-                    recoveryKey: contents.trim(),
-                });
-                await this.validateRecoveryKey();
-            } else {
-                this.setState({
-                    recoveryKeyFileError: true,
-                    recoveryKeyCorrect: false,
-                    recoveryKeyValid: false,
-                    recoveryKey: "",
-                });
-            }
-        }
-    };
-
-    private onRecoveryKeyFileUploadClick = (): void => {
-        this.fileUpload.current?.click();
-    };
-
-    private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent): Promise<void> => {
-        ev.preventDefault();
-
-        if (this.state.passPhrase.length <= 0) {
-            this.inputRef.current?.focus();
-            return;
-        }
-
-        this.setState({ keyMatches: null });
-        const input = { passphrase: this.state.passPhrase };
-        const keyMatches = await this.props.checkPrivateKey(input);
-        if (keyMatches) {
-            this.props.onFinished(input);
-        } else {
-            this.setState({ keyMatches });
-            this.inputRef.current?.focus();
-        }
-    };
-
     private onRecoveryKeyNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent): Promise<void> => {
         ev.preventDefault();
 
-        if (!this.state.recoveryKeyValid) return;
+        const keyParams = await this.validateRecoveryKey(this.state.recoveryKey);
 
-        this.setState({ keyMatches: null });
-        const input = { recoveryKey: this.state.recoveryKey };
-        const keyMatches = await this.props.checkPrivateKey(input);
-        if (keyMatches) {
-            this.props.onFinished(input);
+        if (keyParams !== undefined) {
+            this.props.onFinished(keyParams);
         } else {
-            this.setState({ keyMatches });
+            this.inputRef.current?.focus();
         }
     };
 
-    private onPassPhraseChange = (ev: ChangeEvent<HTMLInputElement>): void => {
-        this.setState({
-            passPhrase: ev.target.value,
-            keyMatches: null,
+    private getKeyValidationClasses(): string {
+        return classNames({
+            "mx_AccessSecretStorageDialog_recoveryKeyFeedback": this.state.recoveryKeyCorrect !== null,
+            "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": this.state.recoveryKeyCorrect === false,
         });
-    };
-
-    private onResetAllClick = (ev: ButtonEvent): void => {
-        ev.preventDefault();
-        this.setState({ resetting: true });
-    };
-
-    private onConfirmResetAllClick = async (): Promise<void> => {
-        // Hide ourselves so the user can interact with the reset dialogs.
-        // We don't conclude the promise chain (onFinished) yet to avoid confusing
-        // any upstream code flows.
-        //
-        // Note: this will unmount us, so don't call `setState` or anything in the
-        // rest of this function.
-        Modal.toggleCurrentDialogVisibility();
+    }
 
-        try {
-            // Force reset secret storage (which resets the key backup)
-            await accessSecretStorage(
-                async (): Promise<void> => {
-                    // Now we can indicate that the user is done pressing buttons, finally.
-                    // Upstream flows will detect the new secret storage, key backup, etc and use it.
-                    this.props.onFinished({});
-                },
-                { forceReset: true, resetCrossSigning: true },
-            );
-        } catch (e) {
-            logger.error(e);
-            this.props.onFinished(false);
+    private getKeyValidationText(): string | null {
+        if (this.state.recoveryKeyCorrect) {
+            return null;
+        } else if (this.state.recoveryKeyCorrect === null) {
+            return _t("encryption|access_secret_storage_dialog|alternatives");
+        } else {
+            return _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key");
         }
-    };
+    }
 
-    private getKeyValidationText(): string {
-        if (this.state.recoveryKeyFileError) {
-            return _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_file_type");
-        } else if (this.state.recoveryKeyCorrect) {
-            return _t("encryption|access_secret_storage_dialog|key_validation_text|recovery_key_is_correct");
-        } else if (this.state.recoveryKeyValid) {
-            return _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key");
-        } else if (this.state.recoveryKeyValid === null) {
-            return "";
+    private getRecoveryKeyFeedback(): React.ReactNode | null {
+        const validationText = this.getKeyValidationText();
+        if (validationText === null) {
+            return null;
         } else {
-            return _t("encryption|access_secret_storage_dialog|key_validation_text|invalid_security_key");
+            return <div className={this.getKeyValidationClasses()}>{validationText}</div>;
         }
     }
 
     public render(): React.ReactNode {
-        const hasPassphrase = this.props.keyInfo?.passphrase?.salt && this.props.keyInfo?.passphrase?.iterations;
-
-        const resetLine = (
-            <strong className="mx_AccessSecretStorageDialog_reset">
-                {_t("encryption|reset_all_button", undefined, {
-                    a: (sub) => (
-                        <AccessibleButton
-                            kind="link_inline"
-                            onClick={this.onResetAllClick}
-                            className="mx_AccessSecretStorageDialog_reset_link"
-                        >
-                            {sub}
-                        </AccessibleButton>
-                    ),
-                })}
-            </strong>
-        );
-
-        let content;
-        let title;
-        let titleClass;
-        if (this.state.resetting) {
-            title = _t("encryption|access_secret_storage_dialog|reset_title");
-            titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_resetBadge"];
-            content = (
-                <div>
-                    <p>{_t("encryption|access_secret_storage_dialog|reset_warning_1")}</p>
-                    <p>{_t("encryption|access_secret_storage_dialog|reset_warning_2")}</p>
-                    <DialogButtons
-                        primaryButton={_t("action|reset")}
-                        onPrimaryButtonClick={this.onConfirmResetAllClick}
-                        hasCancel={true}
-                        onCancel={this.onCancel}
-                        focus={false}
-                        primaryButtonClass="danger"
-                    />
-                </div>
-            );
-        } else if (hasPassphrase && !this.state.forceRecoveryKey) {
-            title = _t("encryption|access_secret_storage_dialog|security_phrase_title");
-            titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle"];
-
-            let keyStatus;
-            if (this.state.keyMatches === false) {
-                keyStatus = (
-                    <div className="mx_AccessSecretStorageDialog_keyStatus">
-                        {"\uD83D\uDC4E "}
-                        {_t("encryption|access_secret_storage_dialog|security_phrase_incorrect_error")}
-                    </div>
-                );
-            } else {
-                keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus" />;
-            }
-
-            content = (
-                <div>
-                    <p>
-                        {_t(
-                            "encryption|access_secret_storage_dialog|enter_phrase_or_key_prompt",
-                            {},
-                            {
-                                button: (s) => (
-                                    <AccessibleButton kind="link_inline" onClick={this.onUseRecoveryKeyClick}>
-                                        {s}
-                                    </AccessibleButton>
-                                ),
-                            },
-                        )}
-                    </p>
-
-                    <form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this.onPassPhraseNext}>
+        const title = _t("encryption|access_secret_storage_dialog|security_key_title");
+
+        const recoveryKeyFeedback = this.getRecoveryKeyFeedback();
+        const content = (
+            <div>
+                <form
+                    className="mx_AccessSecretStorageDialog_primaryContainer"
+                    onSubmit={this.onRecoveryKeyNext}
+                    spellCheck={false}
+                    autoComplete="off"
+                >
+                    <div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
                         <Field
                             inputRef={this.inputRef}
-                            id="mx_passPhraseInput"
-                            className="mx_AccessSecretStorageDialog_passPhraseInput"
-                            type="password"
-                            label={_t("encryption|access_secret_storage_dialog|security_phrase_title")}
-                            value={this.state.passPhrase}
-                            onChange={this.onPassPhraseChange}
+                            element="textarea"
+                            rows={2}
+                            cols={45}
+                            id="mx_securityKey"
+                            label={_t("encryption|access_secret_storage_dialog|security_key_title")}
+                            value={this.state.recoveryKey}
+                            onChange={this.onRecoveryKeyChange}
                             autoFocus={true}
-                            autoComplete="new-password"
-                        />
-                        {keyStatus}
-                        <DialogButtons
-                            primaryButton={_t("action|continue")}
-                            onPrimaryButtonClick={this.onPassPhraseNext}
-                            hasCancel={true}
-                            onCancel={this.onCancel}
-                            focus={false}
-                            primaryDisabled={this.state.passPhrase.length === 0}
-                            additive={resetLine}
+                            forceValidity={this.state.recoveryKeyCorrect ?? undefined}
+                            autoComplete="off"
                         />
-                    </form>
-                </div>
-            );
-        } else {
-            title = _t("encryption|access_secret_storage_dialog|security_key_title");
-            titleClass = ["mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle"];
-
-            const feedbackClasses = classNames({
-                "mx_AccessSecretStorageDialog_recoveryKeyFeedback": true,
-                "mx_AccessSecretStorageDialog_recoveryKeyFeedback--valid": this.state.recoveryKeyCorrect === true,
-                "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": this.state.recoveryKeyCorrect === false,
-            });
-            const recoveryKeyFeedback = <div className={feedbackClasses}>{this.getKeyValidationText()}</div>;
-
-            content = (
-                <div>
-                    <p>{_t("encryption|access_secret_storage_dialog|use_security_key_prompt")}</p>
-
-                    <form
-                        className="mx_AccessSecretStorageDialog_primaryContainer"
-                        onSubmit={this.onRecoveryKeyNext}
-                        spellCheck={false}
-                        autoComplete="off"
-                    >
-                        <div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
-                            <div className="mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput">
-                                <Field
-                                    type="password"
-                                    id="mx_securityKey"
-                                    label={_t("encryption|access_secret_storage_dialog|security_key_title")}
-                                    value={this.state.recoveryKey}
-                                    onChange={this.onRecoveryKeyChange}
-                                    autoFocus={true}
-                                    forceValidity={this.state.recoveryKeyCorrect ?? undefined}
-                                    autoComplete="off"
-                                />
-                            </div>
-                            <span className="mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText">
-                                {_t("encryption|access_secret_storage_dialog|separator", {
-                                    recoveryFile: "",
-                                    securityKey: "",
-                                })}
-                            </span>
-                            <div>
-                                <input
-                                    type="file"
-                                    className="mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput"
-                                    ref={this.fileUpload}
-                                    onClick={chromeFileInputFix}
-                                    onChange={this.onRecoveryKeyFileChange}
-                                />
-                                <AccessibleButton kind="primary" onClick={this.onRecoveryKeyFileUploadClick}>
-                                    {_t("action|upload")}
-                                </AccessibleButton>
-                            </div>
-                        </div>
-                        {recoveryKeyFeedback}
-                        <DialogButtons
-                            primaryButton={_t("action|continue")}
-                            onPrimaryButtonClick={this.onRecoveryKeyNext}
-                            hasCancel={true}
-                            cancelButton={_t("action|go_back")}
-                            cancelButtonClass="warning"
-                            onCancel={this.onCancel}
-                            focus={false}
-                            primaryDisabled={!this.state.recoveryKeyValid}
-                            additive={resetLine}
-                        />
-                    </form>
-                </div>
-            );
-        }
+                    </div>
+                    {recoveryKeyFeedback}
+                    <EncryptionCardButtons>
+                        <Button disabled={!this.state.recoveryKeyCorrect} onClick={this.onRecoveryKeyNext}>
+                            {_t("action|continue")}
+                        </Button>
+                        <Button kind="tertiary" onClick={this.onCancel}>
+                            {_t("action|cancel")}
+                        </Button>
+                    </EncryptionCardButtons>
+                </form>
+            </div>
+        );
 
         return (
-            <BaseDialog
+            <EncryptionCard
+                Icon={LockSolidIcon}
                 className="mx_AccessSecretStorageDialog"
-                onFinished={this.props.onFinished}
                 title={title}
-                titleClass={titleClass}
+                description={_t("encryption|access_secret_storage_dialog|privacy_warning")}
             >
-                <div>{content}</div>
-            </BaseDialog>
+                {content}
+            </EncryptionCard>
         );
     }
 }
diff --git a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx
deleted file mode 100644
index 5e3136c5ad674db37a7961c7fe3648937f5a5df9..0000000000000000000000000000000000000000
--- a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-
-import { _t } from "../../../../languageHandler";
-import BaseDialog from "../BaseDialog";
-import DialogButtons from "../../elements/DialogButtons";
-
-interface IProps {
-    onFinished: (success?: boolean) => void;
-}
-
-export default class ConfirmDestroyCrossSigningDialog extends React.Component<IProps> {
-    private onConfirm = (): void => {
-        this.props.onFinished(true);
-    };
-
-    private onDecline = (): void => {
-        this.props.onFinished(false);
-    };
-
-    public render(): React.ReactNode {
-        return (
-            <BaseDialog
-                className="mx_ConfirmDestroyCrossSigningDialog"
-                hasCancel={true}
-                onFinished={this.props.onFinished}
-                title={_t("encryption|destroy_cross_signing_dialog|title")}
-            >
-                <div className="mx_ConfirmDestroyCrossSigningDialog_content">
-                    <p>{_t("encryption|destroy_cross_signing_dialog|warning")}</p>
-                </div>
-                <DialogButtons
-                    primaryButton={_t("encryption|destroy_cross_signing_dialog|primary_button_text")}
-                    onPrimaryButtonClick={this.onConfirm}
-                    primaryButtonClass="danger"
-                    cancelButton={_t("action|cancel")}
-                    onCancel={this.onDecline}
-                />
-            </BaseDialog>
-        );
-    }
-}
diff --git a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx
index ca0a5e877e923fbd4bd75f703838f6dfb786464a..c221c994485dfd6b54b674197cb49c323543a4a3 100644
--- a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx
+++ b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
+import React, { type ChangeEvent } from "react";
 import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
-import { decodeRecoveryKey, KeyBackupInfo, KeyBackupRestoreResult } from "matrix-js-sdk/src/crypto-api";
+import { decodeRecoveryKey, type KeyBackupInfo, type KeyBackupRestoreResult } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../../../../MatrixClientPeg";
diff --git a/src/components/views/dialogs/spotlight/Option.tsx b/src/components/views/dialogs/spotlight/Option.tsx
index ac568afe26c260015d310b060a2134d0e1a534e8..c24f65c89f3869829a104853ff9390ddf31354e5 100644
--- a/src/components/views/dialogs/spotlight/Option.tsx
+++ b/src/components/views/dialogs/spotlight/Option.tsx
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../elements/AccessibleButton";
 
 interface OptionProps {
     endAdornment?: ReactNode;
diff --git a/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx b/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx
index 3e1025a81f899288387b9c32e2c4c3a15acb0319..566b450e2784bfcaf3c8670871618204c73b74aa 100644
--- a/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx
+++ b/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
 
 import { linkifyAndSanitizeHtml } from "../../../../HtmlUtils";
 import { _t } from "../../../../languageHandler";
diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
index f7b8311f983e8dc33ac513378c5dd73e54fe5d2c..f7fd5c6cb52c40df7b3116cd8306a094e73d5e51 100644
--- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
+++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import { Room } from "matrix-js-sdk/src/matrix";
-import React, { Fragment, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, Fragment, useState } from "react";
 
 import { ContextMenuTooltipButton } from "../../../../accessibility/context_menu/ContextMenuTooltipButton";
 import { useNotificationState } from "../../../../hooks/useRoomNotificationState";
@@ -17,7 +17,7 @@ import { RoomNotifState } from "../../../../RoomNotifs";
 import { RoomGeneralContextMenu } from "../../context_menus/RoomGeneralContextMenu";
 import { RoomNotificationContextMenu } from "../../context_menus/RoomNotificationContextMenu";
 import SpaceContextMenu from "../../context_menus/SpaceContextMenu";
-import { ButtonEvent } from "../../elements/AccessibleButton";
+import { type ButtonEvent } from "../../elements/AccessibleButton";
 import { contextMenuBelow } from "../../rooms/RoomTile";
 import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../../../settings/UIFeature";
diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
index e4f9b11e676b5499e77c3882939caa51eb3a2eda..419a12df896c5716761e7f93ecbec14f2d4c84c7 100644
--- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
+++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx
@@ -6,21 +6,30 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch";
+import { type WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch";
 import classNames from "classnames";
 import { capitalize, sum } from "lodash";
 import {
-    IPublicRoomsChunkRoom,
-    MatrixClient,
+    type IPublicRoomsChunkRoom,
+    type MatrixClient,
     RoomMember,
     RoomType,
-    Room,
-    HierarchyRoom,
+    type Room,
+    type HierarchyRoom,
     JoinRule,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { normalize } from "matrix-js-sdk/src/utils";
-import React, { ChangeEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
+import React, {
+    type JSX,
+    type ChangeEvent,
+    useCallback,
+    useContext,
+    useEffect,
+    useMemo,
+    useRef,
+    useState,
+} from "react";
 import sanitizeHtml from "sanitize-html";
 
 import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
@@ -33,7 +42,7 @@ import {
 import { mediaFromMxc } from "../../../../customisations/Media";
 import { Action } from "../../../../dispatcher/actions";
 import defaultDispatcher from "../../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
 import { useDebouncedCallback } from "../../../../hooks/spotlight/useDebouncedCallback";
 import { useRecentSearches } from "../../../../hooks/spotlight/useRecentSearches";
 import { useProfileInfo } from "../../../../hooks/useProfileInfo";
@@ -49,13 +58,13 @@ import { showStartChatInviteDialog } from "../../../../RoomInvite";
 import { SettingLevel } from "../../../../settings/SettingLevel";
 import SettingsStore from "../../../../settings/SettingsStore";
 import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
-import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
+import { type RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
 import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
 import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
 import { SdkContextClass } from "../../../../contexts/SDKContext";
 import { getMetaSpaceName } from "../../../../stores/spaces";
 import SpaceStore from "../../../../stores/spaces/SpaceStore";
-import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
+import { DirectoryMember, type Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
 import DMRoomMap from "../../../../utils/DMRoomMap";
 import { makeUserPermalink } from "../../../../utils/permalinks/Permalinks";
 import { buildActivityScores, buildMemberScores, compareMembers } from "../../../../utils/SortMembers";
@@ -64,7 +73,7 @@ import BaseAvatar from "../../avatars/BaseAvatar";
 import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
 import { SearchResultAvatar } from "../../avatars/SearchResultAvatar";
 import { NetworkDropdown } from "../../directory/NetworkDropdown";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../elements/AccessibleButton";
 import Spinner from "../../elements/Spinner";
 import NotificationBadge from "../../rooms/NotificationBadge";
 import BaseDialog from "../BaseDialog";
@@ -599,6 +608,21 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
                             {filterToLabel(Filter.People)}
                         </Option>
                     )}
+                    {filter === null && (
+                        <Option
+                            id="mx_SpotlightDialog_button_searchMessages"
+                            className="mx_SpotlightDialog_searchMessages"
+                            onClick={() => {
+                                defaultDispatcher.dispatch({
+                                    action: Action.FocusMessageSearch,
+                                    initialText: trimmedQuery,
+                                });
+                                onFinished();
+                            }}
+                        >
+                            {_t("spotlight_dialog|messages_label")}
+                        </Option>
+                    )}
                 </div>
             </div>
         );
@@ -954,7 +978,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
                         className="mx_SpotlightDialog_createRoom"
                         onClick={() =>
                             defaultDispatcher.dispatch({
-                                action: "view_create_room",
+                                action: Action.CreateRoom,
                                 public: true,
                                 defaultName: capitalize(trimmedQuery),
                             })
@@ -988,28 +1012,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
             );
         }
 
-        let messageSearchSection: JSX.Element | undefined;
-        if (filter === null) {
-            messageSearchSection = (
-                <div
-                    className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"
-                    role="group"
-                    aria-labelledby="mx_SpotlightDialog_section_messageSearch"
-                >
-                    <h4 id="mx_SpotlightDialog_section_messageSearch">
-                        {_t("spotlight_dialog|message_search_section_title")}
-                    </h4>
-                    <div className="mx_SpotlightDialog_otherSearches_messageSearchText">
-                        {_t(
-                            "spotlight_dialog|search_messages_hint",
-                            {},
-                            { icon: () => <div className="mx_SpotlightDialog_otherSearches_messageSearchIcon" /> },
-                        )}
-                    </div>
-                </div>
-            );
-        }
-
         content = (
             <>
                 {peopleSection}
@@ -1022,7 +1024,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
                 {hiddenResultsSection}
                 {otherSearchesSection}
                 {groupChatSection}
-                {messageSearchSection}
             </>
         );
     } else {
diff --git a/src/components/views/dialogs/spotlight/TooltipOption.tsx b/src/components/views/dialogs/spotlight/TooltipOption.tsx
index 26d0dcada849dff0005f0a26e300d55ae96aa356..a91f4c0114319403f161ab769e8675c7a4b73c38 100644
--- a/src/components/views/dialogs/spotlight/TooltipOption.tsx
+++ b/src/components/views/dialogs/spotlight/TooltipOption.tsx
@@ -7,16 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode, type RefObject } from "react";
 
 import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
-import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
-import { Ref } from "../../../../accessibility/roving/types";
+import AccessibleButton, { type ButtonProps } from "../../elements/AccessibleButton";
 
 type TooltipOptionProps<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
     className?: string;
     endAdornment?: ReactNode;
-    inputRef?: Ref;
+    inputRef?: RefObject<HTMLElement | null>;
 };
 
 export const TooltipOption = <T extends keyof HTMLElementTagNameMap>({
diff --git a/src/components/views/directory/NetworkDropdown.tsx b/src/components/views/directory/NetworkDropdown.tsx
index c2a7a11b98bab98cb77947d80ac44e567e6e6614..e46b89578a36239e8118e810a65602a17d56b2e1 100644
--- a/src/components/views/directory/NetworkDropdown.tsx
+++ b/src/components/views/directory/NetworkDropdown.tsx
@@ -17,16 +17,16 @@ import Modal from "../../../Modal";
 import SdkConfig from "../../../SdkConfig";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import SettingsStore from "../../../settings/SettingsStore";
-import { Protocols } from "../../../utils/DirectoryUtils";
+import { type Protocols } from "../../../utils/DirectoryUtils";
 import {
-    AdditionalOptionsProps,
+    type AdditionalOptionsProps,
     GenericDropdownMenu,
-    GenericDropdownMenuItem,
+    type GenericDropdownMenuItem,
 } from "../../structures/GenericDropdownMenu";
 import TextInputDialog from "../dialogs/TextInputDialog";
 import AccessibleButton from "../elements/AccessibleButton";
 import withValidation from "../elements/Validation";
-import { SettingKey, Settings } from "../../../settings/Settings.tsx";
+import { type SettingKey, type Settings } from "../../../settings/Settings.tsx";
 
 const SETTING_NAME = "room_directory_servers";
 
@@ -222,10 +222,10 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
                         const [ok, newServer] = await finished;
                         if (!ok) return;
 
-                        if (!allServers.includes(newServer)) {
-                            setUserDefinedServers([...userDefinedServers, newServer]);
+                        if (!allServers.includes(newServer!)) {
+                            setUserDefinedServers([...userDefinedServers, newServer!]);
                             setConfig({
-                                roomServer: newServer,
+                                roomServer: newServer!,
                             });
                         }
                     }}
diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx
index f12b36359b66bf4729613d0ade17c9bcbb335683..a3e8a57c4791a4d21d7e57682bcd8874082b16b6 100644
--- a/src/components/views/elements/AccessibleButton.tsx
+++ b/src/components/views/elements/AccessibleButton.tsx
@@ -7,13 +7,12 @@ Please see LICENSE files in the repository root for full details.
  */
 
 import React, {
-    ComponentProps,
-    ComponentPropsWithoutRef,
-    forwardRef,
-    FunctionComponent,
-    ReactElement,
-    KeyboardEvent,
-    Ref,
+    type JSX,
+    type ComponentProps,
+    type ComponentPropsWithoutRef,
+    type ReactElement,
+    type KeyboardEvent,
+    type Ref,
 } from "react";
 import classnames from "classnames";
 import { Tooltip } from "@vector-im/compound-web";
@@ -80,7 +79,7 @@ type Props<T extends ElementType = "div"> = {
     /**
      * The tooltip to show on hover or focus.
      */
-    title?: TooltipProps["label"];
+    title?: string;
     /**
      * The caption is a secondary text displayed under the `title` of the tooltip.
      * Only valid when used in conjunction with `title`.
@@ -99,6 +98,8 @@ type Props<T extends ElementType = "div"> = {
      * Whether the tooltip should be disabled.
      */
     disableTooltip?: TooltipProps["disabled"];
+
+    ref?: Ref<HTMLElementTagNameMap[T]>;
 };
 
 export type ButtonProps<T extends ElementType> = Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>;
@@ -118,28 +119,33 @@ type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Ele
  * @param {Object} props  react element properties
  * @returns {Object} rendered react
  */
-const AccessibleButton = forwardRef(function <T extends ElementType = typeof defaultElement>(
-    {
-        element,
-        onClick,
-        children,
-        kind,
-        disabled,
-        className,
-        onKeyDown,
-        onKeyUp,
-        triggerOnMouseDown,
-        title,
-        caption,
-        placement = "right",
-        onTooltipOpenChange,
-        disableTooltip,
-        ...restProps
-    }: ButtonProps<T>,
-    ref: Ref<HTMLElementTagNameMap[T]>,
-): JSX.Element {
-    const newProps = restProps as RenderedElementProps<T>;
-    newProps["aria-label"] = newProps["aria-label"] ?? title;
+const AccessibleButton = function AccessibleButton<T extends ElementType = typeof defaultElement>({
+    element,
+    onClick,
+    children,
+    kind,
+    disabled,
+    className,
+    onKeyDown,
+    onKeyUp,
+    triggerOnMouseDown,
+    title,
+    caption,
+    placement = "right",
+    onTooltipOpenChange,
+    disableTooltip,
+    role = "button",
+    tabIndex = 0,
+    ref,
+    ...restProps
+}: ButtonProps<T>): JSX.Element {
+    const newProps = {
+        ...restProps,
+        tabIndex,
+        role,
+        "aria-label": restProps["aria-label"] ?? title,
+    } as RenderedElementProps<T>;
+
     if (disabled) {
         newProps["aria-disabled"] = true;
         newProps["disabled"] = true;
@@ -218,14 +224,7 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
         );
     }
     return button;
-});
-
-// Type assertion required due to forwardRef type workaround in react.d.ts
-(AccessibleButton as FunctionComponent).defaultProps = {
-    role: "button",
-    tabIndex: 0,
 };
-(AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
 
 interface RefProp<T extends ElementType> {
     ref?: Ref<HTMLElementTagNameMap[T]>;
diff --git a/src/components/views/elements/AppPermission.tsx b/src/components/views/elements/AppPermission.tsx
index c6ac9d2f50ca41b575bd3f77b63b42dccaefa434..8aafb7244b8e2ea372c5b8d631026931b8fe5b9b 100644
--- a/src/components/views/elements/AppPermission.tsx
+++ b/src/components/views/elements/AppPermission.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 import { HelpIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx
index 8af4a95f93a36e80584a3330cf399275f5724c81..cfb95abcc3223d5598e7e970b2d7005c0473ffcd 100644
--- a/src/components/views/elements/AppTile.tsx
+++ b/src/components/views/elements/AppTile.tsx
@@ -9,13 +9,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode } from "react";
+import React, {
+    type JSX,
+    type ContextType,
+    createRef,
+    type CSSProperties,
+    type RefObject,
+    type ReactNode,
+} from "react";
 import classNames from "classnames";
-import { IWidget, MatrixCapabilities } from "matrix-widget-api";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { type IWidget, MatrixCapabilities } from "matrix-widget-api";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
+import { type ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
 import {
     OverflowHorizontalIcon,
     MinusIcon,
@@ -38,14 +45,14 @@ import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWid
 import { showContextMenu, WidgetContextMenu } from "../context_menus/WidgetContextMenu";
 import WidgetAvatar from "../avatars/WidgetAvatar";
 import LegacyCallHandler from "../../../LegacyCallHandler";
-import { IApp, isAppWidget } from "../../../stores/WidgetStore";
+import { type IApp, isAppWidget } from "../../../stores/WidgetStore";
 import { Icon as PopoutIcon } from "../../../../res/img/external-link.svg";
 import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
 import { OwnProfileStore } from "../../../stores/OwnProfileStore";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 import WidgetUtils from "../../../utils/WidgetUtils";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { Action } from "../../../dispatcher/actions";
 import { ElementWidgetCapabilities } from "../../../stores/widgets/ElementWidgetCapabilities";
 import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
@@ -89,7 +96,7 @@ interface IProps {
     widgetPageTitle?: string;
     showLayoutButtons?: boolean;
     // Handle to manually notify the PersistedElement that it needs to move
-    movePersistedElement?: MutableRefObject<(() => void) | undefined>;
+    movePersistedElement?: RefObject<(() => void) | null>;
     // An element to render after the iframe as an overlay
     overlay?: ReactNode;
     // If defined this async method will be called when the widget requests to become sticky.
diff --git a/src/components/views/elements/CopyableText.tsx b/src/components/views/elements/CopyableText.tsx
index 02c1b145637a95c90d84e20792efd7f1a451a01e..9c2472a202d4821021732a96897c5bf9bb23267d 100644
--- a/src/components/views/elements/CopyableText.tsx
+++ b/src/components/views/elements/CopyableText.tsx
@@ -12,7 +12,7 @@ import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
 import { copyPlaintext } from "../../../utils/strings";
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
 
 interface IProps extends React.HTMLAttributes<HTMLDivElement> {
     children?: React.ReactNode;
diff --git a/src/components/views/elements/DesktopCapturerSourcePicker.tsx b/src/components/views/elements/DesktopCapturerSourcePicker.tsx
index 2e100e767c061799c3aa7e862cd5c2dfff20b1c2..636e37ccd0d514da326e0630e104f7e328a408d8 100644
--- a/src/components/views/elements/DesktopCapturerSourcePicker.tsx
+++ b/src/components/views/elements/DesktopCapturerSourcePicker.tsx
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import classNames from "classnames";
 
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import BaseDialog from "..//dialogs/BaseDialog";
 import DialogButtons from "./DialogButtons";
 import AccessibleButton from "./AccessibleButton";
 import TabbedView, { Tab, TabLocation } from "../../structures/TabbedView";
 import PlatformPeg from "../../../PlatformPeg";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
     const options: GetSourcesOptions = {
diff --git a/src/components/views/elements/DialPadBackspaceButton.tsx b/src/components/views/elements/DialPadBackspaceButton.tsx
index 7ebf9694b39c824fea4a9dbd2947f6ce93fca9f5..2a3b7df5e532dca101516920f108f6ebd7923145 100644
--- a/src/components/views/elements/DialPadBackspaceButton.tsx
+++ b/src/components/views/elements/DialPadBackspaceButton.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
 
 interface IProps {
     // Callback for when the button is pressed
diff --git a/src/components/views/elements/DialogButtons.tsx b/src/components/views/elements/DialogButtons.tsx
index defee130bc7acae4d060bb8f11cda6ab36e3bc60..9712639638a735d76244b887ea8dbc6e4e28f615 100644
--- a/src/components/views/elements/DialogButtons.tsx
+++ b/src/components/views/elements/DialogButtons.tsx
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 
 import { _t } from "../../../languageHandler";
 
diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx
index b4d0df69dfb0d2a6dd714b42c6e69b465f8a177e..999c07a81151be25319337eb38573d90edadd4f4 100644
--- a/src/components/views/elements/Dropdown.tsx
+++ b/src/components/views/elements/Dropdown.tsx
@@ -7,15 +7,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef, CSSProperties, ReactElement, ReactNode, Ref } from "react";
+import React, {
+    type JSX,
+    type ChangeEvent,
+    createRef,
+    type CSSProperties,
+    type ReactElement,
+    type ReactNode,
+    type Ref,
+} from "react";
 import classnames from "classnames";
 
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
 import { _t } from "../../../languageHandler";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { objectHasDiff } from "../../../utils/objects";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 interface IMenuOptionProps {
     children: ReactElement;
diff --git a/src/components/views/elements/EditableItemList.tsx b/src/components/views/elements/EditableItemList.tsx
index 134c61519475d55c6a0f20c7ec67a656cbbb4afe..c5679a543b7aa39da0929bb4a53be1f8d306030c 100644
--- a/src/components/views/elements/EditableItemList.tsx
+++ b/src/components/views/elements/EditableItemList.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
+import React, { type JSX, type ChangeEvent } from "react";
 
 import { _t } from "../../../languageHandler";
 import Field from "./Field";
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
 
 interface IItemProps {
     index: number;
@@ -101,7 +101,7 @@ interface IProps {
     onNewItemChanged?(item: string): void;
 }
 
-export default class EditableItemList<P = {}> extends React.PureComponent<IProps & P> {
+export default class EditableItemList<P extends object> extends React.PureComponent<IProps & P> {
     protected onItemAdded = (e: ButtonEvent): void => {
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx
index 746a13539022280a1889906136e09323ceaa2b49..1cbba9407f2cbb13914c3db11e82d5aec6b3e5be 100644
--- a/src/components/views/elements/EffectsOverlay.tsx
+++ b/src/components/views/elements/EffectsOverlay.tsx
@@ -6,12 +6,12 @@ Copyright 2020 Nordeck IT + Consulting GmbH.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import React, { FunctionComponent, useEffect, useRef } from "react";
+import React, { type FunctionComponent, useEffect, useRef } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../../dispatcher/dispatcher";
-import ICanvasEffect from "../../../effects/ICanvasEffect";
+import type ICanvasEffect from "../../../effects/ICanvasEffect";
 import { CHAT_EFFECTS } from "../../../effects";
 import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
 
diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx
index 6ea625cec0e8dc62427fbbb7ce78e9eb357d663c..7fd2ba97124977d1c12dd74ee1a9bcf4a2243a82 100644
--- a/src/components/views/elements/ErrorBoundary.tsx
+++ b/src/components/views/elements/ErrorBoundary.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ErrorInfo, ReactNode } from "react";
+import React, { type JSX, type ErrorInfo, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx
index 79d89aed0d03cfa1dd24a67fc11f82fee1ea5149..7e6ea442e32eb4c140b8a5460a76926467dad99c 100644
--- a/src/components/views/elements/EventListSummary.tsx
+++ b/src/components/views/elements/EventListSummary.tsx
@@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, ReactNode } from "react";
-import { EventType, MatrixEvent, MatrixEventEvent, RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentProps, type ReactNode } from "react";
+import { EventType, type MatrixEvent, MatrixEventEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { throttle } from "lodash";
 
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index dc276858bd9a78f8c37fb60892f469469a4bcb21..02f28fd9dc9e1538bd583410845ae2481a95e172 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import classnames from "classnames";
-import { MatrixEvent, RoomMember, MsgType } from "matrix-js-sdk/src/matrix";
+import { MatrixEvent, type RoomMember, MsgType } from "matrix-js-sdk/src/matrix";
 
 import * as Avatar from "../../../Avatar";
 import EventTile from "../rooms/EventTile";
diff --git a/src/components/views/elements/ExternalLink.tsx b/src/components/views/elements/ExternalLink.tsx
index 8c30f6e2bd205ed70f37cee9ea34046992b30b99..0e3856e13240d4d960aa41c3e94dc59e524e0c18 100644
--- a/src/components/views/elements/ExternalLink.tsx
+++ b/src/components/views/elements/ExternalLink.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { DetailedHTMLProps, AnchorHTMLAttributes } from "react";
+import React, { type DetailedHTMLProps, type AnchorHTMLAttributes } from "react";
 import classNames from "classnames";
 
 interface Props extends DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> {}
diff --git a/src/components/views/elements/FacePile.tsx b/src/components/views/elements/FacePile.tsx
index 49416a8d381faedae447fe103707ffd8f2ed86b4..f2c04ee116cd5810c902ad7b2714fe35b9f5ec30 100644
--- a/src/components/views/elements/FacePile.tsx
+++ b/src/components/views/elements/FacePile.tsx
@@ -6,12 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, HTMLAttributes, ReactNode } from "react";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type FC, type HTMLAttributes, type ReactNode } from "react";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import { AvatarStack, Tooltip } from "@vector-im/compound-web";
+import classNames from "classnames";
 
 import MemberAvatar from "../avatars/MemberAvatar";
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
+import { useToggled } from "../rooms/RoomHeader/toggle/useToggled";
+import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
 
 interface IProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
     members: RoomMember[];
@@ -57,8 +60,14 @@ const FacePile: FC<IProps> = ({
         </>
     );
 
+    const toggled = useToggled(RightPanelPhases.MemberList);
+    const classes = classNames({
+        mx_FacePile: true,
+        mx_FacePile_toggled: toggled,
+    });
+
     const content = (
-        <AccessibleButton {...props} className="mx_FacePile" onClick={onClick ?? null}>
+        <AccessibleButton {...props} className={classes} onClick={onClick ?? null}>
             <AvatarStack>{pileContents}</AvatarStack>
             {children}
         </AccessibleButton>
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx
index ea41582d62091b7e7b9ecf0d785f67ee14f5286a..2d57d1772b40e10f1230564774c49bebd566b569 100644
--- a/src/components/views/elements/Field.tsx
+++ b/src/components/views/elements/Field.tsx
@@ -6,21 +6,21 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, {
-    InputHTMLAttributes,
-    SelectHTMLAttributes,
-    TextareaHTMLAttributes,
-    RefObject,
+    type JSX,
+    type InputHTMLAttributes,
+    type SelectHTMLAttributes,
+    type TextareaHTMLAttributes,
+    type RefObject,
     createRef,
-    ComponentProps,
-    MutableRefObject,
-    RefCallback,
-    Ref,
+    type ComponentProps,
+    type RefCallback,
+    type Ref,
 } from "react";
 import classNames from "classnames";
 import { debounce } from "lodash";
 import { Tooltip } from "@vector-im/compound-web";
 
-import { IFieldState, IValidationResult } from "./Validation";
+import { type IFieldState, type IValidationResult } from "./Validation";
 
 // Invoke validation from user input (when typing, etc.) at most once every N ms.
 const VALIDATION_THROTTLE_MS = 200;
@@ -121,8 +121,7 @@ interface IState {
 
 export default class Field extends React.PureComponent<PropShapes, IState> {
     private readonly id: string;
-    private readonly _inputRef: MutableRefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null> =
-        createRef();
+    private readonly _inputRef = createRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>();
 
     /**
      * When props.inputRef is a callback ref, we will pass callbackRef to the DOM element.
@@ -242,13 +241,13 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
         return valid;
     }
 
-    private get inputRef(): RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> {
+    private get inputRef(): RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null> {
         const inputRef = this.props.inputRef;
         if (typeof inputRef === "function") {
             // This is a callback ref, so return _inputRef which will point to the actual DOM element.
             return this._inputRef;
         }
-        return (inputRef ?? this._inputRef) as RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
+        return inputRef ?? this._inputRef;
     }
 
     private onTooltipOpenChange = (open: boolean): void => {
diff --git a/src/components/views/elements/FilterDropdown.tsx b/src/components/views/elements/FilterDropdown.tsx
index 63e0fca24327088c0891ec638735992b3db16b99..44be6c43727ab8e7633278f225b823aa8eb0f420 100644
--- a/src/components/views/elements/FilterDropdown.tsx
+++ b/src/components/views/elements/FilterDropdown.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 import classNames from "classnames";
 import CheckmarkIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
 
-import Dropdown, { DropdownProps } from "./Dropdown";
-import { NonEmptyArray } from "../../../@types/common";
+import Dropdown, { type DropdownProps } from "./Dropdown";
+import { type NonEmptyArray } from "../../../@types/common";
 
 export type FilterDropdownOption<FilterKeysType extends string> = {
     id: FilterKeysType;
diff --git a/src/components/views/elements/FilterTabGroup.tsx b/src/components/views/elements/FilterTabGroup.tsx
index b63d96bdd33b3aab236e44ef50d7ab1f1da2e4a4..b8380be5baec82229acc229b55b3e0784b21330a 100644
--- a/src/components/views/elements/FilterTabGroup.tsx
+++ b/src/components/views/elements/FilterTabGroup.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FieldsetHTMLAttributes, ReactNode } from "react";
+import React, { type JSX, type FieldsetHTMLAttributes, type ReactNode } from "react";
 
 export type FilterTab<T> = {
     label: string | ReactNode;
diff --git a/src/components/views/elements/GenericEventListSummary.tsx b/src/components/views/elements/GenericEventListSummary.tsx
index 11da97f76bbece91105d2179050cab96e6b2cd3a..3a6e377ba1382efe599113c5c721d1a3effa6304 100644
--- a/src/components/views/elements/GenericEventListSummary.tsx
+++ b/src/components/views/elements/GenericEventListSummary.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useEffect } from "react";
+import React, { type ReactNode, useEffect } from "react";
 import { uniqBy } from "lodash";
-import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import MemberAvatar from "../avatars/MemberAvatar";
diff --git a/src/components/views/elements/IRCTimelineProfileResizer.tsx b/src/components/views/elements/IRCTimelineProfileResizer.tsx
index 625ea2cb530ae16350332c48ebd1cfcd246383ae..90a86ab2587f43b584c70ed4baedd62c00cc6a2d 100644
--- a/src/components/views/elements/IRCTimelineProfileResizer.tsx
+++ b/src/components/views/elements/IRCTimelineProfileResizer.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import SettingsStore from "../../../settings/SettingsStore";
-import Draggable, { ILocationState } from "./Draggable";
+import Draggable, { type ILocationState } from "./Draggable";
 import { SettingLevel } from "../../../settings/SettingLevel";
 
 interface IProps {
diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx
index 3a3006edc570f4a1ad916ba26379e76316598c3e..e3ee6f20d55400d49c4a1b9f83c04d32126370c1 100644
--- a/src/components/views/elements/ImageView.tsx
+++ b/src/components/views/elements/ImageView.tsx
@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, CSSProperties, useRef, useState } from "react";
+import React, { type JSX, createRef, type CSSProperties, useRef, useState, useMemo } from "react";
 import FocusLock from "react-focus-lock";
-import { MatrixEvent, parseErrorResponse } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, parseErrorResponse } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import MemberAvatar from "../avatars/MemberAvatar";
@@ -22,10 +22,10 @@ import SettingsStore from "../../../settings/SettingsStore";
 import { formatFullDate } from "../../../DateUtils";
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { normalizeWheelEvent } from "../../../utils/Mouse";
 import UIStore from "../../../stores/UIStore";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { presentableTextForFile } from "../../../utils/FileUtils";
@@ -33,6 +33,7 @@ import AccessibleButton from "./AccessibleButton";
 import Modal from "../../../Modal";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import { FileDownloader } from "../../../utils/FileDownloader";
+import { MediaEventHelper } from "../../../utils/MediaEventHelper.ts";
 
 // Max scale to keep gaps around the image
 const MAX_SCALE = 0.95;
@@ -549,7 +550,7 @@ export default class ImageView extends React.Component<IProps, IState> {
                             title={_t("lightbox|rotate_right")}
                             onClick={this.onRotateClockwiseClick}
                         />
-                        <DownloadButton url={this.props.src} fileName={this.props.name} />
+                        <DownloadButton url={this.props.src} fileName={this.props.name} mxEvent={this.props.mxEvent} />
                         {contextMenuButton}
                         <AccessibleButton
                             className="mx_ImageView_button mx_ImageView_button_close"
@@ -582,10 +583,19 @@ export default class ImageView extends React.Component<IProps, IState> {
     }
 }
 
-function DownloadButton({ url, fileName }: { url: string; fileName?: string }): JSX.Element {
+function DownloadButton({
+    url,
+    fileName,
+    mxEvent,
+}: {
+    url: string;
+    fileName?: string;
+    mxEvent?: MatrixEvent;
+}): JSX.Element {
     const downloader = useRef(new FileDownloader()).current;
     const [loading, setLoading] = useState(false);
-    const blobRef = useRef<Blob>();
+    const blobRef = useRef<Blob>(undefined);
+    const mediaEventHelper = useMemo(() => (mxEvent ? new MediaEventHelper(mxEvent) : undefined), [mxEvent]);
 
     function showError(e: unknown): void {
         Modal.createDialog(ErrorDialog, {
@@ -625,7 +635,7 @@ function DownloadButton({ url, fileName }: { url: string; fileName?: string }):
     async function downloadBlob(blob: Blob): Promise<void> {
         await downloader.download({
             blob,
-            name: fileName ?? _t("common|image"),
+            name: mediaEventHelper?.fileName ?? fileName ?? _t("common|image"),
         });
         setLoading(false);
     }
diff --git a/src/components/views/elements/InfoTooltip.tsx b/src/components/views/elements/InfoTooltip.tsx
index 7d62b81cb389daa8fc3c7402dd7496b15244f21d..5600fbbbb2ddc749678b22ed2e6441e2776f7566 100644
--- a/src/components/views/elements/InfoTooltip.tsx
+++ b/src/components/views/elements/InfoTooltip.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import classNames from "classnames";
 import { Tooltip } from "@vector-im/compound-web";
 
diff --git a/src/components/views/elements/JoinRuleDropdown.tsx b/src/components/views/elements/JoinRuleDropdown.tsx
index 7e678b02e67a0e98de74f99228628ba44b28d7fa..cded54ffd67cf4d415ec0245edbb81e60ddbfedd 100644
--- a/src/components/views/elements/JoinRuleDropdown.tsx
+++ b/src/components/views/elements/JoinRuleDropdown.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 import { JoinRule } from "matrix-js-sdk/src/matrix";
 
 import Dropdown from "./Dropdown";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 import { Icon as AskToJoinIcon } from "../../../../res/img/element-icons/ask-to-join.svg";
 
 interface IProps {
diff --git a/src/components/views/elements/LabelledCheckbox.tsx b/src/components/views/elements/LabelledCheckbox.tsx
index ea7b5d2b04d4eb340f845eb208969b0458dd0472..ba07ed92014d50cf8ac4e0feb87ef1b31b6a60c3 100644
--- a/src/components/views/elements/LabelledCheckbox.tsx
+++ b/src/components/views/elements/LabelledCheckbox.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -28,13 +28,16 @@ interface IProps {
 
 const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange, className }) => {
     return (
-        <label className={classnames("mx_LabelledCheckbox", className)}>
-            <StyledCheckbox disabled={disabled} checked={value} onChange={(e) => onChange(e.target.checked)} />
-            <div className="mx_LabelledCheckbox_labels">
+        <div className={classnames("mx_LabelledCheckbox", className)}>
+            <StyledCheckbox
+                description={byline}
+                disabled={disabled}
+                checked={value}
+                onChange={(e) => onChange(e.target.checked)}
+            >
                 <span className="mx_LabelledCheckbox_label">{label}</span>
-                {byline ? <span className="mx_LabelledCheckbox_byline">{byline}</span> : null}
-            </div>
-        </label>
+            </StyledCheckbox>
+        </div>
     );
 };
 
diff --git a/src/components/views/elements/LabelledToggleSwitch.tsx b/src/components/views/elements/LabelledToggleSwitch.tsx
index bbc1344efd4caad2ea0dde3af02f15bf9274d8a4..921241d62e5b7935935ff009df2a617b31a2f257 100644
--- a/src/components/views/elements/LabelledToggleSwitch.tsx
+++ b/src/components/views/elements/LabelledToggleSwitch.tsx
@@ -6,9 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type FC, useId } from "react";
 import classNames from "classnames";
-import { randomString } from "matrix-js-sdk/src/randomstring";
 
 import ToggleSwitch from "./ToggleSwitch";
 import { Caption } from "../typography/Caption";
@@ -35,41 +34,50 @@ interface IProps {
     "data-testid"?: string;
 }
 
-export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
-    private readonly id = `mx_LabelledToggleSwitch_${randomString(12)}`;
+const LabelledToggleSwitch: FC<IProps> = ({
+    label,
+    caption,
+    value,
+    disabled,
+    onChange,
+    tooltip,
+    toggleInFront,
+    className,
+    "data-testid": testId,
+}) => {
+    // This is a minimal version of a SettingsFlag
+    const generatedId = useId();
+    const id = `mx_LabelledToggleSwitch_${generatedId}`;
+    let firstPart = (
+        <span className="mx_SettingsFlag_label">
+            <div id={id}>{label}</div>
+            {caption && <Caption id={`${id}_caption`}>{caption}</Caption>}
+        </span>
+    );
+    let secondPart = (
+        <ToggleSwitch
+            checked={value}
+            disabled={disabled}
+            onChange={onChange}
+            tooltip={tooltip}
+            aria-labelledby={id}
+            aria-describedby={caption ? `${id}_caption` : undefined}
+        />
+    );
 
-    public render(): React.ReactNode {
-        // This is a minimal version of a SettingsFlag
-        const { label, caption } = this.props;
-        let firstPart = (
-            <span className="mx_SettingsFlag_label">
-                <div id={this.id}>{label}</div>
-                {caption && <Caption id={`${this.id}_caption`}>{caption}</Caption>}
-            </span>
-        );
-        let secondPart = (
-            <ToggleSwitch
-                checked={this.props.value}
-                disabled={this.props.disabled}
-                onChange={this.props.onChange}
-                tooltip={this.props.tooltip}
-                aria-labelledby={this.id}
-                aria-describedby={caption ? `${this.id}_caption` : undefined}
-            />
-        );
+    if (toggleInFront) {
+        [firstPart, secondPart] = [secondPart, firstPart];
+    }
 
-        if (this.props.toggleInFront) {
-            [firstPart, secondPart] = [secondPart, firstPart];
-        }
+    const classes = classNames("mx_SettingsFlag", className, {
+        mx_SettingsFlag_toggleInFront: toggleInFront,
+    });
+    return (
+        <div data-testid={testId} className={classes}>
+            {firstPart}
+            {secondPart}
+        </div>
+    );
+};
 
-        const classes = classNames("mx_SettingsFlag", this.props.className, {
-            mx_SettingsFlag_toggleInFront: this.props.toggleInFront,
-        });
-        return (
-            <div data-testid={this.props["data-testid"]} className={classes}>
-                {firstPart}
-                {secondPart}
-            </div>
-        );
-    }
-}
+export default LabelledToggleSwitch;
diff --git a/src/components/views/elements/LanguageDropdown.tsx b/src/components/views/elements/LanguageDropdown.tsx
index 743e0df1f705aea0a343e62ba4adcf26a1a4dd55..7bf50f780632608960e3e440d934b870ba9f0e32 100644
--- a/src/components/views/elements/LanguageDropdown.tsx
+++ b/src/components/views/elements/LanguageDropdown.tsx
@@ -7,15 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 import classNames from "classnames";
 
 import * as languageHandler from "../../../languageHandler";
-import SettingsStore from "../../../settings/SettingsStore";
 import { _t } from "../../../languageHandler";
 import Spinner from "./Spinner";
 import Dropdown from "./Dropdown";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 type Languages = Awaited<ReturnType<typeof languageHandler.getAllLanguagesWithLabels>>;
 
@@ -29,7 +28,7 @@ function languageMatchesSearchQuery(query: string, language: Languages[0]): bool
 interface IProps {
     className?: string;
     onOptionChange: (language: string) => void;
-    value?: string;
+    value: string;
     disabled?: boolean;
 }
 
@@ -103,17 +102,6 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {
             return <div key={language.value}>{language.labelInTargetLanguage}</div>;
         }) as NonEmptyArray<ReactElement & { key: string }>;
 
-        // default value here too, otherwise we need to handle null / undefined
-        // values between mounting and the initial value propagating
-        let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
-        let value: string | undefined;
-        if (language) {
-            value = this.props.value || language;
-        } else {
-            language = navigator.language || navigator.userLanguage;
-            value = this.props.value || language;
-        }
-
         return (
             <Dropdown
                 id="mx_LanguageDropdown"
@@ -121,7 +109,7 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {
                 onOptionChange={this.props.onOptionChange}
                 onSearchChange={this.onSearchChange}
                 searchEnabled={true}
-                value={value}
+                value={this.props.value}
                 label={_t("language_dropdown_label")}
                 disabled={this.props.disabled}
             >
diff --git a/src/components/views/elements/LazyRenderList.tsx b/src/components/views/elements/LazyRenderList.tsx
index c4a928ef66641d8cd15686597c54a0274cc11ede..4c02566c223ddd02abad6e173def0ff23ed0ed24 100644
--- a/src/components/views/elements/LazyRenderList.tsx
+++ b/src/components/views/elements/LazyRenderList.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 class ItemRange {
     public constructor(
diff --git a/src/components/views/elements/LearnMore.tsx b/src/components/views/elements/LearnMore.tsx
index ac7ec0995421bd1203f033e2f16634a6f45e7159..7e61bcc789a6093a31af8928d90692b81d081ce8 100644
--- a/src/components/views/elements/LearnMore.tsx
+++ b/src/components/views/elements/LearnMore.tsx
@@ -11,7 +11,7 @@ import React from "react";
 import { _t } from "../../../languageHandler";
 import Modal from "../../../Modal";
 import InfoDialog from "../dialogs/InfoDialog";
-import AccessibleButton, { ButtonProps } from "./AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "./AccessibleButton";
 
 type Props = Omit<ButtonProps<"div">, "element" | "kind" | "onClick" | "className"> & {
     title: string;
diff --git a/src/components/views/elements/Measured.tsx b/src/components/views/elements/Measured.tsx
index 5f0d3acca83daed29e7dbbf9c493dbe27d645e75..b0f084c0edfae280aef92036d89a86f87c2638a0 100644
--- a/src/components/views/elements/Measured.tsx
+++ b/src/components/views/elements/Measured.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { RefObject } from "react";
+import React, { type RefObject } from "react";
 
 import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
 
 interface IProps {
-    sensor: RefObject<Element>;
+    sensor: RefObject<Element | null>;
     breakpoint: number;
     onMeasurement(narrow: boolean): void;
 }
@@ -42,7 +42,7 @@ export default class Measured extends React.PureComponent<IProps> {
             UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`);
         }
         if (current) {
-            UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, this.props.sensor.current);
+            UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, current);
         }
     }
 
diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx
index f5e22ecfab0903719b2be42100fb29e5a6a365d7..49a908d854a64fbf90dd443e86dcacdde4855e0e 100644
--- a/src/components/views/elements/MiniAvatarUploader.tsx
+++ b/src/components/views/elements/MiniAvatarUploader.tsx
@@ -8,11 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import classNames from "classnames";
 import { EventType } from "matrix-js-sdk/src/matrix";
-import React, { useContext, useRef, useState, MouseEvent, ReactNode } from "react";
-import { Tooltip } from "@vector-im/compound-web";
+import React, { useContext, useRef, useState, type MouseEvent, type ReactNode } from "react";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { useTimeout } from "../../../hooks/useTimeout";
 import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
 import AccessibleButton from "./AccessibleButton";
 import Spinner from "./Spinner";
@@ -42,15 +40,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
 }) => {
     const cli = useContext(MatrixClientContext);
     const [busy, setBusy] = useState(false);
-    const [hover, setHover] = useState(false);
-    const [show, setShow] = useState(false);
-
-    useTimeout(() => {
-        setShow(true);
-    }, 3000); // show after 3 seconds
-    useTimeout(() => {
-        setShow(false);
-    }, 13000); // hide after being shown for 10 seconds
 
     const uploadRef = useRef<HTMLInputElement>(null);
 
@@ -61,7 +50,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
         isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
     if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;
 
-    const visible = !!label && (hover || show);
     return (
         <React.Fragment>
             <input
@@ -84,24 +72,23 @@ const MiniAvatarUploader: React.FC<IProps> = ({
                 accept="image/*"
             />
 
-            <Tooltip label={label!} open={visible} onOpenChange={setHover}>
-                <AccessibleButton
-                    className={classNames("mx_MiniAvatarUploader", {
-                        mx_MiniAvatarUploader_busy: busy,
-                        mx_MiniAvatarUploader_hasAvatar: hasAvatar,
-                    })}
-                    disabled={busy}
-                    onClick={() => {
-                        uploadRef.current?.click();
-                    }}
-                >
-                    {children}
+            <AccessibleButton
+                className={classNames("mx_MiniAvatarUploader", {
+                    mx_MiniAvatarUploader_busy: busy,
+                    mx_MiniAvatarUploader_hasAvatar: hasAvatar,
+                })}
+                disabled={busy}
+                onClick={() => {
+                    uploadRef.current?.click();
+                }}
+                aria-label={label}
+            >
+                {children}
 
-                    <div className="mx_MiniAvatarUploader_indicator">
-                        {busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
-                    </div>
-                </AccessibleButton>
-            </Tooltip>
+                <div className="mx_MiniAvatarUploader_indicator">
+                    {busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
+                </div>
+            </AccessibleButton>
         </React.Fragment>
     );
 };
diff --git a/src/components/views/elements/PersistedElement.tsx b/src/components/views/elements/PersistedElement.tsx
index 410b96d037d6b0822275c13ed16d7dc82a357070..bd496ef9b8411b401cb4a9beb4174a028612579b 100644
--- a/src/components/views/elements/PersistedElement.tsx
+++ b/src/components/views/elements/PersistedElement.tsx
@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { MutableRefObject, ReactNode, StrictMode } from "react";
-import { createRoot, Root } from "react-dom/client";
+import React, { type RefObject, type ReactNode, StrictMode } from "react";
+import { createRoot, type Root } from "react-dom/client";
 import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
 import { TooltipProvider } from "@vector-im/compound-web";
 
 import dis from "../../../dispatcher/dispatcher";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 
 export const getPersistKey = (appId: string): string => "widget_" + appId;
 
@@ -54,7 +54,7 @@ interface IProps {
     style?: React.StyleHTMLAttributes<HTMLDivElement>;
 
     // Handle to manually notify this PersistedElement that it needs to move
-    moveRef?: MutableRefObject<(() => void) | undefined>;
+    moveRef?: RefObject<(() => void) | null>;
     children: ReactNode;
 }
 
diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx
index bd49b32dff09e35d7c59071b071c0a1a1b082671..048143dc63944158c88558228ac2f499517541b7 100644
--- a/src/components/views/elements/PersistentApp.tsx
+++ b/src/components/views/elements/PersistentApp.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ContextType, CSSProperties, MutableRefObject, ReactNode } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ContextType, type CSSProperties, type RefObject, type ReactNode } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import WidgetUtils from "../../../utils/WidgetUtils";
 import AppTile from "./AppTile";
@@ -19,7 +19,7 @@ interface IProps {
     persistentWidgetId: string;
     persistentRoomId: string;
     pointerEvents?: CSSProperties["pointerEvents"];
-    movePersistedElement: MutableRefObject<(() => void) | undefined>;
+    movePersistedElement: RefObject<(() => void) | null>;
     children?: ReactNode;
 }
 
diff --git a/src/components/views/elements/Pill.tsx b/src/components/views/elements/Pill.tsx
index 266312124a1f64d91768651d162317e5c907ba31..32e78fc32eb6f92ec2c686b34c6417f544e830ec 100644
--- a/src/components/views/elements/Pill.tsx
+++ b/src/components/views/elements/Pill.tsx
@@ -6,13 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement, useContext } from "react";
 import classNames from "classnames";
-import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 import { LinkIcon, UserSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { usePermalink } from "../../../hooks/usePermalink";
 import RoomAvatar from "../avatars/RoomAvatar";
@@ -25,16 +24,9 @@ export enum PillType {
     AtRoomMention = "TYPE_AT_ROOM_MENTION", // '@room' mention
     EventInSameRoom = "TYPE_EVENT_IN_SAME_ROOM",
     EventInOtherRoom = "TYPE_EVENT_IN_OTHER_ROOM",
+    Keyword = "TYPE_KEYWORD", // Used to highlight keywords that triggered a notification rule
 }
 
-export const pillRoomNotifPos = (text: string | null): number => {
-    return text?.indexOf("@room") ?? -1;
-};
-
-export const pillRoomNotifLen = (): number => {
-    return "@room".length;
-};
-
 const linkIcon = <LinkIcon className="mx_Pill_LinkIcon mx_BaseAvatar" />;
 
 const PillRoomAvatar: React.FC<{
@@ -76,14 +68,33 @@ export interface PillProps {
     room?: Room;
     // Whether to include an avatar in the pill
     shouldShowPillAvatar?: boolean;
+    // Explicitly-provided text to display in the pill
+    text?: string;
 }
 
-export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room, shouldShowPillAvatar = true }) => {
-    const { event, member, onClick, resourceId, targetRoom, text, type } = usePermalink({
+export const Pill: React.FC<PillProps> = ({
+    type: propType,
+    url,
+    inMessage,
+    room,
+    shouldShowPillAvatar = true,
+    text: customPillText,
+}) => {
+    const cli = useContext(MatrixClientContext);
+    const {
+        event,
+        member,
+        onClick,
+        resourceId,
+        targetRoom,
+        text: linkText,
+        type,
+    } = usePermalink({
         room,
         type: propType,
         url,
     });
+    const text = customPillText ?? linkText;
 
     if (!type || !text) {
         return null;
@@ -94,8 +105,9 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
         mx_RoomPill: type === PillType.RoomMention,
         mx_SpacePill: type === "space" || targetRoom?.isSpaceRoom(),
         mx_UserPill: type === PillType.UserMention,
-        mx_UserPill_me: resourceId === MatrixClientPeg.safeGet().getUserId(),
+        mx_UserPill_me: resourceId === cli.getUserId(),
         mx_EventPill: type === PillType.EventInOtherRoom || type === PillType.EventInSameRoom,
+        mx_KeywordPill: type === PillType.Keyword,
     });
 
     let avatar: ReactElement | null = null;
@@ -131,6 +143,8 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
         case PillType.UserMention:
             avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
             break;
+        case PillType.Keyword:
+            break;
         default:
             return null;
     }
@@ -138,26 +152,24 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
     const isAnchor = !!inMessage && !!url;
     return (
         <bdi>
-            <MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
-                <Tooltip
-                    description={resourceId ?? ""}
-                    open={resourceId ? undefined : false}
-                    placement="right"
-                    isTriggerInteractive={isAnchor}
-                >
-                    {isAnchor ? (
-                        <a className={classes} href={url} onClick={onClick}>
-                            {avatar}
-                            <span className="mx_Pill_text">{pillText}</span>
-                        </a>
-                    ) : (
-                        <span className={classes}>
-                            {avatar}
-                            <span className="mx_Pill_text">{pillText}</span>
-                        </span>
-                    )}
-                </Tooltip>
-            </MatrixClientContext.Provider>
+            <Tooltip
+                description={resourceId ?? ""}
+                open={resourceId ? undefined : false}
+                placement="right"
+                isTriggerInteractive={isAnchor}
+            >
+                {isAnchor ? (
+                    <a className={classes} href={url} onClick={onClick}>
+                        {avatar}
+                        <span className="mx_Pill_text">{pillText}</span>
+                    </a>
+                ) : (
+                    <span className={classes}>
+                        {avatar}
+                        <span className="mx_Pill_text">{pillText}</span>
+                    </span>
+                )}
+            </Tooltip>
         </bdi>
     );
 };
diff --git a/src/components/views/elements/PollCreateDialog.tsx b/src/components/views/elements/PollCreateDialog.tsx
index 37139ec0d0cae79988c69bb1690f349bc9355586..41a3bc9dfbfc9cee236b9fd83f831ba5c1d5af54 100644
--- a/src/components/views/elements/PollCreateDialog.tsx
+++ b/src/components/views/elements/PollCreateDialog.tsx
@@ -6,20 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef } from "react";
+import React, { type ChangeEvent, createRef } from "react";
 import {
-    Room,
-    MatrixEvent,
-    KnownPollKind,
+    type Room,
+    type MatrixEvent,
+    type KnownPollKind,
     M_POLL_KIND_DISCLOSED,
     M_POLL_KIND_UNDISCLOSED,
     M_POLL_START,
-    IPartialEvent,
-    TimelineEvents,
+    type IPartialEvent,
+    type TimelineEvents,
 } from "matrix-js-sdk/src/matrix";
 import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 
-import ScrollableBaseModal, { IScrollableBaseState } from "../dialogs/ScrollableBaseModal";
+import ScrollableBaseModal, { type IScrollableBaseState } from "../dialogs/ScrollableBaseModal";
 import QuestionDialog from "../dialogs/QuestionDialog";
 import Modal from "../../../Modal";
 import { _t } from "../../../languageHandler";
@@ -167,18 +167,18 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
             .then(() => this.props.onFinished(true))
             .catch((e) => {
                 console.error("Failed to post poll:", e);
-                Modal.createDialog(QuestionDialog, {
+                const { finished } = Modal.createDialog(QuestionDialog, {
                     title: _t("poll|failed_send_poll_title"),
                     description: _t("poll|failed_send_poll_description"),
                     button: _t("action|try_again"),
                     cancelButton: _t("action|cancel"),
-                    onFinished: (tryAgain: boolean) => {
-                        if (!tryAgain) {
-                            this.cancel();
-                        } else {
-                            this.setState({ busy: false, canSubmit: true });
-                        }
-                    },
+                });
+                finished.then(([tryAgain]) => {
+                    if (!tryAgain) {
+                        this.cancel();
+                    } else {
+                        this.setState({ busy: false, canSubmit: true });
+                    }
                 });
             });
     }
diff --git a/src/components/views/elements/QRCode.tsx b/src/components/views/elements/QRCode.tsx
index a60d5e662592d466af044eb25089bc1e6fbf97f9..18458126f3d40fb5c6ad360d7bb8c687628f5782 100644
--- a/src/components/views/elements/QRCode.tsx
+++ b/src/components/views/elements/QRCode.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
-import { toDataURL, QRCodeSegment, QRCodeToDataURLOptions, QRCodeRenderersOptions } from "qrcode";
+import React from "react";
+import { toDataURL, type QRCodeSegment, type QRCodeToDataURLOptions, type QRCodeRenderersOptions } from "qrcode";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx
index d2b9cf079f57b9a4e0954df9316cebdd307161f2..9ff1c1e017714d5436974402f04acf67abc8fe1b 100644
--- a/src/components/views/elements/ReplyChain.tsx
+++ b/src/components/views/elements/ReplyChain.tsx
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
-import { MatrixEvent, Room, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
-import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { makeUserPermalink, type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import SettingsStore from "../../../settings/SettingsStore";
-import { Layout } from "../../../settings/enums/Layout";
+import { type Layout } from "../../../settings/enums/Layout";
 import { getUserNameColorClass } from "../../../utils/FormattingUtils";
 import { Action } from "../../../dispatcher/actions";
 import Spinner from "./Spinner";
@@ -25,7 +25,7 @@ import AccessibleButton from "./AccessibleButton";
 import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply";
 import RoomContext from "../../../contexts/RoomContext";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 
 /**
  * This number is based on the previous behavior - if we have message of height
@@ -36,8 +36,6 @@ const SHOW_EXPAND_QUOTE_PIXELS = 60;
 interface IProps {
     // the latest event in this chain of replies
     parentEv: MatrixEvent;
-    // called when the ReplyChain contents has changed, including EventTiles thereof
-    onHeightChanged?: () => void;
     permalinkCreator?: RoomPermalinkCreator;
     // Specifies which layout to use.
     layout?: Layout;
@@ -71,8 +69,8 @@ export default class ReplyChain extends React.Component<IProps, IState> {
     private room: Room;
     private blockquoteRef = React.createRef<HTMLQuoteElement>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             events: [],
@@ -95,7 +93,6 @@ export default class ReplyChain extends React.Component<IProps, IState> {
     }
 
     public componentDidUpdate(): void {
-        this.props.onHeightChanged?.();
         this.trySetExpandableQuotes();
     }
 
@@ -266,7 +263,6 @@ export default class ReplyChain extends React.Component<IProps, IState> {
                 <blockquote ref={this.blockquoteRef} className={classname} key={ev.getId()}>
                     <ReplyTile
                         mxEvent={ev}
-                        onHeightChanged={this.props.onHeightChanged}
                         permalinkCreator={this.props.permalinkCreator}
                         toggleExpandedQuote={() => this.props.setQuoteExpanded(!this.props.isQuoteExpanded)}
                         getRelationsForEvent={this.props.getRelationsForEvent}
diff --git a/src/components/views/elements/ResizeHandle.tsx b/src/components/views/elements/ResizeHandle.tsx
index a04f223e036b0671b866f2b4bd665ac2b9d257ef..5e2ad0e5cf766a02c3fa41a42eeda90ef16d5970 100644
--- a/src/components/views/elements/ResizeHandle.tsx
+++ b/src/components/views/elements/ResizeHandle.tsx
@@ -13,7 +13,7 @@ interface IResizeHandleProps {
     vertical?: boolean;
     reverse?: boolean;
     id?: string;
-    passRef?: React.RefObject<HTMLDivElement>;
+    passRef?: React.RefObject<HTMLDivElement | null>;
 }
 
 const ResizeHandle: React.FC<IResizeHandleProps> = ({ vertical, reverse, id, passRef }) => {
diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx
index e87d26a340be93509408f562520550afdc0b1992..e86a5768f2a909e0ed7f211c03ab8f0a6f414696 100644
--- a/src/components/views/elements/RoomAliasField.tsx
+++ b/src/components/views/elements/RoomAliasField.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, KeyboardEventHandler } from "react";
+import React, { type JSX, createRef, type KeyboardEventHandler } from "react";
 import { MatrixError } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
-import withValidation, { IFieldState, IValidationResult } from "./Validation";
-import Field, { IValidateOpts } from "./Field";
+import withValidation, { type IFieldState, type IValidationResult } from "./Validation";
+import Field, { type IValidateOpts } from "./Field";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 
 interface IProps {
@@ -37,8 +37,8 @@ export default class RoomAliasField extends React.PureComponent<IProps, IState>
 
     private fieldRef = createRef<Field>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             isValid: true,
diff --git a/src/components/views/elements/RoomFacePile.tsx b/src/components/views/elements/RoomFacePile.tsx
index 9b65fb98ae88a82009ddc6c6acc0090ec29ffdf4..75b1c665d82b01773995cceebd34e57d072d8790 100644
--- a/src/components/views/elements/RoomFacePile.tsx
+++ b/src/components/views/elements/RoomFacePile.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, HTMLAttributes, useContext } from "react";
-import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type FC, type HTMLAttributes, useContext } from "react";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { sortBy } from "lodash";
 
@@ -16,7 +16,7 @@ import DMRoomMap from "../../../utils/DMRoomMap";
 import FacePile from "./FacePile";
 import { useRoomMembers } from "../../../hooks/useRoomMembers";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { ButtonEvent } from "./AccessibleButton";
+import { type ButtonEvent } from "./AccessibleButton";
 
 const DEFAULT_NUM_FACES = 5;
 
diff --git a/src/components/views/elements/RoomName.tsx b/src/components/views/elements/RoomName.tsx
index 9228a71cf060022b449b75005f0bf95b35e9dbd3..1ff0fc628e94c2be4c4a03af82782c5539b2f2d4 100644
--- a/src/components/views/elements/RoomName.tsx
+++ b/src/components/views/elements/RoomName.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useEffect, useState } from "react";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useEffect, useState } from "react";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 
 import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
 
diff --git a/src/components/views/elements/RoomTopic.tsx b/src/components/views/elements/RoomTopic.tsx
index 4e71a65c4d69361623c859464eb63ae29eef4da6..34bf1c7cd35ab20ab054d877c0921abf1c1685be 100644
--- a/src/components/views/elements/RoomTopic.tsx
+++ b/src/components/views/elements/RoomTopic.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback, useContext, useState } from "react";
-import { Room, EventType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useCallback, useContext, useState } from "react";
+import { type Room, EventType } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 import { Tooltip } from "@vector-im/compound-web";
 
diff --git a/src/components/views/elements/SSOButtons.tsx b/src/components/views/elements/SSOButtons.tsx
index 39355b18894f25b660ab20ec73a02322a6968f78..3945a6651047a6e10c443c478e68c5322567048c 100644
--- a/src/components/views/elements/SSOButtons.tsx
+++ b/src/components/views/elements/SSOButtons.tsx
@@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { chunk } from "lodash";
 import classNames from "classnames";
 import {
-    MatrixClient,
+    type MatrixClient,
     IdentityProviderBrand,
-    SSOFlow,
-    SSOAction,
-    IIdentityProvider,
+    type SSOFlow,
+    type SSOAction,
+    type IIdentityProvider,
     DELEGATED_OIDC_COMPATIBILITY,
 } from "matrix-js-sdk/src/matrix";
-import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
+import { type Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
 
 import PlatformPeg from "../../../PlatformPeg";
 import AccessibleButton from "./AccessibleButton";
diff --git a/src/components/views/elements/SearchWarning.tsx b/src/components/views/elements/SearchWarning.tsx
index 6c6c4d82b1fdc4880a4c18ec485584bb61002362..c8c0ec0bffe9919310946663cb1eadf78ce9df7a 100644
--- a/src/components/views/elements/SearchWarning.tsx
+++ b/src/components/views/elements/SearchWarning.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import EventIndexPeg from "../../../indexing/EventIndexPeg";
@@ -15,7 +15,7 @@ import SdkConfig from "../../../SdkConfig";
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { UserTab } from "../dialogs/UserTab";
-import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "./AccessibleButton";
 
 export enum WarningKind {
     Files,
diff --git a/src/components/views/elements/ServerPicker.tsx b/src/components/views/elements/ServerPicker.tsx
index b433aa5150b5d8bc1b2f235ba6c41b47fcb451aa..430f2053777e874c7d2c9137d589fe60af08c543 100644
--- a/src/components/views/elements/ServerPicker.tsx
+++ b/src/components/views/elements/ServerPicker.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import AccessibleButton from "./AccessibleButton";
-import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
 import { _t } from "../../../languageHandler";
 import TextWithTooltip from "./TextWithTooltip";
 import SdkConfig from "../../../SdkConfig";
@@ -28,9 +28,10 @@ interface IProps {
 const showPickerDialog = (
     title: string | undefined,
     serverConfig: ValidatedServerConfig,
-    onFinished: (config: ValidatedServerConfig) => void,
+    onFinished: (config?: ValidatedServerConfig) => void,
 ): void => {
-    Modal.createDialog(ServerPickerDialog, { title, serverConfig, onFinished });
+    const { finished } = Modal.createDialog(ServerPickerDialog, { title, serverConfig });
+    finished.then(([config]) => onFinished(config));
 };
 
 const onHelpClick = (): void => {
diff --git a/src/components/views/elements/SettingsField.tsx b/src/components/views/elements/SettingsField.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d46b5b0f32caa19346c29cb2a6d0b36f7a67cb37
--- /dev/null
+++ b/src/components/views/elements/SettingsField.tsx
@@ -0,0 +1,59 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type ChangeEvent, type JSX, useCallback, useState } from "react";
+import { EditInPlace } from "@vector-im/compound-web";
+
+import SettingsStore from "../../../settings/SettingsStore";
+import { _t } from "../../../languageHandler";
+import { type SettingLevel } from "../../../settings/SettingLevel";
+import { type StringSettingKey } from "../../../settings/Settings";
+
+interface Props {
+    settingKey: StringSettingKey;
+    level: SettingLevel;
+    roomId?: string; // for per-room settings
+    label?: string;
+    isExplicit?: boolean;
+    onChange?(value: string): void;
+}
+
+const SettingsField = ({ settingKey, level, roomId, isExplicit, label, onChange: _onSave }: Props): JSX.Element => {
+    const settingsValue = SettingsStore.getValueAt(level, settingKey, roomId, isExplicit);
+    const [value, setValue] = useState(settingsValue);
+    const [busy, setBusy] = useState(false);
+
+    const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
+        setValue(e.target.value);
+    }, []);
+    const onCancel = useCallback(() => {
+        setValue(settingsValue);
+    }, [settingsValue]);
+    const onSave = useCallback(async () => {
+        setBusy(true);
+        await SettingsStore.setValue(settingKey, roomId ?? null, level, value);
+        setBusy(false);
+        _onSave?.(value);
+    }, [level, roomId, settingKey, value, _onSave]);
+
+    return (
+        <EditInPlace
+            label={label ?? SettingsStore.getDisplayName(settingKey, level) ?? ""}
+            value={value}
+            saveButtonLabel={_t("common|save")}
+            cancelButtonLabel={_t("common|cancel")}
+            savedLabel={_t("common|saved")}
+            savingLabel={_t("common|updating")}
+            onChange={onChange}
+            onCancel={onCancel}
+            onSave={onSave}
+            disabled={busy}
+        />
+    );
+};
+
+export default SettingsField;
diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx
index f3472f388937ea54efa5e774b5228e95291fc0e7..d7fac9187576addb98528bd8eb1d495da7a31672 100644
--- a/src/components/views/elements/SettingsFlag.tsx
+++ b/src/components/views/elements/SettingsFlag.tsx
@@ -8,14 +8,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 
 import SettingsStore from "../../../settings/SettingsStore";
 import { _t } from "../../../languageHandler";
 import ToggleSwitch from "./ToggleSwitch";
 import StyledCheckbox from "./StyledCheckbox";
-import { SettingLevel } from "../../../settings/SettingLevel";
-import { BooleanSettingKey, defaultWatchManager } from "../../../settings/Settings";
+import { type SettingLevel } from "../../../settings/SettingLevel";
+import { type BooleanSettingKey, defaultWatchManager } from "../../../settings/Settings";
 
 interface IProps {
     // The setting must be a boolean
@@ -35,7 +35,7 @@ interface IState {
 }
 
 export default class SettingsFlag extends React.Component<IProps, IState> {
-    private readonly id = `mx_SettingsFlag_${randomString(12)}`;
+    private readonly id = `mx_SettingsFlag_${secureRandomString(12)}`;
 
     public constructor(props: IProps) {
         super(props);
diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
index 9d250831ff0a82f1910b419d60d82dfff5fbe377..88a1ab1bd690bc0001717808c47296892ce3fda9 100644
--- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
+++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
@@ -6,14 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 
 import Dropdown from "../../views/elements/Dropdown";
 import PlatformPeg from "../../../PlatformPeg";
-import SettingsStore from "../../../settings/SettingsStore";
 import { _t, getUserLanguage } from "../../../languageHandler";
 import Spinner from "./Spinner";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 type Languages = {
     value: string;
@@ -105,17 +104,6 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
             return <div key={language.value}>{language.label}</div>;
         }) as NonEmptyArray<ReactElement & { key: string }>;
 
-        // default value here too, otherwise we need to handle null / undefined;
-        // values between mounting and the initial value propagating
-        let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
-        let value: string | undefined;
-        if (language) {
-            value = this.props.value || language;
-        } else {
-            language = navigator.language || navigator.userLanguage;
-            value = this.props.value || language;
-        }
-
         return (
             <Dropdown
                 id="mx_LanguageDropdown"
@@ -123,7 +111,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
                 onOptionChange={this.props.onOptionChange}
                 onSearchChange={this.onSearchChange}
                 searchEnabled={true}
-                value={value}
+                value={this.props.value}
                 label={_t("language_dropdown_label")}
                 placeholder={_t("settings|general|spell_check_locale_placeholder")}
             >
diff --git a/src/components/views/elements/Spoiler.tsx b/src/components/views/elements/Spoiler.tsx
index 588da46c6f924f40ba2a2bcfbf04a44151cc5687..8d3d0a55fd2a939ca6b8ef4dcc05614aa1ffb643 100644
--- a/src/components/views/elements/Spoiler.tsx
+++ b/src/components/views/elements/Spoiler.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import React from "react";
+import React, { type ReactNode } from "react";
 
 interface IProps {
     reason?: string;
-    contentHtml: string;
+    children: ReactNode;
 }
 
 interface IState {
@@ -38,9 +38,6 @@ export default class Spoiler extends React.Component<IProps, IState> {
         const reason = this.props.reason ? (
             <span className="mx_EventTile_spoiler_reason">{"(" + this.props.reason + ")"}</span>
         ) : null;
-        // react doesn't allow appending a DOM node as child.
-        // as such, we pass the this.props.contentHtml instead and then set the raw
-        // HTML content. This is secure as the contents have already been parsed previously
         return (
             <button
                 className={"mx_EventTile_spoiler" + (this.state.visible ? " visible" : "")}
@@ -48,10 +45,7 @@ export default class Spoiler extends React.Component<IProps, IState> {
             >
                 {reason}
                 &nbsp;
-                <span
-                    className="mx_EventTile_spoiler_content"
-                    dangerouslySetInnerHTML={{ __html: this.props.contentHtml }}
-                />
+                <span className="mx_EventTile_spoiler_content">{this.props.children}</span>
             </button>
         );
     }
diff --git a/src/components/views/elements/StyledCheckbox.tsx b/src/components/views/elements/StyledCheckbox.tsx
index 49d173cd7c5f202c4a63b0a636fb0a7a1e9f6e37..e4cde65d1659bf85fd3c669dc0a6136047448d3e 100644
--- a/src/components/views/elements/StyledCheckbox.tsx
+++ b/src/components/views/elements/StyledCheckbox.tsx
@@ -1,66 +1,51 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2020 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { Ref } from "react";
-import { randomString } from "matrix-js-sdk/src/randomstring";
-import classnames from "classnames";
-
-export enum CheckboxStyle {
-    Solid = "solid",
-    Outline = "outline",
-}
+import React, { useId, type ReactNode, type Ref } from "react";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
+import { CheckboxInput, Form, HelpMessage, InlineField, Label } from "@vector-im/compound-web";
 
 interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
     inputRef?: Ref<HTMLInputElement>;
-    kind?: CheckboxStyle;
     id?: string;
+    description?: ReactNode;
 }
 
-interface IState {}
-
-export default class StyledCheckbox extends React.PureComponent<IProps, IState> {
-    private id: string;
-
-    public static readonly defaultProps = {
-        className: "",
-    };
-
-    public constructor(props: IProps) {
-        super(props);
-        // 56^10 so unlikely chance of collision.
-        this.id = this.props.id || "checkbox_" + randomString(10);
-    }
-
-    public render(): React.ReactNode {
-        /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
-        const { children, className, kind = CheckboxStyle.Solid, inputRef, ...otherProps } = this.props;
-
-        const newClassName = classnames("mx_Checkbox", className, {
-            mx_Checkbox_hasKind: kind,
-            [`mx_Checkbox_kind_${kind}`]: kind,
-        });
-        return (
-            <span className={newClassName}>
-                <input
-                    // Pass through the ref - used for keyboard shortcut access to some buttons
-                    ref={inputRef}
-                    id={this.id}
-                    {...otherProps}
-                    type="checkbox"
-                />
-                <label htmlFor={this.id}>
-                    {/* Using the div to center the image */}
-                    <div className="mx_Checkbox_background">
-                        <div className="mx_Checkbox_checkmark" />
-                    </div>
-                    {!!this.props.children && <div>{this.props.children}</div>}
-                </label>
-            </span>
-        );
-    }
-}
+const StyledCheckbox: React.FC<IProps> = ({
+    id: initialId,
+    children: label,
+    className,
+    inputRef,
+    description,
+    ...otherProps
+}) => {
+    const id = initialId || "checkbox_" + secureRandomString(10);
+    const name = useId();
+    const descriptionId = useId();
+    return (
+        <Form.Root>
+            <InlineField
+                className={className}
+                name={name}
+                control={
+                    <CheckboxInput
+                        ref={inputRef}
+                        aria-describedby={description ? descriptionId : undefined}
+                        id={id}
+                        {...otherProps}
+                    />
+                }
+            >
+                {label && <Label htmlFor={id}>{label}</Label>}
+                {description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
+            </InlineField>
+        </Form.Root>
+    );
+};
+
+export default StyledCheckbox;
diff --git a/src/components/views/elements/StyledRadioButton.tsx b/src/components/views/elements/StyledRadioButton.tsx
index 01e4de1dad85995778fcc911a66a3f137850a7eb..54bee73b4a4fdd50931e549cd1176765ff8019b9 100644
--- a/src/components/views/elements/StyledRadioButton.tsx
+++ b/src/components/views/elements/StyledRadioButton.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { Ref } from "react";
+import React, { type Ref } from "react";
 import classnames from "classnames";
 
 interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
@@ -18,9 +18,7 @@ interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
     childrenInLabel?: boolean;
 }
 
-interface IState {}
-
-export default class StyledRadioButton extends React.PureComponent<IProps, IState> {
+export default class StyledRadioButton extends React.PureComponent<IProps> {
     public static readonly defaultProps = {
         className: "",
         childrenInLabel: true,
diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx
index b93a697bfb1a848f0cf6eba08d9bfdd47a69f419..1dba78d9123f59ef057c5f0503c16e39eaf4b4eb 100644
--- a/src/components/views/elements/StyledRadioGroup.tsx
+++ b/src/components/views/elements/StyledRadioGroup.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ReactNode } from "react";
+import React, { type JSX, type ChangeEvent, type ReactNode } from "react";
 import classNames from "classnames";
 
 import StyledRadioButton from "./StyledRadioButton";
diff --git a/src/components/views/elements/SyntaxHighlight.tsx b/src/components/views/elements/SyntaxHighlight.tsx
index d8c50e5789bb73cdba528198fd81aba324364724..b6f42916327600d911219583bf68b693fa2dbb4a 100644
--- a/src/components/views/elements/SyntaxHighlight.tsx
+++ b/src/components/views/elements/SyntaxHighlight.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
 
diff --git a/src/components/views/elements/Tag.tsx b/src/components/views/elements/Tag.tsx
index 0c1676794ebf9c15ef12e77b6269170157042350..c27442b30aafa15ba44aa9c1b8d44d51cf655a6d 100644
--- a/src/components/views/elements/Tag.tsx
+++ b/src/components/views/elements/Tag.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { DetailedHTMLProps, HTMLAttributes } from "react";
+import React, { type JSX, type DetailedHTMLProps, type HTMLAttributes } from "react";
 import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
 
 import AccessibleButton from "./AccessibleButton";
diff --git a/src/components/views/elements/TagComposer.tsx b/src/components/views/elements/TagComposer.tsx
index 55071964bcdf0131fc1cab56ea93532e16c2b41e..f20bf27253f80212e59605f777258ef22003b23d 100644
--- a/src/components/views/elements/TagComposer.tsx
+++ b/src/components/views/elements/TagComposer.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { ChangeEvent, FormEvent } from "react";
+import React, { type ChangeEvent, type FormEvent } from "react";
 
 import Field from "./Field";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/elements/TextWithTooltip.tsx b/src/components/views/elements/TextWithTooltip.tsx
index 42d436ced41346682f1530592c3494d44cdcba6c..445d1338e92eccb4bfd9da5f9b1fb711cfe18197 100644
--- a/src/components/views/elements/TextWithTooltip.tsx
+++ b/src/components/views/elements/TextWithTooltip.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 
 interface IProps extends HTMLAttributes<HTMLSpanElement> {
diff --git a/src/components/views/elements/ToggleSwitch.tsx b/src/components/views/elements/ToggleSwitch.tsx
index 3ef4981d55b07fca54bccbd30befb3f5159c6044..3f6548c7a0273761ecfaa85c47adfdfcaae4e5b3 100644
--- a/src/components/views/elements/ToggleSwitch.tsx
+++ b/src/components/views/elements/ToggleSwitch.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 
 import AccessibleButton from "./AccessibleButton";
diff --git a/src/components/views/elements/TruncatedList.tsx b/src/components/views/elements/TruncatedList.tsx
index 8e7abc11047801b5054c47a7b75f82caa38359b4..b3102bcc3cf4ea8ebd6a870a3a319e68c9da5884 100644
--- a/src/components/views/elements/TruncatedList.tsx
+++ b/src/components/views/elements/TruncatedList.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 import { _t } from "../../../languageHandler";
 
diff --git a/src/components/views/elements/Validation.tsx b/src/components/views/elements/Validation.tsx
index 2420945c6eb6b38648fe0326c9f8dc51b3364da4..4b2f8d9cd4d641b9591812d08084bfad78a1adb7 100644
--- a/src/components/views/elements/Validation.tsx
+++ b/src/components/views/elements/Validation.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import classNames from "classnames";
 import memoizeOne from "memoize-one";
 
diff --git a/src/components/views/emojipicker/Category.tsx b/src/components/views/emojipicker/Category.tsx
index 397778e0afc562b230d896ea01fe5a96581cda8f..9ffe1dce1428f6d5f55ef12312cdfc3cb5c2db49 100644
--- a/src/components/views/emojipicker/Category.tsx
+++ b/src/components/views/emojipicker/Category.tsx
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { RefObject } from "react";
-import { DATA_BY_CATEGORY, Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
+import React, { type JSX, type RefObject } from "react";
+import { type DATA_BY_CATEGORY, type Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
 
 import { CATEGORY_HEADER_HEIGHT, EMOJI_HEIGHT, EMOJIS_PER_ROW } from "./EmojiPicker";
 import LazyRenderList from "../elements/LazyRenderList";
 import Emoji from "./Emoji";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 
 const OVERFLOW_ROWS = 3;
 
@@ -24,7 +24,7 @@ export interface ICategory {
     name: string;
     enabled: boolean;
     visible: boolean;
-    ref: RefObject<HTMLButtonElement>;
+    ref: RefObject<HTMLButtonElement | null>;
 }
 
 interface IProps {
diff --git a/src/components/views/emojipicker/Emoji.tsx b/src/components/views/emojipicker/Emoji.tsx
index 3b74947f04d5e1240083dc5caac27f24e6ebc96d..26d15d4dc692de3bd40b9244a96b16af0310dba7 100644
--- a/src/components/views/emojipicker/Emoji.tsx
+++ b/src/components/views/emojipicker/Emoji.tsx
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
+import { type Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
 
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
 
 interface IProps {
diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx
index 0c10d778a60590c4f52908c28180b1937f993291..71659d579b4ae015d1f9c666174c710045a17f26 100644
--- a/src/components/views/emojipicker/EmojiPicker.tsx
+++ b/src/components/views/emojipicker/EmojiPicker.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { Dispatch } from "react";
-import { DATA_BY_CATEGORY, getEmojiFromUnicode, Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
+import React, { type Dispatch } from "react";
+import { DATA_BY_CATEGORY, getEmojiFromUnicode, type Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
 
 import { _t } from "../../../languageHandler";
 import * as recent from "../../../emojipicker/recent";
@@ -17,17 +17,17 @@ import Header from "./Header";
 import Search from "./Search";
 import Preview from "./Preview";
 import QuickReactions from "./QuickReactions";
-import Category, { CategoryKey, ICategory } from "./Category";
+import Category, { type CategoryKey, type ICategory } from "./Category";
 import { filterBoolean } from "../../../utils/arrays";
 import {
-    IAction as RovingAction,
-    IState as RovingState,
+    type IAction as RovingAction,
+    type IState as RovingState,
     RovingTabIndexProvider,
     Type,
 } from "../../../accessibility/RovingTabIndex";
 import { Key } from "../../../Keyboard";
 import { clamp } from "../../../utils/numbers";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 
 export const CATEGORY_HEADER_HEIGHT = 20;
 export const EMOJI_HEIGHT = 35;
diff --git a/src/components/views/emojipicker/Header.tsx b/src/components/views/emojipicker/Header.tsx
index 53607e286184210962052aa9fe970681413bb402..985882bd7b3ae012d37d69c60f323c5ecdd6f292 100644
--- a/src/components/views/emojipicker/Header.tsx
+++ b/src/components/views/emojipicker/Header.tsx
@@ -12,7 +12,7 @@ import classNames from "classnames";
 import { findLastIndex } from "lodash";
 
 import { _t } from "../../../languageHandler";
-import { CategoryKey, ICategory } from "./Category";
+import { type CategoryKey, type ICategory } from "./Category";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 
diff --git a/src/components/views/emojipicker/Preview.tsx b/src/components/views/emojipicker/Preview.tsx
index a51125102808a6a4af608609616f716eff0e4b30..d8d2fe99423b7719f46f53ca42896dd501850986 100644
--- a/src/components/views/emojipicker/Preview.tsx
+++ b/src/components/views/emojipicker/Preview.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Emoji } from "@matrix-org/emojibase-bindings";
+import { type Emoji } from "@matrix-org/emojibase-bindings";
 
 interface IProps {
     emoji: Emoji;
diff --git a/src/components/views/emojipicker/QuickReactions.tsx b/src/components/views/emojipicker/QuickReactions.tsx
index 7a8511f997d6e65afb833cb4fbca8cb5bb31641b..a814a6b206b8613f4d6ba81171169a8224e2ac4e 100644
--- a/src/components/views/emojipicker/QuickReactions.tsx
+++ b/src/components/views/emojipicker/QuickReactions.tsx
@@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { getEmojiFromUnicode, Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
+import { getEmojiFromUnicode, type Emoji as IEmoji } from "@matrix-org/emojibase-bindings";
 
 import { _t } from "../../../languageHandler";
 import Emoji from "./Emoji";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import Toolbar from "../../../accessibility/Toolbar";
 
 // We use the variation-selector Heart in Quick Reactions for some reason
diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx
index ea1a502942d329b2807bb50400b11c0086209d73..11b3d9468af49acd7645291fe52c2bd575413f0e 100644
--- a/src/components/views/emojipicker/ReactionPicker.tsx
+++ b/src/components/views/emojipicker/ReactionPicker.tsx
@@ -8,14 +8,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, EventType, RelationType, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, EventType, RelationType, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
 
 import EmojiPicker from "./EmojiPicker";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import RoomContext from "../../../contexts/RoomContext";
-import { FocusComposerPayload } from "../../../dispatcher/payloads/FocusComposerPayload";
+import { type FocusComposerPayload } from "../../../dispatcher/payloads/FocusComposerPayload";
 
 interface IProps {
     mxEvent: MatrixEvent;
@@ -31,8 +31,8 @@ class ReactionPicker extends React.Component<IProps, IState> {
     public static contextType = RoomContext;
     declare public context: React.ContextType<typeof RoomContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             selectedEmojis: new Set(Object.keys(this.getReactions())),
diff --git a/src/components/views/emojipicker/Search.tsx b/src/components/views/emojipicker/Search.tsx
index bc560c46431839e68ea9197fbaf91217720d525a..16d30f6a9bb8b513d2d147f1460fd7f9a7128bd3 100644
--- a/src/components/views/emojipicker/Search.tsx
+++ b/src/components/views/emojipicker/Search.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { _t } from "../../../languageHandler";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
diff --git a/src/components/views/location/LiveDurationDropdown.tsx b/src/components/views/location/LiveDurationDropdown.tsx
index 1bdf93a8e6860859768c9657f794dc1b9adce56b..31b8898cb56f5c8e7ad5021ecbb4062e9b7b4cba 100644
--- a/src/components/views/location/LiveDurationDropdown.tsx
+++ b/src/components/views/location/LiveDurationDropdown.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 
 import { formatDuration } from "../../../DateUtils";
 import { _t } from "../../../languageHandler";
 import Dropdown from "../elements/Dropdown";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 
 const DURATION_MS = {
     fifteenMins: 900000,
diff --git a/src/components/views/location/LocationButton.tsx b/src/components/views/location/LocationButton.tsx
index 3a4460bec47a316c2bac3b8753dab5a2b016531b..e84ce37ec56043b9e65f8194300c9ef433366803 100644
--- a/src/components/views/location/LocationButton.tsx
+++ b/src/components/views/location/LocationButton.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, SyntheticEvent, useContext } from "react";
+import React, { type ReactNode, type SyntheticEvent, useContext } from "react";
 import classNames from "classnames";
-import { RoomMember, IEventRelation } from "matrix-js-sdk/src/matrix";
+import { type RoomMember, type IEventRelation } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { CollapsibleButton } from "../rooms/CollapsibleButton";
-import { aboveLeftOf, useContextMenu, MenuProps } from "../../structures/ContextMenu";
+import { aboveLeftOf, useContextMenu, type MenuProps } from "../../structures/ContextMenu";
 import { OverflowMenuContext } from "../rooms/MessageComposerButtons";
 import LocationShareMenu from "./LocationShareMenu";
 
diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx
index fe0838c166a1153fde90280c30d10b8b4f7f43e2..dce5699fcc0faba4c27bd106e00fac460292698b 100644
--- a/src/components/views/location/LocationPicker.tsx
+++ b/src/components/views/location/LocationPicker.tsx
@@ -6,22 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { SyntheticEvent } from "react";
-import maplibregl, { MapMouseEvent } from "maplibre-gl";
+import React, { type SyntheticEvent } from "react";
+import maplibregl, { type MapMouseEvent } from "maplibre-gl";
 import { logger } from "matrix-js-sdk/src/logger";
-import { RoomMember, ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
+import { type RoomMember, ClientEvent, type IClientWellKnown } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import Modal from "../../../Modal";
 import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
-import { GenericPosition, genericPositionFromGeolocation, getGeoUri } from "../../../utils/beacon";
+import { type GenericPosition, genericPositionFromGeolocation, getGeoUri } from "../../../utils/beacon";
 import { LocationShareError, findMapStyleUrl, positionFailureMessage } from "../../../utils/location";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import AccessibleButton from "../elements/AccessibleButton";
 import { MapError } from "./MapError";
 import LiveDurationDropdown, { DEFAULT_DURATION_MS } from "./LiveDurationDropdown";
-import { LocationShareType, ShareLocationFn } from "./shareLocation";
+import { LocationShareType, type ShareLocationFn } from "./shareLocation";
 import Marker from "./Marker";
 
 export interface ILocationPickerProps {
@@ -47,8 +47,8 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
     private geolocate?: maplibregl.GeolocateControl;
     private marker?: maplibregl.Marker;
 
-    public constructor(props: ILocationPickerProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: ILocationPickerProps) {
+        super(props);
 
         this.state = {
             position: undefined,
diff --git a/src/components/views/location/LocationShareMenu.tsx b/src/components/views/location/LocationShareMenu.tsx
index 546ab4f37350dffd39b62a4f7b911ae1ccabd7e5..453ad33cea0e1dc8d6a7a77924d3f5d8e9b4775d 100644
--- a/src/components/views/location/LocationShareMenu.tsx
+++ b/src/components/views/location/LocationShareMenu.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { SyntheticEvent, useContext, useState } from "react";
-import { Room, IEventRelation } from "matrix-js-sdk/src/matrix";
+import React, { type SyntheticEvent, useContext, useState } from "react";
+import { type Room, type IEventRelation } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import ContextMenu, { MenuProps } from "../../structures/ContextMenu";
-import LocationPicker, { ILocationPickerProps } from "./LocationPicker";
+import ContextMenu, { type MenuProps } from "../../structures/ContextMenu";
+import LocationPicker, { type ILocationPickerProps } from "./LocationPicker";
 import { shareLiveLocation, shareLocation, LocationShareType } from "./shareLocation";
 import SettingsStore from "../../../settings/SettingsStore";
 import ShareDialogButtons from "./ShareDialogButtons";
diff --git a/src/components/views/location/LocationViewDialog.tsx b/src/components/views/location/LocationViewDialog.tsx
index d35f0b75e2b3f2ed562ce8863597327f1d08689f..ad975a0100373e32bc0a94b0935f43a242127d2c 100644
--- a/src/components/views/location/LocationViewDialog.tsx
+++ b/src/components/views/location/LocationViewDialog.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import BaseDialog from "../dialogs/BaseDialog";
 import { locationEventGeoUri, isSelfLocation } from "../../../utils/location";
diff --git a/src/components/views/location/Map.tsx b/src/components/views/location/Map.tsx
index 3af8b795792c186be7d97553a930bce116c3dadc..998dff1d15741381ada3fea3d44e5d450c599f3f 100644
--- a/src/components/views/location/Map.tsx
+++ b/src/components/views/location/Map.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useContext, useEffect, useState } from "react";
+import React, { type ReactNode, useContext, useEffect, useState } from "react";
 import classNames from "classnames";
 import * as maplibregl from "maplibre-gl";
-import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type IClientWellKnown } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -17,7 +17,7 @@ import { useEventEmitterState } from "../../../hooks/useEventEmitter";
 import { parseGeoUri, positionFailureMessage } from "../../../utils/location";
 import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
 import { useMap } from "../../../utils/location/useMap";
-import { Bounds } from "../../../utils/beacon/bounds";
+import { type Bounds } from "../../../utils/beacon/bounds";
 import Modal from "../../../Modal";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/location/MapError.tsx b/src/components/views/location/MapError.tsx
index 93c7dab4066e9bb5b5ebad2c864d12f45407d5f7..e2059f9cd1127cb5e361ca6f5de78774628cf3e6 100644
--- a/src/components/views/location/MapError.tsx
+++ b/src/components/views/location/MapError.tsx
@@ -11,7 +11,7 @@ import classNames from "classnames";
 import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t } from "../../../languageHandler";
-import { getLocationShareErrorMessage, LocationShareError } from "../../../utils/location";
+import { getLocationShareErrorMessage, type LocationShareError } from "../../../utils/location";
 import AccessibleButton from "../elements/AccessibleButton";
 import Heading from "../typography/Heading";
 
diff --git a/src/components/views/location/MapFallback.tsx b/src/components/views/location/MapFallback.tsx
index 94829d3bc0040513f9ae6a5aed76886f482e4db8..59968d421e92d365c7d3c1cad489ee34d1012b7c 100644
--- a/src/components/views/location/MapFallback.tsx
+++ b/src/components/views/location/MapFallback.tsx
@@ -16,7 +16,6 @@ import Spinner from "../elements/Spinner";
 interface Props extends React.HTMLAttributes<HTMLDivElement> {
     className?: string;
     isLoading?: boolean;
-    children?: React.ReactNode | React.ReactNodeArray;
 }
 
 const MapFallback: React.FC<Props> = ({ className, isLoading, children, ...rest }) => {
diff --git a/src/components/views/location/Marker.tsx b/src/components/views/location/Marker.tsx
index 13239240f160929fb3e760faf2227cbb8caa71b4..8502580c2ed3e43b809013d5a3eda23d7788a6d5 100644
--- a/src/components/views/location/Marker.tsx
+++ b/src/components/views/location/Marker.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useState } from "react";
+import React, { type JSX, type ReactNode, type Ref, useState } from "react";
 import classNames from "classnames";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
 
 import { getUserNameColorClass } from "../../../utils/FormattingUtils";
@@ -21,6 +21,7 @@ interface Props {
     // use member text color as background
     useMemberColor?: boolean;
     tooltip?: ReactNode;
+    ref?: Ref<HTMLDivElement>;
 }
 
 /**
@@ -55,7 +56,7 @@ const OptionalTooltip: React.FC<{
 /**
  * Generic location marker
  */
-const Marker = React.forwardRef<HTMLDivElement, Props>(({ id, roomMember, useMemberColor, tooltip }, ref) => {
+const Marker = ({ id, roomMember, useMemberColor, tooltip, ref }: Props): JSX.Element => {
     const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : "";
     return (
         <div
@@ -82,6 +83,6 @@ const Marker = React.forwardRef<HTMLDivElement, Props>(({ id, roomMember, useMem
             </OptionalTooltip>
         </div>
     );
-});
+};
 
 export default Marker;
diff --git a/src/components/views/location/ShareType.tsx b/src/components/views/location/ShareType.tsx
index 923cd265724383f40f95367c5f3f399bb824d6fd..3e1585af479db55c6494b9ffb58ff027e3d857b4 100644
--- a/src/components/views/location/ShareType.tsx
+++ b/src/components/views/location/ShareType.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLAttributes, useContext } from "react";
+import React, { type HTMLAttributes, useContext } from "react";
 import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin-solid";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { _t } from "../../../languageHandler";
 import { OwnProfileStore } from "../../../stores/OwnProfileStore";
 import BaseAvatar from "../avatars/BaseAvatar";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import Heading from "../typography/Heading";
 import { LocationShareType } from "./shareLocation";
 import StyledLiveBeaconIcon from "../beacon/StyledLiveBeaconIcon";
diff --git a/src/components/views/location/SmartMarker.tsx b/src/components/views/location/SmartMarker.tsx
index 4ec42468a6183d73954f1f6c7c42c0f6da0c8d5e..31d34397d6cb2d5856cdda2d19824cfb16ede1d7 100644
--- a/src/components/views/location/SmartMarker.tsx
+++ b/src/components/views/location/SmartMarker.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useCallback, useEffect, useState } from "react";
-import * as maplibregl from "maplibre-gl";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode, useCallback, useEffect, useState } from "react";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 
+import type * as maplibregl from "maplibre-gl";
 import { parseGeoUri } from "../../../utils/location";
 import { createMarker } from "../../../utils/location/map";
 import Marker from "./Marker";
diff --git a/src/components/views/location/ZoomButtons.tsx b/src/components/views/location/ZoomButtons.tsx
index 8926fa20c78567f9634a1131f3d7fc4dbf73b0b2..35fc4c7bb33caf295a149477160fc0798683f19e 100644
--- a/src/components/views/location/ZoomButtons.tsx
+++ b/src/components/views/location/ZoomButtons.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import * as maplibregl from "maplibre-gl";
 import { PlusIcon, MinusIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
+import type * as maplibregl from "maplibre-gl";
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
 
diff --git a/src/components/views/location/index.tsx b/src/components/views/location/index.tsx
index 3f308f381249f6141f9ee06b637d50ded930d1d7..53c411aab14ec8ba25e114f201e7354fe3921434 100644
--- a/src/components/views/location/index.tsx
+++ b/src/components/views/location/index.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 // Exports location components which touch maplibre-gs wrapped in React Suspense to enable code splitting
 
-import React, { ComponentProps, lazy, Suspense } from "react";
+import React, { type JSX, type ComponentProps, lazy, Suspense } from "react";
 
 import Spinner from "../elements/Spinner";
 
diff --git a/src/components/views/location/shareLocation.ts b/src/components/views/location/shareLocation.ts
index 5aed9ec37e9f714b651d333cd5722488e2d5fb3c..91b7dfd5dcae08300ea3a5fcd6f567bd9f2381c3 100644
--- a/src/components/views/location/shareLocation.ts
+++ b/src/components/views/location/shareLocation.ts
@@ -7,19 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
-    IEventRelation,
-    MatrixError,
+    type MatrixClient,
+    type IEventRelation,
+    type MatrixError,
     THREAD_RELATION_TYPE,
     ContentHelpers,
     LocationAssetType,
 } from "matrix-js-sdk/src/matrix";
-import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
 import Modal from "../../../Modal";
-import QuestionDialog, { IQuestionDialogProps } from "../dialogs/QuestionDialog";
+import QuestionDialog, { type IQuestionDialogProps } from "../dialogs/QuestionDialog";
 import SdkConfig from "../../../SdkConfig";
 import { OwnBeaconStore } from "../../../stores/OwnBeaconStore";
 import { doMaybeLocalRoomAction } from "../../../utils/local-room";
diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx
index 5d2e56c4e8a696f1dd8dccd788e3cc97b783b6b1..f8efd8381ea6dd1cf90a14185d2ba017ba88db12 100644
--- a/src/components/views/messages/CallEvent.tsx
+++ b/src/components/views/messages/CallEvent.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, useCallback, useContext, useMemo } from "react";
+import React, { type Ref, useCallback, useContext, useMemo, type JSX } from "react";
 
 import type { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
-import { ConnectionState, ElementCall } from "../../../models/Call";
+import { ConnectionState, type ElementCall } from "../../../models/Call";
 import { _t } from "../../../languageHandler";
 import {
     useCall,
@@ -20,7 +20,7 @@ import {
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../dispatcher/actions";
-import AccessibleButton, { AccessibleButtonKind, ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type AccessibleButtonKind, type ButtonEvent } from "../elements/AccessibleButton";
 import MemberAvatar from "../avatars/MemberAvatar";
 import { LiveContentSummary, LiveContentType } from "../rooms/LiveContentSummary";
 import FacePile from "../elements/FacePile";
@@ -37,60 +37,69 @@ interface ActiveCallEventProps {
     buttonKind: AccessibleButtonKind;
     buttonDisabledTooltip?: string;
     onButtonClick: ((ev: ButtonEvent) => void) | null;
+    ref?: Ref<HTMLDivElement>;
 }
 
-const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
-    ({ mxEvent, call, participatingMembers, buttonText, buttonKind, buttonDisabledTooltip, onButtonClick }, ref) => {
-        const senderName = useMemo(() => mxEvent.sender?.name ?? mxEvent.getSender(), [mxEvent]);
+const ActiveCallEvent = ({
+    mxEvent,
+    call,
+    participatingMembers,
+    buttonText,
+    buttonKind,
+    buttonDisabledTooltip,
+    onButtonClick,
+    ref,
+}: ActiveCallEventProps): JSX.Element => {
+    const senderName = useMemo(() => mxEvent.sender?.name ?? mxEvent.getSender(), [mxEvent]);
+
+    const facePileMembers = useMemo(() => participatingMembers.slice(0, MAX_FACES), [participatingMembers]);
+    const facePileOverflow = participatingMembers.length > facePileMembers.length;
 
-        const facePileMembers = useMemo(() => participatingMembers.slice(0, MAX_FACES), [participatingMembers]);
-        const facePileOverflow = participatingMembers.length > facePileMembers.length;
-
-        return (
-            <div className="mx_CallEvent_wrapper" ref={ref}>
-                <div className="mx_CallEvent mx_CallEvent_active">
-                    <MemberAvatar
-                        member={mxEvent.sender}
-                        fallbackUserId={mxEvent.getSender()}
-                        viewUserOnClick
-                        size="24px"
-                    />
-                    <div className="mx_CallEvent_columns">
-                        <div className="mx_CallEvent_details">
-                            <span className="mx_CallEvent_title">
-                                {_t("timeline|m.call|video_call_started_text", { name: senderName })}
-                            </span>
-                            <LiveContentSummary
-                                type={LiveContentType.Video}
-                                text={_t("voip|video_call")}
-                                active={false}
-                                participantCount={participatingMembers.length}
-                            />
-                            <FacePile members={facePileMembers} size="24px" overflow={facePileOverflow} />
-                        </div>
-                        {call && <SessionDuration session={call.session} />}
-                        <AccessibleButton
-                            className="mx_CallEvent_button"
-                            kind={buttonKind}
-                            disabled={onButtonClick === null || buttonDisabledTooltip !== undefined}
-                            onClick={onButtonClick}
-                            title={buttonDisabledTooltip}
-                        >
-                            {buttonText}
-                        </AccessibleButton>
+    return (
+        <div className="mx_CallEvent_wrapper" ref={ref}>
+            <div className="mx_CallEvent mx_CallEvent_active">
+                <MemberAvatar
+                    member={mxEvent.sender}
+                    fallbackUserId={mxEvent.getSender()}
+                    viewUserOnClick
+                    size="24px"
+                />
+                <div className="mx_CallEvent_columns">
+                    <div className="mx_CallEvent_details">
+                        <span className="mx_CallEvent_title">
+                            {_t("timeline|m.call|video_call_started_text", { name: senderName })}
+                        </span>
+                        <LiveContentSummary
+                            type={LiveContentType.Video}
+                            text={_t("voip|video_call")}
+                            active={false}
+                            participantCount={participatingMembers.length}
+                        />
+                        <FacePile members={facePileMembers} size="24px" overflow={facePileOverflow} />
                     </div>
+                    {call && <SessionDuration session={call.session} />}
+                    <AccessibleButton
+                        className="mx_CallEvent_button"
+                        kind={buttonKind}
+                        disabled={onButtonClick === null || buttonDisabledTooltip !== undefined}
+                        onClick={onButtonClick}
+                        title={buttonDisabledTooltip}
+                    >
+                        {buttonText}
+                    </AccessibleButton>
                 </div>
             </div>
-        );
-    },
-);
+        </div>
+    );
+};
 
 interface ActiveLoadedCallEventProps {
     mxEvent: MatrixEvent;
     call: ElementCall;
+    ref?: Ref<HTMLDivElement>;
 }
 
-const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxEvent, call }, ref) => {
+const ActiveLoadedCallEvent = ({ mxEvent, call, ref }: ActiveLoadedCallEventProps): JSX.Element => {
     const connectionState = useConnectionState(call);
     const participatingMembers = useParticipatingMembers(call);
     const joinCallButtonDisabledTooltip = useJoinCallButtonDisabledTooltip(call);
@@ -126,10 +135,6 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
                 return [_t("action|leave"), "danger", disconnect];
             case ConnectionState.Disconnecting:
                 return [_t("action|leave"), "danger", null];
-            case ConnectionState.Connecting:
-            case ConnectionState.Lobby:
-            case ConnectionState.WidgetLoading:
-                return [_t("action|join"), "primary", null];
         }
     }, [connectionState, connect, disconnect]);
 
@@ -145,16 +150,17 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
             onButtonClick={onButtonClick}
         />
     );
-});
+};
 
 interface CallEventProps {
     mxEvent: MatrixEvent;
+    ref?: Ref<HTMLDivElement>;
 }
 
 /**
  * An event tile representing an active or historical Element call.
  */
-export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
+export const CallEvent = ({ mxEvent, ref }: CallEventProps): JSX.Element => {
     const client = useContext(MatrixClientContext);
     const call = useCall(mxEvent.getRoomId()!);
     const latestEvent = client
@@ -191,4 +197,4 @@ export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
     }
 
     return <ActiveLoadedCallEvent mxEvent={mxEvent} call={call as ElementCall} ref={ref} />;
-});
+};
diff --git a/src/components/views/messages/CodeBlock.tsx b/src/components/views/messages/CodeBlock.tsx
index fd623eea0186b71cb4d5b1cee3a72d889d36887e..cb21ffb5e043a3fdc33eddd5f63bf79557a42ce3 100644
--- a/src/components/views/messages/CodeBlock.tsx
+++ b/src/components/views/messages/CodeBlock.tsx
@@ -5,9 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState } from "react";
+import React, { type JSX, useState } from "react";
 import classNames from "classnames";
-import { TooltipProvider } from "@vector-im/compound-web";
+import { type DOMNode, Element as ParserElement, domToReact } from "html-react-parser";
+import { textContent, getInnerHTML } from "domutils";
 
 import { useSettingValue } from "../../../hooks/useSettings.ts";
 import { CopyTextButton } from "../elements/CopyableText.tsx";
@@ -16,8 +17,7 @@ const MAX_HIGHLIGHT_LENGTH = 4096;
 const MAX_LINES_BEFORE_COLLAPSE = 5;
 
 interface Props {
-    children: HTMLElement;
-    onHeightChanged?(): void;
+    preNode: ParserElement;
 }
 
 const ExpandCollapseButton: React.FC<{
@@ -35,30 +35,31 @@ const ExpandCollapseButton: React.FC<{
     );
 };
 
-const CodeBlock: React.FC<Props> = ({ children, onHeightChanged }) => {
+const CodeBlock: React.FC<Props> = ({ preNode }) => {
     const enableSyntaxHighlightLanguageDetection = useSettingValue("enableSyntaxHighlightLanguageDetection");
     const showCodeLineNumbers = useSettingValue("showCodeLineNumbers");
     const expandCodeByDefault = useSettingValue("expandCodeByDefault");
     const [expanded, setExpanded] = useState(expandCodeByDefault);
 
+    const text = textContent(preNode);
+
     let expandCollapseButton: JSX.Element | undefined;
-    if (children.textContent && children.textContent.split("\n").length >= MAX_LINES_BEFORE_COLLAPSE) {
+    if (text.split("\n").length >= MAX_LINES_BEFORE_COLLAPSE) {
         expandCollapseButton = (
             <ExpandCollapseButton
                 expanded={expanded}
                 onClick={() => {
                     setExpanded(!expanded);
-                    // By expanding/collapsing we changed the height, therefore we call this
-                    onHeightChanged?.();
                 }}
             />
         );
     }
 
+    const innerHTML = getInnerHTML(preNode);
     let lineNumbers: JSX.Element | undefined;
     if (showCodeLineNumbers) {
         // Calculate number of lines in pre
-        const number = children.innerHTML.replace(/\n(<\/code>)?$/, "").split(/\n/).length;
+        const number = innerHTML.replace(/\n(<\/code>)?$/, "").split(/\n/).length;
         // Iterate through lines starting with 1 (number of the first line is 1)
         lineNumbers = (
             <span className="mx_EventTile_lineNumbers">
@@ -108,28 +109,37 @@ const CodeBlock: React.FC<Props> = ({ children, onHeightChanged }) => {
         }
     }
 
+    function highlightCodeRef(div: HTMLElement | null): void {
+        highlightCode(div);
+    }
+
+    let content = domToReact(preNode.children as DOMNode[]);
+
+    // Add code element if it's missing since we depend on it
+    if (!preNode.children.some((child) => child instanceof ParserElement && child.tagName.toUpperCase() === "CODE")) {
+        content = <code>{content}</code>;
+    }
+
     return (
-        <TooltipProvider>
+        <div className="mx_EventTile_pre_container">
             <pre
                 className={classNames({
                     mx_EventTile_collapsedCodeBlock: !expanded,
                 })}
             >
                 {lineNumbers}
-                <div
-                    style={{ display: "contents" }}
-                    dangerouslySetInnerHTML={{ __html: children.innerHTML }}
-                    ref={highlightCode}
-                />
+                <div style={{ display: "contents" }} ref={highlightCodeRef}>
+                    {content}
+                </div>
             </pre>
             {expandCollapseButton}
             <CopyTextButton
-                getTextToCopy={() => children.getElementsByTagName("code")[0]?.textContent ?? null}
+                getTextToCopy={() => text}
                 className={classNames("mx_EventTile_button mx_EventTile_copyButton", {
                     mx_EventTile_buttonBottom: !!expandCollapseButton,
                 })}
             />
-        </TooltipProvider>
+        </div>
     );
 };
 
diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx
index e8515b9347bd0e744ca44e256bd35f464332daee..a885806cf76e4fa531895e45e010872402c3c56d 100644
--- a/src/components/views/messages/DateSeparator.tsx
+++ b/src/components/views/messages/DateSeparator.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { Direction, ConnectionError, MatrixError, HTTPError } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { capitalize } from "lodash";
@@ -22,7 +22,7 @@ import { UIFeature } from "../../../settings/UIFeature";
 import Modal from "../../../Modal";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import BugReportDialog from "../dialogs/BugReportDialog";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { contextMenuBelow } from "../rooms/RoomTile";
 import { ContextMenuTooltipButton } from "../../structures/ContextMenu";
 import IconizedContextMenu, {
@@ -30,7 +30,7 @@ import IconizedContextMenu, {
     IconizedContextMenuOptionList,
 } from "../context_menus/IconizedContextMenu";
 import JumpToDatePicker from "./JumpToDatePicker";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { SdkContextClass } from "../../../contexts/SDKContext";
 import TimelineSeparator from "./TimelineSeparator";
 
diff --git a/src/components/views/messages/DecryptionFailureBody.tsx b/src/components/views/messages/DecryptionFailureBody.tsx
index f3c02d4c0c160ac5739eb81f32cd75d28e1a28ad..f75a7c48f8e623877ce09b0236bbcd5fb267773b 100644
--- a/src/components/views/messages/DecryptionFailureBody.tsx
+++ b/src/components/views/messages/DecryptionFailureBody.tsx
@@ -7,16 +7,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { forwardRef, ForwardRefExoticComponent, useContext } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
 import { BlockIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t } from "../../../languageHandler";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
 
-function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | React.JSX.Element {
+function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): string | JSX.Element {
     switch (mxEvent.decryptionFailureReason) {
         case DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE:
             return _t("timeline|decryption_failure|blocked");
@@ -72,7 +72,7 @@ function errorClassName(mxEvent: MatrixEvent): string | null {
 }
 
 // A placeholder element for messages that could not be decrypted
-export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent }, ref): React.JSX.Element => {
+export const DecryptionFailureBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
     const verificationState = useContext(LocalDeviceVerificationStateContext);
     const classes = classNames("mx_DecryptionFailureBody", "mx_EventTile_content", errorClassName(mxEvent));
 
@@ -81,4 +81,4 @@ export const DecryptionFailureBody = forwardRef<HTMLDivElement, IBodyProps>(({ m
             {getErrorMessage(mxEvent, verificationState)}
         </div>
     );
-}) as ForwardRefExoticComponent<IBodyProps>;
+};
diff --git a/src/components/views/messages/DownloadActionButton.tsx b/src/components/views/messages/DownloadActionButton.tsx
index e85389f2e79008bc8fb65fd6e249d8dde57535b0..340bdffa543cd39f40ffd93bd924e78730704856 100644
--- a/src/components/views/messages/DownloadActionButton.tsx
+++ b/src/components/views/messages/DownloadActionButton.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
-import React from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import { MediaEventHelper } from "../../../utils/MediaEventHelper";
+import { type MediaEventHelper } from "../../../utils/MediaEventHelper";
 import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
 import Spinner from "../elements/Spinner";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import { FileDownloader } from "../../../utils/FileDownloader";
 import Modal from "../../../Modal";
 import ErrorDialog from "../dialogs/ErrorDialog";
diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx
index 1e6333807dce7093c1786509afe5a217ea19af95..c025cac9a512832c437a433de936a5dc54c656db 100644
--- a/src/components/views/messages/EditHistoryMessage.tsx
+++ b/src/components/views/messages/EditHistoryMessage.tsx
@@ -6,15 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
-import { EventStatus, IContent, MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, createRef } from "react";
+import { type EventStatus, type IContent, type MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
-import * as HtmlUtils from "../../../HtmlUtils";
+import EventContentBody from "./EventContentBody.tsx";
 import { editBodyDiffToHtml } from "../../../utils/MessageDiffUtils";
 import { formatTime } from "../../../DateUtils";
-import { pillifyLinks } from "../../../utils/pillify";
-import { tooltipifyLinks } from "../../../utils/tooltipify";
 import { _t } from "../../../languageHandler";
 import Modal from "../../../Modal";
 import RedactedBody from "./RedactedBody";
@@ -23,7 +21,6 @@ import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
 import ViewSource from "../../structures/ViewSource";
 import SettingsStore from "../../../settings/SettingsStore";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { ReactRootManager } from "../../../utils/react";
 
 function getReplacedContent(event: MatrixEvent): IContent {
     const originalContent = event.getOriginalContent();
@@ -48,8 +45,6 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
     private content = createRef<HTMLDivElement>();
-    private pills = new ReactRootManager();
-    private tooltips = new ReactRootManager();
 
     public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
         super(props, context);
@@ -94,37 +89,11 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
         );
     };
 
-    private pillifyLinks(): void {
-        // not present for redacted events
-        if (this.content.current) {
-            pillifyLinks(this.context, this.content.current.children, this.props.mxEvent, this.pills);
-        }
-    }
-
-    private tooltipifyLinks(): void {
-        // not present for redacted events
-        if (this.content.current) {
-            tooltipifyLinks(this.content.current.children, this.pills.elements, this.tooltips);
-        }
-    }
-
-    public componentDidMount(): void {
-        this.pillifyLinks();
-        this.tooltipifyLinks();
-    }
-
     public componentWillUnmount(): void {
-        this.pills.unmount();
-        this.tooltips.unmount();
         const event = this.props.mxEvent;
         event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
     }
 
-    public componentDidUpdate(): void {
-        this.pillifyLinks();
-        this.tooltipifyLinks();
-    }
-
     private renderActionBar(): React.ReactNode {
         // hide the button when already redacted
         let redactButton: JSX.Element | undefined;
@@ -164,9 +133,20 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
             if (this.props.previousEdit) {
                 contentElements = editBodyDiffToHtml(getReplacedContent(this.props.previousEdit), content);
             } else {
-                contentElements = HtmlUtils.bodyToSpan(content, null, {
-                    stripReplyFallback: true,
-                });
+                contentElements = (
+                    <EventContentBody
+                        as="span"
+                        mxEvent={mxEvent}
+                        content={content}
+                        highlights={[]}
+                        stripReply
+                        renderTooltipsForAmbiguousLinks
+                        renderMentionPills
+                        renderCodeBlocks
+                        renderSpoilers
+                        linkify
+                    />
+                );
             }
             if (mxEvent.getContent().msgtype === MsgType.Emote) {
                 const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
diff --git a/src/components/views/messages/EncryptionEvent.tsx b/src/components/views/messages/EncryptionEvent.tsx
index 090881aa02edfb252e39fafa1148e818d428a5ec..5c5f1f0dc2631826f936d0f0ee762ca8997379f8 100644
--- a/src/components/views/messages/EncryptionEvent.tsx
+++ b/src/components/views/messages/EncryptionEvent.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type Ref, type ReactNode } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
 import { _t } from "../../../languageHandler";
@@ -22,9 +22,10 @@ import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
 interface IProps {
     mxEvent: MatrixEvent;
     timestamp?: JSX.Element;
+    ref?: Ref<HTMLDivElement>;
 }
 
-const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
+const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
     const cli = useMatrixClientContext();
     const roomId = mxEvent.getRoomId()!;
     const isRoomEncrypted = useIsEncrypted(cli, cli.getRoom(roomId) || undefined);
@@ -80,6 +81,6 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
             timestamp={timestamp}
         />
     );
-});
+};
 
 export default EncryptionEvent;
diff --git a/src/components/views/messages/EventContentBody.tsx b/src/components/views/messages/EventContentBody.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fce5428e8c50dfc93b56a5e4cbe15649afb01bba
--- /dev/null
+++ b/src/components/views/messages/EventContentBody.tsx
@@ -0,0 +1,198 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { memo, useContext, useMemo, type Ref } from "react";
+import { type IContent, type MatrixEvent, MsgType, PushRuleKind } from "matrix-js-sdk/src/matrix";
+import parse from "html-react-parser";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+
+import { bodyToNode } from "../../../HtmlUtils.tsx";
+import { Linkify } from "../../../Linkify.tsx";
+import PlatformPeg from "../../../PlatformPeg.ts";
+import {
+    applyReplacerOnString,
+    combineRenderers,
+    type Replacer,
+    type RendererMap,
+    keywordPillRenderer,
+    mentionPillRenderer,
+    ambiguousLinkTooltipRenderer,
+    codeBlockRenderer,
+    spoilerRenderer,
+    replacerToRenderFunction,
+} from "../../../renderer";
+import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
+import { useSettingValue } from "../../../hooks/useSettings.ts";
+import { filterBoolean } from "../../../utils/arrays.ts";
+import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";
+
+/**
+ * Returns a RegExp pattern for the keyword in the push rule of the given Matrix event, if any
+ * @param mxEvent - the Matrix event to get the push rule keyword pattern from
+ */
+const getPushDetailsKeywordPatternRegexp = (mxEvent: MatrixEvent): RegExp | undefined => {
+    const pushDetails = mxEvent.getPushDetails();
+    if (
+        pushDetails.rule?.enabled &&
+        pushDetails.rule.kind === PushRuleKind.ContentSpecific &&
+        pushDetails.rule.pattern
+    ) {
+        return PushProcessor.getPushRuleGlobRegex(pushDetails.rule.pattern, true, "gi");
+    }
+    return undefined;
+};
+
+interface ReplacerOptions {
+    /**
+     * Whether to render room/user mentions as pills
+     */
+    renderMentionPills?: boolean;
+    /**
+     * Whether to render push rule keywords as pills
+     */
+    renderKeywordPills?: boolean;
+    /**
+     * Whether to render spoilers as hidden content revealed on click
+     */
+    renderSpoilers?: boolean;
+    /**
+     * Whether to render code blocks as syntax highlighted code with a copy to clipboard button
+     */
+    renderCodeBlocks?: boolean;
+    /**
+     * Whether to render tooltips for ambiguous links, only effective on platforms which specify `needsUrlTooltips` true
+     */
+    renderTooltipsForAmbiguousLinks?: boolean;
+}
+
+// Returns a memoized Replacer based on the input parameters
+const useReplacer = (content: IContent, mxEvent: MatrixEvent | undefined, options: ReplacerOptions): Replacer => {
+    const cli = useContext(MatrixClientContext);
+    const room = cli.getRoom(mxEvent?.getRoomId()) ?? undefined;
+
+    const shouldShowPillAvatar = useSettingValue("Pill.shouldShowPillAvatar");
+    const isHtml = content.format === "org.matrix.custom.html";
+
+    const replacer = useMemo(() => {
+        const keywordRegexpPattern = mxEvent ? getPushDetailsKeywordPatternRegexp(mxEvent) : undefined;
+        const replacers = filterBoolean<RendererMap>([
+            options.renderMentionPills ? mentionPillRenderer : undefined,
+            options.renderKeywordPills && keywordRegexpPattern ? keywordPillRenderer : undefined,
+            options.renderTooltipsForAmbiguousLinks && PlatformPeg.get()?.needsUrlTooltips()
+                ? ambiguousLinkTooltipRenderer
+                : undefined,
+            options.renderSpoilers ? spoilerRenderer : undefined,
+            options.renderCodeBlocks ? codeBlockRenderer : undefined,
+        ]);
+        return combineRenderers(...replacers)({
+            isHtml,
+            mxEvent,
+            room,
+            shouldShowPillAvatar,
+            keywordRegexpPattern,
+        });
+    }, [
+        mxEvent,
+        options.renderMentionPills,
+        options.renderKeywordPills,
+        options.renderTooltipsForAmbiguousLinks,
+        options.renderSpoilers,
+        options.renderCodeBlocks,
+        isHtml,
+        room,
+        shouldShowPillAvatar,
+    ]);
+
+    return replacer;
+};
+
+interface Props extends ReplacerOptions {
+    /**
+     * Whether to render the content in a div or span
+     */
+    as: "span" | "div";
+    /**
+     * Whether to render links as clickable anchors
+     */
+    linkify: boolean;
+    /**
+     * The Matrix event to render, required for renderMentionPills & renderKeywordPills
+     */
+    mxEvent?: MatrixEvent;
+    /**
+     * The content to render
+     */
+    content: IContent;
+    /**
+     * Whether to strip reply fallbacks from the content before rendering
+     */
+    stripReply?: boolean;
+    /**
+     * Highlights to emphasise in the content
+     */
+    highlights?: string[];
+    /**
+     * Whether to include the `dir="auto"` attribute on the rendered element
+     */
+    includeDir?: boolean;
+    ref?: Ref<HTMLElement>;
+}
+
+/**
+ * Component to render a Matrix event's content body.
+ * If the content is formatted HTML then it will be sanitised before rendering.
+ * A number of rendering features are supported as configured by {@link ReplacerOptions}
+ * Returns a div or span depending on `as`, the `dir` on a `div` is always set to `"auto"` but set by `includeDir` otherwise.
+ */
+const EventContentBody = memo(
+    ({ as, mxEvent, stripReply, content, linkify, highlights, includeDir = true, ref, ...options }: Props) => {
+        const enableBigEmoji = useSettingValue("TextualBody.enableBigEmoji");
+        const [mediaIsVisible] = useMediaVisible(mxEvent?.getId(), mxEvent?.getRoomId());
+
+        const replacer = useReplacer(content, mxEvent, options);
+        const linkifyOptions = useMemo(
+            () => ({
+                render: replacerToRenderFunction(replacer),
+            }),
+            [replacer],
+        );
+
+        const isEmote = content.msgtype === MsgType.Emote;
+
+        const { strippedBody, formattedBody, emojiBodyElements, className } = useMemo(
+            () =>
+                bodyToNode(content, highlights, {
+                    disableBigEmoji: isEmote || !enableBigEmoji,
+                    // Part of Replies fallback support
+                    stripReplyFallback: stripReply,
+                    mediaIsVisible,
+                }),
+            [content, mediaIsVisible, enableBigEmoji, highlights, isEmote, stripReply],
+        );
+
+        if (as === "div") includeDir = true; // force dir="auto" on divs
+
+        const As = as;
+        const body = formattedBody ? (
+            <As ref={ref as any} className={className} dir={includeDir ? "auto" : undefined}>
+                {parse(formattedBody, {
+                    replace: replacer,
+                })}
+            </As>
+        ) : (
+            <As ref={ref as any} className={className} dir={includeDir ? "auto" : undefined}>
+                {applyReplacerOnString(emojiBodyElements || strippedBody, replacer)}
+            </As>
+        );
+
+        if (!linkify) return body;
+
+        return <Linkify options={linkifyOptions}>{body}</Linkify>;
+    },
+);
+
+export default EventContentBody;
diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx
index 4ba159089f9238071b404df633babe74a281e184..4569115c0d03aee644fc6ad9d8cf29bf56127c27 100644
--- a/src/components/views/messages/EventTileBubble.tsx
+++ b/src/components/views/messages/EventTileBubble.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, ReactNode, ReactChild } from "react";
+import React, { type JSX, type ReactNode, type Ref } from "react";
 import classNames from "classnames";
 
 interface IProps {
@@ -14,20 +14,19 @@ interface IProps {
     title: string;
     timestamp?: JSX.Element;
     subtitle?: ReactNode;
-    children?: ReactChild;
+    children?: JSX.Element;
+    ref?: Ref<HTMLDivElement>;
 }
 
-const EventTileBubble = forwardRef<HTMLDivElement, IProps>(
-    ({ className, title, timestamp, subtitle, children }, ref) => {
-        return (
-            <div className={classNames("mx_EventTileBubble", className)} ref={ref}>
-                <div className="mx_EventTileBubble_title">{title}</div>
-                {subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
-                {children}
-                {timestamp}
-            </div>
-        );
-    },
-);
+const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
+    return (
+        <div className={classNames("mx_EventTileBubble", className)} ref={ref}>
+            <div className="mx_EventTileBubble_title">{title}</div>
+            {subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
+            {children}
+            {timestamp}
+        </div>
+    );
+};
 
 export default EventTileBubble;
diff --git a/src/components/views/messages/HiddenBody.tsx b/src/components/views/messages/HiddenBody.tsx
index 5485d2fd6ec863f90b976dc9a686281f7475c0e8..20410017be13ce55ce807bc32597524a578918ef 100644
--- a/src/components/views/messages/HiddenBody.tsx
+++ b/src/components/views/messages/HiddenBody.tsx
@@ -6,15 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
 
 import { _t } from "../../../languageHandler";
-import { IBodyProps } from "./IBodyProps";
-
-interface IProps {
-    mxEvent: MatrixEvent;
-}
+import { type IBodyProps } from "./IBodyProps";
 
 /**
  * A message hidden from the user pending moderation.
@@ -22,7 +17,7 @@ interface IProps {
  * Note: This component must not be used when the user is the author of the message
  * or has a sufficient powerlevel to see the message.
  */
-const HiddenBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref) => {
+const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
     let text;
     const visibility = mxEvent.messageVisibility();
     switch (visibility.visible) {
@@ -42,6 +37,6 @@ const HiddenBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref)
             {text}
         </span>
     );
-});
+};
 
 export default HiddenBody;
diff --git a/src/components/views/messages/HiddenMediaPlaceholder.tsx b/src/components/views/messages/HiddenMediaPlaceholder.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..16367ee05a33bcc34f680238ec2bb4abcebba87a
--- /dev/null
+++ b/src/components/views/messages/HiddenMediaPlaceholder.tsx
@@ -0,0 +1,24 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type PropsWithChildren, type MouseEventHandler } from "react";
+import { VisibilityOnIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+
+interface IProps {
+    onClick: MouseEventHandler<HTMLButtonElement>;
+}
+
+export const HiddenMediaPlaceholder: React.FunctionComponent<PropsWithChildren<IProps>> = ({ onClick, children }) => {
+    return (
+        <button onClick={onClick} className="mx_HiddenMediaPlaceholder">
+            <div>
+                <VisibilityOnIcon />
+                <span>{children}</span>
+            </div>
+        </button>
+    );
+};
diff --git a/src/components/views/messages/HideActionButton.tsx b/src/components/views/messages/HideActionButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0c9817b2a6f8b4d19c5ac499b3845a988dffc8c5
--- /dev/null
+++ b/src/components/views/messages/HideActionButton.tsx
@@ -0,0 +1,44 @@
+/*
+Copyright 2024, 2025 New Vector Ltd.
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React from "react";
+import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+
+import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
+import { _t } from "../../../languageHandler";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
+
+interface IProps {
+    /**
+     * Matrix event that this action applies to.
+     */
+    mxEvent: MatrixEvent;
+}
+
+/**
+ * Quick action button for marking a media event as hidden.
+ */
+export const HideActionButton: React.FC<IProps> = ({ mxEvent }) => {
+    const [mediaIsVisible, setVisible] = useMediaVisible(mxEvent.getId(), mxEvent.getRoomId());
+
+    if (!mediaIsVisible) {
+        return;
+    }
+
+    return (
+        <RovingAccessibleButton
+            className="mx_MessageActionBar_iconButton "
+            title={_t("action|hide")}
+            onClick={() => setVisible(false)}
+            placement="left"
+        >
+            <VisibilityOffIcon />
+        </RovingAccessibleButton>
+    );
+};
diff --git a/src/components/views/messages/IBodyProps.ts b/src/components/views/messages/IBodyProps.ts
index c8f6820e23bbe7a2a52017b69105a29085d805b0..37aae37de6d5f3901b4b0d8d5d2bbb9d9d179283 100644
--- a/src/components/views/messages/IBodyProps.ts
+++ b/src/components/views/messages/IBodyProps.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { LegacyRef } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { MediaEventHelper } from "../../../utils/MediaEventHelper";
-import EditorStateTransfer from "../../../utils/EditorStateTransfer";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import type React from "react";
+import { type MediaEventHelper } from "../../../utils/MediaEventHelper";
+import type EditorStateTransfer from "../../../utils/EditorStateTransfer";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 
 export interface IBodyProps {
     mxEvent: MatrixEvent;
@@ -23,9 +23,6 @@ export interface IBodyProps {
     /* link URL for the highlights */
     highlightLink?: string;
 
-    /* callback called when dynamic content in events are loaded */
-    onHeightChanged?: () => void;
-
     showUrlPreview?: boolean;
     forExport?: boolean;
     maxImageHeight?: number;
@@ -46,7 +43,7 @@ export interface IBodyProps {
     // helper function to access relations for this event
     getRelationsForEvent?: GetRelationsForEvent;
 
-    ref?: React.RefObject<any> | LegacyRef<any>;
+    ref?: React.RefObject<any>;
 
     // Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order.
     // This may be useful when displaying a preview of the event.
diff --git a/src/components/views/messages/IMediaBody.ts b/src/components/views/messages/IMediaBody.ts
index 4025f4e6316c4e6e6dd6495d5490ecc789d39388..4bf55403e710d2213200dc793ecefd74c3bb08d4 100644
--- a/src/components/views/messages/IMediaBody.ts
+++ b/src/components/views/messages/IMediaBody.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MediaEventHelper } from "../../../utils/MediaEventHelper";
+import { type MediaEventHelper } from "../../../utils/MediaEventHelper";
 
 export interface IMediaBody {
     getMediaHelper(): MediaEventHelper | undefined;
diff --git a/src/components/views/messages/JumpToDatePicker.tsx b/src/components/views/messages/JumpToDatePicker.tsx
index 28814d1f77b7266a3b0732e5a8a4a9f7a625192a..7f196cfe829e373bf3e54a57626b044777de2ec1 100644
--- a/src/components/views/messages/JumpToDatePicker.tsx
+++ b/src/components/views/messages/JumpToDatePicker.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState, FormEvent } from "react";
+import React, { useState, type FormEvent } from "react";
 
 import { _t } from "../../../languageHandler";
 import Field from "../elements/Field";
diff --git a/src/components/views/messages/LegacyCallEvent.tsx b/src/components/views/messages/LegacyCallEvent.tsx
index fe52a83fb7732b463a1da519d2d38ad871176948..da41a56c02fb8bbfaff8b5437f44c703f639fe06 100644
--- a/src/components/views/messages/LegacyCallEvent.tsx
+++ b/src/components/views/messages/LegacyCallEvent.tsx
@@ -6,14 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, createRef } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { CallErrorCode, CallState } from "matrix-js-sdk/src/webrtc/call";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
 import MemberAvatar from "../avatars/MemberAvatar";
-import LegacyCallEventGrouper, { LegacyCallEventGrouperEvent } from "../../structures/LegacyCallEventGrouper";
+import type LegacyCallEventGrouper from "../../structures/LegacyCallEventGrouper";
+import { LegacyCallEventGrouperEvent } from "../../structures/LegacyCallEventGrouper";
 import AccessibleButton from "../elements/AccessibleButton";
 import InfoTooltip, { InfoTooltipKind } from "../elements/InfoTooltip";
 import { formatPreciseDuration } from "../../../DateUtils";
diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx
index 9c2bc9583b6621eed5a6608777fcd67eef41f601..54003d6da9841cb83d8a068c89514739ef83da0e 100644
--- a/src/components/views/messages/MAudioBody.tsx
+++ b/src/components/views/messages/MAudioBody.tsx
@@ -8,15 +8,15 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { IContent } from "matrix-js-sdk/src/matrix";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import { type IContent } from "matrix-js-sdk/src/matrix";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 
-import { Playback } from "../../../audio/Playback";
+import { type Playback } from "../../../audio/Playback";
 import InlineSpinner from "../elements/InlineSpinner";
 import { _t } from "../../../languageHandler";
 import AudioPlayer from "../audio_messages/AudioPlayer";
 import MFileBody from "./MFileBody";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { PlaybackManager } from "../../../audio/PlaybackManager";
 import { isVoiceMessage } from "../../../utils/EventUtils";
 import { PlaybackQueue } from "../../../audio/PlaybackQueue";
diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx
index 43f7bbd7d34d8d2514b53c2887b344647fb8a439..48294296beded3d186e3bda92013a960aec9e7cb 100644
--- a/src/components/views/messages/MBeaconBody.tsx
+++ b/src/components/views/messages/MBeaconBody.tsx
@@ -6,19 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardRefExoticComponent, useCallback, useContext, useEffect, useState } from "react";
+import React, { type JSX, useCallback, useContext, useEffect, useState } from "react";
 import {
-    Beacon,
+    type Beacon,
     BeaconEvent,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
-    MatrixClient,
+    type MatrixClient,
     RelationType,
-    IRedactOpts,
-    ContentHelpers,
+    type IRedactOpts,
+    type ContentHelpers,
     M_BEACON,
 } from "matrix-js-sdk/src/matrix";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 import classNames from "classnames";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -33,9 +33,9 @@ import OwnBeaconStatus from "../beacon/OwnBeaconStatus";
 import { Map, SmartMarker } from "../location";
 import { MapError } from "../location/MapError";
 import MapFallback from "../location/MapFallback";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 import { BeaconViewDialog } from "../beacon";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 
 const useBeaconState = (
     beaconInfoEvent: MatrixEvent,
@@ -81,10 +81,10 @@ const useBeaconState = (
 // eg thread and main timeline, reply
 // maplibregl needs a unique id to attach the map instance to
 const useUniqueId = (eventId: string): string => {
-    const [id, setId] = useState(`${eventId}_${randomString(8)}`);
+    const [id, setId] = useState(`${eventId}_${secureRandomString(8)}`);
 
     useEffect(() => {
-        setId(`${eventId}_${randomString(8)}`);
+        setId(`${eventId}_${secureRandomString(8)}`);
     }, [eventId]);
 
     return id;
@@ -122,7 +122,7 @@ const useHandleBeaconRedaction = (
     }, [event, onBeforeBeaconInfoRedaction]);
 };
 
-const MBeaconBody = React.forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, getRelationsForEvent }, ref) => {
+const MBeaconBody = ({ mxEvent, getRelationsForEvent, ref }: IBodyProps): JSX.Element => {
     const { beacon, isLive, latestLocationState, waitingToStart } = useBeaconState(mxEvent);
     const mapId = useUniqueId(mxEvent.getId()!);
 
@@ -225,6 +225,6 @@ const MBeaconBody = React.forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, get
             )}
         </div>
     );
-}) as ForwardRefExoticComponent<IBodyProps>;
+};
 
 export default MBeaconBody;
diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx
index 20a27b9744ed70bd02b561d900122720ecbadeaf..24b141599e4a8d1f91dcef3337a8640a63a29289 100644
--- a/src/components/views/messages/MFileBody.tsx
+++ b/src/components/views/messages/MFileBody.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { AllHTMLAttributes, createRef } from "react";
+import React, { type AllHTMLAttributes, createRef } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 import { Button } from "@vector-im/compound-web";
 import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
@@ -18,7 +18,7 @@ import AccessibleButton from "../elements/AccessibleButton";
 import { mediaFromContent } from "../../../customisations/Media";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import { downloadLabelForFile, presentableTextForFile } from "../../../utils/FileUtils";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { FileDownloader } from "../../../utils/FileDownloader";
 import TextWithTooltip from "../elements/TextWithTooltip";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
@@ -93,7 +93,7 @@ export function computedStyle(element: HTMLElement | null): string {
 
 interface IProps extends IBodyProps {
     /* whether or not to show the default placeholder for the file. Defaults to true. */
-    showGenericPlaceholder: boolean;
+    showGenericPlaceholder?: boolean;
 }
 
 interface IState {
@@ -105,13 +105,8 @@ export default class MFileBody extends React.Component<IProps, IState> {
     declare public context: React.ContextType<typeof RoomContext>;
 
     public state: IState = {};
-
-    public static defaultProps = {
-        showGenericPlaceholder: true,
-    };
-
-    private iframe: React.RefObject<HTMLIFrameElement> = createRef();
-    private dummyLink: React.RefObject<HTMLAnchorElement> = createRef();
+    private iframe = createRef<HTMLIFrameElement>();
+    private dummyLink = createRef<HTMLAnchorElement>();
     private userDidClick = false;
     private fileDownloader: FileDownloader = new FileDownloader(() => this.iframe.current);
 
@@ -125,7 +120,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
     }
 
     private get fileName(): string {
-        return this.content.body && this.content.body.length > 0 ? this.content.body : _t("common|attachment");
+        return this.props.mediaEventHelper?.fileName || _t("common|attachment");
     }
 
     private get linkText(): string {
@@ -147,12 +142,6 @@ export default class MFileBody extends React.Component<IProps, IState> {
         });
     }
 
-    public componentDidUpdate(prevProps: IProps, prevState: IState): void {
-        if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
-            this.props.onHeightChanged();
-        }
-    }
-
     private decryptFile = async (): Promise<void> => {
         if (this.state.decryptedBlob) {
             return;
@@ -191,15 +180,17 @@ export default class MFileBody extends React.Component<IProps, IState> {
         const contentUrl = this.getContentUrl();
         const contentFileSize = this.content.info ? this.content.info.size : null;
         const fileType = this.content.info?.mimetype ?? "application/octet-stream";
+        // defaultProps breaks types on IBodyProps, so instead define the default here.
+        const showGenericPlaceholder = this.props.showGenericPlaceholder ?? true;
 
         let showDownloadLink =
-            !this.props.showGenericPlaceholder ||
+            !showGenericPlaceholder ||
             (this.context.timelineRenderingType !== TimelineRenderingType.Room &&
                 this.context.timelineRenderingType !== TimelineRenderingType.Search &&
                 this.context.timelineRenderingType !== TimelineRenderingType.Pinned);
 
         let placeholder: React.ReactNode = null;
-        if (this.props.showGenericPlaceholder) {
+        if (showGenericPlaceholder) {
             placeholder = (
                 <AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}>
                     <span className="mx_MFileBody_info_icon" />
diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx
index 86bc59fdbd14fde9d47349f072e382b75104d883..79f840ce39c57424d35c21cb84ef31b680510f82 100644
--- a/src/components/views/messages/MImageBody.tsx
+++ b/src/components/views/messages/MImageBody.tsx
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, createRef, ReactNode } from "react";
+import React, { type JSX, type ComponentProps, createRef, type ReactNode } from "react";
 import { Blurhash } from "react-blurhash";
 import classNames from "classnames";
 import { CSSTransition, SwitchTransition } from "react-transition-group";
 import { logger } from "matrix-js-sdk/src/logger";
 import { ClientEvent } from "matrix-js-sdk/src/matrix";
-import { ImageContent } from "matrix-js-sdk/src/types";
+import { type ImageContent } from "matrix-js-sdk/src/types";
 import { Tooltip } from "@vector-im/compound-web";
 
 import MFileBody from "./MFileBody";
@@ -21,11 +21,11 @@ import Modal from "../../../Modal";
 import { _t } from "../../../languageHandler";
 import SettingsStore from "../../../settings/SettingsStore";
 import Spinner from "../elements/Spinner";
-import { Media, mediaFromContent } from "../../../customisations/Media";
+import { type Media, mediaFromContent } from "../../../customisations/Media";
 import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media";
 import ImageView from "../elements/ImageView";
-import { IBodyProps } from "./IBodyProps";
-import { ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
+import { type IBodyProps } from "./IBodyProps";
+import { type ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import { blobIsAnimated, mayBeAnimated } from "../../../utils/Image";
@@ -33,6 +33,8 @@ import { presentableTextForFile } from "../../../utils/FileUtils";
 import { createReconnectedListener } from "../../../utils/connection";
 import MediaProcessingError from "./shared/MediaProcessingError";
 import { DecryptError, DownloadError } from "../../../utils/DecryptFile";
+import { HiddenMediaPlaceholder } from "./HiddenMediaPlaceholder";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
 
 enum Placeholder {
     NoImage,
@@ -52,11 +54,25 @@ interface IState {
     };
     hover: boolean;
     focus: boolean;
-    showImage: boolean;
     placeholder: Placeholder;
 }
 
-export default class MImageBody extends React.Component<IBodyProps, IState> {
+interface IProps extends IBodyProps {
+    /**
+     * Should the media be behind a preview.
+     */
+    mediaVisible: boolean;
+    /**
+     * Set the visibility of the media event.
+     * @param visible Should the event be visible.
+     */
+    setMediaVisible: (visible: boolean) => void;
+}
+
+/**
+ * @private Only use for inheritance. Use the default export for presentation.
+ */
+export class MImageBodyInner extends React.Component<IProps, IState> {
     public static contextType = RoomContext;
     declare public context: React.ContextType<typeof RoomContext>;
 
@@ -73,21 +89,14 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
         imgLoaded: false,
         hover: false,
         focus: false,
-        showImage: SettingsStore.getValue("showImages"),
         placeholder: Placeholder.NoImage,
     };
 
-    protected showImage(): void {
-        localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true");
-        this.setState({ showImage: true });
-        this.downloadImage();
-    }
-
     protected onClick = (ev: React.MouseEvent): void => {
         if (ev.button === 0 && !ev.metaKey) {
             ev.preventDefault();
-            if (!this.state.showImage) {
-                this.showImage();
+            if (!this.props.mediaVisible) {
+                this.props.setMediaVisible(true);
                 return;
             }
 
@@ -125,7 +134,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
     private get shouldAutoplay(): boolean {
         return !(
             !this.state.contentUrl ||
-            !this.state.showImage ||
+            !this.props.mediaVisible ||
             !this.state.isAnimated ||
             SettingsStore.getValue("autoplayGifs")
         );
@@ -170,7 +179,6 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
 
     private onImageLoad = (): void => {
         this.clearBlurhashTimeout();
-        this.props.onHeightChanged?.();
 
         let loadedImageDimensions: IState["loadedImageDimensions"];
 
@@ -346,14 +354,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
     public componentDidMount(): void {
         this.unmounted = false;
 
-        const showImage =
-            this.state.showImage || localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true";
-
-        if (showImage) {
+        if (this.props.mediaVisible) {
             // noinspection JSIgnoredPromiseFromCall
             this.downloadImage();
-            this.setState({ showImage: true });
-        } // else don't download anything because we don't want to display anything.
+        }
 
         // Add a 150ms timer for blurhash to first appear.
         if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) {
@@ -372,6 +376,13 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
         });
     }
 
+    public componentDidUpdate(prevProps: Readonly<IProps>): void {
+        if (!prevProps.mediaVisible && this.props.mediaVisible) {
+            // noinspection JSIgnoredPromiseFromCall
+            this.downloadImage();
+        }
+    }
+
     public componentWillUnmount(): void {
         this.unmounted = true;
         MatrixClientPeg.get()?.off(ClientEvent.Sync, this.reconnectedListener);
@@ -425,8 +436,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
             // by the same width and height logic below.
             if (!this.state.loadedImageDimensions) {
                 let imageElement: JSX.Element;
-                if (!this.state.showImage) {
-                    imageElement = <HiddenImagePlaceholder />;
+                if (!this.props.mediaVisible) {
+                    imageElement = (
+                        <HiddenMediaPlaceholder onClick={this.onClick}>
+                            {_t("timeline|m.image|show_image")}
+                        </HiddenMediaPlaceholder>
+                    );
                 } else {
                     imageElement = (
                         <img
@@ -495,8 +510,14 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
             );
         }
 
-        if (!this.state.showImage) {
-            img = <HiddenImagePlaceholder maxWidth={maxWidth} />;
+        if (!this.props.mediaVisible) {
+            img = (
+                <div style={{ width: maxWidth, height: maxHeight }}>
+                    <HiddenMediaPlaceholder onClick={this.onClick}>
+                        {_t("timeline|m.image|show_image")}
+                    </HiddenMediaPlaceholder>
+                </div>
+            );
             showPlaceholder = false; // because we're hiding the image, so don't show the placeholder.
         }
 
@@ -506,7 +527,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
         }
 
         let banner: ReactNode | undefined;
-        if (this.state.showImage && hoverOrFocus) {
+        if (this.props.mediaVisible && hoverOrFocus) {
             banner = this.getBanner(content);
         }
 
@@ -552,7 +573,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
                 </div>
 
                 {/* HACK: This div fills out space while the image loads, to prevent scroll jumps */}
-                {!this.props.forExport && !this.state.imgLoaded && (
+                {!this.props.forExport && !this.state.imgLoaded && !placeholder && (
                     <div style={{ height: maxHeight, width: maxWidth }} />
                 )}
             </div>
@@ -585,12 +606,6 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
                     {children}
                 </a>
             );
-        } else if (!this.state.showImage) {
-            return (
-                <div role="button" onClick={this.onClick}>
-                    {children}
-                </div>
-            );
         }
         return children;
     }
@@ -669,20 +684,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
     }
 }
 
-interface PlaceholderIProps {
-    maxWidth?: number;
-}
+// Wrap MImageBody component so we can use a hook here.
+const MImageBody: React.FC<IBodyProps> = (props) => {
+    const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
+    return <MImageBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
+};
 
-export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProps> {
-    public render(): React.ReactNode {
-        const maxWidth = this.props.maxWidth ? this.props.maxWidth + "px" : null;
-        return (
-            <div className="mx_HiddenImagePlaceholder" style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
-                <div className="mx_HiddenImagePlaceholder_button">
-                    <span className="mx_HiddenImagePlaceholder_eye" />
-                    <span>{_t("timeline|m.image|show_image")}</span>
-                </div>
-            </div>
-        );
-    }
-}
+export default MImageBody;
diff --git a/src/components/views/messages/MImageReplyBody.tsx b/src/components/views/messages/MImageReplyBody.tsx
index 4ba5fc23f26bb902ba568cf6e9663faa09016bd4..b73f8f77c34a0ce7aad98bfce131cac622961ae0 100644
--- a/src/components/views/messages/MImageReplyBody.tsx
+++ b/src/components/views/messages/MImageReplyBody.tsx
@@ -6,14 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { ImageContent } from "matrix-js-sdk/src/types";
+import React, { type JSX } from "react";
+import { type ImageContent } from "matrix-js-sdk/src/types";
 
-import MImageBody from "./MImageBody";
+import { MImageBodyInner } from "./MImageBody";
+import { type IBodyProps } from "./IBodyProps";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
 
 const FORCED_IMAGE_HEIGHT = 44;
 
-export default class MImageReplyBody extends MImageBody {
+class MImageReplyBodyInner extends MImageBodyInner {
     public onClick = (ev: React.MouseEvent): void => {
         ev.preventDefault();
     };
@@ -35,3 +37,9 @@ export default class MImageReplyBody extends MImageBody {
         return <div className="mx_MImageReplyBody">{thumbnail}</div>;
     }
 }
+const MImageReplyBody: React.FC<IBodyProps> = (props) => {
+    const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
+    return <MImageReplyBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
+};
+
+export default MImageReplyBody;
diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx
index 2b785b60b4456a08fbcdddd26991592c43995e08..4bd85f2176610cb25ce6cf7039c9ff503791fc8d 100644
--- a/src/components/views/messages/MJitsiWidgetEvent.tsx
+++ b/src/components/views/messages/MJitsiWidgetEvent.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import WidgetStore from "../../../stores/WidgetStore";
diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx
index fb929f79a2efe895056f00e0d1268d9d1f59a096..8f1f8a6962a298c8027cbfc21651e1165d4661bc 100644
--- a/src/components/views/messages/MKeyVerificationRequest.tsx
+++ b/src/components/views/messages/MKeyVerificationRequest.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx
index 006b35bb9b554e392be54d9ccf17979337b07ea7..abf7e0f507f1d9ec250408d898f630105a73ee72 100644
--- a/src/components/views/messages/MLocationBody.tsx
+++ b/src/components/views/messages/MLocationBody.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { type MatrixEvent, ClientEvent, type ClientEventHandlerMap } from "matrix-js-sdk/src/matrix";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { _t } from "../../../languageHandler";
@@ -21,7 +21,7 @@ import {
 } from "../../../utils/location";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { SmartMarker, Map, LocationViewDialog } from "../location";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { createReconnectedListener } from "../../../utils/connection";
 
 interface IState {
@@ -36,12 +36,12 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
     private mapId: string;
     private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync];
 
-    public constructor(props: IBodyProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IBodyProps) {
+        super(props);
 
         // multiple instances of same map might be in document
         // eg thread and main timeline, reply
-        const idSuffix = `${props.mxEvent.getId()}_${randomString(8)}`;
+        const idSuffix = `${props.mxEvent.getId()}_${secureRandomString(8)}`;
         this.mapId = `mx_MLocationBody_${idSuffix}`;
 
         this.reconnectedListener = createReconnectedListener(this.clearError);
diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx
index 17c7a577df289904ab381feee3241143e43b4717..73fb82083a1654f52a89be12ba68534a1c92aa91 100644
--- a/src/components/views/messages/MPollBody.tsx
+++ b/src/components/views/messages/MPollBody.tsx
@@ -6,30 +6,30 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import {
-    MatrixEvent,
-    MatrixClient,
-    Relations,
-    Poll,
+    type MatrixEvent,
+    type MatrixClient,
+    type Relations,
+    type Poll,
     PollEvent,
     M_POLL_KIND_DISCLOSED,
     M_POLL_RESPONSE,
     M_POLL_START,
-    TimelineEvents,
+    type TimelineEvents,
 } from "matrix-js-sdk/src/matrix";
 import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
-import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
+import { type PollStartEvent, type PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
 
 import { _t } from "../../../languageHandler";
 import Modal from "../../../Modal";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { formatList } from "../../../utils/FormattingUtils";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import ErrorDialog from "../dialogs/ErrorDialog";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 import PollCreateDialog from "../elements/PollCreateDialog";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import Spinner from "../elements/Spinner";
@@ -142,8 +142,8 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
     declare public context: React.ContextType<typeof MatrixClientContext>;
     private seenEventIds: string[] = []; // Events we have already seen
 
-    public constructor(props: IBodyProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IBodyProps) {
+        super(props);
 
         this.state = {
             selected: null,
diff --git a/src/components/views/messages/MPollEndBody.tsx b/src/components/views/messages/MPollEndBody.tsx
index 7df0e8ad581ee97c62b682c82c4436c96a3c9656..95f8a53f2aef434cfbc7586727a48a3014634ffe 100644
--- a/src/components/views/messages/MPollEndBody.tsx
+++ b/src/components/views/messages/MPollEndBody.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useEffect, useState, useContext, ForwardRefExoticComponent } from "react";
+import React, { useEffect, useState, useContext, type JSX } from "react";
 import { MatrixEvent, M_TEXT } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -15,7 +15,7 @@ import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/M
 import { _t } from "../../../languageHandler";
 import { textForEvent } from "../../../TextForEvent";
 import { Caption } from "../typography/Caption";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import MPollBody from "./MPollBody";
 
 const getRelatedPollStartEventId = (event: MatrixEvent): string | undefined => {
@@ -85,7 +85,7 @@ const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent;
     return { pollStartEvent, isLoadingPollStartEvent };
 };
 
-export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...props }, ref) => {
+export const MPollEndBody = ({ mxEvent, ref, ...props }: IBodyProps): JSX.Element => {
     const cli = useMatrixClientContext();
     const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
 
@@ -105,4 +105,4 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
             <MPollBody mxEvent={pollStartEvent} {...props} />
         </div>
     );
-}) as ForwardRefExoticComponent<IBodyProps>;
+};
diff --git a/src/components/views/messages/MStickerBody.tsx b/src/components/views/messages/MStickerBody.tsx
index 0864ecfcfeb8e189d0ee768c6a1452ecabba1d9d..3a922d35aa3a8c57d2547f935a277159d146ef88 100644
--- a/src/components/views/messages/MStickerBody.tsx
+++ b/src/components/views/messages/MStickerBody.tsx
@@ -5,20 +5,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, ReactNode } from "react";
-import { Tooltip } from "@vector-im/compound-web";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import React, { type JSX, type ComponentProps, type ReactNode } from "react";
+import { type Tooltip } from "@vector-im/compound-web";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 
-import MImageBody from "./MImageBody";
+import { MImageBodyInner } from "./MImageBody";
 import { BLURHASH_FIELD } from "../../../utils/image-media";
 import IconsShowStickersSvg from "../../../../res/img/icons-show-stickers.svg";
+import { type IBodyProps } from "./IBodyProps";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
 
-export default class MStickerBody extends MImageBody {
+class MStickerBodyInner extends MImageBodyInner {
     // Mostly empty to prevent default behaviour of MImageBody
     protected onClick = (ev: React.MouseEvent): void => {
         ev.preventDefault();
-        if (!this.state.showImage) {
-            this.showImage();
+        if (!this.props.mediaVisible) {
+            this.props.setMediaVisible?.(true);
         }
     };
 
@@ -26,7 +28,7 @@ export default class MStickerBody extends MImageBody {
     // which is added by mx_MStickerBody_wrapper
     protected wrapImage(contentUrl: string, children: React.ReactNode): JSX.Element {
         let onClick: React.MouseEventHandler | undefined;
-        if (!this.state.showImage) {
+        if (!this.props.mediaVisible) {
             onClick = this.onClick;
         }
         return (
@@ -75,3 +77,10 @@ export default class MStickerBody extends MImageBody {
         return null; // we don't need a banner, we have a tooltip
     }
 }
+
+const MStickerBody: React.FC<IBodyProps> = (props) => {
+    const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
+    return <MStickerBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
+};
+
+export default MStickerBody;
diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx
index c513925acd8269e109a8dd6328d0bb505efa44b1..6a36dae6a8eee8b5e78e6cebb604443906516941 100644
--- a/src/components/views/messages/MVideoBody.tsx
+++ b/src/components/views/messages/MVideoBody.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { decode } from "blurhash";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
@@ -16,11 +16,13 @@ import SettingsStore from "../../../settings/SettingsStore";
 import InlineSpinner from "../elements/InlineSpinner";
 import { mediaFromContent } from "../../../customisations/Media";
 import { BLURHASH_FIELD } from "../../../utils/image-media";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import MFileBody from "./MFileBody";
-import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
+import { type ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import MediaProcessingError from "./shared/MediaProcessingError";
+import { HiddenMediaPlaceholder } from "./HiddenMediaPlaceholder";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
 
 interface IState {
     decryptedUrl: string | null;
@@ -32,7 +34,19 @@ interface IState {
     blurhashUrl: string | null;
 }
 
-export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
+interface IProps extends IBodyProps {
+    /**
+     * Should the media be behind a preview.
+     */
+    mediaVisible: boolean;
+    /**
+     * Set the visibility of the media event.
+     * @param visible Should the event be visible.
+     */
+    setMediaVisible: (visible: boolean) => void;
+}
+
+class MVideoBodyInner extends React.PureComponent<IProps, IState> {
     public static contextType = RoomContext;
     declare public context: React.ContextType<typeof RoomContext>;
 
@@ -49,6 +63,10 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
         blurhashUrl: null,
     };
 
+    private onClick = (): void => {
+        this.props.setMediaVisible(true);
+    };
+
     private getContentUrl(): string | undefined {
         const content = this.props.mxEvent.getContent<MediaEventContent>();
         // During export, the content url will point to the MSC, which will later point to a local url
@@ -120,11 +138,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
         }
     }
 
-    public async componentDidMount(): Promise<void> {
-        this.sizeWatcher = SettingsStore.watchSetting("Images.size", null, () => {
-            this.forceUpdate(); // we don't really have a reliable thing to update, so just update the whole thing
-        });
-
+    private async downloadVideo(): Promise<void> {
         try {
             this.loadBlurhash();
         } catch (e) {
@@ -142,7 +156,6 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
                         decryptedThumbnailUrl: thumbnailUrl,
                         decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value,
                     });
-                    this.props.onHeightChanged?.();
                 } else {
                     logger.log("NOT preloading video");
                     const content = this.props.mxEvent.getContent<MediaEventContent>();
@@ -174,6 +187,23 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
         }
     }
 
+    public async componentDidMount(): Promise<void> {
+        this.sizeWatcher = SettingsStore.watchSetting("Images.size", null, () => {
+            this.forceUpdate(); // we don't really have a reliable thing to update, so just update the whole thing
+        });
+
+        // Do not attempt to load the media if we do not want to show previews here.
+        if (this.props.mediaVisible) {
+            await this.downloadVideo();
+        }
+    }
+
+    public async componentDidUpdate(prevProps: Readonly<IProps>): Promise<void> {
+        if (!prevProps.mediaVisible && this.props.mediaVisible) {
+            await this.downloadVideo();
+        }
+    }
+
     public componentWillUnmount(): void {
         SettingsStore.unwatchSetting(this.sizeWatcher);
     }
@@ -204,7 +234,6 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
                 this.videoRef.current.play();
             },
         );
-        this.props.onHeightChanged?.();
     };
 
     protected get showFileBody(): boolean {
@@ -244,6 +273,22 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
             );
         }
 
+        // Users may not even want to show a poster, so instead show a preview button.
+        if (!this.props.mediaVisible) {
+            return (
+                <span className="mx_MVideoBody">
+                    <div
+                        className="mx_MVideoBody_container"
+                        style={{ width: maxWidth, height: maxHeight, aspectRatio }}
+                    >
+                        <HiddenMediaPlaceholder onClick={this.onClick}>
+                            {_t("timeline|m.video|show_video")}
+                        </HiddenMediaPlaceholder>
+                    </div>
+                </span>
+            );
+        }
+
         // Important: If we aren't autoplaying and we haven't decrypted it yet, show a video with a poster.
         if (!this.props.forExport && content.file !== undefined && this.state.decryptedUrl === null && autoplay) {
             // Need to decrypt the attachment
@@ -294,3 +339,11 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
         );
     }
 }
+
+// Wrap MVideoBody component so we can use a hook here.
+const MVideoBody: React.FC<IBodyProps> = (props) => {
+    const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
+    return <MVideoBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
+};
+
+export default MVideoBody;
diff --git a/src/components/views/messages/MVoiceOrAudioBody.tsx b/src/components/views/messages/MVoiceOrAudioBody.tsx
index af7eb6f84a4c40432761dd009d3f3ad5ee1364bd..9347b7bdc78a382cdc0937fe2c2ae8abb5c06359 100644
--- a/src/components/views/messages/MVoiceOrAudioBody.tsx
+++ b/src/components/views/messages/MVoiceOrAudioBody.tsx
@@ -10,7 +10,7 @@ import React from "react";
 
 import MAudioBody from "./MAudioBody";
 import MVoiceMessageBody from "./MVoiceMessageBody";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import { isVoiceMessage } from "../../../utils/EventUtils";
 
 export default class MVoiceOrAudioBody extends React.PureComponent<IBodyProps> {
diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx
index 1476249c2b2af99beaa64f3b03298fa97eb6b16c..eb109028d957944d8bb13c955fcde9a8d8434391 100644
--- a/src/components/views/messages/MessageActionBar.tsx
+++ b/src/components/views/messages/MessageActionBar.tsx
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, useCallback, useContext, useEffect } from "react";
+import React, { type JSX, type ReactElement, useCallback, useContext, useEffect } from "react";
 import {
     EventStatus,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
     MsgType,
     RelationType,
@@ -19,6 +19,7 @@ import {
     EventTimeline,
     RoomStateEvent,
     EventType,
+    type Relations,
 } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 import {
@@ -35,7 +36,6 @@ import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message
 import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg";
 import { Icon as ExpandMessageIcon } from "../../../../res/img/element-icons/expand-message.svg";
 import { Icon as CollapseMessageIcon } from "../../../../res/img/element-icons/collapse-message.svg";
-import type { Relations } from "matrix-js-sdk/src/matrix";
 import { _t } from "../../../languageHandler";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
@@ -48,19 +48,20 @@ import Resend from "../../../Resend";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { MediaEventHelper } from "../../../utils/MediaEventHelper";
 import DownloadActionButton from "./DownloadActionButton";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
-import ReplyChain from "../elements/ReplyChain";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import type ReplyChain from "../elements/ReplyChain";
 import ReactionPicker from "../emojipicker/ReactionPicker";
 import { CardContext } from "../right_panel/context";
 import { shouldDisplayReply } from "../../../utils/Reply";
 import { Key } from "../../../Keyboard";
 import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
 import { Action } from "../../../dispatcher/actions";
-import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
-import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
+import { type GetRelationsForEvent, type IEventTileType } from "../rooms/EventTile";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import PinningUtils from "../../../utils/PinningUtils";
 import PosthogTrackers from "../../../PosthogTrackers.ts";
+import { HideActionButton } from "./HideActionButton.tsx";
 
 interface IOptionsButtonProps {
     mxEvent: MatrixEvent;
@@ -537,6 +538,9 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
                         />,
                     );
                 }
+                if (MediaEventHelper.canHide(this.props.mxEvent)) {
+                    toolbarOpts.splice(0, 0, <HideActionButton mxEvent={this.props.mxEvent} key="hide" />);
+                }
             } else if (
                 // Show thread icon even for deleted messages, but only within main timeline
                 this.context.timelineRenderingType === TimelineRenderingType.Room &&
diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx
index 6fb239069f8c3334c2872fde56dc027d7c6d1dda..231fa7f1fe5f5d8c9f8b6eb65edc8f30b54a7327 100644
--- a/src/components/views/messages/MessageEvent.tsx
+++ b/src/components/views/messages/MessageEvent.tsx
@@ -17,17 +17,16 @@ import {
     M_LOCATION,
     M_POLL_END,
     M_POLL_START,
-    IContent,
+    type IContent,
 } from "matrix-js-sdk/src/matrix";
 
 import SettingsStore from "../../../settings/SettingsStore";
 import { Mjolnir } from "../../../mjolnir/Mjolnir";
 import RedactedBody from "./RedactedBody";
 import UnknownBody from "./UnknownBody";
-import { IMediaBody } from "./IMediaBody";
+import { type IMediaBody } from "./IMediaBody";
 import { MediaEventHelper } from "../../../utils/MediaEventHelper";
-import { IBodyProps } from "./IBodyProps";
-import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import { type IBodyProps } from "./IBodyProps";
 import TextualBody from "./TextualBody";
 import MImageBody from "./MImageBody";
 import MFileBody from "./MFileBody";
@@ -40,13 +39,13 @@ import MLocationBody from "./MLocationBody";
 import MjolnirBody from "./MjolnirBody";
 import MBeaconBody from "./MBeaconBody";
 import { DecryptionFailureBody } from "./DecryptionFailureBody";
-import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
+import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
 
 // onMessageAllowed is handled internally
 interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
     /* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */
-    overrideBodyTypes?: Record<string, typeof React.Component>;
-    overrideEventTypes?: Record<string, typeof React.Component>;
+    overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
+    overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
 
     // helper function to access relations for this event
     getRelationsForEvent?: GetRelationsForEvent;
@@ -58,7 +57,7 @@ export interface IOperableEventTile {
     getEventTileOps(): IEventTileOps | null;
 }
 
-const baseBodyTypes = new Map<string, typeof React.Component>([
+const baseBodyTypes = new Map<string, React.ComponentType<IBodyProps>>([
     [MsgType.Text, TextualBody],
     [MsgType.Notice, TextualBody],
     [MsgType.Emote, TextualBody],
@@ -78,16 +77,13 @@ const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([
 ]);
 
 export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
-    private body: React.RefObject<React.Component | IOperableEventTile> = createRef();
+    private body = createRef<React.Component | IOperableEventTile>();
     private mediaHelper?: MediaEventHelper;
-    private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
+    private bodyTypes = new Map<string, React.ComponentType<IBodyProps>>(baseBodyTypes.entries());
     private evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries());
 
-    public static contextType = MatrixClientContext;
-    declare public context: React.ContextType<typeof MatrixClientContext>;
-
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         if (MediaEventHelper.isEligible(this.props.mxEvent)) {
             this.mediaHelper = new MediaEventHelper(this.props.mxEvent);
@@ -115,7 +111,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
     }
 
     private updateComponentMaps(): void {
-        this.bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
+        this.bodyTypes = new Map<string, React.ComponentType<IBodyProps>>(baseBodyTypes.entries());
         for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) {
             this.bodyTypes.set(bodyType, bodyComponent);
         }
@@ -306,7 +302,6 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
             maxImageHeight: this.props.maxImageHeight,
             replacingEventId: this.props.replacingEventId,
             editState: this.props.editState,
-            onHeightChanged: this.props.onHeightChanged,
             onMessageAllowed: this.onTileUpdate,
             permalinkCreator: this.props.permalinkCreator,
             mediaEventHelper: this.mediaHelper,
diff --git a/src/components/views/messages/MessageTimestamp.tsx b/src/components/views/messages/MessageTimestamp.tsx
index 34c25303ed9bb86eefb1776e5c85a74c53b5d3e9..61c9a316415d6d8e22491f3bfcf9d32e70b47ffb 100644
--- a/src/components/views/messages/MessageTimestamp.tsx
+++ b/src/components/views/messages/MessageTimestamp.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { formatFullDate, formatTime, formatFullTime, formatRelativeTime } from "../../../DateUtils";
diff --git a/src/components/views/messages/MjolnirBody.tsx b/src/components/views/messages/MjolnirBody.tsx
index 20587f879e0bbc0bcf5b0823077fee7fa6c2ebf1..4050e1750dd69d49dedc5b7593807bab93eca489 100644
--- a/src/components/views/messages/MjolnirBody.tsx
+++ b/src/components/views/messages/MjolnirBody.tsx
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
-import { IBodyProps } from "./IBodyProps";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
+import { type IBodyProps } from "./IBodyProps";
 
 export default class MjolnirBody extends React.Component<IBodyProps> {
     private onAllowClick = (e: ButtonEvent): void => {
diff --git a/src/components/views/messages/PinnedMessageBadge.tsx b/src/components/views/messages/PinnedMessageBadge.tsx
index 7f4c132087ff4f1fe8bd855fd92a12dacb79374b..2652d84ed866ebefefc2ebbfeb51f4df83cf7635 100644
--- a/src/components/views/messages/PinnedMessageBadge.tsx
+++ b/src/components/views/messages/PinnedMessageBadge.tsx
@@ -5,7 +5,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX } from "react";
+import React, { type JSX } from "react";
 import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx
index 504bf1b244d60a367d676b77be9ffb1df2a3dc9d..0c5dbdd1e2999582f55985b7e39b5721e64f4ee9 100644
--- a/src/components/views/messages/ReactionsRow.tsx
+++ b/src/components/views/messages/ReactionsRow.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { SyntheticEvent } from "react";
+import React, { type JSX, type SyntheticEvent } from "react";
 import classNames from "classnames";
-import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
 import { uniqBy } from "lodash";
 import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
 
@@ -127,7 +127,6 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
     };
 
     private onReactionsChange = (): void => {
-        // TODO: Call `onHeightChanged` as needed
         this.setState({
             myReactions: this.getMyReactions(),
         });
diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx
index 9e3763e4390c3f1bc3b8f86b6f76bd05a8a0f4e0..8320237b25dfe95de7503d68f79c9a9fdca29131 100644
--- a/src/components/views/messages/ReactionsRowButton.tsx
+++ b/src/components/views/messages/ReactionsRowButton.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import classNames from "classnames";
-import { EventType, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
 
 import { mediaFromMxc } from "../../../customisations/Media";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.tsx b/src/components/views/messages/ReactionsRowButtonTooltip.tsx
index 84fb95f210ea4cfb18c3545f1ba6cc2de08ed61b..f40002deffadc94b9195708f5daa3d020307f689 100644
--- a/src/components/views/messages/ReactionsRowButtonTooltip.tsx
+++ b/src/components/views/messages/ReactionsRowButtonTooltip.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { PropsWithChildren } from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type PropsWithChildren } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { unicodeToShortcode } from "../../../HtmlUtils";
diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx
index dd311c8f5e9c9c7390b52f1cde45489063bdc036..ddad4d2e4d6f917f9b1058ed8d7e374bb2661f2a 100644
--- a/src/components/views/messages/RedactedBody.tsx
+++ b/src/components/views/messages/RedactedBody.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardRefExoticComponent, useContext } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { useContext, type JSX } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { formatFullDate } from "../../../DateUtils";
 import SettingsStore from "../../../settings/SettingsStore";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 
-const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
+const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
     const cli: MatrixClient = useContext(MatrixClientContext);
     let text = _t("timeline|self_redaction");
     const unsigned = mxEvent.getUnsigned();
@@ -37,6 +37,6 @@ const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
             {text}
         </span>
     );
-}) as ForwardRefExoticComponent<IBodyProps>;
+};
 
 export default RedactedBody;
diff --git a/src/components/views/messages/RoomAvatarEvent.tsx b/src/components/views/messages/RoomAvatarEvent.tsx
index 1d3f9172cabd47c31f844ada2a0b861802cfb655..c80c80c836cf4e8f8f766a332e082d2e91b93ab3 100644
--- a/src/components/views/messages/RoomAvatarEvent.tsx
+++ b/src/components/views/messages/RoomAvatarEvent.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
@@ -70,7 +70,7 @@ export default class RoomAvatarEvent extends React.Component<IProps> {
                                 className="mx_RoomAvatarEvent_avatar"
                                 onClick={this.onAvatarClick}
                             >
-                                <RoomAvatar size="14px" oobData={oobData} />
+                                <RoomAvatar room={room ?? undefined} size="14px" oobData={oobData} />
                             </AccessibleButton>
                         ),
                     },
diff --git a/src/components/views/messages/RoomPredecessorTile.tsx b/src/components/views/messages/RoomPredecessorTile.tsx
index 3b2de5d67dd922b70582d87b691ec8facee35c33..e35728e4b850a1fef4ce58c215a80d8228dd14af 100644
--- a/src/components/views/messages/RoomPredecessorTile.tsx
+++ b/src/components/views/messages/RoomPredecessorTile.tsx
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback } from "react";
+import React, { type JSX, useCallback } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixEvent, Room, RoomState } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
@@ -17,7 +17,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import EventTileBubble from "./EventTileBubble";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { useRoomState } from "../../../hooks/useRoomState";
 import SettingsStore from "../../../settings/SettingsStore";
 import MatrixToPermalinkConstructor from "../../../utils/permalinks/MatrixToPermalinkConstructor";
diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx
index 8e99e45e756d7909c5366c67f1d044e01c26f49b..2e51c1b50abe77f07dd019b3fa1bd4793781ee1a 100644
--- a/src/components/views/messages/SenderProfile.tsx
+++ b/src/components/views/messages/SenderProfile.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import React from "react";
-import { MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
 
 import DisambiguatedProfile from "./DisambiguatedProfile";
 import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx
index 5aa8581f507c1870b319abaf0d5247e3e3956b56..d0107b31ecc667f361e34e1e3d4731e4c690bf97 100644
--- a/src/components/views/messages/TextualBody.tsx
+++ b/src/components/views/messages/TextualBody.tsx
@@ -6,36 +6,29 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, SyntheticEvent, MouseEvent, StrictMode } from "react";
+import React, { type JSX, createRef, type SyntheticEvent, type MouseEvent } from "react";
 import { MsgType } from "matrix-js-sdk/src/matrix";
-import { TooltipProvider } from "@vector-im/compound-web";
 
-import * as HtmlUtils from "../../../HtmlUtils";
+import EventContentBody from "./EventContentBody.tsx";
 import { formatDate } from "../../../DateUtils";
 import Modal from "../../../Modal";
 import dis from "../../../dispatcher/dispatcher";
 import { _t } from "../../../languageHandler";
 import SettingsStore from "../../../settings/SettingsStore";
-import { pillifyLinks } from "../../../utils/pillify";
-import { tooltipifyLinks } from "../../../utils/tooltipify";
 import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
 import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks";
 import { Action } from "../../../dispatcher/actions";
-import Spoiler from "../elements/Spoiler";
 import QuestionDialog from "../dialogs/QuestionDialog";
 import MessageEditHistoryDialog from "../dialogs/MessageEditHistoryDialog";
 import EditMessageComposer from "../rooms/EditMessageComposer";
 import LinkPreviewGroup from "../rooms/LinkPreviewGroup";
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 import RoomContext from "../../../contexts/RoomContext";
 import AccessibleButton from "../elements/AccessibleButton";
 import { options as linkifyOpts } from "../../../linkify-matrix";
 import { getParentEventId } from "../../../utils/Reply";
 import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
-import { IEventTileOps } from "../rooms/EventTile";
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import CodeBlock from "./CodeBlock";
-import { ReactRootManager } from "../../../utils/react";
+import { type IEventTileOps } from "../rooms/EventTile";
 
 interface IState {
     // the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
@@ -48,10 +41,6 @@ interface IState {
 export default class TextualBody extends React.Component<IBodyProps, IState> {
     private readonly contentRef = createRef<HTMLDivElement>();
 
-    private pills = new ReactRootManager();
-    private tooltips = new ReactRootManager();
-    private reactRoots = new ReactRootManager();
-
     public static contextType = RoomContext;
     declare public context: React.ContextType<typeof RoomContext>;
 
@@ -67,61 +56,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
     }
 
     private applyFormatting(): void {
-        // Function is only called from render / componentDidMount → contentRef is set
-        const content = this.contentRef.current!;
-
-        this.activateSpoilers([content]);
-
-        HtmlUtils.linkifyElement(content);
-        pillifyLinks(MatrixClientPeg.safeGet(), [content], this.props.mxEvent, this.pills);
-
         this.calculateUrlPreview();
-
-        // tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
-        // container is empty before the internal component has mounted so calculateUrlPreview
-        // won't find any anchors
-        tooltipifyLinks([content], [...this.pills.elements, ...this.reactRoots.elements], this.tooltips);
-
-        if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
-            // Handle expansion and add buttons
-            const pres = [...content.getElementsByTagName("pre")];
-            if (pres && pres.length > 0) {
-                for (let i = 0; i < pres.length; i++) {
-                    // If there already is a div wrapping the codeblock we want to skip this.
-                    // This happens after the codeblock was edited.
-                    if (pres[i].parentElement?.className == "mx_EventTile_pre_container") continue;
-                    // Add code element if it's missing since we depend on it
-                    if (pres[i].getElementsByTagName("code").length == 0) {
-                        this.addCodeElement(pres[i]);
-                    }
-                    // Wrap a div around <pre> so that the copy button can be correctly positioned
-                    // when the <pre> overflows and is scrolled horizontally.
-                    this.wrapPreInReact(pres[i]);
-                }
-            }
-        }
-    }
-
-    private addCodeElement(pre: HTMLPreElement): void {
-        const code = document.createElement("code");
-        code.append(...pre.childNodes);
-        pre.appendChild(code);
-    }
-
-    private wrapPreInReact(pre: HTMLPreElement): void {
-        const root = document.createElement("div");
-        root.className = "mx_EventTile_pre_container";
-
-        // Insert containing div in place of <pre> block
-        pre.replaceWith(root);
-
-        this.reactRoots.render(
-            <StrictMode>
-                <CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>
-            </StrictMode>,
-            root,
-            pre,
-        );
     }
 
     public componentDidUpdate(prevProps: Readonly<IBodyProps>): void {
@@ -135,12 +70,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
         }
     }
 
-    public componentWillUnmount(): void {
-        this.pills.unmount();
-        this.tooltips.unmount();
-        this.reactRoots.unmount();
-    }
-
     public shouldComponentUpdate(nextProps: Readonly<IBodyProps>, nextState: Readonly<IState>): boolean {
         //console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
 
@@ -180,36 +109,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
         }
     }
 
-    private activateSpoilers(nodes: ArrayLike<Element>): void {
-        let node = nodes[0];
-        while (node) {
-            if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
-                const spoilerContainer = document.createElement("span");
-
-                const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
-                node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
-                const spoiler = (
-                    <StrictMode>
-                        <TooltipProvider>
-                            <Spoiler reason={reason} contentHtml={node.outerHTML} />
-                        </TooltipProvider>
-                    </StrictMode>
-                );
-
-                this.reactRoots.render(spoiler, spoilerContainer, node);
-
-                node.replaceWith(spoilerContainer);
-                node = spoilerContainer;
-            }
-
-            if (node.childNodes && node.childNodes.length) {
-                this.activateSpoilers(node.childNodes as NodeListOf<Element>);
-            }
-
-            node = node.nextSibling as Element;
-        }
-    }
-
     private findLinks(nodes: ArrayLike<Element>): string[] {
         let links: string[] = [];
 
@@ -336,7 +235,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
         scalarClient?.connect().then(() => {
             const completeUrl = scalarClient.getStarterLink(starterLink);
             const integrationsUrl = integrationManager!.uiUrl;
-            Modal.createDialog(QuestionDialog, {
+            const { finished } = Modal.createDialog(QuestionDialog, {
                 title: _t("timeline|scalar_starter_link|dialog_title"),
                 description: (
                     <div>
@@ -344,18 +243,19 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
                     </div>
                 ),
                 button: _t("action|continue"),
-                onFinished(confirmed) {
-                    if (!confirmed) {
-                        return;
-                    }
-                    const width = window.screen.width > 1024 ? 1024 : window.screen.width;
-                    const height = window.screen.height > 800 ? 800 : window.screen.height;
-                    const left = (window.screen.width - width) / 2;
-                    const top = (window.screen.height - height) / 2;
-                    const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
-                    const wnd = window.open(completeUrl, "_blank", features)!;
-                    wnd.opener = null;
-                },
+            });
+
+            finished.then(([confirmed]) => {
+                if (!confirmed) {
+                    return;
+                }
+                const width = window.screen.width > 1024 ? 1024 : window.screen.width;
+                const height = window.screen.height > 800 ? 800 : window.screen.height;
+                const left = (window.screen.width - width) / 2;
+                const top = (window.screen.height - height) / 2;
+                const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
+                const wnd = window.open(completeUrl, "_blank", features)!;
+                wnd.opener = null;
             });
         });
     };
@@ -421,18 +321,25 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
 
         const willHaveWrapper =
             this.props.replacingEventId || this.props.isSeeingThroughMessageHiddenForModeration || isEmote;
-
         // only strip reply if this is the original replying event, edits thereafter do not have the fallback
         const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
-
-        const htmlOpts = {
-            disableBigEmoji: isEmote || !SettingsStore.getValue("TextualBody.enableBigEmoji"),
-            // Part of Replies fallback support
-            stripReplyFallback: stripReply,
-        };
-        let body = willHaveWrapper
-            ? HtmlUtils.bodyToSpan(content, this.props.highlights, htmlOpts, this.contentRef, false)
-            : HtmlUtils.bodyToDiv(content, this.props.highlights, htmlOpts, this.contentRef);
+        let body = (
+            <EventContentBody
+                as={willHaveWrapper ? "span" : "div"}
+                includeDir={false}
+                mxEvent={mxEvent}
+                content={content}
+                stripReply={stripReply}
+                linkify
+                highlights={this.props.highlights}
+                ref={this.contentRef}
+                renderTooltipsForAmbiguousLinks
+                renderKeywordPills
+                renderMentionPills
+                renderCodeBlocks
+                renderSpoilers
+            />
+        );
 
         if (this.props.replacingEventId) {
             body = (
@@ -471,7 +378,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
                     links={this.state.links}
                     mxEvent={this.props.mxEvent}
                     onCancelClick={this.onCancelClick}
-                    onHeightChanged={this.props.onHeightChanged}
                 />
             );
         }
diff --git a/src/components/views/messages/TextualEvent.tsx b/src/components/views/messages/TextualEvent.tsx
index 41a8fdf11542c775407cf8666bc9b4a313696ea0..445913bfc6e4434c8e7b71485c29665405745b4b 100644
--- a/src/components/views/messages/TextualEvent.tsx
+++ b/src/components/views/messages/TextualEvent.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
 
 import RoomContext from "../../../contexts/RoomContext";
 import * as TextForEvent from "../../../TextForEvent";
diff --git a/src/components/views/messages/TileErrorBoundary.tsx b/src/components/views/messages/TileErrorBoundary.tsx
index 33d82e538af873b688a89548ac29b5a58e23539f..2ccac0f288bf02f06a291698c420b4286f07f879 100644
--- a/src/components/views/messages/TileErrorBoundary.tsx
+++ b/src/components/views/messages/TileErrorBoundary.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import classNames from "classnames";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import Modal from "../../../Modal";
@@ -17,7 +17,7 @@ import BugReportDialog from "../dialogs/BugReportDialog";
 import AccessibleButton from "../elements/AccessibleButton";
 import SettingsStore from "../../../settings/SettingsStore";
 import ViewSource from "../../structures/ViewSource";
-import { Layout } from "../../../settings/enums/Layout";
+import { type Layout } from "../../../settings/enums/Layout";
 
 interface IProps {
     mxEvent: MatrixEvent;
diff --git a/src/components/views/messages/TimelineSeparator.tsx b/src/components/views/messages/TimelineSeparator.tsx
index f89ba642925d4ecc4b933be5a9747339dd4b059f..4735c8e00c4f8a144819315ae473e383e8d0cdd0 100644
--- a/src/components/views/messages/TimelineSeparator.tsx
+++ b/src/components/views/messages/TimelineSeparator.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 
 interface Props {
     label: string;
diff --git a/src/components/views/messages/UnknownBody.tsx b/src/components/views/messages/UnknownBody.tsx
index 2e804a221c6385c0169080cc9f876019127909cf..26e4ea5fca9df48d9848d263ef139e8e40508e11 100644
--- a/src/components/views/messages/UnknownBody.tsx
+++ b/src/components/views/messages/UnknownBody.tsx
@@ -7,16 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, ForwardRefExoticComponent } from "react";
+import React, { type JSX } from "react";
 
-import { IBodyProps } from "./IBodyProps";
+import { type IBodyProps } from "./IBodyProps";
 
-export default forwardRef<HTMLDivElement, IBodyProps>(({ mxEvent, children }, ref) => {
+export default ({ mxEvent, ref }: IBodyProps): JSX.Element => {
     const text = mxEvent.getContent().body;
     return (
         <div className="mx_UnknownBody" ref={ref}>
             {text}
-            {children}
         </div>
     );
-}) as ForwardRefExoticComponent<IBodyProps>;
+};
diff --git a/src/components/views/messages/ViewSourceEvent.tsx b/src/components/views/messages/ViewSourceEvent.tsx
index 3489921729fb2cec18d24a677ef7e0f51d0615f5..e7ed1f8332f785faf1458237b76c5a9edc33cd4d 100644
--- a/src/components/views/messages/ViewSourceEvent.tsx
+++ b/src/components/views/messages/ViewSourceEvent.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 import { CollapseIcon, ExpandIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IProps {
     mxEvent: MatrixEvent;
diff --git a/src/components/views/pips/WidgetPip.tsx b/src/components/views/pips/WidgetPip.tsx
index 13df6dbfadfd5e3bbee28b3acd98b10ebf462e99..ed49552cf0694b8b7dce1c1f2a98791047d61ba6 100644
--- a/src/components/views/pips/WidgetPip.tsx
+++ b/src/components/views/pips/WidgetPip.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, MutableRefObject, useCallback, useMemo } from "react";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import React, { type FC, type RefObject, useCallback, useMemo } from "react";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import BackIcon from "@vector-im/compound-design-tokens/assets/web/icons/arrow-left";
 
 import PersistentApp from "../elements/PersistentApp";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../dispatcher/actions";
 import { useCallForWidget } from "../../../hooks/useCall";
 import WidgetStore from "../../../stores/WidgetStore";
@@ -26,14 +26,14 @@ import { WidgetType } from "../../../widgets/WidgetType";
 import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
 import WidgetUtils from "../../../utils/WidgetUtils";
 import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface Props {
     widgetId: string;
     room: Room;
     viewingRoom: boolean;
     onStartMoving: (e: React.MouseEvent<Element, MouseEvent>) => void;
-    movePersistedElement: MutableRefObject<(() => void) | undefined>;
+    movePersistedElement: RefObject<(() => void) | null>;
 }
 
 /**
diff --git a/src/components/views/polls/PollOption.tsx b/src/components/views/polls/PollOption.tsx
index 07c6d141bb8335e04e951d353e9d78a82e01fa4e..a8de8fb6ef19f64969f61f472c5feb106009db7b 100644
--- a/src/components/views/polls/PollOption.tsx
+++ b/src/components/views/polls/PollOption.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import classNames from "classnames";
-import { PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
+import { type PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 
 import { _t } from "../../../languageHandler";
 import { Icon as TrophyIcon } from "../../../../res/img/element-icons/trophy.svg";
diff --git a/src/components/views/polls/pollHistory/PollDetail.tsx b/src/components/views/polls/pollHistory/PollDetail.tsx
index 67da1526cc4b471fb0b31bd590e67ccdd242911f..aafa85263c8f7fa40379210de19c6f195ecb96c4 100644
--- a/src/components/views/polls/pollHistory/PollDetail.tsx
+++ b/src/components/views/polls/pollHistory/PollDetail.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Poll } from "matrix-js-sdk/src/matrix";
+import { type Poll } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import dispatcher from "../../../../dispatcher/dispatcher";
 import { Action } from "../../../../dispatcher/actions";
-import { ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
-import { RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../utils/MediaEventHelper";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
+import { type ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
+import { type RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks";
+import { type MediaEventHelper } from "../../../../utils/MediaEventHelper";
+import AccessibleButton, { type ButtonEvent } from "../../elements/AccessibleButton";
 import MPollBody from "../../messages/MPollBody";
 
 interface Props {
@@ -24,8 +24,6 @@ interface Props {
     permalinkCreator: RoomPermalinkCreator;
 }
 
-const NOOP = (): void => {};
-
 /**
  * Content of PollHistory when a specific poll is selected
  */
@@ -56,8 +54,6 @@ export const PollDetail: React.FC<Props> = ({ poll, permalinkCreator, requestMod
             <MPollBody
                 mxEvent={poll.rootEvent}
                 permalinkCreator={permalinkCreator}
-                onHeightChanged={NOOP}
-                onMessageAllowed={NOOP}
                 // MPollBody doesn't use this
                 // and MessageEvent only defines it for eligible events
                 // should be fixed on IBodyProps types
diff --git a/src/components/views/polls/pollHistory/PollDetailHeader.tsx b/src/components/views/polls/pollHistory/PollDetailHeader.tsx
index 429f1740a757c0d16e956dc4b9b90a36b66b0cd5..4f23299af3d161afc1260f171edf08357a6bccba 100644
--- a/src/components/views/polls/pollHistory/PollDetailHeader.tsx
+++ b/src/components/views/polls/pollHistory/PollDetailHeader.tsx
@@ -11,7 +11,7 @@ import LeftCaretIcon from "@vector-im/compound-design-tokens/assets/web/icons/ch
 
 import { _t } from "../../../../languageHandler";
 import AccessibleButton from "../../elements/AccessibleButton";
-import { PollHistoryFilter } from "./types";
+import { type PollHistoryFilter } from "./types";
 
 interface Props {
     filter: PollHistoryFilter;
diff --git a/src/components/views/polls/pollHistory/PollHistory.tsx b/src/components/views/polls/pollHistory/PollHistory.tsx
index 01fcedd8eec229b37829a4bc4289348543e29a8b..6967ef8d9c4dc2778da89302135fa6d8ce4cea2c 100644
--- a/src/components/views/polls/pollHistory/PollHistory.tsx
+++ b/src/components/views/polls/pollHistory/PollHistory.tsx
@@ -7,14 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState } from "react";
-import { MatrixClient, MatrixEvent, Poll, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, type Poll, type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import { PollHistoryList } from "./PollHistoryList";
-import { PollHistoryFilter } from "./types";
+import { type PollHistoryFilter } from "./types";
 import { PollDetailHeader } from "./PollDetailHeader";
 import { PollDetail } from "./PollDetail";
-import { RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks";
 import { usePollsWithRelations } from "./usePollHistory";
 import { useFetchPastPolls } from "./fetchPastPolls";
 import Heading from "../../typography/Heading";
diff --git a/src/components/views/polls/pollHistory/PollHistoryList.tsx b/src/components/views/polls/pollHistory/PollHistoryList.tsx
index ed0b935050e5721cb98daf479aff063e4eb33986..02facc95ae7d581c4309076195173b6e60ec13d4 100644
--- a/src/components/views/polls/pollHistory/PollHistoryList.tsx
+++ b/src/components/views/polls/pollHistory/PollHistoryList.tsx
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import classNames from "classnames";
-import { MatrixEvent, Poll } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Poll } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import { FilterTabGroup } from "../../elements/FilterTabGroup";
 import InlineSpinner from "../../elements/InlineSpinner";
-import { PollHistoryFilter } from "./types";
+import { type PollHistoryFilter } from "./types";
 import { PollListItem } from "./PollListItem";
 import { PollListItemEnded } from "./PollListItemEnded";
 import AccessibleButton from "../../elements/AccessibleButton";
diff --git a/src/components/views/polls/pollHistory/PollListItem.tsx b/src/components/views/polls/pollHistory/PollListItem.tsx
index 3a631c4242db4bfe8a615e7f8f47ec3492eae5c9..1824bb38da9d22bb27becd7f58479752019a730c 100644
--- a/src/components/views/polls/pollHistory/PollListItem.tsx
+++ b/src/components/views/polls/pollHistory/PollListItem.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { Icon as PollIcon } from "../../../../../res/img/element-icons/room/composer/poll.svg";
diff --git a/src/components/views/polls/pollHistory/PollListItemEnded.tsx b/src/components/views/polls/pollHistory/PollListItemEnded.tsx
index 17a9f9ae3ec11bd9b73db894f688c69f164a1cfb..8e2ed22127cc5ef52e04bbc38786985275b008f1 100644
--- a/src/components/views/polls/pollHistory/PollListItemEnded.tsx
+++ b/src/components/views/polls/pollHistory/PollListItemEnded.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useEffect, useState } from "react";
-import { PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
-import { MatrixEvent, Poll, PollEvent, Relations } from "matrix-js-sdk/src/matrix";
+import { type PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
+import { type MatrixEvent, type Poll, PollEvent, type Relations } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { Icon as PollIcon } from "../../../../../res/img/element-icons/room/composer/poll.svg";
diff --git a/src/components/views/polls/pollHistory/fetchPastPolls.ts b/src/components/views/polls/pollHistory/fetchPastPolls.ts
index 2e6c70759402fca1ce4230c55531d8e4ef7b1ceb..d25ef6bfae41189ce355b492ca168b8927439e68 100644
--- a/src/components/views/polls/pollHistory/fetchPastPolls.ts
+++ b/src/components/views/polls/pollHistory/fetchPastPolls.ts
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
 import { useCallback, useEffect, useState } from "react";
 import {
     M_POLL_START,
-    MatrixClient,
+    type MatrixClient,
     Direction,
     EventTimeline,
-    EventTimelineSet,
-    Room,
+    type EventTimelineSet,
+    type Room,
     Filter,
-    IFilterDefinition,
+    type IFilterDefinition,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
diff --git a/src/components/views/polls/pollHistory/usePollHistory.ts b/src/components/views/polls/pollHistory/usePollHistory.ts
index fbc5715842759671d52d2ae7d4b76c36d976ca5d..fb0714863da5ec3a6eb84e482e7bcfaf77d20d02 100644
--- a/src/components/views/polls/pollHistory/usePollHistory.ts
+++ b/src/components/views/polls/pollHistory/usePollHistory.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useEffect, useState } from "react";
-import { Poll, PollEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Poll, PollEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
 
diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx
index d7d6129960d2412f21bec1d39831d118960bfca3..a564a606959944f530941bdf49861a639797e278 100644
--- a/src/components/views/right_panel/BaseCard.tsx
+++ b/src/components/views/right_panel/BaseCard.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef, ReactNode, KeyboardEvent, Ref, MouseEvent } from "react";
+import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent } from "react";
 import classNames from "classnames";
 import { IconButton, Text } from "@vector-im/compound-web";
 import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
@@ -44,106 +44,102 @@ function closeRightPanel(ev: MouseEvent<HTMLButtonElement>): void {
     RightPanelStore.instance.popCard();
 }
 
-const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
-    (
-        {
-            closeLabel,
-            onClose,
-            onBack,
-            className,
-            id,
-            ariaLabelledBy,
-            role,
-            hideHeaderButtons,
-            header,
-            footer,
-            withoutScrollContainer,
-            children,
-            onKeyDown,
-            closeButtonRef,
-        },
-        ref,
-    ) => {
-        let backButton;
-        const cardHistory = RightPanelStore.instance.roomPhaseHistory;
-        if (cardHistory.length > 1 && !hideHeaderButtons) {
-            const prevCard = cardHistory[cardHistory.length - 2];
-            const onBackClick = (ev: MouseEvent<HTMLButtonElement>): void => {
-                onBack?.(ev);
-                RightPanelStore.instance.popCard();
-            };
-            const label = backLabelForPhase(prevCard.phase) ?? _t("action|back");
-            backButton = (
-                <IconButton
-                    size="28px"
-                    data-testid="base-card-back-button"
-                    onClick={onBackClick}
-                    tooltip={label}
-                    subtleBackground
-                >
-                    <ChevronLeftIcon />
-                </IconButton>
-            );
-        }
+const BaseCard: React.FC<IProps> = ({
+    closeLabel,
+    onClose,
+    onBack,
+    className,
+    id,
+    ariaLabelledBy,
+    role,
+    hideHeaderButtons,
+    header,
+    footer,
+    withoutScrollContainer,
+    children,
+    onKeyDown,
+    closeButtonRef,
+    ref,
+}: IProps) => {
+    let backButton;
+    const cardHistory = RightPanelStore.instance.roomPhaseHistory;
+    if (cardHistory.length > 1 && !hideHeaderButtons) {
+        const prevCard = cardHistory[cardHistory.length - 2];
+        const onBackClick = (ev: MouseEvent<HTMLButtonElement>): void => {
+            onBack?.(ev);
+            RightPanelStore.instance.popCard();
+        };
+        const label = backLabelForPhase(prevCard.phase) ?? _t("action|back");
+        backButton = (
+            <IconButton
+                size="28px"
+                data-testid="base-card-back-button"
+                onClick={onBackClick}
+                tooltip={label}
+                subtleBackground
+            >
+                <ChevronLeftIcon />
+            </IconButton>
+        );
+    }
 
-        let closeButton;
-        if (!hideHeaderButtons) {
-            closeButton = (
-                <IconButton
-                    size="28px"
-                    data-testid="base-card-close-button"
-                    onClick={onClose ?? closeRightPanel}
-                    ref={closeButtonRef}
-                    tooltip={closeLabel ?? _t("action|close")}
-                    subtleBackground
-                >
-                    <CloseIcon />
-                </IconButton>
-            );
-        }
+    let closeButton;
+    if (!hideHeaderButtons) {
+        closeButton = (
+            <IconButton
+                size="28px"
+                data-testid="base-card-close-button"
+                onClick={onClose ?? closeRightPanel}
+                ref={closeButtonRef}
+                tooltip={closeLabel ?? _t("action|close")}
+                subtleBackground
+            >
+                <CloseIcon />
+            </IconButton>
+        );
+    }
 
-        if (!withoutScrollContainer) {
-            children = <AutoHideScrollbar>{children}</AutoHideScrollbar>;
-        }
+    if (!withoutScrollContainer) {
+        children = <AutoHideScrollbar>{children}</AutoHideScrollbar>;
+    }
 
-        const shouldRenderHeader = header || !hideHeaderButtons;
+    const shouldRenderHeader = header || !hideHeaderButtons;
 
-        return (
-            <CardContext.Provider value={{ isCard: true }}>
-                <div
-                    id={id}
-                    aria-labelledby={ariaLabelledBy}
-                    role={role}
-                    className={classNames("mx_BaseCard", className)}
-                    ref={ref}
-                    onKeyDown={onKeyDown}
-                >
-                    {shouldRenderHeader && (
-                        <div className="mx_BaseCard_header">
-                            {backButton}
-                            {typeof header === "string" ? (
-                                <div className="mx_BaseCard_header_title">
-                                    <Text
-                                        size="md"
-                                        weight="medium"
-                                        className="mx_BaseCard_header_title_heading"
-                                        role="heading"
-                                    >
-                                        {header}
-                                    </Text>
-                                </div>
-                            ) : (
-                                (header ?? <div className="mx_BaseCard_header_spacer" />)
-                            )}
-                            {closeButton}
-                        </div>
-                    )}
-                    {children}
-                    {footer && <div className="mx_BaseCard_footer">{footer}</div>}
-                </div>
-            </CardContext.Provider>
-        );
-    },
-);
+    return (
+        <CardContext.Provider value={{ isCard: true }}>
+            <div
+                id={id}
+                aria-labelledby={ariaLabelledBy}
+                role={role}
+                className={classNames("mx_BaseCard", className)}
+                ref={ref}
+                onKeyDown={onKeyDown}
+            >
+                {shouldRenderHeader && (
+                    <div className="mx_BaseCard_header">
+                        {backButton}
+                        {typeof header === "string" ? (
+                            <div className="mx_BaseCard_header_title">
+                                <Text
+                                    size="md"
+                                    weight="medium"
+                                    className="mx_BaseCard_header_title_heading"
+                                    role="heading"
+                                >
+                                    {header}
+                                </Text>
+                            </div>
+                        ) : (
+                            (header ?? <div className="mx_BaseCard_header_spacer" />)
+                        )}
+                        {closeButton}
+                    </div>
+                )}
+                {children}
+                {footer && <div className="mx_BaseCard_footer">{footer}</div>}
+            </div>
+        </CardContext.Provider>
+    );
+};
 
 export default BaseCard;
diff --git a/src/components/views/right_panel/EmptyState.tsx b/src/components/views/right_panel/EmptyState.tsx
index 1c7713a5d8ae53183d36e8b37687807d976ea9a0..f6b2a940a6378902c80fb36aa10a9f128875ece4 100644
--- a/src/components/views/right_panel/EmptyState.tsx
+++ b/src/components/views/right_panel/EmptyState.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentType } from "react";
+import React, { type ComponentType } from "react";
 import { Text } from "@vector-im/compound-web";
 
 import { Flex } from "../../utils/Flex";
diff --git a/src/components/views/right_panel/EncryptionInfo.tsx b/src/components/views/right_panel/EncryptionInfo.tsx
index a2fb6e8f2439813a92ba4d168d38d847b7c84c65..f7e4c00c505837776e6ff052230a370e26b8e0a0 100644
--- a/src/components/views/right_panel/EncryptionInfo.tsx
+++ b/src/components/views/right_panel/EncryptionInfo.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { RoomMember, User } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type RoomMember, type User } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx
index 3ccd07cfc2cc9a860b5a1dad948aff1a31eb64d6..32fc2137dccfdb842e79118722a92189fba382f7 100644
--- a/src/components/views/right_panel/EncryptionPanel.tsx
+++ b/src/components/views/right_panel/EncryptionPanel.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useCallback, useEffect, useRef, useState } from "react";
-import { VerificationPhase, VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
-import { RoomMember, User } from "matrix-js-sdk/src/matrix";
+import { VerificationPhase, type VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
+import { type RoomMember, type User } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import EncryptionInfo from "./EncryptionInfo";
diff --git a/src/components/views/right_panel/ExtensionsCard.tsx b/src/components/views/right_panel/ExtensionsCard.tsx
index 0e0ca12d67bc9ba0b48d8113f413be0e53eecc74..58c2435e72dff655fd21f6863ba927d2d8058dfc 100644
--- a/src/components/views/right_panel/ExtensionsCard.tsx
+++ b/src/components/views/right_panel/ExtensionsCard.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useEffect, useMemo, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useEffect, useMemo, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 import { Button, Link, Separator, Text } from "@vector-im/compound-web";
 import PlusIcon from "@vector-im/compound-design-tokens/assets/web/icons/plus";
@@ -20,7 +20,7 @@ import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../str
 import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
 import UIStore from "../../../stores/UIStore";
 import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
-import { IApp } from "../../../stores/WidgetStore";
+import { type IApp } from "../../../stores/WidgetStore";
 import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
 import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
 import AccessibleButton from "../elements/AccessibleButton";
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index 2ce514c97ae3d3faac51836dbf51a931fe4312d1..daea698edda677b33319ca7b5d3b10bffccccb88 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback, useEffect, JSX, useContext } from "react";
-import { Room, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { useCallback, useEffect, type JSX, useContext } from "react";
+import { type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { Button, Separator } from "@vector-im/compound-web";
 import classNames from "classnames";
 import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin";
@@ -20,7 +20,7 @@ import { PinnedEventTile } from "../rooms/PinnedEventTile";
 import { useRoomState } from "../../../hooks/useRoomState";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import { ReadPinsEventId } from "./types";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { filterBoolean } from "../../../utils/arrays";
 import Modal from "../../../Modal";
 import { UnpinAllDialog } from "../dialogs/UnpinAllDialog";
diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx
deleted file mode 100644
index 0abdba5276e84a42c7cf293e9fd9e27770f8c3f4..0000000000000000000000000000000000000000
--- a/src/components/views/right_panel/RoomSummaryCard.tsx
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2020 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { ChangeEvent, SyntheticEvent, useContext, useEffect, useRef, useState } from "react";
-import classNames from "classnames";
-import {
-    MenuItem,
-    Separator,
-    ToggleMenuItem,
-    Text,
-    Badge,
-    Heading,
-    IconButton,
-    Link,
-    Search,
-    Form,
-} from "@vector-im/compound-web";
-import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite";
-import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
-import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
-import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings";
-import ExportArchiveIcon from "@vector-im/compound-design-tokens/assets/web/icons/export-archive";
-import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
-import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files";
-import ExtensionsIcon from "@vector-im/compound-design-tokens/assets/web/icons/extensions";
-import UserProfileIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
-import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads";
-import PollsIcon from "@vector-im/compound-design-tokens/assets/web/icons/polls";
-import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin";
-import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
-import LockOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-off";
-import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
-import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
-import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
-import { EventType, JoinRule, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
-
-import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
-import BaseCard from "./BaseCard";
-import { _t } from "../../../languageHandler";
-import RoomAvatar from "../avatars/RoomAvatar";
-import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
-import Modal from "../../../Modal";
-import { ShareDialog } from "../dialogs/ShareDialog";
-import { useEventEmitterState } from "../../../hooks/useEventEmitter";
-import { E2EStatus } from "../../../utils/ShieldUtils";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
-import { TimelineRenderingType } from "../../../contexts/RoomContext";
-import RoomName from "../elements/RoomName";
-import ExportDialog from "../dialogs/ExportDialog";
-import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
-import PosthogTrackers from "../../../PosthogTrackers";
-import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
-import { Flex } from "../../utils/Flex";
-import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
-import { DefaultTagID } from "../../../stores/room-list/models";
-import { tagRoom } from "../../../utils/room/tagRoom";
-import { canInviteTo } from "../../../utils/room/canInviteTo";
-import { inviteToRoom } from "../../../utils/room/inviteToRoom";
-import { useAccountData } from "../../../hooks/useAccountData";
-import { useRoomState } from "../../../hooks/useRoomState";
-import { useTopic } from "../../../hooks/room/useTopic";
-import { Linkify, topicToHtml } from "../../../HtmlUtils";
-import { Box } from "../../utils/Box";
-import { onRoomTopicLinkClick } from "../elements/RoomTopic";
-import { useDispatcher } from "../../../hooks/useDispatcher";
-import { Action } from "../../../dispatcher/actions";
-import { Key } from "../../../Keyboard";
-import { useTransition } from "../../../hooks/useTransition";
-import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
-import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
-import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
-import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
-
-interface IProps {
-    room: Room;
-    permalinkCreator: RoomPermalinkCreator;
-    onSearchChange?: (e: ChangeEvent) => void;
-    onSearchCancel?: () => void;
-    focusRoomSearch?: boolean;
-}
-
-const onRoomMembersClick = (): void => {
-    RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true);
-};
-
-const onRoomThreadsClick = (): void => {
-    RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true);
-};
-
-const onRoomFilesClick = (): void => {
-    RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
-};
-
-const onRoomExtensionsClick = (): void => {
-    RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true);
-};
-
-const onRoomPinsClick = (): void => {
-    PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton");
-    RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true);
-};
-
-const onRoomSettingsClick = (ev: Event): void => {
-    defaultDispatcher.dispatch({ action: "open_room_settings" });
-    PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
-};
-
-const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
-    const [expanded, setExpanded] = useState(true);
-
-    const topic = useTopic(room);
-    const body = topicToHtml(topic?.text, topic?.html);
-
-    const canEditTopic = useRoomState(room, (state) =>
-        state.maySendStateEvent(EventType.RoomTopic, room.client.getSafeUserId()),
-    );
-    const onEditClick = (e: SyntheticEvent): void => {
-        e.preventDefault();
-        e.stopPropagation();
-        defaultDispatcher.dispatch({ action: "open_room_settings" });
-    };
-
-    if (!body && !canEditTopic) {
-        return null;
-    }
-
-    if (!body) {
-        return (
-            <Flex
-                as="section"
-                direction="column"
-                justify="center"
-                gap="var(--cpd-space-2x)"
-                className="mx_RoomSummaryCard_topic"
-            >
-                <Box flex="1">
-                    <Link kind="primary" onClick={onEditClick}>
-                        <Text size="sm" weight="regular">
-                            {_t("right_panel|add_topic")}
-                        </Text>
-                    </Link>
-                </Box>
-            </Flex>
-        );
-    }
-
-    const content = expanded ? <Linkify>{body}</Linkify> : body;
-    return (
-        <Flex
-            as="section"
-            direction="column"
-            justify="center"
-            gap="var(--cpd-space-2x)"
-            className={classNames("mx_RoomSummaryCard_topic", {
-                mx_RoomSummaryCard_topic_collapsed: !expanded,
-            })}
-        >
-            <Box flex="1" className="mx_RoomSummaryCard_topic_container">
-                <Text
-                    size="sm"
-                    weight="regular"
-                    onClick={(ev: React.MouseEvent): void => {
-                        if (ev.target instanceof HTMLAnchorElement) {
-                            onRoomTopicLinkClick(ev);
-                            return;
-                        }
-                    }}
-                >
-                    {content}
-                </Text>
-                <IconButton
-                    className="mx_RoomSummaryCard_topic_chevron"
-                    size="24px"
-                    onClick={() => setExpanded(!expanded)}
-                >
-                    <ChevronDownIcon />
-                </IconButton>
-            </Box>
-            {expanded && canEditTopic && (
-                <Box flex="1" className="mx_RoomSummaryCard_topic_edit">
-                    <Link kind="primary" onClick={onEditClick}>
-                        <Text size="sm" weight="regular">
-                            {_t("action|edit")}
-                        </Text>
-                    </Link>
-                </Box>
-            )}
-        </Flex>
-    );
-};
-
-const RoomSummaryCard: React.FC<IProps> = ({
-    room,
-    permalinkCreator,
-    onSearchChange,
-    onSearchCancel,
-    focusRoomSearch,
-}) => {
-    const cli = useContext(MatrixClientContext);
-
-    const onShareRoomClick = (): void => {
-        Modal.createDialog(ShareDialog, {
-            target: room,
-        });
-    };
-
-    const onRoomExportClick = async (): Promise<void> => {
-        Modal.createDialog(ExportDialog, {
-            room,
-        });
-    };
-
-    const onRoomPollHistoryClick = (): void => {
-        Modal.createDialog(PollHistoryDialog, {
-            room,
-            matrixClient: cli,
-            permalinkCreator,
-        });
-    };
-
-    const onLeaveRoomClick = (): void => {
-        defaultDispatcher.dispatch({
-            action: "leave_room",
-            room_id: room.roomId,
-        });
-    };
-
-    const isRoomEncrypted = useIsEncrypted(cli, room);
-    const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
-    const e2eStatus = roomContext.e2eStatus;
-    const isVideoRoom = calcIsVideoRoom(room);
-
-    const roomState = useRoomState(room);
-    const directRoomsList = useAccountData<Record<string, string[]>>(room.client, EventType.Direct);
-    const [isDirectMessage, setDirectMessage] = useState(false);
-    useEffect(() => {
-        for (const [, dmRoomList] of Object.entries(directRoomsList)) {
-            if (dmRoomList.includes(room?.roomId ?? "")) {
-                setDirectMessage(true);
-                break;
-            }
-        }
-    }, [room, directRoomsList]);
-
-    const searchInputRef = useRef<HTMLInputElement>(null);
-    useDispatcher(defaultDispatcher, (payload) => {
-        if (payload.action === Action.FocusMessageSearch) {
-            searchInputRef.current?.focus();
-        }
-    });
-    // Clear the search field when the user leaves the search view
-    useTransition(
-        (prevTimelineRenderingType) => {
-            if (
-                prevTimelineRenderingType === TimelineRenderingType.Search &&
-                roomContext.timelineRenderingType !== TimelineRenderingType.Search &&
-                searchInputRef.current
-            ) {
-                searchInputRef.current.value = "";
-            }
-        },
-        [roomContext.timelineRenderingType],
-    );
-
-    const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
-    const roomInfo = (
-        <header className="mx_RoomSummaryCard_container">
-            <RoomAvatar room={room} size="80px" viewAvatarOnClick />
-            <RoomName room={room}>
-                {(name) => (
-                    <Heading
-                        as="h1"
-                        size="md"
-                        weight="semibold"
-                        className="mx_RoomSummaryCard_roomName text-primary"
-                        title={name}
-                    >
-                        {name}
-                    </Heading>
-                )}
-            </RoomName>
-            <Text
-                as="div"
-                size="sm"
-                weight="semibold"
-                className="mx_RoomSummaryCard_alias text-secondary"
-                title={alias}
-            >
-                {alias}
-            </Text>
-
-            <Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
-                {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
-                    <Badge kind="grey">
-                        <PublicIcon width="1em" />
-                        {_t("common|public_room")}
-                    </Badge>
-                )}
-
-                {isRoomEncrypted && e2eStatus !== E2EStatus.Warning && (
-                    <Badge kind="green">
-                        <LockIcon width="1em" />
-                        {_t("common|encrypted")}
-                    </Badge>
-                )}
-
-                {!e2eStatus && (
-                    <Badge kind="grey">
-                        <LockOffIcon width="1em" />
-                        {_t("common|unencrypted")}
-                    </Badge>
-                )}
-
-                {e2eStatus === E2EStatus.Warning && (
-                    <Badge kind="red">
-                        <ErrorIcon width="1em" />
-                        {_t("common|not_trusted")}
-                    </Badge>
-                )}
-            </Flex>
-
-            <RoomTopic room={room} />
-        </header>
-    );
-
-    const pinCount = usePinnedEvents(room).length;
-
-    const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
-        RoomListStore.instance.getTagsForRoom(room),
-    );
-    const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
-    const isFavorite = roomTags.includes(DefaultTagID.Favourite);
-
-    const header = onSearchChange && (
-        <Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
-            <Search
-                placeholder={_t("room|search|placeholder")}
-                name="room_message_search"
-                onChange={onSearchChange}
-                className="mx_no_textinput"
-                ref={searchInputRef}
-                autoFocus={focusRoomSearch}
-                onKeyDown={(e) => {
-                    if (searchInputRef.current && e.key === Key.ESCAPE) {
-                        searchInputRef.current.value = "";
-                        onSearchCancel?.();
-                    }
-                }}
-            />
-        </Form.Root>
-    );
-
-    return (
-        <BaseCard
-            id="room-summary-panel"
-            className="mx_RoomSummaryCard"
-            ariaLabelledBy="room-summary-panel-tab"
-            role="tabpanel"
-            header={header}
-        >
-            {roomInfo}
-
-            <Separator />
-
-            <div role="menubar" aria-orientation="vertical">
-                <ToggleMenuItem
-                    Icon={FavouriteIcon}
-                    label={_t("room|context_menu|favourite")}
-                    checked={isFavorite}
-                    onSelect={() => tagRoom(room, DefaultTagID.Favourite)}
-                />
-                <MenuItem
-                    Icon={UserAddIcon}
-                    label={_t("action|invite")}
-                    disabled={!canInviteToState}
-                    onSelect={() => inviteToRoom(room)}
-                />
-
-                <Separator />
-
-                <MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={onRoomMembersClick} />
-                <MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={onRoomThreadsClick} />
-                {!isVideoRoom && (
-                    <>
-                        <ReleaseAnnouncement
-                            feature="pinningMessageList"
-                            header={_t("right_panel|pinned_messages|release_announcement|title")}
-                            description={_t("right_panel|pinned_messages|release_announcement|description")}
-                            closeLabel={_t("right_panel|pinned_messages|release_announcement|close")}
-                            placement="top"
-                        >
-                            <div>
-                                <MenuItem
-                                    Icon={PinIcon}
-                                    label={_t("right_panel|pinned_messages_button")}
-                                    onSelect={onRoomPinsClick}
-                                >
-                                    <Text as="span" size="sm">
-                                        {pinCount}
-                                    </Text>
-                                </MenuItem>
-                            </div>
-                        </ReleaseAnnouncement>
-                        <MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onSelect={onRoomFilesClick} />
-                        <MenuItem
-                            Icon={ExtensionsIcon}
-                            label={_t("right_panel|extensions_button")}
-                            onSelect={onRoomExtensionsClick}
-                        />
-                    </>
-                )}
-
-                <Separator />
-
-                <MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={onShareRoomClick} />
-
-                {!isVideoRoom && (
-                    <>
-                        <MenuItem
-                            Icon={PollsIcon}
-                            label={_t("right_panel|polls_button")}
-                            onSelect={onRoomPollHistoryClick}
-                        />
-                        <MenuItem
-                            Icon={ExportArchiveIcon}
-                            label={_t("export_chat|title")}
-                            onSelect={onRoomExportClick}
-                        />
-                    </>
-                )}
-
-                <MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
-
-                <Separator />
-
-                <MenuItem
-                    Icon={LeaveIcon}
-                    kind="critical"
-                    label={_t("action|leave_room")}
-                    onSelect={onLeaveRoomClick}
-                />
-            </div>
-        </BaseCard>
-    );
-};
-
-export default RoomSummaryCard;
diff --git a/src/components/views/right_panel/RoomSummaryCardView.tsx b/src/components/views/right_panel/RoomSummaryCardView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d994e697c6e087b27fc90a248ad8253f9ecaca23
--- /dev/null
+++ b/src/components/views/right_panel/RoomSummaryCardView.tsx
@@ -0,0 +1,328 @@
+/*
+Copyright 2024, 2025 New Vector Ltd.
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { useEffect, useState, type JSX } from "react";
+import classNames from "classnames";
+import {
+    MenuItem,
+    Separator,
+    ToggleMenuItem,
+    Text,
+    Badge,
+    Heading,
+    IconButton,
+    Link,
+    Search,
+    Form,
+} from "@vector-im/compound-web";
+import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite";
+import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
+import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
+import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings";
+import ExportArchiveIcon from "@vector-im/compound-design-tokens/assets/web/icons/export-archive";
+import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
+import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files";
+import ExtensionsIcon from "@vector-im/compound-design-tokens/assets/web/icons/extensions";
+import UserProfileIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
+import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads";
+import PollsIcon from "@vector-im/compound-design-tokens/assets/web/icons/polls";
+import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin";
+import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
+import LockOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-off";
+import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
+import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
+import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
+import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
+
+import BaseCard from "./BaseCard.tsx";
+import { _t } from "../../../languageHandler.tsx";
+import RoomAvatar from "../avatars/RoomAvatar.tsx";
+import { E2EStatus } from "../../../utils/ShieldUtils.ts";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts";
+import RoomName from "../elements/RoomName.tsx";
+import { Flex } from "../../utils/Flex.tsx";
+import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx";
+import { Box } from "../../utils/Box.tsx";
+import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
+import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
+import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
+
+interface IProps {
+    room: Room;
+    permalinkCreator: RoomPermalinkCreator;
+    onSearchChange?: (term: string) => void;
+    onSearchCancel?: () => void;
+    focusRoomSearch?: boolean;
+    searchTerm?: string;
+}
+
+const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
+    const vm = useRoomTopicViewModel(room);
+
+    const body = topicToHtml(vm.topic?.text, vm.topic?.html);
+
+    if (!body && !vm.canEditTopic) {
+        return null;
+    }
+
+    if (!body) {
+        return (
+            <Flex
+                as="section"
+                direction="column"
+                justify="center"
+                gap="var(--cpd-space-2x)"
+                className="mx_RoomSummaryCard_topic"
+            >
+                <Box flex="1">
+                    <Link kind="primary" onClick={vm.onEditClick}>
+                        <Text size="sm" weight="regular">
+                            {_t("right_panel|add_topic")}
+                        </Text>
+                    </Link>
+                </Box>
+            </Flex>
+        );
+    }
+
+    const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
+
+    return (
+        <Flex
+            as="section"
+            direction="column"
+            justify="center"
+            gap="var(--cpd-space-2x)"
+            className={classNames("mx_RoomSummaryCard_topic", {
+                mx_RoomSummaryCard_topic_collapsed: !vm.expanded,
+            })}
+        >
+            <Box flex="1" className="mx_RoomSummaryCard_topic_container">
+                <Text size="sm" weight="regular" onClick={vm.onTopicLinkClick}>
+                    {content}
+                </Text>
+                <IconButton className="mx_RoomSummaryCard_topic_chevron" size="24px" onClick={vm.onExpandedClick}>
+                    <ChevronDownIcon />
+                </IconButton>
+            </Box>
+            {vm.expanded && vm.canEditTopic && (
+                <Box flex="1" className="mx_RoomSummaryCard_topic_edit">
+                    <Link kind="primary" onClick={vm.onEditClick}>
+                        <Text size="sm" weight="regular">
+                            {_t("action|edit")}
+                        </Text>
+                    </Link>
+                </Box>
+            )}
+        </Flex>
+    );
+};
+
+const RoomSummaryCardView: React.FC<IProps> = ({
+    room,
+    permalinkCreator,
+    onSearchChange,
+    onSearchCancel,
+    focusRoomSearch,
+    searchTerm = "",
+}) => {
+    const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel);
+
+    // The search field is controlled and onSearchChange is debounced in RoomView,
+    // so we need to set the value of the input right away
+    const [searchValue, setSearchValue] = useState(searchTerm);
+    useEffect(() => {
+        setSearchValue(searchTerm);
+    }, [searchTerm]);
+
+    const roomInfo = (
+        <header className="mx_RoomSummaryCard_container">
+            <RoomAvatar room={room} size="80px" viewAvatarOnClick />
+            <RoomName room={room}>
+                {(name) => (
+                    <Heading
+                        as="h1"
+                        size="md"
+                        weight="semibold"
+                        className="mx_RoomSummaryCard_roomName text-primary"
+                        title={name}
+                    >
+                        {name}
+                    </Heading>
+                )}
+            </RoomName>
+            <Text
+                as="div"
+                size="sm"
+                weight="semibold"
+                className="mx_RoomSummaryCard_alias text-secondary"
+                title={vm.alias}
+            >
+                {vm.alias}
+            </Text>
+
+            <Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
+                {!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && (
+                    <Badge kind="grey">
+                        <PublicIcon width="1em" />
+                        {_t("common|public_room")}
+                    </Badge>
+                )}
+
+                {vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && (
+                    <Badge kind="green">
+                        <LockIcon width="1em" />
+                        {_t("common|encrypted")}
+                    </Badge>
+                )}
+
+                {!vm.isRoomEncrypted && (
+                    <Badge kind="grey">
+                        <LockOffIcon width="1em" />
+                        {_t("common|unencrypted")}
+                    </Badge>
+                )}
+
+                {vm.e2eStatus === E2EStatus.Warning && (
+                    <Badge kind="red">
+                        <ErrorSolidIcon width="1em" />
+                        {_t("common|not_trusted")}
+                    </Badge>
+                )}
+            </Flex>
+
+            <RoomTopic room={room} />
+        </header>
+    );
+
+    const header = onSearchChange && (
+        <Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
+            <Search
+                placeholder={_t("room|search|placeholder")}
+                name="room_message_search"
+                onChange={(e) => {
+                    setSearchValue(e.currentTarget.value);
+                    onSearchChange(e.currentTarget.value);
+                }}
+                value={searchValue}
+                className="mx_no_textinput"
+                ref={vm.searchInputRef}
+                autoFocus={focusRoomSearch}
+                onKeyDown={vm.onUpdateSearchInput}
+            />
+        </Form.Root>
+    );
+
+    return (
+        <BaseCard
+            id="room-summary-panel"
+            className="mx_RoomSummaryCard"
+            ariaLabelledBy="room-summary-panel-tab"
+            role="tabpanel"
+            header={header}
+        >
+            {roomInfo}
+
+            <Separator />
+
+            <div role="menubar" aria-orientation="vertical">
+                <ToggleMenuItem
+                    Icon={FavouriteIcon}
+                    label={_t("room|context_menu|favourite")}
+                    checked={vm.isFavorite}
+                    onSelect={vm.onFavoriteToggleClick}
+                />
+                <MenuItem
+                    Icon={UserAddIcon}
+                    label={_t("action|invite")}
+                    disabled={!vm.canInviteToState}
+                    onSelect={vm.onInviteToRoomClick}
+                />
+
+                <Separator />
+
+                <MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={vm.onRoomMembersClick} />
+                <MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={vm.onRoomThreadsClick} />
+                {!vm.isVideoRoom && (
+                    <>
+                        <ReleaseAnnouncement
+                            feature="pinningMessageList"
+                            header={_t("right_panel|pinned_messages|release_announcement|title")}
+                            description={_t("right_panel|pinned_messages|release_announcement|description")}
+                            closeLabel={_t("right_panel|pinned_messages|release_announcement|close")}
+                            placement="top"
+                        >
+                            <div>
+                                <MenuItem
+                                    Icon={PinIcon}
+                                    label={_t("right_panel|pinned_messages_button")}
+                                    onSelect={vm.onRoomPinsClick}
+                                >
+                                    <Text as="span" size="sm">
+                                        {vm.pinCount}
+                                    </Text>
+                                </MenuItem>
+                            </div>
+                        </ReleaseAnnouncement>
+                        <MenuItem
+                            Icon={FilesIcon}
+                            label={_t("right_panel|files_button")}
+                            onSelect={vm.onRoomFilesClick}
+                        />
+                        <MenuItem
+                            Icon={ExtensionsIcon}
+                            label={_t("right_panel|extensions_button")}
+                            onSelect={vm.onRoomExtensionsClick}
+                        />
+                    </>
+                )}
+
+                <Separator />
+
+                <MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={vm.onShareRoomClick} />
+
+                {!vm.isVideoRoom && (
+                    <>
+                        <MenuItem
+                            Icon={PollsIcon}
+                            label={_t("right_panel|polls_button")}
+                            onSelect={vm.onRoomPollHistoryClick}
+                        />
+                        <MenuItem
+                            Icon={ExportArchiveIcon}
+                            label={_t("export_chat|title")}
+                            onSelect={vm.onRoomExportClick}
+                        />
+                    </>
+                )}
+
+                <MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={vm.onRoomSettingsClick} />
+
+                <Separator />
+                <div className="mx_RoomSummaryCard_bottomOptions">
+                    <MenuItem
+                        Icon={ErrorIcon}
+                        kind="critical"
+                        label={_t("action|report_room")}
+                        onSelect={vm.onReportRoomClick}
+                    />
+                    <MenuItem
+                        className="mx_RoomSummaryCard_leave"
+                        Icon={LeaveIcon}
+                        kind="critical"
+                        label={_t("action|leave_room")}
+                        onSelect={vm.onLeaveRoomClick}
+                    />
+                </div>
+            </div>
+        </BaseCard>
+    );
+};
+
+export default RoomSummaryCardView;
diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx
index 4cee1ef9b4f69c47c5088b85e25254f55c02d8e4..a7683ae28ecfdf04ba486d21d1059be0088b0dc6 100644
--- a/src/components/views/right_panel/TimelineCard.tsx
+++ b/src/components/views/right_panel/TimelineCard.tsx
@@ -8,33 +8,33 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import {
-    IEventRelation,
-    MatrixEvent,
+    type IEventRelation,
+    type MatrixEvent,
     NotificationCountType,
-    Room,
-    EventTimelineSet,
-    Thread,
+    type Room,
+    type EventTimelineSet,
+    type Thread,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import BaseCard from "./BaseCard";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 import MessageComposer from "../rooms/MessageComposer";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { Layout } from "../../../settings/enums/Layout";
 import TimelinePanel from "../../structures/TimelinePanel";
-import { E2EStatus } from "../../../utils/ShieldUtils";
+import { type E2EStatus } from "../../../utils/ShieldUtils";
 import EditorStateTransfer from "../../../utils/EditorStateTransfer";
-import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
+import RoomContext, { type TimelineRenderingType } from "../../../contexts/RoomContext";
 import dis from "../../../dispatcher/dispatcher";
 import { _t } from "../../../languageHandler";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { Action } from "../../../dispatcher/actions";
 import ContentMessages from "../../../ContentMessages";
 import UploadBar from "../../structures/UploadBar";
 import SettingsStore from "../../../settings/SettingsStore";
 import JumpToBottomButton from "../rooms/JumpToBottomButton";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import Measured from "../elements/Measured";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 import { SdkContextClass } from "../../../contexts/SDKContext";
@@ -77,8 +77,8 @@ export default class TimelineCard extends React.Component<IProps, IState> {
     private card = React.createRef<HTMLDivElement>();
     private readReceiptsSettingWatcher: string | undefined;
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
         this.state = {
             showReadReceipts: SettingsStore.getValue("showReadReceipts", props.room.roomId),
             layout: SettingsStore.getValue("layout"),
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index 4e8968afaa9fbe145b32b1119ffefcc0c1045354..7bba4f09505b02ea398431c8d38d4e94dfe87ffe 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -9,23 +9,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
+import React, { type JSX, type ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
 import classNames from "classnames";
 import {
     ClientEvent,
-    MatrixClient,
+    type MatrixClient,
     RoomMember,
-    Room,
+    type Room,
     RoomStateEvent,
-    MatrixEvent,
+    type MatrixEvent,
     User,
-    Device,
+    type Device,
     EventType,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { UserVerificationStatus, VerificationRequest, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import { type UserVerificationStatus, type VerificationRequest, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Heading, MenuItem, Text, Tooltip } from "@vector-im/compound-web";
+import { Badge, Button, Heading, InlineSpinner, MenuItem, Text, Tooltip } from "@vector-im/compound-web";
+import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
 import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat";
 import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
 import ShareIcon from "@vector-im/compound-design-tokens/assets/web/icons/share";
@@ -42,21 +43,19 @@ import dis from "../../../dispatcher/dispatcher";
 import Modal from "../../../Modal";
 import { _t, UserFriendlyError } from "../../../languageHandler";
 import DMRoomMap from "../../../utils/DMRoomMap";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import SdkConfig from "../../../SdkConfig";
 import MultiInviter from "../../../utils/MultiInviter";
-import E2EIcon from "../rooms/E2EIcon";
 import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
 import { textualPowerLevel } from "../../../Roles";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
 import EncryptionPanel from "./EncryptionPanel";
 import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
-import { verifyDevice, verifyUser } from "../../../verification";
+import { verifyUser } from "../../../verification";
 import { Action } from "../../../dispatcher/actions";
 import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
 import BaseCard from "./BaseCard";
-import { E2EStatus } from "../../../utils/ShieldUtils";
 import ImageView from "../elements/ImageView";
 import Spinner from "../elements/Spinner";
 import PowerSelector from "../elements/PowerSelector";
@@ -68,23 +67,23 @@ import ErrorDialog from "../dialogs/ErrorDialog";
 import QuestionDialog from "../dialogs/QuestionDialog";
 import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
 import { mediaFromMxc } from "../../../customisations/Media";
-import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
+import { type ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
 import ConfirmSpaceUserActionDialog from "../dialogs/ConfirmSpaceUserActionDialog";
 import { bulkSpaceBehaviour } from "../../../utils/space";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
 import { UIComponent } from "../../../settings/UIFeature";
 import { TimelineRenderingType } from "../../../contexts/RoomContext";
 import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
-import { IRightPanelCardState } from "../../../stores/right-panel/RightPanelStoreIPanelState";
+import { type IRightPanelCardState } from "../../../stores/right-panel/RightPanelStoreIPanelState";
 import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
 import PosthogTrackers from "../../../PosthogTrackers";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { DirectoryMember, startDmOnFirstMessage } from "../../../utils/direct-messages";
 import { SdkContextClass } from "../../../contexts/SDKContext";
-import { asyncSome } from "../../../utils/arrays";
 import { Flex } from "../../utils/Flex";
 import CopyableText from "../elements/CopyableText";
 import { useUserTimezone } from "../../../hooks/useUserTimezone";
+
 export interface IDevice extends Device {
     ambiguous?: boolean;
 }
@@ -106,32 +105,6 @@ export const disambiguateDevices = (devices: IDevice[]): void => {
     }
 };
 
-export const getE2EStatus = async (
-    cli: MatrixClient,
-    userId: string,
-    devices: IDevice[],
-): Promise<E2EStatus | undefined> => {
-    const crypto = cli.getCrypto();
-    if (!crypto) return undefined;
-    const isMe = userId === cli.getUserId();
-    const userTrust = await crypto.getUserVerificationStatus(userId);
-    if (!userTrust.isCrossSigningVerified()) {
-        return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
-    }
-
-    const anyDeviceUnverified = await asyncSome(devices, async (device) => {
-        const { deviceId } = device;
-        // For your own devices, we use the stricter check of cross-signing
-        // verification to encourage everyone to trust their own devices via
-        // cross-signing so that other users can then safely trust you.
-        // For other people's devices, the more general verified check that
-        // includes locally verified devices can be used.
-        const deviceTrust = await crypto.getDeviceVerificationStatus(userId, deviceId);
-        return isMe ? !deviceTrust?.crossSigningVerified : !deviceTrust?.isVerified();
-    });
-    return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
-};
-
 /**
  * Converts the member to a DirectoryMember and starts a DM with them.
  */
@@ -145,251 +118,6 @@ async function openDmForUser(matrixClient: MatrixClient, user: Member): Promise<
     await startDmOnFirstMessage(matrixClient, [startDmUser]);
 }
 
-type SetUpdating = (updating: boolean) => void;
-
-function useHasCrossSigningKeys(
-    cli: MatrixClient,
-    member: User,
-    canVerify: boolean,
-    setUpdating: SetUpdating,
-): boolean | undefined {
-    return useAsyncMemo(async () => {
-        if (!canVerify) {
-            return undefined;
-        }
-        setUpdating(true);
-        try {
-            return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
-        } finally {
-            setUpdating(false);
-        }
-    }, [cli, member, canVerify]);
-}
-
-/**
- * Display one device and the related actions
- * @param userId current user id
- * @param device device to display
- * @param isUserVerified false when the user is not verified
- * @constructor
- */
-export function DeviceItem({
-    userId,
-    device,
-    isUserVerified,
-}: {
-    userId: string;
-    device: IDevice;
-    isUserVerified: boolean;
-}): JSX.Element {
-    const cli = useContext(MatrixClientContext);
-    const isMe = userId === cli.getUserId();
-
-    /** is the device verified? */
-    const isVerified = useAsyncMemo(async () => {
-        const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, device.deviceId);
-        if (!deviceTrust) return false;
-
-        // For your own devices, we use the stricter check of cross-signing
-        // verification to encourage everyone to trust their own devices via
-        // cross-signing so that other users can then safely trust you.
-        // For other people's devices, the more general verified check that
-        // includes locally verified devices can be used.
-        return isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified();
-    }, [cli, userId, device]);
-
-    const classes = classNames("mx_UserInfo_device", {
-        mx_UserInfo_device_verified: isVerified,
-        mx_UserInfo_device_unverified: !isVerified,
-    });
-    const iconClasses = classNames("mx_E2EIcon", {
-        mx_E2EIcon_normal: !isUserVerified,
-        mx_E2EIcon_verified: isVerified,
-        mx_E2EIcon_warning: isUserVerified && !isVerified,
-    });
-
-    const onDeviceClick = (): void => {
-        const user = cli.getUser(userId);
-        if (user) {
-            verifyDevice(cli, user, device);
-        }
-    };
-
-    let deviceName;
-    if (!device.displayName?.trim()) {
-        deviceName = device.deviceId;
-    } else {
-        deviceName = device.ambiguous ? device.displayName + " (" + device.deviceId + ")" : device.displayName;
-    }
-
-    let trustedLabel: string | undefined;
-    if (isUserVerified) trustedLabel = isVerified ? _t("common|trusted") : _t("common|not_trusted");
-
-    if (isVerified === undefined) {
-        // we're still deciding if the device is verified
-        return <div className={classes} title={device.deviceId} />;
-    } else if (isVerified) {
-        return (
-            <div className={classes} title={device.deviceId}>
-                <div className={iconClasses} />
-                <div className="mx_UserInfo_device_name">{deviceName}</div>
-                <div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
-            </div>
-        );
-    } else {
-        return (
-            <AccessibleButton
-                className={classes}
-                title={device.deviceId}
-                aria-label={deviceName}
-                onClick={onDeviceClick}
-            >
-                <div className={iconClasses} />
-                <div className="mx_UserInfo_device_name">{deviceName}</div>
-                <div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
-            </AccessibleButton>
-        );
-    }
-}
-
-/**
- * Display a list of devices
- * @param devices devices to display
- * @param userId current user id
- * @param loading displays a spinner instead of the device section
- * @param isUserVerified is false when
- *  - the user is not verified, or
- *  - `MatrixClient.getCrypto.getUserVerificationStatus` async call is in progress (in which case `loading` will also be `true`)
- * @constructor
- */
-function DevicesSection({
-    devices,
-    userId,
-    loading,
-    isUserVerified,
-}: {
-    devices: IDevice[];
-    userId: string;
-    loading: boolean;
-    isUserVerified: boolean;
-}): JSX.Element {
-    const cli = useContext(MatrixClientContext);
-
-    const [isExpanded, setExpanded] = useState(false);
-
-    const deviceTrusts = useAsyncMemo(() => {
-        const cryptoApi = cli.getCrypto();
-        if (!cryptoApi) return Promise.resolve(undefined);
-        return Promise.all(devices.map((d) => cryptoApi.getDeviceVerificationStatus(userId, d.deviceId)));
-    }, [cli, userId, devices]);
-
-    if (loading || deviceTrusts === undefined) {
-        // still loading
-        return <Spinner />;
-    }
-    const isMe = userId === cli.getUserId();
-
-    let expandSectionDevices: IDevice[] = [];
-    const unverifiedDevices: IDevice[] = [];
-
-    let expandCountCaption;
-    let expandHideCaption;
-    let expandIconClasses = "mx_E2EIcon";
-
-    const dehydratedDeviceIds: string[] = [];
-    for (const device of devices) {
-        if (device.dehydrated) {
-            dehydratedDeviceIds.push(device.deviceId);
-        }
-    }
-    // If the user has exactly one device marked as dehydrated, we consider
-    // that as the dehydrated device, and hide it as a normal device (but
-    // indicate that the user is using a dehydrated device).  If the user has
-    // more than one, that is anomalous, and we show all the devices so that
-    // nothing is hidden.
-    const dehydratedDeviceId: string | undefined = dehydratedDeviceIds.length == 1 ? dehydratedDeviceIds[0] : undefined;
-    let dehydratedDeviceInExpandSection = false;
-
-    if (isUserVerified) {
-        for (let i = 0; i < devices.length; ++i) {
-            const device = devices[i];
-            const deviceTrust = deviceTrusts[i];
-            // For your own devices, we use the stricter check of cross-signing
-            // verification to encourage everyone to trust their own devices via
-            // cross-signing so that other users can then safely trust you.
-            // For other people's devices, the more general verified check that
-            // includes locally verified devices can be used.
-            const isVerified = deviceTrust && (isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified());
-
-            if (isVerified) {
-                // don't show dehydrated device as a normal device, if it's
-                // verified
-                if (device.deviceId === dehydratedDeviceId) {
-                    dehydratedDeviceInExpandSection = true;
-                } else {
-                    expandSectionDevices.push(device);
-                }
-            } else {
-                unverifiedDevices.push(device);
-            }
-        }
-        expandCountCaption = _t("user_info|count_of_verified_sessions", { count: expandSectionDevices.length });
-        expandHideCaption = _t("user_info|hide_verified_sessions");
-        expandIconClasses += " mx_E2EIcon_verified";
-    } else {
-        if (dehydratedDeviceId) {
-            devices = devices.filter((device) => device.deviceId !== dehydratedDeviceId);
-            dehydratedDeviceInExpandSection = true;
-        }
-        expandSectionDevices = devices;
-        expandCountCaption = _t("user_info|count_of_sessions", { count: devices.length });
-        expandHideCaption = _t("user_info|hide_sessions");
-        expandIconClasses += " mx_E2EIcon_normal";
-    }
-
-    let expandButton;
-    if (expandSectionDevices.length) {
-        if (isExpanded) {
-            expandButton = (
-                <AccessibleButton kind="link" className="mx_UserInfo_expand" onClick={() => setExpanded(false)}>
-                    <div>{expandHideCaption}</div>
-                </AccessibleButton>
-            );
-        } else {
-            expandButton = (
-                <AccessibleButton kind="link" className="mx_UserInfo_expand" onClick={() => setExpanded(true)}>
-                    <div className={expandIconClasses} />
-                    <div>{expandCountCaption}</div>
-                </AccessibleButton>
-            );
-        }
-    }
-
-    let deviceList = unverifiedDevices.map((device, i) => {
-        return <DeviceItem key={i} userId={userId} device={device} isUserVerified={isUserVerified} />;
-    });
-    if (isExpanded) {
-        const keyStart = unverifiedDevices.length;
-        deviceList = deviceList.concat(
-            expandSectionDevices.map((device, i) => {
-                return (
-                    <DeviceItem key={i + keyStart} userId={userId} device={device} isUserVerified={isUserVerified} />
-                );
-            }),
-        );
-        if (dehydratedDeviceInExpandSection) {
-            deviceList.push(<div>{_t("user_info|dehydrated_device_enabled")}</div>);
-        }
-    }
-
-    return (
-        <div className="mx_UserInfo_devices">
-            <div>{deviceList}</div>
-            <div>{expandButton}</div>
-        </div>
-    );
-}
-
 const MessageButton = ({ member }: { member: Member }): JSX.Element => {
     const cli = useContext(MatrixClientContext);
     const [busy, setBusy] = useState(false);
@@ -580,8 +308,10 @@ export const warnSelfDemote = async (isSpace: boolean): Promise<boolean> => {
 
 const Container: React.FC<{
     children: ReactNode;
-}> = ({ children }) => {
-    return <div className="mx_UserInfo_container">{children}</div>;
+    className?: string;
+}> = ({ children, className }) => {
+    const classes = classNames("mx_UserInfo_container", className);
+    return <div className={classes}>{children}</div>;
 };
 
 interface IPowerLevelsContent {
@@ -1397,12 +1127,84 @@ export const useDevices = (userId: string): IDevice[] | undefined | null => {
     return devices;
 };
 
+function useHasCrossSigningKeys(cli: MatrixClient, member: User, canVerify: boolean): boolean | undefined {
+    return useAsyncMemo(async () => {
+        if (!canVerify) return undefined;
+        return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
+    }, [cli, member, canVerify]);
+}
+
+const VerificationSection: React.FC<{
+    member: User | RoomMember;
+    devices: IDevice[];
+}> = ({ member, devices }) => {
+    const cli = useContext(MatrixClientContext);
+    let content;
+    const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
+
+    const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
+        async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
+        [member.userId],
+        // the user verification status is not initialized
+        undefined,
+    );
+    const hasUserVerificationStatus = Boolean(userTrust);
+    const isUserVerified = Boolean(userTrust?.isVerified());
+    const isMe = member.userId === cli.getUserId();
+    const canVerify =
+        hasUserVerificationStatus &&
+        homeserverSupportsCrossSigning &&
+        !isUserVerified &&
+        !isMe &&
+        devices &&
+        devices.length > 0;
+
+    const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify);
+
+    if (isUserVerified) {
+        content = (
+            <Badge kind="green" className="mx_UserInfo_verified_badge">
+                <VerifiedIcon className="mx_UserInfo_verified_icon" height="16px" width="16px" />
+                <Text size="sm" weight="medium" className="mx_UserInfo_verified_label">
+                    {_t("common|verified")}
+                </Text>
+            </Badge>
+        );
+    } else if (hasCrossSigningKeys === undefined) {
+        // We are still fetching the cross-signing keys for the user, show spinner.
+        content = <InlineSpinner size={24} />;
+    } else if (canVerify && hasCrossSigningKeys) {
+        content = (
+            <div className="mx_UserInfo_container_verifyButton">
+                <Button
+                    className="mx_UserInfo_verify_button"
+                    kind="tertiary"
+                    size="sm"
+                    onClick={() => verifyUser(cli, member as User)}
+                >
+                    {_t("user_info|verify_button")}
+                </Button>
+            </div>
+        );
+    } else {
+        content = (
+            <Text className="mx_UserInfo_verification_unavailable" size="sm">
+                ({_t("user_info|verification_unavailable")})
+            </Text>
+        );
+    }
+
+    return (
+        <Flex justify="center" align="center" className="mx_UserInfo_verification">
+            {content}
+        </Flex>
+    );
+};
+
 const BasicUserInfo: React.FC<{
     room: Room;
     member: User | RoomMember;
-    devices: IDevice[];
-    isRoomEncrypted: boolean;
-}> = ({ room, member, devices, isRoomEncrypted }) => {
+}> = ({ room, member }) => {
     const cli = useContext(MatrixClientContext);
 
     const powerLevels = useRoomPowerLevels(cli, room);
@@ -1500,111 +1302,10 @@ const BasicUserInfo: React.FC<{
         spinner = <Spinner />;
     }
 
-    // only display the devices list if our client supports E2E
-    const cryptoEnabled = Boolean(cli.getCrypto());
-
-    let text;
-    if (!isRoomEncrypted) {
-        if (!cryptoEnabled) {
-            text = _t("encryption|unsupported");
-        } else if (room && !room.isSpaceRoom()) {
-            text = _t("user_info|room_unencrypted");
-        }
-    } else if (!room.isSpaceRoom()) {
-        text = _t("user_info|room_encrypted");
-    }
-
-    let verifyButton;
-    const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
-
-    const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
-        async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
-        [member.userId],
-        // the user verification status is not initialized
-        undefined,
-    );
-    const hasUserVerificationStatus = Boolean(userTrust);
-    const isUserVerified = Boolean(userTrust?.isVerified());
     const isMe = member.userId === cli.getUserId();
-    const canVerify =
-        hasUserVerificationStatus &&
-        homeserverSupportsCrossSigning &&
-        !isUserVerified &&
-        !isMe &&
-        devices &&
-        devices.length > 0;
-
-    const setUpdating: SetUpdating = (updating) => {
-        setPendingUpdateCount((count) => count + (updating ? 1 : -1));
-    };
-    const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify, setUpdating);
-
-    // Display the spinner only when
-    // - the devices are not populated yet, or
-    // - the crypto is available and we don't have the user verification status yet
-    const showDeviceListSpinner = (cryptoEnabled && !hasUserVerificationStatus) || devices === undefined;
-    if (canVerify) {
-        if (hasCrossSigningKeys !== undefined) {
-            // Note: mx_UserInfo_verifyButton is for the end-to-end tests
-            verifyButton = (
-                <div className="mx_UserInfo_container_verifyButton">
-                    <AccessibleButton
-                        kind="link"
-                        className="mx_UserInfo_field mx_UserInfo_verifyButton"
-                        onClick={() => verifyUser(cli, member as User)}
-                    >
-                        {_t("action|verify")}
-                    </AccessibleButton>
-                </div>
-            );
-        } else if (!showDeviceListSpinner) {
-            // HACK: only show a spinner if the device section spinner is not shown,
-            // to avoid showing a double spinner
-            // We should ask for a design that includes all the different loading states here
-            verifyButton = <Spinner />;
-        }
-    }
-
-    let editDevices;
-    if (member.userId == cli.getUserId()) {
-        editDevices = (
-            <div>
-                <AccessibleButton
-                    kind="link"
-                    className="mx_UserInfo_field"
-                    onClick={() => {
-                        dis.dispatch({
-                            action: Action.ViewUserDeviceSettings,
-                        });
-                    }}
-                >
-                    {_t("user_info|edit_own_devices")}
-                </AccessibleButton>
-            </div>
-        );
-    }
-
-    const securitySection = (
-        <Container>
-            <h2>{_t("common|security")}</h2>
-            <p>{text}</p>
-            {verifyButton}
-            {cryptoEnabled && (
-                <DevicesSection
-                    loading={showDeviceListSpinner}
-                    devices={devices}
-                    userId={member.userId}
-                    isUserVerified={isUserVerified}
-                />
-            )}
-            {editDevices}
-        </Container>
-    );
 
     return (
         <React.Fragment>
-            {securitySection}
-
             <UserOptionsSection
                 canInvite={roomPermissions.canInvite}
                 member={member as RoomMember}
@@ -1612,15 +1313,12 @@ const BasicUserInfo: React.FC<{
             >
                 {memberDetails}
             </UserOptionsSection>
-
             {adminToolsContainer}
-
             {!isMe && (
                 <Container>
                     <IgnoreToggleButton member={member} />
                 </Container>
             )}
-
             {spinner}
         </React.Fragment>
     );
@@ -1630,9 +1328,10 @@ export type Member = User | RoomMember;
 
 export const UserInfoHeader: React.FC<{
     member: Member;
-    e2eStatus?: E2EStatus;
+    devices: IDevice[];
     roomId?: string;
-}> = ({ member, e2eStatus, roomId }) => {
+    hideVerificationSection?: boolean;
+}> = ({ member, devices, roomId, hideVerificationSection }) => {
     const cli = useContext(MatrixClientContext);
 
     const onMemberAvatarClick = useCallback(() => {
@@ -1683,7 +1382,6 @@ export const UserInfoHeader: React.FC<{
 
     const timezoneInfo = useUserTimezone(cli, member.userId);
 
-    const e2eIcon = e2eStatus ? <E2EIcon size={18} status={e2eStatus} isUser={true} /> : null;
     const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, {
         roomId,
         withDisplayName: true,
@@ -1707,22 +1405,21 @@ export const UserInfoHeader: React.FC<{
                 </div>
             </div>
 
-            <Container>
+            <Container className="mx_UserInfo_header">
                 <Flex direction="column" align="center" className="mx_UserInfo_profile">
                     <Heading size="sm" weight="semibold" as="h1" dir="auto">
-                        <Flex direction="row-reverse" align="center">
+                        <Flex className="mx_UserInfo_profile_name" direction="row-reverse" align="center">
                             {displayName}
-                            {e2eIcon}
                         </Flex>
                     </Heading>
                     {presenceLabel}
                     {timezoneInfo && (
                         <Tooltip label={timezoneInfo?.timezone ?? ""}>
-                            <span className="mx_UserInfo_timezone">
+                            <Flex align="center" className="mx_UserInfo_timezone">
                                 <Text size="sm" weight="regular">
                                     {timezoneInfo?.friendly ?? ""}
                                 </Text>
-                            </span>
+                            </Flex>
                         </Tooltip>
                     )}
                     <Text size="sm" weight="semibold" className="mx_UserInfo_profile_mxid">
@@ -1731,6 +1428,7 @@ export const UserInfoHeader: React.FC<{
                         </CopyableText>
                     </Text>
                 </Flex>
+                {!hideVerificationSection && <VerificationSection member={member} devices={devices} />}
             </Container>
         </React.Fragment>
     );
@@ -1754,13 +1452,6 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
     const isRoomEncrypted = useIsEncrypted(cli, room);
     const devices = useDevices(user.userId) ?? [];
 
-    const e2eStatus = useAsyncMemo(async () => {
-        if (!isRoomEncrypted || !devices) {
-            return undefined;
-        }
-        return await getE2EStatus(cli, user.userId, devices);
-    }, [cli, isRoomEncrypted, user.userId, devices]);
-
     const classes = ["mx_UserInfo"];
 
     let cardState: IRightPanelCardState = {};
@@ -1776,14 +1467,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
     let content: JSX.Element | undefined;
     switch (phase) {
         case RightPanelPhases.MemberInfo:
-            content = (
-                <BasicUserInfo
-                    room={room as Room}
-                    member={member as User}
-                    devices={devices}
-                    isRoomEncrypted={Boolean(isRoomEncrypted)}
-                />
-            );
+            content = <BasicUserInfo room={room as Room} member={member as User} />;
             break;
         case RightPanelPhases.EncryptionPanel:
             classes.push("mx_UserInfo_smallAvatar");
@@ -1808,7 +1492,12 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
 
     const header = (
         <>
-            <UserInfoHeader member={member} e2eStatus={e2eStatus} roomId={room?.roomId} />
+            <UserInfoHeader
+                hideVerificationSection={phase === RightPanelPhases.EncryptionPanel}
+                member={member}
+                devices={devices}
+                roomId={room?.roomId}
+            />
         </>
     );
 
diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx
index 2a1d8ce3c0a55555609f5a895d81c208c35837b5..1782a52a8855aac4ea71f4f02301b67308c9de70 100644
--- a/src/components/views/right_panel/VerificationPanel.tsx
+++ b/src/components/views/right_panel/VerificationPanel.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import {
-    ShowQrCodeCallbacks,
-    ShowSasCallbacks,
+    type ShowQrCodeCallbacks,
+    type ShowSasCallbacks,
     VerificationPhase as Phase,
-    VerificationRequest,
+    type VerificationRequest,
     VerificationRequestEvent,
     VerifierEvent,
 } from "matrix-js-sdk/src/crypto-api";
-import { Device, RoomMember, User } from "matrix-js-sdk/src/matrix";
+import { type Device, type RoomMember, type User } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { VerificationMethod } from "matrix-js-sdk/src/types";
 
diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx
index b1488aefb43bdea5ac6c71ef4cff0eb9613ee64c..bc6b778e264754c501d31febb48e52420da5d0d2 100644
--- a/src/components/views/right_panel/WidgetCard.tsx
+++ b/src/components/views/right_panel/WidgetCard.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext, useEffect } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext, useEffect } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import BaseCard from "./BaseCard";
diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx
index e76c8a2cd5524a88d4fdc2c5f71bb7aedda6578e..c7efbb61662d0aab5197be34ad973fa0713d69da 100644
--- a/src/components/views/room_settings/AliasSettings.tsx
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -6,10 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ContextType, createRef, SyntheticEvent } from "react";
-import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import React, {
+    type JSX,
+    type ToggleEvent,
+    type ChangeEvent,
+    type ContextType,
+    createRef,
+    type SyntheticEvent,
+} from "react";
+import { type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
+import { type RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
 
 import EditableItemList from "../elements/EditableItemList";
 import { _t } from "../../../languageHandler";
@@ -101,8 +108,8 @@ export default class AliasSettings extends React.Component<IProps, IState> {
         canSetCanonicalAlias: false,
     };
 
-    public constructor(props: IProps, context: ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         const state: IState = {
             altAliases: [],
@@ -278,9 +285,9 @@ export default class AliasSettings extends React.Component<IProps, IState> {
             });
     };
 
-    private onLocalAliasesToggled = (event: ChangeEvent<HTMLDetailsElement>): void => {
+    private onLocalAliasesToggled = (event: ToggleEvent<HTMLDetailsElement>): void => {
         // expanded
-        if (event.target.open) {
+        if (event.currentTarget.open) {
             // if local aliases haven't been preloaded yet at component mount
             if (!this.props.canSetCanonicalAlias && this.state.localAliases.length === 0) {
                 this.loadLocalAliases();
diff --git a/src/components/views/room_settings/RoomProfileSettings.tsx b/src/components/views/room_settings/RoomProfileSettings.tsx
index 8789ea48fc756b24a7f4b3f64393d48041e5c34f..e7674a109acbfaee388184869f80edd24a673cc2 100644
--- a/src/components/views/room_settings/RoomProfileSettings.tsx
+++ b/src/components/views/room_settings/RoomProfileSettings.tsx
@@ -7,15 +7,16 @@ Please see LICENSE files in the repository root for full details.
 
 import React, { createRef } from "react";
 import classNames from "classnames";
-import { EventType } from "matrix-js-sdk/src/matrix";
+import { ContentHelpers, EventType, type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import Field from "../elements/Field";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import AvatarSetting from "../settings/AvatarSetting";
 import { htmlSerializeFromMdIfNeeded } from "../../../editor/serialize";
-import { idNameForRoom } from "../avatars/RoomAvatar";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import { LocalRoom } from "../../../models/LocalRoom";
 
 interface IProps {
     roomId: string;
@@ -36,6 +37,19 @@ interface IState {
     canSetAvatar: boolean;
 }
 
+function idNameForRoom(room: Room): string {
+    const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
+    // If the room is a DM, we use the other user's ID for the color hash
+    // in order to match the room avatar with their avatar
+    if (dmMapUserId) return dmMapUserId;
+
+    if (room instanceof LocalRoom && room.targets.length === 1) {
+        return room.targets[0].userId;
+    }
+
+    return room.roomId;
+}
+
 // TODO: Merge with ProfileSettings?
 export default class RoomProfileSettings extends React.Component<IProps, IState> {
     private avatarUpload = createRef<HTMLInputElement>();
@@ -51,7 +65,7 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
         const avatarUrl = avatarEvent?.getContent()["url"] ?? null;
 
         const topicEvent = room.currentState.getStateEvents(EventType.RoomTopic, "");
-        const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()["topic"] : "";
+        const topic = (topicEvent && ContentHelpers.parseTopicContent(topicEvent.getContent()).text) || "";
 
         const nameEvent = room.currentState.getStateEvents(EventType.RoomName, "");
         const name = nameEvent && nameEvent.getContent() ? nameEvent.getContent()["name"] : "";
@@ -145,6 +159,8 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
 
         if (this.state.originalTopic !== this.state.topic) {
             const html = htmlSerializeFromMdIfNeeded(this.state.topic, { forceHTML: false });
+            // XXX: Note that we deliberately send an empty string on an empty topic rather
+            // than a clearer `undefined` value. Synapse still requires a string in a topic.
             await client.setRoomTopic(this.props.roomId, this.state.topic, html);
             newState.originalTopic = this.state.topic;
         }
diff --git a/src/components/views/room_settings/UrlPreviewSettings.tsx b/src/components/views/room_settings/UrlPreviewSettings.tsx
index 9c7dcabbc64854af0740bf0b3c64c56e084e1510..d200f487ebb95b41d20131c5b8b1913eeaeae8a6 100644
--- a/src/components/views/room_settings/UrlPreviewSettings.tsx
+++ b/src/components/views/room_settings/UrlPreviewSettings.tsx
@@ -9,8 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, JSX } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode, type JSX } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { InlineSpinner } from "@vector-im/compound-web";
 
 import { _t } from "../../../languageHandler";
@@ -20,7 +20,7 @@ import { Action } from "../../../dispatcher/actions";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import SettingsFlag from "../elements/SettingsFlag";
 import SettingsFieldset from "../settings/SettingsFieldset";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
 import { useSettingValueAt } from "../../../hooks/useSettings.ts";
diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx
index 36df51f4cbbbc3fd6de5e0b500e6a0219ba992b2..a5c869c0c34d730b9b513dcddee3c5fef5c05360 100644
--- a/src/components/views/rooms/AppsDrawer.tsx
+++ b/src/components/views/rooms/AppsDrawer.tsx
@@ -6,25 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { AriaRole } from "react";
+import React, { type AriaRole } from "react";
 import classNames from "classnames";
-import { Resizable, Size } from "re-resizable";
-import { Room } from "matrix-js-sdk/src/matrix";
-import { IWidget } from "matrix-widget-api";
+import { Resizable, type Size } from "re-resizable";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import { type IWidget } from "matrix-widget-api";
 
 import AppTile from "../elements/AppTile";
 import dis from "../../../dispatcher/dispatcher";
 import * as ScalarMessaging from "../../../ScalarMessaging";
 import WidgetUtils from "../../../utils/WidgetUtils";
 import WidgetEchoStore from "../../../stores/WidgetEchoStore";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 import ResizeHandle from "../elements/ResizeHandle";
-import Resizer, { IConfig } from "../../../resizer/resizer";
+import Resizer, { type IConfig } from "../../../resizer/resizer";
 import PercentageDistributor from "../../../resizer/distributors/percentage";
 import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
 import { clamp, percentageOf, percentageWithin } from "../../../utils/numbers";
 import UIStore from "../../../stores/UIStore";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import Spinner from "../elements/Spinner";
 import SdkConfig from "../../../SdkConfig";
 
diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx
index f77f394d8cc6a158d01cf0717bda579661a7eae5..a7a211e828963bf405188686343a7096666579e0 100644
--- a/src/components/views/rooms/Autocomplete.tsx
+++ b/src/components/views/rooms/Autocomplete.tsx
@@ -6,13 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, KeyboardEvent, RefObject } from "react";
+import React, { createRef, type RefObject } from "react";
 import classNames from "classnames";
 import { flatMap } from "lodash";
-import { Room } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import Autocompleter, { ICompletion, ISelectionRange, IProviderCompletions } from "../../../autocomplete/Autocompleter";
+import Autocompleter, {
+    type ICompletion,
+    type ISelectionRange,
+    type IProviderCompletions,
+} from "../../../autocomplete/Autocompleter";
 import SettingsStore from "../../../settings/SettingsStore";
 import RoomContext from "../../../contexts/RoomContext";
 
@@ -46,13 +49,13 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
     public queryRequested?: string;
     public debounceCompletionsRequest?: number;
     private containerRef = createRef<HTMLDivElement>();
-    private completionRefs: Record<string, RefObject<HTMLElement>> = {};
+    private completionRefs: Record<string, RefObject<HTMLElement | null>> = {};
 
     public static contextType = RoomContext;
     declare public context: React.ContextType<typeof RoomContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             // list of completionResults, each containing completions
@@ -173,7 +176,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
             }
         }
 
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         this.setState(
             {
                 completions,
@@ -206,7 +209,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
         this.setSelection(1 + index);
     }
 
-    public onEscape(e: KeyboardEvent): boolean | undefined {
+    public onEscape(e: KeyboardEvent | React.KeyboardEvent): boolean | undefined {
         const completionCount = this.countCompletions();
         if (completionCount === 0) {
             // autocomplete is already empty, so don't preventDefault
diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx
index 67a25d433db6bfcb6918097d31be97945c12b540..9e34a6f40d85aa9fb81ff975f2ad0a1065fc1e35 100644
--- a/src/components/views/rooms/AuxPanel.tsx
+++ b/src/components/views/rooms/AuxPanel.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import AppsDrawer from "./AppsDrawer";
 import SettingsStore from "../../../settings/SettingsStore";
 import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
 import { UIFeature } from "../../../settings/UIFeature";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 import LegacyCallViewForRoom from "../voip/LegacyCallViewForRoom";
 import { objectHasDiff } from "../../../utils/objects";
 
diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx
index ffa5e99f927903a08df299041761c180d6269440..23111939c2ef219382aa57c2530674b33ffb6468 100644
--- a/src/components/views/rooms/BasicMessageComposer.tsx
+++ b/src/components/views/rooms/BasicMessageComposer.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { createRef, ClipboardEvent, SyntheticEvent } from "react";
-import { Room, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, createRef, type ClipboardEvent, type SyntheticEvent } from "react";
+import { type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import EMOTICON_REGEX from "emojibase-regex/emoticon";
 import { logger } from "matrix-js-sdk/src/logger";
 import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings";
 
-import EditorModel from "../../../editor/model";
+import type EditorModel from "../../../editor/model";
 import HistoryManager from "../../../editor/history";
-import { Caret, setSelection } from "../../../editor/caret";
+import { type Caret, setSelection } from "../../../editor/caret";
 import {
     formatRange,
     formatRangeAsLink,
@@ -24,7 +24,7 @@ import {
 } from "../../../editor/operations";
 import { getCaretOffsetAndText, getRangeForSelection } from "../../../editor/dom";
 import Autocomplete, { generateCompletionDomId } from "../rooms/Autocomplete";
-import { getAutoCompleteCreator, Part, SerializedPart, Type } from "../../../editor/parts";
+import { getAutoCompleteCreator, type Part, type SerializedPart, Type } from "../../../editor/parts";
 import { parseEvent, parsePlainTextMessage } from "../../../editor/deserialize";
 import { renderModel } from "../../../editor/render";
 import SettingsStore from "../../../settings/SettingsStore";
@@ -32,11 +32,11 @@ import { IS_MAC, Key } from "../../../Keyboard";
 import { CommandCategories, CommandMap, parseCommandString } from "../../../SlashCommands";
 import Range from "../../../editor/range";
 import MessageComposerFormatBar, { Formatting } from "./MessageComposerFormatBar";
-import DocumentOffset from "../../../editor/offset";
-import { IDiff } from "../../../editor/diff";
-import AutocompleteWrapperModel from "../../../editor/autocomplete";
-import DocumentPosition from "../../../editor/position";
-import { ICompletion } from "../../../autocomplete/Autocompleter";
+import type DocumentOffset from "../../../editor/offset";
+import { type IDiff } from "../../../editor/diff";
+import type AutocompleteWrapperModel from "../../../editor/autocomplete";
+import type DocumentPosition from "../../../editor/position";
+import { type ICompletion } from "../../../autocomplete/Autocompleter";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/rooms/CollapsibleButton.tsx b/src/components/views/rooms/CollapsibleButton.tsx
index f1387a7a1ba6d9a94647967cf5c15d24d4c6ad03..60d82eb27fb456bbc2c84f7cbe9550a0d5b5a27c 100644
--- a/src/components/views/rooms/CollapsibleButton.tsx
+++ b/src/components/views/rooms/CollapsibleButton.tsx
@@ -6,16 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext } from "react";
+import React, { type RefObject, useContext } from "react";
 import classNames from "classnames";
 
-import AccessibleButton, { ButtonProps } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "../elements/AccessibleButton";
 import { OverflowMenuContext } from "./MessageComposerButtons";
 import { IconizedContextMenuOption } from "../context_menus/IconizedContextMenu";
-import { Ref } from "../../../accessibility/roving/types";
 
 interface Props extends Omit<ButtonProps<"div">, "element"> {
-    inputRef?: Ref;
+    inputRef?: RefObject<HTMLElement | null>;
     title: string;
     iconClassName: string;
 }
diff --git a/src/components/views/rooms/E2EIcon.tsx b/src/components/views/rooms/E2EIcon.tsx
index 8542ad4b4bdfd423b1b90545c76e6e912d3d7478..e4f19762ff3bafdeddb90ab6ce1f29f22d533617 100644
--- a/src/components/views/rooms/E2EIcon.tsx
+++ b/src/components/views/rooms/E2EIcon.tsx
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, CSSProperties } from "react";
+import React, { type JSX, type ComponentProps, type CSSProperties } from "react";
 import classNames from "classnames";
 import { Tooltip } from "@vector-im/compound-web";
 
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
 import { E2EStatus } from "../../../utils/ShieldUtils";
 
diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx
index dcae21535283e5a9f09a4651de14990fc958e62b..92d60b7571112cac5c527082f49ef003335223cc 100644
--- a/src/components/views/rooms/EditMessageComposer.tsx
+++ b/src/components/views/rooms/EditMessageComposer.tsx
@@ -6,12 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, KeyboardEvent } from "react";
+import React, { createRef, type KeyboardEvent } from "react";
 import classNames from "classnames";
-import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix";
+import { EventStatus, type MatrixEvent, type Room, MsgType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
-import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
+import { type Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
+import {
+    type ReplacementEvent,
+    type RoomMessageEventContent,
+    type RoomMessageTextEventContent,
+} from "matrix-js-sdk/src/types";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
@@ -20,25 +24,25 @@ import { getCaretOffsetAndText } from "../../../editor/dom";
 import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from "../../../editor/serialize";
 import { findEditableEvent } from "../../../utils/EventUtils";
 import { parseEvent } from "../../../editor/deserialize";
-import { CommandPartCreator, Part, PartCreator, SerializedPart } from "../../../editor/parts";
-import EditorStateTransfer from "../../../utils/EditorStateTransfer";
+import { CommandPartCreator, type Part, type PartCreator, type SerializedPart } from "../../../editor/parts";
+import type EditorStateTransfer from "../../../utils/EditorStateTransfer";
 import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
 import { CommandCategories } from "../../../SlashCommands";
 import { Action } from "../../../dispatcher/actions";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import SendHistoryManager from "../../../SendHistoryManager";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import AccessibleButton from "../elements/AccessibleButton";
 import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
 import SettingsStore from "../../../settings/SettingsStore";
-import { withMatrixClientHOC, MatrixClientProps } from "../../../contexts/MatrixClientContext";
+import { withMatrixClientHOC, type MatrixClientProps } from "../../../contexts/MatrixClientContext";
 import RoomContext from "../../../contexts/RoomContext";
 import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload";
 import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { PosthogAnalytics } from "../../../PosthogAnalytics";
 import { editorRoomKey, editorStateKey } from "../../../Editing";
-import DocumentOffset from "../../../editor/offset";
+import type DocumentOffset from "../../../editor/offset";
 import { attachMentions, attachRelation } from "./SendMessageComposer";
 import { filterBoolean } from "../../../utils/arrays";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
diff --git a/src/components/views/rooms/EmojiButton.tsx b/src/components/views/rooms/EmojiButton.tsx
index 79184486b1404ad20a83a037a9dd315d77f44651..9da01ae92f27d0d3305b340d923aba9fb8a2c015 100644
--- a/src/components/views/rooms/EmojiButton.tsx
+++ b/src/components/views/rooms/EmojiButton.tsx
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { useContext } from "react";
+import React, { type JSX, useContext } from "react";
 
 import { _t } from "../../../languageHandler";
-import ContextMenu, { aboveLeftOf, MenuProps, useContextMenu } from "../../structures/ContextMenu";
+import ContextMenu, { aboveLeftOf, type MenuProps, useContextMenu } from "../../structures/ContextMenu";
 import EmojiPicker from "../emojipicker/EmojiPicker";
 import { CollapsibleButton } from "./CollapsibleButton";
 import { OverflowMenuContext } from "./MessageComposerButtons";
diff --git a/src/components/views/rooms/EventPreview.tsx b/src/components/views/rooms/EventPreview.tsx
index 8e7f53a33016346080760225cf0a73b03ab4fecf..bf44211d29aefcb1ff912601a796088e32ce1edd 100644
--- a/src/components/views/rooms/EventPreview.tsx
+++ b/src/components/views/rooms/EventPreview.tsx
@@ -6,8 +6,8 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { HTMLProps, JSX, useContext, useState } from "react";
-import { IContent, M_POLL_START, MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix";
+import React, { type HTMLProps, type JSX, useContext, useState } from "react";
+import { type IContent, M_POLL_START, type MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 1d2983dd0307948f14c98cb20520801d78708b5d..2c10d0afd962aecd35bce400856758b36f78e684 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -7,21 +7,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, forwardRef, JSX, MouseEvent, ReactNode } from "react";
+import React, { createRef, type JSX, type Ref, type MouseEvent, type ReactNode } from "react";
 import classNames from "classnames";
 import {
     EventStatus,
     EventType,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
     MsgType,
-    NotificationCountType,
-    Relations,
-    RelationType,
-    Room,
+    type NotificationCountType,
+    type Relations,
+    type RelationType,
+    type Room,
     RoomEvent,
-    RoomMember,
-    Thread,
+    type RoomMember,
+    type Thread,
     ThreadEvent,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
@@ -31,7 +31,7 @@ import {
     DecryptionFailureCode,
     EventShieldColour,
     EventShieldReason,
-    UserVerificationStatus,
+    type UserVerificationStatus,
 } from "matrix-js-sdk/src/crypto-api";
 import { Tooltip } from "@vector-im/compound-web";
 
@@ -46,35 +46,35 @@ import RoomAvatar from "../avatars/RoomAvatar";
 import MessageContextMenu from "../context_menus/MessageContextMenu";
 import { aboveRightOf } from "../../structures/ContextMenu";
 import { objectHasDiff } from "../../../utils/objects";
-import EditorStateTransfer from "../../../utils/EditorStateTransfer";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import type EditorStateTransfer from "../../../utils/EditorStateTransfer";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
 import NotificationBadge from "./NotificationBadge";
-import LegacyCallEventGrouper from "../../structures/LegacyCallEventGrouper";
-import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
+import type LegacyCallEventGrouper from "../../structures/LegacyCallEventGrouper";
+import { type ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
 import { Action } from "../../../dispatcher/actions";
 import PlatformPeg from "../../../PlatformPeg";
 import MemberAvatar from "../avatars/MemberAvatar";
 import SenderProfile from "../messages/SenderProfile";
 import MessageTimestamp from "../messages/MessageTimestamp";
-import { IReadReceiptPosition } from "./ReadReceiptMarker";
+import { type IReadReceiptPosition } from "./ReadReceiptMarker";
 import MessageActionBar from "../messages/MessageActionBar";
 import ReactionsRow from "../messages/ReactionsRow";
 import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import { MediaEventHelper } from "../../../utils/MediaEventHelper";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import { copyPlaintext, getSelectedText } from "../../../utils/strings";
 import { DecryptionFailureTracker } from "../../../DecryptionFailureTracker";
 import RedactedBody from "../messages/RedactedBody";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { shouldDisplayReply } from "../../../utils/Reply";
 import PosthogTrackers from "../../../PosthogTrackers";
 import TileErrorBoundary from "../messages/TileErrorBoundary";
 import { haveRendererForEvent, isMessageEvent, renderTile } from "../../../events/EventTileFactory";
 import ThreadSummary, { ThreadMessagePreview } from "./ThreadSummary";
 import { ReadReceiptGroup } from "./ReadReceiptGroup";
-import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
+import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
 import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
 import { ElementCall } from "../../../models/Call";
 import { UnreadNotificationBadge } from "./NotificationBadge/UnreadNotificationBadge";
@@ -157,8 +157,7 @@ export interface EventTileProps {
     // is this the focused event
     isSelectedEvent?: boolean;
 
-    // callback called when dynamic content in events are loaded
-    onHeightChanged?: () => void;
+    resizeObserver?: ResizeObserver;
 
     // a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'.
     readReceipts?: IReadReceiptProps[];
@@ -229,6 +228,8 @@ export interface EventTileProps {
     // The following properties are used by EventTilePreview to disable tab indexes within the event tile
     hideTimestamp?: boolean;
     inhibitInteraction?: boolean;
+
+    ref?: Ref<UnwrappedEventTile>;
 }
 
 interface IState {
@@ -289,8 +290,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
     public readonly ref = createRef<HTMLElement>();
 
     public static defaultProps = {
-        // no-op function because onHeightChanged is optional yet some sub-components assume its existence
-        onHeightChanged: function () {},
         forExport: false,
         layout: Layout.Group,
     };
@@ -443,14 +442,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
         }
         this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
         this.unmounted = false;
+        if (this.props.resizeObserver && this.ref.current) this.props.resizeObserver.unobserve(this.ref.current);
     }
 
     public componentDidUpdate(prevProps: Readonly<EventTileProps>, prevState: Readonly<IState>): void {
-        // If the shield state changed, the height might have changed.
-        // XXX: does the shield *actually* cause a change in height? Not sure.
-        if (prevState.shieldColour !== this.state.shieldColour && this.props.onHeightChanged) {
-            this.props.onHeightChanged();
-        }
         // If we're not listening for receipts and expect to be, register a listener.
         if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) {
             MatrixClientPeg.safeGet().on(RoomEvent.Receipt, this.onRoomReceipt);
@@ -460,6 +455,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
         if (prevProps.eventSendStatus !== this.props.eventSendStatus) {
             this.verifyEvent();
         }
+
+        if (this.props.resizeObserver && this.ref.current) this.props.resizeObserver.observe(this.ref.current);
     }
 
     private onNewThread = (thread: Thread): void => {
@@ -564,8 +561,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
     private onDecrypted = (): void => {
         // we need to re-verify the sending device.
         this.verifyEvent();
-        // decryption might, of course, trigger a height change, so call onHeightChanged after the re-render
-        this.forceUpdate(this.props.onHeightChanged);
+        this.forceUpdate();
     };
 
     private onUserVerificationChanged = (userId: string, _trustStatus: UserVerificationStatus): void => {
@@ -1201,7 +1197,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
             replyChain = (
                 <ReplyChain
                     parentEv={this.props.mxEvent}
-                    onHeightChanged={this.props.onHeightChanged}
                     ref={this.replyChain}
                     forExport={this.props.forExport}
                     permalinkCreator={this.props.permalinkCreator}
@@ -1254,7 +1249,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
                                     // appease TS
                                     highlights: this.props.highlights,
                                     highlightLink: this.props.highlightLink,
-                                    onHeightChanged: () => this.props.onHeightChanged,
                                     permalinkCreator: this.props.permalinkCreator!,
                                 },
                                 this.context.showHiddenEvents,
@@ -1401,7 +1395,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
                                     // appease TS
                                     highlights: this.props.highlights,
                                     highlightLink: this.props.highlightLink,
-                                    onHeightChanged: this.props.onHeightChanged,
                                     permalinkCreator: this.props.permalinkCreator,
                                 },
                                 this.context.showHiddenEvents,
@@ -1453,7 +1446,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
                                     // appease TS
                                     highlights: this.props.highlights,
                                     highlightLink: this.props.highlightLink,
-                                    onHeightChanged: this.props.onHeightChanged,
                                     permalinkCreator: this.props.permalinkCreator,
                                 },
                                 this.context.showHiddenEvents,
@@ -1492,15 +1484,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 }
 
 // Wrap all event tiles with the tile error boundary so that any throws even during construction are captured
-const SafeEventTile = forwardRef<UnwrappedEventTile, EventTileProps>((props, ref) => {
+const SafeEventTile = (props: EventTileProps): JSX.Element => {
     return (
-        <>
-            <TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
-                <UnwrappedEventTile ref={ref} {...props} />
-            </TileErrorBoundary>
-        </>
+        <TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
+            <UnwrappedEventTile {...props} />
+        </TileErrorBoundary>
     );
-});
+};
 export default SafeEventTile;
 
 function E2ePadlockUnencrypted(props: Omit<IE2ePadlockProps, "title" | "icon">): JSX.Element {
diff --git a/src/components/views/rooms/EventTile/EventTileThreadToolbar.tsx b/src/components/views/rooms/EventTile/EventTileThreadToolbar.tsx
index 29f3a43633e4daa1acb71f27f0d684839eeb8834..e587f73598fee0f470441e5b424fa35b9909d983 100644
--- a/src/components/views/rooms/EventTile/EventTileThreadToolbar.tsx
+++ b/src/components/views/rooms/EventTile/EventTileThreadToolbar.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
 
 import { RovingAccessibleButton } from "../../../../accessibility/RovingTabIndex";
 import Toolbar from "../../../../accessibility/Toolbar";
 import { _t } from "../../../../languageHandler";
 import { Icon as ViewInRoomIcon } from "../../../../../res/img/element-icons/view-in-room.svg";
-import { ButtonEvent } from "../../elements/AccessibleButton";
+import { type ButtonEvent } from "../../elements/AccessibleButton";
 
 export function EventTileThreadToolbar({
     viewInRoom,
diff --git a/src/components/views/rooms/ExtraTile.tsx b/src/components/views/rooms/ExtraTile.tsx
index d33db33e16d7b08369a02b5fc8e07bd89bdeed69..05bbb3122317a98921084183d97c6afdda8f5343 100644
--- a/src/components/views/rooms/ExtraTile.tsx
+++ b/src/components/views/rooms/ExtraTile.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 
 import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
 import NotificationBadge from "./NotificationBadge";
-import { NotificationState } from "../../../stores/notifications/NotificationState";
-import { ButtonEvent } from "../elements/AccessibleButton";
+import { type NotificationState } from "../../../stores/notifications/NotificationState";
+import { type ButtonEvent } from "../elements/AccessibleButton";
 import useHover from "../../../hooks/useHover";
 
 interface ExtraTileProps {
diff --git a/src/components/views/rooms/JumpToBottomButton.tsx b/src/components/views/rooms/JumpToBottomButton.tsx
index a33fa7857bac72e86ea40aadc63101dc4bb8fec7..b2a69cb5fe56e06eea0f75772433cb9c3eb14956 100644
--- a/src/components/views/rooms/JumpToBottomButton.tsx
+++ b/src/components/views/rooms/JumpToBottomButton.tsx
@@ -9,7 +9,7 @@ import React from "react";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IProps {
     numUnreadMessages?: number;
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/LegacyRoomList.tsx
similarity index 88%
rename from src/components/views/rooms/RoomList.tsx
rename to src/components/views/rooms/LegacyRoomList.tsx
index 22e4f49469ab565827857ed0937d394c3f014dc5..6be226a1720a60934bb327026e67663714a97946 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/LegacyRoomList.tsx
@@ -6,56 +6,66 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, Room, RoomType } from "matrix-js-sdk/src/matrix";
-import React, { ComponentType, createRef, ReactComponentElement, SyntheticEvent } from "react";
-
-import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
-import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
-import { Action } from "../../../dispatcher/actions";
-import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../dispatcher/payloads";
-import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
-import { useEventEmitterState } from "../../../hooks/useEventEmitter";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import PosthogTrackers from "../../../PosthogTrackers";
-import SettingsStore from "../../../settings/SettingsStore";
-import { useFeatureEnabled } from "../../../hooks/useSettings";
-import { UIComponent } from "../../../settings/UIFeature";
-import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
-import { ITagMap } from "../../../stores/room-list/algorithms/models";
-import { DefaultTagID, TagID } from "../../../stores/room-list/models";
-import { UPDATE_EVENT } from "../../../stores/AsyncStore";
-import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
+import { EventType, type Room, RoomType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type SyntheticEvent } from "react";
+
+import { type IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex.tsx";
+import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
+import { shouldShowComponent } from "../../../customisations/helpers/UIComponents.ts";
+import { Action } from "../../../dispatcher/actions.ts";
+import defaultDispatcher from "../../../dispatcher/dispatcher.ts";
+import { type ActionPayload } from "../../../dispatcher/payloads.ts";
+import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload.ts";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload.ts";
+import { useEventEmitterState } from "../../../hooks/useEventEmitter.ts";
+import { _t, _td, type TranslationKey } from "../../../languageHandler.tsx";
+import { MatrixClientPeg } from "../../../MatrixClientPeg.ts";
+import PosthogTrackers from "../../../PosthogTrackers.ts";
+import SettingsStore from "../../../settings/SettingsStore.ts";
+import { useFeatureEnabled } from "../../../hooks/useSettings.ts";
+import { UIComponent } from "../../../settings/UIFeature.ts";
+import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore.ts";
+import { type ITagMap } from "../../../stores/room-list/algorithms/models.ts";
+import { DefaultTagID, type TagID } from "../../../stores/room-list/models.ts";
+import { UPDATE_EVENT } from "../../../stores/AsyncStore.ts";
+import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore.ts";
 import {
     isMetaSpace,
-    ISuggestedRoom,
+    type ISuggestedRoom,
     MetaSpace,
-    SpaceKey,
+    type SpaceKey,
     UPDATE_SELECTED_SPACE,
     UPDATE_SUGGESTED_ROOMS,
-} from "../../../stores/spaces";
-import SpaceStore from "../../../stores/spaces/SpaceStore";
-import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
-import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
-import { shouldShowSpaceInvite, showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space";
-import { ChevronFace, ContextMenuTooltipButton, MenuProps, useContextMenu } from "../../structures/ContextMenu";
-import RoomAvatar from "../avatars/RoomAvatar";
-import { BetaPill } from "../beta/BetaCard";
+} from "../../../stores/spaces/index.ts";
+import SpaceStore from "../../../stores/spaces/SpaceStore.ts";
+import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays.ts";
+import { objectShallowClone, objectWithOnly } from "../../../utils/objects.ts";
+import type ResizeNotifier from "../../../utils/ResizeNotifier.ts";
+import {
+    shouldShowSpaceInvite,
+    showAddExistingRooms,
+    showCreateNewRoom,
+    showSpaceInvite,
+} from "../../../utils/space.tsx";
+import {
+    ChevronFace,
+    ContextMenuTooltipButton,
+    type MenuProps,
+    useContextMenu,
+} from "../../structures/ContextMenu.tsx";
+import RoomAvatar from "../avatars/RoomAvatar.tsx";
+import { BetaPill } from "../beta/BetaCard.tsx";
 import IconizedContextMenu, {
     IconizedContextMenuOption,
     IconizedContextMenuOptionList,
-} from "../context_menus/IconizedContextMenu";
-import ExtraTile from "./ExtraTile";
-import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
-import { SdkContextClass } from "../../../contexts/SDKContext";
-import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
-import { getKeyBindingsManager } from "../../../KeyBindingsManager";
-import AccessibleButton from "../elements/AccessibleButton";
-import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation";
+} from "../context_menus/IconizedContextMenu.tsx";
+import ExtraTile from "./ExtraTile.tsx";
+import RoomSublist, { type IAuxButtonProps } from "./RoomSublist.tsx";
+import { SdkContextClass } from "../../../contexts/SDKContext.ts";
+import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts.ts";
+import { getKeyBindingsManager } from "../../../KeyBindingsManager.ts";
+import AccessibleButton from "../elements/AccessibleButton.tsx";
+import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation.ts";
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx";
 
 interface IProps {
@@ -132,12 +142,12 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
                         {showCreateRooms && (
                             <IconizedContextMenuOption
                                 label={_t("action|start_new_chat")}
-                                iconClassName="mx_RoomList_iconStartChat"
+                                iconClassName="mx_LegacyRoomList_iconStartChat"
                                 onClick={(e) => {
                                     e.preventDefault();
                                     e.stopPropagation();
                                     closeMenu();
-                                    defaultDispatcher.dispatch({ action: "view_create_chat" });
+                                    defaultDispatcher.dispatch({ action: Action.CreateChat });
                                     PosthogTrackers.trackInteraction(
                                         "WebRoomListRoomsSublistPlusMenuCreateChatItem",
                                         e,
@@ -148,7 +158,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
                         {showInviteUsers && (
                             <IconizedContextMenuOption
                                 label={_t("action|invite_to_space")}
-                                iconClassName="mx_RoomList_iconInvite"
+                                iconClassName="mx_LegacyRoomList_iconInvite"
                                 onClick={(e) => {
                                     e.preventDefault();
                                     e.stopPropagation();
@@ -184,7 +194,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
             <AccessibleButton
                 tabIndex={tabIndex}
                 onClick={(e) => {
-                    dispatcher.dispatch({ action: "view_create_chat" });
+                    dispatcher.dispatch({ action: Action.CreateChat });
                     PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateChatItem", e);
                 }}
                 className="mx_RoomSublist_auxButton"
@@ -220,7 +230,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
             <IconizedContextMenuOptionList first>
                 <IconizedContextMenuOption
                     label={_t("action|explore_rooms")}
-                    iconClassName="mx_RoomList_iconExplore"
+                    iconClassName="mx_LegacyRoomList_iconExplore"
                     onClick={(e) => {
                         e.preventDefault();
                         e.stopPropagation();
@@ -237,7 +247,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
                     <>
                         <IconizedContextMenuOption
                             label={_t("action|new_room")}
-                            iconClassName="mx_RoomList_iconNewRoom"
+                            iconClassName="mx_LegacyRoomList_iconNewRoom"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
@@ -251,7 +261,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
                         {videoRoomsEnabled && (
                             <IconizedContextMenuOption
                                 label={_t("action|new_video_room")}
-                                iconClassName="mx_RoomList_iconNewVideoRoom"
+                                iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
                                 onClick={(e) => {
                                     e.preventDefault();
                                     e.stopPropagation();
@@ -269,7 +279,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
                         )}
                         <IconizedContextMenuOption
                             label={_t("action|add_existing_room")}
-                            iconClassName="mx_RoomList_iconAddExistingRoom"
+                            iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
@@ -290,25 +300,25 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
                     <>
                         <IconizedContextMenuOption
                             label={_t("action|new_room")}
-                            iconClassName="mx_RoomList_iconNewRoom"
+                            iconClassName="mx_LegacyRoomList_iconNewRoom"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
                                 closeMenu();
-                                defaultDispatcher.dispatch({ action: "view_create_room" });
+                                defaultDispatcher.dispatch({ action: Action.CreateRoom });
                                 PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
                             }}
                         />
                         {videoRoomsEnabled && (
                             <IconizedContextMenuOption
                                 label={_t("action|new_video_room")}
-                                iconClassName="mx_RoomList_iconNewVideoRoom"
+                                iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
                                 onClick={(e) => {
                                     e.preventDefault();
                                     e.stopPropagation();
                                     closeMenu();
                                     defaultDispatcher.dispatch({
-                                        action: "view_create_room",
+                                        action: Action.CreateRoom,
                                         type: elementCallVideoRoomsEnabled
                                             ? RoomType.UnstableCall
                                             : RoomType.ElementVideo,
@@ -323,7 +333,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
                 {showExploreRooms ? (
                     <IconizedContextMenuOption
                         label={_t("action|explore_public_rooms")}
-                        iconClassName="mx_RoomList_iconExplore"
+                        iconClassName="mx_LegacyRoomList_iconExplore"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
@@ -420,15 +430,15 @@ const TAG_AESTHETICS: TagAestheticsMap = {
     },
 };
 
-export default class RoomList extends React.PureComponent<IProps, IState> {
+export default class LegacyRoomList extends React.PureComponent<IProps, IState> {
     private dispatcherRef?: string;
     private treeRef = createRef<HTMLDivElement>();
 
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             sublists: {},
@@ -668,7 +678,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
                             }
                             onKeyDownHandler(ev);
                         }}
-                        className="mx_RoomList"
+                        className="mx_LegacyRoomList"
                         role="tree"
                         aria-label={_t("common|rooms")}
                         ref={this.treeRef}
diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/LegacyRoomListHeader.tsx
similarity index 90%
rename from src/components/views/rooms/RoomListHeader.tsx
rename to src/components/views/rooms/LegacyRoomListHeader.tsx
index aa41c1f2baa3b3da48d31cf4ce2e1c1707ac5613..719831d8653ad8b920b96a68c42e7687cc8cb29b 100644
--- a/src/components/views/rooms/RoomListHeader.tsx
+++ b/src/components/views/rooms/LegacyRoomListHeader.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, RoomType, Room, RoomEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
-import React, { useContext, useEffect, useState } from "react";
+import { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext, useEffect, useState } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
 import { Action } from "../../../dispatcher/actions";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { useDispatcher } from "../../../hooks/useDispatcher";
 import { useEventEmitterState, useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
 import { useFeatureEnabled } from "../../../hooks/useSettings";
@@ -24,7 +24,7 @@ import { UIComponent } from "../../../settings/UIFeature";
 import {
     getMetaSpaceName,
     MetaSpace,
-    SpaceKey,
+    type SpaceKey,
     UPDATE_HOME_BEHAVIOUR,
     UPDATE_SELECTED_SPACE,
 } from "../../../stores/spaces";
@@ -38,10 +38,10 @@ import {
 } from "../../../utils/space";
 import {
     ChevronFace,
+    ContextMenuButton,
     ContextMenuTooltipButton,
+    type MenuProps,
     useContextMenu,
-    MenuProps,
-    ContextMenuButton,
 } from "../../structures/ContextMenu";
 import { BetaPill } from "../beta/BetaCard";
 import IconizedContextMenu, {
@@ -108,7 +108,7 @@ interface IProps {
     onVisibilityChange?(): void;
 }
 
-const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
+const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
     const cli = useContext(MatrixClientContext);
     const [mainMenuDisplayed, mainMenuHandle, openMainMenu, closeMainMenu] = useContextMenu<HTMLDivElement>();
     const [plusMenuDisplayed, plusMenuHandle, openPlusMenu, closePlusMenu] = useContextMenu<HTMLDivElement>();
@@ -178,7 +178,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
             inviteOption = (
                 <IconizedContextMenuOption
                     label={_t("action|invite")}
-                    iconClassName="mx_RoomListHeader_iconInvite"
+                    iconClassName="mx_LegacyRoomListHeader_iconInvite"
                     onClick={(e) => {
                         e.preventDefault();
                         e.stopPropagation();
@@ -194,7 +194,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
             newRoomOptions = (
                 <>
                     <IconizedContextMenuOption
-                        iconClassName="mx_RoomListHeader_iconNewRoom"
+                        iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
                         label={_t("action|new_room")}
                         onClick={(e) => {
                             e.preventDefault();
@@ -206,7 +206,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     />
                     {videoRoomsEnabled && (
                         <IconizedContextMenuOption
-                            iconClassName="mx_RoomListHeader_iconNewVideoRoom"
+                            iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
                             label={_t("action|new_video_room")}
                             onClick={(e) => {
                                 e.preventDefault();
@@ -236,7 +236,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     {newRoomOptions}
                     <IconizedContextMenuOption
                         label={_t("action|explore_rooms")}
-                        iconClassName="mx_RoomListHeader_iconExplore"
+                        iconClassName="mx_LegacyRoomListHeader_iconExplore"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
@@ -251,7 +251,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     />
                     <IconizedContextMenuOption
                         label={_t("action|add_existing_room")}
-                        iconClassName="mx_RoomListHeader_iconPlus"
+                        iconClassName="mx_LegacyRoomListHeader_iconPlus"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
@@ -264,7 +264,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     {canCreateSpaces && (
                         <IconizedContextMenuOption
                             label={_t("room_list|add_space_label")}
-                            iconClassName="mx_RoomListHeader_iconPlus"
+                            iconClassName="mx_LegacyRoomListHeader_iconPlus"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
@@ -289,22 +289,22 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                 <>
                     <IconizedContextMenuOption
                         label={_t("action|start_new_chat")}
-                        iconClassName="mx_RoomListHeader_iconStartChat"
+                        iconClassName="mx_LegacyRoomListHeader_iconStartChat"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
-                            defaultDispatcher.dispatch({ action: "view_create_chat" });
+                            defaultDispatcher.dispatch({ action: Action.CreateChat });
                             PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
                             closePlusMenu();
                         }}
                     />
                     <IconizedContextMenuOption
                         label={_t("action|new_room")}
-                        iconClassName="mx_RoomListHeader_iconNewRoom"
+                        iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
                         onClick={(e) => {
                             e.preventDefault();
                             e.stopPropagation();
-                            defaultDispatcher.dispatch({ action: "view_create_room" });
+                            defaultDispatcher.dispatch({ action: Action.CreateRoom });
                             PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
                             closePlusMenu();
                         }}
@@ -312,12 +312,12 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     {videoRoomsEnabled && (
                         <IconizedContextMenuOption
                             label={_t("action|new_video_room")}
-                            iconClassName="mx_RoomListHeader_iconNewVideoRoom"
+                            iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
                             onClick={(e) => {
                                 e.preventDefault();
                                 e.stopPropagation();
                                 defaultDispatcher.dispatch({
-                                    action: "view_create_room",
+                                    action: Action.CreateRoom,
                                     type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo,
                                 });
                                 closePlusMenu();
@@ -333,7 +333,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
             joinRoomOpt = (
                 <IconizedContextMenuOption
                     label={_t("room_list|join_public_room_label")}
-                    iconClassName="mx_RoomListHeader_iconExplore"
+                    iconClassName="mx_LegacyRoomListHeader_iconExplore"
                     onClick={(e) => {
                         e.preventDefault();
                         e.stopPropagation();
@@ -378,13 +378,13 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
         })
         .join("\n");
 
-    let contextMenuButton: JSX.Element = <div className="mx_RoomListHeader_contextLessTitle">{title}</div>;
+    let contextMenuButton: JSX.Element = <div className="mx_LegacyRoomListHeader_contextLessTitle">{title}</div>;
     if (canShowMainMenu) {
         const commonProps = {
             ref: mainMenuHandle,
             onClick: openMainMenu,
             isExpanded: mainMenuDisplayed,
-            className: "mx_RoomListHeader_contextMenuButton",
+            className: "mx_LegacyRoomListHeader_contextMenuButton",
             children: title,
         };
 
@@ -401,7 +401,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
     }
 
     return (
-        <aside className="mx_RoomListHeader" aria-label={_t("room|context_menu|title")}>
+        <aside className="mx_LegacyRoomListHeader" aria-label={_t("room|context_menu|title")}>
             {contextMenuButton}
             {pendingActionSummary ? (
                 <Tooltip label={pendingActionSummary} isTriggerInteractive={false}>
@@ -413,7 +413,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
                     ref={plusMenuHandle}
                     onClick={openPlusMenu}
                     isExpanded={plusMenuDisplayed}
-                    className="mx_RoomListHeader_plusButton"
+                    className="mx_LegacyRoomListHeader_plusButton"
                     title={_t("action|add")}
                 />
             )}
@@ -423,4 +423,4 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
     );
 };
 
-export default RoomListHeader;
+export default LegacyRoomListHeader;
diff --git a/src/components/views/rooms/LinkPreviewGroup.tsx b/src/components/views/rooms/LinkPreviewGroup.tsx
index 4ba64cee8c80b22d828df6fcc4f8945a08e701a8..69c98cb6c9a3e1fc79c59987e95fa4b88e524e26 100644
--- a/src/components/views/rooms/LinkPreviewGroup.tsx
+++ b/src/components/views/rooms/LinkPreviewGroup.tsx
@@ -1,13 +1,13 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext, useEffect } from "react";
-import { MatrixEvent, MatrixError, IPreviewUrlResponse, MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { type MatrixEvent, MatrixError, type IPreviewUrlResponse, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
 
@@ -17,6 +17,7 @@ import AccessibleButton from "../elements/AccessibleButton";
 import { _t } from "../../../languageHandler";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
+import { useMediaVisible } from "../../../hooks/useMediaVisible";
 
 const INITIAL_NUM_PREVIEWS = 2;
 
@@ -24,12 +25,12 @@ interface IProps {
     links: string[]; // the URLs to be previewed
     mxEvent: MatrixEvent; // the Event associated with the preview
     onCancelClick(): void; // called when the preview's cancel ('hide') button is clicked
-    onHeightChanged?(): void; // called when the preview's contents has loaded
 }
 
-const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onHeightChanged }) => {
+const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick }) => {
     const cli = useContext(MatrixClientContext);
     const [expanded, toggleExpanded] = useStateToggle();
+    const [mediaVisible] = useMediaVisible(mxEvent.getId(), mxEvent.getRoomId());
 
     const ts = mxEvent.getTs();
     const previews = useAsyncMemo<[string, IPreviewUrlResponse][]>(
@@ -40,10 +41,6 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
         [],
     );
 
-    useEffect(() => {
-        onHeightChanged?.();
-    }, [onHeightChanged, expanded, previews]);
-
     const showPreviews = expanded ? previews : previews.slice(0, INITIAL_NUM_PREVIEWS);
 
     let toggleButton: JSX.Element | undefined;
@@ -60,7 +57,13 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
     return (
         <div className="mx_LinkPreviewGroup">
             {showPreviews.map(([link, preview], i) => (
-                <LinkPreviewWidget key={link} link={link} preview={preview} mxEvent={mxEvent}>
+                <LinkPreviewWidget
+                    mediaVisible={mediaVisible}
+                    key={link}
+                    link={link}
+                    preview={preview}
+                    mxEvent={mxEvent}
+                >
                     {i === 0 ? (
                         <AccessibleButton
                             className="mx_LinkPreviewGroup_hide"
diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx
index 07d41ea534fbe1d49ab3f689407ad0de46e8e814..0bcbaa940a6305aa0fd8e635d2977b210a83c12b 100644
--- a/src/components/views/rooms/LinkPreviewWidget.tsx
+++ b/src/components/views/rooms/LinkPreviewWidget.tsx
@@ -1,17 +1,16 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2016-2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, createRef, ReactNode } from "react";
+import React, { type JSX, type ComponentProps, createRef, type ReactNode } from "react";
 import { decode } from "html-entities";
-import { MatrixEvent, IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type IPreviewUrlResponse } from "matrix-js-sdk/src/matrix";
 
 import { Linkify } from "../../../HtmlUtils";
-import SettingsStore from "../../../settings/SettingsStore";
 import Modal from "../../../Modal";
 import * as ImageUtils from "../../../ImageUtils";
 import { mediaFromMxc } from "../../../customisations/Media";
@@ -24,6 +23,7 @@ interface IProps {
     preview: IPreviewUrlResponse;
     mxEvent: MatrixEvent; // the Event associated with the preview
     children?: ReactNode;
+    mediaVisible: boolean;
 }
 
 export default class LinkPreviewWidget extends React.Component<IProps> {
@@ -69,7 +69,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
 
         // FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
         let image: string | null = p["og:image"] ?? null;
-        if (!SettingsStore.getValue("showImages")) {
+        if (!this.props.mediaVisible) {
             image = null; // Don't render a button to show the image, just hide it outright
         }
         const imageMaxWidth = 100;
diff --git a/src/components/views/rooms/LiveContentSummary.tsx b/src/components/views/rooms/LiveContentSummary.tsx
index 3ffcc6c0ec0caf2796cfa82f778e75ecc3feda60..1f5d36e8d0226a8974501c78de9d55902b4bf0ca 100644
--- a/src/components/views/rooms/LiveContentSummary.tsx
+++ b/src/components/views/rooms/LiveContentSummary.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC } from "react";
+import React, { type FC } from "react";
 import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
-import { Call } from "../../../models/Call";
+import { type Call } from "../../../models/Call";
 import { useParticipantCount } from "../../../hooks/useCall";
 
 export enum LiveContentType {
diff --git a/src/components/views/rooms/MemberList/MemberListHeaderView.tsx b/src/components/views/rooms/MemberList/MemberListHeaderView.tsx
index 7b7531b1e9b855ba0963b807505df8572edea100..88fc07881c8f02db253c859ab22cdadd29f37199 100644
--- a/src/components/views/rooms/MemberList/MemberListHeaderView.tsx
+++ b/src/components/views/rooms/MemberList/MemberListHeaderView.tsx
@@ -11,7 +11,7 @@ import InviteIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-
 import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { Flex } from "../../../utils/Flex";
-import { MemberListViewState } from "../../../viewmodels/memberlist/MemberListViewModel";
+import { type MemberListViewState } from "../../../viewmodels/memberlist/MemberListViewModel";
 import { _t } from "../../../../languageHandler";
 
 interface TooltipProps {
@@ -52,6 +52,7 @@ const InviteButton: React.FC<Props> = ({ vm }) => {
                     Icon={InviteIcon}
                     disabled={disabled}
                     aria-label={_t("action|invite")}
+                    type="button"
                 />
             </OptionalTooltip>
         );
@@ -67,6 +68,7 @@ const InviteButton: React.FC<Props> = ({ vm }) => {
                 className="mx_MemberListHeaderView_invite_large"
                 disabled={!vm.canInvite}
                 onClick={vm.onInviteButtonClick}
+                type="button"
             >
                 {_t("action|invite")}
             </Button>
@@ -88,12 +90,10 @@ function getHeaderLabelJSX(vm: MemberListViewState): React.ReactNode {
             </Flex>
         );
     }
-
-    const filteredMemberCount = vm.members.length;
-    if (filteredMemberCount === 0) {
+    if (vm.memberCount === 0) {
         return _t("member_list|no_matches");
     }
-    return _t("member_list|count", { count: filteredMemberCount });
+    return _t("member_list|count", { count: vm.memberCount });
 }
 
 export const MemberListHeaderView: React.FC<Props> = (props: Props) => {
diff --git a/src/components/views/rooms/MemberList/MemberListView.tsx b/src/components/views/rooms/MemberList/MemberListView.tsx
index 8dd0a0995b5e2659f425b6cec8264be9e04576db..0b5629685c9450f9ad895a457fb9fbf859304076 100644
--- a/src/components/views/rooms/MemberList/MemberListView.tsx
+++ b/src/components/views/rooms/MemberList/MemberListView.tsx
@@ -6,17 +6,22 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { Form } from "@vector-im/compound-web";
-import React from "react";
-import { List, ListRowProps } from "react-virtualized/dist/commonjs/List";
+import React, { type JSX } from "react";
+import { List, type ListRowProps } from "react-virtualized/dist/commonjs/List";
 import { AutoSizer } from "react-virtualized";
 
 import { Flex } from "../../../utils/Flex";
-import { useMemberListViewModel } from "../../../viewmodels/memberlist/MemberListViewModel";
+import {
+    type MemberWithSeparator,
+    SEPARATOR,
+    useMemberListViewModel,
+} from "../../../viewmodels/memberlist/MemberListViewModel";
 import { RoomMemberTileView } from "./tiles/RoomMemberTileView";
 import { ThreePidInviteTileView } from "./tiles/ThreePidInviteTileView";
 import { MemberListHeaderView } from "./MemberListHeaderView";
 import BaseCard from "../../right_panel/BaseCard";
 import { _t } from "../../../../languageHandler";
+import { RovingTabIndexProvider } from "../../../../accessibility/RovingTabIndex";
 
 interface IProps {
     roomId: string;
@@ -26,10 +31,41 @@ interface IProps {
 const MemberListView: React.FC<IProps> = (props: IProps) => {
     const vm = useMemberListViewModel(props.roomId);
 
-    const memberCount = vm.members.length;
+    const totalRows = vm.members.length;
 
-    const rowRenderer = ({ key, index, style }: ListRowProps): React.JSX.Element => {
-        if (index === memberCount) {
+    const getRowComponent = (item: MemberWithSeparator): JSX.Element => {
+        if (item === SEPARATOR) {
+            return <hr className="mx_MemberListView_separator" />;
+        } else if (item.member) {
+            return <RoomMemberTileView member={item.member} showPresence={vm.isPresenceEnabled} />;
+        } else {
+            return <ThreePidInviteTileView threePidInvite={item.threePidInvite} />;
+        }
+    };
+
+    const getRowHeight = ({ index }: { index: number }): number => {
+        if (vm.members[index] === SEPARATOR) {
+            /**
+             * This is a separator of 2px height rendered between
+             * joined and invited members.
+             */
+            return 2;
+        } else if (totalRows && index === totalRows) {
+            /**
+             * The empty spacer div rendered at the bottom should
+             * have a height of 32px.
+             */
+            return 32;
+        } else {
+            /**
+             * The actual member tiles have a height of 56px.
+             */
+            return 56;
+        }
+    };
+
+    const rowRenderer = ({ key, index, style }: ListRowProps): JSX.Element => {
+        if (index === totalRows) {
             // We've rendered all the members,
             // now we render an empty div to add some space to the end of the list.
             return <div key={key} style={style} />;
@@ -37,11 +73,7 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
         const item = vm.members[index];
         return (
             <div key={key} style={style}>
-                {item.member ? (
-                    <RoomMemberTileView member={item.member} showPresence={vm.isPresenceEnabled} />
-                ) : (
-                    <ThreePidInviteTileView threePidInvite={item.threePidInvite} />
-                )}
+                {getRowComponent(item)}
             </div>
         );
     };
@@ -55,26 +87,34 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
             header={_t("common|people")}
             onClose={props.onClose}
         >
-            <Flex align="stretch" direction="column" className="mx_MemberListView_container">
-                <Form.Root>
-                    <MemberListHeaderView vm={vm} />
-                </Form.Root>
-                <AutoSizer>
-                    {({ height, width }) => (
-                        <List
-                            rowRenderer={rowRenderer}
-                            // All the member tiles will have a height of 56px.
-                            // The additional empty div at the end of the list should have a height of 32px.
-                            rowHeight={({ index }) => (index === memberCount ? 32 : 56)}
-                            // The +1 refers to the additional empty div that we render at the end of the list.
-                            rowCount={memberCount + 1}
-                            // Subtract the height of MemberlistHeaderView so that the parent div does not overflow.
-                            height={height - 113}
-                            width={width}
-                        />
-                    )}
-                </AutoSizer>
-            </Flex>
+            <RovingTabIndexProvider handleUpDown scrollIntoView>
+                {({ onKeyDownHandler }) => (
+                    <Flex
+                        align="stretch"
+                        direction="column"
+                        className="mx_MemberListView_container"
+                        onKeyDown={onKeyDownHandler}
+                    >
+                        <Form.Root>
+                            <MemberListHeaderView vm={vm} />
+                        </Form.Root>
+                        <AutoSizer>
+                            {({ height, width }) => (
+                                <List
+                                    rowRenderer={rowRenderer}
+                                    rowHeight={getRowHeight}
+                                    // The +1 refers to the additional empty div that we render at the end of the list.
+                                    rowCount={totalRows + 1}
+                                    // Subtract the height of MemberlistHeaderView so that the parent div does not overflow.
+                                    height={height - 113}
+                                    width={width}
+                                    overscanRowCount={15}
+                                />
+                            )}
+                        </AutoSizer>
+                    </Flex>
+                )}
+            </RovingTabIndexProvider>
         </BaseCard>
     );
 };
diff --git a/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx b/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx
index c0109e619bc43724e09d634da557cfc3f0eef05d..f5fd5203a500856be55ff1b9c8521a44d43cb85f 100644
--- a/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx
+++ b/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx
@@ -5,16 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import DisambiguatedProfile from "../../../messages/DisambiguatedProfile";
-import { RoomMember } from "../../../../../models/rooms/RoomMember";
+import { type RoomMember } from "../../../../../models/rooms/RoomMember";
 import { useMemberTileViewModel } from "../../../../viewmodels/memberlist/tiles/MemberTileViewModel";
 import { E2EIconView } from "./common/E2EIconView";
 import AvatarPresenceIconView from "./common/PresenceIconView";
 import BaseAvatar from "../../../avatars/BaseAvatar";
 import { _t } from "../../../../../languageHandler";
-import { MemberTileLayout } from "./common/MemberTileLayout";
+import { MemberTileView } from "./common/MemberTileView";
+import { InvitedIconView } from "./common/InvitedIconView";
 
 interface IProps {
     member: RoomMember;
@@ -43,25 +44,23 @@ export function RoomMemberTileView(props: IProps): JSX.Element {
         presenceJSX = <AvatarPresenceIconView presenceState={presenceState} />;
     }
 
-    let userLabelJSX;
-    if (vm.userLabel) {
-        userLabelJSX = <div className="mx_MemberTileView_user_label">{vm.userLabel}</div>;
-    }
-
-    let e2eIcon;
+    let iconJsx;
     if (vm.e2eStatus) {
-        e2eIcon = <E2EIconView status={vm.e2eStatus} />;
+        iconJsx = <E2EIconView status={vm.e2eStatus} />;
+    }
+    if (member.isInvite) {
+        iconJsx = <InvitedIconView isThreePid={false} />;
     }
 
     return (
-        <MemberTileLayout
+        <MemberTileView
             title={vm.title}
             onClick={vm.onClick}
             avatarJsx={av}
             presenceJsx={presenceJSX}
             nameJsx={nameJSX}
-            userLabelJsx={userLabelJSX}
-            e2eIconJsx={e2eIcon}
+            userLabel={vm.userLabel}
+            iconJsx={iconJsx}
         />
     );
 }
diff --git a/src/components/views/rooms/MemberList/tiles/ThreePidInviteTileView.tsx b/src/components/views/rooms/MemberList/tiles/ThreePidInviteTileView.tsx
index f6890cc034f6ef733708601805d9d5fd573a73e2..4f6caf06f60e6b750f63340739eb00ed75f624d4 100644
--- a/src/components/views/rooms/MemberList/tiles/ThreePidInviteTileView.tsx
+++ b/src/components/views/rooms/MemberList/tiles/ThreePidInviteTileView.tsx
@@ -5,12 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { useThreePidTileViewModel } from "../../../../viewmodels/memberlist/tiles/ThreePidTileViewModel";
-import { ThreePIDInvite } from "../../../../../models/rooms/ThreePIDInvite";
+import { type ThreePIDInvite } from "../../../../../models/rooms/ThreePIDInvite";
 import BaseAvatar from "../../../avatars/BaseAvatar";
-import { MemberTileLayout } from "./common/MemberTileLayout";
+import { MemberTileView } from "./common/MemberTileView";
+import { InvitedIconView } from "./common/InvitedIconView";
 
 interface Props {
     threePidInvite: ThreePIDInvite;
@@ -19,5 +20,15 @@ interface Props {
 export function ThreePidInviteTileView(props: Props): JSX.Element {
     const vm = useThreePidTileViewModel(props);
     const av = <BaseAvatar name={vm.name} size="32px" aria-hidden="true" />;
-    return <MemberTileLayout nameJsx={vm.name} avatarJsx={av} onClick={vm.onClick} />;
+    const iconJsx = <InvitedIconView isThreePid={true} />;
+
+    return (
+        <MemberTileView
+            nameJsx={vm.name}
+            avatarJsx={av}
+            onClick={vm.onClick}
+            userLabel={vm.userLabel}
+            iconJsx={iconJsx}
+        />
+    );
 }
diff --git a/src/components/views/rooms/MemberList/tiles/common/E2EIconView.tsx b/src/components/views/rooms/MemberList/tiles/common/E2EIconView.tsx
index a1f37e5185c18570d6f1a1ec5a14d3e1055f5e0f..359bb74b2335c66cfbc0520f97fc37fe4dc3cd00 100644
--- a/src/components/views/rooms/MemberList/tiles/common/E2EIconView.tsx
+++ b/src/components/views/rooms/MemberList/tiles/common/E2EIconView.tsx
@@ -5,16 +5,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
-import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
 
 import { _t } from "../../../../../../languageHandler";
 import { E2EStatus } from "../../../../../../utils/ShieldUtils";
 import { crossSigningUserTitles } from "../../../E2EIcon";
 
-function getIconFromStatus(status: E2EStatus): React.JSX.Element | undefined {
+function getIconFromStatus(status: E2EStatus): JSX.Element | undefined {
     switch (status) {
         case E2EStatus.Normal:
             return undefined;
diff --git a/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx b/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8a7f0e06a46b4d4e7a730e4bb262c50842965711
--- /dev/null
+++ b/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx
@@ -0,0 +1,25 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type JSX } from "react";
+import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
+import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid";
+
+import { Flex } from "../../../../../utils/Flex";
+
+interface Props {
+    isThreePid: boolean;
+}
+
+export function InvitedIconView({ isThreePid }: Props): JSX.Element {
+    const Icon = isThreePid ? EmailIcon : UserAddIcon;
+    return (
+        <Flex align="center" className="mx_InvitedIconView">
+            <Icon height="16px" width="16px" />
+        </Flex>
+    );
+}
diff --git a/src/components/views/rooms/MemberList/tiles/common/MemberTileLayout.tsx b/src/components/views/rooms/MemberList/tiles/common/MemberTileView.tsx
similarity index 58%
rename from src/components/views/rooms/MemberList/tiles/common/MemberTileLayout.tsx
rename to src/components/views/rooms/MemberList/tiles/common/MemberTileView.tsx
index ae1cf78b3c34e2e808b43edf8f0cfb89b07cefda..480c3d31e4d3337920e8386181d33708b4f3a844 100644
--- a/src/components/views/rooms/MemberList/tiles/common/MemberTileLayout.tsx
+++ b/src/components/views/rooms/MemberList/tiles/common/MemberTileView.tsx
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
-import AccessibleButton from "../../../../elements/AccessibleButton";
+import { RovingAccessibleButton } from "../../../../../../accessibility/RovingTabIndex";
 
 interface Props {
     avatarJsx: JSX.Element;
@@ -15,15 +15,20 @@ interface Props {
     onClick: () => void;
     title?: string;
     presenceJsx?: JSX.Element;
-    userLabelJsx?: JSX.Element;
-    e2eIconJsx?: JSX.Element;
+    userLabel?: React.ReactNode;
+    iconJsx?: JSX.Element;
 }
 
-export function MemberTileLayout(props: Props): JSX.Element {
+export function MemberTileView(props: Props): JSX.Element {
+    let userLabelJsx: React.ReactNode;
+    if (props.userLabel) {
+        userLabelJsx = <div className="mx_MemberTileView_userLabel">{props.userLabel}</div>;
+    }
+
     return (
         // The wrapping div is required to make the magic mouse listener work, for some reason.
         <div>
-            <AccessibleButton className="mx_MemberTileView" title={props.title} onClick={props.onClick}>
+            <RovingAccessibleButton className="mx_MemberTileView" title={props.title} onClick={props.onClick}>
                 <div className="mx_MemberTileView_left">
                     <div className="mx_MemberTileView_avatar">
                         {props.avatarJsx} {props.presenceJsx}
@@ -31,10 +36,10 @@ export function MemberTileLayout(props: Props): JSX.Element {
                     <div className="mx_MemberTileView_name">{props.nameJsx}</div>
                 </div>
                 <div className="mx_MemberTileView_right">
-                    {props.userLabelJsx}
-                    {props.e2eIconJsx}
+                    {userLabelJsx}
+                    {props.iconJsx}
                 </div>
-            </AccessibleButton>
+            </RovingAccessibleButton>
         </div>
     );
 }
diff --git a/src/components/views/rooms/MemberList/tiles/common/PresenceIconView.tsx b/src/components/views/rooms/MemberList/tiles/common/PresenceIconView.tsx
index 855aecf626f32bd1622598359151d8b79b18cb7a..ac31084adf0775fda5f5d88bdf74764d8e818f3a 100644
--- a/src/components/views/rooms/MemberList/tiles/common/PresenceIconView.tsx
+++ b/src/components/views/rooms/MemberList/tiles/common/PresenceIconView.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8";
 import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8";
 import DNDIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8";
@@ -19,7 +19,7 @@ interface Props {
 
 export const BUSY_PRESENCE_NAME = new UnstableValue("busy", "org.matrix.msc3026.busy");
 
-function getIconForPresenceState(state: string): React.JSX.Element {
+function getIconForPresenceState(state: string): JSX.Element {
     switch (state) {
         case "online":
             return <OnlineOrUnavailableIcon height="8px" width="8px" className="mx_PresenceIconView_online" />;
diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx
index 3cab66201a0f548cc9d45e2c9204ea8b159be0f5..f6b25c20981034a781e1b5da78aad6ca0b7d4c5c 100644
--- a/src/components/views/rooms/MessageComposer.tsx
+++ b/src/components/views/rooms/MessageComposer.tsx
@@ -6,51 +6,51 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, ReactNode } from "react";
+import React, { type JSX, createRef, type ReactNode } from "react";
 import classNames from "classnames";
 import {
-    IEventRelation,
-    MatrixEvent,
-    Room,
-    RoomMember,
+    type IEventRelation,
+    type MatrixEvent,
+    type Room,
+    type RoomMember,
     EventType,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 import { Tooltip } from "@vector-im/compound-web";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import dis from "../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import Stickerpicker from "./Stickerpicker";
-import { makeRoomPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { makeRoomPermalink, type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import E2EIcon from "./E2EIcon";
 import SettingsStore from "../../../settings/SettingsStore";
-import { aboveLeftOf, MenuProps } from "../../structures/ContextMenu";
+import { aboveLeftOf, type MenuProps } from "../../structures/ContextMenu";
 import ReplyPreview from "./ReplyPreview";
 import { UserIdentityWarning } from "./UserIdentityWarning";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
 import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
 import { RecordingState } from "../../../audio/VoiceRecording";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
-import { E2EStatus } from "../../../utils/ShieldUtils";
-import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer";
-import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
+import { type E2EStatus } from "../../../utils/ShieldUtils";
+import SendMessageComposer, { type SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer";
+import { type ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
 import { Action } from "../../../dispatcher/actions";
-import EditorModel from "../../../editor/model";
+import type EditorModel from "../../../editor/model";
 import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
 import RoomContext from "../../../contexts/RoomContext";
-import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
+import { type SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
 import MessageComposerButtons from "./MessageComposerButtons";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
-import { VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
+import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
 import { SendWysiwygComposer, sendMessage, getConversionFunctions } from "./wysiwyg_composer/";
-import { MatrixClientProps, withMatrixClientHOC } from "../../../contexts/MatrixClientContext";
+import { type MatrixClientProps, withMatrixClientHOC } from "../../../contexts/MatrixClientContext";
 import { UIFeature } from "../../../settings/UIFeature";
 import { formatTimeLeft } from "../../../DateUtils";
 import RoomReplacedSvg from "../../../../res/img/room_replaced.svg";
@@ -111,7 +111,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
     private dispatcherRef?: string;
     private messageComposerInput = createRef<SendMessageComposerClass>();
     private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
-    private ref: React.RefObject<HTMLDivElement> = createRef();
+    private ref = createRef<HTMLDivElement>();
     private instanceId: number;
 
     private _voiceRecording: Optional<VoiceMessageRecording>;
@@ -124,9 +124,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
         isRichTextEnabled: true,
     };
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
-        this.context = context; // otherwise React will only set it prior to render due to type def above
+    public constructor(props: IProps) {
+        super(props);
 
         const isWysiwygLabEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
         let isRichTextEnabled = true;
diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx
index b70c39fb024333a0502b156d64233cd7bf358420..af9b61dd0dca816819b5b3c3bfb236842f621fdb 100644
--- a/src/components/views/rooms/MessageComposerButtons.tsx
+++ b/src/components/views/rooms/MessageComposerButtons.tsx
@@ -7,12 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import { IEventRelation, Room, MatrixClient, THREAD_RELATION_TYPE, M_POLL_START } from "matrix-js-sdk/src/matrix";
-import React, { createContext, ReactElement, ReactNode, useContext, useRef } from "react";
+import {
+    type IEventRelation,
+    type Room,
+    type MatrixClient,
+    THREAD_RELATION_TYPE,
+    M_POLL_START,
+} from "matrix-js-sdk/src/matrix";
+import React, { type JSX, createContext, type ReactElement, type ReactNode, useContext, useRef } from "react";
 
 import { _t } from "../../../languageHandler";
 import { CollapsibleButton } from "./CollapsibleButton";
-import { MenuProps } from "../../structures/ContextMenu";
+import { type MenuProps } from "../../structures/ContextMenu";
 import dis from "../../../dispatcher/dispatcher";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import { LocationButton } from "../location";
@@ -27,7 +33,7 @@ import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_m
 import { EmojiButton } from "./EmojiButton";
 import { filterBoolean } from "../../../utils/arrays";
 import { useSettingValue } from "../../../hooks/useSettings";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
 
 interface IProps {
diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx
index cb15917e37fc04dd83a75d87d5b574a250ba69a1..f12b03e288acffdd7a00fc2124d733401e3c1179 100644
--- a/src/components/views/rooms/NewRoomIntro.tsx
+++ b/src/components/views/rooms/NewRoomIntro.tsx
@@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useContext } from "react";
-import { EventType, Room, User, MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useContext } from "react";
+import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import DMRoomMap from "../../../utils/DMRoomMap";
-import { _t, _td, TranslationKey } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import MiniAvatarUploader, { AVATAR_SIZE } from "../elements/MiniAvatarUploader";
 import RoomAvatar from "../avatars/RoomAvatar";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
+import { type ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
 import { Action } from "../../../dispatcher/actions";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
 import { showSpaceInvite } from "../../../utils/space";
diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx
index f2fe9b0886852b1d0ff6bf452b500f376c5f88b2..323becf33c182b13894dc7e39b344f4b0a52badf 100644
--- a/src/components/views/rooms/NotificationBadge.tsx
+++ b/src/components/views/rooms/NotificationBadge.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 
 import SettingsStore from "../../../settings/SettingsStore";
-import { XOR } from "../../../@types/common";
-import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
+import { type XOR } from "../../../@types/common";
+import { type NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
 import { _t } from "../../../languageHandler";
 import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
 import { StatelessNotificationBadge } from "./NotificationBadge/StatelessNotificationBadge";
diff --git a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx
index 7031814607f36ba7caf22e271cb2ad5f5828a92a..8e44a0a3b90754f49937efa0c4cb37fe61b691e6 100644
--- a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx
+++ b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { forwardRef } from "react";
+import React, { type Ref, type JSX, type ReactNode } from "react";
 import classNames from "classnames";
 
 import { formatCount } from "../../../../utils/FormattingUtils";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../elements/AccessibleButton";
 import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
 import { useSettingValue } from "../../../../hooks/useSettings";
-import { XOR } from "../../../../@types/common";
+import { type XOR } from "../../../../@types/common";
 
 interface Props {
     symbol: string | null;
@@ -26,6 +26,8 @@ interface Props {
      * for the difference between the two.
      */
     forceDot?: boolean;
+    children?: ReactNode;
+    ref?: Ref<HTMLDivElement>;
 }
 
 interface ClickableProps extends Props {
@@ -45,61 +47,66 @@ interface ClickableProps extends Props {
  * notifications in the room list, it may have a green badge with the number of unread notifications,
  * but somewhere else it may just have a green dot as a more compact representation of the same information.
  */
-export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props, ClickableProps>>(
-    ({ symbol, count, level, knocked, forceDot = false, ...props }, ref) => {
-        const hideBold = useSettingValue("feature_hidebold");
+export const StatelessNotificationBadge = ({
+    symbol,
+    count,
+    level,
+    knocked,
+    forceDot = false,
+    ...props
+}: XOR<Props, ClickableProps>): JSX.Element => {
+    const hideBold = useSettingValue("feature_hidebold");
 
-        // Don't show a badge if we don't need to
-        if ((level === NotificationLevel.None || (hideBold && level == NotificationLevel.Activity)) && !knocked) {
-            return <></>;
-        }
+    // Don't show a badge if we don't need to
+    if ((level === NotificationLevel.None || (hideBold && level == NotificationLevel.Activity)) && !knocked) {
+        return <></>;
+    }
 
-        const hasUnreadCount = level >= NotificationLevel.Notification && (!!count || !!symbol);
+    const hasUnreadCount = level >= NotificationLevel.Notification && (!!count || !!symbol);
 
-        const isEmptyBadge = symbol === null && count === 0;
+    const isEmptyBadge = symbol === null && count === 0;
 
-        if (symbol === null && count > 0) {
-            symbol = formatCount(count);
-        }
+    if (symbol === null && count > 0) {
+        symbol = formatCount(count);
+    }
 
-        // We show a dot if either:
-        // * The props force us to, or
-        // * It's just an activity-level notification or (in theory) lower and the room isn't knocked
-        const badgeType =
-            forceDot || (level <= NotificationLevel.Activity && !knocked)
-                ? "dot"
-                : !symbol || symbol.length < 3
-                  ? "badge_2char"
-                  : "badge_3char";
+    // We show a dot if either:
+    // * The props force us to, or
+    // * It's just an activity-level notification or (in theory) lower and the room isn't knocked
+    const badgeType =
+        forceDot || (level <= NotificationLevel.Activity && !knocked)
+            ? "dot"
+            : !symbol || symbol.length < 3
+              ? "badge_2char"
+              : "badge_3char";
 
-        const classes = classNames({
-            "mx_NotificationBadge": true,
-            "mx_NotificationBadge_visible": isEmptyBadge || knocked ? true : hasUnreadCount,
-            "mx_NotificationBadge_level_notification": level == NotificationLevel.Notification,
-            "mx_NotificationBadge_level_highlight": level >= NotificationLevel.Highlight,
-            "mx_NotificationBadge_knocked": knocked,
+    const classes = classNames({
+        "mx_NotificationBadge": true,
+        "mx_NotificationBadge_visible": isEmptyBadge || knocked ? true : hasUnreadCount,
+        "mx_NotificationBadge_level_notification": level == NotificationLevel.Notification,
+        "mx_NotificationBadge_level_highlight": level >= NotificationLevel.Highlight,
+        "mx_NotificationBadge_knocked": knocked,
 
-            // Exactly one of mx_NotificationBadge_dot, mx_NotificationBadge_2char, mx_NotificationBadge_3char
-            "mx_NotificationBadge_dot": badgeType === "dot",
-            "mx_NotificationBadge_2char": badgeType === "badge_2char",
-            "mx_NotificationBadge_3char": badgeType === "badge_3char",
-            // Badges with text should always use light colors
-            "cpd-theme-light": badgeType !== "dot",
-        });
-
-        if (props.onClick) {
-            return (
-                <AccessibleButton {...props} className={classes} onClick={props.onClick} ref={ref}>
-                    <span className="mx_NotificationBadge_count">{symbol}</span>
-                    {props.children}
-                </AccessibleButton>
-            );
-        }
+        // Exactly one of mx_NotificationBadge_dot, mx_NotificationBadge_2char, mx_NotificationBadge_3char
+        "mx_NotificationBadge_dot": badgeType === "dot",
+        "mx_NotificationBadge_2char": badgeType === "badge_2char",
+        "mx_NotificationBadge_3char": badgeType === "badge_3char",
+        // Badges with text should always use light colors
+        "cpd-theme-light": badgeType !== "dot",
+    });
 
+    if (props.onClick) {
         return (
-            <div className={classes} ref={ref}>
+            <AccessibleButton {...props} className={classes} onClick={props.onClick} ref={props.ref}>
                 <span className="mx_NotificationBadge_count">{symbol}</span>
-            </div>
+                {props.children}
+            </AccessibleButton>
         );
-    },
-);
+    }
+
+    return (
+        <div className={classes} ref={props.ref}>
+            <span className="mx_NotificationBadge_count">{symbol}</span>
+        </div>
+    );
+};
diff --git a/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx b/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx
index 1c75b34704b4a7a248f1e67797bc581c16f4eeb3..0677278054016406f7aae5a6f3a4cc704ced3a08 100644
--- a/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx
+++ b/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
-import React from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
 
 import { useUnreadNotifications } from "../../../../hooks/useUnreadNotifications";
 import { StatelessNotificationBadge } from "./StatelessNotificationBadge";
diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a93f1428fd9d0ad437d579850bb3666da8102a29
--- /dev/null
+++ b/src/components/views/rooms/NotificationDecoration.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type HTMLProps, type JSX } from "react";
+import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/mention";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
+import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
+import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
+import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
+import { UnreadCounter, Unread } from "@vector-im/compound-web";
+
+import { Flex } from "../../utils/Flex";
+import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
+import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
+import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";
+
+interface NotificationDecorationProps extends HTMLProps<HTMLDivElement> {
+    /**
+     * The notification state of the room or thread.
+     */
+    notificationState: RoomNotificationState;
+    /**
+     * Whether the room has a video call.
+     */
+    hasVideoCall: boolean;
+}
+
+/**
+ * Displays the notification decoration for a room or a thread.
+ */
+export function NotificationDecoration({
+    notificationState,
+    hasVideoCall,
+    ...props
+}: NotificationDecorationProps): JSX.Element | null {
+    // Listen to the notification state and update the component when it changes
+    const {
+        hasAnyNotificationOrActivity,
+        isUnsentMessage,
+        invited,
+        isMention,
+        isActivityNotification,
+        isNotification,
+        count,
+        muted,
+    } = useTypedEventEmitterState(notificationState, NotificationStateEvents.Update, () => ({
+        hasAnyNotificationOrActivity: notificationState.hasAnyNotificationOrActivity,
+        isUnsentMessage: notificationState.isUnsentMessage,
+        invited: notificationState.invited,
+        isMention: notificationState.isMention,
+        isActivityNotification: notificationState.isActivityNotification,
+        isNotification: notificationState.isNotification,
+        count: notificationState.count,
+        muted: notificationState.muted,
+    }));
+
+    if (!hasAnyNotificationOrActivity && !muted && !hasVideoCall) return null;
+
+    return (
+        <Flex
+            align="center"
+            justify="center"
+            gap="var(--cpd-space-1x)"
+            {...props}
+            data-testid="notification-decoration"
+        >
+            {isUnsentMessage && <ErrorIcon width="20px" height="20px" fill="var(--cpd-color-icon-critical-primary)" />}
+            {hasVideoCall && <VideoCallIcon width="20px" height="20px" fill="var(--cpd-color-icon-accent-primary)" />}
+            {invited && <EmailIcon width="20px" height="20px" fill="var(--cpd-color-icon-accent-primary)" />}
+            {isMention && <MentionIcon width="20px" height="20px" fill="var(--cpd-color-icon-accent-primary)" />}
+            {(isMention || isNotification) && <UnreadCounter count={count || null} />}
+            {isActivityNotification && <Unread />}
+            {muted && <NotificationOffIcon width="20px" height="20px" fill="var(--cpd-color-icon-tertiary)" />}
+        </Flex>
+    );
+}
diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx
index 42cc21026f59066dadd1b89eb78bfe1b97607fdb..afb612c3979d6da4fc2e082a84dccebf9f19fb6e 100644
--- a/src/components/views/rooms/PinnedEventTile.tsx
+++ b/src/components/views/rooms/PinnedEventTile.tsx
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { JSX, useCallback, useState } from "react";
-import { EventTimeline, EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useCallback, useState } from "react";
+import { EventTimeline, EventType, type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 import { IconButton, Menu, MenuItem, Separator, Tooltip } from "@vector-im/compound-web";
 import ViewIcon from "@vector-im/compound-design-tokens/assets/web/icons/visibility-on";
 import UnpinIcon from "@vector-im/compound-design-tokens/assets/web/icons/unpin";
@@ -24,15 +24,15 @@ import MessageEvent from "../messages/MessageEvent";
 import MemberAvatar from "../avatars/MemberAvatar";
 import { _t } from "../../../languageHandler";
 import { getUserNameColorClass } from "../../../utils/FormattingUtils";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
 import { useRoomState } from "../../../hooks/useRoomState";
 import { isContentActionable } from "../../../utils/EventUtils";
 import { getForwardableEvent } from "../../../events";
-import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
+import { type OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
 import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
-import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
+import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
 import PinningUtils from "../../../utils/PinningUtils.ts";
 import PosthogTrackers from "../../../PosthogTrackers.ts";
 
@@ -90,7 +90,6 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi
                 <MessageEvent
                     mxEvent={event}
                     maxImageHeight={150}
-                    onHeightChanged={() => {}} // we need to give this, apparently
                     permalinkCreator={permalinkCreator}
                     replacingEventId={event.replacingEventId()}
                 />
diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx
index 4ba9e0a40c6b275915e784f129799305b13bc23d..2b9f335c7f8511b5656b789eba7360ed2a745eb0 100644
--- a/src/components/views/rooms/PinnedMessageBanner.tsx
+++ b/src/components/views/rooms/PinnedMessageBanner.tsx
@@ -6,10 +6,10 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, useEffect, useRef, useState } from "react";
+import React, { type JSX, useEffect, useRef, useState } from "react";
 import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
 import { Button } from "@vector-im/compound-web";
-import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
 
 import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
@@ -18,14 +18,14 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
 import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
 import { useEventEmitter } from "../../../hooks/useEventEmitter";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import dis from "../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../dispatcher/actions";
 import MessageEvent from "../messages/MessageEvent";
 import PosthogTrackers from "../../../PosthogTrackers.ts";
 import { EventPreview } from "./EventPreview.tsx";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 
 /**
  * The props for the {@link PinnedMessageBanner} component.
diff --git a/src/components/views/rooms/ReadReceiptGroup.tsx b/src/components/views/rooms/ReadReceiptGroup.tsx
index 112b41329c0fabf5755eb708d27144a73bfe831e..22d2f03697f15f71e909938ffc66da6768c67395 100644
--- a/src/components/views/rooms/ReadReceiptGroup.tsx
+++ b/src/components/views/rooms/ReadReceiptGroup.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { PropsWithChildren } from "react";
-import { User } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type PropsWithChildren } from "react";
+import { type User } from "matrix-js-sdk/src/matrix";
 import { Tooltip } from "@vector-im/compound-web";
 
-import ReadReceiptMarker, { IReadReceiptPosition } from "./ReadReceiptMarker";
-import { IReadReceiptProps } from "./EventTile";
+import ReadReceiptMarker, { type IReadReceiptPosition } from "./ReadReceiptMarker";
+import { type IReadReceiptProps } from "./EventTile";
 import AccessibleButton from "../elements/AccessibleButton";
 import MemberAvatar from "../avatars/MemberAvatar";
 import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx
index 9d4feb10c400f1ad4e3fb8e42d9854a6d20a166a..6b096d8917f03674c04d4e564db110bb9e220a91 100644
--- a/src/components/views/rooms/ReadReceiptMarker.tsx
+++ b/src/components/views/rooms/ReadReceiptMarker.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { createRef } from "react";
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import NodeAnimator from "../../../NodeAnimator";
diff --git a/src/components/views/rooms/ReplyPreview.tsx b/src/components/views/rooms/ReplyPreview.tsx
index f426a924b87795227d1ad0f488d3b9ea9cf0ed3c..db8f6f4a9e86a83b1e852285f32420099ba60682 100644
--- a/src/components/views/rooms/ReplyPreview.tsx
+++ b/src/components/views/rooms/ReplyPreview.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../../dispatcher/dispatcher";
 import { _t } from "../../../languageHandler";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import ReplyTile from "./ReplyTile";
-import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
+import RoomContext, { type TimelineRenderingType } from "../../../contexts/RoomContext";
 import AccessibleButton from "../elements/AccessibleButton";
 
 function cancelQuoting(context: TimelineRenderingType): void {
diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx
index 73352a806e59c886150e3cf2cf57f8955c68601f..2db56e397e3942890d56e395a6371979c6143b8b 100644
--- a/src/components/views/rooms/ReplyTile.tsx
+++ b/src/components/views/rooms/ReplyTile.tsx
@@ -8,13 +8,13 @@ Please see LICENSE files in the repository root for full details.
 
 import React, { createRef } from "react";
 import classNames from "classnames";
-import { MatrixEvent, MatrixEventEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MatrixEventEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import SenderProfile from "../messages/SenderProfile";
 import MImageReplyBody from "../messages/MImageReplyBody";
 import { isVoiceMessage } from "../../../utils/EventUtils";
@@ -22,17 +22,17 @@ import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils";
 import MFileBody from "../messages/MFileBody";
 import MemberAvatar from "../avatars/MemberAvatar";
 import MVoiceMessageBody from "../messages/MVoiceMessageBody";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { renderReplyTile } from "../../../events/EventTileFactory";
-import { GetRelationsForEvent } from "../rooms/EventTile";
+import { type GetRelationsForEvent } from "../rooms/EventTile";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import { type IBodyProps } from "../messages/IBodyProps";
 
 interface IProps {
     mxEvent: MatrixEvent;
     permalinkCreator?: RoomPermalinkCreator;
     highlights?: string[];
     highlightLink?: string;
-    onHeightChanged?(): void;
     toggleExpandedQuote?: () => void;
     getRelationsForEvent?: GetRelationsForEvent;
 }
@@ -40,10 +40,6 @@ interface IProps {
 export default class ReplyTile extends React.PureComponent<IProps> {
     private anchorElement = createRef<HTMLAnchorElement>();
 
-    public static defaultProps = {
-        onHeightChanged: () => {},
-    };
-
     public componentDidMount(): void {
         this.props.mxEvent.on(MatrixEventEvent.Decrypted, this.onDecrypted);
         this.props.mxEvent.on(MatrixEventEvent.BeforeRedaction, this.onEventRequiresUpdate);
@@ -58,9 +54,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
 
     private onDecrypted = (): void => {
         this.forceUpdate();
-        if (this.props.onHeightChanged) {
-            this.props.onHeightChanged();
-        }
     };
 
     private onEventRequiresUpdate = (): void => {
@@ -139,13 +132,13 @@ export default class ReplyTile extends React.PureComponent<IProps> {
             );
         }
 
-        const msgtypeOverrides: Record<string, typeof React.Component> = {
+        const msgtypeOverrides: Record<string, React.ComponentType<IBodyProps>> = {
             [MsgType.Image]: MImageReplyBody,
             // Override audio and video body with file body. We also hide the download/decrypt button using CSS
             [MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody,
             [MsgType.Video]: MFileBody,
         };
-        const evOverrides: Record<string, typeof React.Component> = {
+        const evOverrides: Record<string, React.ComponentType<IBodyProps>> = {
             // Use MImageReplyBody so that the sticker isn't taking up a lot of space
             [EventType.Sticker]: MImageReplyBody,
         };
@@ -169,7 +162,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
                             // appease TS
                             highlights: this.props.highlights,
                             highlightLink: this.props.highlightLink,
-                            onHeightChanged: this.props.onHeightChanged,
                             permalinkCreator: this.props.permalinkCreator,
                         },
                         false /* showHiddenEvents shouldn't be relevant */,
diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx
index 40290358f54936830036122fdd45bcf391b8d33b..c677b32edca514c0259aa113ef8bf42bd0b3ac5c 100644
--- a/src/components/views/rooms/RoomBreadcrumbs.tsx
+++ b/src/components/views/rooms/RoomBreadcrumbs.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { createRef } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type EmptyObject, type Room } from "matrix-js-sdk/src/matrix";
 import { CSSTransition } from "react-transition-group";
 
 import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore";
@@ -18,10 +18,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
 import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
 import Toolbar from "../../../accessibility/Toolbar";
 import { Action } from "../../../dispatcher/actions";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
-
-interface IProps {}
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IState {
     // Both of these control the animation for the breadcrumbs. For details on the
@@ -59,11 +57,11 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
     );
 };
 
-export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState> {
+export default class RoomBreadcrumbs extends React.PureComponent<EmptyObject, IState> {
     private unmounted = false;
     private toolbar = createRef<HTMLDivElement>();
 
-    public constructor(props: IProps) {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/views/rooms/RoomContextDetails.tsx b/src/components/views/rooms/RoomContextDetails.tsx
index cd6ca327c0637079f25eae6719519470b4825dd6..d7094b0e722e90173ef0c4ef41dbbaa01dc13dfe 100644
--- a/src/components/views/rooms/RoomContextDetails.tsx
+++ b/src/components/views/rooms/RoomContextDetails.tsx
@@ -6,17 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
-import React, { HTMLAttributes, ReactHTML } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type HTMLAttributes } from "react";
 
 import { roomContextDetails } from "../../../utils/i18n-helpers";
 
-type Props<T extends keyof ReactHTML> = HTMLAttributes<T> & {
+type Props<T extends keyof HTMLElementTagNameMap> = HTMLAttributes<T> & {
     component?: T;
     room: Room;
 };
 
-export function RoomContextDetails<T extends keyof ReactHTML>({ room, component, ...other }: Props<T>): JSX.Element {
+export function RoomContextDetails<T extends keyof HTMLElementTagNameMap>({
+    room,
+    component,
+    ...other
+}: Props<T>): JSX.Element {
     const contextDetails = roomContextDetails(room);
     if (contextDetails) {
         return React.createElement(
diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx
deleted file mode 100644
index afe68ee5bece8b46eddc45dd0afabd14ec772721..0000000000000000000000000000000000000000
--- a/src/components/views/rooms/RoomHeader.tsx
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { useCallback, useMemo, useState } from "react";
-import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
-import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
-import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call";
-import CloseCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
-import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
-import RoomInfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info-solid";
-import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-solid";
-import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
-import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
-import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
-import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
-import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
-
-import { useRoomName } from "../../../hooks/useRoomName";
-import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
-import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
-import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMembers";
-import { _t } from "../../../languageHandler";
-import { Flex } from "../../utils/Flex";
-import { Box } from "../../utils/Box";
-import { getPlatformCallTypeProps, useRoomCall } from "../../../hooks/room/useRoomCall";
-import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
-import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
-import SdkConfig from "../../../SdkConfig";
-import { useFeatureEnabled } from "../../../hooks/useSettings";
-import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
-import { E2EStatus } from "../../../utils/ShieldUtils";
-import FacePile from "../elements/FacePile";
-import { useRoomState } from "../../../hooks/useRoomState";
-import RoomAvatar from "../avatars/RoomAvatar";
-import { formatCount } from "../../../utils/FormattingUtils";
-import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
-import PosthogTrackers from "../../../PosthogTrackers";
-import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
-import { RoomKnocksBar } from "./RoomKnocksBar";
-import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
-import { notificationLevelToIndicator } from "../../../utils/notifications";
-import { CallGuestLinkButton } from "./RoomHeader/CallGuestLinkButton";
-import { ButtonEvent } from "../elements/AccessibleButton";
-import WithPresenceIndicator, { useDmMember } from "../avatars/WithPresenceIndicator";
-import { IOOBData } from "../../../stores/ThreepidInviteStore";
-import { MainSplitContentType } from "../../structures/RoomView";
-import defaultDispatcher from "../../../dispatcher/dispatcher.ts";
-import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog.tsx";
-import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
-
-export default function RoomHeader({
-    room,
-    additionalButtons,
-    oobData,
-}: {
-    room: Room;
-    additionalButtons?: ViewRoomOpts["buttons"];
-    oobData?: IOOBData;
-}): JSX.Element {
-    const client = useMatrixClientContext();
-
-    const roomName = useRoomName(room);
-    const joinRule = useRoomState(room, (state) => state.getJoinRule());
-
-    const members = useRoomMembers(room, 2500);
-    const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
-
-    const {
-        voiceCallDisabledReason,
-        voiceCallClick,
-        videoCallDisabledReason,
-        videoCallClick,
-        toggleCallMaximized: toggleCall,
-        isViewingCall,
-        isConnectedToCall,
-        hasActiveCallSession,
-        callOptions,
-        showVoiceCallButton,
-        showVideoCallButton,
-    } = useRoomCall(room);
-
-    const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
-    /**
-     * A special mode where only Element Call is used. In this case we want to
-     * hide the voice call button
-     */
-    const useElementCallExclusively = useMemo(() => {
-        return SdkConfig.get("element_call").use_exclusively && groupCallsEnabled;
-    }, [groupCallsEnabled]);
-
-    const threadNotifications = useRoomThreadNotifications(room);
-    const globalNotificationState = useGlobalNotificationState();
-
-    const dmMember = useDmMember(room);
-    const isDirectMessage = !!dmMember;
-    const e2eStatus = useEncryptionStatus(client, room);
-
-    const notificationsEnabled = useFeatureEnabled("feature_notifications");
-
-    const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
-
-    const videoClick = useCallback(
-        (ev: React.MouseEvent) => videoCallClick(ev, callOptions[0]),
-        [callOptions, videoCallClick],
-    );
-
-    const toggleCallButton = (
-        <Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>
-            <IconButton onClick={toggleCall}>
-                <VideoCallIcon />
-            </IconButton>
-        </Tooltip>
-    );
-
-    const joinCallButton = (
-        <Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
-            <Button
-                size="sm"
-                onClick={videoClick}
-                Icon={VideoCallIcon}
-                className="mx_RoomHeader_join_button"
-                disabled={!!videoCallDisabledReason}
-                color="primary"
-                aria-label={videoCallDisabledReason ?? _t("action|join")}
-            >
-                {_t("action|join")}
-            </Button>
-        </Tooltip>
-    );
-
-    const callIconWithTooltip = (
-        <Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
-            <VideoCallIcon />
-        </Tooltip>
-    );
-
-    const [menuOpen, setMenuOpen] = useState(false);
-
-    const onOpenChange = useCallback(
-        (newOpen: boolean) => {
-            if (!videoCallDisabledReason) setMenuOpen(newOpen);
-        },
-        [videoCallDisabledReason],
-    );
-
-    const startVideoCallButton = (
-        <>
-            {/* Can be either a menu or just a button depending on the number of call options.*/}
-            {callOptions.length > 1 ? (
-                <Menu
-                    open={menuOpen}
-                    onOpenChange={onOpenChange}
-                    title={_t("voip|video_call_using")}
-                    trigger={
-                        <IconButton
-                            disabled={!!videoCallDisabledReason}
-                            aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
-                        >
-                            {callIconWithTooltip}
-                        </IconButton>
-                    }
-                    side="left"
-                    align="start"
-                >
-                    {callOptions.map((option) => {
-                        const { label, children } = getPlatformCallTypeProps(option);
-                        return (
-                            <MenuItem
-                                key={option}
-                                label={label}
-                                aria-label={label}
-                                children={children}
-                                className="mx_RoomHeader_videoCallOption"
-                                onClick={(ev) => videoCallClick(ev, option)}
-                                Icon={VideoCallIcon}
-                                onSelect={() => {} /* Dummy handler since we want the click event.*/}
-                            />
-                        );
-                    })}
-                </Menu>
-            ) : (
-                <IconButton
-                    disabled={!!videoCallDisabledReason}
-                    aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
-                    onClick={videoClick}
-                >
-                    {callIconWithTooltip}
-                </IconButton>
-            )}
-        </>
-    );
-    let voiceCallButton: JSX.Element | undefined = (
-        <Tooltip label={voiceCallDisabledReason ?? _t("voip|voice_call")}>
-            <IconButton
-                // We need both: isViewingCall and isConnectedToCall
-                //  - in the Lobby we are viewing a call but are not connected to it.
-                //  - in pip view we are connected to the call but not viewing it.
-                disabled={!!voiceCallDisabledReason || isViewingCall || isConnectedToCall}
-                aria-label={voiceCallDisabledReason ?? _t("voip|voice_call")}
-                onClick={(ev) => voiceCallClick(ev, callOptions[0])}
-            >
-                <VoiceCallIcon />
-            </IconButton>
-        </Tooltip>
-    );
-    const closeLobbyButton = (
-        <Tooltip label={_t("voip|close_lobby")}>
-            <IconButton onClick={toggleCall}>
-                <CloseCallIcon />
-            </IconButton>
-        </Tooltip>
-    );
-    let videoCallButton: JSX.Element | undefined = startVideoCallButton;
-    if (isConnectedToCall) {
-        videoCallButton = toggleCallButton;
-    } else if (isViewingCall) {
-        videoCallButton = closeLobbyButton;
-    }
-
-    if (!showVideoCallButton) {
-        videoCallButton = undefined;
-    }
-    if (!showVoiceCallButton) {
-        voiceCallButton = undefined;
-    }
-
-    const roomContext = useScopedRoomContext("mainSplitContentType");
-    const isVideoRoom = calcIsVideoRoom(room);
-    const showChatButton =
-        isVideoRoom ||
-        roomContext.mainSplitContentType === MainSplitContentType.MaximisedWidget ||
-        roomContext.mainSplitContentType === MainSplitContentType.Call;
-
-    const onAvatarClick = (): void => {
-        defaultDispatcher.dispatch({
-            action: "open_room_settings",
-            initial_tab_id: RoomSettingsTab.General,
-        });
-    };
-
-    return (
-        <>
-            <Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
-                <WithPresenceIndicator room={room} size="8px">
-                    {/* We hide this from the tabIndex list as it is a pointer shortcut and superfluous for a11y */}
-                    <RoomAvatar
-                        room={room}
-                        size="40px"
-                        oobData={oobData}
-                        onClick={onAvatarClick}
-                        tabIndex={-1}
-                        aria-label={_t("room|header_avatar_open_settings_label")}
-                    />
-                </WithPresenceIndicator>
-                <button
-                    aria-label={_t("right_panel|room_summary_card|title")}
-                    tabIndex={0}
-                    onClick={() => RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary)}
-                    className="mx_RoomHeader_infoWrapper"
-                >
-                    <Box flex="1" className="mx_RoomHeader_info">
-                        <BodyText
-                            as="div"
-                            size="lg"
-                            weight="semibold"
-                            dir="auto"
-                            role="heading"
-                            aria-level={1}
-                            className="mx_RoomHeader_heading"
-                        >
-                            <span className="mx_RoomHeader_truncated mx_lineClamp">{roomName}</span>
-
-                            {!isDirectMessage && joinRule === JoinRule.Public && (
-                                <Tooltip label={_t("common|public_room")} placement="right">
-                                    <PublicIcon
-                                        width="16px"
-                                        height="16px"
-                                        className="mx_RoomHeader_icon text-secondary"
-                                        aria-label={_t("common|public_room")}
-                                    />
-                                </Tooltip>
-                            )}
-
-                            {isDirectMessage && e2eStatus === E2EStatus.Verified && (
-                                <Tooltip label={_t("common|verified")} placement="right">
-                                    <VerifiedIcon
-                                        width="16px"
-                                        height="16px"
-                                        className="mx_RoomHeader_icon mx_Verified"
-                                        aria-label={_t("common|verified")}
-                                    />
-                                </Tooltip>
-                            )}
-
-                            {isDirectMessage && e2eStatus === E2EStatus.Warning && (
-                                <Tooltip label={_t("room|header_untrusted_label")} placement="right">
-                                    <ErrorIcon
-                                        width="16px"
-                                        height="16px"
-                                        className="mx_RoomHeader_icon mx_Untrusted"
-                                        aria-label={_t("room|header_untrusted_label")}
-                                    />
-                                </Tooltip>
-                            )}
-                        </BodyText>
-                    </Box>
-                </button>
-
-                {additionalButtons?.map((props) => {
-                    const label = props.label();
-
-                    return (
-                        <Tooltip label={label} key={props.id}>
-                            <IconButton
-                                aria-label={label}
-                                onClick={(event) => {
-                                    event.stopPropagation();
-                                    props.onClick();
-                                }}
-                            >
-                                {typeof props.icon === "function" ? props.icon() : props.icon}
-                            </IconButton>
-                        </Tooltip>
-                    );
-                })}
-
-                {isViewingCall && <CallGuestLinkButton room={room} />}
-
-                {hasActiveCallSession && !isConnectedToCall && !isViewingCall ? (
-                    joinCallButton
-                ) : (
-                    <>
-                        {!isVideoRoom && videoCallButton}
-                        {!useElementCallExclusively && !isVideoRoom && voiceCallButton}
-                    </>
-                )}
-
-                {showChatButton && <VideoRoomChatButton room={room} />}
-
-                <Tooltip label={_t("common|threads")}>
-                    <IconButton
-                        indicator={notificationLevelToIndicator(threadNotifications)}
-                        onClick={(evt) => {
-                            evt.stopPropagation();
-                            RightPanelStore.instance.showOrHidePhase(RightPanelPhases.ThreadPanel);
-                            PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
-                        }}
-                        aria-label={_t("common|threads")}
-                    >
-                        <ThreadsIcon />
-                    </IconButton>
-                </Tooltip>
-                {notificationsEnabled && (
-                    <Tooltip label={_t("notifications|enable_prompt_toast_title")}>
-                        <IconButton
-                            indicator={notificationLevelToIndicator(globalNotificationState.level)}
-                            onClick={(evt) => {
-                                evt.stopPropagation();
-                                RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
-                            }}
-                            aria-label={_t("notifications|enable_prompt_toast_title")}
-                        >
-                            <NotificationsIcon />
-                        </IconButton>
-                    </Tooltip>
-                )}
-
-                <Tooltip label={_t("right_panel|room_summary_card|title")}>
-                    <IconButton
-                        onClick={(evt) => {
-                            evt.stopPropagation();
-                            RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
-                        }}
-                        aria-label={_t("right_panel|room_summary_card|title")}
-                    >
-                        <RoomInfoIcon />
-                    </IconButton>
-                </Tooltip>
-
-                {!isDirectMessage && (
-                    <BodyText as="div" size="sm" weight="medium">
-                        <FacePile
-                            className="mx_RoomHeader_members"
-                            members={members.slice(0, 3)}
-                            size="20px"
-                            overflow={false}
-                            viewUserOnClick={false}
-                            tooltipLabel={_t("room|header_face_pile_tooltip")}
-                            onClick={(e: ButtonEvent) => {
-                                RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
-                                e.stopPropagation();
-                            }}
-                            aria-label={_t("common|n_members", { count: memberCount })}
-                        >
-                            {formatCount(memberCount)}
-                        </FacePile>
-                    </BodyText>
-                )}
-            </Flex>
-            {askToJoinEnabled && <RoomKnocksBar room={room} />}
-        </>
-    );
-}
diff --git a/src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx b/src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx
index 76e91cada6c0006277d5bafcdd14b6bfdea135b8..16ef5c22344f09207111707acf1cafc74249f189 100644
--- a/src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx
+++ b/src/components/views/rooms/RoomHeader/CallGuestLinkButton.tsx
@@ -9,15 +9,16 @@ import ExternalLinkIcon from "@vector-im/compound-design-tokens/assets/web/icons
 import { Button, IconButton, Tooltip } from "@vector-im/compound-web";
 import React, { useCallback } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
 
 import Modal from "../../../../Modal";
 import { ShareDialog } from "../../dialogs/ShareDialog";
 import { _t } from "../../../../languageHandler";
-import SettingsStore from "../../../../settings/SettingsStore";
 import { calculateRoomVia } from "../../../../utils/permalinks/Permalinks";
 import BaseDialog from "../../dialogs/BaseDialog";
 import { useGuestAccessInformation } from "../../../../hooks/room/useGuestAccessInformation";
+import JoinRuleSettings from "../../settings/JoinRuleSettings";
+import SettingsStore from "../../../../settings/SettingsStore";
 
 /**
  * Display a button to open a dialog to share a link to the call using a element call guest spa url (`element_call:guest_spa_url` in the EW config).
@@ -114,33 +115,32 @@ export const JoinRuleDialog: React.FC<{
                 "",
             );
             // Show the dialog for a bit to give the user feedback
-            setTimeout(() => onFinished(), 500);
+            setTimeout(() => onFinished(), 1000);
         },
         [isUpdating, onFinished, room.client, room.roomId],
     );
     return (
         <BaseDialog title={_t("update_room_access_modal|title")} onFinished={onFinished} className="mx_JoinRuleDialog">
-            <p>{_t("update_room_access_modal|description")}</p>
-            <div className="mx_JoinRuleDialogButtons">
-                {askToJoinEnabled && canInvite && (
-                    <Button
-                        kind="secondary"
-                        className="mx_Dialog_nonDialogButton"
-                        disabled={isUpdating === JoinRule.Knock}
-                        onClick={() => changeJoinRule(JoinRule.Knock)}
-                    >
-                        {_t("action|ask_to_join")}
-                    </Button>
-                )}
-                <Button
-                    className="mx_Dialog_nonDialogButton"
-                    kind="destructive"
-                    disabled={isUpdating === JoinRule.Public}
-                    onClick={() => changeJoinRule(JoinRule.Public)}
-                >
-                    {_t("common|public")}
-                </Button>
-            </div>
+            <p>{_t("update_room_access_modal|description", {}, { b: (sub) => <strong>{sub}</strong> })}</p>
+            <p>
+                {_t("update_room_access_modal|revert_access_description", {}, { b: (sub) => <strong>{sub}</strong> })}
+            </p>
+            <JoinRuleSettings
+                recommendedOption={JoinRule.Knock}
+                room={room}
+                disabledOptions={new Set([JoinRule.Invite, JoinRule.Private, JoinRule.Restricted])}
+                hiddenOptions={
+                    new Set([JoinRule.Restricted].concat(askToJoinEnabled && canInvite ? [] : [JoinRule.Knock]))
+                }
+                beforeChange={async (newRule) => {
+                    await changeJoinRule(newRule).catch(() => {
+                        return false;
+                    });
+                    return true;
+                }}
+                closeSettingsFn={() => {}}
+                onError={(error: unknown) => logger.error("Could not generate change access level:", error)}
+            />
             <p>{_t("update_room_access_modal|dont_change_description")}</p>
             <div className="mx_JoinRuleDialogButtons">
                 <Button
diff --git a/src/components/views/rooms/RoomHeader/RoomHeader.tsx b/src/components/views/rooms/RoomHeader/RoomHeader.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4462e4468afee5bff27ed7547342cb7f15867f9e
--- /dev/null
+++ b/src/components/views/rooms/RoomHeader/RoomHeader.tsx
@@ -0,0 +1,412 @@
+/*
+Copyright 2024 New Vector Ltd.
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type JSX, useCallback, useMemo, useState } from "react";
+import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
+import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
+import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call-solid";
+import CloseCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
+import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
+import RoomInfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info-solid";
+import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-solid";
+import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
+import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
+import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
+import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+
+import { useRoomName } from "../../../../hooks/useRoomName.ts";
+import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
+import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
+import { useRoomMemberCount, useRoomMembers } from "../../../../hooks/useRoomMembers.ts";
+import { _t } from "../../../../languageHandler.tsx";
+import { Flex } from "../../../utils/Flex.tsx";
+import { Box } from "../../../utils/Box.tsx";
+import { getPlatformCallTypeProps, useRoomCall } from "../../../../hooks/room/useRoomCall.tsx";
+import { useRoomThreadNotifications } from "../../../../hooks/room/useRoomThreadNotifications.ts";
+import { useGlobalNotificationState } from "../../../../hooks/useGlobalNotificationState.ts";
+import SdkConfig from "../../../../SdkConfig.ts";
+import { useFeatureEnabled } from "../../../../hooks/useSettings.ts";
+import { useEncryptionStatus } from "../../../../hooks/useEncryptionStatus.ts";
+import { E2EStatus } from "../../../../utils/ShieldUtils.ts";
+import FacePile from "../../elements/FacePile.tsx";
+import { useRoomState } from "../../../../hooks/useRoomState.ts";
+import RoomAvatar from "../../avatars/RoomAvatar.tsx";
+import { formatCount } from "../../../../utils/FormattingUtils.ts";
+import RightPanelStore from "../../../../stores/right-panel/RightPanelStore.ts";
+import PosthogTrackers from "../../../../PosthogTrackers.ts";
+import { VideoRoomChatButton } from "./VideoRoomChatButton.tsx";
+import { RoomKnocksBar } from "../RoomKnocksBar.tsx";
+import { isVideoRoom as calcIsVideoRoom } from "../../../../utils/video-rooms.ts";
+import { notificationLevelToIndicator } from "../../../../utils/notifications.ts";
+import { CallGuestLinkButton } from "./CallGuestLinkButton.tsx";
+import { type ButtonEvent } from "../../elements/AccessibleButton.tsx";
+import WithPresenceIndicator, { useDmMember } from "../../avatars/WithPresenceIndicator.tsx";
+import { type IOOBData } from "../../../../stores/ThreepidInviteStore.ts";
+import { MainSplitContentType } from "../../../structures/RoomView.tsx";
+import defaultDispatcher from "../../../../dispatcher/dispatcher.ts";
+import { RoomSettingsTab } from "../../dialogs/RoomSettingsDialog.tsx";
+import { useScopedRoomContext } from "../../../../contexts/ScopedRoomContext.tsx";
+import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx";
+import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
+
+export default function RoomHeader({
+    room,
+    additionalButtons,
+    oobData,
+}: {
+    room: Room;
+    additionalButtons?: ViewRoomOpts["buttons"];
+    oobData?: IOOBData;
+}): JSX.Element {
+    const client = useMatrixClientContext();
+
+    const roomName = useRoomName(room);
+    const joinRule = useRoomState(room, (state) => state.getJoinRule());
+
+    const members = useRoomMembers(room, 2500);
+    const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
+
+    const {
+        voiceCallDisabledReason,
+        voiceCallClick,
+        videoCallDisabledReason,
+        videoCallClick,
+        toggleCallMaximized: toggleCall,
+        isViewingCall,
+        isConnectedToCall,
+        hasActiveCallSession,
+        callOptions,
+        showVoiceCallButton,
+        showVideoCallButton,
+    } = useRoomCall(room);
+
+    const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
+    /**
+     * A special mode where only Element Call is used. In this case we want to
+     * hide the voice call button
+     */
+    const useElementCallExclusively = useMemo(() => {
+        return SdkConfig.get("element_call").use_exclusively && groupCallsEnabled;
+    }, [groupCallsEnabled]);
+
+    const threadNotifications = useRoomThreadNotifications(room);
+    const globalNotificationState = useGlobalNotificationState();
+
+    const dmMember = useDmMember(room);
+    const isDirectMessage = !!dmMember;
+    const e2eStatus = useEncryptionStatus(client, room);
+
+    const notificationsEnabled = useFeatureEnabled("feature_notifications");
+
+    const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
+
+    const videoClick = useCallback(
+        (ev: React.MouseEvent) => videoCallClick(ev, callOptions[0]),
+        [callOptions, videoCallClick],
+    );
+
+    const toggleCallButton = (
+        <Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>
+            <IconButton onClick={toggleCall}>
+                <VideoCallIcon />
+            </IconButton>
+        </Tooltip>
+    );
+
+    const joinCallButton = (
+        <Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
+            <Button
+                size="sm"
+                onClick={videoClick}
+                Icon={VideoCallIcon}
+                className="mx_RoomHeader_join_button"
+                disabled={!!videoCallDisabledReason}
+                color="primary"
+                aria-label={videoCallDisabledReason ?? _t("action|join")}
+            >
+                {_t("action|join")}
+            </Button>
+        </Tooltip>
+    );
+
+    const callIconWithTooltip = (
+        <Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
+            <VideoCallIcon />
+        </Tooltip>
+    );
+
+    const [menuOpen, setMenuOpen] = useState(false);
+
+    const onOpenChange = useCallback(
+        (newOpen: boolean) => {
+            if (!videoCallDisabledReason) setMenuOpen(newOpen);
+        },
+        [videoCallDisabledReason],
+    );
+
+    const startVideoCallButton = (
+        <>
+            {/* Can be either a menu or just a button depending on the number of call options.*/}
+            {callOptions.length > 1 ? (
+                <Menu
+                    open={menuOpen}
+                    onOpenChange={onOpenChange}
+                    title={_t("voip|video_call_using")}
+                    trigger={
+                        <IconButton
+                            disabled={!!videoCallDisabledReason}
+                            aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
+                        >
+                            {callIconWithTooltip}
+                        </IconButton>
+                    }
+                    side="left"
+                    align="start"
+                >
+                    {callOptions.map((option) => {
+                        const { label, children } = getPlatformCallTypeProps(option);
+                        return (
+                            <MenuItem
+                                key={option}
+                                label={label}
+                                aria-label={label}
+                                children={children}
+                                className="mx_RoomHeader_videoCallOption"
+                                onClick={(ev) => videoCallClick(ev, option)}
+                                Icon={VideoCallIcon}
+                                onSelect={() => {} /* Dummy handler since we want the click event.*/}
+                            />
+                        );
+                    })}
+                </Menu>
+            ) : (
+                <IconButton
+                    disabled={!!videoCallDisabledReason}
+                    aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
+                    onClick={videoClick}
+                >
+                    {callIconWithTooltip}
+                </IconButton>
+            )}
+        </>
+    );
+    let voiceCallButton: JSX.Element | undefined = (
+        <Tooltip label={voiceCallDisabledReason ?? _t("voip|voice_call")}>
+            <IconButton
+                // We need both: isViewingCall and isConnectedToCall
+                //  - in the Lobby we are viewing a call but are not connected to it.
+                //  - in pip view we are connected to the call but not viewing it.
+                disabled={!!voiceCallDisabledReason || isViewingCall || isConnectedToCall}
+                aria-label={voiceCallDisabledReason ?? _t("voip|voice_call")}
+                onClick={(ev) => voiceCallClick(ev, callOptions[0])}
+            >
+                <VoiceCallIcon />
+            </IconButton>
+        </Tooltip>
+    );
+    const closeLobbyButton = (
+        <Tooltip label={_t("voip|close_lobby")}>
+            <IconButton onClick={toggleCall}>
+                <CloseCallIcon />
+            </IconButton>
+        </Tooltip>
+    );
+    let videoCallButton: JSX.Element | undefined = startVideoCallButton;
+    if (isConnectedToCall) {
+        videoCallButton = toggleCallButton;
+    } else if (isViewingCall) {
+        videoCallButton = closeLobbyButton;
+    }
+
+    if (!showVideoCallButton) {
+        videoCallButton = undefined;
+    }
+    if (!showVoiceCallButton) {
+        voiceCallButton = undefined;
+    }
+
+    const roomContext = useScopedRoomContext("mainSplitContentType");
+    const isVideoRoom = calcIsVideoRoom(room);
+    const showChatButton =
+        isVideoRoom ||
+        roomContext.mainSplitContentType === MainSplitContentType.MaximisedWidget ||
+        roomContext.mainSplitContentType === MainSplitContentType.Call;
+
+    const onAvatarClick = (): void => {
+        defaultDispatcher.dispatch({
+            action: "open_room_settings",
+            initial_tab_id: RoomSettingsTab.General,
+        });
+    };
+
+    return (
+        <>
+            <CurrentRightPanelPhaseContextProvider roomId={room.roomId}>
+                <Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
+                    <WithPresenceIndicator room={room} size="8px">
+                        {/* We hide this from the tabIndex list as it is a pointer shortcut and superfluous for a11y */}
+                        <RoomAvatar
+                            room={room}
+                            size="40px"
+                            oobData={oobData}
+                            onClick={onAvatarClick}
+                            tabIndex={-1}
+                            aria-label={_t("room|header_avatar_open_settings_label")}
+                        />
+                    </WithPresenceIndicator>
+                    <button
+                        aria-label={_t("right_panel|room_summary_card|title")}
+                        tabIndex={0}
+                        onClick={() => RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary)}
+                        className="mx_RoomHeader_infoWrapper"
+                    >
+                        <Box flex="1" className="mx_RoomHeader_info">
+                            <BodyText
+                                as="div"
+                                size="lg"
+                                weight="semibold"
+                                dir="auto"
+                                role="heading"
+                                aria-level={1}
+                                className="mx_RoomHeader_heading"
+                            >
+                                <span className="mx_RoomHeader_truncated mx_lineClamp">{roomName}</span>
+
+                                {!isDirectMessage && joinRule === JoinRule.Public && (
+                                    <Tooltip label={_t("common|public_room")} placement="right">
+                                        <PublicIcon
+                                            width="16px"
+                                            height="16px"
+                                            className="mx_RoomHeader_icon text-secondary"
+                                            aria-label={_t("common|public_room")}
+                                        />
+                                    </Tooltip>
+                                )}
+
+                                {isDirectMessage && e2eStatus === E2EStatus.Verified && (
+                                    <Tooltip label={_t("common|verified")} placement="right">
+                                        <VerifiedIcon
+                                            width="16px"
+                                            height="16px"
+                                            className="mx_RoomHeader_icon mx_Verified"
+                                            aria-label={_t("common|verified")}
+                                        />
+                                    </Tooltip>
+                                )}
+
+                                {isDirectMessage && e2eStatus === E2EStatus.Warning && (
+                                    <Tooltip label={_t("room|header_untrusted_label")} placement="right">
+                                        <ErrorIcon
+                                            width="16px"
+                                            height="16px"
+                                            className="mx_RoomHeader_icon mx_Untrusted"
+                                            aria-label={_t("room|header_untrusted_label")}
+                                        />
+                                    </Tooltip>
+                                )}
+                            </BodyText>
+                        </Box>
+                    </button>
+
+                    {additionalButtons?.map((props) => {
+                        const label = props.label();
+
+                        return (
+                            <Tooltip label={label} key={props.id}>
+                                <IconButton
+                                    aria-label={label}
+                                    onClick={(event) => {
+                                        event.stopPropagation();
+                                        props.onClick();
+                                    }}
+                                >
+                                    {typeof props.icon === "function" ? props.icon() : props.icon}
+                                </IconButton>
+                            </Tooltip>
+                        );
+                    })}
+
+                    {isViewingCall && <CallGuestLinkButton room={room} />}
+
+                    {hasActiveCallSession && !isConnectedToCall && !isViewingCall ? (
+                        joinCallButton
+                    ) : (
+                        <>
+                            {!isVideoRoom && videoCallButton}
+                            {!useElementCallExclusively && !isVideoRoom && voiceCallButton}
+                        </>
+                    )}
+
+                    {showChatButton && <VideoRoomChatButton room={room} />}
+
+                    <Tooltip label={_t("common|threads")}>
+                        <IconButton
+                            indicator={notificationLevelToIndicator(threadNotifications)}
+                            onClick={(evt) => {
+                                evt.stopPropagation();
+                                RightPanelStore.instance.showOrHidePhase(RightPanelPhases.ThreadPanel);
+                                PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
+                            }}
+                            aria-label={_t("common|threads")}
+                        >
+                            <ToggleableIcon Icon={ThreadsIcon} phase={RightPanelPhases.ThreadPanel} />
+                        </IconButton>
+                    </Tooltip>
+                    {notificationsEnabled && (
+                        <Tooltip label={_t("notifications|enable_prompt_toast_title")}>
+                            <IconButton
+                                indicator={notificationLevelToIndicator(globalNotificationState.level)}
+                                onClick={(evt) => {
+                                    evt.stopPropagation();
+                                    RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
+                                }}
+                                aria-label={_t("notifications|enable_prompt_toast_title")}
+                            >
+                                <ToggleableIcon Icon={NotificationsIcon} phase={RightPanelPhases.NotificationPanel} />
+                            </IconButton>
+                        </Tooltip>
+                    )}
+
+                    <Tooltip label={_t("right_panel|room_summary_card|title")}>
+                        <IconButton
+                            onClick={(evt) => {
+                                evt.stopPropagation();
+                                RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
+                            }}
+                            aria-label={_t("right_panel|room_summary_card|title")}
+                        >
+                            <ToggleableIcon Icon={RoomInfoIcon} phase={RightPanelPhases.RoomSummary} />
+                        </IconButton>
+                    </Tooltip>
+
+                    {!isDirectMessage && (
+                        <BodyText as="div" size="sm" weight="medium">
+                            <FacePile
+                                className="mx_RoomHeader_members"
+                                members={members.slice(0, 3)}
+                                size="20px"
+                                overflow={false}
+                                viewUserOnClick={false}
+                                tooltipLabel={_t("room|header_face_pile_tooltip")}
+                                onClick={(e: ButtonEvent) => {
+                                    RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
+                                    e.stopPropagation();
+                                }}
+                                aria-label={_t("common|n_members", { count: memberCount })}
+                            >
+                                {formatCount(memberCount)}
+                            </FacePile>
+                        </BodyText>
+                    )}
+                </Flex>
+                {askToJoinEnabled && <RoomKnocksBar room={room} />}
+            </CurrentRightPanelPhaseContextProvider>
+        </>
+    );
+}
diff --git a/src/components/views/rooms/RoomHeader/VideoRoomChatButton.tsx b/src/components/views/rooms/RoomHeader/VideoRoomChatButton.tsx
index 0dbf62dfda3336869356de5a42591db44d077417..792b2903dfe9ef176c7ee3cc5fdc8577004be1da 100644
--- a/src/components/views/rooms/RoomHeader/VideoRoomChatButton.tsx
+++ b/src/components/views/rooms/RoomHeader/VideoRoomChatButton.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React, { useContext } from "react";
 import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat-solid";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { IconButton, Tooltip } from "@vector-im/compound-web";
 
 import { _t } from "../../../../languageHandler";
@@ -17,7 +17,8 @@ import { NotificationStateEvents } from "../../../../stores/notifications/Notifi
 import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
 import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases";
 import { SDKContext } from "../../../../contexts/SDKContext";
-import { ButtonEvent } from "../../elements/AccessibleButton";
+import { type ButtonEvent } from "../../elements/AccessibleButton";
+import { ToggleableIcon } from "./toggle/ToggleableIcon";
 
 /**
  * Display a button to toggle timeline for video rooms
@@ -54,7 +55,7 @@ export const VideoRoomChatButton: React.FC<{ room: Room }> = ({ room }) => {
                 onClick={onClick}
                 indicator={displayUnreadIndicator ? "default" : undefined}
             >
-                <ChatIcon />
+                <ToggleableIcon Icon={ChatIcon} phase={RightPanelPhases.Timeline} />
             </IconButton>
         </Tooltip>
     );
diff --git a/src/components/views/rooms/RoomHeader/toggle/ToggleableIcon.tsx b/src/components/views/rooms/RoomHeader/toggle/ToggleableIcon.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..72f4d6bb8c452df6d5d05931744cdf076cbcb322
--- /dev/null
+++ b/src/components/views/rooms/RoomHeader/toggle/ToggleableIcon.tsx
@@ -0,0 +1,30 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import classNames from "classnames";
+
+import { type RightPanelPhases } from "../../../../../stores/right-panel/RightPanelStorePhases";
+import { useToggled } from "./useToggled";
+
+type Props = {
+    Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
+    phase: RightPanelPhases;
+};
+
+/**
+ * Use this component for room header icons that toggle different right panel phases.
+ * Will add a class to the icon when the specified phase is on.
+ */
+export function ToggleableIcon({ Icon, phase }: Props): React.ReactElement {
+    const toggled = useToggled(phase);
+    const highlightClass = classNames({
+        mx_RoomHeader_toggled: toggled,
+    });
+
+    return <Icon className={highlightClass} />;
+}
diff --git a/src/components/views/rooms/RoomHeader/toggle/useToggled.tsx b/src/components/views/rooms/RoomHeader/toggle/useToggled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3dedc5558161e55dd4731d472d85ae8aee76ab32
--- /dev/null
+++ b/src/components/views/rooms/RoomHeader/toggle/useToggled.tsx
@@ -0,0 +1,23 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useContext } from "react";
+
+import { type RightPanelPhases } from "../../../../../stores/right-panel/RightPanelStorePhases";
+import { CurrentRightPanelPhaseContext } from "../../../../../contexts/CurrentRightPanelPhaseContext";
+
+/**
+ * Hook to easily track whether a given right panel phase is toggled on/off.
+ */
+export function useToggled(phase: RightPanelPhases): boolean {
+    const context = useContext(CurrentRightPanelPhaseContext);
+    if (!context) {
+        return false;
+    }
+    const { currentPhase, isPanelOpen } = context;
+    return !!(isPanelOpen && currentPhase === phase);
+}
diff --git a/src/components/views/rooms/RoomInfoLine.tsx b/src/components/views/rooms/RoomInfoLine.tsx
index 438b804d88d74d5b11455f283302f11b255c5f6b..dd5a113ecbd615f4d5f71736f1f9ab5946fee1d2 100644
--- a/src/components/views/rooms/RoomInfoLine.tsx
+++ b/src/components/views/rooms/RoomInfoLine.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC } from "react";
-import { Room, JoinRule, MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type FC } from "react";
+import { type Room, JoinRule, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/rooms/RoomKnocksBar.tsx b/src/components/views/rooms/RoomKnocksBar.tsx
index 64d69566258973a2228b0e8ed5337e7c681053ba..9b762f0b96c6e95bab6768b0d4c3731e54738da3 100644
--- a/src/components/views/rooms/RoomKnocksBar.tsx
+++ b/src/components/views/rooms/RoomKnocksBar.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { EventTimeline, JoinRule, type MatrixError, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
+import React, { type ReactElement, type ReactNode, useCallback, useState, type FC } from "react";
 import { CloseIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import dis from "../../../dispatcher/dispatcher";
@@ -22,7 +22,7 @@ import AccessibleButton from "../elements/AccessibleButton";
 import Heading from "../typography/Heading";
 import { formatList } from "../../../utils/FormattingUtils";
 
-export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
+export const RoomKnocksBar: FC<{ room: Room }> = ({ room }) => {
     const [disabled, setDisabled] = useState(false);
     const knockMembers = useTypedEventEmitterState(
         room,
diff --git a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1f04e4c86e1ce53a60070f4ead5a6f57b14802fa
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX, type PropsWithChildren } from "react";
+import { Button } from "@vector-im/compound-web";
+import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
+import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room";
+
+import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
+import { Flex } from "../../../utils/Flex";
+import { _t } from "../../../../languageHandler";
+import { FilterKey } from "../../../../stores/room-list-v3/skip-list/filters";
+import { type PrimaryFilter } from "../../../viewmodels/roomlist/useFilteredRooms";
+
+interface EmptyRoomListProps {
+    /**
+     * The view model for the room list
+     */
+    vm: RoomListViewState;
+}
+
+/**
+ * The empty state for the room list
+ */
+export function EmptyRoomList({ vm }: EmptyRoomListProps): JSX.Element | undefined {
+    // If there is no active primary filter, show the default empty state
+    if (!vm.activePrimaryFilter) return <DefaultPlaceholder vm={vm} />;
+
+    switch (vm.activePrimaryFilter.key) {
+        case FilterKey.FavouriteFilter:
+            return (
+                <GenericPlaceholder
+                    title={_t("room_list|empty|no_favourites")}
+                    description={_t("room_list|empty|no_favourites_description")}
+                />
+            );
+        case FilterKey.PeopleFilter:
+            return (
+                <GenericPlaceholder
+                    title={_t("room_list|empty|no_people")}
+                    description={_t("room_list|empty|no_people_description")}
+                />
+            );
+        case FilterKey.RoomsFilter:
+            return (
+                <GenericPlaceholder
+                    title={_t("room_list|empty|no_rooms")}
+                    description={_t("room_list|empty|no_rooms_description")}
+                />
+            );
+        case FilterKey.UnreadFilter:
+            return (
+                <ActionPlaceholder
+                    title={_t("room_list|empty|no_unread")}
+                    action={_t("room_list|empty|show_chats")}
+                    filter={vm.activePrimaryFilter}
+                />
+            );
+        case FilterKey.InvitesFilter:
+            return (
+                <ActionPlaceholder
+                    title={_t("room_list|empty|no_invites")}
+                    action={_t("room_list|empty|show_activity")}
+                    filter={vm.activePrimaryFilter}
+                />
+            );
+        case FilterKey.MentionsFilter:
+            return (
+                <ActionPlaceholder
+                    title={_t("room_list|empty|no_mentions")}
+                    action={_t("room_list|empty|show_activity")}
+                    filter={vm.activePrimaryFilter}
+                />
+            );
+        default:
+            return undefined;
+    }
+}
+
+interface GenericPlaceholderProps {
+    /**
+     * The title of the placeholder
+     */
+    title: string;
+    /**
+     * The description of the placeholder
+     */
+    description?: string;
+}
+
+/**
+ * A generic placeholder for the room list
+ */
+function GenericPlaceholder({ title, description, children }: PropsWithChildren<GenericPlaceholderProps>): JSX.Element {
+    return (
+        <Flex
+            data-testid="empty-room-list"
+            className="mx_EmptyRoomList_GenericPlaceholder"
+            direction="column"
+            align="stretch"
+            justify="center"
+            gap="var(--cpd-space-2x)"
+        >
+            <span className="mx_EmptyRoomList_GenericPlaceholder_title">{title}</span>
+            {description && <span className="mx_EmptyRoomList_GenericPlaceholder_description">{description}</span>}
+            {children}
+        </Flex>
+    );
+}
+
+interface DefaultPlaceholderProps {
+    /**
+     * The view model for the room list
+     */
+    vm: RoomListViewState;
+}
+
+/**
+ * The default empty state for the room list when no primary filter is active
+ * The user can create chat or room (if they have the permission)
+ */
+function DefaultPlaceholder({ vm }: DefaultPlaceholderProps): JSX.Element {
+    return (
+        <GenericPlaceholder
+            title={_t("room_list|empty|no_chats")}
+            description={
+                vm.canCreateRoom
+                    ? _t("room_list|empty|no_chats_description")
+                    : _t("room_list|empty|no_chats_description_no_room_rights")
+            }
+        >
+            <Flex
+                className="mx_EmptyRoomList_DefaultPlaceholder"
+                align="center"
+                justify="center"
+                direction="column"
+                gap="var(--cpd-space-4x)"
+            >
+                <Button size="sm" kind="secondary" Icon={UserAddIcon} onClick={vm.createChatRoom}>
+                    {_t("action|new_message")}
+                </Button>
+                {vm.canCreateRoom && (
+                    <Button size="sm" kind="secondary" Icon={RoomIcon} onClick={vm.createRoom}>
+                        {_t("action|new_room")}
+                    </Button>
+                )}
+            </Flex>
+        </GenericPlaceholder>
+    );
+}
+
+interface ActionPlaceholderProps {
+    filter: PrimaryFilter;
+    title: string;
+    action: string;
+}
+
+/**
+ * A placeholder for the room list when a filter is active
+ * The user can take action to toggle the filter
+ */
+function ActionPlaceholder({ filter, title, action }: ActionPlaceholderProps): JSX.Element {
+    return (
+        <GenericPlaceholder title={title}>
+            <Button kind="tertiary" onClick={filter.toggle}>
+                {action}
+            </Button>
+        </GenericPlaceholder>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomList.tsx b/src/components/views/rooms/RoomListPanel/RoomList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f5f610f5ae47d06e637f6c33d010aa916f9163cd
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomList.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { useCallback, type JSX } from "react";
+import { AutoSizer, List, type ListRowProps } from "react-virtualized";
+
+import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
+import { _t } from "../../../../languageHandler";
+import { RoomListItemView } from "./RoomListItemView";
+import { RovingTabIndexProvider } from "../../../../accessibility/RovingTabIndex";
+import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
+import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
+import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
+
+interface RoomListProps {
+    /**
+     * The view model state for the room list.
+     */
+    vm: RoomListViewState;
+}
+
+/**
+ * A virtualized list of rooms.
+ */
+export function RoomList({ vm: { rooms, activeIndex } }: RoomListProps): JSX.Element {
+    const roomRendererMemoized = useCallback(
+        ({ key, index, style }: ListRowProps) => (
+            <RoomListItemView room={rooms[index]} key={key} style={style} isSelected={activeIndex === index} />
+        ),
+        [rooms, activeIndex],
+    );
+
+    // The first div is needed to make the virtualized list take all the remaining space and scroll correctly
+    return (
+        <RovingTabIndexProvider handleHomeEnd={true} handleUpDown={true}>
+            {({ onKeyDownHandler }) => (
+                <div
+                    className="mx_RoomList"
+                    data-testid="room-list"
+                    onKeyDown={(ev) => {
+                        const navAction = getKeyBindingsManager().getNavigationAction(ev);
+                        if (
+                            navAction === KeyBindingAction.NextLandmark ||
+                            navAction === KeyBindingAction.PreviousLandmark
+                        ) {
+                            LandmarkNavigation.findAndFocusNextLandmark(
+                                Landmark.ROOM_LIST,
+                                navAction === KeyBindingAction.PreviousLandmark,
+                            );
+                            ev.stopPropagation();
+                            ev.preventDefault();
+                            return;
+                        }
+                        onKeyDownHandler(ev);
+                    }}
+                >
+                    <AutoSizer>
+                        {({ height, width }) => (
+                            <List
+                                aria-label={_t("room_list|list_title")}
+                                className="mx_RoomList_List"
+                                rowRenderer={roomRendererMemoized}
+                                rowCount={rooms.length}
+                                rowHeight={48}
+                                height={height}
+                                width={width}
+                                scrollToIndex={activeIndex ?? 0}
+                                tabIndex={-1}
+                            />
+                        )}
+                    </AutoSizer>
+                </div>
+            )}
+        </RovingTabIndexProvider>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx b/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..752f065fd4b379f8cb09736efbac620a00433e23
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+import React, { type JSX, useState } from "react";
+import { IconButton, Menu, MenuItem } from "@vector-im/compound-web";
+import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/compose";
+import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
+import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
+import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room";
+import HomeIcon from "@vector-im/compound-design-tokens/assets/web/icons/home";
+import PreferencesIcon from "@vector-im/compound-design-tokens/assets/web/icons/preferences";
+import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings";
+import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call";
+
+import { _t } from "../../../../languageHandler";
+import { Flex } from "../../../utils/Flex";
+import {
+    type RoomListHeaderViewState,
+    useRoomListHeaderViewModel,
+} from "../../../viewmodels/roomlist/RoomListHeaderViewModel";
+import { RoomListOptionsMenu } from "./RoomListOptionsMenu";
+
+/**
+ * The header view for the room list
+ * The space name is displayed and a compose menu is shown if the user can create rooms
+ */
+export function RoomListHeaderView(): JSX.Element {
+    const vm = useRoomListHeaderViewModel();
+
+    return (
+        <Flex
+            as="header"
+            className="mx_RoomListHeaderView"
+            aria-label={_t("room|context_menu|title")}
+            justify="space-between"
+            align="center"
+            data-testid="room-list-header"
+        >
+            <Flex className="mx_RoomListHeaderView_title" align="center" gap="var(--cpd-space-1x)">
+                <h1 title={vm.title}>{vm.title}</h1>
+                {vm.displaySpaceMenu && <SpaceMenu vm={vm} />}
+            </Flex>
+            <Flex align="center" gap="var(--cpd-space-2x)">
+                <RoomListOptionsMenu vm={vm} />
+                {/* If we don't display the compose menu, it means that the user can only send DM */}
+                {vm.displayComposeMenu ? (
+                    <ComposeMenu vm={vm} />
+                ) : (
+                    <IconButton aria-label={_t("action|new_message")} onClick={(e) => vm.createChatRoom(e.nativeEvent)}>
+                        <ComposeIcon color="var(--cpd-color-icon-secondary)" />
+                    </IconButton>
+                )}
+            </Flex>
+        </Flex>
+    );
+}
+
+interface SpaceMenuProps {
+    /**
+     * The view model for the room list header
+     */
+    vm: RoomListHeaderViewState;
+}
+
+/**
+ * The space menu for the room list header
+ */
+function SpaceMenu({ vm }: SpaceMenuProps): JSX.Element {
+    const [open, setOpen] = useState(false);
+
+    return (
+        <Menu
+            open={open}
+            onOpenChange={setOpen}
+            title={vm.title}
+            side="right"
+            align="start"
+            trigger={
+                <IconButton className="mx_SpaceMenu_button" aria-label={_t("room_list|open_space_menu")} size="20px">
+                    <ChevronDownIcon color="var(--cpd-color-icon-secondary)" />
+                </IconButton>
+            }
+        >
+            <MenuItem
+                Icon={HomeIcon}
+                label={_t("room_list|space_menu|home")}
+                onSelect={vm.openSpaceHome}
+                hideChevron={true}
+            />
+            {vm.canInviteInSpace && (
+                <MenuItem
+                    Icon={UserAddIcon}
+                    label={_t("action|invite")}
+                    onSelect={vm.inviteInSpace}
+                    hideChevron={true}
+                />
+            )}
+            <MenuItem
+                Icon={PreferencesIcon}
+                label={_t("common|preferences")}
+                onSelect={vm.openSpacePreferences}
+                hideChevron={true}
+            />
+            {vm.canAccessSpaceSettings && (
+                <MenuItem
+                    Icon={SettingsIcon}
+                    label={_t("room_list|space_menu|space_settings")}
+                    onSelect={vm.openSpaceSettings}
+                    hideChevron={true}
+                />
+            )}
+        </Menu>
+    );
+}
+
+interface ComposeMenuProps {
+    /**
+     * The view model for the room list header
+     */
+    vm: RoomListHeaderViewState;
+}
+
+/**
+ * The compose menu for the room list header
+ */
+function ComposeMenu({ vm }: ComposeMenuProps): JSX.Element {
+    const [open, setOpen] = useState(false);
+
+    return (
+        <Menu
+            open={open}
+            onOpenChange={setOpen}
+            showTitle={false}
+            title={_t("action|open_menu")}
+            side="right"
+            align="start"
+            trigger={
+                <IconButton aria-label={_t("action|add")}>
+                    <ComposeIcon color="var(--cpd-color-icon-secondary)" />
+                </IconButton>
+            }
+        >
+            <MenuItem
+                Icon={UserAddIcon}
+                label={_t("action|new_message")}
+                onSelect={vm.createChatRoom}
+                hideChevron={true}
+            />
+            {vm.canCreateRoom && (
+                <MenuItem Icon={RoomIcon} label={_t("action|new_room")} onSelect={vm.createRoom} hideChevron={true} />
+            )}
+            {vm.canCreateVideoRoom && (
+                <MenuItem
+                    Icon={VideoCallIcon}
+                    label={_t("action|new_video_room")}
+                    onSelect={vm.createVideoRoom}
+                    hideChevron={true}
+                />
+            )}
+        </Menu>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..07b7be4b434a08756a8d9a1eb6cd6e3bc0026455
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type ComponentProps, type JSX, type Ref, useState } from "react";
+import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem, Tooltip } from "@vector-im/compound-web";
+import MarkAsReadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-read";
+import MarkAsUnreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-unread";
+import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite";
+import ArrowDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/arrow-down";
+import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
+import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
+import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
+import OverflowIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
+import NotificationIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-solid";
+import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
+import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
+import { type Room } from "matrix-js-sdk/src/matrix";
+
+import { _t } from "../../../../languageHandler";
+import { Flex } from "../../../utils/Flex";
+import {
+    type RoomListItemMenuViewState,
+    useRoomListItemMenuViewModel,
+} from "../../../viewmodels/roomlist/RoomListItemMenuViewModel";
+import { RoomNotifState } from "../../../../RoomNotifs";
+
+interface RoomListItemMenuViewProps {
+    /**
+     * The room to display the menu for.
+     */
+    room: Room;
+    /**
+     * Set the menu open state.
+     * @param isOpen
+     */
+    setMenuOpen: (isOpen: boolean) => void;
+}
+
+/**
+ * A view for the room list item menu.
+ */
+export function RoomListItemMenuView({ room, setMenuOpen }: RoomListItemMenuViewProps): JSX.Element {
+    const vm = useRoomListItemMenuViewModel(room);
+
+    return (
+        <Flex className="mx_RoomListItemMenuView" align="center" gap="var(--cpd-space-1x)">
+            {vm.showMoreOptionsMenu && <MoreOptionsMenu setMenuOpen={setMenuOpen} vm={vm} />}
+            {vm.showNotificationMenu && <NotificationMenu setMenuOpen={setMenuOpen} vm={vm} />}
+        </Flex>
+    );
+}
+
+interface MoreOptionsMenuProps {
+    /**
+     * The view model state for the menu.
+     */
+    vm: RoomListItemMenuViewState;
+    /**
+     * Set the menu open state.
+     * @param isOpen
+     */
+    setMenuOpen: (isOpen: boolean) => void;
+}
+
+/**
+ * The more options menu for the room list item.
+ */
+function MoreOptionsMenu({ vm, setMenuOpen }: MoreOptionsMenuProps): JSX.Element {
+    const [open, setOpen] = useState(false);
+
+    return (
+        <Menu
+            open={open}
+            onOpenChange={(isOpen) => {
+                setOpen(isOpen);
+                setMenuOpen(isOpen);
+            }}
+            title={_t("room_list|room|more_options")}
+            showTitle={false}
+            align="start"
+            trigger={<MoreOptionsButton size="24px" />}
+        >
+            {vm.canMarkAsRead && (
+                <MenuItem
+                    Icon={MarkAsReadIcon}
+                    label={_t("room_list|more_options|mark_read")}
+                    onSelect={vm.markAsRead}
+                    onClick={(evt) => evt.stopPropagation()}
+                    hideChevron={true}
+                />
+            )}
+            {vm.canMarkAsUnread && (
+                <MenuItem
+                    Icon={MarkAsUnreadIcon}
+                    label={_t("room_list|more_options|mark_unread")}
+                    onSelect={vm.markAsUnread}
+                    onClick={(evt) => evt.stopPropagation()}
+                    hideChevron={true}
+                />
+            )}
+            <ToggleMenuItem
+                checked={vm.isFavourite}
+                Icon={FavouriteIcon}
+                label={_t("room_list|more_options|favourited")}
+                onSelect={vm.toggleFavorite}
+                onClick={(evt) => evt.stopPropagation()}
+            />
+            <MenuItem
+                Icon={ArrowDownIcon}
+                label={_t("room_list|more_options|low_priority")}
+                onSelect={vm.toggleLowPriority}
+                onClick={(evt) => evt.stopPropagation()}
+                hideChevron={true}
+            />
+            {vm.canInvite && (
+                <MenuItem
+                    Icon={UserAddIcon}
+                    label={_t("action|invite")}
+                    onSelect={vm.invite}
+                    onClick={(evt) => evt.stopPropagation()}
+                    hideChevron={true}
+                />
+            )}
+            {vm.canCopyRoomLink && (
+                <MenuItem
+                    Icon={LinkIcon}
+                    label={_t("room_list|more_options|copy_link")}
+                    onSelect={vm.copyRoomLink}
+                    onClick={(evt) => evt.stopPropagation()}
+                    hideChevron={true}
+                />
+            )}
+            <Separator />
+            <MenuItem
+                kind="critical"
+                Icon={LeaveIcon}
+                label={_t("room_list|more_options|leave_room")}
+                onSelect={vm.leaveRoom}
+                onClick={(evt) => evt.stopPropagation()}
+                hideChevron={true}
+            />
+        </Menu>
+    );
+}
+
+interface MoreOptionsButtonProps extends ComponentProps<typeof IconButton> {
+    ref?: Ref<HTMLButtonElement>;
+}
+
+/**
+ * A button to trigger the more options menu.
+ */
+export const MoreOptionsButton = function MoreOptionsButton(props: MoreOptionsButtonProps): JSX.Element {
+    return (
+        <Tooltip label={_t("room_list|room|more_options")}>
+            <IconButton aria-label={_t("room_list|room|more_options")} {...props}>
+                <OverflowIcon />
+            </IconButton>
+        </Tooltip>
+    );
+};
+
+interface NotificationMenuProps {
+    /**
+     * The view model state for the menu.
+     */
+    vm: RoomListItemMenuViewState;
+    /**
+     * Set the menu open state.
+     * @param isOpen
+     */
+    setMenuOpen: (isOpen: boolean) => void;
+}
+
+function NotificationMenu({ vm, setMenuOpen }: NotificationMenuProps): JSX.Element {
+    const [open, setOpen] = useState(false);
+
+    const checkComponent = <CheckIcon width="24px" height="24px" color="var(--cpd-color-icon-primary)" />;
+
+    return (
+        <Menu
+            open={open}
+            onOpenChange={(isOpen) => {
+                setOpen(isOpen);
+                setMenuOpen(isOpen);
+            }}
+            title={_t("room_list|notification_options")}
+            showTitle={false}
+            align="start"
+            trigger={<NotificationButton isRoomMuted={vm.isNotificationMute} size="24px" />}
+        >
+            <MenuItem
+                aria-selected={vm.isNotificationAllMessage}
+                hideChevron={true}
+                label={_t("notifications|default_settings")}
+                onSelect={() => vm.setRoomNotifState(RoomNotifState.AllMessages)}
+                onClick={(evt) => evt.stopPropagation()}
+            >
+                {vm.isNotificationAllMessage && checkComponent}
+            </MenuItem>
+            <MenuItem
+                aria-selected={vm.isNotificationAllMessageLoud}
+                hideChevron={true}
+                label={_t("notifications|all_messages")}
+                onSelect={() => vm.setRoomNotifState(RoomNotifState.AllMessagesLoud)}
+                onClick={(evt) => evt.stopPropagation()}
+            >
+                {vm.isNotificationAllMessageLoud && checkComponent}
+            </MenuItem>
+            <MenuItem
+                aria-selected={vm.isNotificationMentionOnly}
+                hideChevron={true}
+                label={_t("notifications|mentions_keywords")}
+                onSelect={() => vm.setRoomNotifState(RoomNotifState.MentionsOnly)}
+                onClick={(evt) => evt.stopPropagation()}
+            >
+                {vm.isNotificationMentionOnly && checkComponent}
+            </MenuItem>
+            <MenuItem
+                aria-selected={vm.isNotificationMute}
+                hideChevron={true}
+                label={_t("notifications|mute_room")}
+                onSelect={() => vm.setRoomNotifState(RoomNotifState.Mute)}
+                onClick={(evt) => evt.stopPropagation()}
+            >
+                {vm.isNotificationMute && checkComponent}
+            </MenuItem>
+        </Menu>
+    );
+}
+
+interface NotificationButtonProps extends ComponentProps<typeof IconButton> {
+    /**
+     * Whether the room is muted.
+     */
+    isRoomMuted: boolean;
+    ref?: Ref<HTMLButtonElement>;
+}
+
+/**
+ * A button to trigger the notification menu.
+ */
+export const NotificationButton = function MoreOptionsButton({
+    isRoomMuted,
+    ref,
+    ...props
+}: NotificationButtonProps): JSX.Element {
+    return (
+        <Tooltip label={_t("room_list|notification_options")}>
+            <IconButton aria-label={_t("room_list|notification_options")} {...props} ref={ref}>
+                {isRoomMuted ? <NotificationOffIcon /> : <NotificationIcon />}
+            </IconButton>
+        </Tooltip>
+    );
+};
diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ac4d72ec570c3938bab29a476c9a3fc447076e75
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX, memo, useCallback, useRef, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import classNames from "classnames";
+
+import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel";
+import { Flex } from "../../../utils/Flex";
+import { RoomListItemMenuView } from "./RoomListItemMenuView";
+import { NotificationDecoration } from "../NotificationDecoration";
+import { RoomAvatarView } from "../../avatars/RoomAvatarView";
+import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
+
+interface RoomListItemViewProps extends React.HTMLAttributes<HTMLButtonElement> {
+    /**
+     * The room to display
+     */
+    room: Room;
+    /**
+     * Whether the room is selected
+     */
+    isSelected: boolean;
+}
+
+/**
+ * An item in the room list
+ */
+export const RoomListItemView = memo(function RoomListItemView({
+    room,
+    isSelected,
+    ...props
+}: RoomListItemViewProps): JSX.Element {
+    const buttonRef = useRef<HTMLButtonElement>(null);
+    const [onFocus, isActive, ref] = useRovingTabIndex(buttonRef);
+
+    const vm = useRoomListItemViewModel(room);
+
+    const [isHover, setIsHoverWithDelay] = useIsHover();
+    const [isMenuOpen, setIsMenuOpen] = useState(false);
+    // The compound menu in RoomListItemMenuView needs to be rendered when the hover menu is shown
+    // Using display: none; and then display:flex when hovered in CSS causes the menu to be misaligned
+    const showHoverDecoration = isMenuOpen || isHover;
+    const showHoverMenu = showHoverDecoration && vm.showHoverMenu;
+
+    return (
+        <button
+            ref={ref}
+            className={classNames("mx_RoomListItemView", {
+                mx_RoomListItemView_hover: showHoverDecoration,
+                mx_RoomListItemView_menu_open: showHoverMenu,
+                mx_RoomListItemView_selected: isSelected,
+                mx_RoomListItemView_bold: vm.isBold,
+            })}
+            type="button"
+            aria-selected={isSelected}
+            aria-label={vm.a11yLabel}
+            onClick={() => vm.openRoom()}
+            onMouseOver={() => setIsHoverWithDelay(true)}
+            onMouseOut={() => setIsHoverWithDelay(false)}
+            onFocus={() => {
+                setIsHoverWithDelay(true);
+                onFocus();
+            }}
+            // Adding a timeout because when tabbing to go to the more options and notification menu, the focus moves out of the button
+            // The blur makes the button lose the hover state and these menu are not shown
+            // We delay the blur event to give time to the focus to move to the menu
+            onBlur={() => setIsHoverWithDelay(false, 10)}
+            tabIndex={isActive ? 0 : -1}
+            {...props}
+        >
+            {/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
+            <Flex className="mx_RoomListItemView_container" gap="var(--cpd-space-3x)" align="center">
+                <RoomAvatarView room={room} />
+                <Flex
+                    className="mx_RoomListItemView_content"
+                    gap="var(--cpd-space-2x)"
+                    align="center"
+                    justify="space-between"
+                >
+                    {/* We truncate the room name when too long. Title here is to show the full name on hover */}
+                    <div className="mx_RoomListItemView_text">
+                        <div className="mx_RoomListItemView_roomName" title={vm.name}>
+                            {vm.name}
+                        </div>
+                        <div className="mx_RoomListItemView_messagePreview">{vm.messagePreview}</div>
+                    </div>
+                    {showHoverMenu ? (
+                        <RoomListItemMenuView
+                            room={room}
+                            setMenuOpen={(isOpen) => {
+                                if (isOpen) {
+                                    setIsMenuOpen(isOpen);
+                                } else {
+                                    // To avoid icon blinking when closing the menu, we delay the state update
+                                    setTimeout(() => setIsMenuOpen(isOpen), 0);
+                                    // After closing the menu, we need to set the focus back to the button
+                                    // 10ms because the focus moves to the body and we put back the focus on the button
+                                    setTimeout(() => buttonRef.current?.focus(), 10);
+                                }
+                            }}
+                        />
+                    ) : (
+                        <>
+                            {/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */}
+                            {vm.showNotificationDecoration && (
+                                <NotificationDecoration
+                                    notificationState={vm.notificationState}
+                                    aria-hidden={true}
+                                    hasVideoCall={vm.hasParticipantInCall}
+                                />
+                            )}
+                        </>
+                    )}
+                </Flex>
+            </Flex>
+        </button>
+    );
+});
+
+/**
+ * Custom hook to manage the hover state of the room list item
+ * If the timeout is set, it will set the hover state after the timeout
+ * If the timeout is not set, it will set the hover state immediately
+ * When the set method is called, it will clear any existing timeout
+ *
+ * @returns {boolean} isHover - The hover state
+ */
+function useIsHover(): [boolean, (value: boolean, timeout?: number) => void] {
+    const [isHover, setIsHover] = useState(false);
+    // Store the timeout ID
+    const timeoutRef = useRef<number | undefined>(undefined);
+
+    const setIsHoverWithDelay = useCallback((value: boolean, timeout?: number): void => {
+        // Clear the timeout if it exists
+        clearTimeout(timeoutRef.current);
+
+        // No delay, set the value immediately
+        if (timeout === undefined) {
+            setIsHover(value);
+            return;
+        }
+
+        // Set a timeout to set the value after the delay
+        timeoutRef.current = setTimeout(() => setIsHover(value), timeout);
+    }, []);
+
+    return [isHover, setIsHoverWithDelay];
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListOptionsMenu.tsx b/src/components/views/rooms/RoomListPanel/RoomListOptionsMenu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a78c1dd99436ca2f18eaa30920100785a795f588
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListOptionsMenu.tsx
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { IconButton, Menu, MenuTitle, CheckboxMenuItem, Tooltip, RadioMenuItem } from "@vector-im/compound-web";
+import React, { type Ref, type JSX, useState, useCallback } from "react";
+import FilterIcon from "@vector-im/compound-design-tokens/assets/web/icons/filter";
+
+import { _t } from "../../../../languageHandler";
+import { SortOption } from "../../../viewmodels/roomlist/useSorter";
+import { type RoomListHeaderViewState } from "../../../viewmodels/roomlist/RoomListHeaderViewModel";
+
+interface MenuTriggerProps extends React.ComponentProps<typeof IconButton> {
+    ref?: Ref<HTMLButtonElement>;
+}
+
+const MenuTrigger = ({ ref, ...props }: MenuTriggerProps): JSX.Element => (
+    <Tooltip label={_t("room_list|room_options")}>
+        <IconButton aria-label={_t("room_list|room_options")} {...props} ref={ref}>
+            <FilterIcon color="var(--cpd-color-icon-secondary)" />
+        </IconButton>
+    </Tooltip>
+);
+
+interface Props {
+    /**
+     * The view model for the room list view
+     */
+    vm: RoomListHeaderViewState;
+}
+
+export function RoomListOptionsMenu({ vm }: Props): JSX.Element {
+    const [open, setOpen] = useState(false);
+
+    const onActivitySelected = useCallback(() => {
+        vm.sort(SortOption.Activity);
+    }, [vm]);
+
+    const onAtoZSelected = useCallback(() => {
+        vm.sort(SortOption.AToZ);
+    }, [vm]);
+
+    return (
+        <Menu
+            open={open}
+            onOpenChange={setOpen}
+            title={_t("room_list|room_options")}
+            showTitle={false}
+            align="start"
+            trigger={<MenuTrigger />}
+        >
+            <MenuTitle title={_t("room_list|sort")} />
+            <RadioMenuItem
+                label={_t("room_list|sort_type|activity")}
+                checked={vm.activeSortOption === SortOption.Activity}
+                onSelect={onActivitySelected}
+            />
+            <RadioMenuItem
+                label={_t("room_list|sort_type|atoz")}
+                checked={vm.activeSortOption === SortOption.AToZ}
+                onSelect={onAtoZSelected}
+            />
+            <MenuTitle title={_t("room_list|appearance")} />
+            <CheckboxMenuItem
+                label={_t("room_list|show_message_previews")}
+                onSelect={vm.toggleMessagePreview}
+                checked={vm.shouldShowMessagePreview}
+            />
+        </Menu>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx b/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..291794399fb093ce4858f935338cf1413662927e
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx
@@ -0,0 +1,44 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+
+import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
+import { UIComponent } from "../../../../settings/UIFeature";
+import { RoomListSearch } from "./RoomListSearch";
+import { RoomListHeaderView } from "./RoomListHeaderView";
+import { RoomListView } from "./RoomListView";
+import { Flex } from "../../../utils/Flex";
+
+type RoomListPanelProps = {
+    /**
+     * Current active space
+     * See {@link RoomListSearch}
+     */
+    activeSpace: string;
+};
+
+/**
+ * The panel of the room list
+ */
+export const RoomListPanel: React.FC<RoomListPanelProps> = ({ activeSpace }) => {
+    const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer);
+
+    return (
+        <Flex
+            as="section"
+            className="mx_RoomListPanel"
+            data-testid="room-list-panel"
+            direction="column"
+            align="stretch"
+        >
+            {displayRoomSearch && <RoomListSearch activeSpace={activeSpace} />}
+            <RoomListHeaderView />
+            <RoomListView />
+        </Flex>
+    );
+};
diff --git a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ebf972d36137f55f7c7c66a4267b5f13671c0f82
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import { ChatFilter } from "@vector-im/compound-web";
+
+import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
+import { Flex } from "../../../utils/Flex";
+import { _t } from "../../../../languageHandler";
+
+interface RoomListPrimaryFiltersProps {
+    /**
+     * The view model for the room list
+     */
+    vm: RoomListViewState;
+}
+
+/**
+ * The primary filters for the room list
+ */
+export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element {
+    return (
+        <Flex
+            as="ul"
+            role="listbox"
+            aria-label={_t("room_list|primary_filters")}
+            className="mx_RoomListPrimaryFilters"
+            align="center"
+            gap="var(--cpd-space-2x)"
+            wrap="wrap"
+        >
+            {vm.primaryFilters.map((filter) => (
+                <li role="option" aria-selected={filter.active} key={filter.name}>
+                    <ChatFilter selected={filter.active} onClick={filter.toggle}>
+                        {filter.name}
+                    </ChatFilter>
+                </li>
+            ))}
+        </Flex>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx b/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..26f0ff8baedec98ec7ebb7bdb3591bf2def4179f
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import { Button } from "@vector-im/compound-web";
+import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
+import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
+import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
+
+import { IS_MAC, Key } from "../../../../Keyboard";
+import { _t } from "../../../../languageHandler";
+import { ALTERNATE_KEY_NAME } from "../../../../accessibility/KeyboardShortcuts";
+import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
+import { UIComponent } from "../../../../settings/UIFeature";
+import { MetaSpace } from "../../../../stores/spaces";
+import { Action } from "../../../../dispatcher/actions";
+import PosthogTrackers from "../../../../PosthogTrackers";
+import defaultDispatcher from "../../../../dispatcher/dispatcher";
+import { Flex } from "../../../utils/Flex";
+import { useTypedEventEmitterState } from "../../../../hooks/useEventEmitter";
+import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../../LegacyCallHandler";
+
+type RoomListSearchProps = {
+    /**
+     * Current active space
+     * The explore button is only displayed in the Home meta space
+     */
+    activeSpace: string;
+};
+
+/**
+ * A search component to be displayed at the top of the room list
+ * The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
+ */
+export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element {
+    const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms);
+    // We only display the dial button if the user is can make PSTN calls
+    const displayDialButton = useTypedEventEmitterState(
+        LegacyCallHandler.instance,
+        LegacyCallHandlerEvent.ProtocolSupport,
+        () => LegacyCallHandler.instance.getSupportsPstnProtocol(),
+    );
+
+    return (
+        <Flex className="mx_RoomListSearch" role="search" gap="var(--cpd-space-2x)" align="center">
+            <Button
+                className="mx_RoomListSearch_search"
+                kind="secondary"
+                size="sm"
+                Icon={SearchIcon}
+                onClick={() => defaultDispatcher.fire(Action.OpenSpotlight)}
+            >
+                <Flex as="span" justify="space-between">
+                    <span className="mx_RoomListSearch_search_text">{_t("action|search")}</span>
+                    <kbd>{IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K"}</kbd>
+                </Flex>
+            </Button>
+            {displayDialButton && (
+                <Button
+                    className="mx_RoomListSearch_button"
+                    kind="secondary"
+                    size="sm"
+                    Icon={DialPadIcon}
+                    iconOnly={true}
+                    aria-label={_t("left_panel|open_dial_pad")}
+                    onClick={(ev) => {
+                        defaultDispatcher.fire(Action.OpenDialPad);
+                    }}
+                />
+            )}
+            {displayExploreButton && (
+                <Button
+                    className="mx_RoomListSearch_button"
+                    kind="secondary"
+                    size="sm"
+                    Icon={ExploreIcon}
+                    iconOnly={true}
+                    aria-label={_t("action|explore_rooms")}
+                    onClick={(ev) => {
+                        defaultDispatcher.fire(Action.ViewRoomDirectory);
+                        PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
+                    }}
+                />
+            )}
+        </Flex>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/RoomListView.tsx b/src/components/views/rooms/RoomListPanel/RoomListView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f6d99625aa0c57594543b2ea95fca38f24d15ab1
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/RoomListView.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+
+import { useRoomListViewModel } from "../../../viewmodels/roomlist/RoomListViewModel";
+import { RoomList } from "./RoomList";
+import { EmptyRoomList } from "./EmptyRoomList";
+import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters";
+
+/**
+ * Host the room list and the (future) room filters
+ */
+export function RoomListView(): JSX.Element {
+    const vm = useRoomListViewModel();
+    const isRoomListEmpty = vm.rooms.length === 0;
+    let listBody;
+    if (vm.isLoadingRooms) {
+        listBody = <div className="mx_RoomListSkeleton" />;
+    } else if (isRoomListEmpty) {
+        listBody = <EmptyRoomList vm={vm} />;
+    } else {
+        listBody = <RoomList vm={vm} />;
+    }
+    return (
+        <>
+            <RoomListPrimaryFilters vm={vm} />
+            {listBody}
+        </>
+    );
+}
diff --git a/src/components/views/rooms/RoomListPanel/index.ts b/src/components/views/rooms/RoomListPanel/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4356a51e2628cf4ccd049d8d21a503d144acd9f7
--- /dev/null
+++ b/src/components/views/rooms/RoomListPanel/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+export { RoomListPanel } from "./RoomListPanel";
diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx
index 7b1791d0cdd434a67d14a14acbc9c3f09a4eee50..6939fec099a77747686c1928e135a15a8837e60d 100644
--- a/src/components/views/rooms/RoomPreviewBar.tsx
+++ b/src/components/views/rooms/RoomPreviewBar.tsx
@@ -6,11 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ReactNode } from "react";
-import { Room, RoomMember, EventType, RoomType, JoinRule, MatrixError } from "matrix-js-sdk/src/matrix";
-import { KnownMembership, RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
+import React, { type JSX, type ChangeEvent, type ReactNode } from "react";
+import { type Room, type RoomMember, EventType, RoomType, JoinRule, type MatrixError } from "matrix-js-sdk/src/matrix";
+import { KnownMembership, type RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
 import classNames from "classnames";
-import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import {
+    type RoomPreviewOpts,
+    RoomViewLifecycle,
+} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import { Button } from "@vector-im/compound-web";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import dis from "../../../dispatcher/dispatcher";
@@ -18,7 +22,7 @@ import { _t, UserFriendlyError } from "../../../languageHandler";
 import SdkConfig from "../../../SdkConfig";
 import IdentityAuthClient from "../../../IdentityAuthClient";
 import InviteReason from "../elements/InviteReason";
-import { IOOBData } from "../../../stores/ThreepidInviteStore";
+import { type IOOBData } from "../../../stores/ThreepidInviteStore";
 import Spinner from "../elements/Spinner";
 import AccessibleButton from "../elements/AccessibleButton";
 import RoomAvatar from "../avatars/RoomAvatar";
@@ -87,12 +91,18 @@ interface IProps {
     roomAlias?: string;
 
     onJoinClick?(): void;
-    onRejectClick?(): void;
-    onRejectAndIgnoreClick?(): void;
+    onDeclineClick?(): void;
+    onDeclineAndBlockClick?(): void;
     onForgetClick?(): void;
 
     canAskToJoinAndMembershipIsLeave?: boolean;
     promptAskToJoin?: boolean;
+
+    /**
+     * If true, this will prompt for additional safety options
+     * like reporting an invite or ignoring the user.
+     */
+    promptRejectionOptions?: boolean;
     knocked?: boolean;
     onSubmitAskToJoin?(reason?: string): void;
     onCancelAskToJoin?(): void;
@@ -310,6 +320,8 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
         let primaryActionLabel: string | undefined;
         let secondaryActionHandler: (() => void) | undefined;
         let secondaryActionLabel: string | undefined;
+        let dangerActionHandler: (() => void) | undefined;
+        let dangerActionLabel: string | undefined;
         let footer: JSX.Element | undefined;
         const extraComponents: JSX.Element[] = [];
 
@@ -546,16 +558,11 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
                 }
 
                 primaryActionHandler = this.props.onJoinClick;
-                secondaryActionLabel = _t("action|reject");
-                secondaryActionHandler = this.props.onRejectClick;
-
-                if (this.props.onRejectAndIgnoreClick) {
-                    extraComponents.push(
-                        <AccessibleButton kind="secondary" onClick={this.props.onRejectAndIgnoreClick} key="ignore">
-                            {_t("room|invite_reject_ignore")}
-                        </AccessibleButton>,
-                    );
-                }
+                secondaryActionLabel = _t("action|decline");
+                secondaryActionHandler = this.props.onDeclineClick;
+                dangerActionLabel = _t("action|decline_and_block");
+                dangerActionHandler = this.props.onDeclineAndBlockClick;
+
                 break;
             }
             case MessageCase.ViewingRoom: {
@@ -688,6 +695,15 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
             );
         }
 
+        let dangerActionButton;
+        if (dangerActionHandler) {
+            dangerActionButton = (
+                <Button destructive kind="tertiary" onClick={dangerActionHandler}>
+                    {dangerActionLabel}
+                </Button>
+            );
+        }
+
         const isPanel = this.props.canPreview;
 
         const classes = classNames("mx_RoomPreviewBar", `mx_RoomPreviewBar_${messageCase}`, {
@@ -698,6 +714,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
         // ensure correct tab order for both views
         const actions = isPanel ? (
             <>
+                {dangerActionButton}
                 {secondaryButton}
                 {extraComponents}
                 {primaryButton}
@@ -707,6 +724,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
                 {primaryButton}
                 {extraComponents}
                 {secondaryButton}
+                {dangerActionButton}
             </>
         );
 
diff --git a/src/components/views/rooms/RoomPreviewCard.tsx b/src/components/views/rooms/RoomPreviewCard.tsx
index bce6fc22b30a07068dae810818d9161d813251fc..21f6e09a6de859646980d3f1b477dab863746184 100644
--- a/src/components/views/rooms/RoomPreviewCard.tsx
+++ b/src/components/views/rooms/RoomPreviewCard.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, useContext, useState } from "react";
-import { Room, JoinRule } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type FC, useContext, useState } from "react";
+import { type Room, JoinRule } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { _t } from "../../../languageHandler";
@@ -112,7 +112,7 @@ const RoomPreviewCard: FC<IProps> = ({ room, onJoinButtonClicked, onRejectButton
                         onRejectButtonClicked();
                     }}
                 >
-                    {_t("action|reject")}
+                    {_t("action|decline")}
                 </AccessibleButton>
                 <AccessibleButton
                     kind="primary"
diff --git a/src/components/views/rooms/RoomSearchAuxPanel.tsx b/src/components/views/rooms/RoomSearchAuxPanel.tsx
index 1c3ec238b2569a32707b89df60005bb46f615d70..be821c7de1f30956b52a87f89ea1bc9293693b5f 100644
--- a/src/components/views/rooms/RoomSearchAuxPanel.tsx
+++ b/src/components/views/rooms/RoomSearchAuxPanel.tsx
@@ -14,7 +14,7 @@ import { IconButton, Link } from "@vector-im/compound-web";
 import { _t } from "../../../languageHandler";
 import { PosthogScreenTracker } from "../../../PosthogTrackers";
 import SearchWarning, { WarningKind } from "../elements/SearchWarning";
-import { SearchInfo, SearchScope } from "../../../Searching";
+import { type SearchInfo, SearchScope } from "../../../Searching";
 import InlineSpinner from "../elements/InlineSpinner";
 
 interface Props {
@@ -40,6 +40,8 @@ const RoomSearchAuxPanel: React.FC<Props> = ({ searchInfo, isRoomEncrypted, onSe
                                 { count: searchInfo.count },
                                 { query: () => <strong>{searchInfo.term}</strong> },
                             )
+                        ) : searchInfo?.error !== undefined ? (
+                            searchInfo?.error.message
                         ) : (
                             <InlineSpinner />
                         )}
diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx
index dac72994b0b74fd713be75c1e31f979d2245e299..e5fd061b190005075ed73406dcab3cf26ff6ab28 100644
--- a/src/components/views/rooms/RoomSublist.tsx
+++ b/src/components/views/rooms/RoomSublist.tsx
@@ -8,42 +8,39 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import classNames from "classnames";
-import { Enable, Resizable } from "re-resizable";
-import { Direction } from "re-resizable/lib/resizer";
-import * as React from "react";
-import { ComponentType, createRef, ReactComponentElement, ReactNode } from "react";
+import { type Enable, Resizable } from "re-resizable";
+import { type Direction } from "re-resizable/lib/resizer";
+import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type ReactNode } from "react";
 
 import { polyfillTouchEvent } from "../../../@types/polyfill";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
 import { Action } from "../../../dispatcher/actions";
-import defaultDispatcher, { MatrixDispatcher } from "../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../dispatcher/payloads";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import defaultDispatcher, { type MatrixDispatcher } from "../../../dispatcher/dispatcher";
+import { type ActionPayload } from "../../../dispatcher/payloads";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { _t } from "../../../languageHandler";
-import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
+import { type ListNotificationState } from "../../../stores/notifications/ListNotificationState";
 import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
 import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models";
-import { ListLayout } from "../../../stores/room-list/ListLayout";
-import { DefaultTagID, TagID } from "../../../stores/room-list/models";
+import { type ListLayout } from "../../../stores/room-list/ListLayout";
+import { DefaultTagID, type TagID } from "../../../stores/room-list/models";
 import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
 import RoomListStore, { LISTS_UPDATE_EVENT, LISTS_LOADING_EVENT } from "../../../stores/room-list/RoomListStore";
 import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
 import { objectExcluding, objectHasDiff } from "../../../utils/objects";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 import ContextMenu, {
     ChevronFace,
     ContextMenuTooltipButton,
     StyledMenuItemCheckbox,
     StyledMenuItemRadio,
 } from "../../structures/ContextMenu";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
-import ExtraTile from "./ExtraTile";
-import SettingsStore from "../../../settings/SettingsStore";
-import { SlidingSyncManager } from "../../../SlidingSyncManager";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
+import type ExtraTile from "./ExtraTile";
 import NotificationBadge from "./NotificationBadge";
 import RoomTile from "./RoomTile";
 
@@ -106,12 +103,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
     private heightAtStart: number;
     private notificationState: ListNotificationState;
 
-    private slidingSyncMode: boolean;
-
     public constructor(props: IProps) {
         super(props);
-        // when this setting is toggled it restarts the app so it's safe to not watch this.
-        this.slidingSyncMode = SettingsStore.getValue("feature_sliding_sync");
 
         this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId);
         this.heightAtStart = 0;
@@ -165,9 +158,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
     }
 
     private get numVisibleTiles(): number {
-        if (this.slidingSyncMode) {
-            return this.state.rooms.length;
-        }
         const nVisible = Math.ceil(this.layout.visibleTiles);
         return Math.min(nVisible, this.numTiles);
     }
@@ -329,12 +319,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
     };
 
     private onShowAllClick = async (): Promise<void> => {
-        if (this.slidingSyncMode) {
-            const count = RoomListStore.instance.getCount(this.props.tagId);
-            await SlidingSyncManager.instance.ensureListRegistered(this.props.tagId, {
-                ranges: [[0, count]],
-            });
-        }
         // read number of visible tiles before we mutate it
         const numVisibleTiles = this.numVisibleTiles;
         const newHeight = this.layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
@@ -554,13 +538,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
 
         let contextMenu: JSX.Element | undefined;
         if (this.state.contextMenuPosition) {
-            let isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
-            let isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
-            if (this.slidingSyncMode) {
-                const slidingList = SlidingSyncManager.instance.slidingSync?.getListParams(this.props.tagId);
-                isAlphabetical = (slidingList?.sort || [])[0] === "by_name";
-                isUnreadFirst = (slidingList?.sort || [])[0] === "by_notification_level";
-            }
+            const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
+            const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
 
             // Invites don't get some nonsense options, so only add them if we have to.
             let otherSections: JSX.Element | undefined;
@@ -763,17 +742,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
             // floats above the resize handle, if we have one present. If the user has all
             // tiles visible, it becomes 'show less'.
             let showNButton: JSX.Element | undefined;
-            const hasMoreSlidingSync =
-                this.slidingSyncMode && RoomListStore.instance.getCount(this.props.tagId) > this.state.rooms.length;
 
-            if (maxTilesPx > this.state.height || hasMoreSlidingSync) {
+            if (maxTilesPx > this.state.height) {
                 // the height of all the tiles is greater than the section height: we need a 'show more' button
                 const nonPaddedHeight = this.state.height - RESIZE_HANDLE_HEIGHT - SHOW_N_BUTTON_HEIGHT;
                 const amountFullyShown = Math.floor(nonPaddedHeight / this.layout.tileHeight);
-                let numMissing = this.numTiles - amountFullyShown;
-                if (this.slidingSyncMode) {
-                    numMissing = RoomListStore.instance.getCount(this.props.tagId) - amountFullyShown;
-                }
+                const numMissing = this.numTiles - amountFullyShown;
                 const label = _t("room_list|show_n_more", { count: numMissing });
                 let showMoreText: ReactNode = <span className="mx_RoomSublist_showNButtonText">{label}</span>;
                 if (this.props.isMinimized) showMoreText = null;
diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx
index cbeb56207778d61060eeb1aed9fc874d5d95a098..3eafb671cba59dd9c84ce057a9c33225b9ff6ff7 100644
--- a/src/components/views/rooms/RoomTile.tsx
+++ b/src/components/views/rooms/RoomTile.tsx
@@ -8,32 +8,32 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { createRef } from "react";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import classNames from "classnames";
 
 import type { Call } from "../../../models/Call";
 import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
-import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../views/elements/AccessibleButton";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { _t } from "../../../languageHandler";
-import { ChevronFace, ContextMenuTooltipButton, MenuProps } from "../../structures/ContextMenu";
-import { DefaultTagID, TagID } from "../../../stores/room-list/models";
-import { MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
+import { ChevronFace, ContextMenuTooltipButton, type MenuProps } from "../../structures/ContextMenu";
+import { DefaultTagID, type TagID } from "../../../stores/room-list/models";
+import { type MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
 import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
 import { RoomNotifState } from "../../../RoomNotifs";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { RoomNotificationContextMenu } from "../context_menus/RoomNotificationContextMenu";
 import NotificationBadge from "./NotificationBadge";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
-import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
+import { type NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
 import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
-import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
+import { CachedRoomKey, type RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
 import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber";
 import PosthogTrackers from "../../../PosthogTrackers";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
diff --git a/src/components/views/rooms/RoomTileCallSummary.tsx b/src/components/views/rooms/RoomTileCallSummary.tsx
index 8c1db5f34baa774f04fdbbe92f42ed3d56c10502..eed7d012e215cd072459ae873f0e5c73c6b9bccd 100644
--- a/src/components/views/rooms/RoomTileCallSummary.tsx
+++ b/src/components/views/rooms/RoomTileCallSummary.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC } from "react";
+import React, { type FC } from "react";
 
 import type { Call } from "../../../models/Call";
 import { _t } from "../../../languageHandler";
@@ -27,18 +27,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
             text = _t("common|video");
             active = false;
             break;
-        case ConnectionState.WidgetLoading:
-            text = _t("common|loading");
-            active = false;
-            break;
-        case ConnectionState.Lobby:
-            text = _t("common|lobby");
-            active = false;
-            break;
-        case ConnectionState.Connecting:
-            text = _t("room|joining");
-            active = true;
-            break;
         case ConnectionState.Connected:
         case ConnectionState.Disconnecting:
             text = _t("common|joined");
diff --git a/src/components/views/rooms/RoomTileSubtitle.tsx b/src/components/views/rooms/RoomTileSubtitle.tsx
index 883dc5db0d2fd3e650f861ac7376c204548f211b..715a09c9c4a7ac7f23e3f3304b2ad199a611505b 100644
--- a/src/components/views/rooms/RoomTileSubtitle.tsx
+++ b/src/components/views/rooms/RoomTileSubtitle.tsx
@@ -10,8 +10,8 @@ import React from "react";
 import classNames from "classnames";
 import { ThreadsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import { MessagePreview } from "../../../stores/room-list/MessagePreviewStore";
-import { Call } from "../../../models/Call";
+import { type MessagePreview } from "../../../stores/room-list/MessagePreviewStore";
+import { type Call } from "../../../models/Call";
 import { RoomTileCallSummary } from "./RoomTileCallSummary";
 
 interface Props {
diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
index 3b5685c2299bfe17a77a0e0ebfd4ec3ca9843d1a..2de9eaf7500f6af2204152ac942143128299fe09 100644
--- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 
 import Modal from "../../../Modal";
 import { _t } from "../../../languageHandler";
@@ -27,8 +27,8 @@ export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, I
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
         this.state = {
diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx
index 825a5d235f9ff47bebdaa5ae406ed9776fc373ab..e5a77df60e4b425f0a61bdfb751098fd27f507cb 100644
--- a/src/components/views/rooms/SearchResultTile.tsx
+++ b/src/components/views/rooms/SearchResultTile.tsx
@@ -8,16 +8,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import SettingsStore from "../../../settings/SettingsStore";
-import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
 import DateSeparator from "../messages/DateSeparator";
 import EventTile from "./EventTile";
 import { shouldFormContinuation } from "../../structures/MessagePanel";
 import { wantsDateSeparator } from "../../../DateUtils";
-import LegacyCallEventGrouper, { buildLegacyCallEventGroupers } from "../../structures/LegacyCallEventGrouper";
+import type LegacyCallEventGrouper from "../../structures/LegacyCallEventGrouper";
+import { buildLegacyCallEventGroupers } from "../../structures/LegacyCallEventGrouper";
 import { haveRendererForEvent } from "../../../events/EventTileFactory";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 
@@ -30,7 +31,6 @@ interface IProps {
     timeline: MatrixEvent[];
     // indexes of the matching events (not contextual ones)
     ourEventsIndexes: number[];
-    onHeightChanged?: () => void;
     permalinkCreator?: RoomPermalinkCreator;
 }
 
@@ -41,8 +41,8 @@ export default class SearchResultTile extends React.Component<IProps> {
     // A map of <callId, LegacyCallEventGrouper>
     private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.buildLegacyCallEventGroupers(this.props.timeline);
     }
@@ -114,7 +114,6 @@ export default class SearchResultTile extends React.Component<IProps> {
                         highlights={highlights}
                         permalinkCreator={this.props.permalinkCreator}
                         highlightLink={this.props.resultLink}
-                        onHeightChanged={this.props.onHeightChanged}
                         isTwelveHour={isTwelveHour}
                         alwaysShowTimestamps={alwaysShowTimestamps}
                         lastInSection={lastInSection}
diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx
index d12a84e0c279787a5cf0a1b263392ac232147391..aae9cfd0d03562485fdd06067bc318cb2965d266 100644
--- a/src/components/views/rooms/SendMessageComposer.tsx
+++ b/src/components/views/rooms/SendMessageComposer.tsx
@@ -6,22 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, KeyboardEvent, SyntheticEvent } from "react";
+import React, { createRef, type KeyboardEvent, type SyntheticEvent } from "react";
 import {
-    IContent,
-    MatrixEvent,
-    IEventRelation,
-    IMentions,
-    Room,
+    type IContent,
+    type MatrixEvent,
+    type IEventRelation,
+    type IMentions,
+    type Room,
     EventType,
     MsgType,
     RelationType,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
-import { DebouncedFunc, throttle } from "lodash";
+import { type DebouncedFunc, throttle } from "lodash";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
-import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
+import { type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
 import dis from "../../../dispatcher/dispatcher";
 import EditorModel from "../../../editor/model";
@@ -35,19 +35,19 @@ import {
     unescapeMessage,
 } from "../../../editor/serialize";
 import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
-import { CommandPartCreator, Part, PartCreator, SerializedPart, Type } from "../../../editor/parts";
+import { CommandPartCreator, type Part, type PartCreator, type SerializedPart, Type } from "../../../editor/parts";
 import { findEditableEvent } from "../../../utils/EventUtils";
 import SendHistoryManager from "../../../SendHistoryManager";
 import { CommandCategories } from "../../../SlashCommands";
 import ContentMessages from "../../../ContentMessages";
-import { withMatrixClientHOC, MatrixClientProps } from "../../../contexts/MatrixClientContext";
+import { withMatrixClientHOC, type MatrixClientProps } from "../../../contexts/MatrixClientContext";
 import { Action } from "../../../dispatcher/actions";
 import { containsEmoji } from "../../../effects/utils";
 import { CHAT_EFFECTS } from "../../../effects";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import SettingsStore from "../../../settings/SettingsStore";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { decorateStartSendingTime, sendRoundTripMetric } from "../../../sendTimePerformanceMetrics";
 import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
 import DocumentPosition from "../../../editor/position";
@@ -57,8 +57,8 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { PosthogAnalytics } from "../../../PosthogAnalytics";
 import { addReplyToMessageContent } from "../../../utils/Reply";
 import { doMaybeLocalRoomAction } from "../../../utils/local-room";
-import { Caret } from "../../../editor/caret";
-import { IDiff } from "../../../editor/diff";
+import { type Caret } from "../../../editor/caret";
+import { type IDiff } from "../../../editor/diff";
 import { getBlobSafeMimeType } from "../../../utils/blobs";
 import { EMOJI_REGEX } from "../../../HtmlUtils";
 
@@ -95,16 +95,9 @@ export function attachMentions(
     const userMentions = new Set<string>();
     let roomMention = false;
 
-    // If there's a reply, initialize the mentioned users as the sender of that
-    // event + any mentioned users in that event.
+    // If there's a reply, initialize the mentioned users as the sender of that event.
     if (replyToEvent) {
         userMentions.add(replyToEvent.sender!.userId);
-        // TODO What do we do if the reply event *doeesn't* have this property?
-        // Try to fish out replies from the contents?
-        const userIds = replyToEvent.getContent()["m.mentions"]?.user_ids;
-        if (Array.isArray(userIds)) {
-            userIds.forEach((userId) => userMentions.add(userId));
-        }
     }
 
     // If user provided content is available, check to see if any users are mentioned.
diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx
index 448e05ac55114540b933d7d1a504cbd8208b2b09..6281f287baaa9bfd828ac28c03327831736a2b14 100644
--- a/src/components/views/rooms/Stickerpicker.tsx
+++ b/src/components/views/rooms/Stickerpicker.tsx
@@ -5,24 +5,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { Room, ClientEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type Room, ClientEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { IWidget } from "matrix-widget-api";
+import { type IWidget } from "matrix-widget-api";
 
-import { _t, _td, TranslationKey } from "../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../languageHandler";
 import AppTile from "../elements/AppTile";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import dis from "../../../dispatcher/dispatcher";
 import AccessibleButton from "../elements/AccessibleButton";
-import WidgetUtils, { UserWidget } from "../../../utils/WidgetUtils";
+import WidgetUtils, { type UserWidget } from "../../../utils/WidgetUtils";
 import PersistedElement from "../elements/PersistedElement";
 import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
 import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
 import { WidgetType } from "../../../widgets/WidgetType";
 import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
-import { ActionPayload } from "../../../dispatcher/payloads";
-import ScalarAuthClient from "../../../ScalarAuthClient";
+import { type ActionPayload } from "../../../dispatcher/payloads";
+import type ScalarAuthClient from "../../../ScalarAuthClient";
 import GenericElementContextMenu from "../context_menus/GenericElementContextMenu";
 import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
 import { UPDATE_EVENT } from "../../../stores/AsyncStore";
diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx
index cee38fc078a2f6f548c21e471b80ac2acba1c1df..da626992a1881e1330624fc239594c9eee4d6bdf 100644
--- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx
+++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { EventType, MatrixEvent, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { EventType, type MatrixEvent, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { Button, Text } from "@vector-im/compound-web";
 
diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx
index 1ec6a56e78f9ae4c8895035bea7626b161517881..f56ffc43cb3b04ea6854e13d7a18ce06dc027411 100644
--- a/src/components/views/rooms/ThreadSummary.tsx
+++ b/src/components/views/rooms/ThreadSummary.tsx
@@ -7,18 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext } from "react";
-import { Thread, ThreadEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Thread, ThreadEvent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { IndicatorIcon } from "@vector-im/compound-web";
 import ThreadIconSolid from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
 
 import { _t } from "../../../languageHandler";
 import { CardContext } from "../right_panel/context";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import PosthogTrackers from "../../../PosthogTrackers";
 import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
 import MemberAvatar from "../avatars/MemberAvatar";
 import { Action } from "../../../dispatcher/actions";
-import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
+import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
 import { notificationLevelToIndicator } from "../../../utils/notifications";
diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx
index f9053cc31a236d3f3b068937e1d3c6e920408143..71960c9e2538c4fe03c078f1c3ab5631825c6cbd 100644
--- a/src/components/views/rooms/TopUnreadMessagesBar.tsx
+++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import { _t } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 
 interface IProps {
     onScrollUpClick: (e: ButtonEvent) => void;
diff --git a/src/components/views/rooms/UserIdentityWarning.tsx b/src/components/views/rooms/UserIdentityWarning.tsx
index 06586c26381e03eac8f39ce9c8855e0d72e10a0e..12d1ee924fc553c94f9a4ecbd7678cedabd0f987 100644
--- a/src/components/views/rooms/UserIdentityWarning.tsx
+++ b/src/components/views/rooms/UserIdentityWarning.tsx
@@ -5,16 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback, useEffect, useRef, useState } from "react";
-import { EventType, KnownMembership, MatrixEvent, Room, RoomStateEvent, RoomMember } from "matrix-js-sdk/src/matrix";
-import { CryptoApi, CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
-import { logger } from "matrix-js-sdk/src/logger";
+import React from "react";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { Button, Separator } from "@vector-im/compound-web";
+import classNames from "classnames";
 
 import { _t } from "../../../languageHandler";
 import MemberAvatar from "../avatars/MemberAvatar";
-import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
-import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
+import {
+    useUserIdentityWarningViewModel,
+    type ViolationPrompt,
+} from "../../viewmodels/rooms/UserIdentityWarningViewModel.tsx";
+import { type ButtonEvent } from "../elements/AccessibleButton.tsx";
 
 interface UserIdentityWarningProps {
     /**
@@ -28,308 +30,108 @@ interface UserIdentityWarningProps {
     key: string;
 }
 
-/**
- * Does the given user's identity need to be approved?
- */
-async function userNeedsApproval(crypto: CryptoApi, userId: string): Promise<boolean> {
-    const verificationStatus = await crypto.getUserVerificationStatus(userId);
-    return verificationStatus.needsUserApproval;
-}
-
-/**
- * Whether the component is uninitialised, is in the process of initialising, or
- * has completed initialising.
- */
-enum InitialisationStatus {
-    Uninitialised,
-    Initialising,
-    Completed,
-}
-
 /**
  * Displays a banner warning when there is an issue with a user's identity.
  *
- * Warns when an unverified user's identity has changed, and gives the user a
+ * Warns when an unverified user's identity was reset, and gives the user a
  * button to acknowledge the change.
  */
 export const UserIdentityWarning: React.FC<UserIdentityWarningProps> = ({ room }) => {
-    const cli = useMatrixClientContext();
-    const crypto = cli.getCrypto();
-
-    // The current room member that we are prompting the user to approve.
-    // `undefined` means we are not currently showing a prompt.
-    const [currentPrompt, setCurrentPrompt] = useState<RoomMember | undefined>(undefined);
-
-    // Whether or not we've already initialised the component by loading the
-    // room membership.
-    const initialisedRef = useRef<InitialisationStatus>(InitialisationStatus.Uninitialised);
-    // Which room members need their identity approved.
-    const membersNeedingApprovalRef = useRef<Map<string, RoomMember>>(new Map());
-    // For each user, we assign a sequence number to each verification status
-    // that we get, or fetch.
-    //
-    // Since fetching a verification status is asynchronous, we could get an
-    // update in the middle of fetching the verification status, which could
-    // mean that the status that we fetched is out of date.  So if the current
-    // sequence number is not the same as the sequence number when we started
-    // the fetch, then we drop our fetched result, under the assumption that the
-    // update that we received is the most up-to-date version.  If it is in fact
-    // not the most up-to-date version, then we should be receiving a new update
-    // soon with the newer value, so it will fix itself in the end.
-    //
-    // We also assign a sequence number when the user leaves the room, in order
-    // to prevent prompting about a user who leaves while we are fetching their
-    // verification status.
-    const verificationStatusSequencesRef = useRef<Map<string, number>>(new Map());
-    const incrementVerificationStatusSequence = (userId: string): number => {
-        const verificationStatusSequences = verificationStatusSequencesRef.current;
-        const value = verificationStatusSequences.get(userId);
-        const newValue = value === undefined ? 1 : value + 1;
-        verificationStatusSequences.set(userId, newValue);
-        return newValue;
-    };
-
-    // Update the current prompt.  Select a new user if needed, or hide the
-    // warning if we don't have anyone to warn about.
-    const updateCurrentPrompt = useCallback((): undefined => {
-        const membersNeedingApproval = membersNeedingApprovalRef.current;
-        // We have to do this in a callback to `setCurrentPrompt`
-        // because this function could have been called after an
-        // `await`, and the `currentPrompt` that this function would
-        // have may be outdated.
-        setCurrentPrompt((currentPrompt) => {
-            // If we're already displaying a warning, and that user still needs
-            // approval, continue showing that user.
-            if (currentPrompt && membersNeedingApproval.has(currentPrompt.userId)) return currentPrompt;
+    const { currentPrompt, dispatchAction } = useUserIdentityWarningViewModel(room, room.roomId);
 
-            if (membersNeedingApproval.size === 0) {
-                if (currentPrompt) {
-                    // If we were previously showing a warning, log that we've stopped doing so.
-                    logger.debug("UserIdentityWarning: no users left that need approval");
-                }
-                return undefined;
-            }
+    if (!currentPrompt) return null;
 
-            // We pick the user with the smallest user ID.
-            const keys = Array.from(membersNeedingApproval.keys()).sort((a, b) => a.localeCompare(b));
-            const selection = membersNeedingApproval.get(keys[0]!);
-            logger.debug(`UserIdentityWarning: now warning about user ${selection?.userId}`);
-            return selection;
-        });
-    }, []);
+    const [title, action] = getTitleAndAction(currentPrompt);
 
-    // Add a user to the membersNeedingApproval map, and update the current
-    // prompt if necessary. The user will only be added if they are actually a
-    // member of the room. If they are not a member, this function will do
-    // nothing.
-    const addMemberNeedingApproval = useCallback(
-        (userId: string, member?: RoomMember): void => {
-            if (userId === cli.getUserId()) {
-                // We always skip our own user, because we can't pin our own identity.
-                return;
-            }
-            member = member ?? room.getMember(userId) ?? undefined;
-            if (!member) return;
-
-            membersNeedingApprovalRef.current.set(userId, member);
-            // We only select the prompt if we are done initialising,
-            // because we will select the prompt after we're done
-            // initialising, and we want to start by displaying a warning
-            // for the user with the smallest ID.
-            if (initialisedRef.current === InitialisationStatus.Completed) {
-                logger.debug(
-                    `UserIdentityWarning: user ${userId} now needs approval; approval-pending list now [${Array.from(membersNeedingApprovalRef.current.keys())}]`,
-                );
-                updateCurrentPrompt();
-            }
-        },
-        [cli, room, updateCurrentPrompt],
-    );
-
-    // For each user in the list check if their identity needs approval, and if
-    // so, add them to the membersNeedingApproval map and update the prompt if
-    // needed.
-    const addMembersWhoNeedApproval = useCallback(
-        async (members: RoomMember[]): Promise<void> => {
-            const verificationStatusSequences = verificationStatusSequencesRef.current;
-
-            const promises: Promise<void>[] = [];
-
-            for (const member of members) {
-                const userId = member.userId;
-                const sequenceNum = incrementVerificationStatusSequence(userId);
-                promises.push(
-                    userNeedsApproval(crypto!, userId).then((needsApproval) => {
-                        if (needsApproval) {
-                            // Only actually update the list if we have the most
-                            // recent value.
-                            if (verificationStatusSequences.get(userId) === sequenceNum) {
-                                addMemberNeedingApproval(userId, member);
-                            }
-                        }
-                    }),
-                );
-            }
-
-            await Promise.all(promises);
-        },
-        [crypto, addMemberNeedingApproval],
+    const onButtonClick = (ev: ButtonEvent): void => {
+        ev.preventDefault();
+        if (currentPrompt.type === "VerificationViolation") {
+            dispatchAction({ type: "WithdrawVerification", userId: currentPrompt.member.userId });
+        } else {
+            dispatchAction({ type: "PinUserIdentity", userId: currentPrompt.member.userId });
+        }
+    };
+    return warningBanner(
+        currentPrompt.type === "VerificationViolation",
+        memberAvatar(currentPrompt.member),
+        title,
+        action,
+        onButtonClick,
     );
+};
 
-    // Remove a user from the membersNeedingApproval map, and update the current
-    // prompt if necessary.
-    const removeMemberNeedingApproval = useCallback(
-        (userId: string): void => {
-            membersNeedingApprovalRef.current.delete(userId);
-            logger.debug(
-                `UserIdentityWarning: user ${userId} no longer needs approval; approval-pending list now [${Array.from(membersNeedingApprovalRef.current.keys())}]`,
+function getTitleAndAction(prompt: ViolationPrompt): [title: React.ReactNode, action: string] {
+    let title: React.ReactNode;
+    let action: string;
+    if (prompt.type === "VerificationViolation") {
+        if (prompt.member.rawDisplayName === prompt.member.userId) {
+            title = _t(
+                "encryption|verified_identity_changed_no_displayname",
+                { userId: prompt.member.userId },
+                {
+                    a: substituteATag,
+                    b: substituteBTag,
+                },
+            );
+        } else {
+            title = _t(
+                "encryption|verified_identity_changed",
+                { displayName: prompt.member.rawDisplayName, userId: prompt.member.userId },
+                {
+                    a: substituteATag,
+                    b: substituteBTag,
+                },
             );
-            updateCurrentPrompt();
-        },
-        [updateCurrentPrompt],
-    );
-
-    // Initialise the component.  Get the room members, check which ones need
-    // their identity approved, and pick one to display.
-    const loadMembers = useCallback(async (): Promise<void> => {
-        if (!crypto || initialisedRef.current !== InitialisationStatus.Uninitialised) {
-            return;
         }
-        // If encryption is not enabled in the room, we don't need to do
-        // anything.  If encryption gets enabled later, we will retry, via
-        // onRoomStateEvent.
-        if (!(await crypto.isEncryptionEnabledInRoom(room.roomId))) {
-            return;
+        action = _t("encryption|withdraw_verification_action");
+    } else {
+        if (prompt.member.rawDisplayName === prompt.member.userId) {
+            title = _t(
+                "encryption|pinned_identity_changed_no_displayname",
+                { userId: prompt.member.userId },
+                {
+                    a: substituteATag,
+                    b: substituteBTag,
+                },
+            );
+        } else {
+            title = _t(
+                "encryption|pinned_identity_changed",
+                { displayName: prompt.member.rawDisplayName, userId: prompt.member.userId },
+                {
+                    a: substituteATag,
+                    b: substituteBTag,
+                },
+            );
         }
-        initialisedRef.current = InitialisationStatus.Initialising;
-
-        const members = await room.getEncryptionTargetMembers();
-        await addMembersWhoNeedApproval(members);
-
-        logger.info(
-            `Initialised UserIdentityWarning component for room ${room.roomId} with approval-pending list [${Array.from(membersNeedingApprovalRef.current.keys())}]`,
-        );
-        updateCurrentPrompt();
-        initialisedRef.current = InitialisationStatus.Completed;
-    }, [crypto, room, addMembersWhoNeedApproval, updateCurrentPrompt]);
-
-    useEffect(() => {
-        loadMembers().catch((e) => {
-            logger.error("Error initialising UserIdentityWarning:", e);
-        });
-    }, [loadMembers]);
-
-    // When a user's verification status changes, we check if they need to be
-    // added/removed from the set of members needing approval.
-    const onUserVerificationStatusChanged = useCallback(
-        (userId: string, verificationStatus: UserVerificationStatus): void => {
-            // If we haven't started initialising, that means that we're in a
-            // room where we don't need to display any warnings.
-            if (initialisedRef.current === InitialisationStatus.Uninitialised) {
-                return;
-            }
-
-            incrementVerificationStatusSequence(userId);
-
-            if (verificationStatus.needsUserApproval) {
-                addMemberNeedingApproval(userId);
-            } else {
-                removeMemberNeedingApproval(userId);
-            }
-        },
-        [addMemberNeedingApproval, removeMemberNeedingApproval],
-    );
-    useTypedEventEmitter(cli, CryptoEvent.UserTrustStatusChanged, onUserVerificationStatusChanged);
-
-    // We watch for encryption events (since we only display warnings in
-    // encrypted rooms), and for membership changes (since we only display
-    // warnings for users in the room).
-    const onRoomStateEvent = useCallback(
-        async (event: MatrixEvent): Promise<void> => {
-            if (!crypto || event.getRoomId() !== room.roomId) {
-                return;
-            }
-
-            const eventType = event.getType();
-            if (eventType === EventType.RoomEncryption && event.getStateKey() === "") {
-                // Room is now encrypted, so we can initialise the component.
-                return loadMembers().catch((e) => {
-                    logger.error("Error initialising UserIdentityWarning:", e);
-                });
-            } else if (eventType !== EventType.RoomMember) {
-                return;
-            }
-
-            // We're processing an m.room.member event
-
-            if (initialisedRef.current === InitialisationStatus.Uninitialised) {
-                return;
-            }
-
-            const userId = event.getStateKey();
-
-            if (!userId) return;
-
-            if (
-                event.getContent().membership === KnownMembership.Join ||
-                (event.getContent().membership === KnownMembership.Invite && room.shouldEncryptForInvitedMembers())
-            ) {
-                // Someone's membership changed and we will now encrypt to them.  If
-                // their identity needs approval, show a warning.
-                const member = room.getMember(userId);
-                if (member) {
-                    await addMembersWhoNeedApproval([member]).catch((e) => {
-                        logger.error("Error adding member in UserIdentityWarning:", e);
-                    });
-                }
-            } else {
-                // Someone's membership changed and we no longer encrypt to them.
-                // If we're showing a warning about them, we don't need to any more.
-                removeMemberNeedingApproval(userId);
-                incrementVerificationStatusSequence(userId);
-            }
-        },
-        [crypto, room, addMembersWhoNeedApproval, removeMemberNeedingApproval, loadMembers],
-    );
-    useTypedEventEmitter(cli, RoomStateEvent.Events, onRoomStateEvent);
-
-    if (!crypto || !currentPrompt) return null;
-
-    const confirmIdentity = async (): Promise<void> => {
-        await crypto.pinCurrentUserIdentity(currentPrompt.userId);
-    };
+        action = _t("action|dismiss");
+    }
+    return [title, action];
+}
 
+function warningBanner(
+    isCritical: boolean,
+    avatar: React.ReactNode,
+    title: React.ReactNode,
+    action: string,
+    onButtonClick: (ev: ButtonEvent) => void,
+): React.ReactNode {
     return (
-        <div className="mx_UserIdentityWarning">
+        <div className={classNames("mx_UserIdentityWarning", { critical: isCritical })}>
             <Separator />
             <div className="mx_UserIdentityWarning_row">
-                <MemberAvatar member={currentPrompt} title={currentPrompt.userId} size="30px" />
-                <span className="mx_UserIdentityWarning_main">
-                    {currentPrompt.rawDisplayName === currentPrompt.userId
-                        ? _t(
-                              "encryption|pinned_identity_changed_no_displayname",
-                              { userId: currentPrompt.userId },
-                              {
-                                  a: substituteATag,
-                                  b: substituteBTag,
-                              },
-                          )
-                        : _t(
-                              "encryption|pinned_identity_changed",
-                              { displayName: currentPrompt.rawDisplayName, userId: currentPrompt.userId },
-                              {
-                                  a: substituteATag,
-                                  b: substituteBTag,
-                              },
-                          )}
-                </span>
-                <Button kind="primary" size="sm" onClick={confirmIdentity}>
-                    {_t("action|ok")}
+                {avatar}
+                <span className={classNames("mx_UserIdentityWarning_main", { critical: isCritical })}>{title}</span>
+                <Button kind="secondary" size="sm" onClick={onButtonClick}>
+                    {action}
                 </Button>
             </div>
         </div>
     );
-};
+}
+function memberAvatar(member: RoomMember): React.ReactNode {
+    return <MemberAvatar member={member} title={member.userId} size="30px" />;
+}
 
 function substituteATag(sub: string): React.ReactNode {
     return (
diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx
index 486c9475bc2fccf6394acb1a2b4d2b18508d2258..b329cd84eab9714c6a577baf3eb81c9740da5336 100644
--- a/src/components/views/rooms/VoiceRecordComposerTile.tsx
+++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode } from "react";
+import { type Room, type IEventRelation, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import { _t } from "../../../languageHandler";
 import { RecordingState } from "../../../audio/VoiceRecording";
@@ -32,7 +32,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { attachMentions, attachRelation } from "./SendMessageComposer";
 import { addReplyToMessageContent } from "../../../utils/Reply";
 import RoomContext from "../../../contexts/RoomContext";
-import { IUpload, VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
+import { type IUpload, type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
 import { createVoiceMessageContent } from "../../../utils/createVoiceMessageContent";
 import AccessibleButton from "../elements/AccessibleButton";
 
@@ -56,8 +56,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
     declare public context: React.ContextType<typeof RoomContext>;
     private voiceRecordingId: string;
 
-    public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {};
 
diff --git a/src/components/views/rooms/WhoIsTypingTile.tsx b/src/components/views/rooms/WhoIsTypingTile.tsx
index 7c0c53c7a2ebf062911f21f7a3a62e3a12ae3113..64a7ccaebfadb25369e8c6a73f464d496104f34c 100644
--- a/src/components/views/rooms/WhoIsTypingTile.tsx
+++ b/src/components/views/rooms/WhoIsTypingTile.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { Room, RoomEvent, RoomMember, RoomMemberEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { type Room, RoomEvent, type RoomMember, RoomMemberEvent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import * as WhoIsTyping from "../../../WhoIsTyping";
 import Timer from "../../../utils/Timer";
diff --git a/src/components/views/rooms/wysiwyg_composer/ComposerContext.ts b/src/components/views/rooms/wysiwyg_composer/ComposerContext.ts
index cfd8e6fa80285f2db9f4dddc44eef9b63f979063..df6ef53f952cb52402754e73d173b721a9731e06 100644
--- a/src/components/views/rooms/wysiwyg_composer/ComposerContext.ts
+++ b/src/components/views/rooms/wysiwyg_composer/ComposerContext.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { createContext, useContext } from "react";
-import { IEventRelation } from "matrix-js-sdk/src/matrix";
+import { type IEventRelation } from "matrix-js-sdk/src/matrix";
 
-import { SubSelection } from "./types";
-import EditorStateTransfer from "../../../../utils/EditorStateTransfer";
+import { type SubSelection } from "./types";
+import type EditorStateTransfer from "../../../../utils/EditorStateTransfer";
 
 export function getDefaultContextValue(defaultValue?: Partial<ComposerContextState>): { selection: SubSelection } {
     return {
diff --git a/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx
index f713832d92e13e937248b19b7169566663319b4e..3c9e30148fd4a6ee110a996c1ce162a18f09e286 100644
--- a/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps, lazy, Suspense } from "react";
-import { ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ComponentProps, lazy, Suspense } from "react";
+import { type ISendEventResponse } from "matrix-js-sdk/src/matrix";
 
 // we need to import the types for TS, but do not import the sendMessage
 // function to avoid importing from "@vector-im/matrix-wysiwyg"
-import { SendMessageParams } from "./utils/message";
+import { type SendMessageParams } from "./utils/message";
 
 const SendComposer = lazy(() => import("./SendWysiwygComposer"));
 const EditComposer = lazy(() => import("./EditWysiwygComposer"));
diff --git a/src/components/views/rooms/wysiwyg_composer/EditWysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/EditWysiwygComposer.tsx
index 2de986a299690b00ad755dc9002ea806ba38da9f..c84f130f3459f733f63f4c0b8133e218b7196446 100644
--- a/src/components/views/rooms/wysiwyg_composer/EditWysiwygComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/EditWysiwygComposer.tsx
@@ -6,30 +6,28 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
+import React, { type JSX, type RefObject, useMemo, type ReactNode } from "react";
 import classNames from "classnames";
 
-import EditorStateTransfer from "../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../utils/EditorStateTransfer";
 import { WysiwygComposer } from "./components/WysiwygComposer";
 import { EditionButtons } from "./components/EditionButtons";
 import { useWysiwygEditActionHandler } from "./hooks/useWysiwygEditActionHandler";
 import { useEditing } from "./hooks/useEditing";
 import { useInitialContent } from "./hooks/useInitialContent";
 import { ComposerContext, getDefaultContextValue } from "./ComposerContext";
-import { ComposerFunctions } from "./types";
+import { type ComposerFunctions } from "./types";
 
 interface ContentProps {
     disabled?: boolean;
     composerFunctions: ComposerFunctions;
+    ref?: RefObject<HTMLElement | null>;
 }
 
-const Content = forwardRef<HTMLElement, ContentProps>(function Content(
-    { disabled = false, composerFunctions }: ContentProps,
-    forwardRef: ForwardedRef<HTMLElement>,
-) {
-    useWysiwygEditActionHandler(disabled, forwardRef as MutableRefObject<HTMLElement>, composerFunctions);
+const Content = function Content({ disabled = false, composerFunctions, ref }: ContentProps): ReactNode {
+    useWysiwygEditActionHandler(disabled, ref, composerFunctions);
     return null;
-});
+};
 
 interface EditWysiwygComposerProps {
     disabled?: boolean;
diff --git a/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx
index e705a0f48632f8a0513e1c222fd6bb1c903e2c47..9b21eef545502e8d323ad0ce7f977d796cb32796 100644
--- a/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx
@@ -6,31 +6,29 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
-import { IEventRelation } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type RefObject, useMemo, type ReactNode } from "react";
+import { type IEventRelation } from "matrix-js-sdk/src/matrix";
 
 import { useWysiwygSendActionHandler } from "./hooks/useWysiwygSendActionHandler";
 import { WysiwygComposer } from "./components/WysiwygComposer";
 import { PlainTextComposer } from "./components/PlainTextComposer";
-import { ComposerFunctions } from "./types";
-import { E2EStatus } from "../../../../utils/ShieldUtils";
+import { type ComposerFunctions } from "./types";
+import { type E2EStatus } from "../../../../utils/ShieldUtils";
 import E2EIcon from "../E2EIcon";
-import { MenuProps } from "../../../structures/ContextMenu";
+import { type MenuProps } from "../../../structures/ContextMenu";
 import { Emoji } from "./components/Emoji";
 import { ComposerContext, getDefaultContextValue } from "./ComposerContext";
 
 interface ContentProps {
     disabled?: boolean;
     composerFunctions: ComposerFunctions;
+    ref?: RefObject<HTMLElement | null>;
 }
 
-const Content = forwardRef<HTMLElement, ContentProps>(function Content(
-    { disabled = false, composerFunctions }: ContentProps,
-    forwardRef: ForwardedRef<HTMLElement>,
-) {
-    useWysiwygSendActionHandler(disabled, forwardRef as MutableRefObject<HTMLElement>, composerFunctions);
+const Content = function Content({ disabled = false, composerFunctions, ref }: ContentProps): ReactNode {
+    useWysiwygSendActionHandler(disabled, ref, composerFunctions);
     return null;
-});
+};
 
 export interface SendWysiwygComposerProps {
     initialContent?: string;
diff --git a/src/components/views/rooms/wysiwyg_composer/components/EditionButtons.tsx b/src/components/views/rooms/wysiwyg_composer/components/EditionButtons.tsx
index 0a6a78210c3eb58924f1b8c2c212f91e2652ff52..3742c7dabd7444634f15fe73078883799ec43b56 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/EditionButtons.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/EditionButtons.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { _t } from "../../../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../elements/AccessibleButton";
 
 interface EditionButtonsProps {
     onCancelClick: (e: ButtonEvent) => void;
diff --git a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx
index 79bc19445ca4f5d18a816d7ceeab489c85a3744d..d418a0df198b3d568805d7a415f23f265baefbd4 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { CSSProperties, forwardRef, memo, MutableRefObject, ReactNode } from "react";
+import React, { type CSSProperties, memo, type RefObject, type ReactNode } from "react";
 
 import { useIsExpanded } from "../hooks/useIsExpanded";
 import { useSelection } from "../hooks/useSelection";
@@ -19,44 +19,36 @@ interface EditorProps {
     placeholder?: string;
     leftComponent?: ReactNode;
     rightComponent?: ReactNode;
+    ref?: RefObject<HTMLDivElement | null>;
 }
 
-export const Editor = memo(
-    forwardRef<HTMLDivElement, EditorProps>(function Editor(
-        { disabled, placeholder, leftComponent, rightComponent }: EditorProps,
-        ref,
-    ) {
-        const isExpanded = useIsExpanded(ref as MutableRefObject<HTMLDivElement | null>, HEIGHT_BREAKING_POINT);
-        const { onFocus, onBlur, onInput } = useSelection();
+export const Editor = memo(function Editor({ disabled, placeholder, leftComponent, rightComponent, ref }: EditorProps) {
+    const isExpanded = useIsExpanded(ref, HEIGHT_BREAKING_POINT);
+    const { onFocus, onBlur, onInput } = useSelection();
 
-        return (
-            <div
-                data-testid="WysiwygComposerEditor"
-                className="mx_WysiwygComposer_Editor"
-                data-is-expanded={isExpanded}
-            >
-                {leftComponent}
-                <div className="mx_WysiwygComposer_Editor_container">
-                    <div
-                        className={classNames("mx_WysiwygComposer_Editor_content", {
-                            mx_WysiwygComposer_Editor_content_placeholder: Boolean(placeholder),
-                        })}
-                        style={{ "--placeholder": `"${placeholder}"` } as CSSProperties}
-                        ref={ref}
-                        contentEditable={!disabled}
-                        role="textbox"
-                        aria-multiline="true"
-                        aria-autocomplete="list"
-                        aria-haspopup="listbox"
-                        dir="auto"
-                        aria-disabled={disabled}
-                        onFocus={onFocus}
-                        onBlur={onBlur}
-                        onInput={onInput}
-                    />
-                </div>
-                {rightComponent}
+    return (
+        <div data-testid="WysiwygComposerEditor" className="mx_WysiwygComposer_Editor" data-is-expanded={isExpanded}>
+            {leftComponent}
+            <div className="mx_WysiwygComposer_Editor_container">
+                <div
+                    className={classNames("mx_WysiwygComposer_Editor_content", {
+                        mx_WysiwygComposer_Editor_content_placeholder: Boolean(placeholder),
+                    })}
+                    style={{ "--placeholder": `"${placeholder}"` } as CSSProperties}
+                    ref={ref}
+                    contentEditable={!disabled}
+                    role="textbox"
+                    aria-multiline="true"
+                    aria-autocomplete="list"
+                    aria-haspopup="listbox"
+                    dir="auto"
+                    aria-disabled={disabled}
+                    onFocus={onFocus}
+                    onBlur={onBlur}
+                    onInput={onInput}
+                />
             </div>
-        );
-    }),
-);
+            {rightComponent}
+        </div>
+    );
+});
diff --git a/src/components/views/rooms/wysiwyg_composer/components/Emoji.tsx b/src/components/views/rooms/wysiwyg_composer/components/Emoji.tsx
index 94d7603c6b4635c4097ab3412cb496bb55436935..75e8cc10cd2bf9dbbd0e3683e56b1a137f3220fd 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/Emoji.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/Emoji.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
-import { MenuProps } from "../../../../structures/ContextMenu";
+import { type MenuProps } from "../../../../structures/ContextMenu";
 import { EmojiButton } from "../../EmojiButton";
 import dis from "../../../../../dispatcher/dispatcher";
-import { ComposerInsertPayload } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
+import { type ComposerInsertPayload } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
 import { Action } from "../../../../../dispatcher/actions";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
 
diff --git a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
index e541d17e0dd066d882a761a9e45976ff8c8640c6..27301110f30b8783b8d08046c62fe3517b51fa8e 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { MouseEventHandler, ReactNode } from "react";
-import { FormattingFunctions, AllActionStates, ActionState } from "@vector-im/matrix-wysiwyg";
+import React, { type JSX, type MouseEventHandler, type ReactNode } from "react";
+import { type FormattingFunctions, type AllActionStates, type ActionState } from "@vector-im/matrix-wysiwyg";
 import classNames from "classnames";
 import BoldIcon from "@vector-im/compound-design-tokens/assets/web/icons/bold";
 import BulletedListIcon from "@vector-im/compound-design-tokens/assets/web/icons/list-bulleted";
@@ -23,11 +23,11 @@ import UnderlineIcon from "@vector-im/compound-design-tokens/assets/web/icons/un
 import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
 
 import { _t } from "../../../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../elements/AccessibleButton";
 import { openLinkModal } from "./LinkModal";
 import { useComposerContext } from "../ComposerContext";
 import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
-import { KeyCombo } from "../../../../../KeyBindingsManager";
+import { type KeyCombo } from "../../../../../KeyBindingsManager";
 
 interface ButtonProps {
     icon: ReactNode;
diff --git a/src/components/views/rooms/wysiwyg_composer/components/LinkModal.tsx b/src/components/views/rooms/wysiwyg_composer/components/LinkModal.tsx
index 31f153dbec82c880c95da7ab7026f6fd22a14190..b65caf13e03d3761bcef9ca0e0f2820d696a2fdb 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/LinkModal.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/LinkModal.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { FormattingFunctions } from "@vector-im/matrix-wysiwyg";
-import React, { ChangeEvent, useState } from "react";
+import { type FormattingFunctions } from "@vector-im/matrix-wysiwyg";
+import React, { type ChangeEvent, useState } from "react";
 
 import { _t } from "../../../../../languageHandler";
 import Modal from "../../../../../Modal";
 import Field from "../../../elements/Field";
-import { ComposerContextState } from "../ComposerContext";
+import { type ComposerContextState } from "../ComposerContext";
 import { isSelectionEmpty, setSelection } from "../utils/selection";
 import BaseDialog from "../../../dialogs/BaseDialog";
 import DialogButtons from "../../../elements/DialogButtons";
diff --git a/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx
index c826bbc98b5444f31dbed9bedab672f91d504199..33d8e88e033ab1e406ec98073cdde36226c51f3e 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import { IEventRelation } from "matrix-js-sdk/src/matrix";
-import React, { MutableRefObject, ReactNode } from "react";
+import { type IEventRelation } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type RefObject, type ReactNode } from "react";
 
 import { useComposerFunctions } from "../hooks/useComposerFunctions";
 import { useIsFocused } from "../hooks/useIsFocused";
 import { usePlainTextInitialization } from "../hooks/usePlainTextInitialization";
 import { usePlainTextListeners } from "../hooks/usePlainTextListeners";
 import { useSetCursorPosition } from "../hooks/useSetCursorPosition";
-import { ComposerFunctions } from "../types";
+import { type ComposerFunctions } from "../types";
 import { Editor } from "./Editor";
 import { WysiwygAutocomplete } from "./WysiwygAutocomplete";
 import { useSettingValue } from "../../../../../hooks/useSettings";
@@ -29,7 +29,7 @@ interface PlainTextComposerProps {
     className?: string;
     leftComponent?: ReactNode;
     rightComponent?: ReactNode;
-    children?: (ref: MutableRefObject<HTMLDivElement | null>, composerFunctions: ComposerFunctions) => ReactNode;
+    children?: (ref: RefObject<HTMLDivElement | null>, composerFunctions: ComposerFunctions) => ReactNode;
     eventRelation?: IEventRelation;
 }
 
diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx
index b006ec2d3520145f85b30e6646356969448990ac..9446a32a7baca0265d722171d8642481d71a1e67 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardedRef, forwardRef, FunctionComponent } from "react";
-import { FormattingFunctions, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
+import React, { type JSX, type Ref, type FunctionComponent } from "react";
+import { type FormattingFunctions, type MappedSuggestion } from "@vector-im/matrix-wysiwyg";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import Autocomplete from "../../Autocomplete";
-import { ICompletion } from "../../../../../autocomplete/Autocompleter";
+import { type ICompletion } from "../../../../../autocomplete/Autocompleter";
 import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
 import { getMentionDisplayText, getMentionAttributes, buildQuery } from "../utils/autocomplete";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
@@ -39,6 +39,8 @@ interface WysiwygAutocompleteProps {
      * Handler purely for the at-room mentions special case
      */
     handleAtRoomMention: FormattingFunctions["mentionAtRoom"];
+
+    ref?: Ref<Autocomplete>;
 }
 
 /**
@@ -48,69 +50,70 @@ interface WysiwygAutocompleteProps {
  *
  * @param props.ref - the ref will be attached to the rendered `<Autocomplete />` component
  */
-const WysiwygAutocomplete = forwardRef(
-    (
-        { suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps,
-        ref: ForwardedRef<Autocomplete>,
-    ): JSX.Element | null => {
-        const { room } = useScopedRoomContext("room");
-        const client = useMatrixClientContext();
-
-        function handleConfirm(completion: ICompletion): void {
-            if (client === undefined || room === undefined) {
+const WysiwygAutocomplete = ({
+    suggestion,
+    handleMention,
+    handleCommand,
+    handleAtRoomMention,
+    ref,
+}: WysiwygAutocompleteProps): JSX.Element | null => {
+    const { room } = useScopedRoomContext("room");
+    const client = useMatrixClientContext();
+
+    function handleConfirm(completion: ICompletion): void {
+        if (client === undefined || room === undefined) {
+            return;
+        }
+
+        switch (completion.type) {
+            case "command": {
+                // TODO determine if utils in SlashCommands.tsx are required.
+                // Trim the completion as some include trailing spaces, but we always insert a
+                // trailing space in the rust model anyway
+                handleCommand(completion.completion.trim());
                 return;
             }
-
-            switch (completion.type) {
-                case "command": {
-                    // TODO determine if utils in SlashCommands.tsx are required.
-                    // Trim the completion as some include trailing spaces, but we always insert a
-                    // trailing space in the rust model anyway
-                    handleCommand(completion.completion.trim());
-                    return;
-                }
-                case "at-room": {
-                    handleAtRoomMention(getMentionAttributes(completion, client, room));
-                    return;
-                }
-                case "room":
-                case "user": {
-                    if (typeof completion.href === "string") {
-                        handleMention(
-                            completion.href,
-                            getMentionDisplayText(completion, client),
-                            getMentionAttributes(completion, client, room),
-                        );
-                    }
-                    return;
+            case "at-room": {
+                handleAtRoomMention(getMentionAttributes(completion, client, room));
+                return;
+            }
+            case "room":
+            case "user": {
+                if (typeof completion.href === "string") {
+                    handleMention(
+                        completion.href,
+                        getMentionDisplayText(completion, client),
+                        getMentionAttributes(completion, client, room),
+                    );
                 }
-                // TODO - handle "community" type
-                default:
-                    return;
+                return;
             }
+            // TODO - handle "community" type
+            default:
+                return;
         }
+    }
+
+    if (!room) return null;
+
+    const autoCompleteQuery = buildQuery(suggestion);
+    // debug for https://github.com/vector-im/element-web/issues/26037
+    logger.log(`## 26037 ## Rendering Autocomplete for WysiwygAutocomplete with query: "${autoCompleteQuery}"`);
 
-        if (!room) return null;
-
-        const autoCompleteQuery = buildQuery(suggestion);
-        // debug for https://github.com/vector-im/element-web/issues/26037
-        logger.log(`## 26037 ## Rendering Autocomplete for WysiwygAutocomplete with query: "${autoCompleteQuery}"`);
-
-        // TODO - determine if we show all of the /command suggestions, there are some options in the
-        // list which don't seem to make sense in this context, specifically /html and /plain
-        return (
-            <div className="mx_WysiwygComposer_AutoCompleteWrapper" data-testid="autocomplete-wrapper">
-                <Autocomplete
-                    ref={ref}
-                    query={autoCompleteQuery}
-                    onConfirm={handleConfirm}
-                    selection={{ start: 0, end: 0 }}
-                    room={room}
-                />
-            </div>
-        );
-    },
-);
+    // TODO - determine if we show all of the /command suggestions, there are some options in the
+    // list which don't seem to make sense in this context, specifically /html and /plain
+    return (
+        <div className="mx_WysiwygComposer_AutoCompleteWrapper" data-testid="autocomplete-wrapper">
+            <Autocomplete
+                ref={ref}
+                query={autoCompleteQuery}
+                onConfirm={handleConfirm}
+                selection={{ start: 0, end: 0 }}
+                room={room}
+            />
+        </div>
+    );
+};
 
 (WysiwygAutocomplete as FunctionComponent).displayName = "WysiwygAutocomplete";
 
diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx
index e7dd0213a4caf6b025213efbe7b26fce83b3bd97..4c8ba0834fae1f6701c8da9149b1b20b418b346a 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { memo, MutableRefObject, ReactNode, useEffect, useMemo, useRef } from "react";
-import { IEventRelation } from "matrix-js-sdk/src/matrix";
+import React, { memo, type RefObject, type ReactNode, useEffect, useMemo, useRef } from "react";
+import { type IEventRelation } from "matrix-js-sdk/src/matrix";
 import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings";
-import { useWysiwyg, FormattingFunctions } from "@vector-im/matrix-wysiwyg";
+import { useWysiwyg, type FormattingFunctions } from "@vector-im/matrix-wysiwyg";
 import classNames from "classnames";
 
-import Autocomplete from "../../Autocomplete";
+import type Autocomplete from "../../Autocomplete";
 import { WysiwygAutocomplete } from "./WysiwygAutocomplete";
 import { FormattingButtons } from "./FormattingButtons";
 import { Editor } from "./Editor";
@@ -35,7 +35,7 @@ interface WysiwygComposerProps {
     className?: string;
     leftComponent?: ReactNode;
     rightComponent?: ReactNode;
-    children?: (ref: MutableRefObject<HTMLDivElement | null>, wysiwyg: FormattingFunctions) => ReactNode;
+    children?: (ref: RefObject<HTMLDivElement | null>, wysiwyg: FormattingFunctions) => ReactNode;
     eventRelation?: IEventRelation;
 }
 
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts
index 20d877271ef69bf4c6462817bd737057934c8edf..e9f6365d02068e1b61b939758010a79938559f78 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RefObject, useMemo } from "react";
+import { type RefObject, useMemo } from "react";
 
 import { setSelection } from "../utils/selection";
 
 export function useComposerFunctions(
-    ref: RefObject<HTMLDivElement>,
+    ref: RefObject<HTMLDivElement | null>,
     setContent: (content: string) => void,
 ): {
     clear(): void;
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts
index 8fbd856200a5c874b95295919813d45d014622a9..e48b00e82a72005dd450666d193317a97e8223ab 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import { type ISendEventResponse } from "matrix-js-sdk/src/matrix";
 import { useCallback, useState } from "react";
 
 import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 import { endEditing } from "../utils/editing";
 import { editMessage } from "../utils/message";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useInitialContent.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useInitialContent.ts
index 25afcd431f15da6898ac36229018c504191446a1..f87e8d0f18988657d6d1a855ca171ec90e9e3f71 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useInitialContent.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useInitialContent.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { useMemo } from "react";
 
 import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
 import { parseEvent } from "../../../../../editor/deserialize";
-import { CommandPartCreator, Part } from "../../../../../editor/parts";
+import { CommandPartCreator, type Part } from "../../../../../editor/parts";
 import SettingsStore from "../../../../../settings/SettingsStore";
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
 
 function getFormattedContent(editorStateTransfer: EditorStateTransfer): string {
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts
index 26a72f4b96f01db6cbb83aec047a7f2d48c8deab..30e5649013b3ddd6d03a688d00538442ff0804c6 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useInputEventProcessor.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Wysiwyg, WysiwygEvent } from "@vector-im/matrix-wysiwyg";
+import { type Wysiwyg, type WysiwygEvent } from "@vector-im/matrix-wysiwyg";
 import { useCallback } from "react";
-import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IEventRelation, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useSettingValue } from "../../../../../hooks/useSettings";
 import { getKeyBindingsManager } from "../../../../../KeyBindingsManager";
@@ -16,20 +16,20 @@ import { KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts
 import { findEditableEvent } from "../../../../../utils/EventUtils";
 import dis from "../../../../../dispatcher/dispatcher";
 import { Action } from "../../../../../dispatcher/actions";
-import { IRoomState } from "../../../../structures/RoomView";
-import { ComposerContextState, useComposerContext } from "../ComposerContext";
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import { type IRoomState } from "../../../../structures/RoomView";
+import { type ComposerContextState, useComposerContext } from "../ComposerContext";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
 import { isCaretAtEnd, isCaretAtStart } from "../utils/selection";
 import { getEventsFromEditorStateTransfer, getEventsFromRoom } from "../utils/event";
 import { endEditing } from "../utils/editing";
-import Autocomplete from "../../Autocomplete";
+import type Autocomplete from "../../Autocomplete";
 import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
 
 export function useInputEventProcessor(
     onSend: () => void,
-    autocompleteRef: React.RefObject<Autocomplete>,
+    autocompleteRef: React.RefObject<Autocomplete | null>,
     initialContent?: string,
     eventRelation?: IEventRelation,
 ): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null {
@@ -97,7 +97,7 @@ function handleKeyboardEvent(
     roomContext: Pick<IRoomState, "liveTimeline" | "timelineRenderingType" | "room">,
     composerContext: ComposerContextState,
     mxClient: MatrixClient | undefined,
-    autocompleteRef: React.RefObject<Autocomplete>,
+    autocompleteRef: React.RefObject<Autocomplete | null>,
 ): KeyboardEvent | null {
     const { editorStateTransfer } = composerContext;
     const isEditing = Boolean(editorStateTransfer);
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useIsExpanded.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useIsExpanded.ts
index 9941dda7bb7f1518773f915f96ff56f4654795d2..d5f98502776bcdc4c12c93f7987c5a0da655dec7 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useIsExpanded.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useIsExpanded.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MutableRefObject, useEffect, useState } from "react";
+import { type RefObject, useEffect, useState } from "react";
 
-export function useIsExpanded(ref: MutableRefObject<HTMLElement | null>, breakingPoint: number): boolean {
+export function useIsExpanded(ref: RefObject<HTMLElement | null> | undefined, breakingPoint: number): boolean {
     const [isExpanded, setIsExpanded] = useState(false);
     useEffect(() => {
-        if (ref.current) {
+        if (ref?.current) {
             const editor = ref.current;
             const resizeObserver = new ResizeObserver((entries) => {
                 requestAnimationFrame(() => {
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useIsFocused.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useIsFocused.ts
index 139193971311da49d72ab98e5d55787d76beea66..57039d359921e2e21eec02234522305bd67c8ed6 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useIsFocused.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useIsFocused.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { FocusEvent, useCallback, useEffect, useRef, useState } from "react";
+import { type FocusEvent, useCallback, useEffect, useRef, useState } from "react";
 
 export function useIsFocused(): {
     isFocused: boolean;
     onFocus(event: FocusEvent<HTMLElement>): void;
 } {
     const [isFocused, setIsFocused] = useState(false);
-    const timeoutIDRef = useRef<number>();
+    const timeoutIDRef = useRef<number>(undefined);
 
     useEffect(() => () => clearTimeout(timeoutIDRef.current), [timeoutIDRef]);
     const onFocus = useCallback(
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextInitialization.ts b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextInitialization.ts
index bc58160ce38bc7089cb4aae19025b76037223230..b547afffbea01bc917c98d16e17901038e9645ec 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextInitialization.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextInitialization.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RefObject, useEffect } from "react";
+import { type RefObject, useEffect } from "react";
 
-export function usePlainTextInitialization(initialContent = "", ref: RefObject<HTMLElement>): void {
+export function usePlainTextInitialization(initialContent = "", ref: RefObject<HTMLElement | null>): void {
     useEffect(() => {
         // always read and write the ref.current using .innerHTML for consistency in linebreak and HTML entity handling
         if (ref.current) {
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts
index 6dfae53efed4a452b8112e8faf211d621c16f778..57a5d1d653bc0c8205bb89e5c329ca3039fb6bca 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { KeyboardEvent, RefObject, SyntheticEvent, useCallback, useRef, useState } from "react";
-import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
-import { IEventRelation } from "matrix-js-sdk/src/matrix";
+import { type KeyboardEvent, type RefObject, type SyntheticEvent, useCallback, useRef, useState } from "react";
+import { type AllowedMentionAttributes, type MappedSuggestion } from "@vector-im/matrix-wysiwyg";
+import { type IEventRelation } from "matrix-js-sdk/src/matrix";
 
 import { useSettingValue } from "../../../../../hooks/useSettings";
 import { IS_MAC, Key } from "../../../../../Keyboard";
-import Autocomplete from "../../Autocomplete";
+import type Autocomplete from "../../Autocomplete";
 import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils";
 import { useSuggestion } from "./useSuggestion";
 import { isNotNull, isNotUndefined } from "../../../../../Typeguards";
@@ -49,8 +49,8 @@ export function usePlainTextListeners(
     eventRelation?: IEventRelation,
     isAutoReplaceEmojiEnabled?: boolean,
 ): {
-    ref: RefObject<HTMLDivElement>;
-    autocompleteRef: React.RefObject<Autocomplete>;
+    ref: RefObject<HTMLDivElement | null>;
+    autocompleteRef: RefObject<Autocomplete | null>;
     content?: string;
     onBeforeInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
     onInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
@@ -66,8 +66,8 @@ export function usePlainTextListeners(
     const roomContext = useScopedRoomContext("room", "timelineRenderingType", "replyToEvent");
     const mxClient = useMatrixClientContext();
 
-    const ref = useRef<HTMLDivElement | null>(null);
-    const autocompleteRef = useRef<Autocomplete | null>(null);
+    const ref = useRef<HTMLDivElement>(null);
+    const autocompleteRef = useRef<Autocomplete>(null);
     const [content, setContent] = useState<string | undefined>(initialContent);
 
     const send = useCallback(() => {
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts
index 0689c466f651e1889656093c43d6979133a91ef4..10686f1078c78f104677ffd65aae21879cf6489c 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import { useCallback, useEffect } from "react";
 
 import useFocus from "../../../../../hooks/useFocus";
-import { useComposerContext, ComposerContextState } from "../ComposerContext";
+import { useComposerContext, type ComposerContextState } from "../ComposerContext";
 
 function setSelectionContext(composerContext: ComposerContextState): void {
     const selection = document.getSelection();
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useSetCursorPosition.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useSetCursorPosition.ts
index 8fd39111ff63be8e4ab211aa9c3e336dd01fa4d6..408c24ea7cca370ca0ddfc5749ccd6524d86a20a 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useSetCursorPosition.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useSetCursorPosition.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RefObject, useEffect } from "react";
+import { type RefObject, useEffect } from "react";
 
 import { setCursorPositionAtTheEnd } from "./utils";
 
-export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLElement>): void {
+export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLDivElement | null>): void {
     useEffect(() => {
         if (ref.current && !disabled) {
             setCursorPositionAtTheEnd(ref.current);
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion.ts
index 6e4473f45458db4e21e5822e69ce58dc8e730b0c..89d189257a67cd5145f65b1bad203294db6c415a 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useSuggestion.ts
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings";
-import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
-import { SyntheticEvent, useState, SetStateAction } from "react";
+import { type AllowedMentionAttributes, type MappedSuggestion } from "@vector-im/matrix-wysiwyg";
+import { type SyntheticEvent, useState, type SetStateAction } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { isNotNull } from "../../../../../Typeguards";
@@ -45,7 +45,7 @@ type SuggestionState = Suggestion | null;
  * this will be an object representing that command or mention, otherwise it is null
  */
 export function useSuggestion(
-    editorRef: React.RefObject<HTMLDivElement>,
+    editorRef: React.RefObject<HTMLDivElement | null>,
     setText: (text?: string) => void,
     isAutoReplaceEmojiEnabled?: boolean,
 ): {
@@ -105,7 +105,7 @@ export function useSuggestion(
  * @param isAutoReplaceEmojiEnabled - whether plain text emoticons should be auto replaced with emojis
  */
 export function processSelectionChange(
-    editorRef: React.RefObject<HTMLDivElement>,
+    editorRef: React.RefObject<HTMLDivElement | null>,
     setSuggestionData: React.Dispatch<React.SetStateAction<SuggestionState>>,
     isAutoReplaceEmojiEnabled?: boolean,
 ): void {
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygEditActionHandler.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygEditActionHandler.ts
index 8761b8addb5dc7ddfbe0caaa74de95330f31b14b..c105996a05966ab0305a313a4baa4777989de009 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygEditActionHandler.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygEditActionHandler.ts
@@ -6,23 +6,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RefObject, useCallback, useRef } from "react";
+import { type RefObject, useCallback, useRef } from "react";
 
 import defaultDispatcher from "../../../../../dispatcher/dispatcher";
 import { Action } from "../../../../../dispatcher/actions";
-import { ActionPayload } from "../../../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../../../dispatcher/payloads";
 import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
 import { useDispatcher } from "../../../../../hooks/useDispatcher";
 import { focusComposer } from "./utils";
 import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
-import { ComposerFunctions } from "../types";
+import { type ComposerFunctions } from "../types";
 import { setSelection } from "../utils/selection";
 import { useComposerContext } from "../ComposerContext";
 import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
 
 export function useWysiwygEditActionHandler(
     disabled: boolean,
-    composerElement: RefObject<HTMLElement>,
+    composerElement: RefObject<HTMLElement | null> | undefined,
     composerFunctions: ComposerFunctions,
 ): void {
     const roomContext = useScopedRoomContext("timelineRenderingType");
@@ -33,7 +33,7 @@ export function useWysiwygEditActionHandler(
         (payload: ActionPayload) => {
             // don't let the user into the composer if it is disabled - all of these branches lead
             // to the cursor being in the composer
-            if (disabled || !composerElement.current) return;
+            if (disabled || !composerElement?.current) return;
 
             const context = payload.context ?? TimelineRenderingType.Room;
 
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts
index cef635ef5b2b3f74099ae2dc642f1bcbf6ece273..fcab8f4547d27b5893b3e6f9e0deb91cb53f55f4 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MutableRefObject, useCallback, useRef } from "react";
+import { type RefObject, useCallback, useRef } from "react";
 
 import defaultDispatcher from "../../../../../dispatcher/dispatcher";
 import { Action } from "../../../../../dispatcher/actions";
-import { ActionPayload } from "../../../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../../../dispatcher/payloads";
 import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
 import { useDispatcher } from "../../../../../hooks/useDispatcher";
 import { focusComposer } from "./utils";
-import { ComposerFunctions } from "../types";
+import { type ComposerFunctions } from "../types";
 import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
 import { useComposerContext } from "../ComposerContext";
 import { setSelection } from "../utils/selection";
@@ -22,7 +22,7 @@ import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.
 
 export function useWysiwygSendActionHandler(
     disabled: boolean,
-    composerElement: MutableRefObject<HTMLElement>,
+    composerElement: RefObject<HTMLElement | null> | undefined,
     composerFunctions: ComposerFunctions,
 ): void {
     const roomContext = useScopedRoomContext("timelineRenderingType");
diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts
index 52a61ac0e3d76bc6176d19182e8584bf7f434c31..d3c150504927ddccc91b63d6a2a135787447070d 100644
--- a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts
+++ b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MutableRefObject, RefObject } from "react";
-import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { WysiwygEvent } from "@vector-im/matrix-wysiwyg";
+import { type RefObject } from "react";
+import { type IEventRelation, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type WysiwygEvent } from "@vector-im/matrix-wysiwyg";
 
-import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
-import { IRoomState } from "../../../../structures/RoomView";
-import Autocomplete from "../../Autocomplete";
+import { type TimelineRenderingType } from "../../../../../contexts/RoomContext";
+import { type IRoomState } from "../../../../structures/RoomView";
+import type Autocomplete from "../../Autocomplete";
 import { getKeyBindingsManager } from "../../../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts";
 import { getBlobSafeMimeType } from "../../../../../utils/blobs";
@@ -20,10 +20,10 @@ import ContentMessages from "../../../../../ContentMessages";
 import { isNotNull } from "../../../../../Typeguards";
 
 export function focusComposer(
-    composerElement: MutableRefObject<HTMLElement | null>,
+    composerElement: RefObject<HTMLElement | null>,
     renderingType: TimelineRenderingType,
     roomContext: Pick<IRoomState, "timelineRenderingType">,
-    timeoutId: MutableRefObject<number | null>,
+    timeoutId: RefObject<number | null>,
 ): void {
     if (renderingType === roomContext.timelineRenderingType) {
         // Immediately set the focus, so if you start typing it
@@ -62,13 +62,13 @@ export function setCursorPositionAtTheEnd(element: HTMLElement): void {
  * @returns boolean - whether or not the autocomplete has handled the event
  */
 export function handleEventWithAutocomplete(
-    autocompleteRef: RefObject<Autocomplete>,
+    autocompleteRef: RefObject<Autocomplete | null>,
     // we get a React Keyboard event from plain text composer, a Keyboard Event from the rich text composer
     event: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>,
 ): boolean {
     const autocompleteIsOpen = autocompleteRef?.current && !autocompleteRef.current.state.hide;
 
-    if (!autocompleteIsOpen) {
+    if (!autocompleteRef.current || !autocompleteIsOpen) {
         return false;
     }
 
@@ -92,7 +92,7 @@ export function handleEventWithAutocomplete(
                 handled = true;
                 break;
             case KeyBindingAction.CancelAutocomplete:
-                autocompleteRef.current.onEscape(event as {} as React.KeyboardEvent);
+                autocompleteRef.current.onEscape(event);
                 handled = true;
                 break;
             default:
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts
index 529f15cff3947a344d5b66ebd5c39743c834c2db..d4ee4cfc709b54ce8e3156689b7eb11249c9c9a7 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type AllowedMentionAttributes, type MappedSuggestion } from "@vector-im/matrix-wysiwyg";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
-import { ICompletion } from "../../../../../autocomplete/Autocompleter";
+import { type ICompletion } from "../../../../../autocomplete/Autocompleter";
 import * as Avatar from "../../../../../Avatar";
 
 /**
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts b/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts
index 5778faa61a46dc9ec3d7b6a8c9d9830826b5fdcc..63fe8ca361950f0b01fc67de9b490cdf8f077314 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts
@@ -7,8 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { richToPlain, plainToRich } from "@vector-im/matrix-wysiwyg";
-import { IContent, IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
-import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
+import { type IContent, type IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
+import {
+    type ReplacementEvent,
+    type RoomMessageEventContent,
+    type RoomMessageTextEventContent,
+} from "matrix-js-sdk/src/types";
 
 import SettingsStore from "../../../../../settings/SettingsStore";
 import { parsePermalink } from "../../../../../utils/permalinks/Permalinks";
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/editing.ts b/src/components/views/rooms/wysiwyg_composer/utils/editing.ts
index fd5c7d1997b675539bc365b1756409a068eafb7b..bda8a7a12f51f6589ed8cf65b5d5a5bf24e2e57e 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/editing.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/editing.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventStatus, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { EventStatus, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import { IRoomState } from "../../../../structures/RoomView";
+import { type IRoomState } from "../../../../structures/RoomView";
 import dis from "../../../../../dispatcher/dispatcher";
 import { Action } from "../../../../../dispatcher/actions";
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 
 export function endEditing(roomContext: Pick<IRoomState, "timelineRenderingType">): void {
     // todo local storage
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/event.ts b/src/components/views/rooms/wysiwyg_composer/utils/event.ts
index 5860ae9ec6b1d560d89a6ecfca4fa7d11256b200..a38a27dd92b071a8edc8b3b059cfa4920919ebb2 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/event.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/event.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
 
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
-import { IRoomState } from "../../../../structures/RoomView";
-import { ComposerContextState } from "../ComposerContext";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import { type IRoomState } from "../../../../structures/RoomView";
+import { type ComposerContextState } from "../ComposerContext";
 
 // From EditMessageComposer private get events(): MatrixEvent[]
 export function getEventsFromEditorStateTransfer(
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts b/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts
index 3821ca3512f866e0ce803f869abd1a5d74ef32be..ab215d3f91880d655934d6edbf4072f4c06ccdca 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/isContentModified.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
+import { type RoomMessageEventContent, type RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
 
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 
 export function isContentModified(
     newContent: RoomMessageEventContent,
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts
index 8f95bf8da14d57cd072d2714e45a0387e43dd8a0..b794ba6fe49071a942598ee03d1b2d885e7f759f 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
+import { type Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
 import {
-    IEventRelation,
-    MatrixEvent,
-    ISendEventResponse,
-    MatrixClient,
+    type IEventRelation,
+    type MatrixEvent,
+    type ISendEventResponse,
+    type MatrixClient,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
-import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
 import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
 import SettingsStore from "../../../../../settings/SettingsStore";
@@ -22,11 +22,11 @@ import { decorateStartSendingTime, sendRoundTripMetric } from "../../../../../se
 import { doMaybeLocalRoomAction } from "../../../../../utils/local-room";
 import { CHAT_EFFECTS } from "../../../../../effects";
 import { containsEmoji } from "../../../../../effects/utils";
-import { IRoomState } from "../../../../structures/RoomView";
+import { type IRoomState } from "../../../../structures/RoomView";
 import dis from "../../../../../dispatcher/dispatcher";
 import { createRedactEventDialog } from "../../../dialogs/ConfirmRedactDialog";
 import { endEditing, cancelPreviousPendingEdit } from "./editing";
-import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
+import type EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
 import { createMessageContent, EMOTE_PREFIX } from "./createMessageContent";
 import { isContentModified } from "./isContentModified";
 import { CommandCategories, getCommand } from "../../../../../SlashCommands";
diff --git a/src/components/views/rooms/wysiwyg_composer/utils/selection.ts b/src/components/views/rooms/wysiwyg_composer/utils/selection.ts
index 95da5ef66bf6e2be3c0908d2f8ed5cb88b3cbb1d..d8c6213e644262f16275f67b655fa1f7b4a79cb9 100644
--- a/src/components/views/rooms/wysiwyg_composer/utils/selection.ts
+++ b/src/components/views/rooms/wysiwyg_composer/utils/selection.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SubSelection } from "../types";
+import { type SubSelection } from "../types";
 
 export function setSelection(selection: SubSelection): Promise<void> {
     if (selection.anchorNode && selection.focusNode) {
diff --git a/src/components/views/settings/AddPrivilegedUsers.tsx b/src/components/views/settings/AddPrivilegedUsers.tsx
index 5276e825fd7b43c62e5b03b494a4b6ac4cac36de..68a940a65dbf026c9d9d91c9427194619ce880e5 100644
--- a/src/components/views/settings/AddPrivilegedUsers.tsx
+++ b/src/components/views/settings/AddPrivilegedUsers.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FormEvent, useCallback, useContext, useRef, useState } from "react";
-import { Room, EventType } from "matrix-js-sdk/src/matrix";
+import React, { type FormEvent, useCallback, useContext, useRef, useState } from "react";
+import { type Room, EventType } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
-import { ICompletion } from "../../../autocomplete/Autocompleter";
+import { type ICompletion } from "../../../autocomplete/Autocompleter";
 import UserProvider from "../../../autocomplete/UserProvider";
 import { AutocompleteInput } from "../../structures/AutocompleteInput";
 import PowerSelector from "../elements/PowerSelector";
diff --git a/src/components/views/settings/AddRemoveThreepids.tsx b/src/components/views/settings/AddRemoveThreepids.tsx
index 704f2a12927a7bdb6c412b8d44316963306d830f..91df12f45f77d76759fe031b30170e0c330a7671 100644
--- a/src/components/views/settings/AddRemoveThreepids.tsx
+++ b/src/components/views/settings/AddRemoveThreepids.tsx
@@ -9,22 +9,22 @@ Please see LICENSE files in the repository root for full details.
 import React, { useCallback, useRef, useState } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import {
-    IRequestMsisdnTokenResponse,
-    IRequestTokenResponse,
+    type IRequestMsisdnTokenResponse,
+    type IRequestTokenResponse,
     MatrixError,
     ThreepidMedium,
 } from "matrix-js-sdk/src/matrix";
 
-import AddThreepid, { Binding, ThirdPartyIdentifier } from "../../../AddThreepid";
+import AddThreepid, { type Binding, type ThirdPartyIdentifier } from "../../../AddThreepid";
 import { _t, UserFriendlyError } from "../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
 import Modal from "../../../Modal";
 import ErrorDialog, { extractErrorMessageFromError } from "../dialogs/ErrorDialog";
 import Field from "../elements/Field";
 import { looksValid as emailLooksValid } from "../../../email";
 import CountryDropdown from "../auth/CountryDropdown";
-import { PhoneNumberCountryDefinition } from "../../../phonenumber";
+import { type PhoneNumberCountryDefinition } from "../../../phonenumber";
 import InlineSpinner from "../elements/InlineSpinner";
 
 // Whether we're adding 3pids to the user's account on the homeserver or sharing them on an identity server
@@ -40,7 +40,7 @@ interface ExistingThreepidProps {
 const ExistingThreepid: React.FC<ExistingThreepidProps> = ({ mode, threepid, onChange, disabled }) => {
     const [isConfirming, setIsConfirming] = useState(false);
     const client = useMatrixClientContext();
-    const bindTask = useRef<AddThreepid | undefined>();
+    const bindTask = useRef<AddThreepid>(undefined);
 
     const [isVerifyingBind, setIsVerifyingBind] = useState(false);
     const [continueDisabled, setContinueDisabled] = useState(false);
@@ -289,7 +289,7 @@ const AddThreepidSection: React.FC<{ medium: "email" | "msisdn"; disabled?: bool
     disabled,
     onChange,
 }) => {
-    const addTask = useRef<AddThreepid | undefined>();
+    const addTask = useRef<AddThreepid>(undefined);
     const [newThreepidInput, setNewThreepidInput] = useState("");
     const [phoneCountryInput, setPhoneCountryInput] = useState("");
     const [verificationCodeInput, setVerificationCodeInput] = useState("");
diff --git a/src/components/views/settings/AvatarSetting.tsx b/src/components/views/settings/AvatarSetting.tsx
index 2ed4d065687df0f763e0873b746d78e8c191e53c..9992e3691b243b99a5b43b9c216f0271eaa1b9b1 100644
--- a/src/components/views/settings/AvatarSetting.tsx
+++ b/src/components/views/settings/AvatarSetting.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, createRef, useCallback, useEffect, useState, useId } from "react";
+import React, { type JSX, type ReactNode, createRef, useCallback, useEffect, useState, useId } from "react";
 import EditIcon from "@vector-im/compound-design-tokens/assets/web/icons/edit";
 import UploadIcon from "@vector-im/compound-design-tokens/assets/web/icons/share";
 import DeleteIcon from "@vector-im/compound-design-tokens/assets/web/icons/delete";
diff --git a/src/components/views/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx
index ad9088116ccd871469c89de63b736048d26b3b13..5cec9314d7aae32bb7e826060aa771235fb0ae8b 100644
--- a/src/components/views/settings/BridgeTile.tsx
+++ b/src/components/views/settings/BridgeTile.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ReactNode } from "react";
+import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx
index 07a13cd3476cfcfe4ddfd5aa75e97a3b207fc037..548bfb01f5c50e1d6ca825506f26a695be077c8f 100644
--- a/src/components/views/settings/ChangePassword.tsx
+++ b/src/components/views/settings/ChangePassword.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import Field from "../elements/Field";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import AccessibleButton, { AccessibleButtonKind } from "../elements/AccessibleButton";
+import AccessibleButton, { type AccessibleButtonKind } from "../elements/AccessibleButton";
 import Spinner from "../elements/Spinner";
-import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
+import withValidation, { type IFieldState, type IValidationResult } from "../elements/Validation";
 import { UserFriendlyError, _t, _td } from "../../../languageHandler";
 import Modal from "../../../Modal";
 import PassphraseField from "../auth/PassphraseField";
@@ -330,7 +330,9 @@ export default class ChangePassword extends React.Component<IProps, IState> {
                     <form className={this.props.className} onSubmit={this.onClickChange}>
                         <div className={rowClassName}>
                             <Field
-                                ref={(field) => (this[FIELD_OLD_PASSWORD] = field)}
+                                ref={(field) => {
+                                    this[FIELD_OLD_PASSWORD] = field;
+                                }}
                                 type="password"
                                 label={_t("auth|change_password_current_label")}
                                 value={this.state.oldPassword}
@@ -340,7 +342,9 @@ export default class ChangePassword extends React.Component<IProps, IState> {
                         </div>
                         <div className={rowClassName}>
                             <PassphraseField
-                                fieldRef={(field) => (this[FIELD_NEW_PASSWORD] = field)}
+                                fieldRef={(field) => {
+                                    this[FIELD_NEW_PASSWORD] = field;
+                                }}
                                 type="password"
                                 label={_td("auth|change_password_new_label")}
                                 minScore={PASSWORD_MIN_SCORE}
@@ -353,7 +357,9 @@ export default class ChangePassword extends React.Component<IProps, IState> {
                         </div>
                         <div className={rowClassName}>
                             <Field
-                                ref={(field) => (this[FIELD_NEW_PASSWORD_CONFIRM] = field)}
+                                ref={(field) => {
+                                    this[FIELD_NEW_PASSWORD_CONFIRM] = field;
+                                }}
                                 type="password"
                                 label={_t("auth|change_password_confirm_label")}
                                 value={this.state.newPasswordConfirm}
diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx
deleted file mode 100644
index 9ec9e9f6c1881ce111585dfc7f9a9f0ac32cec87..0000000000000000000000000000000000000000
--- a/src/components/views/settings/CrossSigningPanel.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { logger } from "matrix-js-sdk/src/logger";
-import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
-
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { _t } from "../../../languageHandler";
-import Modal from "../../../Modal";
-import Spinner from "../elements/Spinner";
-import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog";
-import ConfirmDestroyCrossSigningDialog from "../dialogs/security/ConfirmDestroyCrossSigningDialog";
-import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog";
-import { accessSecretStorage, withSecretStorageKeyCache } from "../../../SecurityManager";
-import AccessibleButton from "../elements/AccessibleButton";
-import { SettingsSubsectionText } from "./shared/SettingsSubsection";
-
-interface IState {
-    error: boolean;
-    crossSigningPublicKeysOnDevice?: boolean;
-    crossSigningPrivateKeysInStorage?: boolean;
-    masterPrivateKeyCached?: boolean;
-    selfSigningPrivateKeyCached?: boolean;
-    userSigningPrivateKeyCached?: boolean;
-    homeserverSupportsCrossSigning?: boolean;
-    crossSigningReady?: boolean;
-}
-
-export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
-    private unmounted = false;
-
-    public constructor(props: {}) {
-        super(props);
-
-        this.state = {
-            error: false,
-        };
-    }
-
-    public componentDidMount(): void {
-        this.unmounted = false;
-        const cli = MatrixClientPeg.safeGet();
-        cli.on(ClientEvent.AccountData, this.onAccountData);
-        cli.on(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged);
-        cli.on(CryptoEvent.KeysChanged, this.onStatusChanged);
-        this.getUpdatedStatus();
-    }
-
-    public componentWillUnmount(): void {
-        this.unmounted = true;
-        const cli = MatrixClientPeg.get();
-        if (!cli) return;
-        cli.removeListener(ClientEvent.AccountData, this.onAccountData);
-        cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged);
-        cli.removeListener(CryptoEvent.KeysChanged, this.onStatusChanged);
-    }
-
-    private onAccountData = (event: MatrixEvent): void => {
-        const type = event.getType();
-        if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) {
-            this.getUpdatedStatus();
-        }
-    };
-
-    private onBootstrapClick = (): void => {
-        if (this.state.crossSigningPrivateKeysInStorage) {
-            Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true);
-        } else {
-            // Trigger the flow to set up secure backup, which is what this will do when in
-            // the appropriate state.
-            accessSecretStorage();
-        }
-    };
-
-    private onStatusChanged = (): void => {
-        this.getUpdatedStatus();
-    };
-
-    private async getUpdatedStatus(): Promise<void> {
-        const cli = MatrixClientPeg.safeGet();
-        const crypto = cli.getCrypto();
-        if (!crypto) return;
-
-        const crossSigningStatus = await crypto.getCrossSigningStatus();
-        const crossSigningPublicKeysOnDevice = crossSigningStatus.publicKeysOnDevice;
-        const crossSigningPrivateKeysInStorage = crossSigningStatus.privateKeysInSecretStorage;
-        const masterPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.masterKey;
-        const selfSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.selfSigningKey;
-        const userSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.userSigningKey;
-        const homeserverSupportsCrossSigning =
-            await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
-        const crossSigningReady = await crypto.isCrossSigningReady();
-
-        this.setState({
-            crossSigningPublicKeysOnDevice,
-            crossSigningPrivateKeysInStorage,
-            masterPrivateKeyCached,
-            selfSigningPrivateKeyCached,
-            userSigningPrivateKeyCached,
-            homeserverSupportsCrossSigning,
-            crossSigningReady,
-        });
-    }
-
-    /**
-     * Reset the user's cross-signing keys.
-     */
-    private async resetCrossSigning(): Promise<void> {
-        this.setState({ error: false });
-        try {
-            const cli = MatrixClientPeg.safeGet();
-            await withSecretStorageKeyCache(async () => {
-                await cli.getCrypto()!.bootstrapCrossSigning({
-                    authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
-                        const { finished } = Modal.createDialog(InteractiveAuthDialog, {
-                            title: _t("encryption|bootstrap_title"),
-                            matrixClient: cli,
-                            makeRequest,
-                        });
-                        const [confirmed] = await finished;
-                        if (!confirmed) {
-                            throw new Error("Cross-signing key upload auth canceled");
-                        }
-                    },
-                    setupNewCrossSigning: true,
-                });
-            });
-        } catch (e) {
-            this.setState({ error: true });
-            logger.error("Error bootstrapping cross-signing", e);
-        }
-        if (this.unmounted) return;
-        this.getUpdatedStatus();
-    }
-
-    /**
-     * Callback for when the user clicks the "reset cross signing" button.
-     *
-     * Shows a confirmation dialog, and then does the reset if confirmed.
-     */
-    private onResetCrossSigningClick = (): void => {
-        Modal.createDialog(ConfirmDestroyCrossSigningDialog, {
-            onFinished: async (act) => {
-                if (!act) return;
-                this.resetCrossSigning();
-            },
-        });
-    };
-
-    public render(): React.ReactNode {
-        const {
-            error,
-            crossSigningPublicKeysOnDevice,
-            crossSigningPrivateKeysInStorage,
-            masterPrivateKeyCached,
-            selfSigningPrivateKeyCached,
-            userSigningPrivateKeyCached,
-            homeserverSupportsCrossSigning,
-            crossSigningReady,
-        } = this.state;
-
-        let errorSection;
-        if (error) {
-            errorSection = <div className="error">{error.toString()}</div>;
-        }
-
-        let summarisedStatus;
-        if (homeserverSupportsCrossSigning === undefined) {
-            summarisedStatus = <Spinner />;
-        } else if (!homeserverSupportsCrossSigning) {
-            summarisedStatus = (
-                <SettingsSubsectionText data-testid="summarised-status">
-                    {_t("encryption|cross_signing_unsupported")}
-                </SettingsSubsectionText>
-            );
-        } else if (crossSigningReady && crossSigningPrivateKeysInStorage) {
-            summarisedStatus = (
-                <SettingsSubsectionText data-testid="summarised-status">
-                    ✅ {_t("encryption|cross_signing_ready")}
-                </SettingsSubsectionText>
-            );
-        } else if (crossSigningReady && !crossSigningPrivateKeysInStorage) {
-            summarisedStatus = (
-                <SettingsSubsectionText data-testid="summarised-status">
-                    ⚠️ {_t("encryption|cross_signing_ready_no_backup")}
-                </SettingsSubsectionText>
-            );
-        } else if (crossSigningPrivateKeysInStorage) {
-            summarisedStatus = (
-                <SettingsSubsectionText data-testid="summarised-status">
-                    {_t("encryption|cross_signing_untrusted")}
-                </SettingsSubsectionText>
-            );
-        } else {
-            summarisedStatus = (
-                <SettingsSubsectionText data-testid="summarised-status">
-                    {_t("encryption|cross_signing_not_ready")}
-                </SettingsSubsectionText>
-            );
-        }
-
-        const keysExistAnywhere =
-            crossSigningPublicKeysOnDevice ||
-            crossSigningPrivateKeysInStorage ||
-            masterPrivateKeyCached ||
-            selfSigningPrivateKeyCached ||
-            userSigningPrivateKeyCached;
-        const keysExistEverywhere =
-            crossSigningPublicKeysOnDevice &&
-            crossSigningPrivateKeysInStorage &&
-            masterPrivateKeyCached &&
-            selfSigningPrivateKeyCached &&
-            userSigningPrivateKeyCached;
-
-        const actions: JSX.Element[] = [];
-
-        // TODO: determine how better to expose this to users in addition to prompts at login/toast
-        if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
-            let buttonCaption = _t("encryption|set_up_toast_title");
-            if (crossSigningPrivateKeysInStorage) {
-                buttonCaption = _t("encryption|verify_toast_title");
-            }
-            actions.push(
-                <AccessibleButton key="setup" kind="primary_outline" onClick={this.onBootstrapClick}>
-                    {buttonCaption}
-                </AccessibleButton>,
-            );
-        }
-
-        if (keysExistAnywhere) {
-            actions.push(
-                <AccessibleButton key="reset" kind="danger_outline" onClick={this.onResetCrossSigningClick}>
-                    {_t("action|reset")}
-                </AccessibleButton>,
-            );
-        }
-
-        let actionRow;
-        if (actions.length) {
-            actionRow = <div className="mx_CrossSigningPanel_buttonRow">{actions}</div>;
-        }
-
-        return (
-            <>
-                {summarisedStatus}
-                <details>
-                    <summary className="mx_CrossSigningPanel_advanced">{_t("common|advanced")}</summary>
-                    <table className="mx_CrossSigningPanel_statusList">
-                        <tbody>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_public_keys")}</th>
-                                <td>
-                                    {crossSigningPublicKeysOnDevice
-                                        ? _t("settings|security|cross_signing_in_memory")
-                                        : _t("settings|security|cross_signing_not_found")}
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_private_keys")}</th>
-                                <td>
-                                    {crossSigningPrivateKeysInStorage
-                                        ? _t("settings|security|cross_signing_in_4s")
-                                        : _t("settings|security|cross_signing_not_in_4s")}
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_master_private_Key")}</th>
-                                <td>
-                                    {masterPrivateKeyCached
-                                        ? _t("settings|security|cross_signing_cached")
-                                        : _t("settings|security|cross_signing_not_cached")}
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_self_signing_private_key")}</th>
-                                <td>
-                                    {selfSigningPrivateKeyCached
-                                        ? _t("settings|security|cross_signing_cached")
-                                        : _t("settings|security|cross_signing_not_cached")}
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_user_signing_private_key")}</th>
-                                <td>
-                                    {userSigningPrivateKeyCached
-                                        ? _t("settings|security|cross_signing_cached")
-                                        : _t("settings|security|cross_signing_not_cached")}
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|cross_signing_homeserver_support")}</th>
-                                <td>
-                                    {homeserverSupportsCrossSigning
-                                        ? _t("settings|security|cross_signing_homeserver_support_exists")
-                                        : _t("settings|security|cross_signing_not_found")}
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
-                </details>
-                {errorSection}
-                {actionRow}
-            </>
-        );
-    }
-}
diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx
deleted file mode 100644
index beb08ab1e92b936241d4e043fbb5e13f1c6db8be..0000000000000000000000000000000000000000
--- a/src/components/views/settings/CryptographyPanel.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2021 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { lazy } from "react";
-import { logger } from "matrix-js-sdk/src/logger";
-
-import { _t } from "../../../languageHandler";
-import Modal from "../../../Modal";
-import AccessibleButton from "../elements/AccessibleButton";
-import * as FormattingUtils from "../../../utils/FormattingUtils";
-import SettingsStore from "../../../settings/SettingsStore";
-import SettingsFlag from "../elements/SettingsFlag";
-import { SettingLevel } from "../../../settings/SettingLevel";
-import { SettingsSubsection, SettingsSubsectionText } from "./shared/SettingsSubsection";
-import MatrixClientContext from "../../../contexts/MatrixClientContext";
-
-interface IProps {}
-
-interface IState {
-    /** The device's base64-encoded Ed25519 identity key, or:
-     *
-     * * `undefined`: not yet loaded
-     * * `null`: encryption is not supported (or the crypto stack was not correctly initialized)
-     */
-    deviceIdentityKey: string | undefined | null;
-}
-
-export default class CryptographyPanel extends React.Component<IProps, IState> {
-    public static contextType = MatrixClientContext;
-    declare public context: React.ContextType<typeof MatrixClientContext>;
-
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props);
-
-        if (!context.getCrypto()) {
-            this.state = { deviceIdentityKey: null };
-        } else {
-            this.state = { deviceIdentityKey: undefined };
-        }
-    }
-
-    public componentDidMount(): void {
-        if (this.state.deviceIdentityKey === undefined) {
-            this.context
-                .getCrypto()
-                ?.getOwnDeviceKeys()
-                .then((keys) => {
-                    this.setState({ deviceIdentityKey: keys.ed25519 });
-                })
-                .catch((e) => {
-                    logger.error(`CryptographyPanel: Error fetching own device keys: ${e}`);
-                    this.setState({ deviceIdentityKey: null });
-                });
-        }
-    }
-
-    public render(): React.ReactNode {
-        const client = this.context;
-        const deviceId = client.deviceId;
-        let identityKey = this.state.deviceIdentityKey;
-        if (identityKey === undefined) {
-            // Should show a spinner here really, but since this will be very transitional, I can't be doing with the
-            // necessary styling.
-            identityKey = "...";
-        } else if (identityKey === null) {
-            identityKey = _t("encryption|not_supported");
-        } else {
-            identityKey = FormattingUtils.formatCryptoKey(identityKey);
-        }
-
-        let importExportButtons: JSX.Element | undefined;
-        if (client.getCrypto()) {
-            importExportButtons = (
-                <div className="mx_CryptographyPanel_importExportButtons">
-                    <AccessibleButton kind="primary_outline" onClick={this.onExportE2eKeysClicked}>
-                        {_t("settings|security|export_megolm_keys")}
-                    </AccessibleButton>
-                    <AccessibleButton kind="primary_outline" onClick={this.onImportE2eKeysClicked}>
-                        {_t("settings|security|import_megolm_keys")}
-                    </AccessibleButton>
-                </div>
-            );
-        }
-
-        let noSendUnverifiedSetting: JSX.Element | undefined;
-        if (SettingsStore.canSetValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE)) {
-            noSendUnverifiedSetting = (
-                <SettingsFlag
-                    name="blacklistUnverifiedDevices"
-                    level={SettingLevel.DEVICE}
-                    onChange={this.updateBlacklistDevicesFlag}
-                />
-            );
-        }
-
-        return (
-            <SettingsSubsection heading={_t("settings|security|cryptography_section")}>
-                <SettingsSubsectionText>
-                    <table className="mx_CryptographyPanel_sessionInfo">
-                        <tbody>
-                            <tr>
-                                <th scope="row">{_t("settings|security|session_id")}</th>
-                                <td>
-                                    <code>{deviceId}</code>
-                                </td>
-                            </tr>
-                            <tr>
-                                <th scope="row">{_t("settings|security|session_key")}</th>
-                                <td>
-                                    <code>
-                                        <strong>{identityKey}</strong>
-                                    </code>
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
-                </SettingsSubsectionText>
-                {importExportButtons}
-                {noSendUnverifiedSetting}
-            </SettingsSubsection>
-        );
-    }
-
-    private onExportE2eKeysClicked = (): void => {
-        Modal.createDialog(
-            lazy(() => import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog")),
-            { matrixClient: this.context },
-        );
-    };
-
-    private onImportE2eKeysClicked = (): void => {
-        Modal.createDialog(
-            lazy(() => import("../../../async-components/views/dialogs/security/ImportE2eKeysDialog")),
-            { matrixClient: this.context },
-        );
-    };
-
-    private updateBlacklistDevicesFlag = (checked: boolean): void => {
-        const crypto = this.context.getCrypto();
-        if (crypto) crypto.globalBlacklistUnverifiedDevices = checked;
-    };
-}
diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx
index 41845eb94ea63946e4b279a962849b16da1348b0..fe19aa058032ef0976ff1ee56ce07cf438709fa2 100644
--- a/src/components/views/settings/EventIndexPanel.tsx
+++ b/src/components/views/settings/EventIndexPanel.tsx
@@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { lazy } from "react";
+import React, { type JSX, lazy } from "react";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import SdkConfig from "../../../SdkConfig";
@@ -28,8 +29,8 @@ interface IState {
     eventIndexingEnabled: boolean;
 }
 
-export default class EventIndexPanel extends React.Component<{}, IState> {
-    public constructor(props: {}) {
+export default class EventIndexPanel extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
@@ -118,15 +119,14 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
     };
 
     private confirmEventStoreReset = (): void => {
-        const { close } = Modal.createDialog(SeshatResetDialog, {
-            onFinished: async (success): Promise<void> => {
-                if (success) {
-                    await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, false);
-                    await EventIndexPeg.deleteEventIndex();
-                    await this.onEnable();
-                    close();
-                }
-            },
+        const { finished, close } = Modal.createDialog(SeshatResetDialog);
+        finished.then(async ([success]) => {
+            if (success) {
+                await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, false);
+                await EventIndexPeg.deleteEventIndex();
+                await this.onEnable();
+                close();
+            }
         });
     };
 
diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx
index e1a7f4902fe207e97c1768a4ab46f5d023887309..f1151a426904b418eb78c2f844657b5934702e15 100644
--- a/src/components/views/settings/FontScalingPanel.tsx
+++ b/src/components/views/settings/FontScalingPanel.tsx
@@ -7,10 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import EventTilePreview from "../elements/EventTilePreview";
 import SettingsStore from "../../../settings/SettingsStore";
-import { Layout } from "../../../settings/enums/Layout";
+import { type Layout } from "../../../settings/enums/Layout";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import { _t } from "../../../languageHandler";
@@ -18,8 +19,6 @@ import { SettingsSubsection } from "./shared/SettingsSubsection";
 import Field from "../elements/Field";
 import { FontWatcher } from "../../../settings/watchers/FontWatcher";
 
-interface IProps {}
-
 interface IState {
     browserFontSize: number;
     // String displaying the current selected fontSize.
@@ -34,7 +33,7 @@ interface IState {
     avatarUrl?: string;
 }
 
-export default class FontScalingPanel extends React.Component<IProps, IState> {
+export default class FontScalingPanel extends React.Component<EmptyObject, IState> {
     private readonly MESSAGE_PREVIEW_TEXT = _t("common|preview_message");
     /**
      * Font sizes available (in px)
@@ -43,7 +42,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
     private layoutWatcherRef?: string;
     private unmounted = false;
 
-    public constructor(props: IProps) {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/views/settings/ImageSizePanel.tsx b/src/components/views/settings/ImageSizePanel.tsx
index 8079ea16543cab3535ff2b21b8f5395933e4982c..fea7ec362e1bd909d9df48bf08bb4bb9bbca1022 100644
--- a/src/components/views/settings/ImageSizePanel.tsx
+++ b/src/components/views/settings/ImageSizePanel.tsx
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import SettingsStore from "../../../settings/SettingsStore";
 import StyledRadioButton from "../elements/StyledRadioButton";
@@ -15,16 +16,12 @@ import { SettingLevel } from "../../../settings/SettingLevel";
 import { ImageSize } from "../../../settings/enums/ImageSize";
 import { SettingsSubsection } from "./shared/SettingsSubsection";
 
-interface IProps {
-    // none
-}
-
 interface IState {
     size: ImageSize;
 }
 
-export default class ImageSizePanel extends React.Component<IProps, IState> {
-    public constructor(props: IProps) {
+export default class ImageSizePanel extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx
index 28a5130b25bed7454e8438881326d7d7f6610e87..bb6893551ad419b5a0961de86bf7b868be3a1d47 100644
--- a/src/components/views/settings/IntegrationManager.tsx
+++ b/src/components/views/settings/IntegrationManager.tsx
@@ -10,7 +10,7 @@ import React from "react";
 
 import { _t } from "../../../languageHandler";
 import dis from "../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import Spinner from "../elements/Spinner";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx
index 023b68c282c6dd56d53ab26e41ac0e0d29d3a509..43f8ab9ff6ad97d2ac217008a40168da2848ff23 100644
--- a/src/components/views/settings/JoinRuleSettings.tsx
+++ b/src/components/views/settings/JoinRuleSettings.tsx
@@ -6,25 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useEffect, useState } from "react";
-import { JoinRule, RestrictedAllowType, Room, EventType, Visibility } from "matrix-js-sdk/src/matrix";
-import { RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
+import React, { type JSX, type ReactNode, useEffect, useState } from "react";
+import { JoinRule, RestrictedAllowType, type Room, EventType, Visibility } from "matrix-js-sdk/src/matrix";
+import { type RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";
 
-import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup";
+import StyledRadioGroup, { type IDefinition } from "../elements/StyledRadioGroup";
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
 import RoomAvatar from "../avatars/RoomAvatar";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
 import Modal from "../../../Modal";
 import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog";
-import RoomUpgradeWarningDialog, { IFinishedOpts } from "../dialogs/RoomUpgradeWarningDialog";
+import RoomUpgradeWarningDialog, { type IFinishedOpts } from "../dialogs/RoomUpgradeWarningDialog";
 import { upgradeRoom } from "../../../utils/RoomUpgrade";
 import { arrayHasDiff } from "../../../utils/arrays";
 import { useLocalEcho } from "../../../hooks/useLocalEcho";
 import dis from "../../../dispatcher/dispatcher";
 import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
 import { Action } from "../../../dispatcher/actions";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { doesRoomVersionSupport, PreferredRoomVersions } from "../../../utils/PreferredRoomVersions";
 import SettingsStore from "../../../settings/SettingsStore";
 import LabelledCheckbox from "../elements/LabelledCheckbox";
@@ -36,6 +36,9 @@ export interface JoinRuleSettingsProps {
     onError(error: unknown): void;
     beforeChange?(joinRule: JoinRule): Promise<boolean>; // if returns false then aborts the change
     aliasWarning?: ReactNode;
+    disabledOptions?: Set<JoinRule>;
+    hiddenOptions?: Set<JoinRule>;
+    recommendedOption?: JoinRule;
 }
 
 const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
@@ -45,6 +48,9 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
     onError,
     beforeChange,
     closeSettingsFn,
+    disabledOptions,
+    hiddenOptions,
+    recommendedOption,
 }) => {
     const cli = room.client;
 
@@ -147,7 +153,7 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
                     }
                 });
 
-                closeSettingsFn();
+                closeSettingsFn?.();
 
                 // switch to the new room in the background
                 dis.dispatch<ViewRoomPayload>({
@@ -170,18 +176,26 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
             {_t("room_settings|security|join_rule_upgrade_required")}
         </span>
     );
+    const withRecommendLabel = (label: string, rule: JoinRule): React.ReactNode =>
+        rule === recommendedOption ? (
+            <>
+                {label} (<span className="mx_JoinRuleSettings_recommended">{_t("common|recommended")}</span>)
+            </>
+        ) : (
+            label
+        );
 
     const definitions: IDefinition<JoinRule>[] = [
         {
             value: JoinRule.Invite,
-            label: _t("room_settings|security|join_rule_invite"),
+            label: withRecommendLabel(_t("room_settings|security|join_rule_invite"), JoinRule.Invite),
             description: _t("room_settings|security|join_rule_invite_description"),
             checked:
                 joinRule === JoinRule.Invite || (joinRule === JoinRule.Restricted && !restrictedAllowRoomIds?.length),
         },
         {
             value: JoinRule.Public,
-            label: _t("common|public"),
+            label: withRecommendLabel(_t("common|public"), JoinRule.Public),
             description: (
                 <>
                     {_t("room_settings|security|join_rule_public_description")}
@@ -292,7 +306,7 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
             value: JoinRule.Restricted,
             label: (
                 <>
-                    {_t("room_settings|security|join_rule_restricted")}
+                    {withRecommendLabel(_t("room_settings|security|join_rule_restricted"), JoinRule.Restricted)}
                     {preferredRestrictionVersion && upgradeRequiredPill}
                 </>
             ),
@@ -303,11 +317,11 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
     }
 
     if (askToJoinEnabled && (roomSupportsKnock || preferredKnockVersion)) {
-        definitions.push({
+        definitions.splice(Math.max(0, definitions.length - 1), 0, {
             value: JoinRule.Knock,
             label: (
                 <>
-                    {_t("room_settings|security|join_rule_knock")}
+                    {withRecommendLabel(_t("room_settings|security|join_rule_knock"), JoinRule.Knock)}
                     {preferredKnockVersion && upgradeRequiredPill}
                 </>
             ),
@@ -397,7 +411,9 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
             name="joinRule"
             value={joinRule}
             onChange={onChange}
-            definitions={definitions}
+            definitions={definitions
+                .map((d) => (disabledOptions?.has(d.value) ? { ...d, disabled: true } : d))
+                .filter((d) => !hiddenOptions?.has(d.value))}
             disabled={disabled}
             className="mx_JoinRuleSettings_radioButton"
         />
diff --git a/src/components/views/settings/KeyboardShortcut.tsx b/src/components/views/settings/KeyboardShortcut.tsx
index f84c89208ae29c134e0718de28c1b27b94520cef..8d7dd7cdb3edc28f85181b8d7ddd8e12e4ccf3d8 100644
--- a/src/components/views/settings/KeyboardShortcut.tsx
+++ b/src/components/views/settings/KeyboardShortcut.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { ALTERNATE_KEY_NAME, KEY_ICON } from "../../../accessibility/KeyboardShortcuts";
-import { KeyCombo } from "../../../KeyBindingsManager";
+import { type KeyCombo } from "../../../KeyBindingsManager";
 import { IS_MAC, Key } from "../../../Keyboard";
 import { _t } from "../../../languageHandler";
 
diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx
index 075c92fce775e236f7b2685d33141b8b20f18e88..9df3968859d63d9ee9b33afaff7cbfb261ba9d71 100644
--- a/src/components/views/settings/LayoutSwitcher.tsx
+++ b/src/components/views/settings/LayoutSwitcher.tsx
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, useEffect, useState } from "react";
+import React, { type JSX, useEffect, useState } from "react";
 import { Field, HelpMessage, InlineField, Label, RadioControl, Root, ToggleControl } from "@vector-im/compound-web";
 
 import { SettingsSubsection } from "./shared/SettingsSubsection";
diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx
index 6cf4f8abea9509c4a92e85cf61576b6525e1d6f4..e337b6b3cea679faadb8f7719672ff7cf3d429e6 100644
--- a/src/components/views/settings/Notifications.tsx
+++ b/src/components/views/settings/Notifications.tsx
@@ -6,16 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import {
-    IAnnotatedPushRule,
-    IPusher,
-    PushRuleAction,
+    type IAnnotatedPushRule,
+    type IPusher,
+    type PushRuleAction,
     PushRuleKind,
     RuleId,
-    IThreepid,
+    type IThreepid,
     ThreepidMedium,
-    LocalNotificationSettings,
+    type LocalNotificationSettings,
+    type EmptyObject,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -23,13 +24,13 @@ import Spinner from "../elements/Spinner";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
 import {
     ContentRules,
-    IContentRules,
+    type IContentRules,
     PushRuleVectorState,
     VectorPushRulesDefinitions,
     VectorState,
+    type VectorPushRuleDefinition,
 } from "../../../notifications";
-import type { VectorPushRuleDefinition } from "../../../notifications";
-import { _t, TranslatedString } from "../../../languageHandler";
+import { _t, type TranslatedString } from "../../../languageHandler";
 import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
 import SettingsStore from "../../../settings/SettingsStore";
 import StyledRadioButton from "../elements/StyledRadioButton";
@@ -108,8 +109,6 @@ interface IVectorPushRule {
     syncedVectorState?: VectorState;
 }
 
-interface IProps {}
-
 interface IState {
     phase: Phase;
 
@@ -205,10 +204,10 @@ const NotificationActivitySettings = (): JSX.Element => {
 /**
  * The old, deprecated notifications tab view, only displayed if the user has the labs flag disabled.
  */
-export default class Notifications extends React.PureComponent<IProps, IState> {
+export default class Notifications extends React.PureComponent<EmptyObject, IState> {
     private settingWatchers: string[] = [];
 
-    public constructor(props: IProps) {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
@@ -255,7 +254,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
         this.settingWatchers.forEach((watcher) => SettingsStore.unwatchSetting(watcher));
     }
 
-    public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
+    public componentDidUpdate(prevProps: Readonly<EmptyObject>, prevState: Readonly<IState>): void {
         if (this.state.deviceNotificationsEnabled !== prevState.deviceNotificationsEnabled) {
             this.persistLocalNotificationSettings(this.state.deviceNotificationsEnabled);
         }
@@ -291,7 +290,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
         }
     }
 
-    private persistLocalNotificationSettings(enabled: boolean): Promise<{}> {
+    private persistLocalNotificationSettings(enabled: boolean): Promise<EmptyObject> {
         const cli = MatrixClientPeg.safeGet();
         return cli.setAccountData(getLocalNotificationAccountDataEventType(cli.deviceId), {
             is_silenced: !enabled,
diff --git a/src/components/views/settings/PowerLevelSelector.tsx b/src/components/views/settings/PowerLevelSelector.tsx
index 5bc2da9f92bf85da41c4304fe81e70368627ab64..0b570c151699e9d8414bc6dc9655631eb49544c0 100644
--- a/src/components/views/settings/PowerLevelSelector.tsx
+++ b/src/components/views/settings/PowerLevelSelector.tsx
@@ -6,13 +6,15 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { useState, JSX, PropsWithChildren } from "react";
+import React, { useState, type JSX, type PropsWithChildren } from "react";
 import { Button } from "@vector-im/compound-web";
 
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
 import PowerSelector from "../elements/PowerSelector";
 import { _t } from "../../../languageHandler";
 import SettingsFieldset from "./SettingsFieldset";
+import Modal from "../../../Modal";
+import QuestionDialog from "../dialogs/QuestionDialog";
 
 /**
  * Display in a fieldset, the power level of the users and allow to change them.
@@ -77,6 +79,13 @@ export function PowerLevelSelector({
     // No user to display, we return the children into fragment to convert it to JSX.Element type
     if (!users.length) return <>{children}</>;
 
+    // check at least one admin in the list
+    const roomHasAtLeastOneAdmin = (usersLevels: Record<string, number>): boolean => {
+        const userLevelValues = Object.values(usersLevels);
+        // At least one user as the pL 100 which means he is admin
+        return userLevelValues.some((uL) => uL === 100);
+    };
+
     return (
         <SettingsFieldset legend={title}>
             {users.map((userId) => {
@@ -96,7 +105,24 @@ export function PowerLevelSelector({
                         disabled={!canChange}
                         label={userId}
                         key={userId}
-                        onChange={(value) => setCurrentPowerLevel({ value, userId })}
+                        onChange={async (value) => {
+                            const userLevelsTmp = Object.assign({}, userLevels);
+                            userLevelsTmp[userId] = value;
+                            if (!roomHasAtLeastOneAdmin(userLevelsTmp)) {
+                                const { finished } = Modal.createDialog(QuestionDialog, {
+                                    title: _t("common|warning"),
+                                    description: <div>{_t("user_info|demote_self_confirm_room")}</div>,
+                                    button: _t("action|continue"),
+                                });
+                                const [confirmed] = await finished;
+                                if (!confirmed) {
+                                    // if cancel, we reput initial value
+                                    setCurrentPowerLevel({ value: userLevels[userId], userId });
+                                    return;
+                                }
+                            }
+                            setCurrentPowerLevel({ value, userId });
+                        }}
                     />
                 );
             })}
diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx
deleted file mode 100644
index 3d245678320197bf0b80d55918796853ffecf091..0000000000000000000000000000000000000000
--- a/src/components/views/settings/SecureBackupPanel.tsx
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
-Copyright 2018 New Vector Ltd
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { lazy, ReactNode } from "react";
-import { CryptoEvent, BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
-import { logger } from "matrix-js-sdk/src/logger";
-
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { _t } from "../../../languageHandler";
-import Modal from "../../../Modal";
-import { isSecureBackupRequired } from "../../../utils/WellKnownUtils";
-import Spinner from "../elements/Spinner";
-import AccessibleButton from "../elements/AccessibleButton";
-import QuestionDialog from "../dialogs/QuestionDialog";
-import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog";
-import { accessSecretStorage } from "../../../SecurityManager";
-import { SettingsSubsectionText } from "./shared/SettingsSubsection";
-
-interface IState {
-    loading: boolean;
-    error: boolean;
-    backupKeyStored: boolean | null;
-    backupKeyCached: boolean | null;
-    backupKeyWellFormed: boolean | null;
-    secretStorageKeyInAccount: boolean | null;
-    secretStorageReady: boolean | null;
-
-    /** Information on the current key backup version, as returned by the server.
-     *
-     * `null` could mean any of:
-     *    * we haven't yet requested the data from the server.
-     *    * we were unable to reach the server.
-     *    * the server returned key backup version data we didn't understand or was malformed.
-     *    * there is actually no backup on the server.
-     */
-    backupInfo: KeyBackupInfo | null;
-
-    /**
-     * Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
-     * decrypt it.
-     *
-     * `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
-     */
-    backupTrustInfo: BackupTrustInfo | undefined;
-
-    /**
-     * If key backup is currently enabled, the backup version we are backing up to.
-     */
-    activeBackupVersion: string | null;
-
-    /**
-     * Number of sessions remaining to be backed up. `null` if we have no information on this.
-     */
-    sessionsRemaining: number | null;
-}
-
-export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
-    private unmounted = false;
-
-    public constructor(props: {}) {
-        super(props);
-
-        this.state = {
-            loading: true,
-            error: false,
-            backupKeyStored: null,
-            backupKeyCached: null,
-            backupKeyWellFormed: null,
-            secretStorageKeyInAccount: null,
-            secretStorageReady: null,
-            backupInfo: null,
-            backupTrustInfo: undefined,
-            activeBackupVersion: null,
-            sessionsRemaining: null,
-        };
-    }
-
-    public componentDidMount(): void {
-        this.unmounted = false;
-        this.loadBackupStatus();
-
-        MatrixClientPeg.safeGet().on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
-        MatrixClientPeg.safeGet().on(CryptoEvent.KeyBackupSessionsRemaining, this.onKeyBackupSessionsRemaining);
-    }
-
-    public componentWillUnmount(): void {
-        this.unmounted = true;
-
-        if (MatrixClientPeg.get()) {
-            MatrixClientPeg.get()!.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
-            MatrixClientPeg.get()!.removeListener(
-                CryptoEvent.KeyBackupSessionsRemaining,
-                this.onKeyBackupSessionsRemaining,
-            );
-        }
-    }
-
-    private onKeyBackupSessionsRemaining = (sessionsRemaining: number): void => {
-        this.setState({
-            sessionsRemaining,
-        });
-    };
-
-    private onKeyBackupStatus = (): void => {
-        // This just loads the current backup status rather than forcing
-        // a re-check otherwise we risk causing infinite loops
-        this.loadBackupStatus();
-    };
-
-    private async loadBackupStatus(): Promise<void> {
-        this.setState({ loading: true });
-        this.getUpdatedDiagnostics();
-        try {
-            const cli = MatrixClientPeg.safeGet();
-            const backupInfo = (await cli.getCrypto()?.getKeyBackupInfo()) ?? null;
-            const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
-
-            const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;
-
-            if (this.unmounted) return;
-            this.setState({
-                loading: false,
-                error: false,
-                backupInfo,
-                backupTrustInfo,
-                activeBackupVersion,
-            });
-        } catch (e) {
-            logger.log("Unable to fetch key backup status", e);
-            if (this.unmounted) return;
-            this.setState({
-                loading: false,
-                error: true,
-                backupInfo: null,
-                backupTrustInfo: undefined,
-                activeBackupVersion: null,
-            });
-        }
-    }
-
-    private async getUpdatedDiagnostics(): Promise<void> {
-        const cli = MatrixClientPeg.safeGet();
-        const crypto = cli.getCrypto();
-        if (!crypto) return;
-
-        const secretStorage = cli.secretStorage;
-
-        const backupKeyStored = !!(await cli.isKeyBackupKeyStored());
-        const backupKeyFromCache = await crypto.getSessionBackupPrivateKey();
-        const backupKeyCached = !!backupKeyFromCache;
-        const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
-        const secretStorageKeyInAccount = await secretStorage.hasKey();
-        const secretStorageReady = await crypto.isSecretStorageReady();
-
-        if (this.unmounted) return;
-        this.setState({
-            backupKeyStored,
-            backupKeyCached,
-            backupKeyWellFormed,
-            secretStorageKeyInAccount,
-            secretStorageReady,
-        });
-    }
-
-    private startNewBackup = (): void => {
-        Modal.createDialog(
-            lazy(() => import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog")),
-            {
-                onFinished: () => {
-                    this.loadBackupStatus();
-                },
-            },
-            undefined,
-            /* priority = */ false,
-            /* static = */ true,
-        );
-    };
-
-    private deleteBackup = (): void => {
-        Modal.createDialog(QuestionDialog, {
-            title: _t("settings|security|delete_backup"),
-            description: _t("settings|security|delete_backup_confirm_description"),
-            button: _t("settings|security|delete_backup"),
-            danger: true,
-            onFinished: (proceed) => {
-                if (!proceed) return;
-                this.setState({ loading: true });
-                const versionToDelete = this.state.backupInfo!.version!;
-                // deleteKeyBackupVersion fires a key backup status event
-                // which will update the UI
-                MatrixClientPeg.safeGet().getCrypto()?.deleteKeyBackupVersion(versionToDelete);
-            },
-        });
-    };
-
-    private restoreBackup = async (): Promise<void> => {
-        Modal.createDialog(RestoreKeyBackupDialog, undefined, undefined, /* priority = */ false, /* static = */ true);
-    };
-
-    private resetSecretStorage = async (): Promise<void> => {
-        this.setState({ error: false });
-        try {
-            await accessSecretStorage(async (): Promise<void> => {}, { forceReset: true });
-        } catch (e) {
-            logger.error("Error resetting secret storage", e);
-            if (this.unmounted) return;
-            this.setState({ error: true });
-        }
-        if (this.unmounted) return;
-        this.loadBackupStatus();
-    };
-
-    public render(): React.ReactNode {
-        const {
-            loading,
-            error,
-            backupKeyStored,
-            backupKeyCached,
-            backupKeyWellFormed,
-            secretStorageKeyInAccount,
-            secretStorageReady,
-            backupInfo,
-            backupTrustInfo,
-            sessionsRemaining,
-        } = this.state;
-
-        let statusDescription: JSX.Element;
-        let extraDetailsTableRows: JSX.Element | undefined;
-        let extraDetails: JSX.Element | undefined;
-        const actions: JSX.Element[] = [];
-        if (error) {
-            statusDescription = (
-                <SettingsSubsectionText className="error">
-                    {_t("settings|security|error_loading_key_backup_status")}
-                </SettingsSubsectionText>
-            );
-        } else if (loading) {
-            statusDescription = <Spinner />;
-        } else if (backupInfo) {
-            let restoreButtonCaption = _t("settings|security|restore_key_backup");
-
-            if (this.state.activeBackupVersion !== null) {
-                statusDescription = (
-                    <SettingsSubsectionText>✅ {_t("settings|security|key_backup_active")}</SettingsSubsectionText>
-                );
-            } else {
-                statusDescription = (
-                    <>
-                        <SettingsSubsectionText>
-                            {_t("settings|security|key_backup_inactive", {}, { b: (sub) => <strong>{sub}</strong> })}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|security|key_backup_connect_prompt")}
-                        </SettingsSubsectionText>
-                    </>
-                );
-                restoreButtonCaption = _t("settings|security|key_backup_connect");
-            }
-
-            let uploadStatus: ReactNode;
-            if (sessionsRemaining === null) {
-                // No upload status to show when backup disabled.
-                uploadStatus = "";
-            } else if (sessionsRemaining > 0) {
-                uploadStatus = (
-                    <div>
-                        {_t("settings|security|key_backup_in_progress", { sessionsRemaining })} <br />
-                    </div>
-                );
-            } else {
-                uploadStatus = (
-                    <div>
-                        {_t("settings|security|key_backup_complete")} <br />
-                    </div>
-                );
-            }
-
-            let trustedLocally: string | undefined;
-            if (backupTrustInfo?.matchesDecryptionKey) {
-                trustedLocally = _t("settings|security|key_backup_can_be_restored");
-            }
-
-            extraDetailsTableRows = (
-                <>
-                    <tr>
-                        <th scope="row">{_t("settings|security|key_backup_latest_version")}</th>
-                        <td>
-                            {backupInfo.version} ({_t("settings|security|key_backup_algorithm")}{" "}
-                            <code>{backupInfo.algorithm}</code>)
-                        </td>
-                    </tr>
-                    <tr>
-                        <th scope="row">{_t("settings|security|key_backup_active_version")}</th>
-                        <td>
-                            {this.state.activeBackupVersion === null
-                                ? _t("settings|security|key_backup_active_version_none")
-                                : this.state.activeBackupVersion}
-                        </td>
-                    </tr>
-                </>
-            );
-
-            extraDetails = (
-                <>
-                    {uploadStatus}
-                    <div>{trustedLocally}</div>
-                </>
-            );
-
-            actions.push(
-                <AccessibleButton key="restore" kind="primary_outline" onClick={this.restoreBackup}>
-                    {restoreButtonCaption}
-                </AccessibleButton>,
-            );
-
-            if (!isSecureBackupRequired(MatrixClientPeg.safeGet())) {
-                actions.push(
-                    <AccessibleButton key="delete" kind="danger_outline" onClick={this.deleteBackup}>
-                        {_t("settings|security|delete_backup")}
-                    </AccessibleButton>,
-                );
-            }
-        } else {
-            statusDescription = (
-                <>
-                    <SettingsSubsectionText>
-                        {_t(
-                            "settings|security|key_backup_inactive_warning",
-                            {},
-                            { b: (sub) => <strong>{sub}</strong> },
-                        )}
-                    </SettingsSubsectionText>
-                    <SettingsSubsectionText>{_t("encryption|setup_secure_backup|explainer")}</SettingsSubsectionText>
-                </>
-            );
-            actions.push(
-                <AccessibleButton key="setup" kind="primary_outline" onClick={this.startNewBackup}>
-                    {_t("encryption|setup_secure_backup|title")}
-                </AccessibleButton>,
-            );
-        }
-
-        if (secretStorageKeyInAccount) {
-            actions.push(
-                <AccessibleButton key="reset" kind="danger_outline" onClick={this.resetSecretStorage}>
-                    {_t("action|reset")}
-                </AccessibleButton>,
-            );
-        }
-
-        let backupKeyWellFormedText = "";
-        if (backupKeyCached) {
-            backupKeyWellFormedText = ", ";
-            if (backupKeyWellFormed) {
-                backupKeyWellFormedText += _t("settings|security|backup_key_well_formed");
-            } else {
-                backupKeyWellFormedText += _t("settings|security|backup_key_unexpected_type");
-            }
-        }
-
-        let actionRow: JSX.Element | undefined;
-        if (actions.length) {
-            actionRow = <div className="mx_SecureBackupPanel_buttonRow">{actions}</div>;
-        }
-
-        return (
-            <>
-                <SettingsSubsectionText>{_t("settings|security|backup_keys_description")}</SettingsSubsectionText>
-                {statusDescription}
-                <details>
-                    <summary className="mx_SecureBackupPanel_advanced">{_t("common|advanced")}</summary>
-                    <table className="mx_SecureBackupPanel_statusList">
-                        <tr>
-                            <th scope="row">{_t("settings|security|backup_key_stored_status")}</th>
-                            <td>
-                                {backupKeyStored === true
-                                    ? _t("settings|security|cross_signing_in_4s")
-                                    : _t("settings|security|cross_signing_not_stored")}
-                            </td>
-                        </tr>
-                        <tr>
-                            <th scope="row">{_t("settings|security|backup_key_cached_status")}</th>
-                            <td>
-                                {backupKeyCached
-                                    ? _t("settings|security|cross_signing_cached")
-                                    : _t("settings|security|cross_signing_not_cached")}
-                                {backupKeyWellFormedText}
-                            </td>
-                        </tr>
-                        <tr>
-                            <th scope="row">{_t("settings|security|4s_public_key_status")}</th>
-                            <td>
-                                {secretStorageKeyInAccount
-                                    ? _t("settings|security|4s_public_key_in_account_data")
-                                    : _t("settings|security|cross_signing_not_found")}
-                            </td>
-                        </tr>
-                        <tr>
-                            <th scope="row">{_t("settings|security|secret_storage_status")}</th>
-                            <td>
-                                {secretStorageReady
-                                    ? _t("settings|security|secret_storage_ready")
-                                    : _t("settings|security|secret_storage_not_ready")}
-                            </td>
-                        </tr>
-                        {extraDetailsTableRows}
-                    </table>
-                    {extraDetails}
-                </details>
-                {actionRow}
-            </>
-        );
-    }
-}
diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx
index 9345d87e8255acd8b9d4a2a6cb5440edb61bf2b3..520c642172aa44608f76438e26f3f688fb3247bc 100644
--- a/src/components/views/settings/SetIdServer.tsx
+++ b/src/components/views/settings/SetIdServer.tsx
@@ -1,14 +1,15 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024-2025 New Vector Ltd.
 Copyright 2019-2021 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { IThreepid } from "matrix-js-sdk/src/matrix";
+import { type IThreepid } from "matrix-js-sdk/src/matrix";
+import { EditInPlace, ErrorMessage } from "@vector-im/compound-web";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -19,10 +20,9 @@ import IdentityAuthClient from "../../../IdentityAuthClient";
 import { abbreviateUrl, parseUrl, unabbreviateUrl } from "../../../utils/UrlUtils";
 import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from "../../../utils/IdentityServerUtils";
 import { timeout } from "../../../utils/promise";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import InlineSpinner from "../elements/InlineSpinner";
 import AccessibleButton from "../elements/AccessibleButton";
-import Field from "../elements/Field";
 import QuestionDialog from "../dialogs/QuestionDialog";
 import SettingsFieldset from "./SettingsFieldset";
 import { SettingsSubsectionText } from "./shared/SettingsSubsection";
@@ -86,10 +86,12 @@ export default class SetIdServer extends React.Component<IProps, IState> {
             defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
         }
 
+        const currentClientIdServer = MatrixClientPeg.safeGet().getIdentityServerUrl();
+
         this.state = {
             defaultIdServer,
-            currentClientIdServer: MatrixClientPeg.safeGet().getIdentityServerUrl(),
-            idServer: "",
+            currentClientIdServer,
+            idServer: currentClientIdServer ?? "",
             busy: false,
             disconnectBusy: false,
             checking: false,
@@ -117,26 +119,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
     private onIdentityServerChanged = (ev: React.ChangeEvent<HTMLInputElement>): void => {
         const u = ev.target.value;
 
-        this.setState({ idServer: u });
-    };
-
-    private getTooltip = (): JSX.Element | undefined => {
-        if (this.state.checking) {
-            return (
-                <div>
-                    <InlineSpinner />
-                    {_t("identity_server|checking")}
-                </div>
-            );
-        } else if (this.state.error) {
-            return <strong className="warning">{this.state.error}</strong>;
-        } else {
-            return undefined;
-        }
-    };
-
-    private idServerChangeEnabled = (): boolean => {
-        return !!this.state.idServer && !this.state.busy;
+        this.setState({ idServer: u, error: undefined });
     };
 
     private saveIdServer = (fullUrl: string): void => {
@@ -148,7 +131,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
             busy: false,
             error: undefined,
             currentClientIdServer: fullUrl,
-            idServer: "",
+            idServer: fullUrl,
         });
     };
 
@@ -175,7 +158,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
                 // Double check that the identity server even has terms of service.
                 const hasTerms = await doesIdentityServerHaveTerms(MatrixClientPeg.safeGet(), fullUrl);
                 if (!hasTerms) {
-                    const [confirmed] = await this.showNoTermsWarning(fullUrl);
+                    const [confirmed] = await this.showNoTermsWarning();
                     save = !!confirmed;
                 }
 
@@ -213,7 +196,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
         });
     };
 
-    private showNoTermsWarning(fullUrl: string): Promise<[ok?: boolean]> {
+    private showNoTermsWarning(): Promise<[ok?: boolean]> {
         const { finished } = Modal.createDialog(QuestionDialog, {
             title: _t("terms|identity_server_no_terms_title"),
             description: (
@@ -347,6 +330,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
         });
     };
 
+    private onInputCancel = (): void => this.setState((s) => ({ idServer: s.currentClientIdServer ?? "" }));
+    private onClearServerErrors = (): void => this.setState({ error: undefined });
+
     public render(): React.ReactNode {
         const idServerUrl = this.state.currentClientIdServer;
         let sectionTitle;
@@ -356,13 +342,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
             bodyText = _t(
                 "identity_server|description_connected",
                 {},
-                { server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
+                { server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
             );
             if (this.props.missingTerms) {
                 bodyText = _t(
                     "identity_server|change_server_prompt",
                     {},
-                    { server: (sub) => <strong>{abbreviateUrl(idServerUrl)}</strong> },
+                    { server: () => <strong>{abbreviateUrl(idServerUrl)}</strong> },
                 );
             }
         } else {
@@ -393,28 +379,25 @@ export default class SetIdServer extends React.Component<IProps, IState> {
 
         return (
             <SettingsFieldset legend={sectionTitle} description={bodyText}>
-                <form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
-                    <Field
-                        label={_t("identity_server|url_field_label")}
-                        type="text"
-                        autoComplete="off"
-                        placeholder={this.state.defaultIdServer}
-                        value={this.state.idServer}
-                        onChange={this.onIdentityServerChanged}
-                        tooltipContent={this.getTooltip()}
-                        tooltipClassName="mx_SetIdServer_tooltip"
-                        disabled={this.state.busy}
-                        forceValidity={this.state.error ? false : undefined}
-                    />
-                    <AccessibleButton
-                        kind="primary_sm"
-                        onClick={this.checkIdServer}
-                        disabled={!this.idServerChangeEnabled()}
-                    >
-                        {_t("action|change")}
-                    </AccessibleButton>
-                    {discoSection}
-                </form>
+                <EditInPlace
+                    className="mx_IdentityServerPicker"
+                    cancelButtonLabel={_t("action|reset")}
+                    disabled={!!this.state.busy}
+                    label={_t("identity_server|url_field_label")}
+                    onCancel={this.onInputCancel}
+                    onChange={this.onIdentityServerChanged}
+                    onClearServerErrors={this.onClearServerErrors}
+                    onSave={this.checkIdServer}
+                    placeholder={this.state.defaultIdServer}
+                    saveButtonLabel={_t("action|change")}
+                    savedLabel={this.state.error ? undefined : _t("identity_server|changed")}
+                    savingLabel={_t("identity_server|checking")}
+                    serverInvalid={!!this.state.error}
+                    value={this.state.idServer}
+                >
+                    {this.state.error && <ErrorMessage>{this.state.error}</ErrorMessage>}
+                </EditInPlace>
+                {discoSection}
             </SettingsFieldset>
         );
     }
diff --git a/src/components/views/settings/SetIntegrationManager.tsx b/src/components/views/settings/SetIntegrationManager.tsx
index 01dab7954701eabfca1650354cd53809712039e2..98066bc0a42615b47e503b3ae984da5fa9f0df6a 100644
--- a/src/components/views/settings/SetIntegrationManager.tsx
+++ b/src/components/views/settings/SetIntegrationManager.tsx
@@ -8,26 +8,25 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { logger } from "matrix-js-sdk/src/logger";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
+import { Root, InlineField, Label, ToggleInput } from "@vector-im/compound-web";
 
 import { _t } from "../../../languageHandler";
 import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
-import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
+import { type IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
 import SettingsStore from "../../../settings/SettingsStore";
 import { SettingLevel } from "../../../settings/SettingLevel";
-import ToggleSwitch from "../elements/ToggleSwitch";
 import Heading from "../typography/Heading";
 import { SettingsSubsectionText } from "./shared/SettingsSubsection";
 import { UIFeature } from "../../../settings/UIFeature";
 
-interface IProps {}
-
 interface IState {
     currentManager: IntegrationManagerInstance | null;
     provisioningEnabled: boolean;
 }
 
-export default class SetIntegrationManager extends React.Component<IProps, IState> {
-    public constructor(props: IProps) {
+export default class SetIntegrationManager extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         const currentManager = IntegrationManagers.sharedInstance().getPrimaryManager();
@@ -67,26 +66,33 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
         if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
 
         return (
-            <label
-                className="mx_SetIntegrationManager"
-                data-testid="mx_SetIntegrationManager"
-                htmlFor="toggle_integration"
-            >
+            <div className="mx_SetIntegrationManager" data-testid="mx_SetIntegrationManager">
                 <div className="mx_SettingsFlag">
                     <div className="mx_SetIntegrationManager_heading_manager">
                         <Heading size="3">{_t("integration_manager|manage_title")}</Heading>
                         <Heading size="4">{managerName}</Heading>
                     </div>
-                    <ToggleSwitch
-                        id="toggle_integration"
-                        checked={this.state.provisioningEnabled}
-                        disabled={false}
-                        onChange={this.onProvisioningToggled}
-                    />
                 </div>
                 <SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
                 <SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
-            </label>
+                <Root>
+                    <InlineField
+                        name="enable_im"
+                        control={
+                            <ToggleInput
+                                role="switch"
+                                id="mx_SetIntegrationManager_Toggle"
+                                checked={this.state.provisioningEnabled}
+                                onChange={this.onProvisioningToggled}
+                            />
+                        }
+                    >
+                        <Label htmlFor="mx_SetIntegrationManager_Toggle">
+                            {_t("integration_manager|toggle_label")}
+                        </Label>
+                    </InlineField>
+                </Root>
+            </div>
         );
     }
 }
diff --git a/src/components/views/settings/SettingsFieldset.tsx b/src/components/views/settings/SettingsFieldset.tsx
index 3316b657e61a8ba8f3c9308399b08fc921887743..850647f37b8c3afb33f4ac8d3e1a9bcb39d952cb 100644
--- a/src/components/views/settings/SettingsFieldset.tsx
+++ b/src/components/views/settings/SettingsFieldset.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, HTMLAttributes } from "react";
+import React, { type ReactNode, type HTMLAttributes } from "react";
 import classNames from "classnames";
 
 import { SettingsSubsectionText } from "./shared/SettingsSubsection";
diff --git a/src/components/views/settings/SettingsHeader.tsx b/src/components/views/settings/SettingsHeader.tsx
index 9a83ba1d9296ac8443848b3dca13cc024bf402dd..10534958f4e2eabdf48a4c14d09c4045e0284bbf 100644
--- a/src/components/views/settings/SettingsHeader.tsx
+++ b/src/components/views/settings/SettingsHeader.tsx
@@ -5,7 +5,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX } from "react";
+import React, { type JSX } from "react";
 import { Heading } from "@vector-im/compound-web";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/settings/SettingsSubheader.tsx b/src/components/views/settings/SettingsSubheader.tsx
index 90b8a41b7aea7c99753fe2a2a77fab9807abbb18..158728f5300291d2d89b60c63ef3f93cb85ab294 100644
--- a/src/components/views/settings/SettingsSubheader.tsx
+++ b/src/components/views/settings/SettingsSubheader.tsx
@@ -5,9 +5,9 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX } from "react";
+import React, { type JSX } from "react";
 import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
-import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
 import classNames from "classnames";
 
 interface SettingsSubheaderProps {
diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx
index 1ec8e0adce9c5b50f7513f8bfd95a2cf095a6a59..d1c759418d8645d242ac73bc63ebf7e6a27c6593 100644
--- a/src/components/views/settings/SpellCheckSettings.tsx
+++ b/src/components/views/settings/SpellCheckSettings.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown";
-import AccessibleButton, { ButtonEvent } from "../../../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../components/views/elements/AccessibleButton";
 import { _t, getUserLanguage } from "../../../languageHandler";
 
 interface ExistingSpellCheckLanguageIProps {
diff --git a/src/components/views/settings/ThemeChoicePanel.tsx b/src/components/views/settings/ThemeChoicePanel.tsx
index b60b3fe540d135b49642e9e53b1949f9dfe807ea..6581e823fcecbfd426d745bb35d2fb163c72a4e4 100644
--- a/src/components/views/settings/ThemeChoicePanel.tsx
+++ b/src/components/views/settings/ThemeChoicePanel.tsx
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { ChangeEvent, JSX, useCallback, useMemo, useState } from "react";
+import React, { type ChangeEvent, type JSX, useCallback, useMemo, useState } from "react";
 import {
     InlineField,
     ToggleControl,
@@ -28,10 +28,15 @@ import ThemeWatcher from "../../../settings/watchers/ThemeWatcher";
 import SettingsStore from "../../../settings/SettingsStore";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import dis from "../../../dispatcher/dispatcher";
-import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
+import { type RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
 import { Action } from "../../../dispatcher/actions";
 import { useTheme } from "../../../hooks/useTheme";
-import { findHighContrastTheme, getOrderedThemes, CustomTheme as CustomThemeType, ITheme } from "../../../theme";
+import {
+    findHighContrastTheme,
+    getOrderedThemes,
+    type CustomTheme as CustomThemeType,
+    type ITheme,
+} from "../../../theme";
 import { useSettingValue } from "../../../hooks/useSettings";
 
 /**
diff --git a/src/components/views/settings/UpdateCheckButton.tsx b/src/components/views/settings/UpdateCheckButton.tsx
index e283373eb99decc3466f7050fed4f5c2f4534664..05653c9a29fc770a5d2be1a9517c5810a4a0d2b8 100644
--- a/src/components/views/settings/UpdateCheckButton.tsx
+++ b/src/components/views/settings/UpdateCheckButton.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode, useState } from "react";
+import React, { type JSX, type ReactNode, useState } from "react";
 
 import { UpdateCheckStatus } from "../../../BasePlatform";
 import PlatformPeg from "../../../PlatformPeg";
@@ -16,7 +16,7 @@ import { Action } from "../../../dispatcher/actions";
 import { _t } from "../../../languageHandler";
 import InlineSpinner from "../../../components/views/elements/InlineSpinner";
 import AccessibleButton from "../../../components/views/elements/AccessibleButton";
-import { CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload";
+import { type CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload";
 
 function installUpdate(): void {
     PlatformPeg.get()?.installUpdate();
diff --git a/src/components/views/settings/UserPersonalInfoSettings.tsx b/src/components/views/settings/UserPersonalInfoSettings.tsx
index c08c0f636fedbe66ce555fc6b8559c84708b3f1c..e6dae795c67ba03f509e8877735e4e9e2ad62214 100644
--- a/src/components/views/settings/UserPersonalInfoSettings.tsx
+++ b/src/components/views/settings/UserPersonalInfoSettings.tsx
@@ -14,7 +14,7 @@ import { _t } from "../../../languageHandler";
 import InlineSpinner from "../elements/InlineSpinner";
 import { SettingsSubsection } from "./shared/SettingsSubsection";
 import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
-import { ThirdPartyIdentifier } from "../../../AddThreepid";
+import { type ThirdPartyIdentifier } from "../../../AddThreepid";
 import SettingsStore from "../../../settings/SettingsStore";
 import { UIFeature } from "../../../settings/UIFeature";
 import { AddRemoveThreepids } from "./AddRemoveThreepids";
diff --git a/src/components/views/settings/UserProfileSettings.tsx b/src/components/views/settings/UserProfileSettings.tsx
index 5fba36ad015143577c6f618160779701aaa2841f..11b6a5bb69a743af02f43fb6a90621b8acebff02 100644
--- a/src/components/views/settings/UserProfileSettings.tsx
+++ b/src/components/views/settings/UserProfileSettings.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ReactNode, useCallback, useEffect, useMemo, useState, useId } from "react";
+import React, { type ChangeEvent, type ReactNode, useCallback, useEffect, useMemo, useState, useId } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import { EditInPlace, Alert, ErrorMessage } from "@vector-im/compound-web";
 import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx
index 245c244c90f06b0e0a890f9402bed4411b92ae58..c88649d831510dd974b18d884077fa2525b7101a 100644
--- a/src/components/views/settings/devices/CurrentDeviceSection.tsx
+++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState } from "react";
-import { LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
+import { type LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import Spinner from "../../elements/Spinner";
@@ -17,7 +17,7 @@ import DeviceDetails from "./DeviceDetails";
 import { DeviceExpandDetailsButton } from "./DeviceExpandDetailsButton";
 import DeviceTile from "./DeviceTile";
 import { DeviceVerificationStatusCard } from "./DeviceVerificationStatusCard";
-import { ExtendedDevice } from "./types";
+import { type ExtendedDevice } from "./types";
 import { KebabContextMenu } from "../../context_menus/KebabContextMenu";
 import { IconizedContextMenuOption } from "../../context_menus/IconizedContextMenu";
 
diff --git a/src/components/views/settings/devices/DeviceDetailHeading.tsx b/src/components/views/settings/devices/DeviceDetailHeading.tsx
index 945f5f204af928e5246c190b11c96b06f5101d4a..78c9574486ccf544c34babba52a0fd77af86579b 100644
--- a/src/components/views/settings/devices/DeviceDetailHeading.tsx
+++ b/src/components/views/settings/devices/DeviceDetailHeading.tsx
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
 import React, { useEffect, useState } from "react";
 
 import { _t } from "../../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../elements/AccessibleButton";
 import Field from "../../elements/Field";
 import LearnMore from "../../elements/LearnMore";
 import Spinner from "../../elements/Spinner";
 import { Caption } from "../../typography/Caption";
 import Heading from "../../typography/Heading";
-import { ExtendedDevice } from "./types";
+import { type ExtendedDevice } from "./types";
 
 interface Props {
     device: ExtendedDevice;
diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx
index a97a1b1bfe8667f27aecc39041560fa6c4e946ca..52f5800dac23ee7729e4f826c51dc11cfd68a5c8 100644
--- a/src/components/views/settings/devices/DeviceDetails.tsx
+++ b/src/components/views/settings/devices/DeviceDetails.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import classNames from "classnames";
-import { IPusher, PUSHER_ENABLED, LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
+import { type IPusher, PUSHER_ENABLED, type LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
 
 import { formatDate } from "../../../../DateUtils";
 import { _t } from "../../../../languageHandler";
@@ -17,7 +17,7 @@ import Spinner from "../../elements/Spinner";
 import ToggleSwitch from "../../elements/ToggleSwitch";
 import { DeviceDetailHeading } from "./DeviceDetailHeading";
 import { DeviceVerificationStatusCard } from "./DeviceVerificationStatusCard";
-import { ExtendedDevice } from "./types";
+import { type ExtendedDevice } from "./types";
 import { getManageDeviceUrl } from "../../../../utils/oidc/urls.ts";
 
 interface Props {
diff --git a/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx b/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx
index b227ea38143fd73a151bbe3a05cdb33a1001fb45..dfa188cf692e7bf1dce48a3cd512251e236679a9 100644
--- a/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx
+++ b/src/components/views/settings/devices/DeviceExpandDetailsButton.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React from "react";
+import React, { type JSX } from "react";
 import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t } from "../../../../languageHandler";
-import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonProps } from "../../elements/AccessibleButton";
 
 type Props<T extends keyof HTMLElementTagNameMap> = Omit<
     ButtonProps<T>,
-    "aria-label" | "title" | "kind" | "className" | "element"
+    "aria-label" | "title" | "kind" | "className" | "element" | "ref"
 > & {
     isExpanded: boolean;
 };
diff --git a/src/components/views/settings/devices/DeviceMetaData.tsx b/src/components/views/settings/devices/DeviceMetaData.tsx
index be7bd1199635701fffb49a626b2859a066427178..299cef69cf66af015319e7009a41563ffe166f97 100644
--- a/src/components/views/settings/devices/DeviceMetaData.tsx
+++ b/src/components/views/settings/devices/DeviceMetaData.tsx
@@ -10,7 +10,7 @@ import React, { Fragment } from "react";
 
 import { Icon as InactiveIcon } from "../../../../../res/img/element-icons/settings/inactive.svg";
 import { INACTIVE_DEVICE_AGE_DAYS, isDeviceInactive } from "../../../../components/views/settings/devices/filter";
-import { ExtendedDevice } from "../../../../components/views/settings/devices/types";
+import { type ExtendedDevice } from "../../../../components/views/settings/devices/types";
 import { formatDate, formatRelativeTime } from "../../../../DateUtils";
 import { _t } from "../../../../languageHandler";
 
diff --git a/src/components/views/settings/devices/DeviceSecurityLearnMore.tsx b/src/components/views/settings/devices/DeviceSecurityLearnMore.tsx
index 6a2ca55d4304e95274043e5ac12433172044098a..3dbc7d6123d1908d7a21f552836663ae585384f6 100644
--- a/src/components/views/settings/devices/DeviceSecurityLearnMore.tsx
+++ b/src/components/views/settings/devices/DeviceSecurityLearnMore.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 
 import { _t } from "../../../../languageHandler";
 import LearnMore from "../../elements/LearnMore";
diff --git a/src/components/views/settings/devices/DeviceTile.tsx b/src/components/views/settings/devices/DeviceTile.tsx
index 79d0c720df3eeb30ace384b72efe85498375de0f..1295b8da95585d63476aee4bad91d50c0b4a9ed0 100644
--- a/src/components/views/settings/devices/DeviceTile.tsx
+++ b/src/components/views/settings/devices/DeviceTile.tsx
@@ -10,7 +10,7 @@ import React from "react";
 import classNames from "classnames";
 
 import Heading from "../../typography/Heading";
-import { ExtendedDevice } from "./types";
+import { type ExtendedDevice } from "./types";
 import { DeviceTypeIcon } from "./DeviceTypeIcon";
 import { preventDefaultWrapper } from "../../../../utils/NativeEventUtils";
 import { DeviceMetaData } from "./DeviceMetaData";
diff --git a/src/components/views/settings/devices/DeviceTypeIcon.tsx b/src/components/views/settings/devices/DeviceTypeIcon.tsx
index c685c4c89563931a6810cefcf53453fc235473c2..994a40f250f820e850c468a5d791cdcaa9cfd7cf 100644
--- a/src/components/views/settings/devices/DeviceTypeIcon.tsx
+++ b/src/components/views/settings/devices/DeviceTypeIcon.tsx
@@ -15,8 +15,8 @@ import { Icon as WebIcon } from "../../../../../res/img/element-icons/settings/w
 import { Icon as MobileIcon } from "../../../../../res/img/element-icons/settings/mobile.svg";
 import { Icon as VerifiedIcon } from "../../../../../res/img/e2e/verified.svg";
 import { Icon as UnverifiedIcon } from "../../../../../res/img/e2e/warning.svg";
-import { _t, _td, TranslationKey } from "../../../../languageHandler";
-import { ExtendedDevice } from "./types";
+import { _t, _td, type TranslationKey } from "../../../../languageHandler";
+import { type ExtendedDevice } from "./types";
 import { DeviceType } from "../../../../utils/device/parseUserAgent";
 
 interface Props {
diff --git a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx
index d57231076b3be6c8b56b00e05840cb1abdc50b1d..87cb9113cac35c4d2062ed35fae0dd368620dde8 100644
--- a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx
+++ b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx
@@ -12,7 +12,7 @@ import { _t } from "../../../../languageHandler";
 import AccessibleButton from "../../elements/AccessibleButton";
 import DeviceSecurityCard from "./DeviceSecurityCard";
 import { DeviceSecurityLearnMore } from "./DeviceSecurityLearnMore";
-import { DeviceSecurityVariation, ExtendedDevice } from "./types";
+import { DeviceSecurityVariation, type ExtendedDevice } from "./types";
 
 export interface DeviceVerificationStatusCardProps {
     device: ExtendedDevice;
diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx
index bf37b095486a904e07ae7626b612f3d7d5175ccd..fd300911068c845612dbacf4a8dec9b5eb73e449 100644
--- a/src/components/views/settings/devices/FilteredDeviceList.tsx
+++ b/src/components/views/settings/devices/FilteredDeviceList.tsx
@@ -6,19 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ForwardedRef, forwardRef } from "react";
-import { IPusher, PUSHER_DEVICE_ID, LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type Ref } from "react";
+import { type IPusher, PUSHER_DEVICE_ID, type LocalNotificationSettings } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../languageHandler";
 import AccessibleButton from "../../elements/AccessibleButton";
-import { FilterDropdown, FilterDropdownOption } from "../../elements/FilterDropdown";
+import { FilterDropdown, type FilterDropdownOption } from "../../elements/FilterDropdown";
 import DeviceDetails from "./DeviceDetails";
 import { DeviceExpandDetailsButton } from "./DeviceExpandDetailsButton";
 import DeviceSecurityCard from "./DeviceSecurityCard";
-import { filterDevicesBySecurityRecommendation, FilterVariation, INACTIVE_DEVICE_AGE_DAYS } from "./filter";
+import { filterDevicesBySecurityRecommendation, type FilterVariation, INACTIVE_DEVICE_AGE_DAYS } from "./filter";
 import SelectableDeviceTile from "./SelectableDeviceTile";
-import { DevicesDictionary, DeviceSecurityVariation, ExtendedDevice } from "./types";
-import { DevicesState } from "./useOwnDevices";
+import { type DevicesDictionary, DeviceSecurityVariation, type ExtendedDevice } from "./types";
+import { type DevicesState } from "./useOwnDevices";
 import FilteredDeviceListHeader from "./FilteredDeviceListHeader";
 import Spinner from "../../elements/Spinner";
 import { DeviceSecurityLearnMore } from "./DeviceSecurityLearnMore";
@@ -47,6 +47,7 @@ interface Props {
      * Changes sign out button to be a manage button
      */
     delegatedAuthAccountUrl?: string;
+    ref?: Ref<HTMLDivElement>;
 }
 
 const isDeviceSelected = (
@@ -237,152 +238,148 @@ const DeviceListItem: React.FC<{
  * Filtered list of devices
  * Sorted by latest activity descending
  */
-export const FilteredDeviceList = forwardRef(
-    (
-        {
-            devices,
-            pushers,
-            localNotificationSettings,
-            filter,
-            expandedDeviceIds,
-            signingOutDeviceIds,
-            selectedDeviceIds,
-            onFilterChange,
-            onDeviceExpandToggle,
-            saveDeviceName,
-            onSignOutDevices,
-            onRequestDeviceVerification,
-            setPushNotifications,
-            setSelectedDeviceIds,
-            supportsMSC3881,
-            delegatedAuthAccountUrl,
-        }: Props,
-        ref: ForwardedRef<HTMLDivElement>,
-    ) => {
-        const sortedDevices = getFilteredSortedDevices(devices, filter);
+export const FilteredDeviceList = ({
+    devices,
+    pushers,
+    localNotificationSettings,
+    filter,
+    expandedDeviceIds,
+    signingOutDeviceIds,
+    selectedDeviceIds,
+    onFilterChange,
+    onDeviceExpandToggle,
+    saveDeviceName,
+    onSignOutDevices,
+    onRequestDeviceVerification,
+    setPushNotifications,
+    setSelectedDeviceIds,
+    supportsMSC3881,
+    delegatedAuthAccountUrl,
+    ref,
+}: Props): JSX.Element => {
+    const sortedDevices = getFilteredSortedDevices(devices, filter);
 
-        function getPusherForDevice(device: ExtendedDevice): IPusher | undefined {
-            return pushers.find((pusher) => pusher[PUSHER_DEVICE_ID.name] === device.device_id);
-        }
+    function getPusherForDevice(device: ExtendedDevice): IPusher | undefined {
+        return pushers.find((pusher) => pusher[PUSHER_DEVICE_ID.name] === device.device_id);
+    }
 
-        const toggleSelection = (deviceId: ExtendedDevice["device_id"]): void => {
-            if (isDeviceSelected(deviceId, selectedDeviceIds)) {
-                // remove from selection
-                setSelectedDeviceIds(selectedDeviceIds.filter((id) => id !== deviceId));
-            } else {
-                setSelectedDeviceIds([...selectedDeviceIds, deviceId]);
-            }
-        };
+    const toggleSelection = (deviceId: ExtendedDevice["device_id"]): void => {
+        if (isDeviceSelected(deviceId, selectedDeviceIds)) {
+            // remove from selection
+            setSelectedDeviceIds(selectedDeviceIds.filter((id) => id !== deviceId));
+        } else {
+            setSelectedDeviceIds([...selectedDeviceIds, deviceId]);
+        }
+    };
 
-        const options: FilterDropdownOption<DeviceFilterKey>[] = [
-            { id: ALL_FILTER_ID, label: _t("settings|sessions|filter_all") },
-            {
-                id: DeviceSecurityVariation.Verified,
-                label: _t("common|verified"),
-                description: _t("settings|sessions|filter_verified_description"),
-            },
-            {
-                id: DeviceSecurityVariation.Unverified,
-                label: _t("common|unverified"),
-                description: _t("settings|sessions|filter_unverified_description"),
-            },
-            {
-                id: DeviceSecurityVariation.Inactive,
-                label: _t("settings|sessions|filter_inactive"),
-                description: _t("settings|sessions|filter_inactive_description", {
-                    inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS,
-                }),
-            },
-        ];
+    const options: FilterDropdownOption<DeviceFilterKey>[] = [
+        { id: ALL_FILTER_ID, label: _t("settings|sessions|filter_all") },
+        {
+            id: DeviceSecurityVariation.Verified,
+            label: _t("common|verified"),
+            description: _t("settings|sessions|filter_verified_description"),
+        },
+        {
+            id: DeviceSecurityVariation.Unverified,
+            label: _t("common|unverified"),
+            description: _t("settings|sessions|filter_unverified_description"),
+        },
+        {
+            id: DeviceSecurityVariation.Inactive,
+            label: _t("settings|sessions|filter_inactive"),
+            description: _t("settings|sessions|filter_inactive_description", {
+                inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS,
+            }),
+        },
+    ];
 
-        const onFilterOptionChange = (filterId: DeviceFilterKey): void => {
-            onFilterChange(filterId === ALL_FILTER_ID ? undefined : (filterId as FilterVariation));
-        };
+    const onFilterOptionChange = (filterId: DeviceFilterKey): void => {
+        onFilterChange(filterId === ALL_FILTER_ID ? undefined : (filterId as FilterVariation));
+    };
 
-        const isAllSelected = selectedDeviceIds.length >= sortedDevices.length;
-        const toggleSelectAll = (): void => {
-            if (isAllSelected) {
-                setSelectedDeviceIds([]);
-            } else {
-                setSelectedDeviceIds(sortedDevices.map((device) => device.device_id));
-            }
-        };
+    const isAllSelected = selectedDeviceIds.length >= sortedDevices.length;
+    const toggleSelectAll = (): void => {
+        if (isAllSelected) {
+            setSelectedDeviceIds([]);
+        } else {
+            setSelectedDeviceIds(sortedDevices.map((device) => device.device_id));
+        }
+    };
 
-        const isSigningOut = !!signingOutDeviceIds.length;
+    const isSigningOut = !!signingOutDeviceIds.length;
 
-        return (
-            <div className="mx_FilteredDeviceList" ref={ref}>
-                <FilteredDeviceListHeader
-                    selectedDeviceCount={selectedDeviceIds.length}
-                    isAllSelected={isAllSelected}
-                    toggleSelectAll={toggleSelectAll}
-                    isSelectDisabled={!!delegatedAuthAccountUrl}
-                >
-                    {selectedDeviceIds.length ? (
-                        <>
-                            <AccessibleButton
-                                data-testid="sign-out-selection-cta"
-                                kind="danger_inline"
-                                disabled={isSigningOut}
-                                onClick={() => onSignOutDevices(selectedDeviceIds)}
-                                className="mx_FilteredDeviceList_headerButton"
-                            >
-                                {isSigningOut && <Spinner w={16} h={16} />}
-                                {_t("action|sign_out")}
-                            </AccessibleButton>
-                            <AccessibleButton
-                                data-testid="cancel-selection-cta"
-                                kind="content_inline"
-                                disabled={isSigningOut}
-                                onClick={() => setSelectedDeviceIds([])}
-                                className="mx_FilteredDeviceList_headerButton"
-                            >
-                                {_t("action|cancel")}
-                            </AccessibleButton>
-                        </>
-                    ) : (
-                        <FilterDropdown<DeviceFilterKey>
-                            id="device-list-filter"
-                            label={_t("settings|sessions|filter_label")}
-                            value={filter || ALL_FILTER_ID}
-                            onOptionChange={onFilterOptionChange}
-                            options={options}
-                            selectedLabel={_t("action|show")}
-                        />
-                    )}
-                </FilteredDeviceListHeader>
-                {!!sortedDevices.length ? (
-                    <FilterSecurityCard filter={filter} />
+    return (
+        <div className="mx_FilteredDeviceList" ref={ref}>
+            <FilteredDeviceListHeader
+                selectedDeviceCount={selectedDeviceIds.length}
+                isAllSelected={isAllSelected}
+                toggleSelectAll={toggleSelectAll}
+                isSelectDisabled={!!delegatedAuthAccountUrl}
+            >
+                {selectedDeviceIds.length ? (
+                    <>
+                        <AccessibleButton
+                            data-testid="sign-out-selection-cta"
+                            kind="danger_inline"
+                            disabled={isSigningOut}
+                            onClick={() => onSignOutDevices(selectedDeviceIds)}
+                            className="mx_FilteredDeviceList_headerButton"
+                        >
+                            {isSigningOut && <Spinner w={16} h={16} />}
+                            {_t("action|sign_out")}
+                        </AccessibleButton>
+                        <AccessibleButton
+                            data-testid="cancel-selection-cta"
+                            kind="content_inline"
+                            disabled={isSigningOut}
+                            onClick={() => setSelectedDeviceIds([])}
+                            className="mx_FilteredDeviceList_headerButton"
+                        >
+                            {_t("action|cancel")}
+                        </AccessibleButton>
+                    </>
                 ) : (
-                    <NoResults filter={filter} clearFilter={() => onFilterChange(undefined)} />
+                    <FilterDropdown<DeviceFilterKey>
+                        id="device-list-filter"
+                        label={_t("settings|sessions|filter_label")}
+                        value={filter || ALL_FILTER_ID}
+                        onOptionChange={onFilterOptionChange}
+                        options={options}
+                        selectedLabel={_t("action|show")}
+                    />
                 )}
-                <ol className="mx_FilteredDeviceList_list">
-                    {sortedDevices.map((device) => (
-                        <DeviceListItem
-                            key={device.device_id}
-                            device={device}
-                            pusher={getPusherForDevice(device)}
-                            localNotificationSettings={localNotificationSettings.get(device.device_id)}
-                            isExpanded={expandedDeviceIds.includes(device.device_id)}
-                            isSigningOut={signingOutDeviceIds.includes(device.device_id)}
-                            isSelected={isDeviceSelected(device.device_id, selectedDeviceIds)}
-                            isSelectDisabled={!!delegatedAuthAccountUrl}
-                            onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
-                            onSignOutDevice={() => onSignOutDevices([device.device_id])}
-                            saveDeviceName={(deviceName: string) => saveDeviceName(device.device_id, deviceName)}
-                            onRequestDeviceVerification={
-                                onRequestDeviceVerification
-                                    ? () => onRequestDeviceVerification(device.device_id)
-                                    : undefined
-                            }
-                            setPushNotifications={setPushNotifications}
-                            toggleSelected={() => toggleSelection(device.device_id)}
-                            supportsMSC3881={supportsMSC3881}
-                            delegatedAuthAccountUrl={delegatedAuthAccountUrl}
-                        />
-                    ))}
-                </ol>
-            </div>
-        );
-    },
-);
+            </FilteredDeviceListHeader>
+            {!!sortedDevices.length ? (
+                <FilterSecurityCard filter={filter} />
+            ) : (
+                <NoResults filter={filter} clearFilter={() => onFilterChange(undefined)} />
+            )}
+            <ol className="mx_FilteredDeviceList_list">
+                {sortedDevices.map((device) => (
+                    <DeviceListItem
+                        key={device.device_id}
+                        device={device}
+                        pusher={getPusherForDevice(device)}
+                        localNotificationSettings={localNotificationSettings.get(device.device_id)}
+                        isExpanded={expandedDeviceIds.includes(device.device_id)}
+                        isSigningOut={signingOutDeviceIds.includes(device.device_id)}
+                        isSelected={isDeviceSelected(device.device_id, selectedDeviceIds)}
+                        isSelectDisabled={!!delegatedAuthAccountUrl}
+                        onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
+                        onSignOutDevice={() => onSignOutDevices([device.device_id])}
+                        saveDeviceName={(deviceName: string) => saveDeviceName(device.device_id, deviceName)}
+                        onRequestDeviceVerification={
+                            onRequestDeviceVerification
+                                ? () => onRequestDeviceVerification(device.device_id)
+                                : undefined
+                        }
+                        setPushNotifications={setPushNotifications}
+                        toggleSelected={() => toggleSelection(device.device_id)}
+                        supportsMSC3881={supportsMSC3881}
+                        delegatedAuthAccountUrl={delegatedAuthAccountUrl}
+                    />
+                ))}
+            </ol>
+        </div>
+    );
+};
diff --git a/src/components/views/settings/devices/FilteredDeviceListHeader.tsx b/src/components/views/settings/devices/FilteredDeviceListHeader.tsx
index 28fcec6715822709800398f4d3f44cef25631dbd..a12f209eba5285400953e14e6c0f9a848efc8190 100644
--- a/src/components/views/settings/devices/FilteredDeviceListHeader.tsx
+++ b/src/components/views/settings/devices/FilteredDeviceListHeader.tsx
@@ -1,16 +1,16 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLProps } from "react";
+import React, { type HTMLProps } from "react";
 import { Tooltip } from "@vector-im/compound-web";
 
 import { _t } from "../../../../languageHandler";
-import StyledCheckbox, { CheckboxStyle } from "../../elements/StyledCheckbox";
+import StyledCheckbox from "../../elements/StyledCheckbox";
 
 interface Props extends Omit<HTMLProps<HTMLDivElement>, "className"> {
     selectedDeviceCount: number;
@@ -34,7 +34,6 @@ const FilteredDeviceListHeader: React.FC<Props> = ({
             {!isSelectDisabled && (
                 <Tooltip label={checkboxLabel} placement="top" isTriggerInteractive={false}>
                     <StyledCheckbox
-                        kind={CheckboxStyle.Solid}
                         checked={isAllSelected}
                         onChange={toggleSelectAll}
                         id="device-select-all-checkbox"
diff --git a/src/components/views/settings/devices/LoginWithQRSection.tsx b/src/components/views/settings/devices/LoginWithQRSection.tsx
index 4043d28c805e9a0019e4b67ac188f62dc09880fc..523633c8845e081476a8912a719fe2e2f73eaae9 100644
--- a/src/components/views/settings/devices/LoginWithQRSection.tsx
+++ b/src/components/views/settings/devices/LoginWithQRSection.tsx
@@ -7,7 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { IServerVersions, OidcClientConfig, MatrixClient, DEVICE_CODE_SCOPE } from "matrix-js-sdk/src/matrix";
+import {
+    type IServerVersions,
+    type OidcClientConfig,
+    type MatrixClient,
+    DEVICE_CODE_SCOPE,
+} from "matrix-js-sdk/src/matrix";
 import QrCodeIcon from "@vector-im/compound-design-tokens/assets/web/icons/qr-code";
 import { Text } from "@vector-im/compound-web";
 
@@ -31,8 +36,7 @@ export function shouldShowQr(
 ): boolean {
     const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
 
-    const deviceAuthorizationGrantSupported =
-        oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
+    const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
 
     return (
         !!deviceAuthorizationGrantSupported &&
diff --git a/src/components/views/settings/devices/SecurityRecommendations.tsx b/src/components/views/settings/devices/SecurityRecommendations.tsx
index 599c3bd5b3e110e1c194c15d496463002e15da4d..8c555ca9c67064e091da58dddf400f20a484e562 100644
--- a/src/components/views/settings/devices/SecurityRecommendations.tsx
+++ b/src/components/views/settings/devices/SecurityRecommendations.tsx
@@ -13,8 +13,8 @@ import AccessibleButton from "../../elements/AccessibleButton";
 import { SettingsSubsection } from "../shared/SettingsSubsection";
 import DeviceSecurityCard from "./DeviceSecurityCard";
 import { DeviceSecurityLearnMore } from "./DeviceSecurityLearnMore";
-import { filterDevicesBySecurityRecommendation, FilterVariation, INACTIVE_DEVICE_AGE_DAYS } from "./filter";
-import { DeviceSecurityVariation, ExtendedDevice, DevicesDictionary } from "./types";
+import { filterDevicesBySecurityRecommendation, type FilterVariation, INACTIVE_DEVICE_AGE_DAYS } from "./filter";
+import { DeviceSecurityVariation, type ExtendedDevice, type DevicesDictionary } from "./types";
 
 interface Props {
     devices: DevicesDictionary;
diff --git a/src/components/views/settings/devices/SelectableDeviceTile.tsx b/src/components/views/settings/devices/SelectableDeviceTile.tsx
index b83b2c58705fb3000b80fa2fd9d53db9f2ba8c76..d80ac3af05e0b0ce793a9c54baecf494ba57ef6f 100644
--- a/src/components/views/settings/devices/SelectableDeviceTile.tsx
+++ b/src/components/views/settings/devices/SelectableDeviceTile.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 
-import StyledCheckbox, { CheckboxStyle } from "../../elements/StyledCheckbox";
-import DeviceTile, { DeviceTileProps } from "./DeviceTile";
+import StyledCheckbox from "../../elements/StyledCheckbox";
+import DeviceTile, { type DeviceTileProps } from "./DeviceTile";
 
 interface Props extends DeviceTileProps {
     isSelected: boolean;
@@ -21,7 +21,6 @@ const SelectableDeviceTile: React.FC<Props> = ({ children, device, isSelected, o
     return (
         <div className="mx_SelectableDeviceTile">
             <StyledCheckbox
-                kind={CheckboxStyle.Solid}
                 checked={isSelected}
                 onChange={onSelect}
                 className="mx_SelectableDeviceTile_checkbox"
diff --git a/src/components/views/settings/devices/deleteDevices.tsx b/src/components/views/settings/devices/deleteDevices.tsx
index 16fdca9219c4b51cc3975a3700cec05a6622463e..894e23e0f7a1b5097e9aff4c0327a45425ee3e84 100644
--- a/src/components/views/settings/devices/deleteDevices.tsx
+++ b/src/components/views/settings/devices/deleteDevices.tsx
@@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
-import { AuthDict, IAuthData } from "matrix-js-sdk/src/interactive-auth";
+import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type AuthDict, type IAuthData } from "matrix-js-sdk/src/interactive-auth";
 
 import { _t } from "../../../../languageHandler";
 import Modal from "../../../../Modal";
-import { InteractiveAuthCallback } from "../../../structures/InteractiveAuth";
 import { SSOAuthEntry } from "../../auth/InteractiveAuthEntryComponents";
 import InteractiveAuthDialog from "../../dialogs/InteractiveAuthDialog";
 
@@ -24,7 +23,7 @@ const makeDeleteRequest =
 export const deleteDevicesWithInteractiveAuth = async (
     matrixClient: MatrixClient,
     deviceIds: string[],
-    onFinished: InteractiveAuthCallback<void>,
+    onFinished: (success?: boolean) => Promise<void>,
 ): Promise<void> => {
     if (!deviceIds.length) {
         return;
@@ -32,7 +31,7 @@ export const deleteDevicesWithInteractiveAuth = async (
     try {
         await makeDeleteRequest(matrixClient, deviceIds)(null);
         // no interactive auth needed
-        await onFinished(true, undefined);
+        await onFinished(true);
     } catch (error) {
         if (!(error instanceof MatrixError) || error.httpStatus !== 401 || !error.data?.flows) {
             // doesn't look like an interactive-auth failure
@@ -62,16 +61,16 @@ export const deleteDevicesWithInteractiveAuth = async (
                 continueKind: "danger",
             },
         };
-        Modal.createDialog(InteractiveAuthDialog, {
+        const { finished } = Modal.createDialog(InteractiveAuthDialog, {
             title: _t("common|authentication"),
             matrixClient: matrixClient,
             authData: error.data as IAuthData,
-            onFinished,
             makeRequest: makeDeleteRequest(matrixClient, deviceIds),
             aestheticsForStagePhases: {
                 [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
                 [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
             },
         });
+        finished.then(([success]) => onFinished(success));
     }
 };
diff --git a/src/components/views/settings/devices/filter.ts b/src/components/views/settings/devices/filter.ts
index ba7f6f5c216861e4d26b3d6a87bc7ac475cbc129..3712ab4939ca774a1257ed564bf511ed5149f848 100644
--- a/src/components/views/settings/devices/filter.ts
+++ b/src/components/views/settings/devices/filter.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ExtendedDevice, DeviceSecurityVariation } from "./types";
+import { type ExtendedDevice, DeviceSecurityVariation } from "./types";
 
 type DeviceFilterCondition = (device: ExtendedDevice) => boolean;
 
diff --git a/src/components/views/settings/devices/types.ts b/src/components/views/settings/devices/types.ts
index 3891f6be32b2e278b50298d3e1ff37f83af9fd28..a7f9dfa95260e5c3e1681ef5c0ef7419df0a97e4 100644
--- a/src/components/views/settings/devices/types.ts
+++ b/src/components/views/settings/devices/types.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IMyDevice } from "matrix-js-sdk/src/matrix";
+import { type IMyDevice } from "matrix-js-sdk/src/matrix";
 
-import { ExtendedDeviceInformation } from "../../../../utils/device/parseUserAgent";
+import { type ExtendedDeviceInformation } from "../../../../utils/device/parseUserAgent";
 
 export type DeviceWithVerification = IMyDevice & {
     /**
diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts
index 8cb3f9dbc182f991b0566b8242091d083ea34466..34aec1c01237a83980f9e704e254a2026ef1d666 100644
--- a/src/components/views/settings/devices/useOwnDevices.ts
+++ b/src/components/views/settings/devices/useOwnDevices.ts
@@ -9,23 +9,23 @@ Please see LICENSE files in the repository root for full details.
 import { useCallback, useContext, useEffect, useState } from "react";
 import {
     ClientEvent,
-    IMyDevice,
-    IPusher,
+    type IMyDevice,
+    type IPusher,
     LOCAL_NOTIFICATION_SETTINGS_PREFIX,
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     PUSHER_DEVICE_ID,
     PUSHER_ENABLED,
     UNSTABLE_MSC3852_LAST_SEEN_UA,
-    MatrixError,
-    LocalNotificationSettings,
+    type MatrixError,
+    type LocalNotificationSettings,
 } from "matrix-js-sdk/src/matrix";
-import { VerificationRequest, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import { type VerificationRequest, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../../languageHandler";
 import { getDeviceClientInformation, pruneClientInformation } from "../../../../utils/device/clientInformation";
-import { DevicesDictionary, ExtendedDevice, ExtendedDeviceAppInfo } from "./types";
+import { type DevicesDictionary, type ExtendedDevice, type ExtendedDeviceAppInfo } from "./types";
 import { useEventEmitter } from "../../../../hooks/useEventEmitter";
 import { parseUserAgent } from "../../../../utils/device/parseUserAgent";
 import { isDeviceVerified } from "../../../../utils/device/isDeviceVerified";
diff --git a/src/components/views/settings/discovery/DiscoverySettings.tsx b/src/components/views/settings/discovery/DiscoverySettings.tsx
index 28913f289d1895c6e7fab3eb52499e44cd070ef0..8bfd7d144d8462f300676a9acb48845e2479539c 100644
--- a/src/components/views/settings/discovery/DiscoverySettings.tsx
+++ b/src/components/views/settings/discovery/DiscoverySettings.tsx
@@ -13,19 +13,19 @@ import { Alert } from "@vector-im/compound-web";
 
 import { getThreepidsWithBindStatus } from "../../../../boundThreepids";
 import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
-import { ThirdPartyIdentifier } from "../../../../AddThreepid";
+import { type ThirdPartyIdentifier } from "../../../../AddThreepid";
 import SettingsStore from "../../../../settings/SettingsStore";
 import { UIFeature } from "../../../../settings/UIFeature";
 import { _t } from "../../../../languageHandler";
 import SetIdServer from "../SetIdServer";
 import { SettingsSubsection } from "../shared/SettingsSubsection";
 import InlineTermsAgreement from "../../terms/InlineTermsAgreement";
-import { Service, ServicePolicyPair, startTermsFlow } from "../../../../Terms";
+import { Service, type ServicePolicyPair, startTermsFlow } from "../../../../Terms";
 import IdentityAuthClient from "../../../../IdentityAuthClient";
 import { abbreviateUrl } from "../../../../utils/UrlUtils";
 import { useDispatcher } from "../../../../hooks/useDispatcher";
 import defaultDispatcher from "../../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../../dispatcher/payloads";
 import { AddRemoveThreepids } from "../AddRemoveThreepids";
 
 type RequiredPolicyInfo =
@@ -58,7 +58,7 @@ export const DiscoverySettings: React.FC = () => {
         agreedUrls: null, // From the startTermsFlow callback
         resolve: null, // Promise resolve function for startTermsFlow callback
     });
-    const [hasTerms, setHasTerms] = useState<boolean>(false);
+    const [mustAgreeToTerms, setMustAgreeToTerms] = useState<boolean>(false);
 
     const getThreepidState = useCallback(async () => {
         setIsLoadingThreepids(true);
@@ -103,7 +103,7 @@ export const DiscoverySettings: React.FC = () => {
                         (policiesAndServices, agreedUrls, extraClassNames) => {
                             return new Promise((resolve) => {
                                 setIdServerName(abbreviateUrl(idServerUrl));
-                                setHasTerms(true);
+                                setMustAgreeToTerms(true);
                                 setRequiredPolicyInfo({
                                     policiesAndServices,
                                     agreedUrls,
@@ -113,7 +113,7 @@ export const DiscoverySettings: React.FC = () => {
                         },
                     );
                     // User accepted all terms
-                    setHasTerms(false);
+                    setMustAgreeToTerms(false);
                 } catch (e) {
                     logger.warn(
                         `Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`,
@@ -126,7 +126,7 @@ export const DiscoverySettings: React.FC = () => {
 
     if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
 
-    if (hasTerms && requiredPolicyInfo.policiesAndServices) {
+    if (mustAgreeToTerms && requiredPolicyInfo.policiesAndServices) {
         const intro = (
             <Alert type="info" title={_t("settings|general|discovery_needs_terms_title")}>
                 {_t("settings|general|discovery_needs_terms", { serverName: idServerName })}
@@ -160,7 +160,7 @@ export const DiscoverySettings: React.FC = () => {
                         medium={ThreepidMedium.Email}
                         threepids={emails}
                         onChange={getThreepidState}
-                        disabled={!hasTerms}
+                        disabled={mustAgreeToTerms}
                         isLoading={isLoadingThreepids}
                     />
                 </SettingsSubsection>
@@ -174,7 +174,7 @@ export const DiscoverySettings: React.FC = () => {
                         medium={ThreepidMedium.Phone}
                         threepids={phoneNumbers}
                         onChange={getThreepidState}
-                        disabled={!hasTerms}
+                        disabled={mustAgreeToTerms}
                         isLoading={isLoadingThreepids}
                     />
                 </SettingsSubsection>
diff --git a/src/components/views/settings/encryption/AdvancedPanel.tsx b/src/components/views/settings/encryption/AdvancedPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5e895470514339817b794baa5f041e4aebfcc8b
--- /dev/null
+++ b/src/components/views/settings/encryption/AdvancedPanel.tsx
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX, lazy, type MouseEventHandler } from "react";
+import { Button, HelpMessage, InlineField, InlineSpinner, Label, Root, ToggleControl } from "@vector-im/compound-web";
+import DownloadIcon from "@vector-im/compound-design-tokens/assets/web/icons/download";
+import ShareIcon from "@vector-im/compound-design-tokens/assets/web/icons/share";
+
+import { _t } from "../../../../languageHandler";
+import { SettingsSection } from "../shared/SettingsSection";
+import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
+import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
+import Modal from "../../../../Modal";
+import { SettingLevel } from "../../../../settings/SettingLevel";
+import { useSettingValueAt } from "../../../../hooks/useSettings";
+import SettingsStore from "../../../../settings/SettingsStore";
+
+interface AdvancedPanelProps {
+    /**
+     * Callback for when the user clicks the button to reset their identity.
+     */
+    onResetIdentityClick: MouseEventHandler<HTMLButtonElement>;
+}
+
+/**
+ * The advanced panel of the encryption settings.
+ */
+export function AdvancedPanel({ onResetIdentityClick }: AdvancedPanelProps): JSX.Element {
+    return (
+        <SettingsSection heading={_t("settings|encryption|advanced|title")} legacy={false}>
+            <EncryptionDetails onResetIdentityClick={onResetIdentityClick} />
+            <OtherSettings />
+        </SettingsSection>
+    );
+}
+
+interface EncryptionDetails {
+    /**
+     * Callback for when the user clicks the button to reset their identity.
+     */
+    onResetIdentityClick: MouseEventHandler<HTMLButtonElement>;
+}
+
+/**
+ * The encryption details section of the advanced panel.
+ */
+function EncryptionDetails({ onResetIdentityClick }: EncryptionDetails): JSX.Element {
+    const matrixClient = useMatrixClientContext();
+    // Null when the keys are not loaded yet
+    const keys = useAsyncMemo(() => matrixClient.getCrypto()!.getOwnDeviceKeys(), [matrixClient], null);
+
+    return (
+        <div className="mx_EncryptionDetails" data-testid="encryptionDetails">
+            <div className="mx_EncryptionDetails_session">
+                <h3 className="mx_EncryptionDetails_session_title">
+                    {_t("settings|encryption|advanced|details_title")}
+                </h3>
+                <div>
+                    <span>{_t("settings|encryption|advanced|session_id")}</span>
+                    <span data-testid="deviceId">{matrixClient.deviceId}</span>
+                </div>
+                <div>
+                    <span>{_t("settings|encryption|advanced|session_key")}</span>
+                    <span data-testid="sessionKey">
+                        {keys ? keys.ed25519 : <InlineSpinner aria-label={_t("common|loading")} />}
+                    </span>
+                </div>
+            </div>
+            <div className="mx_EncryptionDetails_buttons">
+                <Button
+                    size="sm"
+                    kind="secondary"
+                    Icon={ShareIcon}
+                    onClick={() =>
+                        Modal.createDialog(
+                            lazy(
+                                () => import("../../../../async-components/views/dialogs/security/ExportE2eKeysDialog"),
+                            ),
+                            { matrixClient },
+                        )
+                    }
+                >
+                    {_t("settings|encryption|advanced|export_keys")}
+                </Button>
+                <Button
+                    size="sm"
+                    kind="secondary"
+                    Icon={DownloadIcon}
+                    onClick={() =>
+                        Modal.createDialog(
+                            lazy(
+                                () => import("../../../../async-components/views/dialogs/security/ImportE2eKeysDialog"),
+                            ),
+                            { matrixClient },
+                        )
+                    }
+                >
+                    {_t("settings|encryption|advanced|import_keys")}
+                </Button>
+            </div>
+            <Button size="sm" kind="tertiary" destructive={true} onClick={onResetIdentityClick}>
+                {_t("settings|encryption|advanced|reset_identity")}
+            </Button>
+        </div>
+    );
+}
+
+/**
+ * Display the never send encrypted message to unverified devices setting.
+ */
+function OtherSettings(): JSX.Element | null {
+    const blacklistUnverifiedDevices = useSettingValueAt(SettingLevel.DEVICE, "blacklistUnverifiedDevices");
+    const canSetValue = SettingsStore.canSetValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE);
+    if (!canSetValue) return null;
+
+    return (
+        <Root
+            data-testid="otherSettings"
+            className="mx_OtherSettings"
+            onChange={async (evt) => {
+                const checked = new FormData(evt.currentTarget).get("neverSendEncrypted") === "on";
+                await SettingsStore.setValue("blacklistUnverifiedDevices", null, SettingLevel.DEVICE, checked);
+            }}
+        >
+            <h3 className="mx_OtherSettings_title">{_t("settings|encryption|advanced|other_people_device_title")}</h3>
+            <InlineField
+                name="neverSendEncrypted"
+                control={<ToggleControl name="neverSendEncrypted" defaultChecked={blacklistUnverifiedDevices} />}
+            >
+                <Label>{_t("settings|encryption|advanced|other_people_device_label")}</Label>
+                <HelpMessage>{_t("settings|encryption|advanced|other_people_device_description")}</HelpMessage>
+            </InlineField>
+        </Root>
+    );
+}
diff --git a/src/components/views/settings/encryption/ChangeRecoveryKey.tsx b/src/components/views/settings/encryption/ChangeRecoveryKey.tsx
index 7e02d7debde582ad58d6d5c681b562ae98c0c707..58efc80afb9dfcaa31741efa9fb2b94869c9481d 100644
--- a/src/components/views/settings/encryption/ChangeRecoveryKey.tsx
+++ b/src/components/views/settings/encryption/ChangeRecoveryKey.tsx
@@ -5,7 +5,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { FormEventHandler, JSX, MouseEventHandler, useState } from "react";
+import React, { type FormEventHandler, type JSX, type MouseEventHandler, useState } from "react";
 import {
     Breadcrumb,
     Button,
@@ -18,14 +18,17 @@ import {
     TextControl,
 } from "@vector-im/compound-web";
 import CopyIcon from "@vector-im/compound-design-tokens/assets/web/icons/copy";
-import { logger } from "matrix-js-sdk/src/logger";
+import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
 
 import { _t } from "../../../../languageHandler";
 import { EncryptionCard } from "./EncryptionCard";
 import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
 import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
 import { copyPlaintext } from "../../../../utils/strings";
+import { initialiseDehydrationIfEnabled } from "../../../../utils/device/dehydration.ts";
 import { withSecretStorageKeyCache } from "../../../../SecurityManager";
+import { EncryptionCardButtons } from "./EncryptionCardButtons";
+import { logErrorAndShowErrorDialog } from "../../../../utils/ErrorUtils.tsx";
 
 /**
  * The possible states of the component.
@@ -121,15 +124,16 @@ export function ChangeRecoveryKey({
                         try {
                             // We need to enable the cache to avoid to prompt the user to enter the new key
                             // when we will try to access the secret storage during the bootstrap
-                            await withSecretStorageKeyCache(() =>
-                                crypto.bootstrapSecretStorage({
+                            await withSecretStorageKeyCache(async () => {
+                                await crypto.bootstrapSecretStorage({
                                     setupNewSecretStorage: true,
                                     createSecretStorageKey: async () => recoveryKey,
-                                }),
-                            );
+                                });
+                                await initialiseDehydrationIfEnabled(matrixClient, { createNewKey: true });
+                            });
                             onFinish();
                         } catch (e) {
-                            logger.error("Failed to bootstrap secret storage", e);
+                            logErrorAndShowErrorDialog("Failed to set up secret storage", e);
                         }
                     }}
                     submitButtonLabel={
@@ -157,7 +161,12 @@ export function ChangeRecoveryKey({
                 pages={pages}
                 onPageClick={onCancelClick}
             />
-            <EncryptionCard title={labels.title} description={labels.description} className="mx_ChangeRecoveryKey">
+            <EncryptionCard
+                Icon={KeyIcon}
+                title={labels.title}
+                description={labels.description}
+                className="mx_ChangeRecoveryKey"
+            >
                 {content}
             </EncryptionCard>
         </>
@@ -231,12 +240,12 @@ function InformationPanel({ onContinueClick, onCancelClick }: InformationPanelPr
             <Text as="span" weight="medium" className="mx_InformationPanel_description">
                 {_t("settings|encryption|recovery|set_up_recovery_secondary_description")}
             </Text>
-            <div className="mx_ChangeRecoveryKey_footer">
+            <EncryptionCardButtons>
                 <Button onClick={onContinueClick}>{_t("action|continue")}</Button>
                 <Button kind="tertiary" onClick={onCancelClick}>
                     {_t("action|cancel")}
                 </Button>
-            </div>
+            </EncryptionCardButtons>
         </>
     );
 }
@@ -278,12 +287,12 @@ function KeyPanel({ recoveryKey, onConfirmClick, onCancelClick }: KeyPanelProps)
                     <CopyIcon />
                 </IconButton>
             </div>
-            <div className="mx_ChangeRecoveryKey_footer">
+            <EncryptionCardButtons>
                 <Button onClick={onConfirmClick}>{_t("action|continue")}</Button>
                 <Button kind="tertiary" onClick={onCancelClick}>
                     {_t("action|cancel")}
                 </Button>
-            </div>
+            </EncryptionCardButtons>
         </>
     );
 }
@@ -341,12 +350,12 @@ function KeyForm({ onCancelClick, onSubmit, recoveryKey, submitButtonLabel }: Ke
                     <ErrorMessage>{_t("settings|encryption|recovery|enter_key_error")}</ErrorMessage>
                 )}
             </Field>
-            <div className="mx_ChangeRecoveryKey_footer">
+            <EncryptionCardButtons>
                 <Button disabled={!isKeyValid}>{submitButtonLabel}</Button>
                 <Button kind="tertiary" onClick={onCancelClick}>
                     {_t("action|cancel")}
                 </Button>
-            </div>
+            </EncryptionCardButtons>
         </Root>
     );
 }
diff --git a/src/components/views/settings/encryption/DeleteKeyStoragePanel.tsx b/src/components/views/settings/encryption/DeleteKeyStoragePanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d4f80d7eeefc8bdd97102f3c78a3a015b40db3d4
--- /dev/null
+++ b/src/components/views/settings/encryption/DeleteKeyStoragePanel.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { Breadcrumb, Button, VisualList, VisualListItem } from "@vector-im/compound-web";
+import CrossIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
+import React, { type JSX, useCallback, useState } from "react";
+
+import { _t } from "../../../../languageHandler";
+import { EncryptionCard } from "./EncryptionCard";
+import { useKeyStoragePanelViewModel } from "../../../viewmodels/settings/encryption/KeyStoragePanelViewModel";
+import SdkConfig from "../../../../SdkConfig";
+import { EncryptionCardButtons } from "./EncryptionCardButtons";
+import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
+
+interface Props {
+    /**
+     * Called when the user either cancels the operation or key storage has been disabled
+     */
+    onFinish: () => void;
+}
+
+/**
+ * Confirms that the user really wants to turn off and delete their key storage.  Part of the "Encryption" settings tab.
+ */
+export function DeleteKeyStoragePanel({ onFinish }: Props): JSX.Element {
+    const { setEnabled } = useKeyStoragePanelViewModel();
+    const [busy, setBusy] = useState(false);
+
+    const onDeleteClick = useCallback(async () => {
+        setBusy(true);
+        try {
+            await setEnabled(false);
+        } finally {
+            setBusy(false);
+        }
+        onFinish();
+    }, [setEnabled, onFinish]);
+
+    return (
+        <>
+            <Breadcrumb
+                backLabel={_t("action|back")}
+                onBackClick={onFinish}
+                pages={[_t("settings|encryption|title"), _t("settings|encryption|delete_key_storage|breadcrumb_page")]}
+                onPageClick={onFinish}
+            />
+            <EncryptionCard
+                Icon={ErrorIcon}
+                destructive={true}
+                title={_t("settings|encryption|delete_key_storage|title")}
+            >
+                <EncryptionCardEmphasisedContent>
+                    {_t("settings|encryption|delete_key_storage|description")}
+                    <VisualList>
+                        <VisualListItem Icon={CrossIcon} destructive={true}>
+                            {_t("settings|encryption|delete_key_storage|list_first")}
+                        </VisualListItem>
+                        <VisualListItem Icon={CrossIcon} destructive={true}>
+                            {_t("settings|encryption|delete_key_storage|list_second", { brand: SdkConfig.get().brand })}
+                        </VisualListItem>
+                    </VisualList>
+                </EncryptionCardEmphasisedContent>
+                <EncryptionCardButtons>
+                    <Button destructive={true} onClick={onDeleteClick} disabled={busy}>
+                        {_t("settings|encryption|delete_key_storage|confirm")}
+                    </Button>
+                    <Button kind="tertiary" onClick={onFinish}>
+                        {_t("action|cancel")}
+                    </Button>
+                </EncryptionCardButtons>
+            </EncryptionCard>
+        </>
+    );
+}
diff --git a/src/components/views/settings/encryption/EncryptionCard.tsx b/src/components/views/settings/encryption/EncryptionCard.tsx
index 8a10802cc3ec48d7673363b5177bbb1b1172260f..89ce2cbb17bc3b9d91f87d9cecbb2e0986150981 100644
--- a/src/components/views/settings/encryption/EncryptionCard.tsx
+++ b/src/components/views/settings/encryption/EncryptionCard.tsx
@@ -5,9 +5,8 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, PropsWithChildren } from "react";
+import React, { type JSX, type PropsWithChildren, type ComponentType, type SVGAttributes } from "react";
 import { BigIcon, Heading } from "@vector-im/compound-web";
-import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
 import classNames from "classnames";
 
 interface EncryptionCardProps {
@@ -22,7 +21,15 @@ interface EncryptionCardProps {
     /**
      * The description of the card.
      */
-    description: string;
+    description?: string;
+    /**
+     * Whether this icon shows a destructive action.
+     */
+    destructive?: boolean;
+    /**
+     * The icon to display.
+     */
+    Icon: ComponentType<SVGAttributes<SVGElement>>;
 }
 
 /**
@@ -32,18 +39,20 @@ export function EncryptionCard({
     title,
     description,
     className,
+    destructive = false,
+    Icon,
     children,
 }: PropsWithChildren<EncryptionCardProps>): JSX.Element {
     return (
         <div className={classNames("mx_EncryptionCard", className)}>
             <div className="mx_EncryptionCard_header">
-                <BigIcon>
-                    <KeyIcon />
+                <BigIcon destructive={destructive}>
+                    <Icon />
                 </BigIcon>
                 <Heading as="h2" size="sm" weight="semibold">
                     {title}
                 </Heading>
-                <span>{description}</span>
+                {description && <span>{description}</span>}
             </div>
             {children}
         </div>
diff --git a/src/components/views/settings/encryption/EncryptionCardButtons.tsx b/src/components/views/settings/encryption/EncryptionCardButtons.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..06e5ef60d7f5858c3a59b6e70803499941886190
--- /dev/null
+++ b/src/components/views/settings/encryption/EncryptionCardButtons.tsx
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX, type PropsWithChildren } from "react";
+
+/**
+ * A component to present action buttons at the bottom of an {@link EncryptionCard}
+ * (mostly as somewhere for the common CSS to live).
+ */
+export function EncryptionCardButtons({ children }: PropsWithChildren): JSX.Element {
+    return <div className="mx_EncryptionCard_buttons">{children}</div>;
+}
diff --git a/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx b/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e630ebaa9faa10fdeaf15023979bdbba8054af2a
--- /dev/null
+++ b/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX, type PropsWithChildren } from "react";
+
+import { Flex } from "../../../utils/Flex";
+
+/**
+ * A component for emphasised text within an {@link EncryptionCard}
+ * (mostly as somewhere for the common CSS to live).
+ */
+export function EncryptionCardEmphasisedContent({ children }: PropsWithChildren): JSX.Element {
+    return (
+        <Flex
+            direction="column"
+            gap="var(--cpd-space-3x)"
+            align="normal"
+            className="mx_EncryptionCard_emphasisedContent"
+        >
+            {children}
+        </Flex>
+    );
+}
diff --git a/src/components/views/settings/encryption/KeyStoragePanel.tsx b/src/components/views/settings/encryption/KeyStoragePanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0de55942a7b85a2fc588154b16585086527ba7fe
--- /dev/null
+++ b/src/components/views/settings/encryption/KeyStoragePanel.tsx
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { useCallback } from "react";
+import { InlineField, InlineSpinner, Label, Root, ToggleControl } from "@vector-im/compound-web";
+
+import type { FormEvent } from "react";
+import { SettingsSection } from "../shared/SettingsSection";
+import { _t } from "../../../../languageHandler";
+import { SettingsHeader } from "../SettingsHeader";
+import { useKeyStoragePanelViewModel } from "../../../viewmodels/settings/encryption/KeyStoragePanelViewModel";
+
+interface Props {
+    /**
+     * Called when the user turns off the "allow key storage" toggle
+     */
+    onKeyStorageDisableClick: () => void;
+}
+
+/**
+ * This component allows the user to set up or change their recovery key.
+ *
+ * It is used within the "Encryption" settings tab.
+ */
+export const KeyStoragePanel: React.FC<Props> = ({ onKeyStorageDisableClick }) => {
+    const { isEnabled, setEnabled, loading, busy } = useKeyStoragePanelViewModel();
+
+    const onKeyBackupChange = useCallback(
+        (e: FormEvent<HTMLInputElement>) => {
+            if (e.currentTarget.checked) {
+                setEnabled(true);
+            } else {
+                onKeyStorageDisableClick();
+            }
+        },
+        [setEnabled, onKeyStorageDisableClick],
+    );
+
+    if (loading) {
+        return <InlineSpinner aria-label={_t("common|loading")} />;
+    }
+
+    return (
+        <SettingsSection
+            legacy={false}
+            heading={
+                <SettingsHeader
+                    hasRecommendedTag={isEnabled === false}
+                    label={_t("settings|encryption|key_storage|title")}
+                />
+            }
+            subHeading={_t("settings|encryption|key_storage|description", undefined, {
+                a: (sub) => (
+                    <a href="https://element.io/help#encryption5" target="_blank" rel="noreferrer noopener">
+                        {sub}
+                    </a>
+                ),
+            })}
+        >
+            <Root className="mx_KeyStoragePanel_toggleRow">
+                <InlineField
+                    name="keyStorage"
+                    control={<ToggleControl name="keyStorage" checked={isEnabled} onChange={onKeyBackupChange} />}
+                >
+                    <Label>{_t("settings|encryption|key_storage|allow_key_storage")}</Label>
+                </InlineField>
+                {busy && <InlineSpinner />}
+            </Root>
+        </SettingsSection>
+    );
+};
diff --git a/src/components/views/settings/encryption/RecoveryPanel.tsx b/src/components/views/settings/encryption/RecoveryPanel.tsx
index 19d81668eb8d0f572b869b06299076e22db33a36..9c7f12efc81742dd68ccb002f347db1b75a38a84 100644
--- a/src/components/views/settings/encryption/RecoveryPanel.tsx
+++ b/src/components/views/settings/encryption/RecoveryPanel.tsx
@@ -5,7 +5,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, useCallback, useEffect, useState } from "react";
+import React, { type JSX } from "react";
 import { Button, InlineSpinner } from "@vector-im/compound-web";
 import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
 
@@ -13,18 +13,15 @@ import { SettingsSection } from "../shared/SettingsSection";
 import { _t } from "../../../../languageHandler";
 import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
 import { SettingsHeader } from "../SettingsHeader";
-import { accessSecretStorage } from "../../../../SecurityManager";
-import { SettingsSubheader } from "../SettingsSubheader";
+import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
 
 /**
  * The possible states of the recovery panel.
  * - `loading`: We are checking the recovery key and the secrets.
  * - `missing_recovery_key`: The user has no recovery key.
- * - `secrets_not_cached`: The user has a recovery key but the secrets are not cached.
- *                         This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
  * - `good`: The user has a recovery key and the secrets are cached.
  */
-type State = "loading" | "missing_recovery_key" | "secrets_not_cached" | "good";
+type State = "loading" | "missing_recovery_key" | "good";
 
 interface RecoveryPanelProps {
     /**
@@ -40,29 +37,18 @@ interface RecoveryPanelProps {
  * This component allows the user to set up or change their recovery key.
  */
 export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps): JSX.Element {
-    const [state, setState] = useState<State>("loading");
-    const isMissingRecoveryKey = state === "missing_recovery_key";
-
     const matrixClient = useMatrixClientContext();
-
-    const checkEncryption = useCallback(async () => {
-        const crypto = matrixClient.getCrypto()!;
-
-        // Check if the user has a recovery key
-        const hasRecoveryKey = Boolean(await matrixClient.secretStorage.getDefaultKeyId());
-        if (!hasRecoveryKey) return setState("missing_recovery_key");
-
-        // Check if the secrets are cached
-        const cachedSecrets = (await crypto.getCrossSigningStatus()).privateKeysCachedLocally;
-        const secretsOk = cachedSecrets.masterKey && cachedSecrets.selfSigningKey && cachedSecrets.userSigningKey;
-        if (!secretsOk) return setState("secrets_not_cached");
-
-        setState("good");
-    }, [matrixClient]);
-
-    useEffect(() => {
-        checkEncryption();
-    }, [checkEncryption]);
+    const state = useAsyncMemo<State>(
+        async () => {
+            // Check if the user has a recovery key
+            const hasRecoveryKey = Boolean(await matrixClient.secretStorage.getDefaultKeyId());
+            if (hasRecoveryKey) return "good";
+            else return "missing_recovery_key";
+        },
+        [matrixClient],
+        "loading",
+    );
+    const isMissingRecoveryKey = state === "missing_recovery_key";
 
     let content: JSX.Element;
     switch (state) {
@@ -76,18 +62,6 @@ export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps):
                 </Button>
             );
             break;
-        case "secrets_not_cached":
-            content = (
-                <Button
-                    size="sm"
-                    kind="primary"
-                    Icon={KeyIcon}
-                    onClick={async () => await accessSecretStorage(checkEncryption)}
-                >
-                    {_t("settings|encryption|recovery|enter_recovery_key")}
-                </Button>
-            );
-            break;
         case "good":
             content = (
                 <Button size="sm" kind="secondary" Icon={KeyIcon} onClick={() => onChangeRecoveryKeyClick(false)}>
@@ -105,32 +79,10 @@ export function RecoveryPanel({ onChangeRecoveryKeyClick }: RecoveryPanelProps):
                     label={_t("settings|encryption|recovery|title")}
                 />
             }
-            subHeading={<Subheader state={state} />}
+            subHeading={_t("settings|encryption|recovery|description")}
+            data-testid="recoveryPanel"
         >
             {content}
         </SettingsSection>
     );
 }
-
-interface SubheaderProps {
-    /**
-     * The state of the recovery panel.
-     */
-    state: State;
-}
-
-/**
- * The subheader for the recovery panel.
- */
-function Subheader({ state }: SubheaderProps): JSX.Element {
-    // If the secrets are not cached, we display a warning message.
-    if (state !== "secrets_not_cached") return <>{_t("settings|encryption|recovery|description")}</>;
-
-    return (
-        <SettingsSubheader
-            label={_t("settings|encryption|recovery|description")}
-            state="error"
-            stateMessage={_t("settings|encryption|recovery|key_storage_warning")}
-        />
-    );
-}
diff --git a/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx b/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a5d47100d6f9a32046de0228851f172d3ea44fbd
--- /dev/null
+++ b/src/components/views/settings/encryption/RecoveryPanelOutOfSync.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import { Button } from "@vector-im/compound-web";
+import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
+
+import { SettingsSection } from "../shared/SettingsSection";
+import { _t } from "../../../../languageHandler";
+import { SettingsSubheader } from "../SettingsSubheader";
+import { accessSecretStorage } from "../../../../SecurityManager";
+
+interface RecoveryPanelOutOfSyncProps {
+    /**
+     * Callback for when the user has finished entering their recovery key.
+     */
+    onFinish: () => void;
+    /**
+     * Callback for when the user clicks on the "Forgot recovery key?" button.
+     */
+    onForgotRecoveryKey: () => void;
+}
+
+/**
+ * This component is shown as part of the {@link EncryptionUserSettingsTab}, instead of the
+ * {@link RecoveryPanel}, when some of the user secrets are not cached in the local client.
+ *
+ * It prompts the user to enter their recovery key so that the secrets can be loaded from 4S into
+ * the client.
+ */
+export function RecoveryPanelOutOfSync({ onForgotRecoveryKey, onFinish }: RecoveryPanelOutOfSyncProps): JSX.Element {
+    return (
+        <SettingsSection
+            legacy={false}
+            heading={_t("settings|encryption|recovery|title")}
+            subHeading={
+                <SettingsSubheader
+                    label={_t("settings|encryption|recovery|description")}
+                    state="error"
+                    stateMessage={_t("settings|encryption|recovery|key_storage_warning")}
+                />
+            }
+            data-testid="recoveryPanel"
+        >
+            <div className="mx_RecoveryPanelOutOfSync">
+                <Button size="sm" kind="secondary" onClick={onForgotRecoveryKey}>
+                    {_t("settings|encryption|recovery|forgot_recovery_key")}
+                </Button>
+                <Button
+                    size="sm"
+                    kind="primary"
+                    Icon={KeyIcon}
+                    onClick={async () => {
+                        await accessSecretStorage();
+                        onFinish();
+                    }}
+                >
+                    {_t("settings|encryption|recovery|enter_recovery_key")}
+                </Button>
+            </div>
+        </SettingsSection>
+    );
+}
diff --git a/src/components/views/settings/encryption/ResetIdentityBody.tsx b/src/components/views/settings/encryption/ResetIdentityBody.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a6b0b2c12ee4bfe0abef91c65f00cf68c35769d3
--- /dev/null
+++ b/src/components/views/settings/encryption/ResetIdentityBody.tsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2024-2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
+import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
+import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
+import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
+import React, { type JSX, useState } from "react";
+
+import { _t } from "../../../../languageHandler";
+import { EncryptionCard } from "./EncryptionCard";
+import { uiAuthCallback } from "../../../../CreateCrossSigning";
+import { EncryptionCardButtons } from "./EncryptionCardButtons";
+import { EncryptionCardEmphasisedContent } from "./EncryptionCardEmphasisedContent";
+import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
+
+interface ResetIdentityBodyProps {
+    /**
+     * Called when the identity is reset.
+     */
+    onReset: () => void;
+
+    /**
+     * Called when the cancel button is clicked.
+     */
+    onCancelClick: () => void;
+
+    /**
+     * The variant of the panel to show. We show more warnings in the 'compromised' variant (no use in showing a user
+     * this warning if they have to reset because they no longer have their key)
+     */
+    variant: ResetIdentityBodyVariant;
+}
+
+/**
+ * The variant of the panel to show.  This affects the message displayed to the user.
+ *
+ * "compromised" is shown when the user chose 'Reset cryptographic identity' explicitly in settings, usually because
+ * they believe their identity has been compromised.
+ *
+ * "sync_failed" is shown when the user tried to recover their identity but the process failed, probably because
+ * the required information is missing from recovery.
+ *
+ * "forgot" is shown when the user chose 'Forgot recovery key?' during `SetupEncryptionToast`.
+ *
+ * "confirm" is shown when the user chose 'Reset all' during `SetupEncryptionBody`.
+ */
+export type ResetIdentityBodyVariant = "compromised" | "forgot" | "sync_failed" | "confirm";
+
+/**
+ * User interface component allowing the user to reset their cryptographic identity.
+ *
+ * Used by {@link ResetIdentityPanel}.
+ */
+export function ResetIdentityBody({ onCancelClick, onReset, variant }: ResetIdentityBodyProps): JSX.Element {
+    const matrixClient = useMatrixClientContext();
+
+    // After the user clicks "Continue", we disable the button so it can't be
+    // clicked again, and warn the user not to close the window.
+    const [inProgress, setInProgress] = useState(false);
+
+    return (
+        <EncryptionCard Icon={ErrorIcon} destructive={true} title={titleForVariant(variant)}>
+            <EncryptionCardEmphasisedContent>
+                <VisualList>
+                    <VisualListItem Icon={CheckIcon} success={true}>
+                        {_t("settings|encryption|advanced|breadcrumb_first_description")}
+                    </VisualListItem>
+                    <VisualListItem Icon={InfoIcon}>
+                        {_t("settings|encryption|advanced|breadcrumb_second_description")}
+                    </VisualListItem>
+                    <VisualListItem Icon={InfoIcon}>
+                        {_t("settings|encryption|advanced|breadcrumb_third_description")}
+                    </VisualListItem>
+                </VisualList>
+                {variant === "compromised" && <span>{_t("settings|encryption|advanced|breadcrumb_warning")}</span>}
+            </EncryptionCardEmphasisedContent>
+            <EncryptionCardButtons>
+                <Button
+                    destructive={true}
+                    disabled={inProgress}
+                    onClick={async () => {
+                        setInProgress(true);
+                        await matrixClient
+                            .getCrypto()
+                            ?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
+                        onReset();
+                    }}
+                >
+                    {inProgress ? (
+                        <>
+                            <InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
+                        </>
+                    ) : (
+                        _t("action|continue")
+                    )}
+                </Button>
+                {inProgress ? (
+                    <EncryptionCardEmphasisedContent>
+                        <span className="mx_ResetIdentityPanel_warning">
+                            {_t("settings|encryption|advanced|do_not_close_warning")}
+                        </span>
+                    </EncryptionCardEmphasisedContent>
+                ) : (
+                    <Button kind="tertiary" onClick={onCancelClick}>
+                        {_t("action|cancel")}
+                    </Button>
+                )}
+            </EncryptionCardButtons>
+        </EncryptionCard>
+    );
+}
+
+function titleForVariant(variant: ResetIdentityBodyVariant): string {
+    switch (variant) {
+        case "compromised":
+        case "confirm":
+            return _t("settings|encryption|advanced|breadcrumb_title");
+        case "sync_failed":
+            return _t("settings|encryption|advanced|breadcrumb_title_sync_failed");
+        case "forgot":
+            return _t("settings|encryption|advanced|breadcrumb_title_forgot");
+    }
+}
diff --git a/src/components/views/settings/encryption/ResetIdentityPanel.tsx b/src/components/views/settings/encryption/ResetIdentityPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..45e34fbbacfe434d3091f0f14d2c674ac7da0d3d
--- /dev/null
+++ b/src/components/views/settings/encryption/ResetIdentityPanel.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { Breadcrumb } from "@vector-im/compound-web";
+import React, { type JSX } from "react";
+
+import { _t } from "../../../../languageHandler";
+import { ResetIdentityBody, type ResetIdentityBodyVariant } from "./ResetIdentityBody";
+
+interface ResetIdentityPanelProps {
+    /**
+     * Called when the identity is reset.
+     */
+    onReset: () => void;
+
+    /**
+     * Called when the cancel button is clicked or when we go back in the breadcrumbs.
+     */
+    onCancelClick: () => void;
+
+    /**
+     * Which variant of this panel to show.
+     */
+    variant: ResetIdentityBodyVariant;
+}
+
+/**
+ * The Encryption Settings panel for resetting the identity of the current user.
+ *
+ * A thin wrapper around {@link ResetIdentityBody}, just adding breadcrumbs.
+ */
+export function ResetIdentityPanel({ onCancelClick, onReset, variant }: ResetIdentityPanelProps): JSX.Element {
+    return (
+        <>
+            <Breadcrumb
+                backLabel={_t("action|back")}
+                onBackClick={onCancelClick}
+                pages={[_t("settings|encryption|title"), _t("settings|encryption|advanced|breadcrumb_page")]}
+                onPageClick={onCancelClick}
+            />
+            <ResetIdentityBody onReset={onReset} onCancelClick={onCancelClick} variant={variant} />
+        </>
+    );
+}
diff --git a/src/components/views/settings/notifications/NotificationPusherSettings.tsx b/src/components/views/settings/notifications/NotificationPusherSettings.tsx
index d5506f7b95792c4b18c23ec84807896133adcbe8..a0560dc9ca6641d2a1a7b1c462e22249371dd0b6 100644
--- a/src/components/views/settings/notifications/NotificationPusherSettings.tsx
+++ b/src/components/views/settings/notifications/NotificationPusherSettings.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ThreepidMedium, IPusher } from "matrix-js-sdk/src/matrix";
-import React, { useCallback, useMemo } from "react";
+import { ThreepidMedium, type IPusher } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useCallback, useMemo } from "react";
 
 import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
 import { Action } from "../../../../dispatcher/actions";
diff --git a/src/components/views/settings/notifications/NotificationSettings2.tsx b/src/components/views/settings/notifications/NotificationSettings2.tsx
index babc4c8bd564f4815a516960663db590d54b8a4a..4e91bebf7bc0669780b4d7596471e92f1ede6003 100644
--- a/src/components/views/settings/notifications/NotificationSettings2.tsx
+++ b/src/components/views/settings/notifications/NotificationSettings2.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState } from "react";
+import React, { type JSX, useState } from "react";
 
 import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg";
 import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
@@ -15,7 +15,7 @@ import { useSettingValue } from "../../../../hooks/useSettings";
 import { _t } from "../../../../languageHandler";
 import {
     DefaultNotificationSettings,
-    NotificationSettings,
+    type NotificationSettings,
 } from "../../../../models/notificationsettings/NotificationSettings";
 import { RoomNotifState } from "../../../../RoomNotifs";
 import { SettingLevel } from "../../../../settings/SettingLevel";
diff --git a/src/components/views/settings/shared/SettingsBanner.tsx b/src/components/views/settings/shared/SettingsBanner.tsx
index a5b0ac2a36a8abf5d404a2e9fb870e4a04093a4a..2558b2c80a73ea7181ef740cd15a8698074aba1e 100644
--- a/src/components/views/settings/shared/SettingsBanner.tsx
+++ b/src/components/views/settings/shared/SettingsBanner.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { PropsWithChildren, ReactNode } from "react";
+import React, { type JSX, type PropsWithChildren, type ReactNode } from "react";
 
 import AccessibleButton from "../../elements/AccessibleButton";
 
diff --git a/src/components/views/settings/shared/SettingsIndent.tsx b/src/components/views/settings/shared/SettingsIndent.tsx
index 7c280311c26a5498d781fc1700e15596e8b3fa14..abcc24412919a62989b818162fd81be4570db633 100644
--- a/src/components/views/settings/shared/SettingsIndent.tsx
+++ b/src/components/views/settings/shared/SettingsIndent.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 
 export interface SettingsIndentProps extends HTMLAttributes<HTMLDivElement> {
     children?: React.ReactNode;
diff --git a/src/components/views/settings/shared/SettingsSection.tsx b/src/components/views/settings/shared/SettingsSection.tsx
index 4763c51e564d41b392b19585b1d25981a747339d..b43b8a4469a45a52752fc89a92cb30adf272b709 100644
--- a/src/components/views/settings/shared/SettingsSection.tsx
+++ b/src/components/views/settings/shared/SettingsSection.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classnames from "classnames";
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 
 import Heading from "../../typography/Heading";
 import { SettingsHeader } from "../SettingsHeader";
diff --git a/src/components/views/settings/shared/SettingsSubsection.tsx b/src/components/views/settings/shared/SettingsSubsection.tsx
index cbb20f771b59fb2c20d73e74a48e0d304941763e..6f7fdde1f0c60aa7aa2caf0182f3f528b9eae033 100644
--- a/src/components/views/settings/shared/SettingsSubsection.tsx
+++ b/src/components/views/settings/shared/SettingsSubsection.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 import { Separator } from "@vector-im/compound-web";
 
 import { SettingsSubsectionHeading } from "./SettingsSubsectionHeading";
diff --git a/src/components/views/settings/shared/SettingsSubsectionHeading.tsx b/src/components/views/settings/shared/SettingsSubsectionHeading.tsx
index 61c29642a9c9e924bdbc426d9f8fffe8d58ba419..63fb79c637dddff018833d19dcd38c9e8c33eb92 100644
--- a/src/components/views/settings/shared/SettingsSubsectionHeading.tsx
+++ b/src/components/views/settings/shared/SettingsSubsectionHeading.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 
 import Heading from "../../typography/Heading";
 
diff --git a/src/components/views/settings/tabs/SettingsTab.tsx b/src/components/views/settings/tabs/SettingsTab.tsx
index ed0b1367f6fa288a5f74f7518598639e57c4b7c0..d9f5d177884826581b8b629bb63d876e105af109 100644
--- a/src/components/views/settings/tabs/SettingsTab.tsx
+++ b/src/components/views/settings/tabs/SettingsTab.tsx
@@ -5,7 +5,7 @@ Copyright 2022 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 import classNames from "classnames";
 
 export interface SettingsTabProps extends HTMLAttributes<HTMLDivElement> {
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index dbd2a635056ad8edd74e8b04512942be1b38f564..b82dc4efcda59f0ef56c7ab7af46d2db8b516cb7 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { EventType, Room } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import { EventType, type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../elements/AccessibleButton";
 import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
 import Modal from "../../../../../Modal";
 import dis from "../../../../../dispatcher/dispatcher";
 import { Action } from "../../../../../dispatcher/actions";
 import CopyableText from "../../../elements/CopyableText";
-import { ViewRoomPayload } from "../../../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../../dispatcher/payloads/ViewRoomPayload";
 import SettingsStore from "../../../../../settings/SettingsStore";
 import SettingsTab from "../SettingsTab";
 import { SettingsSection } from "../../shared/SettingsSection";
diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
index ed80cc31e83b11b7a64d381e0d01900196ef3aed..42fa45e0162bdca90720bf8c02e0082943b5c80e 100644
--- a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
-import { Room, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, type ReactNode } from "react";
+import { type Room, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
 import BridgeTile from "../../BridgeTile";
diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx
index 590b54218f7c2c0efd82ae92998bc6e74b27c4af..b50eb22a7c52c5aea15d4dd2248c0ef19c473317 100644
--- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx
@@ -1,17 +1,17 @@
 /*
-Copyright 2019-2024 New Vector Ltd.
+Copyright 2019-2025 New Vector Ltd.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ContextType } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import React, { type ContextType } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { _t } from "../../../../../languageHandler";
 import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
-import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../elements/AccessibleButton";
 import dis from "../../../../../dispatcher/dispatcher";
 import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
 import SettingsStore from "../../../../../settings/SettingsStore";
@@ -22,6 +22,7 @@ import { SettingsSubsection } from "../../shared/SettingsSubsection";
 import SettingsTab from "../SettingsTab";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { UrlPreviewSettings } from "../../../room_settings/UrlPreviewSettings";
+import { MediaPreviewAccountSettings } from "../user/MediaPreviewAccountSettings";
 
 interface IProps {
     room: Room;
@@ -35,8 +36,8 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
     public static contextType = MatrixClientContext;
     declare public context: ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         this.state = {
             isRoomPublished: false, // loaded async
@@ -92,6 +93,9 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
 
                 <SettingsSection heading={_t("room_settings|general|other_section")}>
                     {urlPreviewSettings}
+                    <SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
+                        <MediaPreviewAccountSettings roomId={room.roomId} />
+                    </SettingsSubsection>
                     {leaveSection}
                 </SettingsSection>
             </SettingsTab>
diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx
index 17e2498ae71fa7669155d051d71b1c77a097e2d0..f400024c0b2f42fc46fa6fb4f93fbe010c716d60 100644
--- a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
+import React, { type JSX, createRef } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../../../languageHandler";
-import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../../../elements/AccessibleButton";
 import Notifier from "../../../../../Notifier";
 import SettingsStore from "../../../../../settings/SettingsStore";
 import { SettingLevel } from "../../../../../settings/SettingLevel";
-import { RoomEchoChamber } from "../../../../../stores/local-echo/RoomEchoChamber";
+import { type RoomEchoChamber } from "../../../../../stores/local-echo/RoomEchoChamber";
 import { EchoChamber } from "../../../../../stores/local-echo/EchoChamber";
 import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
 import StyledRadioGroup from "../../../elements/StyledRadioGroup";
diff --git a/src/components/views/settings/tabs/room/PeopleRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/PeopleRoomSettingsTab.tsx
index b374f36de1b2364274e8cc125b3c6893d7208ace..c3ef87e8c73010b1ac21e3b07f9391e132a28d7d 100644
--- a/src/components/views/settings/tabs/room/PeopleRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/PeopleRoomSettingsTab.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventTimeline, MatrixError, Room, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { EventTimeline, type MatrixError, type Room, type RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import React, { useCallback, useState, VFC } from "react";
+import React, { useCallback, useState, type FC } from "react";
 import { CloseIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { formatRelativeTime } from "../../../../../DateUtils";
 import { useTypedEventEmitterState } from "../../../../../hooks/useEventEmitter";
 import { _t } from "../../../../../languageHandler";
-import Modal, { IHandle } from "../../../../../Modal";
+import Modal, { type IHandle } from "../../../../../Modal";
 import MemberAvatar from "../../../avatars/MemberAvatar";
 import ErrorDialog from "../../../dialogs/ErrorDialog";
 import AccessibleButton from "../../../elements/AccessibleButton";
@@ -22,13 +22,13 @@ import SettingsFieldset from "../../SettingsFieldset";
 import { SettingsSection } from "../../shared/SettingsSection";
 import SettingsTab from "../SettingsTab";
 
-const Timestamp: VFC<{ roomMember: RoomMember }> = ({ roomMember }) => {
+const Timestamp: FC<{ roomMember: RoomMember }> = ({ roomMember }) => {
     const timestamp = roomMember.events.member?.event.origin_server_ts;
     if (!timestamp) return null;
     return <time className="mx_PeopleRoomSettingsTab_timestamp">{formatRelativeTime(new Date(timestamp))}</time>;
 };
 
-const SeeMoreOrLess: VFC<{ roomMember: RoomMember }> = ({ roomMember }) => {
+const SeeMoreOrLess: FC<{ roomMember: RoomMember }> = ({ roomMember }) => {
     const [seeMore, setSeeMore] = useState(false);
     const reason = roomMember.events.member?.getContent().reason;
 
@@ -51,7 +51,7 @@ const SeeMoreOrLess: VFC<{ roomMember: RoomMember }> = ({ roomMember }) => {
     );
 };
 
-const Knock: VFC<{
+const Knock: FC<{
     canInvite: boolean;
     canKick: boolean;
     onApprove: (userId: string) => Promise<void>;
@@ -103,7 +103,7 @@ const Knock: VFC<{
     );
 };
 
-export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
+export const PeopleRoomSettingsTab: FC<{ room: Room }> = ({ room }) => {
     const client = room.client;
     const userId = client.getUserId() || "";
     const canInvite = room.canInvite(userId);
diff --git a/src/components/views/settings/tabs/room/PollHistoryTab.tsx b/src/components/views/settings/tabs/room/PollHistoryTab.tsx
index a33e132e13512a0af77f7c1ebaa913bab38e6a91..72a20350bb1698a312e0a02ac0d86f5ccfa43cb6 100644
--- a/src/components/views/settings/tabs/room/PollHistoryTab.tsx
+++ b/src/components/views/settings/tabs/room/PollHistoryTab.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useContext } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
 import { PollHistory } from "../../../polls/pollHistory/PollHistory";
diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
index e2de5c3e6985de3d82f1ec091b5dfa388eb68c82..c44812d618726b26ff796d88a89f9cd03e5b6df6 100644
--- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
@@ -6,13 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { EventType, RoomMember, RoomState, RoomStateEvent, Room, IContent } from "matrix-js-sdk/src/matrix";
+import React, { type JSX } from "react";
+import {
+    EventType,
+    type RoomMember,
+    type RoomState,
+    RoomStateEvent,
+    type Room,
+    type IContent,
+} from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { throttle, get } from "lodash";
-import { KnownMembership, RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
+import { KnownMembership, type RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
 
-import { _t, _td, TranslationKey } from "../../../../../languageHandler";
+import { _t, _td, type TranslationKey } from "../../../../../languageHandler";
 import AccessibleButton from "../../../elements/AccessibleButton";
 import Modal from "../../../../../Modal";
 import ErrorDialog from "../../../dialogs/ErrorDialog";
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index f90dd9821d086dc7771ab2d2dbae1c384917b88c..89ae96f8a46b7b5213a1d4afceed53219df58f2e 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import {
     GuestAccess,
     HistoryVisibility,
     JoinRule,
-    MatrixEvent,
+    type MatrixEvent,
     RoomStateEvent,
-    Room,
+    type Room,
     EventType,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
@@ -62,8 +62,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: IProps) {
+        super(props);
 
         const state = this.props.room.currentState;
 
@@ -155,7 +155,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
             if (!confirm) return;
         }
 
-        Modal.createDialog(QuestionDialog, {
+        const { finished } = Modal.createDialog(QuestionDialog, {
             title: _t("room_settings|security|enable_encryption_confirm_title"),
             description: _t(
                 "room_settings|security|enable_encryption_confirm_description",
@@ -164,23 +164,23 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
                     a: (sub) => <ExternalLink href={SdkConfig.get("help_encryption_url")}>{sub}</ExternalLink>,
                 },
             ),
-            onFinished: (confirm) => {
-                if (!confirm) {
-                    this.setState({ encrypted: false });
-                    return;
-                }
-
-                const beforeEncrypted = this.state.encrypted;
-                this.setState({ encrypted: true });
-                this.context
-                    .sendStateEvent(this.props.room.roomId, EventType.RoomEncryption, {
-                        algorithm: MEGOLM_ENCRYPTION_ALGORITHM,
-                    })
-                    .catch((e) => {
-                        logger.error(e);
-                        this.setState({ encrypted: beforeEncrypted });
-                    });
-            },
+        });
+        finished.then(([confirm]) => {
+            if (!confirm) {
+                this.setState({ encrypted: false });
+                return;
+            }
+
+            const beforeEncrypted = this.state.encrypted;
+            this.setState({ encrypted: true });
+            this.context
+                .sendStateEvent(this.props.room.roomId, EventType.RoomEncryption, {
+                    algorithm: MEGOLM_ENCRYPTION_ALGORITHM,
+                })
+                .catch((e) => {
+                    logger.error(e);
+                    this.setState({ encrypted: beforeEncrypted });
+                });
         });
     };
 
@@ -213,9 +213,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
 
         const [shouldCreate, opts] = await modal.finished;
         if (shouldCreate) {
-            await createRoom(this.context, opts);
+            await createRoom(this.context, opts!);
         }
-        return shouldCreate;
+        return shouldCreate ?? false;
     };
 
     private onHistoryRadioToggle = (history: HistoryVisibility): void => {
diff --git a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx
index f7b69696be7a373e87af437c542a02e14c3ab196..3583179b924d2a61dea491d2434bc8785886e20c 100644
--- a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useCallback, useMemo, useState } from "react";
-import { JoinRule, EventType, RoomState, Room } from "matrix-js-sdk/src/matrix";
-import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
+import { JoinRule, EventType, type RoomState, type Room } from "matrix-js-sdk/src/matrix";
+import { type RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
 
 import { _t } from "../../../../../languageHandler";
 import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
diff --git a/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx
index c94eef6d2c69755d6923585dc0e5cb588edff57d..014527f697ae111240200441a272f05b3b19a294 100644
--- a/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback, useContext, useEffect } from "react";
+import React, { type JSX, useCallback, useContext, useEffect } from "react";
 import { HTTPError } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -165,10 +165,9 @@ const AccountUserSettingsTab: React.FC<IProps> = ({ closeSettingsFn }) => {
     }, []);
 
     const onDeactivateClicked = useCallback((): void => {
-        Modal.createDialog(DeactivateAccountDialog, {
-            onFinished: (success) => {
-                if (success) closeSettingsFn();
-            },
+        const { finished } = Modal.createDialog(DeactivateAccountDialog);
+        finished.then(([success]) => {
+            if (success) closeSettingsFn();
         });
     }, [closeSettingsFn]);
 
diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
index 4586191db145ef91752192d76f50f360ae6dde74..7d46cf72da559939388b2f0075e8a3c42fbe6f5d 100644
--- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
@@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, ReactNode } from "react";
+import React, { type ChangeEvent, type ReactNode } from "react";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
 import SdkConfig from "../../../../../SdkConfig";
@@ -25,8 +26,6 @@ import SettingsTab from "../SettingsTab";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { SettingsSubsection } from "../../shared/SettingsSubsection";
 
-interface IProps {}
-
 interface IState {
     useBundledEmojiFont: boolean;
     useSystemFont: boolean;
@@ -34,8 +33,8 @@ interface IState {
     showAdvanced: boolean;
 }
 
-export default class AppearanceUserSettingsTab extends React.Component<IProps, IState> {
-    public constructor(props: IProps) {
+export default class AppearanceUserSettingsTab extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx
index d4304eb4d41b6e1ff3e354784cb8d8060b93127a..dea28628fbe29042ca1ef061427f0a4da39f7ea3 100644
--- a/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/EncryptionUserSettingsTab.tsx
@@ -5,9 +5,10 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, useCallback, useEffect, useState } from "react";
-import { Button, InlineSpinner } from "@vector-im/compound-web";
+import React, { type JSX, useCallback, useEffect, useState } from "react";
+import { Button, InlineSpinner, Separator } from "@vector-im/compound-web";
 import ComputerIcon from "@vector-im/compound-design-tokens/assets/web/icons/computer";
+import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
 import SettingsTab from "../SettingsTab";
 import { RecoveryPanel } from "../../encryption/RecoveryPanel";
@@ -18,41 +19,98 @@ import Modal from "../../../../../Modal";
 import SetupEncryptionDialog from "../../../dialogs/security/SetupEncryptionDialog";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { SettingsSubheader } from "../../SettingsSubheader";
+import { AdvancedPanel } from "../../encryption/AdvancedPanel";
+import { ResetIdentityPanel } from "../../encryption/ResetIdentityPanel";
+import { type ResetIdentityBodyVariant } from "../../encryption/ResetIdentityBody";
+import { RecoveryPanelOutOfSync } from "../../encryption/RecoveryPanelOutOfSync";
+import { useTypedEventEmitter } from "../../../../../hooks/useEventEmitter";
+import { KeyStoragePanel } from "../../encryption/KeyStoragePanel";
+import { DeleteKeyStoragePanel } from "../../encryption/DeleteKeyStoragePanel";
 
 /**
  * The state in the encryption settings tab.
  *  - "loading": We are checking if the device is verified.
  *  - "main": The main panel with all the sections (Key storage, recovery, advanced).
+ *  - "key_storage_disabled": The user has chosen to disable key storage and options are unavailable as a result.
  *  - "set_up_encryption": The panel to show when the user is setting up their encryption.
  *                         This happens when the user doesn't have cross-signing enabled, or their current device is not verified.
  *  - "change_recovery_key": The panel to show when the user is changing their recovery key.
  *                           This happens when the user has a recovery key and the user clicks on "Change recovery key" button of the RecoveryPanel.
  *  - "set_recovery_key": The panel to show when the user is setting up their recovery key.
  *                        This happens when the user doesn't have a key a recovery key and the user clicks on "Set up recovery key" button of the RecoveryPanel.
+ *  - "reset_identity_compromised": The panel to show when the user is resetting their identity, in the case where their key is compromised.
+ *  - "reset_identity_forgot": The panel to show when the user is resetting their identity, in the case where they forgot their recovery key.
+ *  - "reset_identity_sync_failed": The panel to show when the user us resetting their identity, in the case where recovery failed.
+ *  - "secrets_not_cached": The secrets are not cached locally. This can happen if we verified another device and secret-gossiping failed, or the other device itself lacked the secrets.
+ *                          If the "set_up_encryption" and "secrets_not_cached" conditions are both filled, "set_up_encryption" prevails.
+ *  - "key_storage_delete": The confirmation page asking if the user really wants to turn off key storage.
  */
-type State = "loading" | "main" | "set_up_encryption" | "change_recovery_key" | "set_recovery_key";
+export type State =
+    | "loading"
+    | "main"
+    | "key_storage_disabled"
+    | "set_up_encryption"
+    | "change_recovery_key"
+    | "set_recovery_key"
+    | "reset_identity_compromised"
+    | "reset_identity_forgot"
+    | "reset_identity_sync_failed"
+    | "secrets_not_cached"
+    | "key_storage_delete";
 
-export function EncryptionUserSettingsTab(): JSX.Element {
-    const [state, setState] = useState<State>("loading");
-    const setUpEncryptionRequired = useSetUpEncryptionRequired(setState);
+interface Props {
+    /**
+     * If the tab should start in a state other than the default
+     */
+    initialState?: State;
+}
+
+/**
+ * The encryption settings tab.
+ */
+export function EncryptionUserSettingsTab({ initialState = "loading" }: Props): JSX.Element {
+    const [state, setState] = useState<State>(initialState);
+
+    const checkEncryptionState = useCheckEncryptionState(state, setState);
 
     let content: JSX.Element;
+
     switch (state) {
         case "loading":
             content = <InlineSpinner aria-label={_t("common|loading")} />;
             break;
         case "set_up_encryption":
-            content = <SetUpEncryptionPanel onFinish={setUpEncryptionRequired} />;
+            content = <SetUpEncryptionPanel onFinish={checkEncryptionState} />;
             break;
-        case "main":
+        case "secrets_not_cached":
             content = (
-                <RecoveryPanel
-                    onChangeRecoveryKeyClick={(setupNewKey) =>
-                        setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key")
-                    }
+                <RecoveryPanelOutOfSync
+                    onFinish={checkEncryptionState}
+                    onForgotRecoveryKey={() => setState("reset_identity_forgot")}
                 />
             );
             break;
+        case "key_storage_disabled":
+        case "main":
+            content = (
+                <>
+                    <KeyStoragePanel onKeyStorageDisableClick={() => setState("key_storage_delete")} />
+                    <Separator kind="section" />
+                    {/* We only show the "Recovery" panel if key storage is enabled.*/}
+                    {state === "main" && (
+                        <>
+                            <RecoveryPanel
+                                onChangeRecoveryKeyClick={(setupNewKey) =>
+                                    setupNewKey ? setState("set_recovery_key") : setState("change_recovery_key")
+                                }
+                            />
+                            <Separator kind="section" />
+                        </>
+                    )}
+                    <AdvancedPanel onResetIdentityClick={() => setState("reset_identity_compromised")} />
+                </>
+            );
+            break;
         case "change_recovery_key":
         case "set_recovery_key":
             content = (
@@ -63,6 +121,20 @@ export function EncryptionUserSettingsTab(): JSX.Element {
                 />
             );
             break;
+        case "reset_identity_compromised":
+        case "reset_identity_forgot":
+        case "reset_identity_sync_failed":
+            content = (
+                <ResetIdentityPanel
+                    variant={findResetVariant(state)}
+                    onCancelClick={checkEncryptionState}
+                    onReset={checkEncryptionState}
+                />
+            );
+            break;
+        case "key_storage_delete":
+            content = <DeleteKeyStoragePanel onFinish={checkEncryptionState} />;
+            break;
     }
 
     return (
@@ -73,9 +145,32 @@ export function EncryptionUserSettingsTab(): JSX.Element {
 }
 
 /**
- * Hook to check if the user needs to go through the SetupEncryption flow.
- * If the user needs to set up the encryption, the state will be set to "set_up_encryption".
- * Otherwise, the state will be set to "main".
+ * Given what state we want the tab to be in, what variant of the
+ * ResetIdentityPanel do we need?
+ */
+function findResetVariant(state: State): ResetIdentityBodyVariant {
+    switch (state) {
+        case "reset_identity_compromised":
+            return "compromised";
+        case "reset_identity_sync_failed":
+            return "sync_failed";
+
+        default:
+        case "reset_identity_forgot":
+            return "forgot";
+    }
+}
+
+/**
+ * Hook to check if the user needs:
+ * - to go through the SetupEncryption flow.
+ * - to enter their recovery key, if the secrets are not cached locally.
+ * ...and also whether megolm key backup is enabled on this device (which we use to set the state of the 'allow key storage' toggle)
+ *
+ * If cross signing is set up, key backup is enabled and the secrets are cached, the state will be set to "main".
+ * If cross signing is not set up, the state will be set to "set_up_encryption".
+ * If key backup is not enabled, the state will be set to "key_storage_disabled".
+ * If secrets are missing, the state will be set to "secrets_not_cached".
  *
  * The state is set once when the component is first mounted.
  * Also returns a callback function which can be called to re-run the logic.
@@ -83,23 +178,44 @@ export function EncryptionUserSettingsTab(): JSX.Element {
  * @param setState - callback passed from the EncryptionUserSettingsTab to set the current `State`.
  * @returns a callback function, which will re-run the logic and update the state.
  */
-function useSetUpEncryptionRequired(setState: (state: State) => void): () => Promise<void> {
+function useCheckEncryptionState(state: State, setState: (state: State) => void): () => Promise<void> {
     const matrixClient = useMatrixClientContext();
 
-    const setUpEncryptionRequired = useCallback(async () => {
+    const checkEncryptionState = useCallback(async () => {
         const crypto = matrixClient.getCrypto()!;
         const isCrossSigningReady = await crypto.isCrossSigningReady();
-        if (isCrossSigningReady) setState("main");
-        else setState("set_up_encryption");
+
+        // Check if the secrets are cached
+        const cachedSecrets = (await crypto.getCrossSigningStatus()).privateKeysCachedLocally;
+        const secretsOk = cachedSecrets.masterKey && cachedSecrets.selfSigningKey && cachedSecrets.userSigningKey;
+
+        // Also check the key backup status
+        const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
+
+        const keyStorageEnabled = activeBackupVersion !== null;
+
+        if (isCrossSigningReady && keyStorageEnabled && secretsOk) setState("main");
+        else if (!isCrossSigningReady) setState("set_up_encryption");
+        else if (!keyStorageEnabled) setState("key_storage_disabled");
+        else setState("secrets_not_cached");
     }, [matrixClient, setState]);
 
     // Initialise the state when the component is mounted
     useEffect(() => {
-        setUpEncryptionRequired();
-    }, [setUpEncryptionRequired]);
+        if (state === "loading") checkEncryptionState();
+    }, [checkEncryptionState, state]);
+
+    useTypedEventEmitter(matrixClient, CryptoEvent.KeyBackupStatus, (): void => {
+        // Recheck the status if the key backup status has changed so we can keep the page up to date.
+        // Note that this could potentially update the UI while the user is trying to do something, although
+        // if their key backup status is changing then they're changing encryption related things
+        // on another device. This code is written with the assumption that it's better for the UI to refresh
+        // and be up to date with whatever changes they've made.
+        checkEncryptionState();
+    });
 
     // Also return the callback so that the component can re-run the logic.
-    return setUpEncryptionRequired;
+    return checkEncryptionState;
 }
 
 interface SetUpEncryptionPanelProps {
@@ -131,7 +247,10 @@ function SetUpEncryptionPanel({ onFinish }: SetUpEncryptionPanelProps): JSX.Elem
             <Button
                 size="sm"
                 Icon={ComputerIcon}
-                onClick={() => Modal.createDialog(SetupEncryptionDialog, { onFinished: onFinish })}
+                onClick={() => {
+                    const { finished } = Modal.createDialog(SetupEncryptionDialog);
+                    finished.then(onFinish);
+                }}
             >
                 {_t("settings|encryption|device_not_verified_button")}
             </Button>
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
index d28c820d20683d2a1f5390e8f0a384574c0ac1f2..ef0d6e57fcacd91882726513bf959f3bc3fd592e 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
@@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import AccessibleButton from "../../../elements/AccessibleButton";
 import { _t } from "../../../../../languageHandler";
@@ -23,19 +24,17 @@ import { SettingsSubsection, SettingsSubsectionText } from "../../shared/Setting
 import ExternalLink from "../../../elements/ExternalLink";
 import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
 
-interface IProps {}
-
 interface IState {
     appVersion: string | null;
     canUpdate: boolean;
 }
 
-export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
+export default class HelpUserSettingsTab extends React.Component<EmptyObject, IState> {
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: EmptyObject) {
+        super(props);
 
         this.state = {
             appVersion: null,
diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx
index b6d2d24c96a834102e50ceca7afcd2a03324094e..44e3623625419502ae3312b1b91ea4424485ba3f 100644
--- a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx
@@ -9,7 +9,12 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 
-import { ICategory, CATEGORIES, CategoryName, KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts";
+import {
+    type ICategory,
+    CATEGORIES,
+    CategoryName,
+    type KeyBindingAction,
+} from "../../../../../accessibility/KeyboardShortcuts";
 import { _t } from "../../../../../languageHandler";
 import {
     getKeyboardShortcutDisplayName,
diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
index 5cee63b48e483d5789a6762404a9efdf558be18b..622343b287d53648638cf45d21e382efee361622 100644
--- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
@@ -5,8 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { sortBy } from "lodash";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
 import SettingsStore from "../../../../../settings/SettingsStore";
@@ -14,7 +15,7 @@ import { SettingLevel } from "../../../../../settings/SettingLevel";
 import SdkConfig from "../../../../../SdkConfig";
 import BetaCard from "../../../beta/BetaCard";
 import SettingsFlag from "../../../elements/SettingsFlag";
-import { FeatureSettingKey, LabGroup, labGroupNames } from "../../../../../settings/Settings";
+import { type FeatureSettingKey, LabGroup, labGroupNames } from "../../../../../settings/Settings";
 import { EnhancedMap } from "../../../../../utils/maps";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
@@ -24,11 +25,11 @@ export const showLabsFlags = (): boolean => {
     return SdkConfig.get("show_labs_settings") || SettingsStore.getValue("developerMode");
 };
 
-export default class LabsUserSettingsTab extends React.Component<{}> {
+export default class LabsUserSettingsTab extends React.Component<EmptyObject> {
     private readonly labs: FeatureSettingKey[];
     private readonly betas: FeatureSettingKey[];
 
-    public constructor(props: {}) {
+    public constructor(props: EmptyObject) {
         super(props);
 
         const features = SettingsStore.getFeatureSettingNames();
diff --git a/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx b/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f6a2b7cf3847faa34afdb1d30206a44ffa5ca8c1
--- /dev/null
+++ b/src/components/views/settings/tabs/user/MediaPreviewAccountSettings.tsx
@@ -0,0 +1,150 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type ChangeEventHandler, useCallback } from "react";
+import { Field, HelpMessage, InlineField, Label, RadioInput, Root } from "@vector-im/compound-web";
+
+import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
+import { type MediaPreviewConfig, MediaPreviewValue } from "../../../../../@types/media_preview";
+import { _t } from "../../../../../languageHandler";
+import { useSettingValue } from "../../../../../hooks/useSettings";
+import SettingsStore from "../../../../../settings/SettingsStore";
+import { SettingLevel } from "../../../../../settings/SettingLevel";
+
+export const MediaPreviewAccountSettings: React.FC<{ roomId?: string }> = ({ roomId }) => {
+    const currentMediaPreview = useSettingValue("mediaPreviewConfig", roomId);
+
+    const changeSetting = useCallback(
+        (newValue: MediaPreviewConfig) => {
+            SettingsStore.setValue(
+                "mediaPreviewConfig",
+                roomId ?? null,
+                roomId ? SettingLevel.ROOM_ACCOUNT : SettingLevel.ACCOUNT,
+                newValue,
+            );
+        },
+        [roomId],
+    );
+
+    const avatarOnChange = useCallback(
+        (c: boolean) => {
+            changeSetting({
+                ...currentMediaPreview,
+                // Switch is inverted. "Hide avatars..."
+                invite_avatars: c ? MediaPreviewValue.Off : MediaPreviewValue.On,
+            });
+        },
+        [changeSetting, currentMediaPreview],
+    );
+
+    const mediaPreviewOnChangeOff = useCallback<ChangeEventHandler<HTMLInputElement>>(
+        (event) => {
+            if (!event.target.checked) {
+                return;
+            }
+            changeSetting({
+                ...currentMediaPreview,
+                media_previews: MediaPreviewValue.Off,
+            });
+        },
+        [changeSetting, currentMediaPreview],
+    );
+
+    const mediaPreviewOnChangePrivate = useCallback<ChangeEventHandler<HTMLInputElement>>(
+        (event) => {
+            if (!event.target.checked) {
+                return;
+            }
+            changeSetting({
+                ...currentMediaPreview,
+                media_previews: MediaPreviewValue.Private,
+            });
+        },
+        [changeSetting, currentMediaPreview],
+    );
+
+    const mediaPreviewOnChangeOn = useCallback<ChangeEventHandler<HTMLInputElement>>(
+        (event) => {
+            if (!event.target.checked) {
+                return;
+            }
+            changeSetting({
+                ...currentMediaPreview,
+                media_previews: MediaPreviewValue.On,
+            });
+        },
+        [changeSetting, currentMediaPreview],
+    );
+
+    return (
+        <Root className="mx_MediaPreviewAccountSetting_Form">
+            {!roomId && (
+                <LabelledToggleSwitch
+                    className="mx_MediaPreviewAccountSetting_ToggleSwitch"
+                    label={_t("settings|media_preview|hide_avatars")}
+                    value={currentMediaPreview.invite_avatars === MediaPreviewValue.Off}
+                    onChange={avatarOnChange}
+                />
+            )}
+            {/* Explict label here because htmlFor is not supported for linking to radiogroups */}
+            <Field
+                id="mx_media_previews"
+                role="radiogroup"
+                name="media_previews"
+                aria-label={_t("settings|media_preview|media_preview_label")}
+            >
+                <Label>{_t("settings|media_preview|media_preview_label")}</Label>
+                <HelpMessage className="mx_MediaPreviewAccountSetting_RadioHelp">
+                    {_t("settings|media_preview|media_preview_description")}
+                </HelpMessage>
+                <InlineField
+                    name="media_preview_off"
+                    className="mx_MediaPreviewAccountSetting_Radio"
+                    control={
+                        <RadioInput
+                            id="mx_media_previews_off"
+                            checked={currentMediaPreview.media_previews === MediaPreviewValue.Off}
+                            onChange={mediaPreviewOnChangeOff}
+                        />
+                    }
+                >
+                    <Label htmlFor="mx_media_previews_off">{_t("settings|media_preview|hide_media")}</Label>
+                </InlineField>
+                {!roomId && (
+                    <InlineField
+                        name="mx_media_previews_private"
+                        className="mx_MediaPreviewAccountSetting_Radio"
+                        control={
+                            <RadioInput
+                                id="mx_media_previews_private"
+                                checked={currentMediaPreview.media_previews === MediaPreviewValue.Private}
+                                onChange={mediaPreviewOnChangePrivate}
+                            />
+                        }
+                    >
+                        <Label htmlFor="mx_media_previews_private">
+                            {_t("settings|media_preview|show_in_private")}
+                        </Label>
+                    </InlineField>
+                )}
+                <InlineField
+                    name="media_preview_on"
+                    className="mx_MediaPreviewAccountSetting_Radio"
+                    control={
+                        <RadioInput
+                            id="mx_media_previews_on"
+                            checked={currentMediaPreview.media_previews === MediaPreviewValue.On}
+                            onChange={mediaPreviewOnChangeOn}
+                        />
+                    }
+                >
+                    <Label htmlFor="mx_media_previews_on">{_t("settings|media_preview|show_media")}</Label>
+                </InlineField>
+            </Field>
+        </Root>
+    );
+};
diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx
index 9b71244e5543017460e682306a668672e352cb77..b13ad4ee8561bcf24e968355b00908f23cb8fd47 100644
--- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx
@@ -6,14 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, SyntheticEvent } from "react";
+import React, { type JSX, type ChangeEvent, type SyntheticEvent } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
 import SdkConfig from "../../../../../SdkConfig";
 import { Mjolnir } from "../../../../../mjolnir/Mjolnir";
-import { ListRule } from "../../../../../mjolnir/ListRule";
-import { BanList, RULE_SERVER, RULE_USER } from "../../../../../mjolnir/BanList";
+import { type ListRule } from "../../../../../mjolnir/ListRule";
+import { type BanList, RULE_SERVER, RULE_USER } from "../../../../../mjolnir/BanList";
 import Modal from "../../../../../Modal";
 import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
 import ErrorDialog from "../../../dialogs/ErrorDialog";
@@ -30,8 +31,8 @@ interface IState {
     newList: string;
 }
 
-export default class MjolnirUserSettingsTab extends React.Component<{}, IState> {
-    public constructor(props: {}) {
+export default class MjolnirUserSettingsTab extends React.Component<EmptyObject, IState> {
+    public constructor(props: EmptyObject) {
         super(props);
 
         this.state = {
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx
index 8ad00f350fcbb9f35c5d023dc75b32a00c4c1df5..fa72cfe28e6441524f5c54e2b897b797b377a153 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019-2023 The Matrix.org Foundation C.I.C.
 Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
 
@@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, useCallback, useEffect, useState } from "react";
+import React, { type JSX, type ReactElement, useCallback, useEffect, useState } from "react";
 
-import { NonEmptyArray } from "../../../../../@types/common";
+import { type NonEmptyArray } from "../../../../../@types/common";
 import { _t, getCurrentLanguage } from "../../../../../languageHandler";
 import SettingsStore from "../../../../../settings/SettingsStore";
 import Field from "../../../elements/Field";
@@ -19,7 +19,7 @@ import SettingsFlag from "../../../elements/SettingsFlag";
 import AccessibleButton from "../../../elements/AccessibleButton";
 import dis from "../../../../../dispatcher/dispatcher";
 import { UserTab } from "../../../dialogs/UserTab";
-import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
+import { type OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
 import { Action } from "../../../../../dispatcher/actions";
 import SdkConfig from "../../../../../SdkConfig";
 import { SettingsSubsection } from "../../shared/SettingsSubsection";
@@ -31,7 +31,8 @@ import { IS_MAC } from "../../../../../Keyboard";
 import SpellCheckSettings from "../../SpellCheckSettings";
 import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
 import * as TimezoneHandler from "../../../../../TimezoneHandler";
-import { BooleanSettingKey } from "../../../../../settings/Settings.tsx";
+import { type BooleanSettingKey } from "../../../../../settings/Settings.tsx";
+import { MediaPreviewAccountSettings } from "./MediaPreviewAccountSettings.tsx";
 
 interface IProps {
     closeSettingsFn(success: boolean): void;
@@ -146,7 +147,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
         "urlPreviewsEnabled",
         "autoplayGifs",
         "autoplayVideo",
-        "showImages",
     ];
 
     private static TIMELINE_SETTINGS: BooleanSettingKey[] = [
@@ -224,8 +224,14 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
         SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
     };
 
-    private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): React.ReactNodeArray {
-        return settingIds.map((i) => <SettingsFlag key={i} name={i} level={level} />);
+    private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): JSX.Element {
+        return (
+            <>
+                {settingIds.map((i) => (
+                    <SettingsFlag key={i} name={i} level={level} />
+                ))}
+            </>
+        );
     }
 
     private onKeyboardShortcutsClicked = (): void => {
@@ -329,6 +335,10 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
                         {this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
                     </SettingsSubsection>
 
+                    <SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
+                        <MediaPreviewAccountSettings />
+                    </SettingsSubsection>
+
                     <SettingsSubsection heading={_t("settings|preferences|room_directory_heading")}>
                         {this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)}
                     </SettingsSubsection>
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
index 13fc695c3a74ef3ab1292119b2e3217524c90bb0..1c85aada3414b57231788ef2fc5a556bd76b9115 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { sleep } from "matrix-js-sdk/src/utils";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
-import { KnownMembership, Membership } from "matrix-js-sdk/src/types";
+import { type Room, RoomEvent, type IServerVersions } from "matrix-js-sdk/src/matrix";
+import { KnownMembership, type Membership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../../../languageHandler";
@@ -17,19 +17,15 @@ import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
 import AccessibleButton from "../../../elements/AccessibleButton";
 import dis from "../../../../../dispatcher/dispatcher";
 import { SettingLevel } from "../../../../../settings/SettingLevel";
-import SecureBackupPanel from "../../SecureBackupPanel";
 import SettingsStore from "../../../../../settings/SettingsStore";
 import { UIFeature } from "../../../../../settings/UIFeature";
-import { ActionPayload } from "../../../../../dispatcher/payloads";
-import CryptographyPanel from "../../CryptographyPanel";
+import { type ActionPayload } from "../../../../../dispatcher/payloads";
 import SettingsFlag from "../../../elements/SettingsFlag";
-import CrossSigningPanel from "../../CrossSigningPanel";
 import EventIndexPanel from "../../EventIndexPanel";
 import InlineSpinner from "../../../elements/InlineSpinner";
 import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
 import { showDialog as showAnalyticsLearnMoreDialog } from "../../../dialogs/AnalyticsLearnMoreDialog";
 import { privateShouldBeEncrypted } from "../../../../../utils/rooms";
-import type { IServerVersions } from "matrix-js-sdk/src/matrix";
 import SettingsTab from "../SettingsTab";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
@@ -43,21 +39,20 @@ interface IIgnoredUserProps {
     inProgress: boolean;
 }
 
-const DehydratedDeviceStatus: React.FC = () => {
+const SecureBackup: React.FC = () => {
     const { dehydratedDeviceId } = useOwnDevices();
+    if (!dehydratedDeviceId) return null;
 
-    if (dehydratedDeviceId) {
-        return (
+    return (
+        <SettingsSubsection heading={_t("common|secure_backup")}>
             <div className="mx_SettingsSubsection_content">
                 <div className="mx_SettingsFlag_label">{_t("settings|security|dehydrated_device_enabled")}</div>
                 <div className="mx_SettingsSubsection_text">
                     {_t("settings|security|dehydrated_device_description")}
                 </div>
             </div>
-        );
-    } else {
-        return null;
-    }
+        </SettingsSubsection>
+    );
 };
 
 export class IgnoredUser extends React.Component<IIgnoredUserProps> {
@@ -68,7 +63,7 @@ export class IgnoredUser extends React.Component<IIgnoredUserProps> {
     public render(): React.ReactNode {
         const id = `mx_SecurityUserSettingsTab_ignoredUser_${this.props.userId}`;
         return (
-            <div className="mx_SecurityUserSettingsTab_ignoredUser">
+            <li className="mx_SecurityUserSettingsTab_ignoredUser" aria-label={this.props.userId}>
                 <AccessibleButton
                     onClick={this.onUnignoreClicked}
                     kind="primary_sm"
@@ -78,7 +73,7 @@ export class IgnoredUser extends React.Component<IIgnoredUserProps> {
                     {_t("action|unignore")}
                 </AccessibleButton>
                 <span id={id}>{this.props.userId}</span>
-            </div>
+            </li>
         );
     }
 }
@@ -235,23 +230,34 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
 
     private renderIgnoredUsers(): JSX.Element {
         const { waitingUnignored, ignoredUserIds } = this.state;
-
-        const userIds = !ignoredUserIds?.length
-            ? _t("settings|security|ignore_users_empty")
-            : ignoredUserIds.map((u) => {
-                  return (
-                      <IgnoredUser
-                          userId={u}
-                          onUnignored={this.onUserUnignored}
-                          key={u}
-                          inProgress={waitingUnignored.includes(u)}
-                      />
-                  );
-              });
+        if (!ignoredUserIds?.length) {
+            return (
+                <SettingsSubsection heading={_t("settings|security|ignore_users_section")}>
+                    <SettingsSubsectionText>{_t("settings|security|ignore_users_empty")}</SettingsSubsectionText>
+                </SettingsSubsection>
+            );
+        }
 
         return (
-            <SettingsSubsection heading={_t("settings|security|ignore_users_section")}>
-                <SettingsSubsectionText>{userIds}</SettingsSubsectionText>
+            <SettingsSubsection
+                id="mx_SecurityUserSettingsTab_ignoredUsersHeading"
+                heading={_t("settings|security|ignore_users_section")}
+            >
+                <SettingsSubsectionText>
+                    <ul
+                        aria-label={_t("settings|security|ignore_users_section")}
+                        className="mx_SecurityUserSettingsTab_ignoredUsers"
+                    >
+                        {ignoredUserIds.map((u) => (
+                            <IgnoredUser
+                                userId={u}
+                                onUnignored={this.onUserUnignored}
+                                key={u}
+                                inProgress={waitingUnignored.includes(u)}
+                            />
+                        ))}
+                    </ul>
+                </SettingsSubsectionText>
             </SettingsSubsection>
         );
     }
@@ -287,12 +293,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
     }
 
     public render(): React.ReactNode {
-        const secureBackup = (
-            <SettingsSubsection heading={_t("common|secure_backup")}>
-                <SecureBackupPanel />
-                <DehydratedDeviceStatus />
-            </SettingsSubsection>
-        );
+        const secureBackup = <SecureBackup />;
 
         const eventIndex = (
             <SettingsSubsection heading={_t("settings|security|message_search_section")}>
@@ -300,16 +301,6 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
             </SettingsSubsection>
         );
 
-        // XXX: There's no such panel in the current cross-signing designs, but
-        // it's useful to have for testing the feature. If there's no interest
-        // in having advanced details here once all flows are implemented, we
-        // can remove this.
-        const crossSigning = (
-            <SettingsSubsection heading={_t("common|cross_signing")}>
-                <CrossSigningPanel />
-            </SettingsSubsection>
-        );
-
         let warning;
         if (!privateShouldBeEncrypted(MatrixClientPeg.safeGet())) {
             warning = (
@@ -319,7 +310,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
             );
         }
 
-        let privacySection;
+        let posthogSection;
         if (PosthogAnalytics.instance.isEnabled()) {
             const onClickAnalyticsLearnMore = (): void => {
                 showAnalyticsLearnMoreDialog({
@@ -327,9 +318,8 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
                     hasCancel: false,
                 });
             };
-            privacySection = (
-                <SettingsSection heading={_t("common|privacy")}>
-                    <DiscoverySettings />
+            posthogSection = (
+                <>
                     <SettingsSubsection
                         heading={_t("common|analytics")}
                         description={_t("settings|security|analytics_description")}
@@ -344,7 +334,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
                     <SettingsSubsection heading={_t("settings|sessions|title")}>
                         <SettingsFlag name="deviceClientInformationOptIn" level={SettingLevel.ACCOUNT} />
                     </SettingsSubsection>
-                </SettingsSection>
+                </>
             );
         }
 
@@ -370,10 +360,11 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
                 <SettingsSection heading={_t("settings|security|encryption_section")}>
                     {secureBackup}
                     {eventIndex}
-                    {crossSigning}
-                    <CryptographyPanel />
                 </SettingsSection>
-                {privacySection}
+                <SettingsSection heading={_t("common|privacy")}>
+                    <DiscoverySettings />
+                    {posthogSection}
+                </SettingsSection>
                 {advancedSection}
             </SettingsTab>
         );
diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
index bc4355652b6aef23aa743dfab0254bc268ce987c..ab5b941cdefcdcf53fbd1abff5a0722b10e799b0 100644
--- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx
+++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
@@ -7,9 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react";
-import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import { _t } from "../../../../../languageHandler";
 import Modal from "../../../../../Modal";
@@ -21,14 +20,14 @@ import { useOwnDevices } from "../../devices/useOwnDevices";
 import { FilteredDeviceList } from "../../devices/FilteredDeviceList";
 import CurrentDeviceSection from "../../devices/CurrentDeviceSection";
 import SecurityRecommendations from "../../devices/SecurityRecommendations";
-import { ExtendedDevice } from "../../devices/types";
+import { type ExtendedDevice } from "../../devices/types";
 import { deleteDevicesWithInteractiveAuth } from "../../devices/deleteDevices";
 import SettingsTab from "../SettingsTab";
 import LoginWithQRSection from "../../devices/LoginWithQRSection";
 import { Mode } from "../../../auth/LoginWithQR-types";
 import { useAsyncMemo } from "../../../../../hooks/useAsyncMemo";
 import QuestionDialog from "../../../dialogs/QuestionDialog";
-import { FilterVariation } from "../../devices/filter";
+import { type FilterVariation } from "../../devices/filter";
 import { OtherSessionsSectionHeading } from "../../devices/OtherSessionsSectionHeading";
 import { SettingsSection } from "../../shared/SettingsSection";
 import { getManageDeviceUrl } from "../../../../../utils/oidc/urls.ts";
@@ -98,9 +97,9 @@ const useSignOut = (
                 const url = getManageDeviceUrl(delegatedAuthAccountUrl, deviceId);
                 window.open(url, "_blank");
             } else {
-                const deferredSuccess = defer<boolean>();
+                const deferredSuccess = Promise.withResolvers<boolean>();
                 await deleteDevicesWithInteractiveAuth(matrixClient, deviceIds, async (success) => {
-                    deferredSuccess.resolve(success);
+                    deferredSuccess.resolve(!!success);
                 });
                 success = await deferredSuccess.promise;
             }
@@ -143,7 +142,7 @@ const SessionManagerTab: React.FC<{
     const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice["device_id"][]>([]);
     const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice["device_id"][]>([]);
     const filteredDeviceListRef = useRef<HTMLDivElement>(null);
-    const scrollIntoViewTimeoutRef = useRef<number>();
+    const scrollIntoViewTimeoutRef = useRef<number>(undefined);
 
     const sdkContext = useContext(SDKContext);
     const matrixClient = sdkContext.client!;
@@ -163,10 +162,7 @@ const SessionManagerTab: React.FC<{
     const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
     const oidcClientConfig = useAsyncMemo(async () => {
         try {
-            const authIssuer = await matrixClient?.getAuthIssuer();
-            if (authIssuer) {
-                return discoverAndValidateOIDCIssuerWellKnown(authIssuer.issuer);
-            }
+            return await matrixClient?.getAuthMetadata();
         } catch (e) {
             logger.error("Failed to discover OIDC metadata", e);
         }
@@ -206,7 +202,8 @@ const SessionManagerTab: React.FC<{
     const shouldShowOtherSessions = otherSessionsCount > 0;
 
     const onVerifyCurrentDevice = (): void => {
-        Modal.createDialog(SetupEncryptionDialog, { onFinished: refreshDevices });
+        const { finished } = Modal.createDialog(SetupEncryptionDialog);
+        finished.then(refreshDevices);
     };
 
     const onTriggerDeviceVerification = useCallback(
@@ -215,14 +212,14 @@ const SessionManagerTab: React.FC<{
                 return;
             }
             const verificationRequestPromise = requestDeviceVerification(deviceId);
-            Modal.createDialog(VerificationRequestDialog, {
+            const { finished } = Modal.createDialog(VerificationRequestDialog, {
                 verificationRequestPromise,
                 member: currentUserMember,
-                onFinished: async (): Promise<void> => {
-                    const request = await verificationRequestPromise;
-                    request.cancel();
-                    await refreshDevices();
-                },
+            });
+            finished.then(async () => {
+                const request = await verificationRequestPromise;
+                request.cancel();
+                await refreshDevices();
             });
         },
         [requestDeviceVerification, refreshDevices, currentUserMember],
diff --git a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx
index d7b6283aa45bd705408935adb557b5ff3a3c917f..041e437661e2fcf27a620d4d409662b173516783 100644
--- a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx
@@ -1,12 +1,12 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021-2023 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, useMemo } from "react";
+import React, { type ChangeEvent, useMemo } from "react";
 import {
     VideoCallSolidIcon,
     HomeSolidIcon,
@@ -14,7 +14,6 @@ import {
     FavouriteSolidIcon,
 } from "@vector-im/compound-design-tokens/assets/web/icons";
 
-import { Icon as HashCircleIcon } from "../../../../../../res/img/element-icons/roomlist/hash-circle.svg";
 import { _t } from "../../../../../languageHandler";
 import SettingsStore from "../../../../../settings/SettingsStore";
 import { SettingLevel } from "../../../../../settings/SettingLevel";
@@ -24,7 +23,7 @@ import { MetaSpace } from "../../../../../stores/spaces";
 import PosthogTrackers from "../../../../../PosthogTrackers";
 import SettingsTab from "../SettingsTab";
 import { SettingsSection } from "../../shared/SettingsSection";
-import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
+import { SettingsSubsection } from "../../shared/SettingsSubsection";
 import SdkConfig from "../../../../../SdkConfig";
 
 type InteractionName = "WebSettingsSidebarTabSpacesCheckbox" | "WebQuickSettingsPinToSidebarCheckbox";
@@ -72,6 +71,9 @@ const SidebarUserSettingsTab: React.FC = () => {
         PosthogTrackers.trackInteraction("WebSettingsSidebarTabSpacesCheckbox", event, 1);
     };
 
+    // "Favourites" and "People" meta spaces are not available in the new room list
+    const newRoomListEnabled = useSettingValue("feature_new_room_list");
+
     return (
         <SettingsTab>
             <SettingsSection>
@@ -84,14 +86,10 @@ const SidebarUserSettingsTab: React.FC = () => {
                         onChange={onMetaSpaceChangeFactory(MetaSpace.Home, "WebSettingsSidebarTabSpacesCheckbox")}
                         className="mx_SidebarUserSettingsTab_checkbox"
                         disabled={homeEnabled}
+                        description={_t("settings|sidebar|metaspaces_home_description")}
                     >
-                        <SettingsSubsectionText>
-                            <HomeSolidIcon />
-                            {_t("common|home")}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_home_description")}
-                        </SettingsSubsectionText>
+                        <HomeSolidIcon className="mx_SidebarUserSettingsTab_icon" />
+                        {_t("common|home")}
                     </StyledCheckbox>
 
                     <StyledCheckbox
@@ -100,55 +98,48 @@ const SidebarUserSettingsTab: React.FC = () => {
                         onChange={onAllRoomsInHomeToggle}
                         className="mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
                         data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
+                        description={_t("settings|sidebar|metaspaces_home_all_rooms_description")}
                     >
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_home_all_rooms")}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_home_all_rooms_description")}
-                        </SettingsSubsectionText>
+                        {_t("settings|sidebar|metaspaces_home_all_rooms")}
                     </StyledCheckbox>
 
-                    <StyledCheckbox
-                        checked={!!favouritesEnabled}
-                        onChange={onMetaSpaceChangeFactory(MetaSpace.Favourites, "WebSettingsSidebarTabSpacesCheckbox")}
-                        className="mx_SidebarUserSettingsTab_checkbox"
-                    >
-                        <SettingsSubsectionText>
-                            <FavouriteSolidIcon />
-                            {_t("common|favourites")}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_favourites_description")}
-                        </SettingsSubsectionText>
-                    </StyledCheckbox>
+                    {!newRoomListEnabled && (
+                        <>
+                            <StyledCheckbox
+                                checked={!!favouritesEnabled}
+                                onChange={onMetaSpaceChangeFactory(
+                                    MetaSpace.Favourites,
+                                    "WebSettingsSidebarTabSpacesCheckbox",
+                                )}
+                                className="mx_SidebarUserSettingsTab_checkbox"
+                                description={_t("settings|sidebar|metaspaces_favourites_description")}
+                            >
+                                <FavouriteSolidIcon className="mx_SidebarUserSettingsTab_icon" />
+                                {_t("common|favourites")}
+                            </StyledCheckbox>
 
-                    <StyledCheckbox
-                        checked={!!peopleEnabled}
-                        onChange={onMetaSpaceChangeFactory(MetaSpace.People, "WebSettingsSidebarTabSpacesCheckbox")}
-                        className="mx_SidebarUserSettingsTab_checkbox"
-                    >
-                        <SettingsSubsectionText>
-                            <UserProfileSolidIcon />
-                            {_t("common|people")}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_people_description")}
-                        </SettingsSubsectionText>
-                    </StyledCheckbox>
+                            <StyledCheckbox
+                                checked={!!peopleEnabled}
+                                onChange={onMetaSpaceChangeFactory(
+                                    MetaSpace.People,
+                                    "WebSettingsSidebarTabSpacesCheckbox",
+                                )}
+                                className="mx_SidebarUserSettingsTab_checkbox"
+                                description={_t("settings|sidebar|metaspaces_people_description")}
+                            >
+                                <UserProfileSolidIcon className="mx_SidebarUserSettingsTab_icon" />
+                                {_t("common|people")}
+                            </StyledCheckbox>
+                        </>
+                    )}
 
                     <StyledCheckbox
                         checked={!!orphansEnabled}
                         onChange={onMetaSpaceChangeFactory(MetaSpace.Orphans, "WebSettingsSidebarTabSpacesCheckbox")}
                         className="mx_SidebarUserSettingsTab_checkbox"
+                        description={_t("settings|sidebar|metaspaces_orphans_description")}
                     >
-                        <SettingsSubsectionText>
-                            <HashCircleIcon />
-                            {_t("settings|sidebar|metaspaces_orphans")}
-                        </SettingsSubsectionText>
-                        <SettingsSubsectionText>
-                            {_t("settings|sidebar|metaspaces_orphans_description")}
-                        </SettingsSubsectionText>
+                        {_t("settings|sidebar|metaspaces_orphans")}
                     </StyledCheckbox>
                     {SettingsStore.getValue("feature_video_rooms") && (
                         <StyledCheckbox
@@ -158,12 +149,10 @@ const SidebarUserSettingsTab: React.FC = () => {
                                 "WebSettingsSidebarTabSpacesCheckbox",
                             )}
                             className="mx_SidebarUserSettingsTab_checkbox"
+                            description={conferenceSubsectionText}
                         >
-                            <SettingsSubsectionText>
-                                <VideoCallSolidIcon />
-                                {_t("settings|sidebar|metaspaces_video_rooms")}
-                            </SettingsSubsectionText>
-                            <SettingsSubsectionText>{conferenceSubsectionText}</SettingsSubsectionText>
+                            <VideoCallSolidIcon className="mx_SidebarUserSettingsTab_icon" />
+                            {_t("settings|sidebar|metaspaces_video_rooms")}
                         </StyledCheckbox>
                     )}
                 </SettingsSubsection>
diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx
index a52a232cd5f63a1cc1055113b7ac77ef40e8d916..84b4ec778fd171d3135d147a4f5a942986a76eb7 100644
--- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx
@@ -7,12 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 import { FALLBACK_ICE_SERVER } from "matrix-js-sdk/src/webrtc/call";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../../../languageHandler";
-import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler";
+import MediaDeviceHandler, { type IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler";
 import Field from "../../../elements/Field";
 import AccessibleButton from "../../../elements/AccessibleButton";
 import { SettingLevel } from "../../../../../settings/SettingLevel";
@@ -49,12 +50,12 @@ const mapDeviceKindToHandlerValue = (deviceKind: MediaDeviceKindEnum): string |
     }
 };
 
-export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
+export default class VoiceUserSettingsTab extends React.Component<EmptyObject, IState> {
     public static contextType = MatrixClientContext;
     declare public context: React.ContextType<typeof MatrixClientContext>;
 
-    public constructor(props: {}, context: React.ContextType<typeof MatrixClientContext>) {
-        super(props, context);
+    public constructor(props: EmptyObject) {
+        super(props);
 
         this.state = {
             mediaDevices: null,
diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx
index 21e2fda30138d05f12bb74a079794d9e34f4db5f..7132032a18215a5f8aacee7762cded245a4f4b31 100644
--- a/src/components/views/spaces/QuickSettingsButton.tsx
+++ b/src/components/views/spaces/QuickSettingsButton.tsx
@@ -1,17 +1,18 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024,2025 New Vector Ltd.
 Copyright 2021-2023 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 import {
     OverflowHorizontalIcon,
     UserProfileSolidIcon,
     FavouriteSolidIcon,
+    PinSolidIcon,
 } from "@vector-im/compound-design-tokens/assets/web/icons";
 
 import { _t } from "../../../languageHandler";
@@ -25,7 +26,6 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { UserTab } from "../dialogs/UserTab";
 import QuickThemeSwitcher from "./QuickThemeSwitcher";
-import { Icon as PinUprightIcon } from "../../../../res/img/element-icons/room/pin-upright.svg";
 import Modal from "../../../Modal";
 import DevtoolsDialog from "../dialogs/DevtoolsDialog";
 import { SdkContextClass } from "../../../contexts/SDKContext";
@@ -40,13 +40,19 @@ const QuickSettingsButton: React.FC<{
 
     const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
     const developerModeEnabled = useSettingValue("developerMode");
+    // "Favourites" and "People" meta spaces are not available in the new room list
+    const newRoomListEnabled = useSettingValue("feature_new_room_list");
 
     let contextMenu: JSX.Element | undefined;
     if (menuDisplayed && handle.current) {
         contextMenu = (
             <ContextMenu
                 {...alwaysAboveRightOf(handle.current.getBoundingClientRect(), ChevronFace.None, 16)}
-                wrapperClassName="mx_QuickSettingsButton_ContextMenuWrapper"
+                wrapperClassName={classNames("mx_QuickSettingsButton_ContextMenuWrapper", {
+                    mx_QuickSettingsButton_ContextMenuWrapper_new_room_list: newRoomListEnabled,
+                })}
+                // Eventually replace with a properly aria-labelled menu
+                data-testid="quick-settings-menu"
                 onFinished={closeMenu}
                 managed={false}
                 focusLock={true}
@@ -81,41 +87,49 @@ const QuickSettingsButton: React.FC<{
                     </AccessibleButton>
                 )}
 
-                <h4 className="mx_QuickSettingsButton_pinToSidebarHeading">
-                    <PinUprightIcon className="mx_QuickSettingsButton_icon" />
-                    {_t("quick_settings|metaspace_section")}
-                </h4>
-
-                <StyledCheckbox
-                    className="mx_QuickSettingsButton_favouritesCheckbox"
-                    checked={!!favouritesEnabled}
-                    onChange={onMetaSpaceChangeFactory(MetaSpace.Favourites, "WebQuickSettingsPinToSidebarCheckbox")}
-                >
-                    <FavouriteSolidIcon className="mx_QuickSettingsButton_icon" />
-                    {_t("common|favourites")}
-                </StyledCheckbox>
-                <StyledCheckbox
-                    className="mx_QuickSettingsButton_peopleCheckbox"
-                    checked={!!peopleEnabled}
-                    onChange={onMetaSpaceChangeFactory(MetaSpace.People, "WebQuickSettingsPinToSidebarCheckbox")}
-                >
-                    <UserProfileSolidIcon className="mx_QuickSettingsButton_icon" />
-                    {_t("common|people")}
-                </StyledCheckbox>
-                <AccessibleButton
-                    className="mx_QuickSettingsButton_moreOptionsButton"
-                    onClick={() => {
-                        closeMenu();
-                        defaultDispatcher.dispatch({
-                            action: Action.ViewUserSettings,
-                            initialTabId: UserTab.Sidebar,
-                        });
-                    }}
-                >
-                    <OverflowHorizontalIcon className="mx_QuickSettingsButton_icon" />
-                    {_t("quick_settings|sidebar_settings")}
-                </AccessibleButton>
-
+                {!newRoomListEnabled && (
+                    <>
+                        <h4>
+                            <PinSolidIcon className="mx_QuickSettingsButton_icon" />
+                            {_t("quick_settings|metaspace_section")}
+                        </h4>
+                        <StyledCheckbox
+                            className="mx_QuickSettingsButton_option"
+                            checked={!!favouritesEnabled}
+                            onChange={onMetaSpaceChangeFactory(
+                                MetaSpace.Favourites,
+                                "WebQuickSettingsPinToSidebarCheckbox",
+                            )}
+                        >
+                            <FavouriteSolidIcon className="mx_QuickSettingsButton_icon" />
+                            {_t("common|favourites")}
+                        </StyledCheckbox>
+                        <StyledCheckbox
+                            className="mx_QuickSettingsButton_option"
+                            checked={!!peopleEnabled}
+                            onChange={onMetaSpaceChangeFactory(
+                                MetaSpace.People,
+                                "WebQuickSettingsPinToSidebarCheckbox",
+                            )}
+                        >
+                            <UserProfileSolidIcon className="mx_QuickSettingsButton_icon" />
+                            {_t("common|people")}
+                        </StyledCheckbox>
+                        <AccessibleButton
+                            className="mx_QuickSettingsButton_moreOptionsButton mx_QuickSettingsButton_option"
+                            onClick={() => {
+                                closeMenu();
+                                defaultDispatcher.dispatch({
+                                    action: Action.ViewUserSettings,
+                                    initialTabId: UserTab.Sidebar,
+                                });
+                            }}
+                        >
+                            <OverflowHorizontalIcon className="mx_QuickSettingsButton_icon" />
+                            {_t("quick_settings|sidebar_settings")}
+                        </AccessibleButton>
+                    </>
+                )}
                 <QuickThemeSwitcher requestClose={closeMenu} />
             </ContextMenu>
         );
diff --git a/src/components/views/spaces/QuickThemeSwitcher.tsx b/src/components/views/spaces/QuickThemeSwitcher.tsx
index 5d2372647e07dedbe9af7a4b6adb09abd2b1416c..ddb058f7882faf64742021557414397ed29fd6dd 100644
--- a/src/components/views/spaces/QuickThemeSwitcher.tsx
+++ b/src/components/views/spaces/QuickThemeSwitcher.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement, useMemo } from "react";
+import React, { type ReactElement, useMemo } from "react";
 
 import { _t } from "../../../languageHandler";
 import { Action } from "../../../dispatcher/actions";
@@ -15,9 +15,9 @@ import Dropdown from "../elements/Dropdown";
 import SettingsStore from "../../../settings/SettingsStore";
 import { SettingLevel } from "../../../settings/SettingLevel";
 import dis from "../../../dispatcher/dispatcher";
-import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
+import { type RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
 import PosthogTrackers from "../../../PosthogTrackers";
-import { NonEmptyArray } from "../../../@types/common";
+import { type NonEmptyArray } from "../../../@types/common";
 import { useTheme } from "../../../hooks/useTheme";
 
 type Props = {
diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx
index 4832da50b3ae1e668c2f61965b44216bd36a1915..fb841e936033ca56106e3695a4681727780e4d63 100644
--- a/src/components/views/spaces/SpaceBasicSettings.tsx
+++ b/src/components/views/spaces/SpaceBasicSettings.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, useRef, useState } from "react";
+import React, { type ChangeEvent, useRef, useState } from "react";
 
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
diff --git a/src/components/views/spaces/SpaceChildrenPicker.tsx b/src/components/views/spaces/SpaceChildrenPicker.tsx
index 49f873fcf5f0cc4900e6440d8e8c109734dd4d14..268b22d2887470088c793e65e2624854e15a050e 100644
--- a/src/components/views/spaces/SpaceChildrenPicker.tsx
+++ b/src/components/views/spaces/SpaceChildrenPicker.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useEffect, useMemo, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import StyledRadioGroup from "../elements/StyledRadioGroup";
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx
index e964894502ca020714bc6a84d9a6ea137ce928f2..fd5b0cb1c0feffc0a90f13854ea4bf82667ef0db 100644
--- a/src/components/views/spaces/SpaceCreateMenu.tsx
+++ b/src/components/views/spaces/SpaceCreateMenu.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, {
-    ComponentProps,
-    RefObject,
-    SyntheticEvent,
-    KeyboardEvent,
+    type ComponentProps,
+    type RefObject,
+    type SyntheticEvent,
+    type KeyboardEvent,
     useContext,
     useRef,
     useState,
-    ChangeEvent,
-    ReactNode,
+    type ChangeEvent,
+    type ReactNode,
     useEffect,
 } from "react";
 import classNames from "classnames";
@@ -24,17 +24,18 @@ import {
     HistoryVisibility,
     Preset,
     Visibility,
-    MatrixClient,
-    ICreateRoomOpts,
+    type MatrixClient,
+    type ICreateRoomOpts,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
 import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
-import createRoom, { IOpts as ICreateOpts } from "../../../createRoom";
+import createRoom, { type IOpts as ICreateOpts } from "../../../createRoom";
 import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
-import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import type SpaceBasicSettings from "./SpaceBasicSettings";
+import { SpaceAvatar } from "./SpaceBasicSettings";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import Field from "../elements/Field";
 import withValidation from "../elements/Validation";
 import RoomAliasField from "../elements/RoomAliasField";
@@ -43,7 +44,7 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { Filter } from "../dialogs/spotlight/Filter";
-import { OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload.ts";
+import { type OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload.ts";
 
 export const createSpace = async (
     client: MatrixClient,
@@ -119,8 +120,8 @@ type BProps = Omit<ComponentProps<typeof SpaceBasicSettings>, "nameDisabled" | "
 interface ISpaceCreateFormProps extends BProps {
     busy: boolean;
     alias: string;
-    nameFieldRef: RefObject<Field>;
-    aliasFieldRef: RefObject<RoomAliasField>;
+    nameFieldRef: RefObject<Field | null>;
+    aliasFieldRef: RefObject<RoomAliasField | null>;
     showAliasField?: boolean;
     children?: ReactNode;
     onSubmit(e: SyntheticEvent): void;
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index 3dd46abb8d40685600f36f107bad37dbc63388ad..9f0d534767bdb12c523d84475cc61c2801f0efae 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -7,20 +7,21 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, {
-    ComponentProps,
-    Dispatch,
-    ReactNode,
-    RefCallback,
-    SetStateAction,
+    type ComponentProps,
+    type Dispatch,
+    type ReactNode,
+    type RefCallback,
+    type SetStateAction,
+    type JSX,
     useCallback,
     useEffect,
     useLayoutEffect,
     useRef,
     useState,
 } from "react";
-import { DragDropContext, Draggable, Droppable, DroppableProvidedProps } from "react-beautiful-dnd";
+import { DragDropContext, Draggable, Droppable, type DroppableProvidedProps } from "react-beautiful-dnd";
 import classNames from "classnames";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { useContextMenu } from "../../structures/ContextMenu";
@@ -31,7 +32,7 @@ import SpaceStore from "../../../stores/spaces/SpaceStore";
 import {
     getMetaSpaceName,
     MetaSpace,
-    SpaceKey,
+    type SpaceKey,
     UPDATE_HOME_BEHAVIOUR,
     UPDATE_INVITED_SPACES,
     UPDATE_SELECTED_SPACE,
@@ -42,7 +43,7 @@ import {
     RoomNotificationStateStore,
     UPDATE_STATUS_INDICATOR,
 } from "../../../stores/notifications/RoomNotificationStateStore";
-import SpaceContextMenu from "../context_menus/SpaceContextMenu";
+import type SpaceContextMenu from "../context_menus/SpaceContextMenu";
 import IconizedContextMenu, {
     IconizedContextMenuCheckbox,
     IconizedContextMenuOptionList,
@@ -56,9 +57,9 @@ import UserMenu from "../../structures/UserMenu";
 import IndicatorScrollbar from "../../structures/IndicatorScrollbar";
 import { useDispatcher } from "../../../hooks/useDispatcher";
 import defaultDispatcher from "../../../dispatcher/dispatcher";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { Action } from "../../../dispatcher/actions";
-import { NotificationState } from "../../../stores/notifications/NotificationState";
+import { type NotificationState } from "../../../stores/notifications/NotificationState";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
@@ -362,6 +363,8 @@ const SpacePanel: React.FC = () => {
         }
     });
 
+    const newRoomListEnabled = useSettingValue("feature_new_room_list");
+
     return (
         <RovingTabIndexProvider handleHomeEnd handleUpDown={!dragging}>
             {({ onKeyDownHandler, onDragEndHandler }) => (
@@ -377,7 +380,10 @@ const SpacePanel: React.FC = () => {
                     }}
                 >
                     <nav
-                        className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })}
+                        className={classNames("mx_SpacePanel", {
+                            collapsed: isPanelCollapsed,
+                            newUi: newRoomListEnabled,
+                        })}
                         onKeyDown={(ev) => {
                             const navAction = getKeyBindingsManager().getNavigationAction(ev);
                             if (
diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx
index 8e148567ac7812308196f11ac3b39acd97b71910..c3a9fae56246e2ff74623a6b0000cbbfe7bf814d 100644
--- a/src/components/views/spaces/SpacePublicShare.tsx
+++ b/src/components/views/spaces/SpacePublicShare.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { sleep } from "matrix-js-sdk/src/utils";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
index 09f14ec2e136a0b0c6abc12d93710a4a29fc6cdb..f8a984e0c31fe3d4b032468e387adafaea8ca337 100644
--- a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { useState } from "react";
-import { Room, EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Room, EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../../languageHandler";
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 3612f668c9c0f99469d11bc06fd9dc25a8382182..e456070cce3cae095bcf4208d32d90ba9b2b7828 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -6,8 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useState } from "react";
-import { Room, EventType, GuestAccess, HistoryVisibility, JoinRule, MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useState } from "react";
+import {
+    type Room,
+    EventType,
+    GuestAccess,
+    HistoryVisibility,
+    JoinRule,
+    type MatrixClient,
+} from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import AccessibleButton from "../elements/AccessibleButton";
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index d85e5ff2a911151864e0b962fb179e98ce99e19b..d03ac8a1e20adadf3b61ad477830140d94830501 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -7,22 +7,23 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, {
-    MouseEvent,
-    ComponentProps,
-    ComponentType,
+    type JSX,
+    type MouseEvent,
+    type ComponentProps,
+    type ComponentType,
     createRef,
-    InputHTMLAttributes,
-    LegacyRef,
-    RefObject,
+    type InputHTMLAttributes,
+    type LegacyRef,
+    type RefObject,
 } from "react";
 import classNames from "classnames";
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
+import { type DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
 
 import RoomAvatar from "../avatars/RoomAvatar";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
-import { SpaceKey } from "../../../stores/spaces";
+import { type SpaceKey } from "../../../stores/spaces";
 import SpaceTreeLevelLayoutStore from "../../../stores/spaces/SpaceTreeLevelLayoutStore";
 import NotificationBadge from "../rooms/NotificationBadge";
 import { _t } from "../../../languageHandler";
@@ -30,18 +31,21 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
 import { Action } from "../../../dispatcher/actions";
 import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
 import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
-import AccessibleButton, { ButtonEvent, ButtonProps as AccessibleButtonProps } from "../elements/AccessibleButton";
+import AccessibleButton, {
+    type ButtonEvent,
+    type ButtonProps as AccessibleButtonProps,
+} from "../elements/AccessibleButton";
 import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
 import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
-import { NotificationState } from "../../../stores/notifications/NotificationState";
+import { type NotificationState } from "../../../stores/notifications/NotificationState";
 import SpaceContextMenu from "../context_menus/SpaceContextMenu";
 import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 
 type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
     AccessibleButtonProps<T>,
-    "title" | "onClick" | "size" | "element"
+    "title" | "onClick" | "size" | "element" | "ref"
 > & {
     space?: Room;
     spaceKey?: SpaceKey;
@@ -52,7 +56,7 @@ type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
     notificationState?: NotificationState;
     isNarrow?: boolean;
     size: string;
-    innerRef?: RefObject<HTMLDivElement>;
+    innerRef?: RefObject<HTMLDivElement | null>;
     ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
     onClick?(ev?: ButtonEvent): void;
 };
diff --git a/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentre.tsx b/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentre.tsx
index 562019a33f8522e1f67df65e88dfbb8e89217966..be7fa2bd0ebb94cd3e317bb24041257316911232 100644
--- a/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentre.tsx
+++ b/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentre.tsx
@@ -6,28 +6,25 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { JSX, useState } from "react";
+import React, { type JSX, useState } from "react";
 import { Menu, MenuItem } from "@vector-im/compound-web";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { ThreadsActivityCentreButton } from "./ThreadsActivityCentreButton";
 import { _t } from "../../../../languageHandler";
 import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
 import { Action } from "../../../../dispatcher/actions";
 import defaultDispatcher from "../../../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
 import RightPanelStore from "../../../../stores/right-panel/RightPanelStore";
 import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases";
 import { useUnreadThreadRooms } from "./useUnreadThreadRooms";
 import { StatelessNotificationBadge } from "../../rooms/NotificationBadge/StatelessNotificationBadge";
-import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
+import { type NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
 import PosthogTrackers from "../../../../PosthogTrackers";
 import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
-import { ReleaseAnnouncement } from "../../../structures/ReleaseAnnouncement";
-import { useIsReleaseAnnouncementOpen } from "../../../../hooks/useIsReleaseAnnouncementOpen";
 import { useSettingValue } from "../../../../hooks/useSettings";
-import { ReleaseAnnouncementStore } from "../../../../stores/ReleaseAnnouncementStore";
 
 interface ThreadsActivityCentreProps {
     /**
@@ -43,7 +40,6 @@ interface ThreadsActivityCentreProps {
 export function ThreadsActivityCentre({ displayButtonLabel }: ThreadsActivityCentreProps): JSX.Element {
     const [open, setOpen] = useState(false);
     const roomsAndNotifications = useUnreadThreadRooms(open);
-    const isReleaseAnnouncementOpen = useIsReleaseAnnouncementOpen("threadsActivityCentre");
     const settingTACOnlyNotifs = useSettingValue("Notifications.tac_only_notifications");
 
     const emptyCaption = settingTACOnlyNotifs
@@ -65,59 +61,39 @@ export function ThreadsActivityCentre({ displayButtonLabel }: ThreadsActivityCen
                 }
             }}
         >
-            {isReleaseAnnouncementOpen ? (
-                <ReleaseAnnouncement
-                    feature="threadsActivityCentre"
-                    header={_t("threads_activity_centre|release_announcement_header")}
-                    description={_t("threads_activity_centre|release_announcement_description")}
-                    closeLabel={_t("action|ok")}
-                >
+            <Menu
+                align="start"
+                side="top"
+                open={open}
+                onOpenChange={(newOpen) => {
+                    // Track only when the Threads Activity Centre is opened
+                    if (newOpen) PosthogTrackers.trackInteraction("WebThreadsActivityCentreButton");
+
+                    setOpen(newOpen);
+                }}
+                title={_t("threads_activity_centre|header")}
+                trigger={
                     <ThreadsActivityCentreButton
-                        disableTooltip={true}
                         displayLabel={displayButtonLabel}
                         notificationLevel={roomsAndNotifications.greatestNotificationLevel}
-                        onClick={async () => {
-                            // Open the TAC after the release announcement closing
-                            setOpen(true);
-                            await ReleaseAnnouncementStore.instance.nextReleaseAnnouncement();
-                        }}
                     />
-                </ReleaseAnnouncement>
-            ) : (
-                <Menu
-                    align="start"
-                    side="top"
-                    open={open}
-                    onOpenChange={(newOpen) => {
-                        // Track only when the Threads Activity Centre is opened
-                        if (newOpen) PosthogTrackers.trackInteraction("WebThreadsActivityCentreButton");
-
-                        setOpen(newOpen);
-                    }}
-                    title={_t("threads_activity_centre|header")}
-                    trigger={
-                        <ThreadsActivityCentreButton
-                            displayLabel={displayButtonLabel}
-                            notificationLevel={roomsAndNotifications.greatestNotificationLevel}
+                }
+            >
+                {/* Make the content of the pop-up scrollable */}
+                <div className="mx_ThreadsActivityCentre_rows">
+                    {roomsAndNotifications.rooms.map(({ room, notificationLevel }) => (
+                        <ThreadsActivityCentreRow
+                            key={room.roomId}
+                            room={room}
+                            notificationLevel={notificationLevel}
+                            onClick={() => setOpen(false)}
                         />
-                    }
-                >
-                    {/* Make the content of the pop-up scrollable */}
-                    <div className="mx_ThreadsActivityCentre_rows">
-                        {roomsAndNotifications.rooms.map(({ room, notificationLevel }) => (
-                            <ThreadsActivityCentreRow
-                                key={room.roomId}
-                                room={room}
-                                notificationLevel={notificationLevel}
-                                onClick={() => setOpen(false)}
-                            />
-                        ))}
-                        {roomsAndNotifications.rooms.length === 0 && (
-                            <div className="mx_ThreadsActivityCentre_emptyCaption">{emptyCaption}</div>
-                        )}
-                    </div>
-                </Menu>
-            )}
+                    ))}
+                    {roomsAndNotifications.rooms.length === 0 && (
+                        <div className="mx_ThreadsActivityCentre_emptyCaption">{emptyCaption}</div>
+                    )}
+                </div>
+            </Menu>
         </div>
     );
 }
diff --git a/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentreButton.tsx b/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentreButton.tsx
index 123b8a072eba7fad62c97079fe72943c74dacd50..9d43655c60be66310862f807e47e34ef086fc6ae 100644
--- a/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentreButton.tsx
+++ b/src/components/views/spaces/threads-activity-centre/ThreadsActivityCentreButton.tsx
@@ -6,13 +6,13 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { ComponentProps, forwardRef } from "react";
+import React, { type ComponentProps, type Ref, type JSX } from "react";
 import ThreadsSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
 import classNames from "classnames";
 import { IconButton, Text, Tooltip } from "@vector-im/compound-web";
 
 import { _t } from "../../../../languageHandler";
-import { NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
+import { type NotificationLevel } from "../../../../stores/notifications/NotificationLevel";
 import { notificationLevelToIndicator } from "../../../../utils/notifications";
 
 interface ThreadsActivityCentreButtonProps extends ComponentProps<typeof IconButton> {
@@ -28,44 +28,46 @@ interface ThreadsActivityCentreButtonProps extends ComponentProps<typeof IconBut
      * The notification level of the threads.
      */
     notificationLevel: NotificationLevel;
+    ref?: Ref<HTMLButtonElement>;
 }
 
 /**
  * A button to open the thread activity centre.
  */
-export const ThreadsActivityCentreButton = forwardRef<HTMLButtonElement, ThreadsActivityCentreButtonProps>(
-    function ThreadsActivityCentreButton(
-        { displayLabel, notificationLevel, disableTooltip, ...props },
-        ref,
-    ): React.JSX.Element {
-        // Disable tooltip when the label is displayed
-        const openTooltip = disableTooltip || displayLabel ? false : undefined;
+export const ThreadsActivityCentreButton = function ThreadsActivityCentreButton({
+    displayLabel,
+    notificationLevel,
+    disableTooltip,
+    ref,
+    ...props
+}: ThreadsActivityCentreButtonProps): JSX.Element {
+    // Disable tooltip when the label is displayed
+    const openTooltip = disableTooltip || displayLabel ? false : undefined;
 
-        return (
-            <Tooltip label={_t("common|threads")} placement="right" open={openTooltip}>
-                <IconButton
-                    aria-label={_t("common|threads")}
-                    className={classNames("mx_ThreadsActivityCentreButton", { expanded: displayLabel })}
-                    indicator={notificationLevelToIndicator(notificationLevel)}
-                    {...props}
-                    ref={ref}
-                >
-                    <>
-                        <ThreadsSolidIcon className="mx_ThreadsActivityCentreButton_Icon" />
-                        {/* This is dirty, but we need to add the label to the indicator icon */}
-                        {displayLabel && (
-                            <Text
-                                className="mx_ThreadsActivityCentreButton_Text"
-                                as="span"
-                                size="md"
-                                title={_t("common|threads")}
-                            >
-                                {_t("common|threads")}
-                            </Text>
-                        )}
-                    </>
-                </IconButton>
-            </Tooltip>
-        );
-    },
-);
+    return (
+        <Tooltip label={_t("common|threads")} placement="right" open={openTooltip}>
+            <IconButton
+                aria-label={_t("common|threads")}
+                className={classNames("mx_ThreadsActivityCentreButton", { expanded: displayLabel })}
+                indicator={notificationLevelToIndicator(notificationLevel)}
+                {...props}
+                ref={ref}
+            >
+                <>
+                    <ThreadsSolidIcon className="mx_ThreadsActivityCentreButton_Icon" />
+                    {/* This is dirty, but we need to add the label to the indicator icon */}
+                    {displayLabel && (
+                        <Text
+                            className="mx_ThreadsActivityCentreButton_Text"
+                            as="span"
+                            size="md"
+                            title={_t("common|threads")}
+                        >
+                            {_t("common|threads")}
+                        </Text>
+                    )}
+                </>
+            </IconButton>
+        </Tooltip>
+    );
+};
diff --git a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts
index dea00bafa7056c06c74f62d3e6eab80b752b6333..966e223df6a59c39141661382e2b41f64a8f6e08 100644
--- a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts
+++ b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts
@@ -7,7 +7,7 @@
  */
 
 import { useCallback, useEffect, useMemo, useState } from "react";
-import { ClientEvent, MatrixClient, MatrixEventEvent, Room } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient, MatrixEventEvent, type Room } from "matrix-js-sdk/src/matrix";
 import { throttle } from "lodash";
 
 import { doesRoomHaveUnreadThreads } from "../../../../Unread";
diff --git a/src/components/views/terms/InlineTermsAgreement.tsx b/src/components/views/terms/InlineTermsAgreement.tsx
index 2e4f48712ff87f6aa6ef9fb1a8b4486853b3a412..fbe8f23b371ec7db16458b570848ccdde659d851 100644
--- a/src/components/views/terms/InlineTermsAgreement.tsx
+++ b/src/components/views/terms/InlineTermsAgreement.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
-import { _t, pickBestLanguage } from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
 import { objectClone } from "../../../utils/objects";
 import StyledCheckbox from "../elements/StyledCheckbox";
 import AccessibleButton from "../elements/AccessibleButton";
-import { ServicePolicyPair } from "../../../Terms";
+import { pickBestPolicyLanguage, type ServicePolicyPair } from "../../../Terms";
 
 interface IProps {
     policiesAndServicePairs: ServicePolicyPair[];
@@ -47,11 +47,12 @@ export default class InlineTermsAgreement extends React.Component<IProps, IState
         for (const servicePolicies of this.props.policiesAndServicePairs) {
             const availablePolicies = Object.values(servicePolicies.policies);
             for (const policy of availablePolicies) {
-                const language = pickBestLanguage(Object.keys(policy).filter((p) => p !== "version"));
+                const internationalisedPolicy = pickBestPolicyLanguage(policy);
+                if (!internationalisedPolicy) continue;
                 const renderablePolicy: Policy = {
                     checked: false,
-                    url: policy[language].url,
-                    name: policy[language].name,
+                    url: internationalisedPolicy.url,
+                    name: internationalisedPolicy.name,
                 };
                 policies.push(renderablePolicy);
             }
diff --git a/src/components/views/toasts/GenericExpiringToast.tsx b/src/components/views/toasts/GenericExpiringToast.tsx
index b019babaf23692e7e1d00aa315675432a67b914c..abbbacc5f87d019f0d6a4411a5acc594584a7d2b 100644
--- a/src/components/views/toasts/GenericExpiringToast.tsx
+++ b/src/components/views/toasts/GenericExpiringToast.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 
 import ToastStore from "../../../stores/ToastStore";
-import GenericToast, { IProps as IGenericToastProps } from "./GenericToast";
+import GenericToast, { type IProps as IGenericToastProps } from "./GenericToast";
 import { useExpiringCounter } from "../../../hooks/useTimeout";
 
 interface IProps extends IGenericToastProps {
diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx
index 61c62723771e77c5509766c06c3c2535652340e6..d57dddc7056d48071410cd85a9b5f8a16f17dde5 100644
--- a/src/components/views/toasts/GenericToast.tsx
+++ b/src/components/views/toasts/GenericToast.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentType, ReactNode } from "react";
+import React, { type ComponentType, type ReactNode } from "react";
 import { Button } from "@vector-im/compound-web";
 
-import { XOR } from "../../../@types/common";
+import { type XOR } from "../../../@types/common";
 
 export interface IProps {
     description: ReactNode;
diff --git a/src/components/views/toasts/VerificationRequestToast.tsx b/src/components/views/toasts/VerificationRequestToast.tsx
index f235b8f488b9aa347229bfaa76c2bcaadb4abc55..12843c0b87fcba9f4bef7cfa805bf43be40d8902 100644
--- a/src/components/views/toasts/VerificationRequestToast.tsx
+++ b/src/components/views/toasts/VerificationRequestToast.tsx
@@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import {
     canAcceptVerificationRequest,
-    VerificationRequest,
+    type VerificationRequest,
     VerificationRequestEvent,
 } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Device } from "matrix-js-sdk/src/matrix";
+import { type Device } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
@@ -26,7 +26,7 @@ import GenericToast from "./GenericToast";
 import { Action } from "../../../dispatcher/actions";
 import VerificationRequestDialog from "../dialogs/VerificationRequestDialog";
 import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
-import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
 import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo";
 
 interface IProps {
@@ -124,18 +124,16 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
                     request.roomId,
                 );
             } else {
-                Modal.createDialog(
+                const { finished } = Modal.createDialog(
                     VerificationRequestDialog,
                     {
                         verificationRequest: request,
-                        onFinished: () => {
-                            request.cancel();
-                        },
                     },
                     undefined,
                     /* priority = */ false,
                     /* static = */ true,
                 );
+                finished.then(() => request.cancel());
             }
             await request.accept();
         } catch (err) {
diff --git a/src/components/views/typography/Caption.tsx b/src/components/views/typography/Caption.tsx
index 0ce9fbb65c113abf3d593c39832802d776216e2b..8279ac9b7c10a8542927d4d8117fc6e8e77d355b 100644
--- a/src/components/views/typography/Caption.tsx
+++ b/src/components/views/typography/Caption.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classNames from "classnames";
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 
 interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "className"> {
     children: React.ReactNode;
diff --git a/src/components/views/typography/Heading.tsx b/src/components/views/typography/Heading.tsx
index c367c5d445769697035bf58ee3ba55bc0323c86b..883f5f3b265e0c42d3ee79f51f78112b651ec00f 100644
--- a/src/components/views/typography/Heading.tsx
+++ b/src/components/views/typography/Heading.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 import classNames from "classnames";
 
 type Size = "1" | "2" | "3" | "4";
diff --git a/src/components/views/verification/VerificationShowSas.tsx b/src/components/views/verification/VerificationShowSas.tsx
index df9ddba89217d450ac981f167ba65c2fb9dd5049..2292aad09072f7df460a6e0becf60f1168776a8c 100644
--- a/src/components/views/verification/VerificationShowSas.tsx
+++ b/src/components/views/verification/VerificationShowSas.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Device } from "matrix-js-sdk/src/matrix";
-import { GeneratedSas, EmojiMapping } from "matrix-js-sdk/src/crypto-api";
+import { type Device } from "matrix-js-sdk/src/matrix";
+import { type GeneratedSas, type EmojiMapping } from "matrix-js-sdk/src/crypto-api";
 import SasEmoji from "@matrix-org/spec/sas-emoji.json";
 
 import { _t, getNormalizedLanguageKeys, getUserLanguage } from "../../../languageHandler";
@@ -165,12 +165,12 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
         } else {
             confirm = (
                 <div className="mx_VerificationShowSas_buttonRow">
-                    <AccessibleButton onClick={this.onDontMatchClick} kind="danger">
-                        {_t("encryption|verification|sas_no_match")}
-                    </AccessibleButton>
                     <AccessibleButton onClick={this.onMatchClick} kind="primary">
                         {_t("encryption|verification|sas_match")}
                     </AccessibleButton>
+                    <AccessibleButton onClick={this.onDontMatchClick} kind="secondary">
+                        {_t("encryption|verification|sas_no_match")}
+                    </AccessibleButton>
                 </div>
             );
         }
diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx
index 36a02c21cd77ec0bb119696a94d41373d58bef00..834318f41ad48e9e12749a3e5983a73510ac0f78 100644
--- a/src/components/views/voip/AudioFeed.tsx
+++ b/src/components/views/voip/AudioFeed.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { createRef } from "react";
-import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
+import { type CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
diff --git a/src/components/views/voip/AudioFeedArrayForLegacyCall.tsx b/src/components/views/voip/AudioFeedArrayForLegacyCall.tsx
index 5b2a0d0c87f7c941ceae3de17ebfbde9b3e9e0e6..062ea8693e5c7b7744962343272cd11d2a58d98a 100644
--- a/src/components/views/voip/AudioFeedArrayForLegacyCall.tsx
+++ b/src/components/views/voip/AudioFeedArrayForLegacyCall.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { CallEvent, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
-import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
+import React, { type JSX } from "react";
+import { CallEvent, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
 
 import AudioFeed from "./AudioFeed";
 
diff --git a/src/components/views/voip/CallDuration.tsx b/src/components/views/voip/CallDuration.tsx
index 34f5d0ba608f86b58b35b109078ee397ad740d84..ccf57d3f026b763420590d7fafd7c91d8dedc654 100644
--- a/src/components/views/voip/CallDuration.tsx
+++ b/src/components/views/voip/CallDuration.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, useState, useEffect, memo } from "react";
-import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
+import React, { type FC, useState, useEffect, memo } from "react";
+import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 
 import { formatPreciseDuration } from "../../../DateUtils";
 
diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx
index b7b15c2f7155d997ed4ee72aaecd410092e18244..87bf84d8c4a6a4116a07cd2b56d4727d481d9c73 100644
--- a/src/components/views/voip/CallView.tsx
+++ b/src/components/views/voip/CallView.tsx
@@ -6,15 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { FC, useContext, useEffect, AriaRole, useCallback } from "react";
+import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
 
 import type { Room } from "matrix-js-sdk/src/matrix";
-import { Call, ConnectionState, ElementCall } from "../../../models/Call";
-import { useCall } from "../../../hooks/useCall";
+import { type Call, CallEvent } from "../../../models/Call";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
 import AppTile from "../elements/AppTile";
 import { CallStore } from "../../../stores/CallStore";
 import { SdkContextClass } from "../../../contexts/SDKContext";
+import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
+import { useCall } from "../../../hooks/useCall";
 
 interface JoinCallViewProps {
     room: Room;
@@ -22,10 +23,12 @@ interface JoinCallViewProps {
     call: Call;
     skipLobby?: boolean;
     role?: AriaRole;
+    onClose: () => void;
 }
 
-const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role }) => {
+const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
     const cli = useContext(MatrixClientContext);
+    useTypedEventEmitter(call, CallEvent.Close, onClose);
 
     useEffect(() => {
         // We'll take this opportunity to tidy up our room state
@@ -38,17 +41,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
         call.widget.data.skipLobby = skipLobby;
     }, [call.widget, skipLobby]);
 
-    useEffect(() => {
-        if (call.connectionState === ConnectionState.Disconnected) {
-            // immediately start the call
-            // (this will start the lobby view in the widget and connect to all required widget events)
-            call.start();
-        }
-        return (): void => {
-            // If we are connected the widget is sticky and we do not want to destroy the call.
-            if (!call.connected) call.destroy();
-        };
-    }, [call]);
     const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
         // The stickyPromise has to resolve before the widget actually becomes sticky.
         // We only let the widget become sticky after disconnecting all other active calls.
@@ -57,8 +49,9 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
         );
         await Promise.all(calls.map(async (call) => await call.disconnect()));
     }, []);
+
     return (
-        <div className="mx_CallView">
+        <div className="mx_CallView" role={role}>
             <AppTile
                 app={call.widget}
                 room={room}
@@ -76,26 +69,27 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
 interface CallViewProps {
     room: Room;
     resizing: boolean;
-    /**
-     * If true, the view will be blank until a call appears. Otherwise, the join
-     * button will create a call if there isn't already one.
-     */
-    waitForCall: boolean;
     skipLobby?: boolean;
     role?: AriaRole;
+    /**
+     * Callback for when the user closes the call.
+     */
+    onClose: () => void;
 }
 
-export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
+export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
     const call = useCall(room.roomId);
 
-    useEffect(() => {
-        if (call === null && !waitForCall) {
-            ElementCall.create(room, skipLobby);
-        }
-    }, [call, room, skipLobby, waitForCall]);
-    if (call === null) {
-        return null;
-    } else {
-        return <JoinCallView room={room} resizing={resizing} call={call} skipLobby={skipLobby} role={role} />;
-    }
+    return (
+        call && (
+            <JoinCallView
+                room={room}
+                resizing={resizing}
+                call={call}
+                skipLobby={skipLobby}
+                role={role}
+                onClose={onClose}
+            />
+        )
+    );
 };
diff --git a/src/components/views/voip/DialPad.tsx b/src/components/views/voip/DialPad.tsx
index a23c89fdef89afba8867e9839649df217ece2193..d84b05015255d391e6d1a316263729e12431abae 100644
--- a/src/components/views/voip/DialPad.tsx
+++ b/src/components/views/voip/DialPad.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React, { type JSX } from "react";
 
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import { _t } from "../../../languageHandler";
-import { XOR } from "../../../@types/common";
+import { type XOR } from "../../../@types/common";
 
 export const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"];
 export const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""];
diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx
index 172a9cbaa572e8f9e85cc71206d4e8f58ef176e5..538f59c3ea605ef409f8307934205a7ceb8520bb 100644
--- a/src/components/views/voip/DialPadModal.tsx
+++ b/src/components/views/voip/DialPadModal.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent, createRef, SyntheticEvent } from "react";
+import React, { type ChangeEvent, createRef, type SyntheticEvent } from "react";
 
-import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
 import Field from "../elements/Field";
 import DialPad from "./DialPad";
 import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
@@ -23,7 +23,7 @@ interface IState {
 }
 
 export default class DialpadModal extends React.PureComponent<IProps, IState> {
-    private numberEntryFieldRef: React.RefObject<Field> = createRef();
+    private numberEntryFieldRef = createRef<Field>();
 
     public constructor(props: IProps) {
         super(props);
diff --git a/src/components/views/voip/LegacyCallView.tsx b/src/components/views/voip/LegacyCallView.tsx
index 9c5246b912f16b43e94c8dbac7fb91ecf7435abc..255b1ab0f469bfc8757b49af75966b0e54fc5a7b 100644
--- a/src/components/views/voip/LegacyCallView.tsx
+++ b/src/components/views/voip/LegacyCallView.tsx
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef } from "react";
-import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import React, { type JSX, createRef } from "react";
+import { CallEvent, CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import classNames from "classnames";
-import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
+import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
 import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
 
 import dis from "../../../dispatcher/dispatcher";
@@ -25,7 +25,7 @@ import { avatarUrlForMember } from "../../../Avatar";
 import LegacyCallViewSidebar from "./LegacyCallViewSidebar";
 import LegacyCallViewHeader from "./LegacyCallView/LegacyCallViewHeader";
 import LegacyCallViewButtons from "./LegacyCallView/LegacyCallViewButtons";
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { getKeyBindingsManager } from "../../../KeyBindingsManager";
 import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
 
@@ -66,26 +66,15 @@ interface IState {
 }
 
 function getFullScreenElement(): Element | null {
-    return (
-        document.fullscreenElement ||
-        // moz omitted because firefox supports this unprefixed now (webkit here for safari)
-        document.webkitFullscreenElement ||
-        document.msFullscreenElement
-    );
+    return document.fullscreenElement;
 }
 
 function requestFullscreen(element: Element): void {
-    const method =
-        element.requestFullscreen ||
-        // moz omitted since firefox supports unprefixed now
-        element.webkitRequestFullScreen ||
-        element.msRequestFullscreen;
-    if (method) method.call(element);
+    element.requestFullscreen();
 }
 
 function exitFullscreen(): void {
-    const exitMethod = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen;
-    if (exitMethod) exitMethod.call(document);
+    document.exitFullscreen();
 }
 
 export default class LegacyCallView extends React.Component<IProps, IState> {
diff --git a/src/components/views/voip/LegacyCallView/LegacyCallViewButtons.tsx b/src/components/views/voip/LegacyCallView/LegacyCallViewButtons.tsx
index 7788cb0e78e6b8be451a758ed2b302542d653ba4..0e81b5278e687e99d266f3bc633d59fb2129592c 100644
--- a/src/components/views/voip/LegacyCallView/LegacyCallViewButtons.tsx
+++ b/src/components/views/voip/LegacyCallView/LegacyCallViewButtons.tsx
@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, useState, forwardRef } from "react";
+import React, { createRef, useState, type Ref, type FC } from "react";
 import classNames from "classnames";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 
 import LegacyCallContextMenu from "../../context_menus/LegacyCallContextMenu";
 import DialpadContextMenu from "../../context_menus/DialpadContextMenu";
@@ -24,7 +24,10 @@ import {
 import { _t } from "../../../../languageHandler";
 import DeviceContextMenu from "../../context_menus/DeviceContextMenu";
 import { MediaDeviceKindEnum } from "../../../../MediaDeviceHandler";
-import AccessibleButton, { ButtonEvent, ButtonProps as AccessibleButtonProps } from "../../elements/AccessibleButton";
+import AccessibleButton, {
+    type ButtonEvent,
+    type ButtonProps as AccessibleButtonProps,
+} from "../../elements/AccessibleButton";
 
 // Height of the header duplicated from CSS because we need to subtract it from our max
 // height to get the max height of the video
@@ -38,31 +41,40 @@ type ButtonProps = Omit<AccessibleButtonProps<"div">, "title" | "element"> & {
     offLabel?: string;
     forceHide?: boolean;
     onHover?: (hovering: boolean) => void;
+    ref?: Ref<HTMLElement>;
 };
 
-const LegacyCallViewToggleButton = forwardRef<HTMLElement, ButtonProps>(
-    ({ children, state: isOn, className, onLabel, offLabel, forceHide, onHover, ...props }, ref) => {
-        const classes = classNames("mx_LegacyCallViewButtons_button", className, {
-            mx_LegacyCallViewButtons_button_on: isOn,
-            mx_LegacyCallViewButtons_button_off: !isOn,
-        });
+const LegacyCallViewToggleButton: FC<ButtonProps> = ({
+    children,
+    state: isOn,
+    className,
+    onLabel,
+    offLabel,
+    forceHide,
+    onHover,
+    ref,
+    ...props
+}) => {
+    const classes = classNames("mx_LegacyCallViewButtons_button", className, {
+        mx_LegacyCallViewButtons_button_on: isOn,
+        mx_LegacyCallViewButtons_button_off: !isOn,
+    });
 
-        const title = forceHide ? undefined : isOn ? onLabel : offLabel;
+    const title = forceHide ? undefined : isOn ? onLabel : offLabel;
 
-        return (
-            <AccessibleButton
-                ref={ref}
-                className={classes}
-                title={title}
-                placement="top"
-                onTooltipOpenChange={onHover}
-                {...props}
-            >
-                {children}
-            </AccessibleButton>
-        );
-    },
-);
+    return (
+        <AccessibleButton
+            ref={ref}
+            className={classes}
+            title={title}
+            placement="top"
+            onTooltipOpenChange={onHover}
+            {...props}
+        >
+            {children}
+        </AccessibleButton>
+    );
+};
 
 interface IDropdownButtonProps extends ButtonProps {
     deviceKinds: MediaDeviceKindEnum[];
diff --git a/src/components/views/voip/LegacyCallView/LegacyCallViewHeader.tsx b/src/components/views/voip/LegacyCallView/LegacyCallViewHeader.tsx
index b443636d85a76a182a2df0ef897f90173feffe22..0c56bd267c9358eaa1398d1fcb4c6212a90505bb 100644
--- a/src/components/views/voip/LegacyCallView/LegacyCallViewHeader.tsx
+++ b/src/components/views/voip/LegacyCallView/LegacyCallViewHeader.tsx
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
 import { _t } from "../../../../languageHandler";
diff --git a/src/components/views/voip/LegacyCallViewForRoom.tsx b/src/components/views/voip/LegacyCallViewForRoom.tsx
index ed55f908be20b053f9447c6030b1eaebec272952..bd44af5fa522506af1b63cef28b0f31a5b77140b 100644
--- a/src/components/views/voip/LegacyCallViewForRoom.tsx
+++ b/src/components/views/voip/LegacyCallViewForRoom.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import React from "react";
 import { Resizable } from "re-resizable";
 
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler";
 import LegacyCallView from "./LegacyCallView";
-import ResizeNotifier from "../../../utils/ResizeNotifier";
+import type ResizeNotifier from "../../../utils/ResizeNotifier";
 
 interface IProps {
     // What room we should display the call for
diff --git a/src/components/views/voip/LegacyCallViewSidebar.tsx b/src/components/views/voip/LegacyCallViewSidebar.tsx
index 3dcf3c644d5da24ea92b8016cefaf90cd07f7c74..71140bff9c4ced1b320c6214c054ec0d75cadb59 100644
--- a/src/components/views/voip/LegacyCallViewSidebar.tsx
+++ b/src/components/views/voip/LegacyCallViewSidebar.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
-import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
 import classNames from "classnames";
 
 import VideoFeed from "./VideoFeed";
diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx
index 80ea27223c7e3328f9a8a09439c0b133c2387a50..3db168d0978e4550863593f35195fcedaa7dbbd3 100644
--- a/src/components/views/voip/VideoFeed.tsx
+++ b/src/components/views/voip/VideoFeed.tsx
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classnames from "classnames";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import React from "react";
-import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
+import { type CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed";
 import { logger } from "matrix-js-sdk/src/logger";
 import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
 
diff --git a/src/contexts/CurrentRightPanelPhaseContext.tsx b/src/contexts/CurrentRightPanelPhaseContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8bc3080fc382392efb7a5cb9c13327e561f3cf0a
--- /dev/null
+++ b/src/contexts/CurrentRightPanelPhaseContext.tsx
@@ -0,0 +1,34 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { createContext } from "react";
+
+import { useCurrentPhase } from "../hooks/right-panel/useCurrentPhase";
+import { type RightPanelPhases } from "../stores/right-panel/RightPanelStorePhases";
+
+type Context = {
+    isPanelOpen: boolean;
+    currentPhase: RightPanelPhases | null;
+};
+
+export const CurrentRightPanelPhaseContext = createContext<Context | null>(null);
+
+type Props = {
+    roomId: string;
+};
+
+export const CurrentRightPanelPhaseContextProvider: React.FC<React.PropsWithChildren<Props>> = ({
+    roomId,
+    children,
+}) => {
+    const { currentPhase, isOpen } = useCurrentPhase(roomId);
+    return (
+        <CurrentRightPanelPhaseContext.Provider value={{ currentPhase, isPanelOpen: isOpen }}>
+            {children}
+        </CurrentRightPanelPhaseContext.Provider>
+    );
+};
diff --git a/src/contexts/MatrixClientContext.tsx b/src/contexts/MatrixClientContext.tsx
index 79ce5e50094c3ebc2b5060f42f000d0a2ce47df9..1783c9a12f017736d4d9318612c16a239c34419a 100644
--- a/src/contexts/MatrixClientContext.tsx
+++ b/src/contexts/MatrixClientContext.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentClass, createContext, forwardRef, useContext } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentClass, createContext, useContext } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 // This context is available to components under LoggedInView,
 // the context must not be used by components outside a MatrixClientContext tree.
@@ -24,22 +24,16 @@ export function useMatrixClientContext(): MatrixClient {
     return useContext(MatrixClientContext);
 }
 
-const matrixHOC = <ComposedComponentProps extends {}>(
-    ComposedComponent: ComponentClass<ComposedComponentProps>,
-): ((
-    props: Omit<ComposedComponentProps, "mxClient"> & React.RefAttributes<InstanceType<typeof ComposedComponent>>,
-) => React.ReactElement | null) => {
-    type ComposedComponentInstance = InstanceType<typeof ComposedComponent>;
-
-    // eslint-disable-next-line react-hooks/rules-of-hooks
-
-    const TypedComponent = ComposedComponent;
-
-    return forwardRef<ComposedComponentInstance, Omit<ComposedComponentProps, "mxClient">>((props, ref) => {
+const matrixHOC =
+    <ComposedComponentProps extends object>(
+        ComposedComponent: ComponentClass<ComposedComponentProps>,
+    ): ((
+        props: Omit<ComposedComponentProps, "mxClient"> & React.RefAttributes<InstanceType<typeof ComposedComponent>>,
+    ) => React.ReactElement | null) =>
+    (props) => {
         const client = useContext(MatrixClientContext);
 
         // @ts-ignore
-        return <TypedComponent ref={ref} {...props} mxClient={client} />;
-    });
-};
+        return <ComposedComponent {...props} mxClient={client} />;
+    };
 export const withMatrixClientHOC = matrixHOC;
diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts
index 19f87d9471c32535b772d32176bd081944cceb06..95e21fb0b8e1a564c9cfb2abf3afd02c44f5d7c4 100644
--- a/src/contexts/RoomContext.ts
+++ b/src/contexts/RoomContext.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { createContext } from "react";
 
-import { IRoomState } from "../components/structures/RoomView";
+import { type IRoomState } from "../components/structures/RoomView";
 import { Layout } from "../settings/enums/Layout";
 
 export enum TimelineRenderingType {
@@ -70,7 +70,6 @@ const RoomContext = createContext<
     threadId: undefined,
     liveTimeline: undefined,
     narrow: false,
-    activeCall: null,
     msc3946ProcessDynamicPredecessor: false,
     canAskToJoin: false,
     promptAskToJoin: false,
diff --git a/src/contexts/SDKContext.ts b/src/contexts/SDKContext.ts
index 184bea4cfaabf49f3c7d2768cc2cd2595cbedeab..1b8ede3efd728b957b443a146dd011143c3d7c41 100644
--- a/src/contexts/SDKContext.ts
+++ b/src/contexts/SDKContext.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { createContext } from "react";
 
 import defaultDispatcher from "../dispatcher/dispatcher";
@@ -17,7 +17,7 @@ import { MemberListStore } from "../stores/MemberListStore";
 import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
 import RightPanelStore from "../stores/right-panel/RightPanelStore";
 import { RoomViewStore } from "../stores/RoomViewStore";
-import SpaceStore, { SpaceStoreClass } from "../stores/spaces/SpaceStore";
+import SpaceStore, { type SpaceStoreClass } from "../stores/spaces/SpaceStore";
 import TypingStore from "../stores/TypingStore";
 import { UserProfilesStore } from "../stores/UserProfilesStore";
 import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
diff --git a/src/contexts/ScopedRoomContext.tsx b/src/contexts/ScopedRoomContext.tsx
index f08911cbb28797ffaeabfb5f445616bd13f4fa12..1d5840d87123bcd4d290af318dc6ddd82111b436 100644
--- a/src/contexts/ScopedRoomContext.tsx
+++ b/src/contexts/ScopedRoomContext.tsx
@@ -7,7 +7,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
-import React, { ContextType, createContext, memo, ReactNode, useContext, useEffect, useMemo, useState } from "react";
+import React, {
+    type JSX,
+    type ContextType,
+    createContext,
+    memo,
+    type ReactNode,
+    useContext,
+    useEffect,
+    useMemo,
+    useState,
+} from "react";
 
 import { objectKeyChanges } from "../utils/objects.ts";
 import { useTypedEventEmitter } from "../hooks/useEventEmitter.ts";
diff --git a/src/contexts/ToastContext.tsx b/src/contexts/ToastContext.tsx
index 268539d0e779b90f413a899a590ae8f633dbd382..7382a412dcbfd7a9446bb5aca7a0a86449935dd2 100644
--- a/src/contexts/ToastContext.tsx
+++ b/src/contexts/ToastContext.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactNode, createContext, useCallback, useContext, useEffect, useState, useMemo } from "react";
+import { type ReactNode, createContext, useCallback, useContext, useEffect, useState, useMemo } from "react";
 
 /**
  * A ToastContext helps components display any kind of toast message and can be provided
diff --git a/src/createRoom.ts b/src/createRoom.ts
index 45f071aac7d89e4bf53c479c65851a6f8dcc59fb..df0f90b91fd22524981be63a01d59831001137a9 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -8,13 +8,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
+    type MatrixClient,
     ClientEvent,
-    Room,
+    type Room,
     EventType,
     RoomCreateTypeField,
     RoomType,
-    ICreateRoomOpts,
+    type ICreateRoomOpts,
     HistoryVisibility,
     JoinRule,
     Preset,
@@ -23,19 +23,18 @@ import {
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import Modal, { IHandle } from "./Modal";
+import Modal, { type IHandle } from "./Modal";
 import { _t, UserFriendlyError } from "./languageHandler";
 import dis from "./dispatcher/dispatcher";
 import * as Rooms from "./Rooms";
 import { getAddressType } from "./UserAddress";
-import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
 import SpaceStore from "./stores/spaces/SpaceStore";
 import { makeSpaceParentEvent } from "./utils/space";
 import { JitsiCall, ElementCall } from "./models/Call";
 import { Action } from "./dispatcher/actions";
 import ErrorDialog from "./components/views/dialogs/ErrorDialog";
 import Spinner from "./components/views/elements/Spinner";
-import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
 import { findDMForUser } from "./utils/dm/findDMForUser";
 import { privateShouldBeEncrypted } from "./utils/rooms";
 import { shouldForceDisableEncryption } from "./utils/crypto/shouldForceDisableEncryption";
@@ -343,7 +342,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
                 await client.setPowerLevel(roomId, client.getUserId()!, 100);
             } else if (opts.roomType === RoomType.UnstableCall) {
                 // Set up this video room with an Element call
-                await ElementCall.create(await room);
+                ElementCall.create(await room);
 
                 // Reset our power level back to admin so that the call becomes immutable
                 await client.setPowerLevel(roomId, client.getUserId()!, 100);
@@ -423,36 +422,6 @@ export async function canEncryptToAllUsers(client: MatrixClient, userIds: string
     return true;
 }
 
-// Similar to ensureDMExists but also adds creation content
-// without polluting ensureDMExists with unrelated stuff (also
-// they're never encrypted).
-export async function ensureVirtualRoomExists(
-    client: MatrixClient,
-    userId: string,
-    nativeRoomId: string,
-): Promise<string | null> {
-    const existingDMRoom = findDMForUser(client, userId);
-    let roomId: string | null;
-    if (existingDMRoom) {
-        roomId = existingDMRoom.roomId;
-    } else {
-        roomId = await createRoom(client, {
-            dmUserId: userId,
-            spinner: false,
-            andView: false,
-            createOpts: {
-                creation_content: {
-                    // This allows us to recognise that the room is a virtual room
-                    // when it comes down our sync stream (we also put the ID of the
-                    // respective native room in there because why not?)
-                    [VIRTUAL_ROOM_EVENT_TYPE]: nativeRoomId,
-                },
-            },
-        });
-    }
-    return roomId;
-}
-
 export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string | null> {
     const existingDMRoom = findDMForUser(client, userId);
     let roomId: string | null;
diff --git a/src/customisations/Alias.ts b/src/customisations/Alias.ts
index 6e5c60be5868505cc477689c7fea700f9fad50c5..742de9cd45be6e84ffea6bf2dd46c108dbe129d7 100644
--- a/src/customisations/Alias.ts
+++ b/src/customisations/Alias.ts
@@ -6,19 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
-    // E.g. prefer one of the aliases over another
-    return null;
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IAliasCustomisations {
-    getDisplayAliasForAliasSet?: typeof getDisplayAliasForAliasSet;
-}
+import type { AliasCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
-// customisation points that make up `IAliasCustomisations`.
-export default {} as IAliasCustomisations;
+// customisation points that make up `AliasCustomisations`.
+export default {} as AliasCustomisations;
diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts
index ae979d4273c1ba1aa7b2154c060bd65a287cdc32..0b5c73a92ce445c20cbccdcc5ed160f463bcde5b 100644
--- a/src/customisations/ChatExport.ts
+++ b/src/customisations/ChatExport.ts
@@ -6,20 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ExportFormat, ExportType } from "../utils/exportUtils/exportUtils";
+import { type ChatExportCustomisations } from "@element-hq/element-web-module-api";
 
-export type ForceChatExportParameters = {
-    format?: ExportFormat;
-    range?: ExportType;
-    // must be < 10**8
-    // only used when range is 'LastNMessages'
-    // default is 100
-    numberOfMessages?: number;
-    includeAttachments?: boolean;
-    // maximum size of exported archive
-    // must be > 0 and < 8000
-    sizeMb?: number;
-};
+import { type ExportFormat, type ExportType } from "../utils/exportUtils/exportUtils";
+
+export type ForceChatExportParameters = ReturnType<
+    ChatExportCustomisations<ExportFormat, ExportType>["getForceChatExportParameters"]
+>;
 
 /**
  * Force parameters in room chat export
@@ -30,15 +23,8 @@ const getForceChatExportParameters = (): ForceChatExportParameters => {
     return {};
 };
 
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IChatExportCustomisations {
-    getForceChatExportParameters: typeof getForceChatExportParameters;
-}
-
 // A real customisation module will define and export one or more of the
 // customisation points that make up `IChatExportCustomisations`.
 export default {
     getForceChatExportParameters,
-} as IChatExportCustomisations;
+} as ChatExportCustomisations<ExportFormat, ExportType>;
diff --git a/src/customisations/ComponentVisibility.ts b/src/customisations/ComponentVisibility.ts
index c69b6ee185bfce362d03e0cd163160522f75b684..210863f0e36e761dfd0fed841184c22a334fbd74 100644
--- a/src/customisations/ComponentVisibility.ts
+++ b/src/customisations/ComponentVisibility.ts
@@ -12,29 +12,7 @@ Please see LICENSE files in the repository root for full details.
 
 // Populate this class with the details of your customisations when copying it.
 
-import { UIComponent } from "../settings/UIFeature";
-
-/**
- * Determines whether or not the active MatrixClient user should be able to use
- * the given UI component. If shown, the user might still not be able to use the
- * component depending on their contextual permissions. For example, invite options
- * might be shown to the user but they won't have permission to invite users to
- * the current room: the button will appear disabled.
- * @param {UIComponent} component The component to check visibility for.
- * @returns {boolean} True (default) if the user is able to see the component, false
- * otherwise.
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function shouldShowComponent(component: UIComponent): boolean {
-    return true; // default to visible
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IComponentVisibilityCustomisations {
-    shouldShowComponent?: typeof shouldShowComponent;
-}
+import { type ComponentVisibilityCustomisations as IComponentVisibilityCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up the interface above.
diff --git a/src/customisations/Directory.ts b/src/customisations/Directory.ts
index c5536801258c957bea1643eb1ee1ddeb58dc7844..a85283774fb19147187f25e7a2cb52404c918642 100644
--- a/src/customisations/Directory.ts
+++ b/src/customisations/Directory.ts
@@ -6,19 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function requireCanonicalAliasAccessToPublish(): boolean {
-    // Some environments may not care about this requirement and could return false
-    return true;
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IDirectoryCustomisations {
-    requireCanonicalAliasAccessToPublish?: typeof requireCanonicalAliasAccessToPublish;
-}
+import type { DirectoryCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up `IDirectoryCustomisations`.
-export default {} as IDirectoryCustomisations;
+export default {} as DirectoryCustomisations;
diff --git a/src/customisations/Lifecycle.ts b/src/customisations/Lifecycle.ts
index 2873093414e7fd1f574d3265a67aace72b0e9961..e8e3cca38ad3241768b969ef40139eea085cdf3a 100644
--- a/src/customisations/Lifecycle.ts
+++ b/src/customisations/Lifecycle.ts
@@ -6,18 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function onLoggedOutAndStorageCleared(): void {
-    // E.g. redirect user or call other APIs after logout
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface ILifecycleCustomisations {
-    onLoggedOutAndStorageCleared?: typeof onLoggedOutAndStorageCleared;
-}
+import type { LifecycleCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up `ILifecycleCustomisations`.
-export default {} as ILifecycleCustomisations;
+export default {} as LifecycleCustomisations;
diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts
index d7b367c5843dc8a7e32fb12d44218e8a11bf0c52..1763ade3a85f91692e9df6b8963f1b11f986aa2f 100644
--- a/src/customisations/Media.ts
+++ b/src/customisations/Media.ts
@@ -6,12 +6,13 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { MatrixClient, parseErrorResponse, ResizeMethod } from "matrix-js-sdk/src/matrix";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
-import { Optional } from "matrix-events-sdk";
+import { type MatrixClient, parseErrorResponse, type ResizeMethod } from "matrix-js-sdk/src/matrix";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
+import { type Optional } from "matrix-events-sdk";
 
+import type { MediaCustomisations, Media } from "@element-hq/element-web-module-api";
 import { MatrixClientPeg } from "../MatrixClientPeg";
-import { IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
+import { type IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
 import { UserFriendlyError } from "../languageHandler";
 
 // Populate this class with the details of your customisations when copying it.
@@ -25,7 +26,7 @@ import { UserFriendlyError } from "../languageHandler";
  * A media object is a representation of a "source media" and an optional
  * "thumbnail media", derived from event contents or external sources.
  */
-export class Media {
+class MediaImplementation implements Media {
     private client: MatrixClient;
 
     // Per above, this constructor signature can be whatever is helpful for you.
@@ -149,22 +150,27 @@ export class Media {
     }
 }
 
+export type { Media };
+
+type BaseMedia = MediaCustomisations<Partial<MediaEventContent>, MatrixClient, IPreparedMedia>;
+
 /**
  * Creates a media object from event content.
  * @param {MediaEventContent} content The event content.
- * @param {MatrixClient} client? Optional client to use.
- * @returns {Media} The media object.
+ * @param {MatrixClient} client Optional client to use.
+ * @returns {MediaImplementation} The media object.
  */
-export function mediaFromContent(content: Partial<MediaEventContent>, client?: MatrixClient): Media {
-    return new Media(prepEventContentAsMedia(content), client);
-}
+export const mediaFromContent: BaseMedia["mediaFromContent"] = (
+    content: Partial<MediaEventContent>,
+    client?: MatrixClient,
+): Media => new MediaImplementation(prepEventContentAsMedia(content), client);
 
 /**
  * Creates a media object from an MXC URI.
  * @param {string} mxc The MXC URI.
- * @param {MatrixClient} client? Optional client to use.
- * @returns {Media} The media object.
+ * @param {MatrixClient} client Optional client to use.
+ * @returns {MediaImplementation} The media object.
  */
-export function mediaFromMxc(mxc?: string, client?: MatrixClient): Media {
+export const mediaFromMxc: BaseMedia["mediaFromMxc"] = (mxc?: string, client?: MatrixClient): Media => {
     return mediaFromContent({ url: mxc }, client);
-}
+};
diff --git a/src/customisations/RoomList.ts b/src/customisations/RoomList.ts
index a62e1bf6fca6a7061a2e76c121c43091dccc3e30..1460d9a7b2f61556c48aa628062ac67864e25cb3 100644
--- a/src/customisations/RoomList.ts
+++ b/src/customisations/RoomList.ts
@@ -6,33 +6,12 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-// Populate this file with the details of your customisations when copying it.
+import type { RoomListCustomisations as IRoomListCustomisations } from "@element-hq/element-web-module-api";
 
-/**
- * Determines if a room is visible in the room list or not. By default,
- * all rooms are visible. Where special handling is performed by Element,
- * those rooms will not be able to override their visibility in the room
- * list - Element will make the decision without calling this function.
- *
- * This function should be as fast as possible to avoid slowing down the
- * client.
- * @param {Room} room The room to check the visibility of.
- * @returns {boolean} True if the room should be visible, false otherwise.
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function isRoomVisible(room: Room): boolean {
-    return true;
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IRoomListCustomisations {
-    isRoomVisible?: typeof isRoomVisible;
-}
+// Populate this file with the details of your customisations when copying it.
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up the interface above.
-export const RoomListCustomisations: IRoomListCustomisations = {};
+export const RoomListCustomisations: IRoomListCustomisations<Room> = {};
diff --git a/src/customisations/UserIdentifier.ts b/src/customisations/UserIdentifier.ts
index cc36a1d8c7f9de5046761e121a91d43bf02d2c5a..9c96a80fadf4da577da581b8a92066a33c848889 100644
--- a/src/customisations/UserIdentifier.ts
+++ b/src/customisations/UserIdentifier.ts
@@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
+import type { UserIdentifierCustomisations } from "@element-hq/element-web-module-api";
+
 /**
  * Customise display of the user identifier
  * hide userId for guests, display 3pid
@@ -19,15 +21,8 @@ function getDisplayUserIdentifier(
     return userId;
 }
 
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IUserIdentifierCustomisations {
-    getDisplayUserIdentifier: typeof getDisplayUserIdentifier;
-}
-
 // A real customisation module will define and export one or more of the
 // customisation points that make up `IUserIdentifierCustomisations`.
 export default {
     getDisplayUserIdentifier,
-} as IUserIdentifierCustomisations;
+} as UserIdentifierCustomisations;
diff --git a/src/customisations/WidgetPermissions.ts b/src/customisations/WidgetPermissions.ts
index 9734fee9d01947e83c6ed53883efcc6b80ab7a56..785ca46c2e8c4cd1607a2c9f28c2bf5ed127a12c 100644
--- a/src/customisations/WidgetPermissions.ts
+++ b/src/customisations/WidgetPermissions.ts
@@ -7,35 +7,10 @@
  */
 
 // Populate this class with the details of your customisations when copying it.
-import { Capability, Widget } from "matrix-widget-api";
+import { type Capability, type Widget } from "matrix-widget-api";
 
-/**
- * Approves the widget for capabilities that it requested, if any can be
- * approved. Typically this will be used to give certain widgets capabilities
- * without having to prompt the user to approve them. This cannot reject
- * capabilities that Element will be automatically granting, such as the
- * ability for Jitsi widgets to stay on screen - those will be approved
- * regardless.
- * @param {Widget} widget The widget to approve capabilities for.
- * @param {Set<Capability>} requestedCapabilities The capabilities the widget requested.
- * @returns {Set<Capability>} Resolves to the capabilities that are approved for use
- * by the widget. If none are approved, this should return an empty Set.
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-async function preapproveCapabilities(
-    widget: Widget,
-    requestedCapabilities: Set<Capability>,
-): Promise<Set<Capability>> {
-    return new Set(); // no additional capabilities approved
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IWidgetPermissionCustomisations {
-    preapproveCapabilities?: typeof preapproveCapabilities;
-}
+import type { WidgetPermissionsCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up the interface above.
-export const WidgetPermissionCustomisations: IWidgetPermissionCustomisations = {};
+export const WidgetPermissionCustomisations: WidgetPermissionsCustomisations<Widget, Capability> = {};
diff --git a/src/customisations/WidgetVariables.ts b/src/customisations/WidgetVariables.ts
index 6a6651dd102b05ba0c070557050c1826c7375fed..135dfa36d05b251bf767f407919f4d8657574edc 100644
--- a/src/customisations/WidgetVariables.ts
+++ b/src/customisations/WidgetVariables.ts
@@ -7,41 +7,8 @@
  */
 
 // Populate this class with the details of your customisations when copying it.
-import { ITemplateParams } from "matrix-widget-api";
-
-/**
- * Provides a partial set of the variables needed to render any widget. If
- * variables are missing or not provided then they will be filled with the
- * application-determined defaults.
- *
- * This will not be called until after isReady() resolves.
- * @returns {Partial<Omit<ITemplateParams, "widgetRoomId">>} The variables.
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function provideVariables(): Partial<Omit<ITemplateParams, "widgetRoomId">> {
-    return {};
-}
-
-/**
- * Resolves to whether or not the customisation point is ready for variables
- * to be provided. This will block widgets being rendered.
- * @returns {Promise<boolean>} Resolves when ready.
- */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-async function isReady(): Promise<void> {
-    return; // default no waiting
-}
-
-// This interface summarises all available customisation points and also marks
-// them all as optional. This allows customisers to only define and export the
-// customisations they need while still maintaining type safety.
-export interface IWidgetVariablesCustomisations {
-    provideVariables?: typeof provideVariables;
-
-    // If not provided, the app will assume that the customisation is always ready.
-    isReady?: typeof isReady;
-}
+import { type WidgetVariablesCustomisations } from "@element-hq/element-web-module-api";
 
 // A real customisation module will define and export one or more of the
 // customisation points that make up the interface above.
-export const WidgetVariableCustomisations: IWidgetVariablesCustomisations = {};
+export const WidgetVariableCustomisations: WidgetVariablesCustomisations = {};
diff --git a/src/customisations/helpers/UIComponents.ts b/src/customisations/helpers/UIComponents.ts
index 1c710f112dcd42afeff4615e37dca05ba7bca41e..4f15ddaf90ea37e8847837be8e5e6a30a3a9ab7d 100644
--- a/src/customisations/helpers/UIComponents.ts
+++ b/src/customisations/helpers/UIComponents.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { UIComponent } from "../../settings/UIFeature";
+import { type UIComponent } from "../../settings/UIFeature";
 import { ComponentVisibilityCustomisations } from "../ComponentVisibility";
 
 export function shouldShowComponent(component: UIComponent): boolean {
diff --git a/src/customisations/models/IMediaEventContent.ts b/src/customisations/models/IMediaEventContent.ts
index 607fc012ad5b3336dd08e4908b1d86f222ce122d..9fbe0e977fc909d979503c214f47b068444e9d38 100644
--- a/src/customisations/models/IMediaEventContent.ts
+++ b/src/customisations/models/IMediaEventContent.ts
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { EncryptedFile, MediaEventContent } from "matrix-js-sdk/src/types";
+import { type EncryptedFile, type MediaEventContent } from "matrix-js-sdk/src/types";
 
 export interface IPreparedMedia extends IMediaObject {
     thumbnail?: IMediaObject;
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index cd8b7aea3dddfd0083f9ffc46449ef511ff9ec32..e6616a7e215fe83e3c80f90b3b4d647507c0fe72 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -26,6 +26,11 @@ export enum Action {
      */
     ViewUser = "view_user",
 
+    /**
+     * Share a text message by forwarding it to a room selected by the user
+     */
+    Share = "share",
+
     /**
      * Open the user settings. No additional payload information required.
      * Optionally can include an OpenToTabPayload.
@@ -235,6 +240,12 @@ export enum Action {
      */
     AfterLeaveRoom = "after_leave_room",
 
+    /**
+     * Dispatched after a room has been successfully forgotten
+     * Should be used with AfterForgetRoomPayload.
+     */
+    AfterForgetRoom = "after_forget_room",
+
     /**
      * Used to defer actions until after sync is complete
      * LifecycleStore will emit deferredAction payload after 'MatrixActions.sync'
@@ -362,7 +373,17 @@ export enum Action {
     View3pidInvite = "view_3pid_invite",
 
     /**
-     * Opens right panel room summary and focuses the search input
+     * Opens right panel room summary and focuses the search input. Use with a FocusMessageSearchPayload.
      */
     FocusMessageSearch = "focus_search",
+
+    /**
+     * Open the direct message dialog
+     */
+    CreateChat = "view_create_chat",
+
+    /**
+     * Open the create room dialog
+     */
+    CreateRoom = "view_create_room",
 }
diff --git a/src/dispatcher/dispatcher.ts b/src/dispatcher/dispatcher.ts
index 8b093e2e4a87176a1c093e22b8f7b672998f5519..99c4555f5d2456fbf643cb8eddc427268162a11d 100644
--- a/src/dispatcher/dispatcher.ts
+++ b/src/dispatcher/dispatcher.ts
@@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Action } from "./actions";
-import { ActionPayload, AsyncActionPayload } from "./payloads";
+import { type Action } from "./actions";
+import { type ActionPayload, AsyncActionPayload } from "./payloads";
 
 type DispatchToken = string;
 
diff --git a/src/dispatcher/payloads.ts b/src/dispatcher/payloads.ts
index 114f1e55150dd0aa5f47afc1d822eedd8a7f36c0..248a486a075dc3113d0bf9d551af80964e7b2a38 100644
--- a/src/dispatcher/payloads.ts
+++ b/src/dispatcher/payloads.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { DispatcherAction } from "./actions";
+import { type DispatcherAction } from "./actions";
 
 /**
  * The base dispatch type exposed by our dispatcher.
diff --git a/src/dispatcher/payloads/ActiveRoomChangedPayload.ts b/src/dispatcher/payloads/ActiveRoomChangedPayload.ts
index bb1c0e4afb43e308f88d9762c8be2c7710fe1748..68d55c3b52cf8fb3366ad6338d492cf686eb8a1f 100644
--- a/src/dispatcher/payloads/ActiveRoomChangedPayload.ts
+++ b/src/dispatcher/payloads/ActiveRoomChangedPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface ActiveRoomChangedPayload extends ActionPayload {
     action: Action.ActiveRoomChanged;
diff --git a/src/dispatcher/payloads/AfterForgetRoomPayload.ts b/src/dispatcher/payloads/AfterForgetRoomPayload.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ba480e7ff753efd2a00d124511eaaee7f872fff3
--- /dev/null
+++ b/src/dispatcher/payloads/AfterForgetRoomPayload.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { type Room } from "matrix-js-sdk/src/matrix";
+
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+
+export interface AfterForgetRoomPayload extends ActionPayload {
+    action: Action.AfterForgetRoom;
+    room: Room;
+}
diff --git a/src/dispatcher/payloads/AfterLeaveRoomPayload.ts b/src/dispatcher/payloads/AfterLeaveRoomPayload.ts
index 5a74dce7beece1fdd53949b7a2fe387e5f59bbeb..07e7c54ad2e54bbb327f638be425579dc880d088 100644
--- a/src/dispatcher/payloads/AfterLeaveRoomPayload.ts
+++ b/src/dispatcher/payloads/AfterLeaveRoomPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface AfterLeaveRoomPayload extends ActionPayload {
     action: Action.AfterLeaveRoom;
diff --git a/src/dispatcher/payloads/CancelAskToJoinPayload.ts b/src/dispatcher/payloads/CancelAskToJoinPayload.ts
index d9ca8d291e955ba6e63d36f121bdd8834f87746b..0b84cb47cf0033e833ac562471b6b3a73f0f66df 100644
--- a/src/dispatcher/payloads/CancelAskToJoinPayload.ts
+++ b/src/dispatcher/payloads/CancelAskToJoinPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface CancelAskToJoinPayload extends Pick<ActionPayload, "action"> {
     action: Action.CancelAskToJoin;
diff --git a/src/dispatcher/payloads/CheckUpdatesPayload.ts b/src/dispatcher/payloads/CheckUpdatesPayload.ts
index b588193b4ff709e1c13882a6c54cd22487f35e5f..6ab6e5eb3502c058df964014d6464fee1069e3ce 100644
--- a/src/dispatcher/payloads/CheckUpdatesPayload.ts
+++ b/src/dispatcher/payloads/CheckUpdatesPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { UpdateStatus } from "../../BasePlatform";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type UpdateStatus } from "../../BasePlatform";
 
 export interface CheckUpdatesPayload extends ActionPayload, UpdateStatus {
     action: Action.CheckUpdates;
diff --git a/src/dispatcher/payloads/ComposerInsertPayload.ts b/src/dispatcher/payloads/ComposerInsertPayload.ts
index 941f8c135ceed678663b3c0ceea65d3b178974d0..9712a8303a048557253e95ce285325a061a8cd60 100644
--- a/src/dispatcher/payloads/ComposerInsertPayload.ts
+++ b/src/dispatcher/payloads/ComposerInsertPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { TimelineRenderingType } from "../../contexts/RoomContext";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type TimelineRenderingType } from "../../contexts/RoomContext";
 
 export enum ComposerType {
     Send = "send",
diff --git a/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts b/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts
index 2f2ed06f66138d99f706db87b84fac01488977f8..e73ebcfc01c4dd1efc4646a30167387ff38c0e8a 100644
--- a/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts
+++ b/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface DoAfterSyncPreparedPayload<T extends ActionPayload> extends Pick<ActionPayload, "action"> {
     action: Action.DoAfterSyncPrepared;
diff --git a/src/dispatcher/payloads/FocusComposerPayload.ts b/src/dispatcher/payloads/FocusComposerPayload.ts
index 6ec62e4758927424aaddf9bdbe116b5324173d7f..2a72906716d5a7ebc6035e8203f7c95e692317a2 100644
--- a/src/dispatcher/payloads/FocusComposerPayload.ts
+++ b/src/dispatcher/payloads/FocusComposerPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { TimelineRenderingType } from "../../contexts/RoomContext";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type TimelineRenderingType } from "../../contexts/RoomContext";
 
 export interface FocusComposerPayload extends ActionPayload {
     action:
diff --git a/src/dispatcher/payloads/FocusMessageSearchPayload.ts b/src/dispatcher/payloads/FocusMessageSearchPayload.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a06f7f5174590e41259d26f435147b8cb7a39f48
--- /dev/null
+++ b/src/dispatcher/payloads/FocusMessageSearchPayload.ts
@@ -0,0 +1,15 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+
+export interface FocusMessageSearchPayload extends ActionPayload {
+    action: Action.FocusMessageSearch;
+
+    initialText?: string;
+}
diff --git a/src/dispatcher/payloads/JoinRoomErrorPayload.ts b/src/dispatcher/payloads/JoinRoomErrorPayload.ts
index 1112674f21d183f10bb8a9d51a4ce6272dfb2cff..283a3c691596f03bffb9f702fda47c203b6261ce 100644
--- a/src/dispatcher/payloads/JoinRoomErrorPayload.ts
+++ b/src/dispatcher/payloads/JoinRoomErrorPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixError } from "matrix-js-sdk/src/matrix";
+import { type MatrixError } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface JoinRoomErrorPayload extends Pick<ActionPayload, "action"> {
     action: Action.JoinRoomError;
diff --git a/src/dispatcher/payloads/JoinRoomPayload.ts b/src/dispatcher/payloads/JoinRoomPayload.ts
index d4d0f5ca99cd59ad474786fb608d435285581fb8..ba5019be6c987e2ecd7e8cabef77880f07421eaf 100644
--- a/src/dispatcher/payloads/JoinRoomPayload.ts
+++ b/src/dispatcher/payloads/JoinRoomPayload.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
-import { IJoinRoomOpts } from "matrix-js-sdk/src/matrix";
+import { type JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
+import { type IJoinRoomOpts } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 /* eslint-disable camelcase */
 export interface JoinRoomPayload extends Pick<ActionPayload, "action"> {
diff --git a/src/dispatcher/payloads/JoinRoomReadyPayload.ts b/src/dispatcher/payloads/JoinRoomReadyPayload.ts
index 5eaa9e3c8d7dc31fe5c611c8f644e72897ca0c5c..516f5b4d5c91e3806e55633ed53ee631bb5855eb 100644
--- a/src/dispatcher/payloads/JoinRoomReadyPayload.ts
+++ b/src/dispatcher/payloads/JoinRoomReadyPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
+import { type JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 /* eslint-disable camelcase */
 export interface JoinRoomReadyPayload extends Pick<ActionPayload, "action"> {
diff --git a/src/dispatcher/payloads/OpenAddExistingToSpaceDialogPayload.ts b/src/dispatcher/payloads/OpenAddExistingToSpaceDialogPayload.ts
index ebcddb36c8c945b4580717bb54ed23e42f1e25a4..49dee1dff59fabf6ac9645070957ac1d69592115 100644
--- a/src/dispatcher/payloads/OpenAddExistingToSpaceDialogPayload.ts
+++ b/src/dispatcher/payloads/OpenAddExistingToSpaceDialogPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface OpenAddExistingToSpaceDialogPayload extends ActionPayload {
     action: Action.OpenAddToExistingSpaceDialog;
diff --git a/src/dispatcher/payloads/OpenForwardDialogPayload.ts b/src/dispatcher/payloads/OpenForwardDialogPayload.ts
index cc1bffa21ec816924dd5db1d16fcc2c440e9ddfa..a883a8bdc195a6ff8c65fb899758ffe13d7ccf0d 100644
--- a/src/dispatcher/payloads/OpenForwardDialogPayload.ts
+++ b/src/dispatcher/payloads/OpenForwardDialogPayload.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
-import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
 
 export interface OpenForwardDialogPayload extends ActionPayload {
     action: Action.OpenForwardDialog;
diff --git a/src/dispatcher/payloads/OpenInviteDialogPayload.ts b/src/dispatcher/payloads/OpenInviteDialogPayload.ts
index 1d275078217628aeab740a76206baeda9772e44c..77973c239f73cd128d0aefe1cb625941799504bf 100644
--- a/src/dispatcher/payloads/OpenInviteDialogPayload.ts
+++ b/src/dispatcher/payloads/OpenInviteDialogPayload.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type Optional } from "matrix-events-sdk";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { InviteKind } from "../../components/views/dialogs/InviteDialogTypes";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type InviteKind } from "../../components/views/dialogs/InviteDialogTypes";
 
 export interface OpenInviteDialogPayload extends ActionPayload {
     action: Action.OpenInviteDialog;
diff --git a/src/dispatcher/payloads/OpenReportEventDialogPayload.ts b/src/dispatcher/payloads/OpenReportEventDialogPayload.ts
index a4b355e9849f2212d64c5a507a465acc1ecdc137..5c3e5d8cd367e43e8b7d760a4b19723bac472c81 100644
--- a/src/dispatcher/payloads/OpenReportEventDialogPayload.ts
+++ b/src/dispatcher/payloads/OpenReportEventDialogPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface OpenReportEventDialogPayload extends ActionPayload {
     action: Action.OpenReportEventDialog;
diff --git a/src/dispatcher/payloads/OpenSpacePreferencesPayload.ts b/src/dispatcher/payloads/OpenSpacePreferencesPayload.ts
index ea068054db27040e1adfe5be6bbbe3725bbe7a43..8ee488d064a316b821c97bc9432db7d61bc5c7a4 100644
--- a/src/dispatcher/payloads/OpenSpacePreferencesPayload.ts
+++ b/src/dispatcher/payloads/OpenSpacePreferencesPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export enum SpacePreferenceTab {
     Appearance = "SPACE_PREFERENCE_APPEARANCE_TAB",
diff --git a/src/dispatcher/payloads/OpenSpaceSettingsPayload.ts b/src/dispatcher/payloads/OpenSpaceSettingsPayload.ts
index 9a1feae34d82b8c351cb92396cb44e7ebfbdb940..0bb9bb5ac2abc4c16b548a15a20a955648c68f43 100644
--- a/src/dispatcher/payloads/OpenSpaceSettingsPayload.ts
+++ b/src/dispatcher/payloads/OpenSpaceSettingsPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface OpenSpaceSettingsPayload extends ActionPayload {
     action: Action.OpenSpaceSettings;
diff --git a/src/dispatcher/payloads/OpenSpotlightPayload.ts b/src/dispatcher/payloads/OpenSpotlightPayload.ts
index ec2a630d073a92540b58f7f09d2024615808b344..5cdd5c129532be74e760a0189d2bf64925713da2 100644
--- a/src/dispatcher/payloads/OpenSpotlightPayload.ts
+++ b/src/dispatcher/payloads/OpenSpotlightPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
-import { Filter } from "../../components/views/dialogs/spotlight/Filter";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Filter } from "../../components/views/dialogs/spotlight/Filter";
 
 export interface OpenSpotlightPayload extends ActionPayload {
     action: Action.OpenSpotlight;
diff --git a/src/dispatcher/payloads/OpenToTabPayload.ts b/src/dispatcher/payloads/OpenToTabPayload.ts
index 872ae626656061aaa6d373b61d1e2a78777619e9..9f43c507e6d502d209cea525c291b81818382b49 100644
--- a/src/dispatcher/payloads/OpenToTabPayload.ts
+++ b/src/dispatcher/payloads/OpenToTabPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface OpenToTabPayload extends ActionPayload {
     action: Action.ViewUserSettings | string; // TODO: Add room settings action
diff --git a/src/dispatcher/payloads/OverwriteLoginPayload.ts b/src/dispatcher/payloads/OverwriteLoginPayload.ts
index dd53b0b7f9bcefba38b21946ece92f0296f4c154..b617611d2d1a9c170e059bd50c206c97a7995b42 100644
--- a/src/dispatcher/payloads/OverwriteLoginPayload.ts
+++ b/src/dispatcher/payloads/OverwriteLoginPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { IMatrixClientCreds } from "../../MatrixClientPeg";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type IMatrixClientCreds } from "../../MatrixClientPeg";
 
 export interface OverwriteLoginPayload extends ActionPayload {
     action: Action.OverwriteLogin;
diff --git a/src/dispatcher/payloads/PlatformSetPayload.ts b/src/dispatcher/payloads/PlatformSetPayload.ts
index 69dc79121c10f7459530151860d652aae1633b12..6d12978f8f8db4d3654f6ccf758a215f9eeaf8c7 100644
--- a/src/dispatcher/payloads/PlatformSetPayload.ts
+++ b/src/dispatcher/payloads/PlatformSetPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
-import BasePlatform from "../../BasePlatform";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import type BasePlatform from "../../BasePlatform";
 
 export interface PlatformSetPayload extends ActionPayload {
     action: Action.PlatformSet;
diff --git a/src/dispatcher/payloads/RecheckThemePayload.ts b/src/dispatcher/payloads/RecheckThemePayload.ts
index 87419465ae1a3f38df89faaa8b210f5c906fd67d..f87d45582c8ee45e9fdf26bbceb8f3b84d89b07d 100644
--- a/src/dispatcher/payloads/RecheckThemePayload.ts
+++ b/src/dispatcher/payloads/RecheckThemePayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface RecheckThemePayload extends ActionPayload {
     action: Action.RecheckTheme;
diff --git a/src/dispatcher/payloads/SettingUpdatedPayload.ts b/src/dispatcher/payloads/SettingUpdatedPayload.ts
index 16c8d50590985f57dd490807fc12c187dc82da5d..ed84c2b18e32ce2eeeeebd366880af773fb2ca90 100644
--- a/src/dispatcher/payloads/SettingUpdatedPayload.ts
+++ b/src/dispatcher/payloads/SettingUpdatedPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { SettingLevel } from "../../settings/SettingLevel";
-import { SettingValueType } from "../../settings/Settings";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type SettingLevel } from "../../settings/SettingLevel";
+import { type SettingValueType } from "../../settings/Settings";
 
 export interface SettingUpdatedPayload extends ActionPayload {
     action: Action.SettingUpdated;
diff --git a/src/dispatcher/payloads/SharePayload.ts b/src/dispatcher/payloads/SharePayload.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0aa44fe12f7a033c82dd39f3f7b295ffe1c70a38
--- /dev/null
+++ b/src/dispatcher/payloads/SharePayload.ts
@@ -0,0 +1,29 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+
+export enum ShareFormat {
+    Text = "text",
+    Html = "html",
+    Markdown = "md",
+}
+
+export interface SharePayload extends ActionPayload {
+    action: Action.Share;
+
+    /**
+     * The format of message to be shared (optional)
+     */
+    format: ShareFormat;
+
+    /**
+     * The message to be shared.
+     */
+    msg: string;
+}
diff --git a/src/dispatcher/payloads/ShowThreadPayload.ts b/src/dispatcher/payloads/ShowThreadPayload.ts
index 700758af94b0ee9f336b5eeca32773ae7d1cee76..1d3efc18b84bda0eb528f8e88c7a59b9c0c90b7f 100644
--- a/src/dispatcher/payloads/ShowThreadPayload.ts
+++ b/src/dispatcher/payloads/ShowThreadPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface ShowThreadPayload extends ActionPayload {
     action: Action.ShowThread;
diff --git a/src/dispatcher/payloads/SubmitAskToJoinPayload.ts b/src/dispatcher/payloads/SubmitAskToJoinPayload.ts
index 40334fab8d85b614df4e367a8d51253d6644b430..725409fe4a3c3d03a393524dd3233aa90a4eccde 100644
--- a/src/dispatcher/payloads/SubmitAskToJoinPayload.ts
+++ b/src/dispatcher/payloads/SubmitAskToJoinPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { KnockRoomOpts } from "matrix-js-sdk/src/matrix";
+import { type KnockRoomOpts } from "matrix-js-sdk/src/matrix";
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface SubmitAskToJoinPayload extends Pick<ActionPayload, "action"> {
     action: Action.SubmitAskToJoin;
diff --git a/src/dispatcher/payloads/SwitchSpacePayload.ts b/src/dispatcher/payloads/SwitchSpacePayload.ts
index 7ad22a7b2d08b13fb7214e1f111a60038cdd118f..d33ea051f4124b032fd3bef708a83da1c47fbd0e 100644
--- a/src/dispatcher/payloads/SwitchSpacePayload.ts
+++ b/src/dispatcher/payloads/SwitchSpacePayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface SwitchSpacePayload extends ActionPayload {
     action: Action.SwitchSpace;
diff --git a/src/dispatcher/payloads/ThreadPayload.ts b/src/dispatcher/payloads/ThreadPayload.ts
index 2f21576c334bfdb6ffb93c54797ea735903fe2b2..ac74370afa8b4eb7c7e46e98e7755cc1046a1cf4 100644
--- a/src/dispatcher/payloads/ThreadPayload.ts
+++ b/src/dispatcher/payloads/ThreadPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 /* eslint-disable camelcase */
 export interface ThreadPayload extends Pick<ActionPayload, "action"> {
diff --git a/src/dispatcher/payloads/UpdateFontSizeDeltaPayload.ts b/src/dispatcher/payloads/UpdateFontSizeDeltaPayload.ts
index 40495615b52eb0d4da8c24a1b61fc9923710c6a1..503299702cecf37ca5abdbaf9cf742a805fc5bda 100644
--- a/src/dispatcher/payloads/UpdateFontSizeDeltaPayload.ts
+++ b/src/dispatcher/payloads/UpdateFontSizeDeltaPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface UpdateFontSizeDeltaPayload extends ActionPayload {
     action: Action.UpdateFontSizeDelta;
diff --git a/src/dispatcher/payloads/UpdateSystemFontPayload.ts b/src/dispatcher/payloads/UpdateSystemFontPayload.ts
index 91951244a9fe85c91f8cf383db257b4c7117ea4a..1e1fd0f8a38a9d9454273d8059a3bed4d667f4c7 100644
--- a/src/dispatcher/payloads/UpdateSystemFontPayload.ts
+++ b/src/dispatcher/payloads/UpdateSystemFontPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface UpdateSystemFontPayload extends ActionPayload {
     action: Action.UpdateSystemFont;
diff --git a/src/dispatcher/payloads/UploadPayload.ts b/src/dispatcher/payloads/UploadPayload.ts
index 6d26a4dd2be11a985f0472ff210e55bcf83ff47a..acfce43d7fbd6c6a8e5d7fc4cfa82e6630515d4a 100644
--- a/src/dispatcher/payloads/UploadPayload.ts
+++ b/src/dispatcher/payloads/UploadPayload.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { RoomUpload } from "../../models/RoomUpload";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type RoomUpload } from "../../models/RoomUpload";
 
 export interface UploadPayload extends ActionPayload {
     /**
diff --git a/src/dispatcher/payloads/ViewHomePagePayload.ts b/src/dispatcher/payloads/ViewHomePagePayload.ts
index 8743569bdf6c5fc0f1f78c7cad2d8348f927db27..c18d896ae454bb82cab4b9727848958f0c2453e9 100644
--- a/src/dispatcher/payloads/ViewHomePagePayload.ts
+++ b/src/dispatcher/payloads/ViewHomePagePayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Action } from "../actions";
-import { ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type ActionPayload } from "../payloads";
 
 export interface ViewHomePagePayload extends ActionPayload {
     action: Action.ViewHomePage;
diff --git a/src/dispatcher/payloads/ViewRoomDeltaPayload.ts b/src/dispatcher/payloads/ViewRoomDeltaPayload.ts
index fefe5ec54460d488491f88d02ab1f37f7cab5c6d..461b68f065516d0c7f7bcd4971a9e92ff7636af5 100644
--- a/src/dispatcher/payloads/ViewRoomDeltaPayload.ts
+++ b/src/dispatcher/payloads/ViewRoomDeltaPayload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface ViewRoomDeltaPayload extends ActionPayload {
     action: Action.ViewRoomDelta;
diff --git a/src/dispatcher/payloads/ViewRoomErrorPayload.ts b/src/dispatcher/payloads/ViewRoomErrorPayload.ts
index fd85b9af09b6ea749fa55f98fbd6345db33a5dd1..d14dc5c6da4ab9dd431d70e32e93695657f6f2e9 100644
--- a/src/dispatcher/payloads/ViewRoomErrorPayload.ts
+++ b/src/dispatcher/payloads/ViewRoomErrorPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixError, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixError, type Room } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface ViewRoomErrorPayload extends Pick<ActionPayload, "action"> {
     action: Action.ViewRoomError;
diff --git a/src/dispatcher/payloads/ViewRoomPayload.ts b/src/dispatcher/payloads/ViewRoomPayload.ts
index 95affe573e60fcf6ce89d15f767bc01bccef40ff..525dd50d624885b498b49d4d9a2ec871f7313bda 100644
--- a/src/dispatcher/payloads/ViewRoomPayload.ts
+++ b/src/dispatcher/payloads/ViewRoomPayload.ts
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
-
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
-import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
-import { IOpts } from "../../createRoom";
-import { JoinRoomPayload } from "./JoinRoomPayload";
-import { AtLeastOne } from "../../@types/common";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
+
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
+import { type IOOBData, type IThreepidInvite } from "../../stores/ThreepidInviteStore";
+import { type IOpts } from "../../createRoom";
+import { type JoinRoomPayload } from "./JoinRoomPayload";
+import { type AtLeastOne } from "../../@types/common";
 
 export type FocusNextType = "composer" | "threadsPanel" | undefined;
 
diff --git a/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts b/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts
index 0c988d917a7989d2b6a89635be55f99913f35afc..9a23d39505efcfaf048e9ec7d90cfee190ff731d 100644
--- a/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts
+++ b/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { User } from "matrix-js-sdk/src/matrix";
+import { type User } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface ViewStartChatOrReusePayload extends Pick<ActionPayload, "action"> {
     action: Action.ViewStartChatOrReuse;
diff --git a/src/dispatcher/payloads/ViewUserPayload.ts b/src/dispatcher/payloads/ViewUserPayload.ts
index 497ebf5671e44f564fa8fc970b71f1953b57b9be..63c11f29a2fdd6385b531931f0c7efa3e6e993aa 100644
--- a/src/dispatcher/payloads/ViewUserPayload.ts
+++ b/src/dispatcher/payloads/ViewUserPayload.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomMember, User } from "matrix-js-sdk/src/matrix";
+import { type RoomMember, type User } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../payloads";
-import { Action } from "../actions";
+import { type ActionPayload } from "../payloads";
+import { type Action } from "../actions";
 
 export interface ViewUserPayload extends ActionPayload {
     action: Action.ViewUser;
diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts
index ff1e89b1f16cec06e76b02aba0598b959ee97283..f3a3148b862de2fa9eb5dad832e539ed652a0068 100644
--- a/src/editor/autocomplete.ts
+++ b/src/editor/autocomplete.ts
@@ -6,12 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { KeyboardEvent } from "react";
-
-import { Part, CommandPartCreator, PartCreator } from "./parts";
-import DocumentPosition from "./position";
-import { ICompletion, ISelectionRange } from "../autocomplete/Autocompleter";
-import Autocomplete from "../components/views/rooms/Autocomplete";
+import type React from "react";
+import { type Part, type CommandPartCreator, type PartCreator } from "./parts";
+import type DocumentPosition from "./position";
+import { type ICompletion, type ISelectionRange } from "../autocomplete/Autocompleter";
+import type Autocomplete from "../components/views/rooms/Autocomplete";
 
 export interface ICallback {
     replaceParts?: Part[];
@@ -33,7 +32,7 @@ export default class AutocompleteWrapperModel {
         private partCreator: PartCreator | CommandPartCreator,
     ) {}
 
-    public onEscape(e: KeyboardEvent): void {
+    public onEscape(e: KeyboardEvent | React.KeyboardEvent): void {
         this.getAutocompleterComponent()?.onEscape(e);
     }
 
diff --git a/src/editor/caret.ts b/src/editor/caret.ts
index 7db2c59b770539099c8562bcba465150531fabeb..2f36b4b1bfb5141880a910c18684fa5d3321612c 100644
--- a/src/editor/caret.ts
+++ b/src/editor/caret.ts
@@ -8,9 +8,10 @@ Please see LICENSE files in the repository root for full details.
 
 import { needsCaretNodeBefore, needsCaretNodeAfter } from "./render";
 import Range from "./range";
-import EditorModel from "./model";
-import DocumentPosition, { IPosition } from "./position";
-import { Part, Type } from "./parts";
+import type EditorModel from "./model";
+import { type IPosition } from "./position";
+import type DocumentPosition from "./position";
+import { type Part, Type } from "./parts";
 
 export type Caret = Range | DocumentPosition;
 
diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx
index cb38f88d0de6c88d1d01b82f7149f2d3673f77fa..f24e1307cb90072847f022d373ade5a842751f09 100644
--- a/src/editor/commands.tsx
+++ b/src/editor/commands.tsx
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
-import EditorModel from "./model";
+import type EditorModel from "./model";
 import { Type } from "./parts";
-import { Command, CommandCategories, getCommand } from "../SlashCommands";
+import { type Command, CommandCategories, getCommand } from "../SlashCommands";
 import { UserFriendlyError, _t, _td } from "../languageHandler";
 import Modal from "../Modal";
 import ErrorDialog from "../components/views/dialogs/ErrorDialog";
diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts
index 77ca729febd70d67da1fc04d3553605c93db8d93..3bca5e585a7a2deba191f4732ca1bc489e1b99d3 100644
--- a/src/editor/deserialize.ts
+++ b/src/editor/deserialize.ts
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
 
 import { checkBlockNode } from "../HtmlUtils";
 import { getPrimaryPermalinkEntity } from "../utils/permalinks/Permalinks";
-import { Part, PartCreator, Type } from "./parts";
+import { type Part, type PartCreator, Type } from "./parts";
 import SdkConfig from "../SdkConfig";
 import { textToHtmlRainbow } from "../utils/colour";
 import { stripPlainReply } from "../utils/Reply";
diff --git a/src/editor/dom.ts b/src/editor/dom.ts
index b0fd9b47e9ce4e256279515fd4443f7eed3e1bb1..358364b9f39d5732c1e9046550f0065110f560e6 100644
--- a/src/editor/dom.ts
+++ b/src/editor/dom.ts
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import { CARET_NODE_CHAR, isCaretNode } from "./render";
 import DocumentOffset from "./offset";
-import EditorModel from "./model";
-import Range from "./range";
+import type EditorModel from "./model";
+import type Range from "./range";
 
 type Predicate = (node: Node) => boolean;
 type Callback = (node: Node) => void;
diff --git a/src/editor/history.ts b/src/editor/history.ts
index ca5a39bb77d19e3ad2e0c12fefba1d8626205785..9e510a5a5d76f8ce00896248acfcbbecff06e03c 100644
--- a/src/editor/history.ts
+++ b/src/editor/history.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import EditorModel from "./model";
-import { IDiff } from "./diff";
-import { SerializedPart } from "./parts";
-import { Caret } from "./caret";
+import type EditorModel from "./model";
+import { type IDiff } from "./diff";
+import { type SerializedPart } from "./parts";
+import { type Caret } from "./caret";
 
 export interface IHistory {
     parts: SerializedPart[];
diff --git a/src/editor/model.ts b/src/editor/model.ts
index c583c93972f7828b0f861969947aee77fd18d98b..4328b666d857d48a31edc5af87520511ba5496ea 100644
--- a/src/editor/model.ts
+++ b/src/editor/model.ts
@@ -6,13 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { diffAtCaret, diffDeletion, IDiff } from "./diff";
-import DocumentPosition, { IPosition } from "./position";
+import { diffAtCaret, diffDeletion, type IDiff } from "./diff";
+import DocumentPosition, { type IPosition } from "./position";
 import Range from "./range";
-import { SerializedPart, Part, PartCreator } from "./parts";
-import AutocompleteWrapperModel, { ICallback } from "./autocomplete";
-import DocumentOffset from "./offset";
-import { Caret } from "./caret";
+import { type SerializedPart, type Part, type PartCreator } from "./parts";
+import { type ICallback } from "./autocomplete";
+import type AutocompleteWrapperModel from "./autocomplete";
+import type DocumentOffset from "./offset";
+import { type Caret } from "./caret";
 
 /**
  * @callback ModelCallback
diff --git a/src/editor/offset.ts b/src/editor/offset.ts
index 143fcdd0f0588e6e14d1921806c7f752d49285dc..7e0d854246e5231a68de5edaf16d46a9132b707d 100644
--- a/src/editor/offset.ts
+++ b/src/editor/offset.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import EditorModel from "./model";
-import DocumentPosition from "./position";
+import type EditorModel from "./model";
+import type DocumentPosition from "./position";
 
 export default class DocumentOffset {
     public constructor(
diff --git a/src/editor/operations.ts b/src/editor/operations.ts
index 6ee663968d4fe56dccce94d6516f229eceacdfcf..5594ed09c34205f9a9bee28835722bd0dac4909f 100644
--- a/src/editor/operations.ts
+++ b/src/editor/operations.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import Range from "./range";
-import { Part, Type } from "./parts";
+import type Range from "./range";
+import { type Part, Type } from "./parts";
 import { Formatting } from "../components/views/rooms/MessageComposerFormatBar";
 import { longestBacktickSequence } from "./deserialize";
 
diff --git a/src/editor/parts.ts b/src/editor/parts.ts
index 907758e39ad825ff8a98980629c021a68637cd27..ad49058609e27c9f83d81d4c614334750a8657c6 100644
--- a/src/editor/parts.ts
+++ b/src/editor/parts.ts
@@ -6,9 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, RoomMember, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type RoomMember, type Room } from "matrix-js-sdk/src/matrix";
 
-import AutocompleteWrapperModel, { GetAutocompleterComponent, UpdateCallback, UpdateQuery } from "./autocomplete";
+import AutocompleteWrapperModel, {
+    type GetAutocompleterComponent,
+    type UpdateCallback,
+    type UpdateQuery,
+} from "./autocomplete";
 import { EMOJI_REGEX, unicodeToShortcode } from "../HtmlUtils";
 import * as Avatar from "../Avatar";
 import defaultDispatcher from "../dispatcher/dispatcher";
diff --git a/src/editor/position.ts b/src/editor/position.ts
index 2b728b22b4e56dd82258ff2639fb3131100540d9..4b7a8c576ba16b90faf0991ab4b43f0a3cdacc0b 100644
--- a/src/editor/position.ts
+++ b/src/editor/position.ts
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import DocumentOffset from "./offset";
-import EditorModel from "./model";
-import { Part } from "./parts";
+import type EditorModel from "./model";
+import { type Part } from "./parts";
 
 export interface IPosition {
     index: number;
diff --git a/src/editor/range.ts b/src/editor/range.ts
index 6441e92d0499c1224e98f28add0eba0cabea1e95..eff439ec8111e3e7d8585b41f0d62b4c4f87c3b3 100644
--- a/src/editor/range.ts
+++ b/src/editor/range.ts
@@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import EditorModel from "./model";
-import DocumentPosition, { Predicate } from "./position";
-import { Part } from "./parts";
+import type EditorModel from "./model";
+import { type Predicate } from "./position";
+import type DocumentPosition from "./position";
+import { type Part } from "./parts";
 
 const whitespacePredicate: Predicate = (index, offset, part) => {
     return part.text[offset].trim() === "";
diff --git a/src/editor/render.ts b/src/editor/render.ts
index 4ac2fc54b314e331916bfab5d7b30904cb629ca3..fe0059de6e28d212ec9862d407361d2e4838b7c7 100644
--- a/src/editor/render.ts
+++ b/src/editor/render.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Part, Type } from "./parts";
-import EditorModel from "./model";
+import { type Part, Type } from "./parts";
+import type EditorModel from "./model";
 
 export function needsCaretNodeBefore(part: Part, prevPart?: Part): boolean {
     const isFirst = !prevPart || prevPart.type === Type.Newline;
diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts
index 8b05063b4ec16691f683676e07bce525be41bc12..d2c5ecc693951cd05d42a9758c0addefcf93d0de 100644
--- a/src/editor/serialize.ts
+++ b/src/editor/serialize.ts
@@ -12,7 +12,7 @@ import escapeHtml from "escape-html";
 
 import Markdown from "../Markdown";
 import { makeGenericPermalink } from "../utils/permalinks/Permalinks";
-import EditorModel from "./model";
+import type EditorModel from "./model";
 import SettingsStore from "../settings/SettingsStore";
 import SdkConfig from "../SdkConfig";
 import { Type } from "./parts";
diff --git a/src/effects/confetti/index.ts b/src/effects/confetti/index.ts
index 4c9046f8df0d3229ce21f6db4392c5b0d7c30f75..766d2700b2494a561a67033a0a148f6d7d0007f2 100644
--- a/src/effects/confetti/index.ts
+++ b/src/effects/confetti/index.ts
@@ -7,7 +7,7 @@ Copyright 2020 Nordeck IT + Consulting GmbH.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 
 export type ConfettiOptions = {
     /**
diff --git a/src/effects/effect.ts b/src/effects/effect.ts
index 6a8ae5d8ba53b86899a17aee77bc935120b8b40c..571ce494ed1e5d3729d26179322d6f429694a669 100644
--- a/src/effects/effect.ts
+++ b/src/effects/effect.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import { TranslationKey } from "../languageHandler";
+import { type TranslationKey } from "../languageHandler";
 
 export type Effect<TOptions extends { [key: string]: any }> = {
     /**
diff --git a/src/effects/fireworks/index.ts b/src/effects/fireworks/index.ts
index 13f6e6408b4a7888bce0e905e723a29f9b3dc7bd..05cbd8b838d2d19624b142e8ad0ef0a5bda5dd81 100644
--- a/src/effects/fireworks/index.ts
+++ b/src/effects/fireworks/index.ts
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 
 export type FireworksOptions = {
     /**
diff --git a/src/effects/hearts/index.ts b/src/effects/hearts/index.ts
index 00082049ebf95858fc455697a50aabe896f14703..a3de6ada84c5d9393af1fbaab6ccff09a82454d6 100644
--- a/src/effects/hearts/index.ts
+++ b/src/effects/hearts/index.ts
@@ -6,7 +6,7 @@ Copyright 2022 Arseny Uskov
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 import { arrayFastClone } from "../../utils/arrays";
 
 export type HeartOptions = {
diff --git a/src/effects/index.ts b/src/effects/index.ts
index 1ed7d2c3a3db0209f30777a77d4c32b97a921466..96a200e4b1dead3d482b240ebf13ee9db4f74990 100644
--- a/src/effects/index.ts
+++ b/src/effects/index.ts
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 import { _t, _td } from "../languageHandler";
-import { ConfettiOptions } from "./confetti";
-import { Effect } from "./effect";
-import { FireworksOptions } from "./fireworks";
-import { RainfallOptions } from "./rainfall";
-import { SnowfallOptions } from "./snowfall";
-import { SpaceInvadersOptions } from "./spaceinvaders";
-import { HeartOptions } from "./hearts";
+import { type ConfettiOptions } from "./confetti";
+import { type Effect } from "./effect";
+import { type FireworksOptions } from "./fireworks";
+import { type RainfallOptions } from "./rainfall";
+import { type SnowfallOptions } from "./snowfall";
+import { type SpaceInvadersOptions } from "./spaceinvaders";
+import { type HeartOptions } from "./hearts";
 
 /**
  * This configuration defines room effects that can be triggered by custom message types and emojis
diff --git a/src/effects/rainfall/index.ts b/src/effects/rainfall/index.ts
index 9fac36d6158b4fab31aa978cd79aa4c8b26dedf7..a992d8f43bf681657ccc68538dcab8f7d73b42ea 100644
--- a/src/effects/rainfall/index.ts
+++ b/src/effects/rainfall/index.ts
@@ -6,7 +6,7 @@ Copyright 2021 Josias Allestad
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 import { arrayFastClone } from "../../utils/arrays";
 
 export type RainfallOptions = {
diff --git a/src/effects/snowfall/index.ts b/src/effects/snowfall/index.ts
index bfc690d1a406191de320d2c2a385173a5afa7d65..df6f691b78a60f2d2b78d4cbd62146d7c32a3c14 100644
--- a/src/effects/snowfall/index.ts
+++ b/src/effects/snowfall/index.ts
@@ -5,7 +5,7 @@ Copyright 2020-2023 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 import { arrayFastClone } from "../../utils/arrays";
 
 export type SnowfallOptions = {
diff --git a/src/effects/spaceinvaders/index.ts b/src/effects/spaceinvaders/index.ts
index 49aeb26e8d697bcc723da74fe89421b146b6a712..0bc6e3df94bdd396ebe064c51df48a00adc27dd1 100644
--- a/src/effects/spaceinvaders/index.ts
+++ b/src/effects/spaceinvaders/index.ts
@@ -5,7 +5,7 @@ Copyright 2021-2023 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
  */
-import ICanvasEffect from "../ICanvasEffect";
+import type ICanvasEffect from "../ICanvasEffect";
 import { arrayFastClone } from "../../utils/arrays";
 
 export type SpaceInvadersOptions = {
diff --git a/src/effects/utils.ts b/src/effects/utils.ts
index 1479b6f5cd55d68dcc4e3efa218d5fc6a3b70cb9..31d07eb5737f5241920842d23e02f8e6f4398c68 100644
--- a/src/effects/utils.ts
+++ b/src/effects/utils.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import { IContent } from "matrix-js-sdk/src/matrix";
+import { type IContent } from "matrix-js-sdk/src/matrix";
 
 /**
  * Checks a message if it contains one of the provided emojis
diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx
index 3079e45abf2f356fb71a8419c7dbf5064d9a45e5..f1fc224471cd00838253f65de5bad34da63b142b 100644
--- a/src/events/EventTileFactory.tsx
+++ b/src/events/EventTileFactory.tsx
@@ -6,22 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import {
-    MatrixEvent,
+    type MatrixEvent,
     EventType,
     MsgType,
     RelationType,
-    MatrixClient,
+    type MatrixClient,
     GroupCallIntent,
     M_POLL_END,
     M_POLL_START,
 } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import SettingsStore from "../settings/SettingsStore";
-import LegacyCallEventGrouper from "../components/structures/LegacyCallEventGrouper";
-import { EventTileProps } from "../components/views/rooms/EventTile";
+import type LegacyCallEventGrouper from "../components/structures/LegacyCallEventGrouper";
+import { type EventTileProps } from "../components/views/rooms/EventTile";
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import MessageEvent from "../components/views/messages/MessageEvent";
 import LegacyCallEvent from "../components/views/messages/LegacyCallEvent";
@@ -42,6 +42,7 @@ import HiddenBody from "../components/views/messages/HiddenBody";
 import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
 import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
 import { ElementCall } from "../models/Call";
+import { type IBodyProps } from "../components/views/messages/IBodyProps";
 
 // Subset of EventTile's IProps plus some mixins
 export interface EventTileTypeProps
@@ -51,7 +52,6 @@ export interface EventTileTypeProps
         | "highlights"
         | "highlightLink"
         | "showUrlPreview"
-        | "onHeightChanged"
         | "forExport"
         | "getRelationsForEvent"
         | "editState"
@@ -64,12 +64,12 @@ export interface EventTileTypeProps
     ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
     timestamp?: JSX.Element;
     maxImageHeight?: number; // pixels
-    overrideBodyTypes?: Record<string, typeof React.Component>;
-    overrideEventTypes?: Record<string, typeof React.Component>;
+    overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
+    overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
 }
 
 type FactoryProps = Omit<EventTileTypeProps, "ref">;
-type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element;
+type Factory<X = FactoryProps> = (ref: React.RefObject<any> | undefined, props: X) => JSX.Element;
 
 export const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />;
 const LegacyCallEventFactory: Factory<FactoryProps & { callEventGrouper: LegacyCallEventGrouper }> = (ref, props) => (
@@ -273,7 +273,6 @@ export function renderTile(
         highlightLink,
         showUrlPreview,
         permalinkCreator,
-        onHeightChanged,
         callEventGrouper,
         getRelationsForEvent,
         isSeeingThroughMessageHiddenForModeration,
@@ -291,7 +290,6 @@ export function renderTile(
                 highlights,
                 highlightLink,
                 showUrlPreview,
-                onHeightChanged,
                 editState,
                 replacingEventId,
                 getRelationsForEvent,
@@ -310,7 +308,6 @@ export function renderTile(
                 highlightLink,
                 showUrlPreview,
                 permalinkCreator,
-                onHeightChanged,
                 callEventGrouper,
                 getRelationsForEvent,
                 isSeeingThroughMessageHiddenForModeration,
@@ -343,7 +340,6 @@ export function renderReplyTile(
         mxEvent,
         highlights,
         highlightLink,
-        onHeightChanged,
         showUrlPreview,
         overrideBodyTypes,
         overrideEventTypes,
@@ -358,7 +354,6 @@ export function renderReplyTile(
         mxEvent,
         highlights,
         highlightLink,
-        onHeightChanged,
         showUrlPreview,
         overrideBodyTypes,
         overrideEventTypes,
diff --git a/src/events/RelationsHelper.ts b/src/events/RelationsHelper.ts
index e063395c9a6567d6bcae61be98b9e1486f1e783e..83f1eeb26467166465e2d27263500ce595bfaf68 100644
--- a/src/events/RelationsHelper.ts
+++ b/src/events/RelationsHelper.ts
@@ -7,16 +7,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     MatrixEventEvent,
-    RelationType,
+    type RelationType,
     TypedEventEmitter,
-    Relations,
+    type Relations,
     RelationsEvent,
 } from "matrix-js-sdk/src/matrix";
 
-import { IDestroyable } from "../utils/IDestroyable";
+import { type IDestroyable } from "../utils/IDestroyable";
 
 export enum RelationsHelperEvent {
     Add = "add",
diff --git a/src/events/forward/getForwardableEvent.ts b/src/events/forward/getForwardableEvent.ts
index 151da1e8af2d654babfbf5636880ded06a82a81d..4f04f02339cd3cc4fe8af6706cbadd5efe13cbbd 100644
--- a/src/events/forward/getForwardableEvent.ts
+++ b/src/events/forward/getForwardableEvent.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { M_POLL_END, M_POLL_START, M_BEACON_INFO, MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { M_POLL_END, M_POLL_START, M_BEACON_INFO, type MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation";
 
diff --git a/src/events/location/getShareableLocationEvent.ts b/src/events/location/getShareableLocationEvent.ts
index e5c9a0f3a11fbba86f1d714ad8b365424c1320dd..e56d5014e0762d34cb60e5663410864db957d81e 100644
--- a/src/events/location/getShareableLocationEvent.ts
+++ b/src/events/location/getShareableLocationEvent.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { M_BEACON_INFO, MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { M_BEACON_INFO, type MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation";
 import { isLocationEvent } from "../../utils/EventUtils";
diff --git a/src/hooks/right-panel/useCurrentPhase.ts b/src/hooks/right-panel/useCurrentPhase.ts
new file mode 100644
index 0000000000000000000000000000000000000000..926c6f6e8c2ce0700d2f87481831fb70a50ef50a
--- /dev/null
+++ b/src/hooks/right-panel/useCurrentPhase.ts
@@ -0,0 +1,45 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useContext, useState } from "react";
+
+import { SDKContext } from "../../contexts/SDKContext";
+import { type RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
+import { useEventEmitter } from "../useEventEmitter";
+import { UPDATE_EVENT } from "../../stores/AsyncStore";
+
+/**
+ * Returns:
+ * - state which will always reflect the currently active right panel phase or null.
+ * - boolean state representing whether any panel is open or not.
+ * @param roomId room id if available.
+ */
+export function useCurrentPhase(roomId?: string): { currentPhase: RightPanelPhases | null; isOpen: boolean } {
+    const sdkContext = useContext(SDKContext);
+
+    const getCurrentPhase = (): RightPanelPhases | null => {
+        const card = roomId
+            ? sdkContext.rightPanelStore.currentCardForRoom(roomId)
+            : sdkContext.rightPanelStore.currentCard;
+        return card.phase;
+    };
+
+    const getIsOpen = (): boolean => {
+        const isOpen = roomId ? sdkContext.rightPanelStore.isOpenForRoom(roomId) : sdkContext.rightPanelStore.isOpen;
+        return isOpen;
+    };
+
+    const [currentPhase, setCurrentPhase] = useState<RightPanelPhases | null>(getCurrentPhase());
+    const [isOpen, setIsOpen] = useState<boolean>(getIsOpen());
+
+    useEventEmitter(sdkContext.rightPanelStore, UPDATE_EVENT, () => {
+        setCurrentPhase(getCurrentPhase());
+        setIsOpen(getIsOpen());
+    });
+
+    return { currentPhase, isOpen };
+}
diff --git a/src/hooks/room/useGuestAccessInformation.ts b/src/hooks/room/useGuestAccessInformation.ts
index bf49d434ef351fbb40b83eb70d115f73a4d94aa5..58945a5bf60d442b28c83eb2c186b903e3a47b94 100644
--- a/src/hooks/room/useGuestAccessInformation.ts
+++ b/src/hooks/room/useGuestAccessInformation.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useMemo } from "react";
-import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../../SdkConfig";
 import { useRoomState } from "../useRoomState";
diff --git a/src/hooks/room/useRoomCall.tsx b/src/hooks/room/useRoomCall.tsx
index 74a518b2170b2f998201760f12ef6d347dfd9c98..8d7667aa7d7afb01279a5af1ff8afca67bcec476 100644
--- a/src/hooks/room/useRoomCall.tsx
+++ b/src/hooks/room/useRoomCall.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
-import React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
 import { CallType } from "matrix-js-sdk/src/webrtc/call";
 
-import { useFeatureEnabled } from "../useSettings";
+import { useFeatureEnabled, useSettingValue } from "../useSettings";
 import SdkConfig from "../../SdkConfig";
 import { useEventEmitter, useEventEmitterState } from "../useEventEmitter";
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
@@ -24,19 +24,18 @@ import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutS
 import { useRoomState } from "../useRoomState";
 import { _t } from "../../languageHandler";
 import { isManagedHybridWidget, isManagedHybridWidgetEnabled } from "../../widgets/ManagedHybrid";
-import { IApp } from "../../stores/WidgetStore";
+import { type IApp } from "../../stores/WidgetStore";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { UPDATE_EVENT } from "../../stores/AsyncStore";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../dispatcher/actions";
 import { CallStore, CallStoreEvent } from "../../stores/CallStore";
 import { isVideoRoom } from "../../utils/video-rooms";
 import { useGuestAccessInformation } from "./useGuestAccessInformation";
-import SettingsStore from "../../settings/SettingsStore";
 import { UIFeature } from "../../settings/UIFeature";
 import { BetaPill } from "../../components/views/beta/BetaCard";
-import { InteractionName } from "../../PosthogTrackers";
+import { type InteractionName } from "../../PosthogTrackers";
 
 export enum PlatformCallType {
     ElementCall,
@@ -102,6 +101,8 @@ export const useRoomCall = (
 } => {
     // settings
     const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
+    const widgetsFeatureEnabled = useSettingValue(UIFeature.Widgets);
+    const voipFeatureEnabled = useSettingValue(UIFeature.Voip);
     const useElementCallExclusively = useMemo(() => {
         return SdkConfig.get("element_call").use_exclusively;
     }, []);
@@ -285,8 +286,8 @@ export const useRoomCall = (
     // We hide the voice call button if it'd have the same effect as the video call button
     let hideVoiceCallButton = isManagedHybridWidgetEnabled(room) || !callOptions.includes(PlatformCallType.LegacyCall);
     let hideVideoCallButton = false;
-    // We hide both buttons if they require widgets but widgets are disabled.
-    if (memberCount > 2 && !SettingsStore.getValue(UIFeature.Widgets)) {
+    // We hide both buttons if they require widgets but widgets are disabled, or if the Voip feature is disabled.
+    if ((memberCount > 2 && !widgetsFeatureEnabled) || !voipFeatureEnabled) {
         hideVoiceCallButton = true;
         hideVideoCallButton = true;
     }
diff --git a/src/hooks/room/useRoomIdName.ts b/src/hooks/room/useRoomIdName.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7930029e26e632e951be810a09ad2972b200032
--- /dev/null
+++ b/src/hooks/room/useRoomIdName.ts
@@ -0,0 +1,32 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type Room } from "matrix-js-sdk/src/matrix";
+
+import { useDmMember } from "../../components/views/avatars/WithPresenceIndicator.tsx";
+import { LocalRoom } from "../../models/LocalRoom.ts";
+
+/**
+ * Determine a stable ID for generating hash colours. If the room
+ * is a DM (or local room), then the other user's ID will be used.
+ * @param oobData - out-of-band information about the room
+ * @returns An ID string, or undefined if the room and oobData are undefined.
+ */
+export function useRoomIdName(room?: Room, oobData?: { roomId?: string }): string | undefined {
+    const dmMember = useDmMember(room);
+    if (dmMember) {
+        // If the room is a DM, we use the other user's ID for the color hash
+        // in order to match the room avatar with their avatar
+        return dmMember.userId;
+    } else if (room instanceof LocalRoom && room.targets.length === 1) {
+        return room.targets[0].userId;
+    } else if (room) {
+        return room.roomId;
+    } else {
+        return oobData?.roomId;
+    }
+}
diff --git a/src/hooks/room/useRoomMemberProfile.ts b/src/hooks/room/useRoomMemberProfile.ts
index 123e6f09917c48a1361cfb017e1dd21a939d5c93..5b1b4ae125877eb2ee38f7b4eeaf03b55a1374c3 100644
--- a/src/hooks/room/useRoomMemberProfile.ts
+++ b/src/hooks/room/useRoomMemberProfile.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomMember } from "matrix-js-sdk/src/matrix";
+import { type RoomMember } from "matrix-js-sdk/src/matrix";
 import { useMemo } from "react";
 
 import { TimelineRenderingType } from "../../contexts/RoomContext";
diff --git a/src/hooks/room/useRoomThreadNotifications.ts b/src/hooks/room/useRoomThreadNotifications.ts
index 2740a016f76da0ad53ea9a903f221c1484733fa2..25c55ab165b0fb684ce916f3497efd2a636971bc 100644
--- a/src/hooks/room/useRoomThreadNotifications.ts
+++ b/src/hooks/room/useRoomThreadNotifications.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { NotificationCountType, Room, RoomEvent, ThreadEvent } from "matrix-js-sdk/src/matrix";
+import { NotificationCountType, type Room, RoomEvent, ThreadEvent } from "matrix-js-sdk/src/matrix";
 import { useCallback, useEffect, useState } from "react";
 
 import { NotificationLevel } from "../../stores/notifications/NotificationLevel";
diff --git a/src/hooks/room/useTopic.ts b/src/hooks/room/useTopic.ts
index 3f8de87e39a4516717209507a00f7b6a6e69d432..e254e7a4ea6d64995903b3974ce32d9adb62bd2c 100644
--- a/src/hooks/room/useTopic.ts
+++ b/src/hooks/room/useTopic.ts
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
 import { useEffect, useState } from "react";
 import {
     EventType,
-    MatrixEvent,
-    Room,
+    type MatrixEvent,
+    type Room,
     RoomStateEvent,
     ContentHelpers,
-    MRoomTopicEventContent,
+    type MRoomTopicEventContent,
 } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import { useTypedEventEmitter } from "../useEventEmitter";
 
diff --git a/src/hooks/spotlight/useRecentSearches.ts b/src/hooks/spotlight/useRecentSearches.ts
index 7f7ec89c194bc6332464e4fd34acc9dba58c150c..5adf643953396586158c0c316c9f20ec7fba6d9f 100644
--- a/src/hooks/spotlight/useRecentSearches.ts
+++ b/src/hooks/spotlight/useRecentSearches.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 import { SettingLevel } from "../../settings/SettingLevel";
diff --git a/src/hooks/useAccountData.ts b/src/hooks/useAccountData.ts
index 7cbc77d525f876227f89a964d709d4bacefc2528..85c73f958fdf058b788e1d33735c15c0db431e5a 100644
--- a/src/hooks/useAccountData.ts
+++ b/src/hooks/useAccountData.ts
@@ -7,14 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useCallback, useState } from "react";
-import { AccountDataEvents, ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type AccountDataEvents, ClientEvent, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { useTypedEventEmitter } from "./useEventEmitter";
 
-const tryGetContent = <T extends {}>(ev?: MatrixEvent): T | undefined => ev?.getContent<T>();
+const tryGetContent = <T extends object>(ev?: MatrixEvent): T | undefined => ev?.getContent<T>();
 
 // Hook to simplify listening to Matrix account data
-export const useAccountData = <T extends {}>(cli: MatrixClient, eventType: keyof AccountDataEvents): T => {
+export const useAccountData = <T extends object>(cli: MatrixClient, eventType: keyof AccountDataEvents): T => {
     const [value, setValue] = useState<T | undefined>(() => tryGetContent<T>(cli.getAccountData(eventType)));
 
     const handler = useCallback(
diff --git a/src/hooks/useAsyncMemo.ts b/src/hooks/useAsyncMemo.ts
index 49925d0c747e2fc2b5641d1439c7803d2b7488c9..d5d99065651a65a58190539a8b8b72ec5f6fc152 100644
--- a/src/hooks/useAsyncMemo.ts
+++ b/src/hooks/useAsyncMemo.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { useState, useEffect, DependencyList } from "react";
+import { useState, useEffect, type DependencyList } from "react";
 
 type Fn<T> = () => Promise<T>;
 
diff --git a/src/hooks/useAsyncRefreshMemo.ts b/src/hooks/useAsyncRefreshMemo.ts
index 3d167d56a6c80983c6fc79f51f0ecffce6e732a1..a1845ac8166badd376ad44bdeafd9a9c125b37a0 100644
--- a/src/hooks/useAsyncRefreshMemo.ts
+++ b/src/hooks/useAsyncRefreshMemo.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { DependencyList, useCallback, useEffect, useState } from "react";
+import { type DependencyList, useCallback, useEffect, useState } from "react";
 
 type Fn<T> = () => Promise<T>;
 
diff --git a/src/hooks/useCall.ts b/src/hooks/useCall.ts
index 63f5cd87adad6a82259322fd6752846d96b9a102..db0660df2bf5de6e82dbeff5d1d46652560cc7e2 100644
--- a/src/hooks/useCall.ts
+++ b/src/hooks/useCall.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { useState, useCallback, useMemo } from "react";
+import { useState, useCallback, useMemo, useEffect } from "react";
 
 import type { RoomMember } from "matrix-js-sdk/src/matrix";
-import { Call, ConnectionState, CallEvent } from "../models/Call";
+import { type Call, ConnectionState, CallEvent } from "../models/Call";
 import { useTypedEventEmitterState, useEventEmitter } from "./useEventEmitter";
 import { CallStore, CallStoreEvent } from "../stores/CallStore";
 import SdkConfig, { DEFAULTS } from "../SdkConfig";
@@ -20,6 +20,12 @@ export const useCall = (roomId: string): Call | null => {
     useEventEmitter(CallStore.instance, CallStoreEvent.Call, (call: Call | null, forRoomId: string) => {
         if (forRoomId === roomId) setCall(call);
     });
+
+    // Reset the value when the roomId changes
+    useEffect(() => {
+        setCall(CallStore.instance.getCall(roomId));
+    }, [roomId]);
+
     return call;
 };
 
@@ -75,9 +81,5 @@ export const useFull = (call: Call | null): boolean => {
 
 export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
     const isFull = useFull(call);
-    const state = useConnectionState(call);
-
-    if (state === ConnectionState.Connecting) return _t("voip|join_button_tooltip_connecting");
-    if (isFull) return _t("voip|join_button_tooltip_call_full");
-    return null;
+    return isFull ? _t("voip|join_button_tooltip_call_full") : null;
 };
diff --git a/src/hooks/useDispatcher.ts b/src/hooks/useDispatcher.ts
index 2072e3182403d1a6b9551419ff1f3050ec4708ac..6d234a353c910d448eda7f93045bde6b03754f34 100644
--- a/src/hooks/useDispatcher.ts
+++ b/src/hooks/useDispatcher.ts
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import { useEffect, useRef } from "react";
 
-import { ActionPayload } from "../dispatcher/payloads";
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
 
 // Hook to simplify listening to event dispatches
 export const useDispatcher = (dispatcher: MatrixDispatcher, handler: (payload: ActionPayload) => void): void => {
diff --git a/src/hooks/useEncryptionStatus.ts b/src/hooks/useEncryptionStatus.ts
index 32238aa4421bea15c3fe6e4207cd9ad20e445769..75c894c8031c6442d3adfd759c490e088cf99e35 100644
--- a/src/hooks/useEncryptionStatus.ts
+++ b/src/hooks/useEncryptionStatus.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { useEffect, useMemo, useState } from "react";
 import { throttle } from "lodash";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
-import { E2EStatus, shieldStatusForRoom } from "../utils/ShieldUtils";
+import { type E2EStatus, shieldStatusForRoom } from "../utils/ShieldUtils";
 import { useTypedEventEmitter } from "./useEventEmitter";
 
 export function useEncryptionStatus(client: MatrixClient, room: Room): E2EStatus | null {
diff --git a/src/hooks/useEventEmitter.ts b/src/hooks/useEventEmitter.ts
index b7c35081cd5bab866eeaf2c1e04cd667d95e49e7..67049195a0db5a7bf2966573f833ffbf1d746b13 100644
--- a/src/hooks/useEventEmitter.ts
+++ b/src/hooks/useEventEmitter.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useRef, useEffect, useState, useCallback } from "react";
-import { ListenerMap, TypedEventEmitter } from "matrix-js-sdk/src/matrix";
+import { type ListenerMap, type TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 
 import type { EventEmitter } from "events";
 
diff --git a/src/hooks/useGlobalNotificationState.ts b/src/hooks/useGlobalNotificationState.ts
index fd2f0b706374a7c3a28383d3523be82b2effb1d3..d596cabf67a8173726db9636bef3bf6c8ebde292 100644
--- a/src/hooks/useGlobalNotificationState.ts
+++ b/src/hooks/useGlobalNotificationState.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { useState } from "react";
 
-import { SummarizedNotificationState } from "../stores/notifications/SummarizedNotificationState";
+import { type SummarizedNotificationState } from "../stores/notifications/SummarizedNotificationState";
 import {
     RoomNotificationStateStore,
     UPDATE_STATUS_INDICATOR,
diff --git a/src/hooks/useIsEncrypted.ts b/src/hooks/useIsEncrypted.ts
index 7cdfcc68a13d974477b82cec339a90a1afc001b2..75c43958dcf0ec633d50315999d0c7a8be9d4969 100644
--- a/src/hooks/useIsEncrypted.ts
+++ b/src/hooks/useIsEncrypted.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, type Room, EventType } from "matrix-js-sdk/src/matrix";
 
 import { useRoomState } from "./useRoomState.ts";
 import { useAsyncMemo } from "./useAsyncMemo.ts";
diff --git a/src/hooks/useIsReleaseAnnouncementOpen.ts b/src/hooks/useIsReleaseAnnouncementOpen.ts
index 68ab65f97a4950c7e165442a9ab1c8f1304f579e..ee3e90f2e0efd7760c66d35341d7f939e5ab1cbe 100644
--- a/src/hooks/useIsReleaseAnnouncementOpen.ts
+++ b/src/hooks/useIsReleaseAnnouncementOpen.ts
@@ -9,7 +9,7 @@
 import { useState } from "react";
 
 import { useTypedEventEmitter, useTypedEventEmitterState } from "./useEventEmitter";
-import { Feature, ReleaseAnnouncementStore } from "../stores/ReleaseAnnouncementStore";
+import { type Feature, ReleaseAnnouncementStore } from "../stores/ReleaseAnnouncementStore";
 import Modal, { ModalManagerEvent } from "../Modal";
 
 /**
diff --git a/src/hooks/useLocalStorageState.ts b/src/hooks/useLocalStorageState.ts
index 5efec21035f7576cd3dc0d2d6de0bf02bcf6f0bd..be1b79b49f3953ee2a3c271f43f28bb29236485e 100644
--- a/src/hooks/useLocalStorageState.ts
+++ b/src/hooks/useLocalStorageState.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Dispatch, useCallback, useEffect, useState } from "react";
+import { type Dispatch, useCallback, useEffect, useState } from "react";
 
 const getValue = <T>(key: string, initialValue: T): T => {
     try {
diff --git a/src/hooks/useMediaVisible.ts b/src/hooks/useMediaVisible.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f367e87c4f0f23c7e132019ef1613acf5649a627
--- /dev/null
+++ b/src/hooks/useMediaVisible.ts
@@ -0,0 +1,57 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { useCallback } from "react";
+import { JoinRule } from "matrix-js-sdk/src/matrix";
+
+import { SettingLevel } from "../settings/SettingLevel";
+import { useSettingValue } from "./useSettings";
+import SettingsStore from "../settings/SettingsStore";
+import { useMatrixClientContext } from "../contexts/MatrixClientContext";
+import { MediaPreviewValue } from "../@types/media_preview";
+import { useRoomState } from "./useRoomState";
+
+const PRIVATE_JOIN_RULES: JoinRule[] = [JoinRule.Invite, JoinRule.Knock, JoinRule.Restricted];
+
+/**
+ * Should the media event be visible in the client, or hidden.
+ * @param eventId The eventId of the media event.
+ * @returns A boolean describing the hidden status, and a function to set the visiblity.
+ */
+export function useMediaVisible(eventId?: string, roomId?: string): [boolean, (visible: boolean) => void] {
+    const mediaPreviewSetting = useSettingValue("mediaPreviewConfig", roomId);
+    const client = useMatrixClientContext();
+    const eventVisibility = useSettingValue("showMediaEventIds");
+    const joinRule = useRoomState(client.getRoom(roomId) ?? undefined, (state) => state.getJoinRule());
+    const setMediaVisible = useCallback(
+        (visible: boolean) => {
+            SettingsStore.setValue("showMediaEventIds", null, SettingLevel.DEVICE, {
+                ...eventVisibility,
+                [eventId!]: visible,
+            });
+        },
+        [eventId, eventVisibility],
+    );
+
+    const roomIsPrivate = joinRule ? PRIVATE_JOIN_RULES.includes(joinRule) : false;
+
+    const explicitEventVisiblity = eventId ? eventVisibility[eventId] : undefined;
+    // Always prefer the explicit per-event user preference here.
+    if (explicitEventVisiblity !== undefined) {
+        return [explicitEventVisiblity, setMediaVisible];
+    } else if (mediaPreviewSetting.media_previews === MediaPreviewValue.Off) {
+        return [false, setMediaVisible];
+    } else if (mediaPreviewSetting.media_previews === MediaPreviewValue.On) {
+        return [true, setMediaVisible];
+    } else if (mediaPreviewSetting.media_previews === MediaPreviewValue.Private) {
+        return [roomIsPrivate, setMediaVisible];
+    } else {
+        // Invalid setting.
+        console.warn("Invalid media visibility setting", mediaPreviewSetting.media_previews);
+        return [false, setMediaVisible];
+    }
+}
diff --git a/src/hooks/useNotificationSettings.tsx b/src/hooks/useNotificationSettings.tsx
index 3036a8122c211f3ca4d1e9f45f0520d14c22fe21..dc1c9f1ba74a1fc60b1db3eed10cd29440e01256 100644
--- a/src/hooks/useNotificationSettings.tsx
+++ b/src/hooks/useNotificationSettings.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPushRules, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IPushRules, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 
-import { NotificationSettings } from "../models/notificationsettings/NotificationSettings";
-import { PushRuleDiff } from "../models/notificationsettings/PushRuleDiff";
+import { type NotificationSettings } from "../models/notificationsettings/NotificationSettings";
+import { type PushRuleDiff } from "../models/notificationsettings/PushRuleDiff";
 import { reconcileNotificationSettings } from "../models/notificationsettings/reconcileNotificationSettings";
 import { toNotificationSettings } from "../models/notificationsettings/toNotificationSettings";
 
diff --git a/src/hooks/usePermalink.ts b/src/hooks/usePermalink.ts
index f9174654fce3dd0d45fee8c999351d27a6ee14c4..146c44cdfe9a9c295d164d5802a3b53c3e1be791 100644
--- a/src/hooks/usePermalink.ts
+++ b/src/hooks/usePermalink.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 
-import { ButtonEvent } from "../components/views/elements/AccessibleButton";
+import { type ButtonEvent } from "../components/views/elements/AccessibleButton";
 import { PillType } from "../components/views/elements/Pill";
 import { parsePermalink } from "../utils/permalinks/Permalinks";
 import dis from "../dispatcher/dispatcher";
 import { Action } from "../dispatcher/actions";
-import { PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
+import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
 import { _t } from "../languageHandler";
 import { usePermalinkTargetRoom } from "./usePermalinkTargetRoom";
 import { usePermalinkEvent } from "./usePermalinkEvent";
diff --git a/src/hooks/usePermalinkEvent.ts b/src/hooks/usePermalinkEvent.ts
index 12844d73bb488f34ee8c92fddcfae2b91fb66e77..bb95f3b5c06aacf495c7a2ada9ffb70bb19214ce 100644
--- a/src/hooks/usePermalinkEvent.ts
+++ b/src/hooks/usePermalinkEvent.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 import { useEffect, useState } from "react";
 
 import { PillType } from "../components/views/elements/Pill";
 import { MatrixClientPeg } from "../MatrixClientPeg";
-import { PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
+import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
 
 /**
  * Tries to find the initial event.
diff --git a/src/hooks/usePermalinkMember.ts b/src/hooks/usePermalinkMember.ts
index 787062ca3a531c45751169db8481d6f9134046ed..b5e751d28fd8da1e4a1fd9e778a6c135505d8cf9 100644
--- a/src/hooks/usePermalinkMember.ts
+++ b/src/hooks/usePermalinkMember.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IMatrixProfile, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
-import { useEffect, useState } from "react";
+import { type IMatrixProfile, type MatrixEvent, type Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { useContext, useEffect, useState } from "react";
 
 import { PillType } from "../components/views/elements/Pill";
-import { SdkContextClass } from "../contexts/SDKContext";
-import { PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
+import { SDKContext, type SdkContextClass } from "../contexts/SDKContext";
+import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
 
 const createMemberFromProfile = (userId: string, profile: IMatrixProfile): RoomMember => {
     const member = new RoomMember("", userId);
@@ -65,12 +65,12 @@ const determineUserId = (
  *          If sharing at least one room with the user, then the result will be the profile fetched via API.
  *          null in all other cases.
  */
-const determineMember = (userId: string, targetRoom: Room): RoomMember | null => {
+const determineMember = (userId: string, targetRoom: Room, context: SdkContextClass): RoomMember | null => {
     const targetRoomMember = targetRoom.getMember(userId);
 
     if (targetRoomMember) return targetRoomMember;
 
-    const knownProfile = SdkContextClass.instance.userProfilesStore.getOnlyKnownProfile(userId);
+    const knownProfile = context.userProfilesStore.getOnlyKnownProfile(userId);
 
     if (knownProfile) {
         return createMemberFromProfile(userId, knownProfile);
@@ -97,11 +97,12 @@ export const usePermalinkMember = (
     targetRoom: Room | null,
     event: MatrixEvent | null,
 ): RoomMember | null => {
+    const context = useContext(SDKContext);
     // User mentions and permalinks to events in the same room require to know the user.
     // If it cannot be initially determined, it will be looked up later by a memo hook.
     const shouldLookUpUser = type && [PillType.UserMention, PillType.EventInSameRoom].includes(type);
     const userId = determineUserId(type, parseResult, event);
-    const userInRoom = shouldLookUpUser && userId && targetRoom ? determineMember(userId, targetRoom) : null;
+    const userInRoom = shouldLookUpUser && userId && targetRoom ? determineMember(userId, targetRoom, context) : null;
     const [member, setMember] = useState<RoomMember | null>(userInRoom);
 
     useEffect(() => {
@@ -111,7 +112,7 @@ export const usePermalinkMember = (
         }
 
         const doProfileLookup = async (): Promise<void> => {
-            const fetchedProfile = await SdkContextClass.instance.userProfilesStore.fetchOnlyKnownProfile(userId);
+            const fetchedProfile = await context.userProfilesStore.fetchOnlyKnownProfile(userId);
 
             if (fetchedProfile) {
                 const newMember = createMemberFromProfile(userId, fetchedProfile);
@@ -120,7 +121,7 @@ export const usePermalinkMember = (
         };
 
         doProfileLookup();
-    }, [member, shouldLookUpUser, targetRoom, userId]);
+    }, [context, member, shouldLookUpUser, targetRoom, userId]);
 
     return member;
 };
diff --git a/src/hooks/usePermalinkTargetRoom.ts b/src/hooks/usePermalinkTargetRoom.ts
index 7a3a40ae521c4d9656da489cdae4be0afcb8cadf..9de18cc4f87ea73b45bfdc5a615adced4db7d914 100644
--- a/src/hooks/usePermalinkTargetRoom.ts
+++ b/src/hooks/usePermalinkTargetRoom.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { useEffect, useState } from "react";
 
 import { PillType } from "../components/views/elements/Pill";
 import { MatrixClientPeg } from "../MatrixClientPeg";
-import { PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
+import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
 
 /**
  * Tries to determine the initial room.
diff --git a/src/hooks/usePinnedEvents.ts b/src/hooks/usePinnedEvents.ts
index b065edc83f714c0e0c0901d447396c00453690a6..f56321112952d7c77fad9642f677844751c3034f 100644
--- a/src/hooks/usePinnedEvents.ts
+++ b/src/hooks/usePinnedEvents.ts
@@ -8,14 +8,14 @@
 
 import { useCallback, useEffect, useMemo, useState } from "react";
 import {
-    Room,
+    type Room,
     RoomEvent,
     RoomStateEvent,
     MatrixEvent,
     EventType,
     RelationType,
     EventTimeline,
-    MatrixClient,
+    type MatrixClient,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts
index 2e8cfb21b62d9a4d8ea5b82d607b2c317720a17e..530e61e0704563f274741f6b9eed29e4acbe2f55 100644
--- a/src/hooks/usePublicRoomDirectory.ts
+++ b/src/hooks/usePublicRoomDirectory.ts
@@ -6,14 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomType, IProtocol, IPublicRoomsChunkRoom, IRoomDirectoryOptions } from "matrix-js-sdk/src/matrix";
+import {
+    type RoomType,
+    type IProtocol,
+    type IPublicRoomsChunkRoom,
+    type IRoomDirectoryOptions,
+} from "matrix-js-sdk/src/matrix";
 import { useCallback, useEffect, useState } from "react";
 
-import { IPublicRoomDirectoryConfig } from "../components/views/directory/NetworkDropdown";
+import { type IPublicRoomDirectoryConfig } from "../components/views/directory/NetworkDropdown";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import SdkConfig from "../SdkConfig";
 import SettingsStore from "../settings/SettingsStore";
-import { Protocols } from "../utils/DirectoryUtils";
+import { type Protocols } from "../utils/DirectoryUtils";
 import { useLatestResult } from "./useLatestResult";
 import { useSettingValue } from "./useSettings";
 
diff --git a/src/hooks/usePushers.ts b/src/hooks/usePushers.ts
index 8dc62832913fc97d61ec9ca9c3bf90fdafa7a0f7..d6b95f7e4e5207e13dd223f91f5ba91bc2c2905e 100644
--- a/src/hooks/usePushers.ts
+++ b/src/hooks/usePushers.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPusher, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IPusher, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useAsyncRefreshMemo } from "./useAsyncRefreshMemo";
 
diff --git a/src/hooks/useRoomMembers.ts b/src/hooks/useRoomMembers.ts
index 7a37d693dfb01fbae8f6307e5f8e0d6b5ca9b57c..14448ba3077914f5c91ef499c037fff11b240dd0 100644
--- a/src/hooks/useRoomMembers.ts
+++ b/src/hooks/useRoomMembers.ts
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useMemo, useState } from "react";
-import { Room, RoomEvent, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
-import { Membership } from "matrix-js-sdk/src/types";
+import { type Room, RoomEvent, type RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { type Membership } from "matrix-js-sdk/src/types";
 import { throttle } from "lodash";
 
 import { useTypedEventEmitter } from "./useEventEmitter";
diff --git a/src/hooks/useRoomName.ts b/src/hooks/useRoomName.ts
index 201cc088de69a1beebaf2b47d1a40a293f451af2..2f46eb287ccc69f3249562c1c039b394837dc7bf 100644
--- a/src/hooks/useRoomName.ts
+++ b/src/hooks/useRoomName.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { useEffect, useState } from "react";
 
-import { IOOBData } from "../stores/ThreepidInviteStore";
+import { type IOOBData } from "../stores/ThreepidInviteStore";
 import { useTypedEventEmitter } from "./useEventEmitter";
 import { _t } from "../languageHandler";
 
diff --git a/src/hooks/useRoomNotificationState.ts b/src/hooks/useRoomNotificationState.ts
index e76295a34758461f8c2e9d3f1d817880a654e307..b1e1b1b1683cff050777079e05506ec406fd4d68 100644
--- a/src/hooks/useRoomNotificationState.ts
+++ b/src/hooks/useRoomNotificationState.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useCallback, useMemo, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { RoomNotifState } from "../RoomNotifs";
+import { type RoomNotifState } from "../RoomNotifs";
 import { EchoChamber } from "../stores/local-echo/EchoChamber";
 import { PROPERTY_UPDATED } from "../stores/local-echo/GenericEchoChamber";
 import { CachedRoomKey } from "../stores/local-echo/RoomEchoChamber";
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
index 24c4609b12abe12e1328a204e7cd6ab58fac366d..5555629bb13a94ac50bc6a3b81d113390cde804a 100644
--- a/src/hooks/useRoomState.ts
+++ b/src/hooks/useRoomState.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useCallback, useEffect, useRef, useState } from "react";
-import { Room, RoomState, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomState, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 
 import { useTypedEventEmitter } from "./useEventEmitter";
 
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
index 7fbf5231fa15c9d2ee2e34a400af97b3cfb143c7..67f84b4c7426711c2e2e1c482b307533ed0ef94a 100644
--- a/src/hooks/useSettings.ts
+++ b/src/hooks/useSettings.ts
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import { useEffect, useState } from "react";
 
 import SettingsStore from "../settings/SettingsStore";
-import { SettingLevel } from "../settings/SettingLevel";
-import { FeatureSettingKey, SettingKey, Settings } from "../settings/Settings.tsx";
+import { type SettingLevel } from "../settings/SettingLevel";
+import { type FeatureSettingKey, type SettingKey, type Settings } from "../settings/Settings.tsx";
 
 // Hook to fetch the value of a setting and dynamically update when it changes
 export function useSettingValue<S extends SettingKey>(
diff --git a/src/hooks/useSlidingSyncRoomSearch.ts b/src/hooks/useSlidingSyncRoomSearch.ts
deleted file mode 100644
index 079d11d7e7ac120ac724255061047267da4900eb..0000000000000000000000000000000000000000
--- a/src/hooks/useSlidingSyncRoomSearch.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { useCallback, useState } from "react";
-import { Room } from "matrix-js-sdk/src/matrix";
-
-import { MatrixClientPeg } from "../MatrixClientPeg";
-import { useLatestResult } from "./useLatestResult";
-import { SlidingSyncManager } from "../SlidingSyncManager";
-
-export interface SlidingSyncRoomSearchOpts {
-    limit: number;
-    query: string;
-}
-
-export const useSlidingSyncRoomSearch = (): {
-    loading: boolean;
-    rooms: Room[];
-    search(opts: SlidingSyncRoomSearchOpts): Promise<boolean>;
-} => {
-    const [rooms, setRooms] = useState<Room[]>([]);
-
-    const [loading, setLoading] = useState(false);
-
-    const [updateQuery, updateResult] = useLatestResult<{ term: string; limit?: number }, Room[]>(setRooms);
-
-    const search = useCallback(
-        async ({ limit = 100, query: term }: SlidingSyncRoomSearchOpts): Promise<boolean> => {
-            const opts = { limit, term };
-            updateQuery(opts);
-
-            if (!term?.length) {
-                setRooms([]);
-                return true;
-            }
-
-            try {
-                setLoading(true);
-                await SlidingSyncManager.instance.ensureListRegistered(SlidingSyncManager.ListSearch, {
-                    ranges: [[0, limit]],
-                    filters: {
-                        room_name_like: term,
-                    },
-                });
-                const rooms: Room[] = [];
-                const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync!.getListData(
-                    SlidingSyncManager.ListSearch,
-                )!;
-                let i = 0;
-                while (roomIndexToRoomId[i]) {
-                    const roomId = roomIndexToRoomId[i];
-                    const room = MatrixClientPeg.safeGet().getRoom(roomId);
-                    if (room) {
-                        rooms.push(room);
-                    }
-                    i++;
-                }
-                updateResult(opts, rooms);
-                return true;
-            } catch (e) {
-                console.error("Could not fetch sliding sync rooms for params", { limit, term }, e);
-                updateResult(opts, []);
-                return false;
-            } finally {
-                setLoading(false);
-                // TODO: delete the list?
-            }
-        },
-        [updateQuery, updateResult],
-    );
-
-    return {
-        loading,
-        rooms,
-        search,
-    } as const;
-};
diff --git a/src/hooks/useSpaceResults.ts b/src/hooks/useSpaceResults.ts
index 3c4698cb667e5ac576d6614104fa9358ae5903b9..d7fb6440870ac222f652a2c6a4ee99c46d2b1775 100644
--- a/src/hooks/useSpaceResults.ts
+++ b/src/hooks/useSpaceResults.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useCallback, useEffect, useMemo, useState } from "react";
-import { Room, RoomType, HierarchyRoom } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomType, type HierarchyRoom } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
 import { normalize } from "matrix-js-sdk/src/utils";
diff --git a/src/hooks/useStateCallback.ts b/src/hooks/useStateCallback.ts
index ec46d0151f54fddde808158083367815992cd88a..6d45adf5db0d349dece04dac8af4ceab20cfb369 100644
--- a/src/hooks/useStateCallback.ts
+++ b/src/hooks/useStateCallback.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Dispatch, useState } from "react";
+import { type Dispatch, useState } from "react";
 
 // Hook to simplify interactions with a store-backed state values
 // Returns value and method to change the state value
diff --git a/src/hooks/useStateToggle.ts b/src/hooks/useStateToggle.ts
index d808d9b133e3c35456795283f417e3756598346f..962a47c6be1cf7c3112203ce35bf25913f338d39 100644
--- a/src/hooks/useStateToggle.ts
+++ b/src/hooks/useStateToggle.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Dispatch, SetStateAction, useState } from "react";
+import { type Dispatch, type SetStateAction, useState } from "react";
 
 // Hook to simplify toggling of a boolean state value
 // Returns value, method to toggle boolean value and method to set the boolean value
diff --git a/src/hooks/useThreepids.ts b/src/hooks/useThreepids.ts
index 32884bc7cc117ff7d2c332a1dda60d4bd9d52d57..9cc03f5bccddffd269be7c1af49f3450be6d6f74 100644
--- a/src/hooks/useThreepids.ts
+++ b/src/hooks/useThreepids.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, IThreepid } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type IThreepid } from "matrix-js-sdk/src/matrix";
 
 import { useAsyncRefreshMemo } from "./useAsyncRefreshMemo";
 
diff --git a/src/hooks/useTimeout.ts b/src/hooks/useTimeout.ts
index 64343cbfadb74b333473de3afbf29d1549ac3712..102dd3670cb9ebed1d3795992379af62f30d0a25 100644
--- a/src/hooks/useTimeout.ts
+++ b/src/hooks/useTimeout.ts
@@ -13,7 +13,7 @@ type Handler = () => void;
 // Hook to simplify timeouts in functional components
 export const useTimeout = (handler: Handler, timeoutMs: number): void => {
     // Create a ref that stores handler
-    const savedHandler = useRef<Handler>();
+    const savedHandler = useRef<Handler>(undefined);
 
     // Update ref.current value if handler changes.
     useEffect(() => {
@@ -32,7 +32,7 @@ export const useTimeout = (handler: Handler, timeoutMs: number): void => {
 // Hook to simplify intervals in functional components
 export const useInterval = (handler: Handler, intervalMs: number): void => {
     // Create a ref that stores handler
-    const savedHandler = useRef<Handler>();
+    const savedHandler = useRef<Handler>(undefined);
 
     // Update ref.current value if handler changes.
     useEffect(() => {
diff --git a/src/hooks/useTimeoutToggle.ts b/src/hooks/useTimeoutToggle.ts
index c67af845dbccdcb7dec0f2a43aaf46065f9382c5..91e0452da5001f5cf03db579a61c87e3c7897f34 100644
--- a/src/hooks/useTimeoutToggle.ts
+++ b/src/hooks/useTimeoutToggle.ts
@@ -21,7 +21,7 @@ export const useTimeoutToggle = (
     value: boolean;
     toggle(): void;
 } => {
-    const timeoutId = useRef<number | undefined>();
+    const timeoutId = useRef<number | undefined>(undefined);
     const [value, setValue] = useState<boolean>(defaultValue);
 
     const toggle = (): void => {
diff --git a/src/hooks/useTransition.ts b/src/hooks/useTransition.ts
deleted file mode 100644
index e1f48eeac4ac4cf9e9a39eefa02d18824c882673..0000000000000000000000000000000000000000
--- a/src/hooks/useTransition.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2024 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-// Based on https://stackoverflow.com/a/61680184
-
-import { DependencyList, useEffect, useRef } from "react";
-
-export const useTransition = <D extends DependencyList>(callback: (...params: D) => void, deps: D): void => {
-    const func = useRef<(...params: D) => void>(callback);
-
-    useEffect(() => {
-        func.current = callback;
-    }, [callback]);
-
-    const args = useRef<D | null>(null);
-
-    useEffect(() => {
-        if (args.current !== null) func.current(...args.current);
-        args.current = deps;
-        // eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps
-    }, deps);
-};
diff --git a/src/hooks/useUserTimezone.ts b/src/hooks/useUserTimezone.ts
index c6c98483ddfb3bf49d7b8e5c0cc1b5ef800c574e..0e5f046d7422e47dca085ed32eebbf6f5d00abba 100644
--- a/src/hooks/useUserTimezone.ts
+++ b/src/hooks/useUserTimezone.ts
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 import { useEffect, useState } from "react";
-import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+
+import { getTwelveHourOptions } from "../DateUtils.ts";
+import { useSettingValue } from "./useSettings.ts";
 
 /**
  * Fetch a user's delclared timezone through their profile, and return
@@ -23,6 +26,7 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone:
     const [updateInterval, setUpdateInterval] = useState<ReturnType<typeof setTimeout>>();
     const [friendly, setFriendly] = useState<string>();
     const [supported, setSupported] = useState<boolean>();
+    const showTwelveHour = useSettingValue("showTwelveHourTimestamps");
 
     useEffect(() => {
         if (!cli || supported !== undefined) {
@@ -62,8 +66,8 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone:
                 const updateTime = (): void => {
                     const currentTime = new Date();
                     const friendly = currentTime.toLocaleString(undefined, {
+                        ...getTwelveHourOptions(showTwelveHour),
                         timeZone: tz,
-                        hour12: true,
                         hour: "2-digit",
                         minute: "2-digit",
                         timeZoneName: "shortOffset",
@@ -84,7 +88,7 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone:
                 console.error("Could not render current timezone for user", ex);
             }
         })();
-    }, [supported, userId, cli]);
+    }, [supported, userId, cli, showTwelveHour]);
 
     if (!timezone || !friendly) {
         return null;
diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json
index 0acb4727af0c7d2f46becb167253c79700982d39..f454a2f1e42a5edd1b9d35e3fb1ab219ff63c9d8 100644
--- a/src/i18n/strings/cs.json
+++ b/src/i18n/strings/cs.json
@@ -12,6 +12,18 @@
             "one": "Nepřečtená zmínka."
         },
         "recent_rooms": "Nedávné místnosti",
+        "room_messsage_not_sent": "Otevřít místnost %(roomName)s s nenastavenou zprávou.",
+        "room_n_unread_invite": "Otevřít pozvánku do místnosti %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Otevřít místnost %(roomName)s s 1 nepřečtenou zprávou.",
+            "few": "",
+            "other": "Otevřít místnost %(roomName)s s %(count)s nepřečtenými zprávami."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Otevřít místnost %(roomName)s s 1 nepřečtenou zmínkou.",
+            "few": "",
+            "other": "Otevřít místnost %(roomName)s s %(count)s nepřečtenými zprávami včetně zmínek."
+        },
         "room_name": "Místnost %(name)s",
         "room_status_bar": "Stavový řádek místnosti",
         "seek_bar_label": "Panel posunu zvuku",
@@ -45,6 +57,8 @@
         "create_a_room": "Vytvořit místnost",
         "create_account": "Vytvořit účet",
         "decline": "Odmítnout",
+        "decline_and_block": "Odmítnout a zablokovat",
+        "decline_invite": "Odmítnout pozvání",
         "delete": "Smazat",
         "deny": "Odmítnout",
         "disable": "Zakázat",
@@ -64,6 +78,7 @@
         "go": "Ok",
         "go_back": "Zpět",
         "got_it": "Rozumím",
+        "hide": "Skrýt",
         "hide_advanced": "Skrýt pokročilé možnosti",
         "hold": "Podržet",
         "ignore": "Ignorovat",
@@ -80,12 +95,14 @@
         "maximise": "Maximalizovat",
         "mention": "Zmínit",
         "minimise": "Minimalizovat",
+        "new_message": "Nová zpráva",
         "new_room": "Nová místnost",
         "new_video_room": "Nová video místnost",
         "next": "Další",
         "no": "Ne",
         "ok": "OK",
         "open": "Otevřít",
+        "open_menu": "Otevřít nabídku",
         "pause": "Pozastavit",
         "pin": "Připnout",
         "play": "Přehrát",
@@ -94,13 +111,13 @@
         "react": "Reagovat",
         "refresh": "Obnovit",
         "register": "Zaregistrovat",
-        "reject": "Odmítnout",
         "reload": "Znovu načíst",
         "remove": "Smazat",
         "rename": "Přejmenovat",
         "reply": "Odpovědět",
         "reply_in_thread": "Odpovědět ve vlákně",
         "report_content": "Nahlásit obsah",
+        "report_room": "Nahlásit místnost",
         "resend": "Poslat znovu",
         "reset": "Resetovat",
         "resume": "Pokračovat",
@@ -110,6 +127,7 @@
         "save": "Uložit",
         "search": "Hledání",
         "send_report": "Nahlásit",
+        "set_avatar": "Nastavit profilový obrázek",
         "share": "Sdílet",
         "show": "Zobrazit",
         "show_advanced": "Zobrazit pokročilé možnosti",
@@ -133,6 +151,7 @@
         "update": "Aktualizovat",
         "upgrade": "Aktualizovat",
         "upload": "Nahrát",
+        "upload_file": "Nahrát soubor",
         "verify": "Ověřit",
         "view": "Zobrazit",
         "view_all": "Zobrazit všechny",
@@ -140,6 +159,7 @@
         "view_message": "Zobrazit zprávu",
         "view_source": "Zobrazit zdroj",
         "yes": "Ano",
+        "yes_dismiss": "Ano, zamítnout",
         "zoom_in": "Přiblížit",
         "zoom_out": "Oddálit"
     },
@@ -227,6 +247,7 @@
         },
         "misconfigured_body": "Požádejte správce vašeho %(brand)su, aby zkontroloval <a>vaši konfiguraci</a>. Pravděpodobně obsahuje chyby nebo duplicity.",
         "misconfigured_title": "%(brand)s je špatně nakonfigurován",
+        "mobile_create_account_title": "Chystáte se vytvořit účet na%(hsName)s",
         "msisdn_field_description": "Ostatní uživatelé vás můžou pozvat do místností podle kontaktních údajů",
         "msisdn_field_label": "Telefon",
         "msisdn_field_number_invalid": "Toto telefonní číslo nevypadá úplně správně, zkontrolujte ho a zkuste to znovu",
@@ -244,12 +265,40 @@
         "phone_label": "Telefon",
         "phone_optional_label": "Telefonní číslo (nepovinné)",
         "qr_code_login": {
+            "check_code_explainer": "Tím ověříte, zda je připojení k druhému zařízení bezpečné.",
+            "check_code_heading": "Zadejte číslo zobrazené na druhém zařízení",
+            "check_code_input_label": "2místný kód",
+            "check_code_mismatch": "Čísla se neshodují",
             "completing_setup": "Dokončování nastavení nového zařízení",
+            "error_etag_missing": "Došlo k neočekávané chybě. Příčinou může být rozšíření prohlížeče, proxy server nebo chybná konfigurace serveru.",
+            "error_expired": "Přihlášení vypršelo. Zkuste to prosím znovu.",
+            "error_expired_title": "Přihlášení nebylo dokončeno včas",
+            "error_insecure_channel_detected": "K novému zařízení se nepodařilo navázat bezpečné připojení. Vaše stávající zařízení jsou stále v bezpečí a nemusíte se o ně obávat.",
+            "error_insecure_channel_detected_instructions": "Co teď?",
+            "error_insecure_channel_detected_instructions_1": "Zkuste se znovu přihlásit k druhému zařízení pomocí kódu QR pro případ, že by se jednalo o problém se sítí.",
+            "error_insecure_channel_detected_instructions_2": "Pokud se setkáte se stejným problémem, zkuste použít jinou síť wifi nebo místo wifi použijte mobilní data.",
+            "error_insecure_channel_detected_instructions_3": "Pokud to nefunguje, přihlaste se ručně",
+            "error_insecure_channel_detected_title": "Připojení není zabezpečené",
+            "error_other_device_already_signed_in": "Nemusíte dělat nic jiného.",
+            "error_other_device_already_signed_in_title": "Vaše druhé zařízení je již přihlášeno",
             "error_rate_limited": "Příliš mnoho pokusů v krátkém čase. Počkejte chvíli, než to zkusíte znovu.",
-            "error_unexpected": "Došlo k neočekávané chybě.",
-            "scan_code_instruction": "Níže uvedený QR kód naskenujte pomocí přihlašovaného zařízení.",
-            "scan_qr_code": "Skenovat QR kód",
-            "select_qr_code": "Vybrat '%(scanQRCode)s'",
+            "error_unexpected": "Vyskytla se neočekávaná chyba. Požadavek na připojení vašeho druhého zařízení byl zrušen.",
+            "error_unsupported_protocol": "Toto zařízení nepodporuje přihlášení k jinému zařízení pomocí kódu QR.",
+            "error_unsupported_protocol_title": "Jiné zařízení není kompatibilní",
+            "error_user_cancelled": "Přihlášení na druhém zařízení bylo zrušeno.",
+            "error_user_cancelled_title": "Žádost o přihlášení byla zrušena",
+            "error_user_declined": "Vy nebo poskytovatel účtu jste odmítli žádost o přihlášení.",
+            "error_user_declined_title": "Přihlášení bylo odmítnuto",
+            "follow_remaining_instructions": "Postupujte podle zbývajících pokynů",
+            "open_element_other_device": "Otevřete %(brand)s na druhém zařízení",
+            "point_the_camera": "Naskenujte zde zobrazený QR kód",
+            "scan_code_instruction": "Naskenujte QR kód jiným zařízením",
+            "scan_qr_code": "Přihlaste se pomocí QR kódu",
+            "security_code": "Bezpečnostní kód",
+            "security_code_prompt": "Pokud budete vyzváni, zadejte na druhém zařízení níže uvedený kód.",
+            "select_qr_code": "Vyberte \"%(scanQRCode)s\"",
+            "unsupported_explainer": "Váš poskytovatel účtu nepodporuje přihlášení do nového zařízení pomocí QR kódu.",
+            "unsupported_heading": "QR kód není podporován",
             "waiting_for_device": "Čekání na přihlášení zařízení"
         },
         "register_action": "Vytvořit účet",
@@ -338,6 +387,9 @@
             "email_resend_prompt": "Nedostali jste ho? <a>Poslat znovu</a>",
             "email_resent": "Přeposláno!",
             "fallback_button": "Zahájit autentizaci",
+            "mas_cross_signing_reset_cta": "Přejděte na svůj účet",
+            "mas_cross_signing_reset_description": "Resetujte svou identitu prostřednictvím svého poskytovatele účtu a poté se vraťte a klikněte na \"Opakovat\".",
+            "mas_cross_signing_reset_title": "Přejděte na svůj účet a obnovte svou identitu",
             "msisdn": "Na číslo %(msisdn)s byla odeslána textová zpráva",
             "msisdn_token_incorrect": "Neplatný token",
             "msisdn_token_prompt": "Prosím zadejte kód z této zprávy:",
@@ -372,7 +424,15 @@
         "download_logs": "Stáhnout záznamy",
         "downloading_logs": "Stahování záznamů",
         "error_empty": "Dejte nám vědět, prosím, co se pokazilo nebo vytvořte issue na GitHubu, kde problém popište.",
-        "failed_send_logs": "Nepodařilo se odeslat záznamy: ",
+        "failed_download_logs": "Stažení ladících protokolů se nezdařilo: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server tuto aplikaci nepodporuje.",
+            "rejected_generic": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server odmítl obsah zprávy kvůli zásadám.",
+            "rejected_recovery_key": "Vaše hlášení o chybě bylo z bezpečnostních důvodů odmítnuto, protože obsahovalo klíč pro obnovení.",
+            "rejected_version": "Vaše hlášení o chybě bylo zamítnuto, protože verze, kterou používáte, je příliš stará.",
+            "server_unknown_error": "Rageshake server narazil na neznámou chybu a nemohl zpracovat zprávu.",
+            "unknown_error": "Odeslání protokolů se nezdařilo."
+        },
         "github_issue": "issue na GitHubu",
         "introduction": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. ",
         "log_request": "Abychom tomu mohli pro příště předejít, <a>pošlete nám prosím záznamy</a>.",
@@ -412,7 +472,7 @@
         "access_token": "Přístupový token",
         "accessibility": "Přístupnost",
         "advanced": "Rozšířené",
-        "all_rooms": "Všechny místnosti",
+        "all_chats": "Všechny chaty",
         "analytics": "Analytické údaje",
         "and_n_others": {
             "other": "a %(count)s další...",
@@ -427,10 +487,10 @@
         "beta": "Beta",
         "camera": "Kamera",
         "cameras": "Kamery",
+        "cancel": "Zrušit",
         "capabilities": "Schopnosti",
         "copied": "Zkopírováno!",
         "credits": "Poděkování",
-        "cross_signing": "Křížové podepisování",
         "dark": "Tmavý",
         "description": "Popis",
         "deselect_all": "Zrušit výběr všech",
@@ -461,14 +521,15 @@
         "legal": "Právní informace",
         "light": "Světlý",
         "loading": "Načítání…",
-        "lobby": "Předsálí",
         "location": "Poloha",
         "low_priority": "Nízká priorita",
         "matrix": "Matrix",
         "message": "Zpráva",
         "message_layout": "Zobrazení zpráv",
+        "message_timestamp_invalid": "Neplatné časové razítko",
         "microphone": "Mikrofon",
         "model": "Model",
+        "moderation_and_safety": "Moderování a bezpečnost",
         "modern": "Moderní",
         "mute": "Ztlumit",
         "n_members": {
@@ -504,13 +565,15 @@
         "qr_code": "QR kód",
         "random": "Náhodný",
         "reactions": "Reakce",
+        "recommended": "Doporučeno",
         "report_a_bug": "Nahlásit chybu",
         "room": "Místnost",
         "room_name": "Název místnosti",
         "rooms": "Místnosti",
+        "save": "Uložit",
+        "saved": "Uloženo",
         "saving": "Ukládání…",
         "secure_backup": "Zabezpečená záloha",
-        "security": "Zabezpečení",
         "select_all": "Vybrat všechny",
         "server": "Server",
         "settings": "Nastavení",
@@ -529,13 +592,13 @@
         "thread": "Vlákno",
         "threads": "Vlákna",
         "timeline": "Časová osa",
-        "trusted": "Důvěryhodné",
         "unavailable": "nedostupný",
         "unencrypted": "Nezašifrované",
         "unmute": "Povolit",
         "unnamed_room": "Nepojmenovaná místnost",
         "unnamed_space": "Nejmenovaný prostor",
         "unverified": "Neověřeno",
+        "updating": "Aktualizace...",
         "user": "Uživatel",
         "user_avatar": "Profilový obrázek",
         "username": "Uživatelské jméno",
@@ -688,12 +751,58 @@
         "twemoji": "<twemoji>Twemoji</twemoji> emoji grafika je © <author>Twitter, Inc a další přispěvatelé</author> používána za podmínek <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "Písmo <colr>twemoji-colr</colr> je © <author>Mozilla Foundation</author> používané za podmínek <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Opravdu chcete odmítnout pozvánku do \"%(roomName)s\"?",
+        "ignore_user_help": "Od tohoto uživatele neuvidíte žádné zprávy ani pozvánky do místnosti.",
+        "reason_description": "Popište důvod nahlášení místnosti.",
+        "report_room_description": "Nahlaste tuto místnost svému poskytovateli účtu.",
+        "title": "Odmítnout pozvání"
+    },
+    "desktop_default_device_name": "%(brand)s Desktop: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktivní widgety",
         "category_other": "Další možnosti",
         "category_room": "Místnost",
         "caution_colon": "Pozor:",
         "client_versions": "Verze klienta",
+        "crypto": {
+            "4s_public_key_in_account_data": "v datech účtu",
+            "4s_public_key_not_in_account_data": "nenalezeno",
+            "4s_public_key_status": "Veřejný klíč bezpečného úložiště:",
+            "backup_key_cached": "uložen lokálně",
+            "backup_key_cached_status": "Klíč zálohy v mezipaměti:",
+            "backup_key_not_stored": "není uložen",
+            "backup_key_stored": "v bezpečném úložišti",
+            "backup_key_stored_status": "Klíč zálohy uložen:",
+            "backup_key_unexpected_type": "neočekávaný typ",
+            "backup_key_well_formed": "ve správném tvaru",
+            "cross_signing": "Křížové podepisování",
+            "cross_signing_cached": "uložen lokálně",
+            "cross_signing_not_ready": "Křížové podepisování není nastaveno.",
+            "cross_signing_private_keys_in_storage": "v bezpečném úložišti",
+            "cross_signing_private_keys_in_storage_status": "Soukromé klíče pro křížový podpis:",
+            "cross_signing_private_keys_not_in_storage": "nebylo nalezeno v úložišti",
+            "cross_signing_public_keys_on_device": "v paměti",
+            "cross_signing_public_keys_on_device_status": "Veřejné klíče pro křížový podpis:",
+            "cross_signing_ready": "Křížové podepisování je připraveno k použití.",
+            "cross_signing_status": "Stav křížového podepisování:",
+            "cross_signing_untrusted": "Váš účet má v bezpečném úložišti identitu pro křížový podpis, ale v této relaci jí zatím nevěříte.",
+            "crypto_not_available": "Kryptografický modul není k dispozici",
+            "key_backup_active_version": "Verze aktivní zálohy:",
+            "key_backup_active_version_none": "Žádné",
+            "key_backup_inactive_warning": "Vaše klíče nejsou z této relace zálohovány.",
+            "key_backup_latest_version": "Nejnovější verze zálohy na serveru:",
+            "key_storage": "Úložiště klíčů",
+            "master_private_key_cached_status": "Hlavní soukromý klíč:",
+            "not_found": "nenalezeno",
+            "not_found_locally": "nenalezen lolálně",
+            "secret_storage_not_ready": "nepřipraveno",
+            "secret_storage_ready": "připraveno",
+            "secret_storage_status": "Bezpečné úložiště:",
+            "self_signing_private_key_cached_status": "Soukromý klíč s vlastním podpisem:",
+            "title": "Koncové šifrování",
+            "user_signing_private_key_cached_status": "Podpisový soukromý klíč uživatele:"
+        },
         "developer_mode": "Vývojářský režim",
         "developer_tools": "Nástroje pro vývojáře",
         "edit_setting": "Upravit nastavení",
@@ -734,8 +843,7 @@
         "room_status": "Stav místnosti",
         "room_unread_status_count": {
             "one": "Stav nepřečtení místnosti: <strong>%(status)s</strong>, počet: <strong>%(count)s</strong>",
-            "few": "",
-            "other": "Stav nepřečtení místnosti: <strong>%(status)s</strong>, počet: <strong>%(count)s</strong>"
+            "other": "Stav nepřečtení místností: <strong>%(status)s</strong>, počet: <strong>%(count)s</strong>"
         },
         "save_setting_values": "Uložit hodnoty nastavení",
         "see_history": "Prohlédnout historii",
@@ -750,6 +858,9 @@
         "setting_colon": "Nastavení:",
         "setting_definition": "Definice nastavení:",
         "setting_id": "ID nastavení",
+        "settings": {
+            "elementCallUrl": "Element Call URL"
+        },
         "settings_explorer": "Průzkumník nastavení",
         "show_hidden_events": "Zobrazovat v časové ose skryté události",
         "spaces": {
@@ -802,52 +913,37 @@
     "empty_room_was_name": "Prázdná místnost (dříve %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Zadejte bezpečnostní frázi nebo <button>použijte bezpečnostní klíč</button> pro pokračování.",
+            "alternatives": "Pokud máte bezpečnostní klíč nebo bezpečnostní frázi, bude to také fungovat.",
             "key_validation_text": {
-                "invalid_security_key": "Neplatný bezpečnostní klíč",
-                "recovery_key_is_correct": "To vypadá dobře!",
-                "wrong_file_type": "Špatný typ souboru",
-                "wrong_security_key": "Špatný bezpečnostní klíč"
-            },
-            "reset_title": "Resetovat vše",
-            "reset_warning_1": "Udělejte to, pouze pokud nemáte žádné jiné zařízení, se kterým byste mohli dokončit ověření.",
-            "reset_warning_2": "Pokud vše resetujete, začnete bez důvěryhodných relací, bez důvěryhodných uživatelů a možná nebudete moci zobrazit minulé zprávy.",
+                "wrong_security_key": "Zadaný klíč pro obnovení není správný."
+            },
+            "privacy_warning": "Ujistěte se, že tuto obrazovku nikdo nevidí!",
             "restoring": "Obnovení klíčů ze zálohy",
-            "security_key_title": "Bezpečnostní klíč",
-            "security_phrase_incorrect_error": "Nelze získat přístup k zabezpečenému úložišti. Ověřte, zda jste zadali správnou bezpečnostní frázi.",
-            "security_phrase_title": "Bezpečnostní fráze",
-            "separator": "%(securityKey)s nebo %(recoveryFile)s",
-            "use_security_key_prompt": "Pokračujte pomocí bezpečnostního klíče."
+            "security_key_title": "Klíč pro obnovení"
         },
         "bootstrap_title": "Příprava klíčů",
         "cancel_entering_passphrase_description": "Chcete určitě zrušit zadávání přístupové fráze?",
         "cancel_entering_passphrase_title": "Zrušit zadávání přístupové fráze?",
         "confirm_encryption_setup_body": "Kliknutím na tlačítko níže potvrďte nastavení šifrování.",
         "confirm_encryption_setup_title": "Potvrďte nastavení šifrování",
-        "cross_signing_not_ready": "Křížové podepisování není nastaveno.",
-        "cross_signing_ready": "Křížové podepisování je připraveno k použití.",
-        "cross_signing_ready_no_backup": "Křížové podepisování je připraveno, ale klíče nejsou zálohovány.",
         "cross_signing_room_normal": "Místnost je koncově šifrovaná",
         "cross_signing_room_verified": "V této místnosti jsou všichni ověřeni",
         "cross_signing_room_warning": "Někdo používá neznámou relaci",
-        "cross_signing_unsupported": "Váš domovský server nepodporuje křížové podepisování.",
-        "cross_signing_untrusted": "Váš účet má v bezpečném úložišti identitu pro křížový podpis, ale v této relaci jí zatím nevěříte.",
         "cross_signing_user_normal": "Tohoto uživatele jste neověřili.",
         "cross_signing_user_verified": "Tohoto uživatele jste ověřili a on ověřil všechny své relace.",
         "cross_signing_user_warning": "Tento uživatel zatím neověřil všechny své relace.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Smazat klíče pro křížové podepisování",
-            "title": "Nenávratně smazat klíče pro křížové podepisování?",
-            "warning": "Smazání klíčů pro křížové podepisování je definitivní. Každý, kdo vás ověřil, teď uvidí bezpečnostní varování. Pokud jste zrovna neztratili všechna zařízení, ze kterých se můžete ověřit, tak to asi nechcete udělat."
-        },
+        "enter_recovery_key": "Zadejte klíč pro obnovení",
         "event_shield_reason_authenticity_not_guaranteed": "Pravost této šifrované zprávy nelze na tomto zařízení ověřit.",
         "event_shield_reason_mismatched_sender_key": "Šifrované neověřenou relací",
         "event_shield_reason_unknown_device": "Šifrováno neznámým nebo smazaným zařízením.",
         "event_shield_reason_unsigned_device": "Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem.",
         "event_shield_reason_unverified_identity": "Šifrováno neověřeným uživatelem.",
         "export_unsupported": "Váš prohlížeč nepodporuje požadovaná kryptografická rozšíření",
+        "forgot_recovery_key": "Zapomněli jste klíč pro obnovení?",
         "import_invalid_keyfile": "Neplatný soubor s klíčem %(brand)s",
         "import_invalid_passphrase": "Kontrola ověření selhala: špatné heslo?",
+        "key_storage_out_of_sync": "Vaše úložiště klíčů není synchronizováno.",
+        "key_storage_out_of_sync_description": "Potvrďte klíč pro obnovení, abyste zachovali přístup k úložišti klíčů a historii zpráv.",
         "messages_not_secure": {
             "cause_1": "Váš domovský server",
             "cause_2": "Domovský server, ke kterému je ověřovaný uživatel připojen",
@@ -862,7 +958,8 @@
             "title": "Nový způsob obnovy",
             "warning": "Pokud jste nenastavili nový způsob obnovy vy, mohou se pokoušet k vašemu účtu dostat útočníci. Změňte si raději ihned heslo a nastavte nový způsob obnovy v Nastavení."
         },
-        "not_supported": "<nepodporováno>",
+        "pinned_identity_changed": "Identita %(displayName)s (<b>%(userId)s</b>) se změnila. <a>Další informace</a>",
+        "pinned_identity_changed_no_displayname": "Identita uživatele <b>%(userId)s</b> se změnila. <a>Další informace</a>",
         "recovery_method_removed": {
             "description_1": "Tato relace zjistila, že byla odstraněna vaše bezpečnostní fráze a klíč pro zabezpečené zprávy.",
             "description_2": "Pokud se vám to stalo neúmyslně, můžete znovu nastavit zálohu zpráv pro tuto relaci. To znovu zašifruje historii zpráv novým způsobem.",
@@ -870,12 +967,16 @@
             "warning": "Pokud jste způsob obnovy neodstranili vy, mohou se pokoušet k vašemu účtu dostat útočníci. Změňte si raději ihned heslo a nastavte nový způsob obnovy v Nastavení."
         },
         "reset_all_button": "Zapomněli nebo ztratili jste všechny metody obnovy? <a>Resetovat vše</a>",
+        "set_up_recovery": "Nastavení obnovení",
+        "set_up_recovery_later": "Teď ne",
+        "set_up_recovery_toast_description": "Vygenerujte klíč pro obnovení, který lze použít k obnovení historie šifrovaných zpráv v případě, že ztratíte přístup k zařízením.",
         "set_up_toast_description": "Zabezpečení proti ztrátě přístupu k šifrovaným zprávám a datům",
         "set_up_toast_title": "Nastavení zabezpečené zálohy",
         "setup_secure_backup": {
-            "explainer": "Před odhlášením si zazálohujte klíče abyste o ně nepřišli.",
-            "title": "Nastavit"
+            "explainer": "Před odhlášením si zazálohujte klíče abyste o ně nepřišli."
         },
+        "turn_on_key_storage": "Zapnout úložiště klíčů",
+        "turn_on_key_storage_description": "Bezpečně uložte svou kryptografickou identitu a klíče zpráv na serveru. To vám umožní zobrazit historii zpráv na všech nových zařízeních.",
         "udd": {
             "interactive_verification_button": "Interaktivní ověření pomocí emoji",
             "other_ask_verify_text": "Požádejte tohoto uživatele, aby ověřil svou relaci, nebo jí níže můžete ověřit manuálně.",
@@ -885,12 +986,10 @@
             "title": "Nedůvěryhodné"
         },
         "unable_to_setup_keys_error": "Nepovedlo se nastavit klíče",
-        "unsupported": "Tento klient nepodporuje koncové šifrování.",
         "verification": {
             "accepting": "Přijímání…",
             "after_new_login": {
                 "device_verified": "Zařízení ověřeno",
-                "reset_confirmation": "Opravdu chcete resetovat ověřovací klíče?",
                 "skip_verification": "Prozatím přeskočit ověřování",
                 "unable_to_verify": "Nelze ověřit toto zařízení",
                 "verify_this_device": "Ověřit toto zařízení"
@@ -912,7 +1011,7 @@
             "incoming_sas_dialog_waiting": "Čekání na potvrzení partnerem…",
             "incoming_sas_user_dialog_text_1": "Po ověření bude uživatel označen jako důvěryhodný. Ověřování uživatelů vám dává větší jistotu, že je komunikace důvěrná.",
             "incoming_sas_user_dialog_text_2": "Ověření uživatele označí jeho relace za důvěryhodné a vaše relace budou důvěryhodné pro něj.",
-            "no_key_or_device": "Vypadá to, že nemáte bezpečnostní klíč ani žádné jiné zařízení, které byste mohli ověřit.  Toto zařízení nebude mít přístup ke starým šifrovaným zprávám. Abyste mohli na tomto zařízení ověřit svou totožnost, budete muset resetovat ověřovací klíče.",
+            "no_key_or_device": "Vypadá to, že nemáte klíč pro obnovení ani žádné jiné zařízení, které byste mohli ověřit.  Toto zařízení nebude mít přístup ke starým zašifrovaným zprávám. Abyste mohli na tomto zařízení ověřit svou identitu, budete muset obnovit ověřovací klíče.",
             "no_support_qr_emoji": "Zařízení, které se snažíte ověřit, neumožňuje ověření QR kódem ani pomocí emotikonů, které %(brand)s podporuje. Zkuste použít jiného klienta.",
             "other_party_cancelled": "Druhá strana ověření zrušila.",
             "prompt_encrypted": "Ověřit všechny uživatele v místnosti, abyste se přesvědčili o bezpečnosti.",
@@ -925,6 +1024,7 @@
             "qr_reciprocate_same_shield_device": "Už to skoro je! Zobrazuje vaše druhé zařízení stejný štít?",
             "qr_reciprocate_same_shield_user": "Téměř hotovo! Je relace %(displayName)s také ověřená?",
             "request_toast_accept": "Ověřit relaci",
+            "request_toast_accept_user": "Ověřit uživatele",
             "request_toast_decline_counter": "Ignorovat (%(counter)s)",
             "request_toast_detail": "%(deviceId)s z %(ip)s",
             "reset_proceed_prompt": "Pokračovat v resetování",
@@ -950,7 +1050,7 @@
             "unverified_sessions_toast_description": "Zkontrolujte, zda je váš účet v bezpečí",
             "unverified_sessions_toast_reject": "Později",
             "unverified_sessions_toast_title": "Máte neověřené relace",
-            "verification_description": "Ověřte svou identitu, abyste získali přístup k šifrovaným zprávám a prokázali svou identitu ostatním.",
+            "verification_description": "Ověřte svou identitu, abyste získali přístup k šifrovaným zprávám a prokázali svou identitu ostatním. Pokud používáte také mobilní zařízení, před pokračováním otevřete aplikaci.",
             "verification_dialog_title_device": "Ověřit jiné zařízení",
             "verification_dialog_title_user": "Požadavek na ověření",
             "verification_skip_warning": "Bez ověření nebudete mít přístup ke všem svým zprávám a můžete se ostatním jevit jako nedůvěryhodní.",
@@ -960,19 +1060,20 @@
             "verify_emoji_prompt": "Ověření porovnáním několika emoji.",
             "verify_emoji_prompt_qr": "Pokud vám skenování kódů nefunguje, ověřte se porovnáním emoji.",
             "verify_later": "Ověřím se později",
-            "verify_reset_warning_1": "Resetování ověřovacích klíčů nelze vrátit zpět. Po jejich resetování nebudete mít přístup ke starým zašifrovaným zprávám a všem přátelům, kteří vás dříve ověřili, se zobrazí bezpečnostní varování, dokud se u nich znovu neověříte.",
-            "verify_reset_warning_2": "Pokračujte pouze v případě, že jste si jisti, že jste ztratili všechna ostatní zařízení a bezpečnostní klíč.",
             "verify_using_device": "Ověřit pomocí jiného zařízení",
-            "verify_using_key": "Ověření pomocí bezpečnostního klíče",
-            "verify_using_key_or_phrase": "Ověření pomocí bezpečnostního klíče nebo fráze",
+            "verify_using_key": "Ověřit pomocí klíče pro obnovení",
+            "verify_using_key_or_phrase": "Ověření pomocí klíče pro obnovení nebo fráze",
             "waiting_for_user_accept": "Čekáme, než %(displayName)s výzvu přijme…",
             "waiting_other_device": "Čekáme na ověření na jiném zařízení…",
             "waiting_other_device_details": "Čekáme na ověření na vašem dalším zařízení, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Čekám až nás %(displayName)s ověří…"
         },
         "verification_requested_toast_title": "Žádost ověření",
+        "verified_identity_changed": "Ověřená identita uživatele %(displayName)s (<b>%(userId)s</b>) se změnila. <a>Další informace</a>",
+        "verified_identity_changed_no_displayname": "Ověřená identita uživatele <b>%(userId)s</b> se změnila. <a>Další informace</a>",
         "verify_toast_description": "Ostatní uživatelé této relaci nemusí věřit",
-        "verify_toast_title": "Ověřit tuto relaci"
+        "verify_toast_title": "Ověřit tuto relaci",
+        "withdraw_verification_action": "Zrušit ověření"
     },
     "error": {
         "admin_contact": "Pro pokračování využívání této služby prosím kontaktujte jejího <a>správce</a>.",
@@ -1003,7 +1104,7 @@
             "description_3": "Vymazání úložiště prohlížeče možná váš problém opraví, zároveň se tím ale odhlásíte a můžete přijít o historii svých šifrovaných konverzací.",
             "title": "Nelze obnovit relaci"
         },
-        "something_went_wrong": "Něco se nepodařilo!",
+        "something_went_wrong": "Něco se nepovedlo!",
         "storage_evicted_description_1": "Některá data sezení, například klíče od šifrovaných zpráv, nám chybí. Přihlaste se prosím znovu a obnovte si klíče ze zálohy.",
         "storage_evicted_description_2": "Prohlížeč data možná smazal aby ušetřil místo na disku.",
         "storage_evicted_title": "Chybějící data relace",
@@ -1027,11 +1128,7 @@
             "title": "Nelze zkopírovat odkaz na místnost"
         },
         "error_loading_user_profile": "Nepovedlo se načíst profil uživatele",
-        "forget_room_failed": "Nepodařilo se zapomenout místnost %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Server může být nedostupný, přetížený nebo vyhledávání vypršelo :(",
-            "title": "Vyhledávání selhalo"
-        }
+        "forget_room_failed": "Nepodařilo se zapomenout místnost %(errCode)s"
     },
     "error_user_not_logged_in": "Uživatel není přihlášen",
     "event_preview": {
@@ -1056,7 +1153,15 @@
             "you": "Reagovali jste %(reaction)s na %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Zvuk",
+            "file": "Soubor",
+            "image": "Obrázek",
+            "poll": "Anketa",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s: </bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Export zrušen",
@@ -1148,6 +1253,7 @@
         "change": "Změnit server identit",
         "change_prompt": "Odpojit se ze serveru <current /> a připojit na <new />?",
         "change_server_prompt": "Pokud nechcete na hledání existujících kontaktů používat server <server />, zvolte si jiný server.",
+        "changed": "Váš server identit byl změněn",
         "checking": "Kontrolování serveru",
         "description_connected": "Pro hledání existujících kontaktů používáte server identit <server></server>. Níže ho můžete změnit.",
         "description_disconnected": "Pro hledání existujících kontaktů nepoužíváte žádný server identit <server></server>. Abyste mohli hledat kontakty, nějaký níže nastavte.",
@@ -1179,7 +1285,20 @@
         "other": "V %(spaceName)s a %(count)s ostatních prostorech."
     },
     "incompatible_browser": {
-        "title": "Nepodporovaný prohlížeč"
+        "continue": "Přesto pokračovat",
+        "description": "%(brand)s používá některé funkce prohlížeče, které nejsou k dispozici ve vašem aktuálním prohlížeči. %(detail)s",
+        "detail_can_continue": "Pokud budete pokračovat, některé funkce mohou přestat fungovat a existuje riziko, že v budoucnu můžete ztratit data.",
+        "detail_no_continue": "Zkuste tento prohlížeč aktualizovat, pokud nepoužíváte nejnovější verzi, a zkuste to znovu.",
+        "learn_more": "Zjistěte více",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Pro nejlepší zážitek použijte <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge> nebo <Safari>Safari</Safari>.",
+        "title": "Nepodporovaný prohlížeč",
+        "use_desktop_heading": "Místo toho použijte %(brand)s Desktop",
+        "use_mobile_heading": "Místo toho použijte %(brand)s na mobilu",
+        "use_mobile_heading_after_desktop": "Nebo použijte naši mobilní aplikaci",
+        "windows_64bit": "Windows (64-bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
     },
     "info_tooltip_title": "Informace",
     "integration_manager": {
@@ -1188,6 +1307,7 @@
         "error_connecting_heading": "Nepovedlo se připojení ke správci integrací",
         "explainer": "Správci integrace přijímají konfigurační data a mohou vaším jménem upravovat widgety, odesílat pozvánky do místností a nastavovat úrovně oprávnění.",
         "manage_title": "Správa integrací",
+        "toggle_label": "Povolit správce integrací",
         "use_im": "Použít správce integrací na správu botů, widgetů a nálepek.",
         "use_im_default": "Použít správce integrací <b>(%(serverName)s)</b> na správu botů, widgetů a nálepek."
     },
@@ -1303,12 +1423,14 @@
         "navigate_next_message_edit": "Přejít na následující zprávu, kterou chcete upravit",
         "navigate_prev_history": "Předchozí nedávno navštívená místnost nebo prostor",
         "navigate_prev_message_edit": "Přejít na předchozí zprávu, kterou chcete upravit",
+        "next_landmark": "Přejít na další orientační bod",
         "next_room": "Následující místnost nebo přímá zpráva",
         "next_unread_room": "Následující nepřečtená místnost nebo přímá zpráva",
         "number": "[číslo]",
         "open_user_settings": "Otevřít nastavení uživatele",
         "page_down": "O stránku dolů",
         "page_up": "Stránka nahoru",
+        "prev_landmark": "Přejít na předchozí orientační bod",
         "prev_room": "Předchozí místnost nebo přímá zpráva",
         "prev_unread_room": "Předchozí nepřečtená místnost nebo přímá zpráva",
         "room_list_collapse_section": "Sbalit seznam místností",
@@ -1353,8 +1475,11 @@
         "dynamic_room_predecessors": "Předchůdci dynamické místnosti",
         "dynamic_room_predecessors_description": "Povolit MSC3946 (podpora pozdních archivů místností)",
         "element_call_video_rooms": "Element Call video místnosti",
+        "exclude_insecure_devices": "Vyloučit nezabezpečená zařízení při odesílání/přijímání zpráv",
+        "exclude_insecure_devices_description": "Pokud je tento režim povolen, šifrované zprávy nebudou sdíleny s neověřenými zařízeními a zprávy z neověřených zařízení se zobrazí jako chyba. Upozorňujeme, že pokud tento režim povolíte, může se stát, že nebudete moci komunikovat s uživateli, kteří svá zařízení neověřili.",
         "experimental_description": "Rádi experimentujete? Vyzkoušejte naše nejnovější nápady ve vývoji. Tyto funkce nejsou dokončeny; mohou být nestabilní, mohou se změnit nebo mohou být zcela vypuštěny. <a>Zjistěte více</a>.",
         "experimental_section": "Předběžné ukázky",
+        "extended_profiles_msc_support": "Vyžaduje, aby váš server podporoval MSC4133",
         "feature_disable_call_per_sender_encryption": "Zakázat šifrování podle odesílatele pro Element Call",
         "feature_wysiwyg_composer_description": "V editoru zpráv použít formátovaný text namísto Markdown.",
         "group_calls": "Nový zážitek ze skupinových hovorů",
@@ -1368,6 +1493,7 @@
         "group_spaces": "Prostory",
         "group_themes": "Motivy vzhledu",
         "group_threads": "Vlákna",
+        "group_ui": "Uživatelské rozhraní",
         "group_voip": "Zvuk a video",
         "group_widgets": "Widgety",
         "hidebold": "Skrýt tečku oznámení (zobrazit pouze odznaky čítačů)",
@@ -1383,10 +1509,12 @@
         "location_share_live_description": "Dočasná implementace. Polohy zůstanou v historii místností.",
         "mjolnir": "Nové způsoby ignorování lidí",
         "msc3531_hide_messages_pending_moderation": "Umožnit moderátorům skrývat zprávy čekající na moderaci.",
+        "new_room_list": "Povolit nový seznam místností",
         "notification_settings": "Nová nastavení oznámení",
         "notification_settings_beta_caption": "Představujeme jednodušší způsob, jak změnit nastavení oznámení. Přizpůsobte svůj %(brand)s přesně tak, jak se vám líbí.",
         "notification_settings_beta_title": "Nastavení oznámení",
         "notifications": "Zapnout panel oznámení v záhlaví místnosti",
+        "release_announcement": "Oznámení o vydání",
         "render_reaction_images": "Vykreslování vlastních obrázků v reakcích",
         "render_reaction_images_description": "Někdy se označují jako \"vlastní emoji\".",
         "report_to_moderators": "Nahlásit moderátorům",
@@ -1394,7 +1522,7 @@
         "sliding_sync": "Režim klouzavé synchronizace",
         "sliding_sync_description": "V aktivním vývoji, nelze zakázat.",
         "sliding_sync_disabled_notice": "Pro vypnutí se odhlaste a znovu přihlaste",
-        "sliding_sync_server_no_support": "Váš server nemá nativní podporu",
+        "sliding_sync_server_no_support": "Váš server nemá podporu",
         "under_active_development": "V aktivním vývoji.",
         "unrealiable_e2e": "Nespolehlivé v šifrovaných místnostech",
         "video_rooms": "Video místnosti",
@@ -1446,6 +1574,8 @@
         "last_person_warning": "Jste zde jediná osoba. Pokud odejdete, nikdo se v budoucnu nebude moci připojit, včetně vás.",
         "leave_room_question": "Opravdu chcete opustit místnost '%(roomName)s'?",
         "leave_space_question": "Opravdu chcete opustit prostor '%(spaceName)s'?",
+        "room_leave_admin_warning": "V této místnosti jste jediný správce. Pokud odejdete, nikdo nebude moci měnit nastavení místnosti ani provádět jiné důležité akce.",
+        "room_leave_mod_warning": "V této místnosti jste jediný moderátor. Pokud odejdete, nikdo nebude moci měnit nastavení místnosti ani provádět jiné důležité akce.",
         "room_rejoin_warning": "Tato místnost není veřejná. Bez pozvánky nebudete moci znovu vstoupit.",
         "space_rejoin_warning": "Tento prostor není veřejný. Bez pozvánky se nebudete moci znovu připojit."
     },
@@ -1503,8 +1633,15 @@
         "toggle_attribution": "Přepnout atribut"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s člen",
+            "few": "%(count)s členové",
+            "other": "%(count)s členů"
+        },
         "filter_placeholder": "Najít člena místnosti",
         "invite_button_no_perms_tooltip": "Nemáte oprávnění zvát uživatele",
+        "invited_label": "Pozván",
+        "no_matches": "Žádné shody",
         "power_label": "%(userName)s (oprávnění %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Členové místnosti",
@@ -1527,6 +1664,7 @@
         "class_global": "Globální",
         "class_other": "Další možnosti",
         "default": "Výchozí",
+        "default_settings": "Shoda výchozího nastavení",
         "email_pusher_app_display_name": "E-mailová oznámení",
         "enable_prompt_toast_description": "Povolit oznámení na ploše",
         "enable_prompt_toast_title": "Oznámení",
@@ -1545,7 +1683,8 @@
         "mentions_and_keywords_description": "Dostávat oznámení pouze o zmínkách a klíčových slovech podle <a>nastavení</a>",
         "mentions_keywords": "Zmínky a klíčová slova",
         "message_didnt_send": "Zpráva se neodeslala. Klikněte pro informace.",
-        "mute_description": "Nebudete dostávat žádná oznámení"
+        "mute_description": "Nebudete dostávat žádná oznámení",
+        "mute_room": "Ztlumit místnost"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s žádá o ověření"
@@ -1647,11 +1786,6 @@
         "ongoing": "Odstaňování…",
         "reason_label": "Důvod (volitelné)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Opravdu chcete odmítnout pozvání?",
-        "failed": "Nepodařilo se odmítnout pozvání",
-        "title": "Odmítnout pozvání"
-    },
     "report_content": {
         "description": "Nahlášení této zprávy pošle její jedinečné 'event ID' správci vašeho domovského serveru. Pokud jsou zprávy šifrované, správce nebude mít možnost přečíst text zprávy ani se podívat na soubory nebo obrázky.",
         "disagree": "Nesouhlasím",
@@ -1674,38 +1808,66 @@
         "spam_or_propaganda": "Spam nebo propaganda",
         "toxic_behaviour": "Nevhodné chování"
     },
+    "report_room": {
+        "description": "Nahlaste tuto místnost svému poskytovateli účtu. Pokud jsou zprávy zašifrované, váš administrátor je nebude moci číst.",
+        "reason_label": "Popište důvod"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Nepovedlo se rozšifrovat %(failedCount)s sezení!",
         "count_of_successfully_restored_keys": "Úspěšně obnoveno %(sessionCount)s klíčů",
-        "enter_key_description": "Vstupte do historie zabezpečených zpráv a nastavte zabezpečené zprávy zadáním bezpečnostního klíče.",
-        "enter_key_title": "Zadejte bezpečnostní klíč",
+        "enter_key_description": "Získejte přístup k historii zabezpečených zpráv a nastavte zabezpečené zasílání zpráv zadáním klíče pro obnovení.",
+        "enter_key_title": "Zadejte klíč pro obnovení",
         "enter_phrase_description": "Vstupte do historie zabezpečených zpráv a nastavte zabezpečené zprávy zadáním bezpečnostní fráze.",
         "enter_phrase_title": "Zadejte bezpečnostní frázi",
         "incorrect_security_phrase_dialog": "Zálohu nebylo možné dešifrovat pomocí této bezpečnostní fráze: ověřte, zda jste zadali správnou bezpečnostní frázi.",
         "incorrect_security_phrase_title": "Nesprávná bezpečnostní fráze",
         "key_backup_warning": "<b>Uporoznění</b>: záloha by měla být prováděna na důvěryhodném počítači.",
         "key_fetch_in_progress": "Načítání klíčů ze serveru…",
-        "key_forgotten_text": "Pokud jste zapomněli bezpečnostní klíč, můžete <button>nastavit nové možnosti obnovení</button>",
-        "key_is_invalid": "Neplatný bezpečnostní klíč",
-        "key_is_valid": "Vypadá to jako platný bezpečnostní klíč!",
+        "key_forgotten_text": "Pokud jste zapomněli klíč pro obnovení, můžete <button>nastavit nové možnosti obnovení</button>",
+        "key_is_invalid": "Neplatný klíč pro obnovení",
+        "key_is_valid": "Vypadá to jako platný klíč pro obnovení!",
         "keys_restored_title": "Klíče byly obnoveny",
         "load_error_content": "Nepovedlo se načíst stav zálohy",
         "load_keys_progress": "Obnoveno %(completed)s z %(total)s klíčů",
         "no_backup_error": "Nenalezli jsme žádnou zálohu!",
-        "phrase_forgotten_text": "Pokud jste zapomněli bezpečnostní frázi, můžete <button1>použít bezpečnostní klíč</button1> nebo <button2>nastavit nové možnosti obnovení</button2>",
-        "recovery_key_mismatch_description": "Zálohu nebylo možné dešifrovat pomocí tohoto bezpečnostního klíče: ověřte, zda jste zadali správný bezpečnostní klíč.",
-        "recovery_key_mismatch_title": "Neshoda bezpečnostního klíče",
+        "phrase_forgotten_text": "Pokud jste zapomněli bezpečnostní frázi, můžete <button1>použít klíč pro obnovení</button1> nebo <button2>nastavit nové možnosti obnovení</button2>",
+        "recovery_key_mismatch_description": "Zálohu se nepodařilo dešifrovat pomocí tohoto klíče pro obnovení: ověřte, zda jste zadali správný klíč pro obnovení.",
+        "recovery_key_mismatch_title": "Neshoda klíče pro obnovení",
         "restore_failed_error": "Nepovedlo se obnovit ze zálohy"
     },
     "right_panel": {
-        "add_integrations": "Přidat widgety, propojení a boty",
+        "add_integrations": "Přidejte rozšíření",
+        "add_topic": "Přidat téma",
+        "extensions_button": "Rozšíření",
+        "extensions_empty_description": "Vyberte \"%(addIntegrations)s\" pro procházení a přidávání rozšíření do této místnosti",
+        "extensions_empty_title": "Zvyšte produktivitu pomocí více nástrojů, widgetů a robotů",
         "files_button": "Soubory",
         "pinned_messages": {
+            "empty_description": "Vyberte zprávu a zvolte \"%(pinAction)s\", abyste ji sem zahrnuli.",
+            "empty_title": "Připněte důležité zprávy, aby je bylo možné snadno najít",
+            "header": {
+                "one": "1 Připnutá zpráva",
+                "few": "%(count)s Připnuté zprávy",
+                "other": "%(count)s Připnutých zpráv"
+            },
             "limits": {
                 "other": "Můžete připnout až %(count)s widgetů"
-            }
+            },
+            "menu": "Otevřít menu",
+            "release_announcement": {
+                "close": "OK",
+                "description": "Všechny připnuté zprávy najdete zde. Přejděte na libovolnou zprávu a výběrem možnosti \"Připnout\" ji přidejte.",
+                "title": "Všechny nové připnuté zprávy"
+            },
+            "reply_thread": "Odpověď na <link>vlákno zprávy</link>",
+            "unpin_all": {
+                "button": "Odepnout všechny zprávy",
+                "content": "Ujistěte se, že opravdu chcete odstranit všechny připnuté zprávy. Tuto akci nelze vrátit zpět.",
+                "title": "Odepnout všechny zprávy?"
+            },
+            "view": "Zobrazit na časové ose"
         },
-        "pinned_messages_button": "Připnuto",
+        "pinned_messages_button": "Připnuté zprávy",
         "poll": {
             "active_heading": "Aktivní hlasování",
             "empty_active": "V této místnosti nejsou žádná aktivní hlasování",
@@ -1730,7 +1892,7 @@
             "view_in_timeline": "Zobrazit hlasování na časové ose",
             "view_poll": "Zobrazit hlasování"
         },
-        "polls_button": "Historie hlasování",
+        "polls_button": "Ankety",
         "room_summary_card": {
             "title": "Informace o místnosti"
         },
@@ -1763,7 +1925,7 @@
             "notifications_default": "Odpovídá výchozímu nastavení",
             "notifications_mute": "Ztlumit místnost",
             "title": "Možnosti místnosti",
-            "unfavourite": "Oblíbená"
+            "unfavourite": "Oblíbené"
         },
         "creating_room_text": "Vytváříme místnost s %(names)s",
         "dm_invite_action": "Zahájit konverzaci",
@@ -1809,7 +1971,8 @@
             },
             "room_is_public": "Tato místnost je veřejná"
         },
-        "header_face_pile_tooltip": "Přepnout seznam členů",
+        "header_avatar_open_settings_label": "Otevřít nastavení místnosti",
+        "header_face_pile_tooltip": "Lidé",
         "header_untrusted_label": "Nedůvěryhodné",
         "inaccessible": "Tato místnost nebo prostor není v tuto chvíli přístupná.",
         "inaccessible_name": "Místnost %(roomName)s není v tuto chvíli dostupná.",
@@ -1833,10 +1996,9 @@
             "you_created": "Vytvořili jste tuto místnost."
         },
         "invite_email_mismatch_suggestion": "Sdílet tento e-mail v nastavení, abyste mohli dostávat pozvánky přímo v %(brand)su.",
-        "invite_reject_ignore": "Odmítnout a ignorovat uživatele",
         "invite_sent_to_email": "Tato pozvánka byla odeslána na adresu %(email)s",
         "invite_sent_to_email_room": "Pozvánka do %(roomName)s byla odeslána na adresu %(email)s",
-        "invite_subtitle": "<userName/> vás pozval(a)",
+        "invite_subtitle": "Pozván od <userName/>",
         "invite_this_room": "Pozvat do této místnosti",
         "invite_title": "Chcete vstoupit do místnosti %(roomName)s?",
         "inviter_unknown": "Neznámý",
@@ -1879,11 +2041,25 @@
         "not_found_title": "Tato místnost nebo prostor neexistuje.",
         "not_found_title_name": "%(roomName)s neexistuje.",
         "peek_join_prompt": "Nahlížíte do místnosti %(roomName)s. Chcete do ní vstoupit?",
+        "pinned_message_badge": "Připnutá zpráva",
+        "pinned_message_banner": {
+            "button_close_list": "Zavřít seznam",
+            "button_view_all": "Zobrazit vše",
+            "description": "Tato místnost má připnuté zprávy. Kliknutím je zobrazíte.",
+            "go_to_message": "Zobrazit připnutou zprávu na časové ose.",
+            "title": "<bold>%(index)sz%(length)s</bold> Připnuté zprávy"
+        },
         "read_topic": "Klikněte pro přečtení tématu",
         "rejecting": "Odmítání pozvánky…",
         "rejoin_button": "Znovu vstoupit",
         "search": {
             "all_rooms_button": "Vyhledávat ve všech místnostech",
+            "placeholder": "Hledat zprávy…",
+            "summary": {
+                "one": "1 výsledek nalezen pro “<query/>”",
+                "few": "%(count)s výsledky nalezeny pro “<query/>”",
+                "other": "%(count)s výsledků nalezeno pro “<query/>”"
+            },
             "this_room_button": "Vyhledávat v této místnosti"
         },
         "status_bar": {
@@ -1917,38 +2093,90 @@
             },
             "uploading_single_file": "Nahrávání %(filename)s"
         },
+        "video_room": "Tato místnost je video místnost",
         "waiting_for_join_subtitle": "Jakmile se pozvaní uživatelé připojí k %(brand)s, budete moci chatovat a místnost bude koncově šifrovaná",
         "waiting_for_join_title": "Čekání na připojení uživatelů k %(brand)s"
     },
     "room_list": {
         "add_room_label": "Přidat místnost",
         "add_space_label": "Přidat prostor",
+        "appearance": "Vzhled",
         "breadcrumbs_empty": "Žádné nedávno navštívené místnosti",
         "breadcrumbs_label": "Nedávno navštívené místnosti",
+        "empty": {
+            "no_chats": "Zatím žádné chaty",
+            "no_chats_description": "Začněte tím, že někomu pošlete zprávu nebo vytvoříte místnost",
+            "no_chats_description_no_room_rights": "Začněte tím, že někomu pošlete zprávu",
+            "no_favourites": "Zatím nemáte oblíbený chat",
+            "no_favourites_description": "Chat si můžete přidat do oblíbených v nastavení chatu",
+            "no_invites": "Nemáte žádné nepřečtené pozvánky",
+            "no_mentions": "Nemáte žádné nepřečtené zmínky",
+            "no_people": "Zatím s nikým nemáte přímé chaty",
+            "no_people_description": "Můžete zrušit výběr filtrů, abyste viděli ostatní chaty",
+            "no_rooms": "Ještě nejste v žádné místnosti",
+            "no_rooms_description": "Můžete zrušit výběr filtrů, abyste viděli své další chaty",
+            "no_unread": "Gratulujeme! Nemáte žádné nepřečtené zprávy",
+            "show_activity": "Zobrazit veškerou aktivitu",
+            "show_chats": "Zobrazit všechny chaty"
+        },
         "failed_add_tag": "Nepodařilo se přidat štítek %(tagName)s k místnosti",
         "failed_remove_tag": "Nepodařilo se odstranit štítek %(tagName)s z místnosti",
         "failed_set_dm_tag": "Nepodařilo se nastavit značku přímé zprávy",
+        "filters": {
+            "favourite": "Oblíbené",
+            "invites": "Pozvánky",
+            "mentions": "Zmínky",
+            "people": "Lidé",
+            "rooms": "Místnosti",
+            "unread": "Nepřečtené"
+        },
         "home_menu_label": "Možnosti domovské obrazovky",
         "join_public_room_label": "Připojit se k veřejné místnosti",
         "joining_rooms_status": {
             "one": "Momentálně se připojuje %(count)s místnost",
             "other": "Momentálně se připojuje %(count)s místností"
         },
+        "list_title": "Seznam místností",
+        "more_options": {
+            "copy_link": "Kopírovat odkaz na místnost",
+            "favourited": "Oblíbené",
+            "leave_room": "Opustit místnost",
+            "low_priority": "Nízká priorita",
+            "mark_read": "Označit jako přečtené",
+            "mark_unread": "Označit jako nepřečtené"
+        },
         "notification_options": "Možnosti oznámení",
+        "open_space_menu": "Otevřít nabídku prostoru",
+        "primary_filters": "Filtry seznamu místností",
         "redacting_messages_status": {
             "one": "Momentálně se odstraňují zprávy v %(count)s místnosti",
             "other": "Momentálně se odstraňují zprávy v %(count)s místnostech"
         },
+        "room": {
+            "more_options": "Více možností",
+            "open_room": "Otevřít místnost %(roomName)s"
+        },
+        "room_options": "Možnosti místnosti",
         "show_less": "Zobrazit méně",
+        "show_message_previews": "Zobrazit náhledy zpráv",
         "show_n_more": {
             "other": "Zobrazit %(count)s dalších",
             "one": "Zobrazit %(count)s další"
         },
         "show_previews": "Zobrazovat náhledy zpráv",
+        "sort": "Řadit",
         "sort_by": "Řadit dle",
         "sort_by_activity": "Aktivity",
         "sort_by_alphabet": "A–Z",
+        "sort_type": {
+            "activity": "Aktivita",
+            "atoz": "A–Z"
+        },
         "sort_unread_first": "Zobrazovat místnosti s nepřečtenými zprávami jako první",
+        "space_menu": {
+            "home": "Domov prostoru",
+            "space_settings": "Nastavení prostoru"
+        },
         "space_menu_label": "Nabídka pro %(spaceName)s",
         "sublist_options": "Možnosti seznamu",
         "suggested_rooms_heading": "Doporučené místnosti"
@@ -2020,6 +2248,8 @@
             "error_deleting_alias_description": "Při odstraňování adresy došlo k chybě. Adresa již nemusí ekzistovat, nebo mohlo dojít k dočasné chybě.",
             "error_deleting_alias_description_forbidden": "Nemáte oprávnění adresu smazat.",
             "error_deleting_alias_title": "Chyba při odstraňování adresy",
+            "error_publishing": "Místnost nelze publikovat",
+            "error_publishing_detail": "Při publikování této místnosti došlo k chybě",
             "error_save_space_settings": "Nastavení prostoru se nepodařilo uložit.",
             "error_updating_alias_description": "Nepovedlo se změnit alternativní adresy místnosti. Možná to server neumožňuje a nebo je to dočasná chyba.",
             "error_updating_canonical_alias_description": "Nastala chyba při pokusu o nastavení hlavní adresy místnosti. Mohl to zakázat server, nebo to může být dočasná chyba.",
@@ -2175,7 +2405,7 @@
             "public_without_alias_warning": "Přidejte prosím místnosti adresu aby na ní šlo odkazovat.",
             "publish_room": "Zviditelněte tuto místnost ve veřejném adresáři místností.",
             "publish_space": "Zviditelněte tento prostor ve veřejném adresáři místností.",
-            "strict_encryption": "Nikdy v této místnosti neposílat šifrované zprávy neověřeným relacím",
+            "strict_encryption": "Odesílat zprávy pouze ověřeným uživatelům.",
             "title": "Zabezpečení a soukromí"
         },
         "title": "Nastavení místnosti - %(roomName)s",
@@ -2229,6 +2459,10 @@
         "recent_changes_heading": "Nedávné změny, které dosud nebyly přijaty",
         "title": "Server neodpovídá"
     },
+    "service_worker_error": {
+        "description": "%(brand)s vyžaduje service worker pro načítání ověřených médií z úložiště obsahu Matrixu. Váš prohlížeč to nepodporuje, takže může dojít k selhání načtení médií.",
+        "title": "Nepodařilo se načíst service worker"
+    },
     "seshat": {
         "error_initialising": "Inicializace vyhledávání zpráv se nezdařila, zkontrolujte <a>svá nastavení</a>",
         "reset_button": "Resetovat úložiště událostí",
@@ -2246,6 +2480,7 @@
             "brand_version": "Verze %(brand)s:",
             "clear_cache_reload": "Smazat mezipaměť a načíst znovu",
             "crypto_version": "Verze kryptografie:",
+            "dialog_title": "<strong>Nastavení:</strong> Nápověda a informace",
             "help_link": "Pro pomoc s používáním %(brand)su klepněte <a>sem</a>.",
             "homeserver": "Domovský server je <code>%(homeserverUrl)s</code>",
             "identity_server": "Server identit je <code>%(identityServerUrl)s</code>",
@@ -2254,19 +2489,30 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Nastavení:</strong> Účet",
+            "title": "Účet"
+        },
         "all_rooms_home": "Zobrazit všechny místnosti v Domovu",
         "all_rooms_home_description": "Všechny místnosti, ve kterých se nacházíte, se zobrazí v Domovu.",
         "always_show_message_timestamps": "Vždy zobrazovat časové značky zpráv",
         "appearance": {
             "bundled_emoji_font": "Použít přibalené písmo emodži",
+            "compact_layout": "Zobrazení kompaktního textu a zpráv",
+            "compact_layout_description": "Pro použití této funkce je nutné zvolit moderní rozložení.",
             "custom_font": "Používat systémové nastavení písma",
             "custom_font_description": "Zadejte jméno písma, které máte naistalované v systému, a %(brand)s se jej pokusí použít.",
             "custom_font_name": "Jméno systémového písma",
             "custom_font_size": "Použít vlastní velikost",
-            "custom_theme_error_downloading": "Nepovedlo se stáhnout informace o vzhledu.",
+            "custom_theme_add": "Přidat vlastní motiv",
+            "custom_theme_downloading": "Stahování vlastního motivu…",
+            "custom_theme_error_downloading": "Chyba při stahování motivu",
+            "custom_theme_help": "Zadejte adresu URL vlastního motivu, který chcete použít.",
             "custom_theme_invalid": "Neplatné schéma vzhledu.",
+            "dialog_title": "<strong>Nastavení:</strong> Vzhled",
             "font_size": "Velikost písma",
             "font_size_default": "%(fontSize)s (výchozí)",
+            "high_contrast": "Vysoký kontrast",
             "image_size_default": "Výchozí",
             "image_size_large": "Velký",
             "layout_bubbles": "Bubliny zpráv",
@@ -2281,9 +2527,80 @@
         "code_block_expand_default": "Ve výchozím nastavení rozbalit bloky kódu",
         "code_block_line_numbers": "Zobrazit čísla řádků v blocích kódu",
         "disable_historical_profile": "Zobrazit aktuální profilové obrázky a jména uživatelů v historii zpráv",
+        "discovery": {
+            "title": "Jak vás najít"
+        },
         "emoji_autocomplete": "Napovídat emoji",
         "enable_markdown": "Povolit Markdown",
         "enable_markdown_description": "Začněte zprávy s <code>/plain</code> pro odeslání bez markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Podrobnosti o vašem účtu, kontakty, předvolby a seznam chatů budou zachovány",
+                "breadcrumb_page": "Obnovit šifrování",
+                "breadcrumb_second_description": "Ztratíte veškerou historii zpráv, která je uložena pouze na serveru",
+                "breadcrumb_third_description": "Budete muset znovu ověřit všechna svá stávající zařízení a kontakty",
+                "breadcrumb_title": "Opravdu chcete obnovit svou identitu?",
+                "breadcrumb_title_forgot": "Zapomněli jste klíč pro obnovení? Budete muset obnovit svou identitu.",
+                "breadcrumb_title_sync_failed": "Synchronizace úložiště klíčů se nezdařila. Musíte obnovit svou identitu.",
+                "breadcrumb_warning": "Udělejte to pouze v případě, že se domníváte, že váš účet byl napaden.",
+                "details_title": "Podrobnosti o šifrování",
+                "do_not_close_warning": "Nezavírejte toto okno, dokud není resetování dokončeno",
+                "export_keys": "Exportovat klíče",
+                "import_keys": "Importovat klíče",
+                "other_people_device_description": "Upozornění: Uživatelé, kteří s vámi výslovně neověřili (např. pomocí emotikonů), nedostanou vaše šifrované zprávy. Také neověřená zařízení ověřených uživatelů nebudou přijímat vaše šifrované zprávy.",
+                "other_people_device_label": "V šifrovaných místnostech posílat zprávy pouze ověřeným uživatelům",
+                "other_people_device_title": "Zařízení ostatních uživatelů",
+                "reset_identity": "Obnovit kryptografickou identitu",
+                "reset_in_progress": "Probíhá resetování...",
+                "session_id": "ID relace:",
+                "session_key": "Klíč relace:",
+                "title": "Rozšířené"
+            },
+            "confirm_key_storage_off": "Opravdu chcete ponechat úložiště klíčů vypnuté?",
+            "confirm_key_storage_off_description": "Pokud se odhlásíte ze všech svých zařízení, ztratíte historii zpráv a budete muset znovu ověřit všechny své stávající kontakty. <a>Další informace</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Smazat úložiště klíčů",
+                "confirm": "Smazat úložiště klíčů",
+                "description": "Smazáním úložiště klíčů odstraníte ze serveru vaši kryptografickou identitu a klíče zpráv a vypnete následující funkce zabezpečení:",
+                "list_first": "Na nových zařízeních nebudete mít šifrovanou historii zpráv",
+                "list_second": "Pokud se všude odhlásíte z %(brand)s, ztratíte přístup ke svým zašifrovaným zprávám",
+                "title": "Opravdu chcete vypnout úložiště klíčů a smazat jej?"
+            },
+            "device_not_verified_button": "Ověřte toto zařízení",
+            "device_not_verified_description": "Abyste mohli zobrazit nastavení šifrování, musíte toto zařízení ověřit.",
+            "device_not_verified_title": "Zařízení nebylo ověřeno",
+            "dialog_title": "<strong>Nastavení: </strong> Šifrování",
+            "key_storage": {
+                "allow_key_storage": "Povolit úložiště klíčů",
+                "description": "Uložte svou kryptografickou identitu a klíče zpráv bezpečně na serveru. To vám umožní zobrazit historii zpráv na všech nových zařízeních. <a>Další informace</a>",
+                "title": "Úložiště klíčů"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Potvrďte nový klíč pro obnovení",
+                "change_recovery_confirm_description": "Pro dokončení zadejte svůj nový klíč pro obnovení níže. Váš starý klíč již nebude fungovat.",
+                "change_recovery_confirm_title": "Zadejte nový klíč pro obnovení",
+                "change_recovery_key": "Změnit klíč pro obnovení",
+                "change_recovery_key_description": "Zapište si tento nový klíč pro obnovení na bezpečné místo. Poté klikněte na tlačítko Pokračovat a potvrďte změnu.",
+                "change_recovery_key_title": "Změnit klíč pro obnovení?",
+                "description": "Pokud jste ztratili všechna stávající zařízení, obnovte kryptografickou identitu a historii zpráv pomocí klíče pro obnovení.",
+                "enter_key_error": "Zadaný klíč pro obnovení není správný.",
+                "enter_recovery_key": "Zadejte klíč pro obnovení",
+                "forgot_recovery_key": "Zapomněli jste klíč pro obnovení?",
+                "key_storage_warning": "Vaše úložiště klíčů není synchronizováno. Klikněte na jedno z níže uvedených tlačítek a problém vyřešte.",
+                "save_key_description": "Nesdílejte ho s nikým!",
+                "save_key_title": "Klíč pro obnovení",
+                "set_up_recovery": "Nastavení obnovy",
+                "set_up_recovery_confirm_button": "Dokončete nastavení",
+                "set_up_recovery_confirm_description": "Zadejte obnovovací klíč zobrazený na předchozí obrazovce a dokončete nastavení obnovy.",
+                "set_up_recovery_confirm_title": "Pro potvrzení zadejte klíč pro obnovení",
+                "set_up_recovery_description": "Vaše úložiště klíčů je chráněno klíčem pro obnovení. Pokud potřebujete nový klíč pro obnovení po nastavení, můžete jej znovu vytvořit výběrem '%(changeRecoveryKeyButton)s'.",
+                "set_up_recovery_save_key_description": "Tento klíč pro obnovení si zapište na bezpečné místo, jako je správce hesel, šifrovaná poznámka nebo fyzický trezor.",
+                "set_up_recovery_save_key_title": "Uložte si klíč pro obnovení na bezpečném místě",
+                "set_up_recovery_secondary_description": "Po kliknutí na Pokračovat vám vygenerujeme obnovovací klíč.",
+                "title": "Obnovení"
+            },
+            "title": "Šifrování"
+        },
         "general": {
             "account_management_section": "Správa účtu",
             "account_section": "Účet",
@@ -2296,6 +2613,14 @@
             "add_msisdn_dialog_title": "Přidat telefonní číslo",
             "add_msisdn_instructions": "SMS zpráva byla odeslána na +%(msisdn)s. Zadejte prosím ověřovací kód, který obsahuje.",
             "add_msisdn_misconfigured": "Přidání/připojení s MSISDN je nesprávně nakonfigurováno",
+            "allow_spellcheck": "Povolit kontrolu pravopisu",
+            "application_language": "Jazyk aplikace",
+            "application_language_reload_hint": "Po výběru jiného jazyka se aplikace znovu načte.",
+            "avatar_remove_progress": "Odstranění obrázku...",
+            "avatar_save_progress": "Nahrávání obrázku...",
+            "avatar_upload_error_text": "Formát souboru není podporován nebo je obrázek větší než %(size)s.",
+            "avatar_upload_error_text_generic": "Formát souboru nemusí být podporován.",
+            "avatar_upload_error_title": "Obrázek avatara nelze nahrát",
             "confirm_adding_email_body": "Kliknutím na tlačítko potvrdíte přidání emailové adresy.",
             "confirm_adding_email_title": "Potvrdit přidání emailu",
             "deactivate_confirm_body": "Opravdu chcete deaktivovat účet? Je to nevratné.",
@@ -2311,10 +2636,14 @@
             "deactivate_confirm_erase_label": "Skrýt mé zprávy před novými uživateli",
             "deactivate_section": "Deaktivovat účet",
             "deactivate_warning": "Deaktivace účtu je trvalá akce - buďte opatrní!",
-            "discovery_email_empty": "Možnosti nastavení veřejného profilu se objeví po přidání e-mailové adresy výše.",
+            "discovery_email_empty": "Po přidání e-mailu se zobrazí možnosti zjišťování.",
             "discovery_email_verification_instructions": "Ověřte odkaz v e-mailové schránce",
-            "discovery_msisdn_empty": "Možnosti nastavení veřejného profilu se objeví po přidání telefonního čísla výše.",
+            "discovery_msisdn_empty": "Možnosti vyhledávání se zobrazí, jakmile přidáte telefonní číslo.",
             "discovery_needs_terms": "Pro zapsáním do registru e-mailových adres a telefonních čísel odsouhlaste podmínky používání serveru (%(serverName)s).",
+            "discovery_needs_terms_title": "Umožněte lidem, aby vás našli",
+            "display_name": "Zobrazovaný název",
+            "display_name_error": "Nelze nastavit zobrazovaný název",
+            "email_adding_unsupported_by_hs": "Tento domovský server nepodporuje přidávání e-mailových adres do vašeho účtu.",
             "email_address_in_use": "Tato e-mailová adresa je již používána",
             "email_address_label": "E-mailová adresa",
             "email_not_verified": "Vaše e-mailová adresa ještě nebyla ověřena",
@@ -2339,7 +2668,9 @@
             "error_share_msisdn_discovery": "Nepovedlo se nasdílet telefonní číslo",
             "identity_server_no_token": "Nebyl nalezen žádný přístupový token identity",
             "identity_server_not_set": "Server identit není nastaven",
-            "language_section": "Jazyk a region",
+            "invalid_phone_number": "Zadané telefonní číslo se nezdá být platné.",
+            "language_section": "Jazyk",
+            "msisdn_adding_unsupported_by_hs": "Tento domovský server nepodporuje přidávání telefonních čísel do vašeho účtu.",
             "msisdn_in_use": "Toto telefonní číslo je již používáno",
             "msisdn_label": "Telefonní číslo",
             "msisdn_verification_field_label": "Ověřovací kód",
@@ -2348,11 +2679,16 @@
             "oidc_manage_button": "Spravovat účet",
             "password_change_section": "Nastavení nového hesla k účtu…",
             "password_change_success": "Vaše heslo bylo úspěšně změněno.",
+            "personal_info": "Osobní informace",
+            "profile_subtitle": "Takto se v aplikaci zobrazujete ostatním.",
+            "profile_subtitle_oidc": "Váš účet je spravován samostatně poskytovatelem identit, a proto zde nelze některé vaše osobní údaje měnit.",
             "remove_email_prompt": "Odstranit adresu %(email)s?",
             "remove_msisdn_prompt": "Odstranit %(phone)s?",
-            "spell_check_locale_placeholder": "Zvolte jazyk"
+            "spell_check_locale_placeholder": "Zvolte jazyk",
+            "unable_to_load_emails": "Nelze načíst e-mailové adresy",
+            "unable_to_load_msisdns": "Nelze načíst telefonní čísla",
+            "username": "Uživatelské jméno"
         },
-        "image_thumbnails": "Zobrazovat náhledy obrázků",
         "inline_url_previews_default": "Nastavit povolení náhledů URL adres jako výchozí",
         "inline_url_previews_room": "Povolit náhledy URL adres pro členy této místnosti jako výchozí",
         "inline_url_previews_room_account": "Povolit náhledy URL adres pro tuto místnost (ovlivňuje pouze vás)",
@@ -2374,21 +2710,21 @@
                 "enter_phrase_description": "Zadejte bezpečnostní frázi, kterou znáte jen vy, protože slouží k ochraně vašich dat. V zájmu bezpečnosti byste neměli heslo k účtu používat opakovaně.",
                 "enter_phrase_title": "Zadání bezpečnostní fráze",
                 "enter_phrase_to_confirm": "Zadejte bezpečnostní frázi podruhé a potvrďte ji.",
-                "generate_security_key_description": "Vygenerujeme vám bezpečnostní klíč, který uložíte na bezpečné místo, například do správce hesel nebo do trezoru.",
-                "generate_security_key_title": "Vygenerovat bezpečnostní klíč",
+                "generate_security_key_description": "Vygenerujeme klíč pro obnovení, který můžete uložit někde v bezpečí, jako je správce hesel nebo trezor.",
+                "generate_security_key_title": "Vygenerujte klíč pro obnovení",
                 "pass_phrase_match_failed": "To nesedí.",
                 "pass_phrase_match_success": "To odpovídá!",
                 "phrase_strong_enough": "Skvělé! Tato bezpečnostní fráze vypadá dostatečně silně.",
                 "secret_storage_query_failure": "Nelze zjistit stav úložiště klíčů",
-                "security_key_safety_reminder": "Bezpečnostní klíč uložte na bezpečné místo, například do správce hesel nebo do trezoru, protože slouží k ochraně zašifrovaných dat.",
+                "security_key_safety_reminder": "Klíč pro obnovení uložte na bezpečném místě, jako je správce hesel nebo trezor, protože slouží k ochraně vašich šifrovaných dat.",
                 "set_phrase_again": "Nastavit heslo znovu.",
                 "settings_reminder": "Zabezpečené zálohování a správu klíčů můžete také nastavit v Nastavení.",
                 "title_confirm_phrase": "Potvrďte bezpečnostní frázi",
-                "title_save_key": "Uložte svůj bezpečnostní klíč",
+                "title_save_key": "Uložte si klíč pro obnovení",
                 "title_set_phrase": "Nastavit bezpečnostní frázi",
                 "unable_to_setup": "Nepovedlo se nastavit bezpečné úložiště",
                 "use_different_passphrase": "Použít jinou přístupovou frázi?",
-                "use_phrase_only_you_know": "Použijte tajnou frázi, kterou znáte pouze vy, a volitelně uložte bezpečnostní klíč, který použijete pro zálohování."
+                "use_phrase_only_you_know": "Použijte tajnou frázi, kterou znáte pouze vy, a volitelně uložte klíč pro obnovení, který použijete pro zálohování."
             }
         },
         "key_export_import": {
@@ -2406,12 +2742,28 @@
             "phrase_strong_enough": "Skvělé! Tato bezpečnostní fráze vypadá dostatečně silná"
         },
         "keyboard": {
+            "dialog_title": "<strong>Nastavení:</strong> Klávesnice",
             "title": "Klávesnice"
         },
+        "labs": {
+            "dialog_title": "<strong>Nastavení:</strong> Labs"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Nastavení:</strong> Ignorovaní uživatelé"
+        },
+        "media_preview": {
+            "hide_avatars": "Skrýt avatary místnosti a zvoucího",
+            "hide_media": "Vždy skrýt",
+            "media_preview_description": "Skryté médium lze vždy zobrazit klepnutím na něj",
+            "media_preview_label": "Zobrazit média na časové ose",
+            "show_in_private": "V soukromých místnostech",
+            "show_media": "Vždy zobrazit"
+        },
         "notifications": {
             "default_setting_description": "Toto nastavení se ve výchozím stavu použije pro všechny vaše místnosti.",
             "default_setting_section": "Chci být upozorňován na (Výchozí nastavení)",
             "desktop_notification_message_preview": "Zobrazit náhled zprávy v oznámení na ploše",
+            "dialog_title": "<strong>Nastavení:</strong> Oznámení",
             "email_description": "Přijímat e-mailový souhrn zmeškaných oznámení",
             "email_section": "E-mailový souhrn",
             "email_select": "Vyberte e-maily, na které chcete zasílat souhrny. E-maily můžete spravovat v nastavení <button>Obecné</button>.",
@@ -2470,12 +2822,15 @@
             "code_blocks_heading": "Bloky kódu",
             "compact_modern": "Použít kompaktnější \"moderní\" rozložení",
             "composer_heading": "Editor zpráv",
+            "default_timezone": "Výchozí nastavení prohlížeče (%(timezone)s )",
+            "dialog_title": "<strong>Nastavení:</strong> Předvolby",
             "enable_hardware_acceleration": "Povolit hardwarovou akceleraci",
             "enable_tray_icon": "Zobrazit ikonu v oznamovací oblasti a minimalizivat při zavření okna",
             "keyboard_heading": "Klávesové zkratky",
             "keyboard_view_shortcuts_button": "Pro zobrazení všech klávesových zkratek, <a>klikněte zde</a>.",
             "media_heading": "Obrázky, GIFy a videa",
             "presence_description": "Sdílejte své aktivity a stav s ostatními.",
+            "publish_timezone": "Zveřejnit časové pásmo na veřejném profilu",
             "rm_lifetime": "Platnost značky přečteno (ms)",
             "rm_lifetime_offscreen": "Platnost značky přečteno mimo obrazovku (ms)",
             "room_directory_heading": "Adresář místností",
@@ -2483,59 +2838,26 @@
             "show_avatars_pills": "Zobrazovat avatary ve zmínkách o uživatelích, místnostech a událostech",
             "show_polls_button": "Zobrazit tlačítko hlasování",
             "surround_text": "Ohraničit označený text při psaní speciálních znaků",
-            "time_heading": "Zobrazování času"
+            "time_heading": "Zobrazování času",
+            "user_timezone": "Nastavit časové pásmo"
         },
         "prompt_invite": "Potvrdit odeslání pozvánky potenciálně neplatným Matrix ID",
         "replace_plain_emoji": "Automaticky nahrazovat textové emoji",
         "security": {
-            "4s_public_key_in_account_data": "v datech účtu",
-            "4s_public_key_status": "Veřejný klíč bezpečného úložiště:",
             "analytics_description": "Sdílet anonymní údaje, které nám pomohou identifikovat problémy. Nic osobního. Žádné třetí strany.",
-            "backup_key_cached_status": "Klíč zálohy cachován:",
-            "backup_key_stored_status": "Klíč zálohy uložen:",
-            "backup_key_unexpected_type": "neočekávaný typ",
-            "backup_key_well_formed": "ve správném tvaru",
-            "backup_keys_description": "Zálohujte šifrovací klíče s daty účtu pro případ, že ztratíte přístup k relacím. Vaše klíče budou zabezpečeny jedinečným bezpečnostním klíčem.",
             "bulk_options_accept_all_invites": "Přijmout pozvání do všech těchto místností: %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Odmítnutí všech %(invitedRooms)s pozvání",
             "bulk_options_section": "Hromadná možnost",
-            "cross_signing_cached": "uložen lokálně",
-            "cross_signing_homeserver_support": "Funkce podporovaná domovským serverem:",
-            "cross_signing_homeserver_support_exists": "existuje",
-            "cross_signing_in_4s": "v bezpečném úložišti",
-            "cross_signing_in_memory": "v paměti",
-            "cross_signing_master_private_Key": "Hlavní soukromý klíč:",
-            "cross_signing_not_cached": "nenalezen lolálně",
-            "cross_signing_not_found": "nenalezeno",
-            "cross_signing_not_in_4s": "nebylo nalezeno v úložišti",
-            "cross_signing_not_stored": "není uložen",
-            "cross_signing_private_keys": "Soukromé klíče pro křížový podpis:",
-            "cross_signing_public_keys": "Veřejné klíče pro křížový podpis:",
-            "cross_signing_self_signing_private_key": "Vlastní podpisový klíč:",
-            "cross_signing_user_signing_private_key": "Podpisový klíč uživatele:",
-            "cryptography_section": "Šifrování",
-            "delete_backup": "Smazat zálohu",
-            "delete_backup_confirm_description": "Opravdu? Pokud klíče nejsou správně zálohované můžete přijít o šifrované zprávy.",
+            "dehydrated_device_description": "Funkce offline zařízení umožňuje přijímat šifrované zprávy, i když nejste přihlášeni k žádnému zařízení.",
+            "dehydrated_device_enabled": "Offline zařízení povoleno",
+            "dialog_title": "<strong>Nastavení:</strong> Zabezpečení a soukromí",
             "e2ee_default_disabled_warning": "Správce vašeho serveru vypnul ve výchozím nastavení koncové šifrování v soukromých místnostech a přímých zprávách.",
             "enable_message_search": "Povolit vyhledávání v šifrovaných místnostech",
             "encryption_section": "Šifrování",
-            "error_loading_key_backup_status": "Nepovedlo se načíst stav zálohy",
-            "export_megolm_keys": "Exportovat šifrovací klíče místností",
             "ignore_users_empty": "Nemáte žádné ignorované uživatele.",
             "ignore_users_section": "Ignorovaní uživatelé",
-            "import_megolm_keys": "Importovat šifrovací klíče místností",
-            "key_backup_active": "Tato relace zálohuje vaše klíče.",
-            "key_backup_active_version": "Verze aktivní zálohy:",
-            "key_backup_active_version_none": "Žádné",
             "key_backup_algorithm": "Algoritmus:",
-            "key_backup_can_be_restored": "Tuto zálohu lze obnovit v této relaci",
-            "key_backup_complete": "Všechny klíče jsou zazálohované",
             "key_backup_connect": "Připojit k zálohování klíčů",
-            "key_backup_connect_prompt": "Než se odhlásíte, připojte tuto relaci k záloze klíčů, abyste nepřišli o klíče, které mohou být jen v této relaci.",
-            "key_backup_in_progress": "Zálohování %(sessionsRemaining)s klíčů…",
-            "key_backup_inactive": "Tato relace <b>nezálohuje vaše klíče</b>, ale už máte zálohu ze které je můžete obnovit.",
-            "key_backup_inactive_warning": "Vaše klíče <b>nejsou z této relace zálohovány</b>.",
-            "key_backup_latest_version": "Nejnovější verze zálohy na serveru:",
             "message_search_disable_warning": "Když je to zakázané, zprávy v šifrovaných místnostech se nebudou objevovat ve výsledcích vyhledávání.",
             "message_search_disabled": "Bezpečně uchovávat zprávy na tomto zařízení aby se v nich dalo vyhledávat.",
             "message_search_enabled": {
@@ -2555,14 +2877,8 @@
             "message_search_unsupported": "%(brand)su chybí nějaké komponenty, které jsou potřeba pro vyhledávání v zabezpečených místnostech. Pokud chcete s touto funkcí experimentovat, tak si pořiďte vlastní %(brand)s Desktop s <nativeLink>přidanými komponentami</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s nemůže bezpečně ukládat šifrované zprávy lokálně v prohlížeči. Pro zobrazení šifrovaných zpráv ve výsledcích vyhledávání použijte <desktopLink>%(brand)s Desktop</desktopLink>.",
             "record_session_details": "Zaznamenat název, verzi a url pro snadnější rozpoznání relací ve správci relací",
-            "restore_key_backup": "Obnovit ze zálohy",
-            "secret_storage_not_ready": "nepřipraveno",
-            "secret_storage_ready": "připraveno",
-            "secret_storage_status": "Bezpečné úložiště:",
             "send_analytics": "Odesílat analytická data",
-            "session_id": "ID relace:",
-            "session_key": "Klíč relace:",
-            "strict_encryption": "Nikdy neposílat šifrované zprávy do neověřených relací z této relace"
+            "strict_encryption": "Odesílat zprávy pouze ověřeným uživatelům"
         },
         "send_read_receipts": "Odesílat potvrzení o přečtení",
         "send_read_receipts_unsupported": "Váš server nepodporuje vypnutí odesílání potvrzení o přečtení.",
@@ -2583,8 +2899,9 @@
                 "other": "Odhlášení zařízení"
             },
             "confirm_sign_out_sso": {
-                "one": "Potvrďte odhlášení tohoto zařízení pomocí Jednotného přihlášení, abyste prokázali svou totožnost.",
-                "other": "Potvrďte odhlášení těchto zařízení pomocí Jednotného přihlášení, abyste prokázali svou totožnost."
+                "one": "Potvrďte odhlášení tohoto zařízení pomocí Jednotného přihlášení, abyste prokázali svou identitu.",
+                "few": "",
+                "other": "Potvrďte odhlášení těchto zařízení pomocí Jednotného přihlášení, abyste prokázali svou identitu."
             },
             "current_session": "Aktuální relace",
             "desktop_session": "Relace stolního počítače",
@@ -2593,6 +2910,7 @@
             "device_unverified_description_current": "Ověřte svou aktuální relaci pro vylepšené zabezpečené zasílání zpráv.",
             "device_verified_description": "Tato relace je připravena na bezpečné zasílání zpráv.",
             "device_verified_description_current": "Vaše aktuální relace je připravena pro bezpečné zasílání zpráv.",
+            "dialog_title": "<strong>Nastavení:</strong> Relace",
             "error_pusher_state": "Nepodařilo se nastavit stav push oznámení",
             "error_set_name": "Nepodařilo se nastavit název relace",
             "filter_all": "Všechny",
@@ -2609,6 +2927,7 @@
             "inactive_sessions_list_description": "Zvažte odhlášení ze starých relací (%(inactiveAgeDays)s dní nebo starších), které již nepoužíváte.",
             "ip": "IP adresa",
             "last_activity": "Poslední aktivita",
+            "manage": "Spravovat tuto relaci",
             "mobile_session": "Relace mobilního zařízení",
             "n_sessions_selected": {
                 "one": "%(count)s vybraná relace",
@@ -2632,9 +2951,10 @@
             "security_recommendations_description": "Zlepšete zabezpečení svého účtu dodržováním těchto doporučení.",
             "session_id": "ID sezení",
             "show_details": "Zobrazit podrobnosti",
-            "sign_in_with_qr": "Přihlásit se pomocí QR kódu",
+            "sign_in_with_qr": "Připojit nové zařízení",
             "sign_in_with_qr_button": "Zobrazit QR kód",
-            "sign_in_with_qr_description": "Toto zařízení můžete použít k přihlášení nového zařízení pomocí QR kódu. QR kód zobrazený na tomto zařízení musíte naskenovat pomocí odhlášeného zařízení.",
+            "sign_in_with_qr_description": "Pomocí QR kódu se přihlaste do jiného zařízení a nastavte zabezpečené zasílání zpráv.",
+            "sign_in_with_qr_unsupported": "Není podporováno vaším poskytovatelem účtu",
             "sign_out": "Odhlásit se z této relace",
             "sign_out_all_other_sessions": "Odhlásit se ze všech ostatních relací (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2658,7 +2978,7 @@
             "url": "URL",
             "verified_session": "Ověřená relace",
             "verified_sessions": "Ověřené relace",
-            "verified_sessions_explainer_1": "Ověřené relace jsou všude tam, kde tento účet používáte po zadání své přístupové fráze nebo po potvrzení své totožnosti jinou ověřenou relací.",
+            "verified_sessions_explainer_1": "Ověřené relace jsou všude tam, kde tento účet používáte po zadání své přístupové fráze nebo po potvrzení své identity jinou ověřenou relací.",
             "verified_sessions_explainer_2": "To znamená, že máte všechny klíče potřebné k odemknutí zašifrovaných zpráv a potvrzení ostatním uživatelům, že této relaci důvěřujete.",
             "verified_sessions_list_description": "Pro nejlepší zabezpečení se odhlaste z každé relace, kterou již nepoznáváte nebo nepoužíváte.",
             "verify_session": "Ověřit relaci",
@@ -2674,7 +2994,9 @@
         "show_redaction_placeholder": "Zobrazovat smazané zprávy",
         "show_stickers_button": "Tlačítko Zobrazit nálepky",
         "show_typing_notifications": "Zobrazovat oznámení „... právě píše...“",
+        "showbold": "Zobrazit veškerou aktivitu v seznamu místnosti (body nebo počet nepřečtených zpráv).",
         "sidebar": {
+            "dialog_title": "<strong>Nastavení:</strong> Postranní panel",
             "metaspaces_favourites_description": "Seskupte všechny své oblíbené místnosti a osoby na jednom místě.",
             "metaspaces_home_all_rooms": "Zobrazit všechny místnosti",
             "metaspaces_home_all_rooms_description": "Zobrazit všechny místnosti v Domovu, i když jsou v prostoru.",
@@ -2683,10 +3005,14 @@
             "metaspaces_orphans_description": "Seskupte všechny místnosti, které nejsou součástí prostoru, na jednom místě.",
             "metaspaces_people_description": "Seskupte všechny své kontakty na jednom místě.",
             "metaspaces_subsection": "Prostory pro zobrazení",
+            "metaspaces_video_rooms": "Video místnosti a konference",
+            "metaspaces_video_rooms_description": "Seskupte všechny soukromé videomístnosti a konference.",
+            "metaspaces_video_rooms_description_invite_extension": "Na konference můžete pozvat i lidi mimo matrix.",
             "spaces_explainer": "Prostory jsou způsoby, jak seskupit místnosti a lidi. Kromě prostor, ve kterých se nacházíte, můžete použít i některé předem vytvořené.",
             "title": "Postranní panel"
         },
         "start_automatically": "Zahájit automaticky po přihlášení do systému",
+        "tac_only_notifications": "Zobrazení oznámení pouze v centru aktivity vlákna",
         "use_12_hour_format": "Zobrazovat čas v 12hodinovém formátu (např. 2:30 odp.)",
         "use_command_enter_send_message": "K odeslání zprávy použijte Command + Enter",
         "use_command_f_search": "Stiskněte Command + F k vyhledávání v časové ose",
@@ -2700,6 +3026,7 @@
             "audio_output_empty": "Nebyly rozpoznány žádné zvukové výstupy",
             "auto_gain_control": "Automatická úprava zesílení",
             "connection_section": "Připojení",
+            "dialog_title": "<strong>Nastavení:</strong> Hlas a video",
             "echo_cancellation": "Potlačení ozvěny",
             "enable_fallback_ice_server": "Povolit záložní asistenční server hovorů (%(server)s)",
             "enable_fallback_ice_server_description": "Platí pouze v případě, že váš domovský server tuto možnost nenabízí. Vaše IP adresa bude během hovoru sdílena.",
@@ -2718,6 +3045,7 @@
         "warning": "<w>UPOZORNĚNÍ:</w> <description/>"
     },
     "share": {
+        "link_copied": "Odkaz zkopírován",
         "permalink_message": "Odkaz na vybranou zprávu",
         "permalink_most_recent": "Odkaz na nejnovější zprávu",
         "share_call": "Odkaz na pozvánku na konferenci",
@@ -2793,8 +3121,6 @@
         "topic": "Nastaví nebo zjistí téma místnosti",
         "topic_none": "Tato místnost nemá žádné specifické téma.",
         "topic_room_error": "Nepodařilo se získat téma místnosti: Nelze najít místnost (%(roomId)s",
-        "tovirtual": "Přepne do virtuální místnosti této místnosti, pokud ji má",
-        "tovirtual_not_found": "Žádná virtuální místnost pro tuto místnost",
         "unban": "Zruší vykázání uživatele s daným identifikátorem",
         "unflip": "Vloží ┬──┬ ノ( ゜-゜ノ) na začátek zprávy",
         "unholdcall": "Zruší podržení hovoru v aktuální místnosti",
@@ -2812,6 +3138,7 @@
         "view": "Zobrazí místnost s danou adresou",
         "whois": "Zobrazuje informace o uživateli"
     },
+    "sliding_sync_legacy_no_longer_supported": "Starší klouzavá synchronizace již není podporována: odhlaste se a znovu přihlaste, abyste povolili nový příznak klouzavé synchronizace",
     "space": {
         "add_existing_room_space": {
             "create": "Chcete místo toho přidat novou místnost?",
@@ -2847,7 +3174,7 @@
         "invite_link": "Sdílet odkaz na pozvánku",
         "joining_space": "Připojování",
         "landing_welcome": "Vítejte v <name/>",
-        "leave_dialog_action": "Opusit prostor",
+        "leave_dialog_action": "Opustit prostor",
         "leave_dialog_description": "Odcházíte z <spaceName/>.",
         "leave_dialog_only_admin_room_warning": "Jste jediným správcem některých místností nebo prostorů, které chcete opustit. Jejich opuštěním zůstanou bez správců.",
         "leave_dialog_only_admin_warning": "Jste jediným správcem tohoto prostoru. Jeho opuštění bude znamenat, že nad ním nebude mít nikdo kontrolu.",
@@ -2916,7 +3243,7 @@
         "heading_without_query": "Hledat",
         "join_button_text": "Vstoupit do %(roomAddress)s",
         "keyboard_scroll_hint": "K pohybu použijte <arrows/>",
-        "message_search_section_title": "Další vyhledávání",
+        "messages_label": "Zprávy",
         "other_rooms_in_space": "Další místnosti v %(spaceName)s",
         "public_rooms_label": "Veřejné místnosti",
         "public_spaces_label": "Veřejné prostory",
@@ -2926,7 +3253,6 @@
         "result_may_be_hidden_privacy_warning": "Některé výsledky mohou být z důvodu ochrany soukromí skryté",
         "result_may_be_hidden_warning": "Některé výsledky mohou být skryté",
         "search_dialog": "Dialogové okno hledání",
-        "search_messages_hint": "Pro vyhledávání zpráv hledejte tuto ikonu v horní části místnosti <icon/>",
         "spaces_title": "Prostory, ve kterých se nacházíte",
         "start_group_chat_button": "Zahájit skupinový chat"
     },
@@ -2963,14 +3289,19 @@
             "one": "%(count)s odpověď",
             "other": "%(count)s odpovědí"
         },
+        "empty_description": "Při najetí na zprávu použijte \"%(replyInThread)s\".",
+        "empty_title": "Vlákna pomáhají udržovat konverzace k tématu a snadno je sledovat.",
         "error_start_thread_existing_relation": "Nelze založit vlákno ve vlákně",
+        "mark_all_read": "Označit vše jako přečtené",
         "my_threads": "Moje vlákna",
         "my_threads_description": "Zobrazit všechna vlákna, kterých jste se zúčastnili",
         "open_thread": "Otevřít vlákno",
         "show_thread_filter": "Zobrazit:"
     },
     "threads_activity_centre": {
-        "header": "Aktivita vláken"
+        "header": "Aktivita vláken",
+        "no_rooms_with_threads_notifs": "Zatím nemáte k dispozici místnosti s upozorněními na vlákna.",
+        "no_rooms_with_unread_threads": "Zatím nemáte místnosti s nepřečtenými vlákny."
     },
     "time": {
         "about_day_ago": "před jedním dnem",
@@ -3013,9 +3344,21 @@
         },
         "creation_summary_dm": "%(creator)s vytvořil(a) tuto přímou zprávu.",
         "creation_summary_room": "%(creator)s vytvořil(a) a nakonfiguroval(a) místnost.",
+        "decryption_failure": {
+            "blocked": "Odesílatel vám zablokoval příjem této zprávy, protože vaše zařízení není ověřeno.",
+            "historical_event_no_key_backup": "Historické zprávy nejsou v tomto zařízení k dispozici",
+            "historical_event_unverified_device": "Pro přístup k historickým zprávám je třeba toto zařízení ověřit.",
+            "historical_event_user_not_joined": "K této zprávě nemáte přístup",
+            "sender_identity_previously_verified": "Ověřená identita odesílatele se změnila",
+            "sender_unsigned_device": "Šifrováno zařízením, které nebylo ověřeno jeho majitelem.",
+            "unable_to_decrypt": "Zprávu nelze dešifrovat"
+        },
         "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Dešifrování",
         "download_action_downloading": "Stahování",
+        "download_failed": "Stažení se nezdařilo",
+        "download_failed_description": "Při stahování tohoto souboru došlo k chybě",
+        "e2e_state": "Stav šifrování mezi koncovými body",
         "edits": {
             "tooltip_label": "Upraveno v %(date)s. Klinutím zobrazíte změny.",
             "tooltip_sub": "Klikněte pro zobrazení úprav",
@@ -3069,7 +3412,7 @@
         },
         "m.file": {
             "error_decrypting": "Chyba při dešifrování přílohy",
-            "error_invalid": "Neplatný soubor%(extra)s"
+            "error_invalid": "Neplatný soubor"
         },
         "m.image": {
             "error": "Obrázek nelze zobrazit kvůli chybě",
@@ -3140,7 +3483,7 @@
             "unknown": "%(senderDisplayName)s změnil(a) pravidlo pro přístup hostů na %(rule)s"
         },
         "m.room.history_visibility": {
-            "invited": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich pozvání.",
+            "invited": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy od chvíle jejich pozvání.",
             "joined": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy od chvíle jejich vstupu.",
             "shared": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy.",
             "unknown": "%(senderName)s nastavil viditelnost budoucí zpráv v místnosti neznámým (%(visibility)s).",
@@ -3170,6 +3513,7 @@
             "left_reason": "%(targetName)s opustil(a) místnost: %(reason)s",
             "no_change": "%(senderName)s neprovedl(a) žádnou změnu",
             "reject_invite": "%(targetName)s odmítl(a) pozvání",
+            "reject_invite_reason": "%(targetName)s pozvánku odmítl(a): %(reason)s",
             "remove_avatar": "%(senderName)s odstranil(a) svůj profilový obrázek",
             "remove_name": "%(senderName)s odstranil(a) své zobrazované jméno (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s si nastavil(a) profilový obrázek",
@@ -3205,10 +3549,14 @@
             "sent": "%(senderName)s pozval(a) uživatele %(targetDisplayName)s ke vstupu do místnosti."
         },
         "m.room.tombstone": "%(senderDisplayName)s aktualizoval(a) místnost.",
-        "m.room.topic": "%(senderDisplayName)s změnil(a) téma na „%(topic)s“.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s změnil(a) téma na „%(topic)s“.",
+            "removed": "%(senderDisplayName)s odstranil(a) téma."
+        },
         "m.sticker": "%(senderDisplayName)s poslal(a) nálepku.",
         "m.video": {
-            "error_decrypting": "Chyba při dešifrování videa"
+            "error_decrypting": "Chyba při dešifrování videa",
+            "show_video": "Zobrazit video"
         },
         "m.widget": {
             "added": "%(senderName)s přidal(a) widget %(widgetName)s",
@@ -3256,7 +3604,8 @@
         "reactions": {
             "add_reaction_prompt": "Přidat reakci",
             "custom_reaction_fallback_label": "Vlastní reakce",
-            "label": "%(reactors)s reagoval(a) na %(content)s"
+            "label": "%(reactors)s reagoval(a) na %(content)s",
+            "tooltip_caption": "reagoval s%(shortName)s"
         },
         "read_receipt_title": {
             "one": "Viděl %(count)s člověk",
@@ -3441,6 +3790,10 @@
     "truncated_list_n_more": {
         "other": "A %(count)s dalších..."
     },
+    "unsupported_browser": {
+        "description": "Pokud budete pokračovat, mohou některé funkce přestat fungovat a hrozí, že v budoucnu přijdete o data. Chcete-li pokračovat v používání %(brand)s, aktualizujte svůj prohlížeč.",
+        "title": "%(brand)s nepodporuje tento prohlížeč"
+    },
     "unsupported_server_description": "Tento server používá starší verzi Matrix. Chcete-li používat %(brand)s bez možných problémů, aktualizujte Matrixu na %(version)s .",
     "unsupported_server_title": "Váš server není podporován",
     "update": {
@@ -3458,6 +3811,13 @@
         "toast_title": "Aktualizovat %(brand)s",
         "unavailable": "Nedostupné"
     },
+    "update_room_access_modal": {
+        "description": "Chcete-li vytvořit sdílený odkaz, nastavte tuto místnost <b>veřejnou</b> nebo povolte možnost, aby uživatelé <b>požádali o vstup</b>. To umožňuje hostům připojit se, aniž by byli pozváni.",
+        "dont_change_description": "Pokud nechcete změnit přístup k této místnosti, můžete pro odkaz na hovor vytvořit novou místnost.",
+        "no_change": "Nechci měnit úroveň přístupu.",
+        "revert_access_description": "(Tuto hodnotu lze vrátit na předchozí hodnotu v Nastavení místnosti: <b>Zabezpečení a soukromí</b> / <b>Přístup</b>)",
+        "title": "Povolit hostům vstup do této místnosti"
+    },
     "upload_failed_generic": "Soubor '%(fileName)s' se nepodařilo nahrát.",
     "upload_failed_size": "Soubor '%(fileName)s' je větší než povoluje limit domovského serveru",
     "upload_failed_title": "Nahrávání selhalo",
@@ -3467,6 +3827,7 @@
         "error_files_too_large": "Tyto soubory jsou <b>příliš velké</b>. Limit je %(limit)s.",
         "error_some_files_too_large": "Některé soubory <b>jsou příliš velké</b>. Limit je %(limit)s.",
         "error_title": "Chyba při nahrávání",
+        "not_image": "Soubor, který jste vybrali, není platný soubor obrázku.",
         "title": "Nahrát soubory",
         "title_progress": "Nahrát soubory (%(current)s z %(total)s)",
         "upload_all_button": "Nahrát vše",
@@ -3482,14 +3843,6 @@
         "ban_room_confirm_title": "Vykázat z %(roomName)s",
         "ban_space_everything": "Vykázat je všude, kde mohu",
         "ban_space_specific": "Vykázat je z konkrétních míst, ze kterých jsem schopen",
-        "count_of_sessions": {
-            "other": "%(count)s relací",
-            "one": "%(count)s relace"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s ověřených relací",
-            "one": "1 ověřená relace"
-        },
         "deactivate_confirm_action": "Deaktivovat uživatele",
         "deactivate_confirm_description": "Deaktivování uživatele ho odhlásí a zabrání mu v opětovném přihlášení. Navíc bude odstraněn ze všech místností. Akci nelze vzít zpět. Opravdu chcete uživatele deaktivovat?",
         "deactivate_confirm_title": "Deaktivovat uživatele?",
@@ -3500,15 +3853,13 @@
         "disinvite_button_room": "Zrušit pozvánku do místnosti",
         "disinvite_button_room_name": "Zrušit pozvánku do %(roomName)s",
         "disinvite_button_space": "Zrušit pozvánku do prostoru",
-        "edit_own_devices": "Upravit zařízení",
         "error_ban_user": "Nepodařilo se vykázat uživatele",
         "error_deactivate": "Deaktivace uživatele se nezdařila",
         "error_kicking_user": "Nepodařilo se odebrat uživatele",
         "error_mute_user": "Ztlumení uživatele se nezdařilo",
         "error_revoke_3pid_invite_description": "Pozvání se nepovedlo zrušit. Mohlo dojít k dočasnému problému nebo na to nemáte dostatečná práva.",
         "error_revoke_3pid_invite_title": "Pozvání se nepovedlo zrušit",
-        "hide_sessions": "Skrýt relace",
-        "hide_verified_sessions": "Skrýt ověřené relace",
+        "ignore_button": "Ignorovat",
         "ignore_confirm_description": "Všechny zprávy a pozvánky od tohoto uživatele budou skryty. Opravdu je chcete ignorovat?",
         "ignore_confirm_title": "Ignorovat %(user)s",
         "invited_by": "Pozván od uživatele %(sender)s",
@@ -3536,23 +3887,27 @@
             "no_recent_messages_description": "Zkuste posunout časovou osu nahoru, jestli tam nejsou nějaké dřívější.",
             "no_recent_messages_title": "Nebyly nalezeny žádné nedávné zprávy od uživatele %(user)s"
         },
-        "redact_button": "Odstranit nedávné zprávy",
+        "redact_button": "Odebrat zprávy",
         "revoke_invite": "Zrušit pozvání",
         "room_encrypted": "Zprávy jsou v této místnosti koncově šifrované.",
         "room_encrypted_detail": "Vaše zprávy jsou zabezpečené - pouze vy a jejich příjemci máte klíče potřebné k jejich přečtení.",
         "room_unencrypted": "Zprávy nejsou koncově šifrované.",
         "room_unencrypted_detail": "V šifrovaných místnostech jsou vaše zprávy bezpečné a pouze vy a příjemce má klíče k jejich rozšifrování.",
-        "share_button": "Sdílet odkaz na uživatele",
+        "send_message": "Poslat zprávu",
+        "share_button": "Sdílet profil",
         "unban_button_room": "Zrušit vykázání z místnosti",
         "unban_button_space": "Zrušit vykázání z prostoru",
         "unban_room_confirm_title": "Zrušit vykázání z %(roomName)s",
         "unban_space_everything": "Zrušit jejich vykázání všude, kde mám oprávnění",
         "unban_space_specific": "Zrušit jejich vykázání z konkrétních míst, kde mám oprávnění",
         "unban_space_warning": "Nebudou mít přístup ke všemu, čeho nejste správcem.",
+        "unignore_button": "Zrušit ignorování",
+        "verification_unavailable": "Ověření uživatele není k dispozici",
         "verify_button": "Ověřit uživatele",
         "verify_explainer": "Pro lepší bezpečnost, ověřte uživatele zkontrolováním jednorázového kódu na vašich zařízeních."
     },
     "user_menu": {
+        "link_new_device": "Připojit nové zařízení",
         "settings": "Všechna nastavení",
         "switch_theme_dark": "Přepnout do tmavého režimu",
         "switch_theme_light": "Přepnout do světlého režimu"
@@ -3600,10 +3955,12 @@
         "input_devices": "Vstupní zařízení",
         "jitsi_call": "Jitsi konference",
         "join_button_tooltip_call_full": "Omlouváme se — tento hovor je v současné době plný",
-        "join_button_tooltip_connecting": "Spojování",
         "legacy_call": "Zastaralý způsob hovoru",
         "maximise": "Vyplnit obrazovku",
         "maximise_call": "Maximalizovat hovor",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konference"
+        },
         "minimise_call": "Minimalizovat hovor",
         "misconfigured_server": "Volání selhalo, protože je rozbitá konfigurace serveru",
         "misconfigured_server_description": "Požádejte správce svého domovského serveru (<code>%(homeserverDomain)s</code>) jestli by nemohl nakonfigurovat TURN server, aby volání fungovala spolehlivě.",
@@ -3752,7 +4109,7 @@
         "error_need_to_be_logged_in": "Musíte být přihlášeni.",
         "error_unable_start_audio_stream_description": "Nelze spustit streamování zvuku.",
         "error_unable_start_audio_stream_title": "Nepodařilo spustit živý přenos",
-        "modal_data_warning": "Data na této obrazovce jsou sdílena s %(widgetDomain)s",
+        "modal_data_warning": "Níže uvedené údaje jsou sdíleny s %(widgetDomain)s",
         "modal_title_default": "Modální widget",
         "no_name": "Neznámá aplikace",
         "open_id_permissions_dialog": {
@@ -3761,7 +4118,7 @@
             "title": "Povolte tomuto widgetu ověřit vaši identitu"
         },
         "popout": "Otevřít widget v novém okně",
-        "set_room_layout": "Nastavit všem rozložení mé místnosti",
+        "set_room_layout": "Nastavte rozložení pro každého",
         "shared_data_avatar": "URL vašeho profilového obrázku",
         "shared_data_device_id": "ID vašeho zařízení",
         "shared_data_lang": "Váš jazyk",
diff --git a/src/i18n/strings/cy.json b/src/i18n/strings/cy.json
new file mode 100644
index 0000000000000000000000000000000000000000..d73c13a384cb884593cbcb3141831cf56c9678c3
--- /dev/null
+++ b/src/i18n/strings/cy.json
@@ -0,0 +1,3811 @@
+{
+    "a11y": {
+        "emoji_picker": "Dewisydd Emoji",
+        "jump_first_invite": "Symud i'r gwahoddiad cyntaf.",
+        "message_composer": "Neges cyfansoddwr",
+        "n_unread_messages": {
+            "%(count)s neges heb eu darllen": "other",
+            "1 neges heb ei darllen": "one"
+        },
+        "n_unread_messages_mentions": {
+            "%(count)s crybwylliadau heb eu darllen": "zero",
+            "1 crybwylliad heb ei ddarllen": "one",
+            "%(count)s grybwylliad heb eu darllen": "two",
+            "%(count)s crybwylliad heb eu darllen": "other"
+        },
+        "recent_rooms": "Ystafelloedd diweddar",
+        "room_messsage_not_sent": "Agor ystafell %(roomName)s gyda neges heb ei gosod.",
+        "room_n_unread_invite": "Agor gwahoddiad i ystafell %(roomName)s.",
+        "room_n_unread_messages": {
+            "Ystafell agored%(roomName)s gyda %(count)s negeseuon heb eu darllen.": "zero",
+            "Ystafell agored %(roomName)s gydag 1 neges heb ei darllen.": "one",
+            "Ystafell agored%(roomName)s gyda %(count)s neges heb eu darllen.": "other"
+        },
+        "room_n_unread_messages_mentions": {
+            "Ystafell agored %(roomName)s gyda %(count)s negeseuon heb eu darllen gan gynnwys crybwylliadau.": "zero",
+            "Ystafell agored %(roomName)s gydag 1 crybwylliad heb ei ddarllen.": "one",
+            "Ystafell agored %(roomName)s gyda %(count)s neges heb eu darllen gan gynnwys crybwylliadau.": "other"
+        },
+        "room_name": "Ystafell %(matere)s",
+        "room_status_bar": "Bar statws ystafell",
+        "seek_bar_label": "Bar chwilio sain",
+        "unread_messages": "Negeseuon heb eu darllen.",
+        "user_menu": "Dewislen defnyddiwr"
+    },
+    "a11y_jump_first_unread_room": "Symud i'r ystafell gyntaf heb ei darllen.",
+    "action": {
+        "accept": "Derbyn",
+        "add": "Ychwanegu",
+        "add_existing_room": "Ychwanegu ystafell bresennol",
+        "add_people": "Ychwanegu pobl",
+        "apply": "Gweithredu",
+        "approve": "Cymeradwyo",
+        "ask_to_join": "Gofyn i gael ymuno",
+        "back": "Nôl",
+        "call": "Galw",
+        "cancel": "Canslo",
+        "change": "Newid",
+        "clear": "Clirio",
+        "click": "Clicio",
+        "click_to_copy": "Clicio i gopïo",
+        "close": "Cau",
+        "collapse": "Cau",
+        "complete": "Wedi cwblhau",
+        "confirm": "Cadarnhau",
+        "continue": "Parhau",
+        "copy": "Copïo",
+        "copy_link": "Copïo dolen",
+        "create": "Creu",
+        "create_a_room": "Creu ystafell",
+        "create_account": "Creu Cyfrif",
+        "decline": "Gwrthod",
+        "decline_and_block": "Gwrthod a rhwystro",
+        "decline_invite": "Gwrthod y gwahoddiad",
+        "delete": "Dileu",
+        "deny": "Gwrthod",
+        "disable": "Analluogi",
+        "disconnect": "Datgysylltu",
+        "dismiss": "Gwrthod",
+        "done": "Gorffen",
+        "download": "Llwytho i lawr",
+        "edit": "Golygu",
+        "enable": "Galluogi",
+        "enter_fullscreen": "Mynd i'r sgrin lawn",
+        "exit_fullscreeen": "Gadael y sgrin lawn",
+        "expand": "Dangos",
+        "explore_public_rooms": "Archwiliwch ystafelloedd cyhoeddus",
+        "explore_rooms": "Archwilio Ystafelloedd",
+        "export": "Allforio",
+        "forward": "Ymlaen",
+        "go": "Mynd",
+        "go_back": "Nôl",
+        "got_it": "Iawn",
+        "hide": "Cuddio",
+        "hide_advanced": "Cuddio uwch",
+        "hold": "Dal",
+        "ignore": "Anwybyddu",
+        "import": "Mewnforio",
+        "invite": "Gwahodd",
+        "invite_to_space": "Gwahoddiad i'r gofod",
+        "invites_list": "Gwahoddiadau",
+        "join": "Ymuno",
+        "learn_more": "Dysgu rhagor",
+        "leave": "Gadael",
+        "leave_room": "Gadael yr ystafell",
+        "logout": "Allgofnodi",
+        "manage": "Rheoli",
+        "maximise": "Mwyhau",
+        "mention": "Crybwyll",
+        "minimise": "Lleihau",
+        "new_message": "Neges newydd",
+        "new_room": "Ystafell newydd",
+        "new_video_room": "Ystafell fideo newydd",
+        "next": "Nesaf",
+        "no": "Na",
+        "ok": "Iawn",
+        "open": "Agor",
+        "open_menu": "Agor dewislen",
+        "pause": "Oedi",
+        "pin": "Pinio",
+        "play": "Chwarae",
+        "proceed": "Parhau",
+        "quote": "Dyfyniad",
+        "react": "Ymateb",
+        "refresh": "Adnewyddu",
+        "register": "Cofrestru",
+        "reload": "Ail-lwytho",
+        "remove": "Tynnu",
+        "rename": "Ailenwi",
+        "reply": "Ateb",
+        "reply_in_thread": "Ateb i edefyn",
+        "report_content": "Cynnwys yr Adroddiad",
+        "report_room": "Ystafell adrodd",
+        "resend": "Ailanfon",
+        "reset": "Ailosod",
+        "resume": "Ailgychwyn",
+        "retry": "Ceisio eto",
+        "review": "Adolygu",
+        "revoke": "Dirymu",
+        "save": "Cadw",
+        "search": "Chwilio",
+        "send_report": "Anfon adroddiad",
+        "set_avatar": "Gosod  llun proffil",
+        "share": "Rhannu",
+        "show": "Dangos",
+        "show_advanced": "Dangos uwch",
+        "show_all": "Dangos y cyfan",
+        "sign_in": "Mewngofnodi",
+        "sign_out": "Allgofnodi",
+        "skip": "Hepgor",
+        "start": "Dechrau",
+        "start_chat": "Dechrau sgwrs",
+        "start_new_chat": "Dechrau sgwrs newydd",
+        "stop": "Atal",
+        "submit": "Cyflwyno",
+        "subscribe": "Tanysgrifio",
+        "transfer": "Trosglwyddo",
+        "trust": "Ymddiried",
+        "try_again": "Ceisiwch eto",
+        "unban": "Adfer",
+        "unignore": "Dadanwybyddu",
+        "unpin": "Dadbinio",
+        "unsubscribe": "Dad-danysgrifio",
+        "update": "Diweddaru",
+        "upgrade": "Uwchraddio",
+        "upload": "Llwytho i fyny",
+        "upload_file": "Llwytho ffeil i fyny",
+        "verify": "Gwirio",
+        "view": "Golwg",
+        "view_all": "Gweld y cyfan",
+        "view_list": "Gweld rhestr",
+        "view_message": "Gweld neges",
+        "view_source": "Gweld Ffynhonnell",
+        "yes": "Iawn",
+        "yes_dismiss": "Iawn, diystyru",
+        "zoom_in": "Chwyddo mewn",
+        "zoom_out": "Chwyddo allan"
+    },
+    "analytics": {
+        "accept_button": "Mae hynny'n iawn",
+        "bullet_1": "<Bold>Nid</Bold> ydym ni yn cofnodi neu broffilio unrhyw ddata cyfrif",
+        "bullet_2": "<Bold>Nid</Bold> ydym ni yn rhannu gwybodaeth gyda thrydydd partïon",
+        "consent_migration": "Rydych wedi cydsynio yn barod i rannu data defnydd dienw gyda ni. Rydym yn diweddaru sut mae hynny'n gweithio.",
+        "disable_prompt": "Gallwch ddiffodd hwn unrhyw bryd yn y gosodiadau",
+        "enable_prompt": "Helpwch i wella %(analyticsOwner)s",
+        "learn_more": "Rhanwch ddata dienw i'n helpu i nodi problemau. Dim byd personol. Dim trydydd partïon. <LearnMoreLink>Dysgwch Ragor</LearnMoreLink>",
+        "privacy_policy": "Gallwch ddarllen ein holl delerau<PrivacyPolicyUrl>yma</PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "Helpwch ni i nodi problemau a gwella %(analyticsOwner)s trwy rannu data defnydd dienw. Er mwyn deall sut mae pobl yn defnyddio dyfeisiau lluosog, byddwn yn cynhyrchu dynodwr ar hap, a rennir gan eich dyfeisiau.",
+        "shared_data_heading": "Mae modd rhannu unrhyw un o’r data canlynol:"
+    },
+    "auth": {
+        "3pid_in_use": "Mae'r cyfeiriad e-bost neu'r rhif ffôn hwnnw eisoes yn cael ei ddefnyddio.",
+        "account_clash": "Mae eich cyfrif newydd (%(newAccountId)s) wedi'i gofrestru, ond rydych chi eisoes wedi mewngofnodi i gyfrif gwahanol (%(loggedInUserId)s).",
+        "account_clash_previous_account": "Parhau gyda'r cyfrif blaenorol",
+        "account_deactivated": "Mae'r cyfrif hwn wedi'i gau.",
+        "autodiscovery_generic_failure": "Wedi methu cael ffurfweddiad awtoddarganfod o'r gweinydd",
+        "autodiscovery_hs_incompatible": "Mae eich gweinydd cartref yn rhy hen ac nid yw'n cefnogi'r fersiwn API lleiaf sydd ei angen. Cysylltwch â pherchennog eich gweinydd, neu uwchraddiwch eich gweinydd.",
+        "autodiscovery_invalid": "Ymateb darganfod gweinydd cartref annilys",
+        "autodiscovery_invalid_hs": "Nid yw'n ymddangos bod URL Gweinyddcartref yn weinydd cartref Matrix dilys",
+        "autodiscovery_invalid_hs_base_url": "base_url annilys ar gyfer m.homeserver",
+        "autodiscovery_invalid_is": "Nid yw'n ymddangos bod URL gweinydd hunaniaeth yn weinydd adnabod dilys",
+        "autodiscovery_invalid_is_base_url": "base_url annilys ar gyfer m.identity_server",
+        "autodiscovery_invalid_is_response": "Ymateb canfod gweinydd hunaniaeth annilys",
+        "autodiscovery_invalid_json": "JSON annilys",
+        "autodiscovery_no_well_known": "Heb ganfod ffeil JSON .well-known",
+        "autodiscovery_unexpected_error_hs": "Gwall annisgwyl wrth ddatrys ffurfweddiad y gweinydd cartref",
+        "autodiscovery_unexpected_error_is": "Gwall annisgwyl wrth ddatrys ffurfweddiad gweinydd hunaniaeth",
+        "captcha_description": "Hoffai'r gweinydd cartref hwn sicrhau nad ydych chi'n robot.",
+        "change_password_action": "Newid Cyfrinair",
+        "change_password_confirm_invalid": "Nid yw cyfrineiriau yn cyfateb",
+        "change_password_confirm_label": "Cadarnhau cyfrinair",
+        "change_password_current_label": "Cyfrinair cyfredol",
+        "change_password_empty": "Ni all cyfrineiriau fod yn wag",
+        "change_password_error": "Gwall wrth newid cyfrinair: %(error)s",
+        "change_password_mismatch": "Nid yw cyfrineiriau newydd yn cyfateb",
+        "change_password_new_label": "Cyfrinair Newydd",
+        "check_email_explainer": "Dilynwch y cyfarwyddiadau a anfonwyd at <b>%(email)s</b>",
+        "check_email_resend_prompt": "Heb ei dderbyn?",
+        "check_email_resend_tooltip": "Anfonwyd e-bost cyswllt dilysu!",
+        "check_email_wrong_email_button": "Rhowch gyfeiriad e-bost eto",
+        "check_email_wrong_email_prompt": "Cyfeiriad e-bost anghywir?",
+        "continue_with_idp": "Parhau gyda %(provider)s",
+        "continue_with_sso": "Parhau gyda %(ssoButtons)s",
+        "country_dropdown": "Cwymplen Gwlad",
+        "create_account_prompt": "Newydd yma? <a>Creu cyfrif</a>",
+        "create_account_title": "Creu cyfrif",
+        "email_discovery_text": "Defnyddiwch e-bost i gael ei ddarganfod gan gysylltiadau presennol yn ddewisol.",
+        "email_field_label": "E-bost",
+        "email_field_label_invalid": "Nid yw'n edrych fel cyfeiriad e-bost dilys",
+        "email_field_label_required": "Rhowch gyfeiriad e-bost",
+        "email_help_text": "Ychwanegwch e-bost i allu ailosod eich cyfrinair.",
+        "email_phone_discovery_text": "Defnyddiwch e-bost neu ffôn i gael eich darganfod gan gysylltiadau presennol.",
+        "enter_email_explainer": "Bydd <b>%(homeserver)s</b> yn anfon dolen ddilysu atoch i'ch galluogi i ailosod eich cyfrinair.",
+        "enter_email_heading": "Rhowch eich e-bost i ailosod cyfrinair",
+        "failed_connect_identity_server": "Methu cyrraedd gweinydd hunaniaeth",
+        "failed_connect_identity_server_other": "Gallwch fewngofnodi, ond ni fydd rhai nodweddion ar gael nes bod y gweinydd hunaniaeth yn ôl ar-lein. Os ydych chi'n gweld y rhybudd hwn o hyd, gwiriwch eich ffurfweddiad neu cysylltwch â gweinyddwr gweinydd.",
+        "failed_connect_identity_server_register": "Gallwch gofrestru, ond ni fydd rhai nodweddion ar gael nes bod y gweinydd hunaniaeth yn ôl ar-lein. Os ydych chi'n gweld y rhybudd hwn o hyd, gwiriwch eich ffurfweddiad neu cysylltwch â gweinyddwr gweinydd.",
+        "failed_connect_identity_server_reset_password": "Gallwch ailosod eich cyfrinair, ond ni fydd rhai nodweddion ar gael nes bod y gweinydd hunaniaeth yn ôl ar-lein. Os ydych chi'n gweld y rhybudd hwn o hyd, gwiriwch eich ffurfweddiad neu cysylltwch â gweinyddwr gweinydd.",
+        "failed_homeserver_discovery": "Wedi methu â pherfformio darganfyddiad gweinydd cartref",
+        "failed_query_registration_methods": "Methu ymholi am ddulliau cofrestru a gefnogir.",
+        "failed_soft_logout_auth": "Wedi methu ag ail-ddilysu",
+        "failed_soft_logout_homeserver": "Wedi methu ag ail-ddilysu oherwydd problem gweinydd cartref",
+        "forgot_password_email_invalid": "Nid yw'n ymddangos bod y cyfeiriad e-bost yn ddilys.",
+        "forgot_password_email_required": "Rhaid nodi'r cyfeiriad e-bost sy'n gysylltiedig â'ch cyfrif.",
+        "forgot_password_prompt": "Wedi anghofio eich cyfrinair?",
+        "forgot_password_send_email": "Anfon e-bost",
+        "identifier_label": "Mewngofnodwch gyda",
+        "incorrect_credentials": "Enw defnyddiwr a/neu gyfrinair anghywir.",
+        "incorrect_credentials_detail": "Sylwch eich bod yn mewngofnodi i'r gweinydd %(hs)s, nid matrix.org.",
+        "incorrect_password": "Cyfrinair anghywir",
+        "log_in_new_account": "<a>Mewngofnodwch</a> i'ch cyfrif newydd.",
+        "logout_dialog": {
+            "description": "Ydych chi'n siŵr eich bod am allgofnodi?",
+            "megolm_export": "Allforio allweddi â llaw",
+            "setup_key_backup_title": "Byddwch yn colli mynediad i'ch negeseuon wedi'u hamgryptio",
+            "setup_secure_backup_description_1": "Mae negeseuon wedi'u hamgryptio yn cael eu diogelu gydag amgryptio o'r dechrau i'r diwedd. Dim ond chi a'r derbynwyr sydd â'r allweddi i ddarllen y negeseuon hyn.",
+            "setup_secure_backup_description_2": "Pan fyddwch yn allgofnodi, bydd yr allweddi hyn yn cael eu dileu o'r ddyfais hon, sy'n golygu na fyddwch yn gallu darllen negeseuon wedi'u hamgryptio oni bai bod gennych yr allweddi ar eu cyfer ar eich dyfeisiau eraill, neu eu bod wedi'u gwneud wrth gefn i'r gweinydd.",
+            "skip_key_backup": "Dydw i ddim eisiau fy negeseuon wedi'u hamgryptio",
+            "use_key_backup": "Dechreuwch ddefnyddio Key Backup"
+        },
+        "misconfigured_body": "Gofynnwch i'ch gweinyddwr %(brand)s wirio'ch <a>ffurfweddiad</a> am gofnodion anghywir neu ddyblyg.",
+        "misconfigured_title": "Mae eich %(brand)s wedi'i gam ffurfweddu",
+        "mobile_create_account_title": "Rydych ar fin creu cyfrif ar %(hsName)s",
+        "msisdn_field_description": "Gall defnyddwyr eraill eich gwahodd i ystafelloedd gan ddefnyddio eich manylion cyswllt",
+        "msisdn_field_label": "Ffôn",
+        "msisdn_field_number_invalid": "Nid yw'r rhif ffôn hwnnw'n edrych yn hollol gywir, gwiriwch a cheisiwch eto",
+        "msisdn_field_required_invalid": "Rhowch rif ffôn",
+        "no_hs_url_provided": "Heb ddarparu URL gweinydd cartref",
+        "oidc": {
+            "error_title": "Heb allu eich mewngofnodi",
+            "generic_auth_error": "Aeth rhywbeth o'i le yn ystod y dilysu. Mynd i'r dudalen mewngofnodi a cheisiwch eto.",
+            "missing_or_invalid_stored_state": "Rydym wedi gofyn i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Mynd i'r dudalen mewngofnodi a cheisiwch eto."
+        },
+        "password_field_keep_going_prompt": "Daliwch ati…",
+        "password_field_label": "Rhowch gyfrinair",
+        "password_field_strong_label": "Cyfrinair da, cryf!",
+        "password_field_weak_label": "Mae caniatâd i'r cyfrinair, ond nid yw'n ddiogel",
+        "phone_label": "Ffôn",
+        "phone_optional_label": "Ffôn (dewisol)",
+        "qr_code_login": {
+            "check_code_explainer": "Bydd hyn yn gwirio bod y cysylltiad â'ch dyfais arall yn ddiogel.",
+            "check_code_heading": "Rhowch y rhif sy'n cael ei ddangos ar eich dyfais arall",
+            "check_code_input_label": "cod 2 ddigid",
+            "check_code_mismatch": "Nid yw'r rhifau'n cyfateb",
+            "completing_setup": "Yn cwblhau gosodiad eich dyfais newydd",
+            "error_etag_missing": "Digwyddodd gwall annisgwyl. Gall hyn fod oherwydd estyniad porwr, gweinydd dirprwy, neu gam ffurfweddiad gweinydd.",
+            "error_expired": "Mewngofnodi wedi dod i ben. Ceisiwch eto.",
+            "error_expired_title": "Heb gwblhau'r mewngofnodi mewn pryd",
+            "error_insecure_channel_detected": "Nid oedd modd gwneud cysylltiad diogel â'r ddyfais newydd. Mae eich dyfeisiau presennol yn dal yn ddiogel ac nid oes angen i chi boeni amdanynt.",
+            "error_insecure_channel_detected_instructions": "Nawr beth?",
+            "error_insecure_channel_detected_instructions_1": "Ceisiwch fewngofnodi i'r ddyfais arall eto gyda chod QR rhag ofn bod hyn yn broblem rhwydwaith",
+            "error_insecure_channel_detected_instructions_2": "Os ydych chi'n dod ar draws yr un broblem, rhowch gynnig ar rwydwaith wifi gwahanol neu defnyddiwch eich data symudol yn lle wifi",
+            "error_insecure_channel_detected_instructions_3": "Os nad yw hynny'n gweithio, mewngofnodwch â llaw",
+            "error_insecure_channel_detected_title": "Nid yw'r cysylltiad yn ddiogel",
+            "error_other_device_already_signed_in": "Nid oes angen i chi wneud unrhyw beth arall.",
+            "error_other_device_already_signed_in_title": "Mae eich dyfais arall eisoes wedi mewngofnodi",
+            "error_rate_limited": "Gormod o ymdrechion mewn amser byr. Arhoswch beth amser cyn ceisio eto.",
+            "error_unexpected": "Digwyddodd gwall annisgwyl. Mae'r cais i gysylltu eich dyfais arall wedi'i ddiddymu.",
+            "error_unsupported_protocol": "Nid yw'r ddyfais hon yn cefnogi mewngofnodi i'r ddyfais arall gyda chod QR.",
+            "error_unsupported_protocol_title": "Dyfais arall ddim yn gydnaws",
+            "error_user_cancelled": "Cafodd y mewngofnodi ei ddiddymu ar y ddyfais arall.",
+            "error_user_cancelled_title": "Cais mewngofnodi wedi'i ddiddymu",
+            "error_user_declined": "Rydych chi neu ddarparwr y cyfrif wedi gwrthod y cais mewngofnodi.",
+            "error_user_declined_title": "Gwrthodwyd y mewngofnodi",
+            "follow_remaining_instructions": "Dilynwch y cyfarwyddiadau sy'n weddill",
+            "open_element_other_device": "Agorwch %(brand)s ar eich dyfais arall",
+            "point_the_camera": "Sganiwch y cod QR sy'n cael ei ddangos yma",
+            "scan_code_instruction": "Sganiwch y cod QR gyda dyfais arall",
+            "scan_qr_code": "Mewngofnodwch gyda chod QR",
+            "security_code": "Cod diogelwch",
+            "security_code_prompt": "Os gofynnir i chi, nodwch y cod isod ar eich dyfais arall.",
+            "select_qr_code": "Dewiswch \"%(scanQRCode)s\"",
+            "unsupported_explainer": "Nid yw darparwr eich cyfrif yn cefnogi mewngofnodi i ddyfais newydd gyda chod QR.",
+            "unsupported_heading": "Nid yw'r cod QR yn cael ei gefnogi",
+            "waiting_for_device": "Yn aros i'r ddyfais fewngofnodi"
+        },
+        "register_action": "Creu Cyfrif",
+        "registration": {
+            "continue_without_email_description": "Yn syml, os na fyddwch chi'n ychwanegu e-bost ac yn anghofio'ch cyfrinair, fe allech chi <b>golli mynediad i'ch cyfrif yn barhaol</b>.",
+            "continue_without_email_field_label": "E-bost (dewisol)",
+            "continue_without_email_title": "Parhau heb e-bost"
+        },
+        "registration_disabled": "Mae cofrestriad wedi'i analluogi ar y gweinydd cartref hwn.",
+        "registration_msisdn_field_required_invalid": "Rhowch rif ffôn (sy'n ofynnol ar y gweinydd cartref hwn)",
+        "registration_successful": "Cofrestru yn Llwyddiannus",
+        "registration_username_in_use": "Mae gan rywun yr enw defnyddiwr hwnnw eisoes. Rhowch gynnig ar un arall neu os mai chi ydyw, mewngofnodwch isod.",
+        "registration_username_unable_check": "Methu gwirio a yw'r enw defnyddiwr wedi'i gymryd. Ceisiwch eto yn nes ymlaen.",
+        "registration_username_validation": "Defnyddiwch lythrennau bach, rhifau, llinellau toriad a thanlinellau yn unig",
+        "reset_password": {
+            "confirm_new_password": "Cadarnhau cyfrinair newydd",
+            "devices_logout_success": "Rydych wedi cael eich allgofnodi o bob dyfais ac ni fyddwch yn derbyn hysbysiadau gwthio mwyach. I ail-alluogi hysbysiadau, mewngofnodwch eto ar bob dyfais.",
+            "other_devices_logout_warning_1": "Bydd allgofnodi eich dyfeisiau yn dileu'r bysellau amgryptio neges sydd wedi'u storio arnynt, gan wneud hanes sgwrsio wedi'i amgryptio yn annarllenadwy.",
+            "other_devices_logout_warning_2": "Os ydych chi am gadw mynediad i'ch hanes sgwrsio mewn ystafelloedd wedi'u hamgryptio, gosodwch Key Backup neu allforiwch allweddi eich neges o un o'ch dyfeisiau eraill cyn symud ymlaen.",
+            "password_not_entered": "Rhaid rhoi cyfrinair newydd.",
+            "passwords_mismatch": "Rhaid i gyfrineiriau newydd gyd-fynd â'i gilydd.",
+            "rate_limit_error": "Gormod o ymdrechion mewn amser byr. Arhoswch beth amser cyn ceisio eto.",
+            "rate_limit_error_with_time": "Gormod o ymdrechion mewn amser byr. Rhowch gynnig arall arni ar ôl %(timeout)s.",
+            "reset_successful": "Cafodd eich cyfrinair ei ailosod.",
+            "return_to_login": "Dychwelyd i'r sgrin mewngofnodi",
+            "sign_out_other_devices": "Allgofnodi o bob dyfais"
+        },
+        "reset_password_action": "Ailosod cyfrinair",
+        "reset_password_button": "Wedi anghofio'ch cyfrinair?",
+        "reset_password_email_field_description": "Defnyddiwch gyfeiriad e-bost i adfer eich cyfrif",
+        "reset_password_email_field_required_invalid": "Rhowch gyfeiriad e-bost (sy'n ofynnol ar y gweinydd cartref hwn)",
+        "reset_password_email_not_associated": "Nid yw'n ymddangos bod eich cyfeiriad e-bost yn gysylltiedig ag ID Matrics ar y gweinydd cartref hwn.",
+        "reset_password_email_not_found_title": "Ni ddaethpwyd o hyd i'r cyfeiriad e-bost hwn",
+        "reset_password_title": "Ailosod eich cyfrinair",
+        "server_picker_custom": "Gweinydd cartref arall",
+        "server_picker_description": "Gallwch ddefnyddio'r dewisiadau gweinydd arferol i fewngofnodi i weinyddion Matrics eraill trwy nodi URL gweinydd cartref gwahanol. Mae hyn yn eich galluogi i ddefnyddio %(brand)s gyda chyfrif Matrics presennol ar weinydd cartref gwahanol.",
+        "server_picker_description_matrix.org": "Ymunwch â miliynau am ddim ar y gweinydd cyhoeddus mwyaf",
+        "server_picker_dialog_title": "Penderfynwch ble mae'ch cyfrif yn cael ei gynnal",
+        "server_picker_explainer": "Defnyddiwch eich gweinydd cartref Matrix dewisol os oes gennych chi un, neu gwesteiwch un eich hun.",
+        "server_picker_failed_validate_homeserver": "Methu dilysu'r gweinydd cartref",
+        "server_picker_intro": "Rydyn ni'n galw'r gofodau lle gallwch chi gynnal eich cyfrif yn 'homeservers'.",
+        "server_picker_invalid_url": "URL annilys",
+        "server_picker_learn_more": "Am weinyddion cartref",
+        "server_picker_matrix.org": "Matrix.org yw'r gweinydd cartref cyhoeddus mwyaf yn y byd, felly mae'n lle da i lawer.",
+        "server_picker_required": "Nodwch gweinydd cartref",
+        "server_picker_title": "Mewngofnodwch i'ch gweinydd cartref",
+        "server_picker_title_default": "Dewisiadau'r Gweinydd",
+        "server_picker_title_registration": "Cyfrif gwesteiwr ymlaen",
+        "session_logged_out_description": "Er diogelwch, mae'r sesiwn hon wedi'i hallgofnodi. Mewngofnodwch eto.",
+        "session_logged_out_title": "Wedi Allgofnodi",
+        "set_email": {
+            "description": "Bydd hyn yn caniatáu i chi ailosod eich cyfrinair a derbyn hysbysiadau.",
+            "verification_pending_description": "Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Unwaith y gwneir hyn, cliciwch parhau.",
+            "verification_pending_title": "Yn Yn aros i Wirio"
+        },
+        "set_email_prompt": "Ydych chi am osod cyfeiriad e-bost?",
+        "sign_in_description": "Defnyddiwch eich cyfrif i barhau.",
+        "sign_in_instead": "Mewngofnodwch yn lle hynny",
+        "sign_in_instead_prompt": "Oes gennych chi gyfrif yn barod? <a>Mewngofnodwch yma</a>",
+        "sign_in_or_register": "Mewngofnodi neu Creu Cyfrif",
+        "sign_in_or_register_description": "Defnyddiwch eich cyfrif neu crëwch un newydd i barhau.",
+        "sign_in_prompt": "Oes gennych chi gyfrif? <a>Mewngofnodwch</a>",
+        "sign_in_with_sso": "Mewngofnodwch gyda mewngofnod sengl",
+        "signing_in": "Wrthi'n mewngofnodi…",
+        "soft_logout": {
+            "clear_data_button": "Clirio'r holl ddata",
+            "clear_data_description": "Mae clirio'r holl ddata o'r sesiwn hon yn barhaol. Bydd negeseuon wedi'u hamgryptio yn cael eu colli oni bai bod eu bysellau wedi'u gwneud wrth gefn.",
+            "clear_data_title": "Clirio'r holl ddata yn y sesiwn hon?"
+        },
+        "soft_logout_heading": "Rydych chi wedi allgofnodi",
+        "soft_logout_intro_password": "Rhowch eich cyfrinair i fewngofnodi ac adennill mynediad i'ch cyfrif.",
+        "soft_logout_intro_sso": "Mewngofnodwch ac adennill mynediad i'ch cyfrif.",
+        "soft_logout_intro_unsupported_auth": "Allwch chi ddim fewngofnodi i'ch cyfrif. Cysylltwch â gweinyddwr eich gweinydd cartref am ragor o wybodaeth.",
+        "soft_logout_subheading": "Clirio data personol",
+        "soft_logout_warning": "Rhybudd: mae eich data personol (gan gynnwys allweddi amgryptio) yn dal i gael ei storio yn y sesiwn hon. Cliriwch ef os ydych wedi gorffen defnyddio'r sesiwn hon, neu eisiau mewngofnodi i gyfrif arall.",
+        "sso": "Mewngofnod Sengl",
+        "sso_complete_in_browser_dialog_title": "Mynd i'ch porwr i gwblhau Mewngofnodi",
+        "sso_failed_missing_storage": "Rydym wedi gofyn i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Mynd i'r dudalen mewngofnodi a cheisiwch eto.",
+        "sso_or_username_password": "%(ssoButtons)s Neu %(usermaterePassword)s",
+        "sync_footer_subtitle": "Os ydych chi wedi ymuno â llawer o ystafelloedd, gallai hyn gymryd peth amser",
+        "syncing": "Cydweddu…",
+        "uia": {
+            "code": "Cod",
+            "email": "I greu eich cyfrif, agorwch y ddolen yn yr e-bost yr ydym newydd ei anfon at %(emailAddress)s.",
+            "email_auth_header": "Gwiriwch eich e-bost i barhau",
+            "email_resend_prompt": "Heb ei dderbyn? <a>Ei anfon eto</a>",
+            "email_resent": "Ailanfonwyd!",
+            "fallback_button": "Dechrau dilysu",
+            "mas_cross_signing_reset_cta": "Mynd i'ch cyfrif",
+            "mas_cross_signing_reset_description": "Ailosodwch eich hunaniaeth trwy ddarparwr eich cyfrif ac yna dewch yn ôl a chlicio \"Ailgynnig\".",
+            "mas_cross_signing_reset_title": "Ewch i'ch cyfrif i ailosod eich hunaniaeth",
+            "msisdn": "Mae neges destun wedi'i hanfon at %(msisdn)s",
+            "msisdn_token_incorrect": "Tocyn yn anghywir",
+            "msisdn_token_prompt": "Rhowch y cod sydd ynddo:",
+            "password_prompt": "Cadarnhewch eich hunaniaeth trwy nodi cyfrinair eich cyfrif isod.",
+            "recaptcha_missing_params": "Mae allwedd gyhoeddus captcha ar goll yn ffurfweddiad y gweinydd cartref. Rhowch wybod i'ch gweinyddwr gweinyddwr am hyn.",
+            "registration_token_label": "Tocyn cofrestru",
+            "registration_token_prompt": "Rhowch docyn cofrestru a ddarparwyd gan weinyddwr y gweinydd cartref.",
+            "sso_body": "Cadarnhewch ychwanegu'r cyfeiriad e-bost hwn trwy ddefnyddio Mewngofnodi Sengli brofi pwy ydych.",
+            "sso_failed": "Aeth rhywbeth o'i le wrth gadarnhau pwy ydych chi. Diddymu a cheisio eto.",
+            "sso_postauth_body": "Cliciwch ar y botwm isod i gadarnhau pwy ydych chi.",
+            "sso_postauth_title": "Cadarnhau i barhau",
+            "sso_preauth_body": "I barhau, defnyddiwch Mewngofnod Sengl i brofi pwy ydych.",
+            "sso_title": "Defnyddiwch Mewngofnod Sengl i barhau",
+            "terms": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn:",
+            "terms_invalid": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn"
+        },
+        "unsupported_auth": "Nid yw'r gweinydd cartref hwn yn cynnig unrhyw lifau mewngofnodi a gefnogir gan y cleient hwn.",
+        "unsupported_auth_email": "Nid yw'r gweinydd cartref hwn yn cefnogi mewngofnodi gan ddefnyddio cyfeiriad e-bost.",
+        "unsupported_auth_msisdn": "Nid yw'r gweinydd hwn yn cefnogi dilysu gyda rhif ffôn.",
+        "username_field_required_invalid": "Rhowch enw defnyddiwr",
+        "username_in_use": "Mae gan rywun yr enw defnyddiwr hwnnw'n barod, rhowch gynnig ar un arall.",
+        "verify_email_explainer": "Mae angen i ni wybod mai chi sydd yno cyn ailosod eich cyfrinair. Cliciwch y ddolen yn yr e-bost yr ydym newydd ei anfon at <b>%(email)s</b>",
+        "verify_email_heading": "Dilyswch eich e-bost i barhau"
+    },
+    "bug_reporting": {
+        "additional_context": "Os oes cyd-destun ychwanegol a fyddai'n helpu i ddadansoddi'r mater, megis yr hyn yr oeddech yn ei wneud ar y pryd, IDau ystafelloedd, IDau defnyddiwr, ac ati, cynhwyswch y pethau hynny yma.",
+        "before_submitting": "Rydym yn argymell <a>creu mater GitHub</a> i sicrhau bod eich adroddiad yn cael ei ddarllen.",
+        "collecting_information": "Casglu gwybodaeth fersiwn ap",
+        "collecting_logs": "Casglu cofnodion",
+        "create_new_issue": "<newIssueLink>Crewch fater newydd</newIssueLink> ar GitHub fel y gallwn ymchwilio i'r byg hwn.",
+        "description": "Mae cofnodion dadfygio yn cynnwys data defnydd cymhwysiad gan gynnwys eich enw defnyddiwr, IDau neu arallenwau'r ystafelloedd yr ydych wedi ymweld â nhw, pa elfennau UI y gwnaethoch ryngweithio â nhw ddiwethaf, ac enwau defnyddwyr defnyddwyr eraill. Nid ydyn nhw'n cynnwys negeseuon.",
+        "download_logs": "Llwytho logiau i lawr",
+        "downloading_logs": "Wrthi'n llwytho logiau i lawr",
+        "error_empty": "Dywedwch wrthym beth aeth o'i le neu, yn well, crëwch fater GitHub sy'n disgrifio'r broblem.",
+        "failed_download_logs": "Wedi methu llwytho i lawr logiau dadfygio: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Gwrthodwyd eich adroddiad gwall. Nid yw'r gweinydd rageshake yn cefnogi'r rhaglen hon.",
+            "rejected_generic": "Gwrthodwyd eich adroddiad gwall. Gwrthododd y gweinydd rageshake gynnwys yr adroddiad oherwydd polisi.",
+            "rejected_recovery_key": "Gwrthodwyd eich adroddiad gwall am resymau diogelwch, gan ei fod yn cynnwys allwedd adfer.",
+            "rejected_version": "Gwrthodwyd eich adroddiad gwall gan fod y fersiwn rydych chi'n ei rhedeg yn rhy hen.",
+            "server_unknown_error": "Daeth y gweinydd rageshake ar draws gwall anhysbys ac ni allai drin yr adroddiad.",
+            "unknown_error": "Wedi methu ag anfon cofnodion."
+        },
+        "github_issue": "Mater GitHub",
+        "introduction": "Os ydych chi wedi cyflwyno byg trwy GitHub, gall logiau dadfygio ein helpu i ddod o hyd i'r broblem. ",
+        "log_request": "Er mwyn ein helpu i atal hyn yn y dyfodol, <a>anfonwch logiau atom</a>.",
+        "logs_sent": "Logiau wedi'u hanfon",
+        "matrix_security_issue": "I roi gwybod am fater diogelwch yn ymwneud â Matrix, darllenwch y <a>Polisi Datgelu Diogelwch</a> Matrix.org .",
+        "preparing_download": "Paratoi i lwytho logiau i lawr",
+        "preparing_logs": "Paratoi i anfon logiau",
+        "send_logs": "Anfon logiau",
+        "submit_debug_logs": "Cyflwynwch gofnodion dadfygio",
+        "textarea_label": "Nodiadau",
+        "thank_you": "Diolch!",
+        "title": "Adrodd gwallau",
+        "unsupported_browser": "Nodyn atgoffa: Nid yw eich porwr yn cael ei gefnogi, felly mae'n bosibl y bydd eich profiad yn anrhagweladwy.",
+        "uploading_logs": "Wrthi'n lwytho logiau i fyny",
+        "waiting_for_server": "Yn aros am ymateb gan y gweinydd"
+    },
+    "cannot_invite_without_identity_server": "Methu gwahodd defnyddiwr trwy e-bost heb weinydd adnabod. Gallwch gysylltu ag un o dan \"Gosodiadau\".",
+    "cannot_reach_homeserver": "Methu cyrraedd homeserver",
+    "cannot_reach_homeserver_detail": "Sicrhewch fod gennych gysylltiad rhyngrwyd sefydlog, neu cysylltwch â gweinyddwr y gweinydd",
+    "cant_load_page": "Methu llwytho'r dudalen",
+    "chat_card_back_action_label": "Nôl i sgwrsio",
+    "chat_effects": {
+        "confetti_description": "Yn anfon y neges a roddwyd gyda conffeti",
+        "confetti_message": "yn anfon conffeti",
+        "fireworks_description": "Yn anfon y neges a roddwyd gyda thân gwyllt",
+        "fireworks_message": "yn anfon tân gwyllt",
+        "hearts_description": "Yn anfon y neges a roddwyd gyda chalonnau",
+        "hearts_message": "yn anfon calonnau",
+        "rainfall_description": "Yn anfon y neges a roddir gyda glaw",
+        "rainfall_message": "yn anfon glaw",
+        "snowfall_description": "Yn anfon y neges a roddwyd gydag eira",
+        "snowfall_message": "yn anfon eira",
+        "spaceinvaders_description": "Yn anfon y neges a roddwyd gydag effaith ar thema gofod",
+        "spaceinvaders_message": "yn anfon goresgynwyr gofod"
+    },
+    "common": {
+        "access_token": "Tocyn Mynediad",
+        "accessibility": "Hygyrchedd",
+        "advanced": "Uwch",
+        "all_chats": "Pob Sgwrs",
+        "analytics": "Dadansoddi Gwe",
+        "appearance": "Gwedd",
+        "application": "Rhaglen",
+        "are_you_sure": "Ydych chi'n siŵr?",
+        "attachment": "Atodiad",
+        "authentication": "Allweddi Dilysu",
+        "avatar": "Afatar",
+        "beta": "Beta",
+        "camera": "Camera",
+        "cameras": "Camerâu",
+        "cancel": "Diddymu",
+        "capabilities": "Gallu",
+        "copied": "Wedi'i gopïo!",
+        "credits": "Diolchiadau",
+        "dark": "Tywyll",
+        "description": "Disgrifiad",
+        "deselect_all": "Dad-ddewis y cwbl",
+        "device": "Dyfais",
+        "edited": "golygwyd",
+        "email_address": "Cyfeiriad e-bost",
+        "emoji": "Emoji",
+        "encrypted": "Wedi'i amgryptio",
+        "encryption_enabled": "Amgryptio wedi'i alluogi",
+        "error": "Gwall",
+        "faq": "Cwestiynau Cyffredin",
+        "favourites": "Ffefrynnau",
+        "feedback": "Adborth",
+        "filter_results": "Hidlo canlyniadau",
+        "forward_message": "Anfonwyd neges ymlaen",
+        "general": "Cyffredinol",
+        "go_to_settings": "Mynd i'r Gosodiadau",
+        "guest": "Gwestai",
+        "help": "Cymorth",
+        "historical": "Hanesyddol",
+        "home": "Cartref",
+        "homeserver": "Gweinydd cartref",
+        "identity_server": "Gweinydd hunaniaeth",
+        "image": "Delwedd",
+        "integration_manager": "Rheolwr integreiddio",
+        "joined": "Ymunwyd",
+        "labs": "Labs",
+        "legal": "Cyfreithiol",
+        "light": "Golau",
+        "loading": "Yn Llwytho…",
+        "location": "Lleoliad",
+        "low_priority": "Blaenoriaeth isel",
+        "matrix": "Matrics",
+        "message": "Neges",
+        "message_layout": "Cynllun y neges",
+        "message_timestamp_invalid": "Stamp amser annilys",
+        "microphone": "Meicroffôn",
+        "model": "Model",
+        "moderation_and_safety": "Cymedroli a diogelwch",
+        "modern": "Modern",
+        "mute": "Tewi",
+        "name": "Enw",
+        "no_results": "Dim canlyniadau",
+        "no_results_found": "Heb ganfod canlyniad",
+        "not_trusted": "Heb ymddiried ynddo",
+        "off": "Diffodd",
+        "offline": "All-lein",
+        "on": "Ymlaen",
+        "options": "Dewisiadau",
+        "orphan_rooms": "Ystafelloedd eraill",
+        "password": "Cyfrinair",
+        "people": "Pobl",
+        "preferences": "Dewisiadau",
+        "presence": "Presenoldeb",
+        "preview_message": "Hei ti. Ti yw'r gorau!",
+        "privacy": "Preifatrwydd",
+        "private": "Preifat",
+        "private_room": "Ystafell breifat",
+        "private_space": "Gofod preifat",
+        "profile": "Proffil",
+        "public": "Cyhoeddus",
+        "public_room": "Ystafell gyhoeddus",
+        "public_space": "Man cyhoeddus",
+        "qr_code": "Cod QR",
+        "random": "Ar hap",
+        "reactions": "Adweithiau",
+        "recommended": "Argymhellwyd",
+        "report_a_bug": "Adrodd byg",
+        "room": "Ystafell",
+        "room_name": "Enw'r ystafell",
+        "rooms": "Ystafelloedd",
+        "save": "Cadw",
+        "saved": "Cadwyd",
+        "saving": "Yn cadw…",
+        "secure_backup": "Copi Wrth Gefn Diogel",
+        "select_all": "Dewis y cyfan",
+        "server": "Gweinydd",
+        "settings": "Gosodiadau",
+        "setup_secure_messages": "Gosod  Negeseuon Diogel",
+        "show_more": "Dangos mwy",
+        "someone": "Rhywun",
+        "space": "Bwlch",
+        "spaces": "Gofodau",
+        "sticker": "Sticer",
+        "stickerpack": "Pecyn sticeri",
+        "success": "Llwyddiant",
+        "suggestions": "Awgrymiadau",
+        "support": "Cymorth",
+        "system_alerts": "Rhybuddion System",
+        "theme": "Thema",
+        "thread": "Edefyn",
+        "threads": "Edafedd",
+        "timeline": "Llinell Amser",
+        "unavailable": "ddim ar gael",
+        "unencrypted": "Heb ei amgryptio",
+        "unmute": "Dad-dewi",
+        "unnamed_room": "Ystafell Ddienw",
+        "unnamed_space": "Gofod Dienw",
+        "unverified": "Heb ei wirio",
+        "updating": "Diweddaru...",
+        "user": "Defnyddwyr",
+        "user_avatar": "Llun proffil",
+        "username": "Enw defnyddiwr",
+        "verification_cancelled": "Dilysiad wedi'i ddiddymu",
+        "verified": "Wedi ei wirio",
+        "version": "Fersiwn",
+        "video": "Fideo",
+        "video_room": "Ystafell fideo",
+        "view_message": "Gweld neges",
+        "warning": "Rhybudd"
+    },
+    "composer": {
+        "autocomplete": {
+            "@room_description": "Rhowch wybod i'r ystafell gyfan",
+            "command_a11y": "Awtogwblhau Gorchymyn",
+            "command_description": "Gorchmynion",
+            "emoji_a11y": "Awtogwblhau Emoji",
+            "notification_a11y": "Awtogwblhau Hysbysiad",
+            "notification_description": "Hysbysiad Ystafell",
+            "room_a11y": "Awtogwblhau Ystafell",
+            "space_a11y": "Awtogwblhau Gofod",
+            "user_a11y": "Awtogwblhau Defnyddiwr",
+            "user_description": "Defnyddwyr"
+        },
+        "close_sticker_picker": "Cuddio sticeri",
+        "edit_composer_label": "Golygu neges",
+        "format_bold": "Trwm",
+        "format_code_block": "Bloc cod",
+        "format_decrease_indent": "Lleihau mewnoliad",
+        "format_increase_indent": "Cynnyddu mewnoliad",
+        "format_inline_code": "Cod",
+        "format_insert_link": "Mewnosod dolen",
+        "format_italic": "Italig",
+        "format_italics": "Italigau",
+        "format_link": "Dolen",
+        "format_ordered_list": "Rhestr wedi'i rhifo",
+        "format_strikethrough": "Llinell Drwodd",
+        "format_underline": "Tanlinellu",
+        "format_unordered_list": "Rhestr bwled",
+        "formatting_toolbar_label": "Fformatio",
+        "link_modal": {
+            "link_field_label": "Dolen",
+            "text_field_label": "Testun",
+            "title_create": "Creu dolen",
+            "title_edit": "Golygu dolen"
+        },
+        "mode_plain": "Cuddio fformatio",
+        "mode_rich_text": "Dangos fformatio",
+        "no_perms_notice": "Nid oes gennych ganiatâd i bostio i'r ystafell hon",
+        "placeholder": "Anfon neges…",
+        "placeholder_encrypted": "Anfon neges wedi'i hamgryptio…",
+        "placeholder_reply": "Anfon ateb…",
+        "placeholder_reply_encrypted": "Anfon ateb wedi'i amgryptio…",
+        "placeholder_thread": "Ateb i edefyn…",
+        "placeholder_thread_encrypted": "Ymateb i edefyn wedi'i amgryptio…",
+        "poll_button": "Arolwg",
+        "poll_button_no_perms_description": "Nid oes gennych ganiatâd i ddechrau pleidleisio yn yr ystafell hon.",
+        "poll_button_no_perms_title": "Angen Caniatâd",
+        "replying_title": "Yn ateb",
+        "room_upgraded_link": "Mae'r sgwrs yn parhau yma.",
+        "room_upgraded_notice": "Mae'r ystafell hon wedi'i hadnewyddu ac nid yw'n weithredol mwyach.",
+        "send_button_title": "Anfon neges",
+        "send_button_voice_message": "Anfon neges llais",
+        "send_voice_message": "Anfon neges llais",
+        "stop_voice_message": "Stopio recordio",
+        "voice_message_button": "Neges Llais"
+    },
+    "console_dev_note": "Os ydych chi'n gwybod beth rydych chi'n ei wneud, mae Element yn raglen cod agored, gwnewch yn siŵr eich bod chi'n edrych ar ein GitHub ( https://github.com/vector-im/element-web/ ) a chyfrannu!",
+    "console_scam_warning": "Os dywedodd rhywun wrthych am gopïo/gludo rhywbeth yma, mae'n debygol iawn eich bod yn cael eich twyllo!",
+    "console_wait": "Arhoswch!",
+    "create_room": {
+        "action_create_room": "Creu ystafell",
+        "action_create_video_room": "Creu ystafell fideo",
+        "encrypted_video_room_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Bydd yr ystafell yn cael ei hamgryptio ond ni fydd yr alwad wedi'i mewnosod.",
+        "encrypted_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Ni fydd pontydd a'r mwyafrif o fotiau'n gweithio eto.",
+        "encryption_forced": "Mae angen amgryptio ar eich gweinydd er mwyn ei alluogi mewn ystafelloedd preifat.",
+        "encryption_label": "Galluogi amgryptio o'r dechrau i'r diwedd",
+        "error_title": "Methu creu ystafell",
+        "generic_error": "Mae'n bosibl nad yw'r gweinydd ar gael, wedi'i orlwytho, neu eich bod yn taro byg.",
+        "join_rule_change_notice": "Gallwch newid hwn unrhyw bryd o osodiadau ystafell.",
+        "join_rule_invite": "Ystafell breifat (gwahoddiad yn unig)",
+        "join_rule_invite_label": "Dim ond pobl a wahoddwyd fydd yn gallu dod o hyd i'r ystafell hon ac ymuno â hi.",
+        "join_rule_knock_label": "Gall unrhyw un wneud cais i ymuno, ond mae angen i weinyddwyr neu gymedrolwyr ganiatáu mynediad. Gallwch newid hyn yn nes ymlaen.",
+        "join_rule_public_label": "Bydd unrhyw un yn gallu dod o hyd i'r ystafell hon ac ymuno â hi.",
+        "join_rule_public_parent_space_label": "Bydd unrhyw un yn gallu dod o hyd i'r ystafell hon ac ymuno â hi, nid dim ond aelodau o<SpaceName/>.",
+        "join_rule_restricted": "Yn weladwy i aelodau'r gofod",
+        "join_rule_restricted_label": "Bydd pawb yn <SpaceName/> yn gallu dod o hyd i'r ystafell hon ac ymuno â hi.",
+        "name_validation_required": "Rhowch enw ar gyfer yr ystafell",
+        "room_visibility_label": "Gwelededd ystafell",
+        "title_private_room": "Creu ystafell breifat",
+        "title_public_room": "Creu ystafell gyhoeddus",
+        "title_video_room": "Creu ystafell fideo",
+        "topic_label": "Pwnc (dewisol)",
+        "unfederated": "Rhwystro unrhyw un nad yw'n rhan o %(serverName)s rhag ymuno â'r ystafell hon byth.",
+        "unfederated_label_default_off": "Efallai y byddwch yn galluogi hyn os mai dim ond ar gyfer cydweithio â thimau mewnol ar eich gweinydd cartref y bydd yr ystafell yn cael ei defnyddio. Nid oes modd newid hyn yn ddiweddarach.",
+        "unfederated_label_default_on": "Efallai y byddwch yn analluogi hyn os bydd yr ystafell yn cael ei defnyddio ar gyfer cydweithio â thimau allanol sydd â'u gweinydd cartref eu hunain. Nid oes modd newid hyn yn ddiweddarach.",
+        "unsupported_version": "Nid yw'r gweinydd yn cefnogi'r fersiwn ystafell a nodwyd."
+    },
+    "create_space": {
+        "add_details_prompt": "Ychwanegwch rai manylion i helpu pobl i'w adnabod.",
+        "add_details_prompt_2": "Gallwch newid y rhain ar unrhyw bryd.",
+        "add_existing_rooms_description": "Dewiswch ystafelloedd neu sgyrsiau i'w hychwanegu. Dim ond lle i chi yw hwn, ni fydd neb yn cael gwybod. Gallwch ychwanegu mwy yn ddiweddarach.",
+        "add_existing_rooms_heading": "Beth ydych chi eisiau ei drefnu?",
+        "address_label": "Cyfeiriad",
+        "address_placeholder": "e.e. fy-ngofod",
+        "creating": "Wrthi'n creu…",
+        "creating_rooms": "Wrthi'n creu ystafelloedd…",
+        "done_action": "Mynd i'm gofod",
+        "done_action_first_room": "Mynd i fy ystafell gyntaf",
+        "explainer": "Mae gofodau yn ffordd newydd o grwpio ystafelloedd a phobl. Pa fath o Ofod ydych chi am ei greu? Gallwch newid hyn yn nes ymlaen.",
+        "failed_create_initial_rooms": "Wedi methu â chreu ystafelloedd gofod cychwynnol",
+        "failed_invite_users": "Wedi methu â gwahodd y defnyddwyr canlynol i'ch gofod: %(csvUsers)s",
+        "invite_teammates_by_username": "Gwahoddiad yn ôl enw defnyddiwr",
+        "invite_teammates_description": "Sicrhewch fod gan y bobl iawn fynediad. Gallwch wahodd mwy yn ddiweddarach.",
+        "invite_teammates_heading": "Gwahoddwch eich cyd-chwaraewyr",
+        "inviting_users": "Wrthi'n gwahodd…",
+        "label": "Creu gofod",
+        "name_required": "Rhowch enw ar gyfer y gofod",
+        "personal_space": "Dim ond fi",
+        "personal_space_description": "Man preifat i drefnu eich ystafelloedd",
+        "private_description": "Gwahoddiad yn unig, sydd orau i chi'ch hun neu dimau",
+        "private_heading": "Eich lle preifat",
+        "private_personal_description": "Sicrhewch fod gan y bobl iawn fynediad i %(matere)s",
+        "private_personal_heading": "Gyda phwy ydych chi'n gweithio?",
+        "private_space": "Fi a fy nghyd-chwaraewyr",
+        "private_space_description": "Man preifat i chi a'ch cyd-chwaraewyr",
+        "public_description": "Gofodau agored i unrhyw un, gorau i gymunedau",
+        "public_heading": "Eich gofod cyhoeddus",
+        "search_public_button": "Chwilio am ofodau cyhoeddus",
+        "setup_rooms_community_description": "Gadewch i ni greu ystafell ar gyfer pob un ohonyn nhw.",
+        "setup_rooms_community_heading": "Beth yw rhai pethau rydych am eu trafod yn %(spaceName)s?",
+        "setup_rooms_description": "Gallwch ychwanegu mwy yn nes ymlaen hefyd, gan gynnwys y rhai sydd eisoes yn bodoli.",
+        "setup_rooms_private_description": "Byddwn yn creu ystafelloedd ar gyfer pob un ohonyn nhw.",
+        "setup_rooms_private_heading": "Pa brosiectau y mae eich tîm yn gweithio arnyn nhw?",
+        "share_description": "Dim ond chi yw e ar hyn o bryd, bydd hyd yn oed yn well gydag eraill.",
+        "share_heading": "Rhanwch %(matere)s",
+        "skip_action": "Hepgor am nawr",
+        "subspace_adding": "Yn ychwanegu…",
+        "subspace_beta_notice": "Ychwanegwch le at ofod rydych chi'n ei reoli.",
+        "subspace_dropdown_title": "Creu gofod",
+        "subspace_existing_space_prompt": "Eisiau ychwanegu gofod presennol yn lle?",
+        "subspace_join_rule_invite_description": "Dim ond y bobl sy'n cel gwahoddiad fydd yn gallu dod o hyd i'r gofod hwn ac ymuno ag ef.",
+        "subspace_join_rule_invite_only": "Man preifat (gwahoddiad yn unig)",
+        "subspace_join_rule_label": "Gwelededd gofod",
+        "subspace_join_rule_public_description": "Bydd unrhyw un yn gallu dod o hyd i'r gofod hwn ac ymuno ag ef, nid dim ond aelodau o<SpaceName/>.",
+        "subspace_join_rule_restricted_description": "Bydd unrhyw un sydd yn <SpaceName/> yn gallu dod o hyd ac ymuno."
+    },
+    "credits": {
+        "default_cover_photo": "Mae'r <photo> llun clawr rhagosodedig </photo> yn © <author>Jesús Roncero</author> sy'n cael ei ddefnyddio o dan delerau<terms>CC-BY-SA 4.0</terms>.",
+        "twemoji": "Mae'r celf emoji <twemoji>Twemoji</twemoji> yn ©<author> Twitter, Inc a chyfranwyr eraill</author> sy'n cael ei ddefnyddio dan delerau<terms> CC-BY 4.0</terms>.",
+        "twemoji_colr": "Mae'r ffont <colr>twemoji-colr</colr> yn ©<author> Sefydliad Mozilla</author> sy'n cael ei ddefnyddio o dan delerau<terms> Apache 2.0</terms>."
+    },
+    "decline_invitation_dialog": {
+        "confirm": "Ydych chi'n siŵr eich bod am wrthod y gwahoddiad i ymuno â \"%(roomName)s\"?",
+        "ignore_user_help": "Byddwch chi ddim yn gweld unrhyw negeseuon neu wahoddiadau ystafell gan y defnyddiwr hwn.",
+        "reason_description": "Disgrifiwch y rheswm dros adrodd am yr ystafell.",
+        "report_room_description": "Adrodd am yr ystafell hon i ddarparwr eich cyfrif.",
+        "title": "Gwrthod gwahoddiad"
+    },
+    "desktop_default_device_name": "Bwrdd gwaith %(brand)s: %(platformName)s",
+    "devtools": {
+        "active_widgets": "Widgets Actif",
+        "category_other": "Arall",
+        "category_room": "Ystafell",
+        "caution_colon": "Rhybudd:",
+        "client_versions": "Fersiynau Cleient",
+        "crypto": {
+            "4s_public_key_in_account_data": "mewn data cyfrif",
+            "4s_public_key_not_in_account_data": "heb ei ganfod",
+            "4s_public_key_status": "Allwedd gyhoeddus storio gyfrinachol:",
+            "backup_key_cached": "wedi'i storio'n lleol",
+            "backup_key_cached_status": "Allwedd wrth gefn wedi'i storio:",
+            "backup_key_not_stored": "heb ei storio",
+            "backup_key_stored": "mewn storfa gyfrinachol",
+            "backup_key_stored_status": "Allwedd wrth gefn wedi'i storio:",
+            "backup_key_unexpected_type": "math annisgwyl",
+            "backup_key_well_formed": "wedi'i ffurfio'n dda",
+            "cross_signing": "Traws-arwyddo",
+            "cross_signing_cached": "wedi'i storio'n lleol",
+            "cross_signing_not_ready": "Nid yw traws-arwyddo wedi'i osod.",
+            "cross_signing_private_keys_in_storage": "mewn storfa gyfrinachol",
+            "cross_signing_private_keys_in_storage_status": "Traws-lofnodi allweddi preifat:",
+            "cross_signing_private_keys_not_in_storage": "heb ei ganfod yn y storfa",
+            "cross_signing_public_keys_on_device": "yn y cof",
+            "cross_signing_public_keys_on_device_status": "Traws-lofnodi allweddi cyhoeddus:",
+            "cross_signing_ready": "Mae croes-arwyddo yn barod i'w ddefnyddio.",
+            "cross_signing_status": "Statws traws-arwyddo:",
+            "cross_signing_untrusted": "Mae gan eich cyfrif hunaniaeth traws-lofnodi mewn storfa gyfrinachol, ond nid yw'r sesiwn hon yn ymddiried ynddo eto.",
+            "crypto_not_available": "Nid yw'r modiwl cryptograffig ar gael",
+            "key_backup_active_version": "Fersiwn wrth gefn gweithredol:",
+            "key_backup_active_version_none": "Dim",
+            "key_backup_inactive_warning": "Nid yw'ch allweddi'n cael eu gwneud wrth gefn o'r sesiwn hon.",
+            "key_backup_latest_version": "Fersiwn wrth gefn diweddaraf ar y gweinydd:",
+            "key_storage": "Storio Allwedd",
+            "master_private_key_cached_status": "Prif allwedd breifat:",
+            "not_found": "heb ei ganfod",
+            "not_found_locally": "heb ei ganfod yn lleol",
+            "secret_storage_not_ready": "ddim yn barod",
+            "secret_storage_ready": "barod",
+            "secret_storage_status": "Storfa cyfrinachol:",
+            "self_signing_private_key_cached_status": "Allwedd breifat hunan-lofnodi:",
+            "title": "Amgryptio o ben i ben",
+            "user_signing_private_key_cached_status": "Allwedd breifat llofnodi'r defnyddiwr:"
+        },
+        "developer_mode": "Modd datblygwr",
+        "developer_tools": "Offer Datblygwr",
+        "edit_setting": "Golygu gosodiad",
+        "edit_values": "Golygu gwerthoedd",
+        "empty_string": "<empty string>",
+        "event_content": "Cynnwys Digwyddiad",
+        "event_id": "ID y digwyddiad: %(eventId)s",
+        "event_sent": "Digwyddiad wedi'i anfon!",
+        "event_type": "Math o Ddigwyddiad",
+        "explore_account_data": "Archwilio data'r cyfrif",
+        "explore_room_account_data": "Archwilio data cyfrif ystafell",
+        "explore_room_state": "Archwilio cyflwr yr ystafell",
+        "failed_to_find_widget": "Bu gwall wrth ddod o hyd i'r teclyn hwn.",
+        "failed_to_load": "Wedi methu llwytho.",
+        "failed_to_save": "Wedi methu cadw gosodiadau.",
+        "failed_to_send": "Wedi methu ag anfon digwyddiad!",
+        "id": "ID: ",
+        "invalid_json": "Nid yw'n edrych fel JSON dilys.",
+        "level": "Lefel",
+        "low_bandwidth_mode": "Modd lled band isel",
+        "low_bandwidth_mode_description": "Angen gweinydd cartref cydnaws.",
+        "main_timeline": "Prif linell amser",
+        "no_receipt_found": "Heb ganfod derbynneb",
+        "notification_state": "Y cyflwr hysbysu yw <strong>%(notificationState)s</strong>",
+        "notifications_debug": "Dadfygio hysbysiadau",
+        "number_of_users": "Nifer y defnyddwyr",
+        "original_event_source": "Ffynhonnell wreiddiol y digwyddiad",
+        "room_encrypted": "Mae'r ystafell <strong>wedi'i hamgryptio ✅</strong>",
+        "room_id": "ID ystafell: %(roomId)s",
+        "room_not_encrypted": "<strong>Nid yw'r ystafell wedi'i hamgryptio 🚨</strong>",
+        "room_notifications_dot": "Dot: ",
+        "room_notifications_highlight": "Amlygu: ",
+        "room_notifications_last_event": "Digwyddiad diwethaf:",
+        "room_notifications_sender": "Anfonwr: ",
+        "room_notifications_thread_id": "ID Edefyn: ",
+        "room_notifications_total": "Cyfanswm: ",
+        "room_notifications_type": "Math: ",
+        "room_status": "Statws ystafell",
+        "save_setting_values": "Cadw gwerthoedd gosod",
+        "see_history": "Gweld hanes",
+        "send_custom_account_data_event": "Anfon digwyddiad data cyfrif personol",
+        "send_custom_room_account_data_event": "Anfon digwyddiad data cyfrif ystafell personol",
+        "send_custom_state_event": "Anfon digwyddiad cyflwr personol",
+        "send_custom_timeline_event": "Anfonwch ddigwyddiad llinell amser wedi'i deilwra",
+        "server_info": "Gwybodaeth am y gweinydd",
+        "server_versions": "Fersiynau Gweinydd",
+        "settable_global": "Settable yn fyd-eang",
+        "settable_room": "Gosodadwy yn yr ystafell",
+        "setting_colon": "Gosodiad:",
+        "setting_definition": "Gosod  diffiniad:",
+        "setting_id": "Gosod  ID",
+        "settings": {
+            "elementCallUrl": "URL Galwad Element"
+        },
+        "settings_explorer": "Archwiliwr gosodiadau",
+        "show_hidden_events": "Dangos digwyddiadau cudd yn y llinell amser",
+        "state_key": "Allwedd Cyflwr",
+        "thread_root_id": "ID Gwraidd Edefyn: %(threadRootId)s",
+        "threads_timeline": "Llinell amser edafedd",
+        "title": "Offer datblygwr",
+        "toggle_event": "toglo digwyddiad",
+        "toolbox": "Blwch offer",
+        "use_at_own_risk": "NID yw'r UI hwn yn gwirio'r mathau o werthoedd. Defnyddiwch ar eich menter eich hun.",
+        "user_read_up_to": "Darllenodd defnyddiwr hyd at: ",
+        "user_read_up_to_ignore_synthetic": "Darlleniad defnyddiwr hyd at (anwybydduSynthetic): ",
+        "user_read_up_to_private": "Darlleniad defnyddiwr hyd at (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "Darlleniad defnyddiwr hyd at (m.read.private;ignoreSynthetic): ",
+        "value": "Gwerth",
+        "value_colon": "Gwerth:",
+        "value_in_this_room": "Gwerth yn yr ystafell hon",
+        "value_this_room_colon": "Gwerth yn yr ystafell hon:",
+        "values_explicit": "Gwerthoedd ar lefelau penodol",
+        "values_explicit_colon": "Gwerthoedd ar lefelau penodol:",
+        "values_explicit_room": "Gwerthoedd ar lefelau penodol yn yr ystafell hon",
+        "values_explicit_this_room_colon": "Gwerthoedd ar lefelau penodol yn yr ystafell hon:",
+        "view_servers_in_room": "Gweld gweinyddion yn yr ystafell",
+        "view_source_decrypted_event_source": "Ffynhonnell digwyddiad wedi'i dadgryptio",
+        "view_source_decrypted_event_source_unavailable": "Nid yw ffynhonnell wedi'i dadgryptio ar gael",
+        "widget_screenshots": "Galluogi lluniau sgrin teclyn ar declynau syn cael eu cefnogi"
+    },
+    "dialog_close_label": "Cau'r ddeialog",
+    "download_completed": "Llwytho i Lawr Wedi'i Gwblhau",
+    "emoji": {
+        "categories": "Categorïau",
+        "category_activities": "Gweithgareddau",
+        "category_animals_nature": "Anifeiliaid a Natur",
+        "category_flags": "Baneri",
+        "category_food_drink": "Bwyd a Diod",
+        "category_frequently_used": "Defnydd Cyffredin",
+        "category_objects": "Gwrthrychau",
+        "category_smileys_people": "Wynebau hapus a Phobl",
+        "category_symbols": "Symbolau",
+        "category_travel_places": "Teithio a Llefydd",
+        "quick_reactions": "Ymatebion Cyflym"
+    },
+    "emoji_picker": {
+        "cancel_search_label": "Cau'r chwilio"
+    },
+    "empty_room": "Ystafell wag",
+    "empty_room_was_name": "Ystafell wag (roedd yn %(oldName)s)",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "key_validation_text": {
+                "wrong_security_key": "Allwedd Adfer Anghywir"
+            },
+            "restoring": "Adfer allweddi o'r copi wrth gefn",
+            "security_key_title": "Allwedd Adfer"
+        },
+        "bootstrap_title": "Gosod  allweddi",
+        "cancel_entering_passphrase_description": "Ydych chi'n siŵr eich bod am ddiddymu'r cyfrinymadrodd?",
+        "cancel_entering_passphrase_title": "Diddymu cyflwyno cyfrinymadrodd?",
+        "confirm_encryption_setup_body": "Clicio'r botwm isod i gadarnhau gosod amgryptio.",
+        "confirm_encryption_setup_title": "Cadarnhau gosodiad amgryptio",
+        "cross_signing_room_normal": "Mae'r ystafell hon wedi'i hamgryptio o ben-i-ben",
+        "cross_signing_room_verified": "Mae pawb yn yr ystafell hon wedi'u gwirio",
+        "cross_signing_room_warning": "Mae rhywun yn defnyddio sesiwn anhysbys",
+        "cross_signing_user_normal": "Nid ydych wedi gwirio'r defnyddiwr hwn.",
+        "cross_signing_user_verified": "Rydych chi wedi gwirio'r defnyddiwr hwn. Mae'r defnyddiwr hwn wedi gwirio eu holl sesiynau.",
+        "cross_signing_user_warning": "Nid yw'r defnyddiwr hwn wedi gwirio eu holl sesiynau.",
+        "enter_recovery_key": "Rhowch allwedd adfer",
+        "event_shield_reason_authenticity_not_guaranteed": "Nid oes modd gwarantu dilysrwydd y neges hon sydd wedi'i hamgryptio ar y ddyfais hon.",
+        "event_shield_reason_mismatched_sender_key": "Wedi'i amgryptio gan sesiwn heb ei wirio",
+        "event_shield_reason_unknown_device": "Wedi'i amgryptio gan ddyfais anhysbys neu wedi'i dileu.",
+        "event_shield_reason_unsigned_device": "Wedi'i amgryptio gan ddyfais nad yw wedi'i dilysu gan ei pherchennog.",
+        "event_shield_reason_unverified_identity": "Wedi'i amgryptio gan ddefnyddiwr heb ei wirio.",
+        "export_unsupported": "Nid yw eich porwr yn cynnal yr estyniadau cryptograffeg gofynnol",
+        "forgot_recovery_key": "Wedi anghofio'ch allwedd adfer?",
+        "import_invalid_keyfile": "Ddim yn ffeil bysell %(brand)s ddilys",
+        "import_invalid_passphrase": "Methodd y gwiriad dilysu: cyfrinair anghywir?",
+        "key_storage_out_of_sync": "Nid yw eich storfa allweddi wedi'i gydweddu.",
+        "key_storage_out_of_sync_description": "Cadarnhewch eich allwedd adfer i gynnal mynediad i'ch storfa allweddi a'ch hanes negeseuon.",
+        "messages_not_secure": {
+            "cause_1": "Eich gweinydd cartref",
+            "cause_2": "Y gweinydd cartref y mae'r defnyddiwr rydych chi'n ei wirio wedi'i gysylltu ag ef",
+            "cause_3": "Eich cysylltiad, neu gysylltiad rhyngrwyd defnyddwyr eraill",
+            "cause_4": "Eich cysylltiad neu sesiwn y defnyddwyr eraill",
+            "heading": "Gall un o’r canlynol gael ei beryglu:",
+            "title": "Nid yw eich negeseuon yn ddiogel"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "Mae Ymadrodd Diogelwch newydd ac allwedd ar gyfer Negeseuon Diogel wedi'u canfod.",
+            "description_2": "Mae'r sesiwn hon yn amgryptio hanes gan ddefnyddio'r dull adfer newydd.",
+            "title": "Dull Adfer Newydd",
+            "warning": "Os na wnaethoch chi osod y dull adfer newydd, mae'n bosibl bod ymosodwr yn ceisio cael mynediad i'ch cyfrif. Newidiwch eich cyfrinair cyfrif a gosodwch ddull adfer newydd ar unwaith yn y Gosodiadau."
+        },
+        "pinned_identity_changed": "Cafodd hunaniaeth (<b>%(userId)s</b>) %(displayName)s ei ailosod. <a>Dysgu rhagor</a>",
+        "pinned_identity_changed_no_displayname": "Cafodd hunaniaeth <b>%(userId)s</b> ei ailosod. <a>Dysgu rhagor</a>",
+        "recovery_method_removed": {
+            "description_1": "Mae'r sesiwn hon wedi canfod bod eich Ymadrodd Diogelwch a'ch allwedd ar gyfer Negeseuon Diogel wedi'u dileu.",
+            "description_2": "Os gwnaethoch hyn yn ddamweiniol, gallwch osod Negeseuon Diogel ar y sesiwn hon a fydd yn ail-amgryptio hanes negeseuon y sesiwn hon gyda dull adfer newydd.",
+            "title": "Dull Adfer Wedi'i Dynnu",
+            "warning": "Os na wnaethoch chi ddileu'r dull adfer, mae'n bosibl bod ymosodwr yn ceisio cael mynediad i'ch cyfrif. Newidiwch eich cyfrinair cyfrif a gosodwch ddull adfer newydd ar unwaith yn y Gosodiadau."
+        },
+        "reset_all_button": "Wedi anghofio neu golli pob dull adfer? <a>Ailosod y cyfan</a>",
+        "set_up_recovery": "Gosod  adfer",
+        "set_up_recovery_later": "Nid nawr",
+        "set_up_recovery_toast_description": "Cynhyrchwch allwedd adfer y mae modd ei defnyddio i adfer hanes eich neges wedi'i hamgryptio rhag ofn i chi golli mynediad i'ch dyfeisiau.",
+        "set_up_toast_description": "Diogelu rhag colli mynediad i negeseuon a data wedi'u hamgryptio",
+        "set_up_toast_title": "Gosod Copi Wrth Gefn Diogel",
+        "setup_secure_backup": {
+            "explainer": "Gwnewch gopi wrth gefn o'ch allweddi cyn allgofnodi er mwyn osgoi eu colli."
+        },
+        "turn_on_key_storage": "Troi storfa allweddi ymlaen",
+        "turn_on_key_storage_description": "Cadwch eich hunaniaeth cryptograffig a'ch allweddi neges yn ddiogel ar y gweinydd. Bydd hyn yn caniatáu ichi weld hanes eich neges ar unrhyw ddyfeisiau newydd. %1$s.",
+        "udd": {
+            "interactive_verification_button": "Dilyswch yn rhyngweithiol trwy emoji",
+            "other_ask_verify_text": "Gofynnwch i'r defnyddiwr hwn wirio ei sesiwn, neu ei wirio â llaw isod.",
+            "other_new_session_text": "Mae %(matere)s (%(userId)s) wedi mewngofnodi i sesiwn newydd heb ei wirio:",
+            "own_ask_verify_text": "Dilyswch eich sesiwn arall gan ddefnyddio un o'r dewisiadau isod.",
+            "own_new_session_text": "Fe wnaethoch chi fewngofnodi i sesiwn newydd heb ei wirio:",
+            "title": "Heb Ymddiried"
+        },
+        "unable_to_setup_keys_error": "Methu gosod allweddi",
+        "verification": {
+            "accepting": "Yn derbyn…",
+            "after_new_login": {
+                "device_verified": "Dyfais wedi'i dilysu",
+                "skip_verification": "Hepgor dilysu am y tro",
+                "unable_to_verify": "Methu â gwirio'r ddyfais hon",
+                "verify_this_device": "Dilyswch y ddyfais hon"
+            },
+            "cancelled": "Rydych chi wedi diddymu'r dilysiad.",
+            "cancelled_self": "Rydych chi wedi diddymu dilysiad ar eich dyfais arall.",
+            "cancelled_user": "%(displayName)s dilysiad wedi'i ddiddymu.",
+            "cancelling": "Wrthi'n diddymu…",
+            "complete_action": "Iawn",
+            "complete_description": "Rydych chi wedi dilysu'r defnyddiwr hwn yn llwyddiannus.",
+            "complete_title": "Gwirwyd!",
+            "error_starting_description": "Nid oeddem yn gallu dechrau sgwrs gyda'r defnyddiwr arall.",
+            "error_starting_title": "Gwall wrth gychwyn y dilysu",
+            "explainer": "Mae negeseuon diogel gyda'r defnyddiwr hwn wedi'u hamgryptio pen-i-ben ac ni all trydydd parti eu darllen.",
+            "in_person": "I fod yn ddiogel, gwnewch hyn eich hun neu defnyddiwch ffordd ddibynadwy o gyfathrebu.",
+            "incoming_sas_device_dialog_text_1": "Dilyswch y ddyfais hon i'w marcio fel un y mae modd ymddiried ynddi. Mae ymddiried yn y ddyfais hon yn rhoi tawelwch meddwl ychwanegol i chi a defnyddwyr eraill wrth ddefnyddio negeseuon wedi'u hamgryptio o'r dechrau i'r diwedd.",
+            "incoming_sas_device_dialog_text_2": "Bydd gwirio'r ddyfais hon yn ei nodi fel un y mae modd ymddiried ynddo, a bydd defnyddwyr sydd wedi gwirio gyda chi yn ymddiried yn y ddyfais hon.",
+            "incoming_sas_dialog_title": "Cais Dilysu Derbyn",
+            "incoming_sas_dialog_waiting": "Yn aros i bartner gadarnhau…",
+            "incoming_sas_user_dialog_text_1": "Dilyswch y defnyddiwr hwn i'w nodi fel un y mae modd ymddiried ynddo. Mae ymddiried mewn defnyddwyr yn rhoi tawelwch meddwl ychwanegol i chi wrth ddefnyddio negeseuon wedi'u hamgryptio o'r dechrau i'r diwedd.",
+            "incoming_sas_user_dialog_text_2": "Bydd dilysu'r defnyddiwr hwn yn nodi ei sesiwn fel un y mae modd ymddiried ynddi, a hefyd yn nodi bod eich sesiwn yn ymddiried ynddo.",
+            "no_key_or_device": "Mae'n ymddangos nad oes gennych Allwedd Adfer nac unrhyw ddyfeisiau eraill y gallwch wirio yn eu herbyn.  Ni fydd y ddyfais hon yn gallu cyrchu hen negeseuon wedi'u hamgryptio. Er mwyn gwirio pwy ydych ar y ddyfais hon, bydd angen i chi ailosod eich allweddi dilysu.",
+            "no_support_qr_emoji": "Nid yw'r ddyfais rydych chi'n ceisio'i dilysu yn cefnogi sganio cod QR na dilysiad emoji, sef yr hyn y mae %(brand)s yn ei gefnogi. Ceisiwch gyda chleient gwahanol.",
+            "other_party_cancelled": "Mae'r parti arall wedi diddymu'r dilysiad.",
+            "prompt_encrypted": "Gwiriwch yr holl ddefnyddwyr mewn ystafell i sicrhau ei bod yn ddiogel.",
+            "prompt_self": "Dechreuwch ddilysu eto o'r hysbysiad.",
+            "prompt_unencrypted": "Mewn ystafelloedd wedi'u hamgryptio, gwiriwch bob defnyddiwr i sicrhau ei fod yn ddiogel.",
+            "prompt_user": "Dechrau dilysu eto o'u proffil.",
+            "qr_or_sas": "%(qrCode)s neu %(emojiCompare)s",
+            "qr_or_sas_header": "Dilyswch y ddyfais hon trwy gwblhau un o'r canlynol:",
+            "qr_prompt": "Sganiwch y cod unigryw hwn",
+            "qr_reciprocate_same_shield_device": "Bron yno! A yw eich dyfais arall yn dangos yr un darian?",
+            "qr_reciprocate_same_shield_user": "Bron yno! Ydy %(displayName)s yn dangos yr un darian?",
+            "request_toast_accept": "Dilysu Sesiwn",
+            "request_toast_accept_user": "Dilysu Defnyddiwr",
+            "request_toast_decline_counter": "Anwybyddu (%(counter)s)",
+            "request_toast_detail": "%(deviceId)s oddi wrth %(ip)s",
+            "reset_proceed_prompt": "Mynd ymlaen i ailosod",
+            "sas_caption_self": "Dilyswch y ddyfais hon trwy gadarnhau bod y rhif canlynol yn ymddangos ar ei sgrin.",
+            "sas_caption_user": "Dilyswch y defnyddiwr hwn trwy gadarnhau bod y rhif canlynol yn ymddangos ar eu sgrin.",
+            "sas_description": "Cymharwch set unigryw o emoji os nad oes gennych gamera ar y naill ddyfais na'r llall",
+            "sas_emoji_caption_self": "Cadarnhewch fod yr emoji isod yn cael eu harddangos ar y ddwy ddyfais, yn yr un drefn:",
+            "sas_emoji_caption_user": "Dilyswch y defnyddiwr hwn trwy gadarnhau bod yr emoji canlynol yn ymddangos ar eu sgrin.",
+            "sas_match": "Maen nhw'n cyfateb",
+            "sas_no_match": "D'yn nhw ddim yn cyfateb",
+            "sas_prompt": "Cymharu emoji unigryw",
+            "scan_qr": "Gwirio trwy sganio",
+            "scan_qr_explainer": "Gofynnwch i %(displayName)s sganio'ch cod:",
+            "self_verification_hint": "I symud ymlaen, derbyniwch y cais dilysu ar eich dyfais arall.",
+            "start_button": "Dechrau Dilysu",
+            "successful_device": "Rydych chi wedi dilysu %(deviceName)s (%(deviceId)s) yn llwyddiannus!",
+            "successful_own_device": "Rydych chi wedi dilysu'ch dyfais yn llwyddiannus!",
+            "successful_user": "Rydych chi wedi dilysu %(displayName)s yn llwyddiannus!",
+            "timed_out": "Daeth y cyfnod dilysu i ben.",
+            "unsupported_method": "Methu dod o hyd i ddull dilysu a gefnogir.",
+            "unverified_session_toast_accept": "Ie, fi wnaeth",
+            "unverified_session_toast_title": "Mewngofnod newydd. Ai chi oedd hwn?",
+            "unverified_sessions_toast_description": "Adolygwch i sicrhau bod eich cyfrif yn ddiogel",
+            "unverified_sessions_toast_reject": "Yn hwyrach",
+            "unverified_sessions_toast_title": "Mae gennych sesiynau heb eu gwirio",
+            "verification_description": "Dilyswch eich hunaniaeth i gael mynediad at negeseuon wedi'u hamgryptio a phrofwch eich hunaniaeth i eraill. Os ydych hefyd yn defnyddio dyfais symudol, a fyddech cystal ag agor yr ap yno cyn i chi fynd ymlaen.",
+            "verification_dialog_title_device": "Dilysu dyfais arall",
+            "verification_dialog_title_user": "Cais Dilysu",
+            "verification_skip_warning": "Heb ddilysu, ni fydd gennych fynediad i'ch holl negeseuon a gallech ymddangos fel rhai nad ydych yn ymddiried ynddynt i eraill.",
+            "verification_success_with_backup": "Mae eich dyfais newydd bellach wedi'i dilysu. Mae ganddo fynediad i'ch negeseuon wedi'u hamgryptio, a bydd defnyddwyr eraill yn ei ystyried yn rhai y mae modd ymddiried ynddynt.",
+            "verification_success_without_backup": "Mae eich dyfais newydd bellach wedi'i dilysu. Bydd defnyddwyr eraill yn gweld fod modd ymddiried ynddo.",
+            "verify_emoji": "Gwirio trwy emoji",
+            "verify_emoji_prompt": "Gwiriwch trwy gymharu emoji unigryw.",
+            "verify_emoji_prompt_qr": "Os na allwch sganio'r cod uchod, gwiriwch trwy gymharu emoji unigryw.",
+            "verify_later": "Byddaf yn gwirio yn ddiweddarach",
+            "verify_using_device": "Gwiriwch gyda dyfais arall",
+            "verify_using_key": "Dilyswch gydag Allwedd Adfer",
+            "verify_using_key_or_phrase": "Dilyswch gydag Allwedd Adfer neu Ymadrodd",
+            "waiting_for_user_accept": "Yn aros i %(displayName)s dderbyn…",
+            "waiting_other_device": "Yn aros i chi wirio ar eich dyfais arall…",
+            "waiting_other_device_details": "Yn aros i chi wirio ar eich dyfais arall, %(deviceName)s (%(deviceId)s)…",
+            "waiting_other_user": "Yn aros i %(displayName)s wirio…"
+        },
+        "verification_requested_toast_title": "Gofynnwyd am ddilysiad",
+        "verified_identity_changed": "Cafodd hunaniaeth (<b>%(userId)s</b>) %(displayName)s ei ailosod. <a>Dysgu rhagor</a>",
+        "verified_identity_changed_no_displayname": "Cafodd hunaniaeth <b>%(userId)s</b> ei ailosod. <a>Dysgu rhagor</a>",
+        "verify_toast_description": "Efallai na fydd defnyddwyr eraill yn ymddiried ynddo",
+        "verify_toast_title": "Gwiriwch y sesiwn hon",
+        "withdraw_verification_action": "Tynnu'r dilysiad yn ôl"
+    },
+    "error": {
+        "admin_contact": "Cysylltwch â <a>gweinyddwr eich gwasanaeth</a> i barhau i ddefnyddio'r gwasanaeth hwn.",
+        "admin_contact_short": "Cysylltwch <a>â gweinyddwr eich gweinydd</a>.",
+        "app_launch_unexpected_error": "Gwall annisgwyl wrth baratoi'r ap. Gwelwch y consol am fanylion.",
+        "cannot_load_config": "Methu llwytho ffeil ffurfweddu: adnewyddwch y dudalen i geisio eto.",
+        "connection": "Roedd problem wrth gyfathrebu gyda'r gweinydd cartref, ceisiwch eto yn nes ymlaen.",
+        "dialog_description_default": "Digwyddodd gwall.",
+        "download_media": "Wedi methu llwytho i lawrcyfryngau ffynhonnell, heb ddod o hyd i url ffynhonnell",
+        "edit_history_unsupported": "Nid yw'n ymddangos bod eich gweinydd cartref yn cefnogi'r nodwedd hon.",
+        "failed_copy": "Wedi methu â chopïo",
+        "hs_blocked": "Mae'r gweinydd cartref hwn wedi'i rwystro gan ei weinyddwr.",
+        "invalid_configuration_mixed_server": "Ffurfweddiad annilys: nid oes modd pennu default_hs_url ynghyd â default_server_matere neu default_server_config",
+        "invalid_configuration_no_server": "Ffurfweddiad annilys: heb benodi gweinydd rhagosodedig.",
+        "invalid_json": "Mae eich ffurfweddiad Elfen yn cynnwys JSON annilys. Cywirwch y broblem ac ail-lwythwch y dudalen.",
+        "invalid_json_detail": "Y neges o'r didolydd yw: %(message)s",
+        "invalid_json_generic": "JSON annilys",
+        "mau": "Mae'r gweinydd cartref hwn wedi cyrraedd terfyn Defnyddiwr Gweithredol Misol.",
+        "misconfigured": "Mae eich Elfen wedi'i cham ffurfweddu",
+        "mixed_content": "Methu cysylltu â homeserver trwy HTTP pan fydd URL HTTPS ym mar eich porwr. Naill ai defnyddiwch HTTPS neu <a>galluogwch sgriptiau anniogel</a>.",
+        "non_urgent_echo_failure_toast": "Nid yw eich gweinydd yn ymateb i rai <a>ceisiadau</a>.",
+        "resource_limits": "Mae'r gweinydd cartref hwn wedi mynd y tu hwnt i un o'i derfynau adnoddau.",
+        "session_restore": {
+            "clear_storage_button": "Clirio Storio ac Arwyddo Allan",
+            "clear_storage_description": "Allgofnodi a dileu allweddi amgryptio?",
+            "description_1": "Wedi canfod gwall wrth geisio adfer eich sesiwn flaenorol.",
+            "description_2": "Os ydych wedi defnyddio fersiwn mwy diweddar o %(brand)s o'r blaen, mae'n bosibl y bydd eich sesiwn yn anghydnaws â'r fersiwn hon. Caewch y ffenestr hon a dychwelyd i'r fersiwn mwy diweddar.",
+            "description_3": "Efallai y bydd clirio storfa eich porwr yn datrys y broblem, ond bydd yn eich allgofnodi ac yn achosi i unrhyw hanes sgwrsio sydd wedi'i amgryptio ddod yn annarllenadwy.",
+            "title": "Methu adfer y sesiwn"
+        },
+        "something_went_wrong": "Aeth rhywbeth o'i le!",
+        "storage_evicted_description_1": "Mae rhywfaint o ddata sesiwn, gan gynnwys bysellau neges wedi'u hamgryptio, ar goll. Allgofnodwch a mewngofnodwch i drwsio hyn, gan adfer allweddi o'r copi wrth gefn.",
+        "storage_evicted_description_2": "Mae'n debyg bod eich porwr wedi dileu'r data hwn wrth redeg yn isel ar ofod disg.",
+        "storage_evicted_title": "Data sesiwn ar goll",
+        "sync": "Methu cysylltu â Homeserver. Wrthi'n ailgynnig…",
+        "tls": "Methu cysylltu â homeserver - gwiriwch eich cysylltedd, sicrhewch eich bod yn ymddiried yn <a>nhystysgrif SSL eich gweinyddwr cartref</a> , ac nad yw estyniad porwr yn rhwystro ceisiadau.",
+        "unknown": "Gwall anhysbys",
+        "unknown_error_code": "cod gwall anhysbys",
+        "update_power_level": "Wedi methu â newid lefel pŵer"
+    },
+    "error_app_open_in_another_tab": "Newidiwch i'r tab arall i gysylltu â %(brand)s. Gellir cau'r tab hwn nawr.",
+    "error_app_open_in_another_tab_title": "Mae %(brand)s wedi'i gysylltu mewn tab arall",
+    "error_app_opened_in_another_window": "Mae %(brand)s ar agor mewn ffenestr arall. Cliciwch \"%(label)s\" i ddefnyddio %(brand)s yma a datgysylltu'r ffenestr arall.",
+    "error_database_closed_description": {
+        "for_desktop": "Efallai bod eich disg yn llawn. Cliriwch ychydig o le ac ail-lwythwch.",
+        "for_web": "Os gwnaethoch glirio data pori yna disgwylir y neges hon. Gall %(brand)s fod ar agor mewn tab arall hefyd, neu mae eich disg yn llawn. Cliriwch ychydig o le ac ail-lwythwch"
+    },
+    "error_database_closed_title": "Mae %(brand)s wedi stopio gweithio",
+    "error_dialog": {
+        "copy_room_link_failed": {
+            "description": "Methu â chopïo dolen i'r ystafell i'r clipfwrdd.",
+            "title": "Methu â chopïo dolen ystafell"
+        },
+        "error_loading_user_profile": "Methu llwytho proffil defnyddiwr",
+        "forget_room_failed": "Wedi methu ag anghofio ystafell %(errCode)s"
+    },
+    "error_user_not_logged_in": "Nid yw'r defnyddiwr wedi mewngofnodi",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "Galwad ar y gweill",
+            "user": "Ymunodd %(senderName)s â'r alwad",
+            "you": "Fe wnaethoch chi ymuno â'r alwad"
+        },
+        "m.call.hangup": {
+            "user": "Daeth %(senderName)s â'r alwad i ben",
+            "you": "Daethoch â'r alwad i ben"
+        },
+        "m.call.invite": {
+            "dm_receive": "Mae %(senderName)s yn galw",
+            "dm_send": "Yn aros am ateb",
+            "user": "Dechreuodd %(senderName)s alwad",
+            "you": "Rydych chi wedi dechrau galwad"
+        },
+        "m.emote": "* %(senderName)s %(emote)s",
+        "m.reaction": {
+            "user": "Ymatebodd %(sender)s %(reaction)s i %(message)s",
+            "you": "Rydych wedi ymateb %(reaction)s i %(message)s"
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Sain",
+            "file": "Ffeil",
+            "image": "Delwedd",
+            "poll": "Arolwg",
+            "video": "Fideo"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
+    },
+    "export_chat": {
+        "cancelled": "Allforio wedi'i Ddiddymu",
+        "cancelled_detail": "Cafodd yr allforyn ei ddiddymu'n llwyddiannus",
+        "confirm_stop": "Ydych chi'n siŵr eich bod am roi'r gorau i allforio eich data? Os gwnewch hynny, bydd angen i chi ddechrau o'r newydd.",
+        "creating_html": "Wrthi'n creu HTML…",
+        "creating_output": "Wrthi'n creu allbwn…",
+        "creator_summary": "Creodd %(creatorName)s yr ystafell hon.",
+        "current_timeline": "Llinell Amser Presennol",
+        "enter_number_between_min_max": "Rhowch rif rhwng %(min)s a %(max)s",
+        "error_fetching_file": "Gwall wrth nôl ffeil",
+        "export_info": "Dyma ddechrau allforio o<roomName/>. Wedi'i allforio gan<exporterDetails/> yn %(exportDate)s.",
+        "export_successful": "Allforio yn llwyddiannus!",
+        "exporting_your_data": "Allforio eich data",
+        "fetching_events": "Wrthi'n nôl digwyddiadau…",
+        "file_attached": "Ffeil wedi'i Atodi",
+        "format": "Fformat",
+        "from_the_beginning": "O'r dechrau",
+        "generating_zip": "Cynhyrchu ZIP",
+        "html": "HTML",
+        "html_title": "Data wedi'i Allforio",
+        "include_attachments": "Cynnwysch Atodiadau",
+        "json": "JSON",
+        "media_omitted": "Cyfryngau wedi'u hepgor",
+        "media_omitted_file_size": "Cyfryngau wedi'u hepgor - wedi mynd dros y terfyn maint ffeil",
+        "messages": "Negeseuon",
+        "next_page": "Grŵp nesaf o negeseuon",
+        "num_messages": "Nifer y negeseuon",
+        "num_messages_min_max": "Gall nifer y negeseuon fod dim ond yn rhif rhwng %(min)s a %(max)s",
+        "number_of_messages": "Nodwch nifer o negeseuon",
+        "previous_page": "Grŵp blaenorol o negeseuon",
+        "processing": "Wrthi'n prosesu…",
+        "processing_event_n": "Digwyddiad prosesu %(number)s allan o %(total)s",
+        "select_option": "Dewiswch o'r dewisiadau isod i allforio sgyrsiau o'ch llinell amser",
+        "size_limit": "Terfyn Maint",
+        "size_limit_min_max": "Dim ond rhwng %(min)s MB a %(max)s MB y gall maint fod",
+        "size_limit_postfix": "MB",
+        "starting_export": "Yn dechrau allforio…",
+        "successful": "Allforio yn Llwyddiannus",
+        "successful_detail": "Roedd eich allforyn yn llwyddiannus. Dewch o hyd iddo yn eich ffolder Llwytho i Lawr.",
+        "text": "Testun Plaen",
+        "title": "Allforio Sgwrs",
+        "topic": "Pwnc: %(topic)s",
+        "unload_confirm": "Ydych chi'n siŵr eich bod am adael yn ystod yr allforyn hwn?"
+    },
+    "failed_load_async_component": "Methu llwytho! Gwiriwch eich cysylltedd rhwydwaith a rhowch gynnig arall arni.",
+    "feedback": {
+        "can_contact_label": "Gallwch gysylltu â mi os oes gennych unrhyw gwestiynau dilynol",
+        "comment_label": "Sylw",
+        "existing_issue_link": "Edrychwch ar y <existingIssuesLink> materion presennol ar Github</existingIssuesLink> yn gyntaf. Dim cyfatebiaeth?<newIssueLink> Dechreuwch un newydd</newIssueLink>.",
+        "may_contact_label": "Gallwch gysylltu â mi os ydych am ddilyn neu i adael i mi brofi syniadau'r dyfodol",
+        "platform_username": "Bydd eich platfform a'ch enw defnyddiwr yn cael eu nodi i'n helpu ni i ddefnyddio'ch adborth cymaint ag y gallwn.",
+        "pro_type": "AWGRYM PRO: Os byddwch chi'n dechrau mater, cyflwynwch<debugLogsLink>logiau dadfygio</debugLogsLink> i'n helpu i ddod o hyd i'r broblem.",
+        "send_feedback_action": "Anfon adborth",
+        "sent": "Anfonwyd adborth! Diolch, rydym yn ei werthfawrogi!"
+    },
+    "file_panel": {
+        "empty_description": "Atodwch ffeiliau o sgwrs neu llusgo a gollwng nhw unrhyw le mewn ystafell.",
+        "empty_heading": "Dim ffeiliau i'w gweld yn yr ystafell hon",
+        "guest_note": "Rhaid i chi <a>gofrestru</a> i ddefnyddio'r swyddogaeth hon",
+        "peek_note": "Rhaid i chi ymuno â'r ystafell i weld ei ffeiliau"
+    },
+    "forward": {
+        "filter_placeholder": "Chwiliwch am ystafelloedd neu bobl",
+        "message_preview_heading": "Rhagolwg neges",
+        "no_perms_title": "Nid oes gennych ganiatâd i wneud hyn",
+        "open_room": "Ystafell agored",
+        "send_label": "Anfon",
+        "sending": "Anfon",
+        "sent": "Anfonwyd"
+    },
+    "identity_server": {
+        "change": "Newid gweinydd hunaniaeth",
+        "change_prompt": "Datgysylltwch o'r gweinydd hunaniaeth<current />a chysylltu â<new /> yn lle hynny?",
+        "change_server_prompt": "Os nad ydych am ddefnyddio <server /> i ganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol yr ydych yn eu hadnabod, rhowch weinydd hunaniaeth arall isod.",
+        "changed": "Mae eich gweinydd hunaniaeth wedi'i newid",
+        "checking": "Gwirio gweinydd",
+        "description_connected": "Ar hyn o bryd rydych yn defnyddio <server></server> i ganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod. Gallwch newid eich gweinydd hunaniaeth isod.",
+        "description_disconnected": "Nid ydych yn defnyddio gweinydd adnabod ar hyn o bryd. I ddarganfod a chael eich darganfod gan gysylltiadau presennol rydych chi'n eu hadnabod, ychwanegwch un isod.",
+        "description_optional": "Mae defnyddio gweinydd hunaniaeth yn ddewisol. Os byddwch yn dewis peidio â defnyddio gweinydd hunaniaeth, ni fydd defnyddwyr eraill yn gallu eich darganfod ac ni fyddwch yn gallu gwahodd eraill trwy e-bost neu ffôn.",
+        "disconnect": "Datgysylltu gweinydd hunaniaeth",
+        "disconnect_anyway": "Datgysylltwch beth bynnag",
+        "disconnect_offline_warning": "Dylech <b>dynnu eich data personol </b> oddi ar weinydd hunaniaeth <idserver /> cyn datgysylltu. Yn anffodus, gweinydd hunaniaeth <idserver /> ar hyn o bryd all-lein neu nid oes modd ei gyrraedd.",
+        "disconnect_personal_data_warning_1": "Rydych yn dal <b>i rannu eich data personol</b> ar y gweinydd hunaniaeth <idserver />.",
+        "disconnect_personal_data_warning_2": "Rydym yn argymell eich bod yn tynnu eich cyfeiriadau e-bost a rhifau ffôn oddi ar y gweinydd hunaniaeth cyn datgysylltu.",
+        "disconnect_server": "Datgysylltwch o'r gweinydd hunaniaeth <idserver />?",
+        "disconnect_warning": "Bydd datgysylltu oddi wrth eich gweinydd hunaniaeth yn golygu na fydd defnyddwyr eraill yn gallu eich darganfod ac ni fyddwch yn gallu gwahodd eraill trwy e-bost neu ffôn.",
+        "do_not_use": "Peidiwch â defnyddio gweinydd hunaniaeth",
+        "error_connection": "Methu cysylltu â gweinydd hunaniaeth",
+        "error_invalid": "Nid yw'n weinydd hunaniaeth dilys (cod statws %(code)s)",
+        "error_invalid_or_terms": "Nid yw'n derbyn telerau gwasanaeth neu mae'r gweinydd hunaniaeth yn annilys.",
+        "no_terms": "Nid oes gan y gweinydd hunaniaeth rydych chi wedi'i ddewis unrhyw delerau gwasanaeth.",
+        "suggestions": "Dylech chi:",
+        "suggestions_1": "wiro ategion eich porwr am unrhyw beth a allai rwystro'r gweinydd hunaniaeth (fel Privacy Badger)",
+        "suggestions_2": "cysylltu â gweinyddwyr gweinydd hunaniaeth <idserver />",
+        "suggestions_3": "aros a cheisio eto yn nes ymlaen",
+        "url": "Gweinydd adnabod (%(server)s)",
+        "url_field_label": "Rhowch weinydd hunaniaeth newydd",
+        "url_not_https": "Rhaid i URL gweinydd hunaniaeth fod yn HTTPS"
+    },
+    "in_space": "Yn %(spaceName)s.",
+    "in_space1_and_space2": "Mewn gofodau %(space1Name)s a %(space2Name)s.",
+    "incompatible_browser": {
+        "continue": "Parhau beth bynnag",
+        "description": "Mae %(brand)s yn defnyddio rhai nodweddion porwr nad ydyn nhw ar gael yn eich porwr presennol. %(detail)s",
+        "detail_can_continue": "Os byddwch yn parhau, efallai na fydd rhai nodweddion yn gweithio ac mae risg y gallech golli data yn y dyfodol.",
+        "detail_no_continue": "Ceisiwch ddiweddaru'r porwr hwn os nad ydych yn defnyddio'r fersiwn diweddaraf a cheisiwch eto.",
+        "learn_more": "Dysgu rhagor",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Am y profiad gorau, defnyddiwch <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge> Edge</Edge>, neu <Safari>Safari</Safari>.",
+        "title": "Nid yw %(brand)s yn cefnogi'r porwr hwn",
+        "use_desktop_heading": "Defnyddiwch %(brand)s y Bwrdd Gwaith yn lle hynny",
+        "use_mobile_heading": "Defnyddiwch %(brand)s ar ffôn symudol yn lle hynny",
+        "use_mobile_heading_after_desktop": "Neu defnyddiwch ein ap symudol",
+        "windows_64bit": "Windows (64-did)",
+        "windows_arm_64bit": "Windows (ARM 64-did)"
+    },
+    "info_tooltip_title": "Gwybodaeth",
+    "integration_manager": {
+        "connecting": "Wrthi'n cysylltu â'r rheolwr integreiddio…",
+        "error_connecting": "Mae'r rheolwr integreiddio all-lein neu ni all gyrraedd eich gweinydd cartref.",
+        "error_connecting_heading": "Methu cysylltu â'r rheolwr integreiddio",
+        "explainer": "Mae rheolwyr integreiddio yn derbyn data ffurfweddu, a gallant addasu teclynnau, anfon gwahoddiadau ystafell, a gosod lefelau pŵer ar eich rhan.",
+        "manage_title": "Rheoli integreiddiadau",
+        "toggle_label": "Galluogi'r rheolwr integreiddio",
+        "use_im": "Defnyddiwch reolwr integreiddio i reoli botiau, teclynnau a phecynnau sticeri.",
+        "use_im_default": "Defnyddiwch reolwr integreiddio <b>(%(serverName)s)</b> i reoli botiau, teclynnau a phecynnau sticeri."
+    },
+    "integrations": {
+        "disabled_dialog_description": "Galluogi '%(manageIntegrations)s' yn y Gosodiadau i wneud hyn.",
+        "disabled_dialog_title": "Mae integreiddiadau wedi'u hanalluogi",
+        "impossible_dialog_description": "Nid yw eich %(brand)s yn caniatáu i chi ddefnyddio rheolwr integreiddio i wneud hyn. Cysylltwch â gweinyddwr.",
+        "impossible_dialog_title": "Nid yw integreiddiadau yn cael eu caniatâu"
+    },
+    "invite": {
+        "ask_anyway_description": "Methu dod o hyd i broffiliau ar gyfer yr IDau Matrics a restrir isod - a hoffech chi ddechrau DM beth bynnag?",
+        "ask_anyway_label": "Dechreuwch DM beth bynnag",
+        "ask_anyway_never_warn_label": "Dechreuwch DM beth bynnag a pheidiwch byth â fy rhybuddio eto",
+        "email_caption": "Gwahodd trwy e-bost",
+        "email_limit_one": "Dim ond un ar y tro y mae moddanfon gwahoddiadau trwy e-bost",
+        "email_use_default_is": "Defnyddiwch weinydd hunaniaeth i wahodd trwy e-bost.<default> Defnyddiwch y rhagosodiad (%(defaultIdentityServerName)s)</default> neu reoli mewn<settings> Gosodiadau</settings>.",
+        "email_use_is": "Defnyddiwch weinydd hunaniaeth i wahodd trwy e-bost. Rheoli o fewn y <settings>Gosodiadau</settings>.",
+        "error_already_invited_room": "Mae'r defnyddiwr eisoes wedi'i wahodd i'r ystafell",
+        "error_already_invited_space": "Mae'r defnyddiwr eisoes wedi'i wahodd i'r gofod",
+        "error_already_joined_room": "Mae'r defnyddiwr eisoes yn yr ystafell",
+        "error_already_joined_space": "Mae'r defnyddiwr eisoes yn y gofod",
+        "error_bad_state": "Rhaid i'r defnyddiwr fod heb ei wahardd cyn y mae moddei wahodd.",
+        "error_dm": "Nid oedd modd i ni greu eich DM.",
+        "error_find_room": "Aeth rhywbeth o'i le wrth geisio gwahodd y defnyddwyr.",
+        "error_find_user_description": "Mae'n bosib nad yw'r defnyddwyr canlynol yn bodoli neu'n annilys, ac nid oes modd eu gwahodd: %(csvNames)s",
+        "error_find_user_title": "Wedi methu dod o hyd i'r defnyddwyr canlynol",
+        "error_invite": "Nid oedd modd i ni wahodd y defnyddwyr hynny. Gwiriwch y defnyddwyr yr ydych am eu gwahodd a cheisiwch eto.",
+        "error_permissions_room": "Nid oes gennych ganiatâd i wahodd pobl i'r ystafell hon.",
+        "error_permissions_space": "Nid oes gennych ganiatâd i wahodd pobl i'r gofod hwn.",
+        "error_profile_undisclosed": "Gall defnyddiwr fodoli neu beidio",
+        "error_transfer_multiple_target": "Dim ond i un defnyddiwr y mae modd trosglwyddo galwad.",
+        "error_unfederated_room": "Mae'r ystafell hon yn ddi-ffederal. Allwch chi ddim gwahodd pobl o weinyddion allanol.",
+        "error_unfederated_space": "Mae'r gofod hwn yn ddiffederal. Allwch chi ddim gwahodd pobl o weinyddion allanol.",
+        "error_unknown": "Gwall gweinydd anhysbys",
+        "error_user_not_found": "Nid yw'r defnyddiwr yn bodoli",
+        "error_version_unsupported_room": "Nid yw gweinydd cartref y defnyddiwr yn cefnogi'r fersiwn o'r ystafell.",
+        "error_version_unsupported_space": "Nid yw gweinydd cartref y defnyddiwr yn cefnogi'r fersiwn o'r gofod.",
+        "failed_generic": "Methodd y gweithrediad",
+        "failed_title": "Wedi methu â gwahodd",
+        "invalid_address": "Cyfeiriad heb ei gydnabod",
+        "name_email_mxid_share_room": "Gwahoddwch rywun i ddefnyddio ei enw, cyfeiriad e-bost, enw defnyddiwr (fel <userId/>) neu <a>rannu'r ystafell hon</a>.",
+        "name_email_mxid_share_space": "Gwahoddwch rywun i ddefnyddio ei enw, cyfeiriad e-bost, enw defnyddiwr (fel <userId/>) neu <a>rhannu'r gofod hwn</a>.",
+        "name_mxid_share_room": "Gwahoddwch rywun i ddefnyddio eu henw, enw defnyddiwr (fel <userId/>) neu <a>rannu'r ystafell hon</a>.",
+        "name_mxid_share_space": "Gwahoddwch rywun i ddefnyddio eu henw, enw defnyddiwr (fel <userId/>) neu <a>rhannu'r gofod hwn</a>.",
+        "recents_section": "Sgyrsiau Diweddar",
+        "room_failed_partial": "Rydym wedi anfon y lleill, ond nid oes modd gwahodd y bobl isod i <RoomName/>",
+        "room_failed_partial_title": "Nid oedd modd anfon rhai gwahoddiadau",
+        "room_failed_title": "Wedi methu gwahodd defnyddwyr i %(roomName)s",
+        "send_link_prompt": "Neu anfonwch ddolen wahoddiad",
+        "start_conversation_name_email_mxid_prompt": "Dechreuwch sgwrs gyda rhywun gan ddefnyddio eu henw, cyfeiriad e-bost neu enw defnyddiwr (fel <userId/>).",
+        "start_conversation_name_mxid_prompt": "Dechreuwch sgwrs gyda rhywun gan ddefnyddio eu henw neu enw defnyddiwr (fel <userId/>).",
+        "suggestions_disclaimer": "Efallai y bydd rhai awgrymiadau'n cael eu cuddio er preifatrwydd.",
+        "suggestions_disclaimer_prompt": "Os na allwch weld pwy rydych yn chwilio amdano, anfonwch eich dolen wahoddiad isod.",
+        "suggestions_section": "Neges Uniongyrchol Ddiweddar",
+        "to_room": "Gwahodd i %(roomName)s",
+        "to_space": "Gwahodd i %(spaceName)s",
+        "transfer_dial_pad_tab": "Pad deialu",
+        "transfer_user_directory_tab": "Cyfeiriadur Defnyddwyr",
+        "unable_find_profiles_description_default": "Methu dod o hyd i broffiliau ar gyfer yr IDau Matrics wedi'u rhestru isod - a hoffech chi eu gwahodd beth bynnag?",
+        "unable_find_profiles_invite_label_default": "Gwahodd beth bynnag",
+        "unable_find_profiles_invite_never_warn_label_default": "Gwahodd beth bynnag a pheidiwch byth â fy rhybuddio eto",
+        "unable_find_profiles_title": "Efallai na fydd y defnyddwyr canlynol yn bodoli",
+        "unban_first_title": "Nid oes modd gwahodd defnyddiwr nes ei fod heb ei ddad-wahardd"
+    },
+    "inviting_user1_and_user2": "Yn gwahodd %(user1)s a %(user2)s",
+    "keyboard": {
+        "activate_button": "Agor y botwm hwn",
+        "alt": "Alt",
+        "autocomplete_cancel": "Diddymu awtogwblhau",
+        "autocomplete_force": "Gorfodi cwblhau",
+        "autocomplete_navigate_next": "Awgrym awtolenwi nesaf",
+        "autocomplete_navigate_prev": "Awgrym awtolenwi blaenorol",
+        "backspace": "Backspace",
+        "cancel_reply": "Diddymu ateb neges",
+        "category_autocomplete": "Awtogwblhau",
+        "category_calls": "Galwadau",
+        "category_navigation": "Llywio",
+        "category_room_list": "Rhestr Ystafell",
+        "close_dialog_menu": "Caewch y ddewislen deialog neu gyd-destun",
+        "composer_jump_end": "Symud i ddiwedd y cyfansoddwr",
+        "composer_jump_start": "Symud i ddechrau'r cyfansoddwr",
+        "composer_navigate_next_history": "Ymlaen i'r neges nesaf yn hanes y cyfansoddwr",
+        "composer_navigate_prev_history": "Ymlaen i'r neges flaenorol yn hanes y cyfansoddwr",
+        "composer_new_line": "Llinell newydd",
+        "composer_redo": "Ail-wneud golygu",
+        "composer_toggle_bold": "Toglo Trwm",
+        "composer_toggle_code_block": "Toglo Bloc Cod",
+        "composer_toggle_italics": "Toglo Italics",
+        "composer_toggle_link": "Toglo Dolen",
+        "composer_toggle_quote": "Toglo Dyfyniad",
+        "composer_undo": "Dadwneud golygu",
+        "control": "Ctrl",
+        "dismiss_read_marker_and_jump_bottom": "Diystyru'r marciwr darllen a symud i'r gwaelod",
+        "end": "Diwedd",
+        "enter": "Enter",
+        "escape": "Esc",
+        "go_home_view": "Mynd i Golwg Cartref",
+        "home": "Cartref",
+        "jump_first_message": "Symud i'r neges gyntaf",
+        "jump_last_message": "Symud i'r neges olaf",
+        "jump_room_search": "Symud i chwilio ystafelloedd",
+        "jump_to_read_marker": "Symud i'r neges hynaf heb ei darllen",
+        "keyboard_shortcuts_tab": "Agor y tab gosodiadau hwn",
+        "navigate_next_history": "Ymwelwyd ag ystafell neu ofod ddiweddar nesaf",
+        "navigate_next_message_edit": "Ymlaen i'r neges nesaf i'w golygu",
+        "navigate_prev_history": "Ystafell neu ofod yr ymwelwyd â hwy yn ddiweddar",
+        "navigate_prev_message_edit": "Symud i'r neges flaenorol i'w golygu",
+        "next_landmark": "Mynd i'r tirnod nesaf",
+        "next_room": "Ystafell nesaf neu DM",
+        "next_unread_room": "Ystafell nesaf heb ei darllen neu DM",
+        "number": "[number]",
+        "open_user_settings": "Agor gosodiadau defnyddiwr",
+        "page_down": "Tudalen i Lawr",
+        "page_up": "Tudalen i Fyny",
+        "prev_landmark": "Mynd i'r tirnod blaenorol",
+        "prev_room": "Ystafell flaenorol neu DM",
+        "prev_unread_room": "Ystafell flaenorol heb ei darllen neu DM",
+        "room_list_collapse_section": "Lleihau adran rhestr ystafelloedd",
+        "room_list_expand_section": "Ehangu'r adran rhestr ystafelloedd",
+        "room_list_navigate_down": "Symud i lawr yn y rhestr ystafelloedd",
+        "room_list_navigate_up": "Symud i fyny yn y rhestr ystafelloedd",
+        "room_list_select_room": "Dewis ystafell o'r rhestr ystafelloedd",
+        "scroll_down_timeline": "Sgrolio i lawr yn y llinell amser",
+        "scroll_up_timeline": "Sgrolio i fyny yn y llinell amser",
+        "search": "Chwilio (rhaid ei alluogi)",
+        "send_sticker": "Anfon sticer",
+        "shift": "Shift",
+        "space": "Gofod",
+        "switch_to_space": "Newid i'r gofod yn ôl rhif",
+        "toggle_hidden_events": "Toglo gwelededd digwyddiad cudd",
+        "toggle_microphone_mute": "Toglo tewi meicroffon",
+        "toggle_right_panel": "Toglo panel dde",
+        "toggle_space_panel": "Toglo panel gofod",
+        "toggle_top_left_menu": "Toglo'r ddewislen chwith uchaf",
+        "toggle_webcam_mute": "Toglo camera gwe ymlaen / i ffwrdd",
+        "upload_file": "Llwytho ffeil i fyny"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "Caniatáu modd rhannu sgrin yn unig",
+        "ask_to_join": "Galluogi gofyn i ymuno",
+        "automatic_debug_logs": "Anfon logiau dadfygio yn awtomatig ar unrhyw wall",
+        "automatic_debug_logs_decryption": "Anfon logiau dadgryptio yn awtomatig ar wallau dadgryptio",
+        "automatic_debug_logs_key_backup": "Anfon logiau dadfygio yn awtomatig pan nad yw copi wrth gefn allweddol yn gweithio",
+        "beta_description": "Beth sydd nesaf i %(brand)s? Labs yw'r ffordd orau o gael pethau'n gynnar, profi nodweddion newydd a helpu i'w siapio cyn iddynt gael eu lansio.",
+        "beta_feature": "Mae hon yn nodwedd beta",
+        "beta_feedback_leave_button": "I adael y beta, ewch i'ch gosodiadau.",
+        "beta_feedback_title": "Adborth beta %(featureName)s",
+        "beta_section": "Nodweddion sy ar eu ffordd",
+        "bridge_state": "Dangos gwybodaeth am bontydd mewn gosodiadau ystafell",
+        "bridge_state_channel": "Sianel:<channelLink/>",
+        "bridge_state_creator": "Darparwyd y bont hon gan<user />.",
+        "bridge_state_manager": "Mae'r bont hon yn cael ei rheoli gan <user />.",
+        "bridge_state_workspace": "Gweithle: <networkLink/>",
+        "click_for_info": "Cliciwch am fwy o wybodaeth",
+        "currently_experimental": "Arbrofol ar hyn o bryd.",
+        "custom_themes": "Cefnogi ychwanegu themâu personol",
+        "dynamic_room_predecessors": "Rhagflaenwyr ystafell deimaterig",
+        "dynamic_room_predecessors_description": "Galluogi MSC3946 (i gefnogi archifau ystafelloedd sy'n cyrraedd yn hwyr)",
+        "element_call_video_rooms": "Ystafelloedd fideo Element Call",
+        "exclude_insecure_devices": "Gwahardd dyfeisiau anniogel wrth anfon/derbyn negeseuon",
+        "exclude_insecure_devices_description": "Pan fydd y modd hwn wedi'i alluogi, ni fydd negeseuon wedi'u hamgryptio yn cael eu rhannu â dyfeisiau heb eu gwirio, a bydd negeseuon o ddyfeisiau heb eu gwirio yn cael eu dangos fel gwall. Sylwch, os ydych chi'n galluogi'r modd hwn, efallai na fyddwch chi'n gallu cyfathrebu â defnyddwyr nad ydyn nhw wedi gwirio eu dyfeisiau.",
+        "experimental_description": "Teimlo'n arbrofol? Rhowch gynnig ar ein syniadau diweddaraf sy'n cael eu datblygu. Nid yw'r nodweddion hyn yn derfynol; gallant fod yn ansefydlog, gallant newid, neu gallant gael eu gollwng yn gyfan gwbl. <a>Dysgu rhagor</a>.",
+        "experimental_section": "Rhagolygon cynnar",
+        "extended_profiles_msc_support": "Mae angen i'ch gweinydd gefnogi MSC4133",
+        "feature_disable_call_per_sender_encryption": "Analluogi amgryptio fesul anfonwr ar gyfer Element Call",
+        "feature_wysiwyg_composer_description": "Defnyddiwch destun cyfoethog yn lle Markdown yng nghyfansoddwr y neges.",
+        "group_calls": "Profiad galwad grŵp newydd",
+        "group_developer": "Datblygwr",
+        "group_encryption": "Amgryptio",
+        "group_experimental": "Arbrofol",
+        "group_messaging": "Negeseuon",
+        "group_moderation": "Cymedroil",
+        "group_profile": "Proffil",
+        "group_rooms": "Ystafelloedd",
+        "group_spaces": "Gofodau",
+        "group_themes": "Themâu",
+        "group_threads": "Edafedd",
+        "group_ui": "Rhyngwyneb",
+        "group_voip": "Llais a Fideo",
+        "group_widgets": "Teclynnau",
+        "hidebold": "Cuddio dot hysbysu (dangos bathodynnau cownteri yn unig)",
+        "html_topic": "Dangos cynrychiolaeth HTML o bynciau ystafell",
+        "join_beta": "Ymunwch â'r beta",
+        "join_beta_reload": "Bydd ymuno â'r beta yn ail-lwytho %(brand)s.",
+        "jump_to_date": "Symud i ddyddiad (ychwanegu / jumtodate a symud i benynnau dyddiad)",
+        "jump_to_date_msc_support": "Mae angen i'ch gweinydd gefnogi MSC3030",
+        "latex_maths": "Rendro mathemateg LaTeX mewn negeseuon",
+        "leave_beta": "Gadael y beta",
+        "leave_beta_reload": "Bydd gadael y beta yn ail-lwytho %(brand)s.",
+        "location_share_live": "Rhannu Lleoliad Byw",
+        "location_share_live_description": "Gweithredu dros dro. Mae lleoliadau'n parhau yn hanes ystafelloedd.",
+        "mjolnir": "Ffyrdd newydd o anwybyddu pobl",
+        "msc3531_hide_messages_pending_moderation": "Gadael i cymedrolwyr guddio negeseuon tra'n aros i'w gymedroli.",
+        "new_room_list": "Galluogi rhestr ystafelloedd newydd",
+        "notification_settings": "Gosodiadau Hysbysu Newydd",
+        "notification_settings_beta_caption": "Cyflwyno ffordd symlach o newid eich gosodiadau hysbysu. Addaswch eich %(brand)s, yn union fel y dymunwch.",
+        "notification_settings_beta_title": "Gosodiadau Hysbysiadau",
+        "notifications": "Galluogi'r panel hysbysiadau ym mhennyn yr ystafell",
+        "release_announcement": "Cyhoeddiad rhyddhau",
+        "render_reaction_images": "Rendro delweddau personol mewn adweithiau",
+        "render_reaction_images_description": "Weithiau'n cael eu hadnabod fel \"emojis cyfaddas\".",
+        "report_to_moderators": "Adrodd i cymedrolwyr",
+        "report_to_moderators_description": "Mewn ystafelloedd sy'n cefnogi cymedroli, bydd y botwm “Adrodd” yn gadael i chi adrodd ar gam-drin i gymedrolwyr ystafelloedd.",
+        "sliding_sync": "Modd Cydweddu Llithro",
+        "sliding_sync_description": "Wrthi'n datblygu, nid oes modd ei analluogi.",
+        "sliding_sync_disabled_notice": "Allgofnodwch a mewngofnodi i analluogi",
+        "sliding_sync_server_no_support": "Nid oes gan eich gweinydd gefnogaeth",
+        "under_active_development": "Yn cael ei ddatblygu.",
+        "unrealiable_e2e": "Annibynadwy mewn ystafelloedd wedi'u hamgryptio",
+        "video_rooms": "Ystafelloedd fideo",
+        "video_rooms_a_new_way_to_chat": "Ffordd newydd o sgwrsio dros lais a fideo yn %(brand)s.",
+        "video_rooms_always_on_voip_channels": "Mae ystafelloedd fideo bob amser yn sianeli VoIP sydd wedi'u mewnosod o fewn ystafell yn %(brand)s.",
+        "video_rooms_beta": "Mae ystafelloedd fideo yn nodwedd beta",
+        "video_rooms_faq1_answer": "Defnyddiwch y botwm “+” yn adran ystafell y panel chwith.",
+        "video_rooms_faq1_question": "Sut mae modd creu ystafell fideo?",
+        "video_rooms_faq2_answer": "Oes, mae'r llinell amser sgwrsio yn cael ei harddangos ochr yn ochr â'r fideo.",
+        "video_rooms_faq2_question": "A oes modd defnyddio sgwrs testun ochr yn ochr â'r alwad fideo?",
+        "video_rooms_feedbackSubheading": "Diolch am roi cynnig ar y beta, ewch i gymaint o fanylion ag y gallwch fel y gallwn ei wella.",
+        "wysiwyg_composer": "Golygydd testun cyfoethog"
+    },
+    "labs_mjolnir": {
+        "advanced_warning": "⚠ Mae'r gosodiadau hyn wedi'u bwriadu ar gyfer defnyddwyr uwch.",
+        "ban_reason": "Wedi'i anwybyddu/rhwystro",
+        "error_adding_ignore": "Gwall wrth ychwanegu defnyddiwr/gweinydd a anwybyddwyd",
+        "error_adding_list_description": "Gwiriwch ID yr ystafell neu'r cyfeiriad a cheisiwch eto.",
+        "error_adding_list_title": "Gwall wrth danysgrifio i'r rhestr",
+        "error_removing_ignore": "Gwall wrth ddileu defnyddiwr/gweinydd a anwybyddwyd",
+        "error_removing_list_description": "Ceisiwch eto neu edrychwch ar eich consol am awgrymiadau.",
+        "error_removing_list_title": "Gwall wrth ddad-danysgrifio o'r rhestr",
+        "explainer_1": "Ychwanegwch ddefnyddwyr a gweinyddwyr yr ydych am eu hanwybyddu yma. Defnyddiwch seren i gael %(brand)s cyfateb unrhyw nodau. Er enghraifft, byddai <code>@bot:*</code> yn anwybyddu pob defnyddiwr sydd â'r enw 'bot' ar unrhyw weinydd.",
+        "explainer_2": "Mae anwybyddu pobl yn cael ei wneud trwy restrau gwahardd sy'n cynnwys rheolau ar gyfer pwy i'w gwahardd. Mae tanysgrifio i restr wahardd yn golygu y bydd y defnyddwyr/gweinyddion sydd wedi'u rhwystro gan y rhestr honno yn cael eu cuddio oddi wrthych.",
+        "lists": "Rydych chi wedi tanysgrifio i:",
+        "lists_description_1": "Bydd tanysgrifio i restr wahardd yn achosi ichi ymuno â hi!",
+        "lists_description_2": "Os nad dyma'r hyn yr ydych ei eisiau, defnyddiwch offeryn gwahanol i anwybyddu defnyddwyr.",
+        "lists_heading": "Rhestrau tanysgrifiedig",
+        "lists_new_label": "ID ystafell neu gyfeiriad y rhestr wahardd",
+        "no_lists": "Nid ydych wedi eich tanysgrifio i unrhyw restrau",
+        "personal_description": "Mae eich rhestr gwaharddiadau personol yn dal yr holl ddefnyddwyr/gweinyddion nad ydych chi'n bersonol am weld negeseuon ganddynt. Ar ôl anwybyddu'ch defnyddiwr/gweinydd cyntaf, bydd ystafell newydd yn ymddangos yn eich rhestr ystafelloedd o'r enw '%(myBanList)s' - arhoswch yn yr ystafell hon i gadw'r rhestr wahardd mewn grym.",
+        "personal_empty": "Nid ydych wedi anwybyddu neb.",
+        "personal_heading": "Rhestr gwaharddiad personol",
+        "personal_new_label": "Gweinydd neu ID defnyddiwr i'w anwybyddu",
+        "personal_new_placeholder": "ee: @bot:* neu example.org",
+        "personal_section": "Ar hyn o bryd rydych yn anwybyddu:",
+        "room_name": "Fy Rhestr Gwaharddiadau",
+        "room_topic": "Dyma'ch rhestr o ddefnyddwyr/gweinyddion rydych chi wedi'u rhwystro - peidiwch â gadael yr ystafell!",
+        "rules_empty": "Dim",
+        "rules_server": "Rheolau'r gweinydd",
+        "rules_title": "Rheolau rhestr gwahardd - %(roomName)s",
+        "rules_user": "Rheolau defnyddiwr",
+        "something_went_wrong": "Aeth rhywbeth o'i le. Ceisiwch eto neu edrychwch ar eich consol am awgrymiadau.",
+        "title": "Defnyddwyr wedi'u hanwybyddu",
+        "view_rules": "Gweld rheolau"
+    },
+    "language_dropdown_label": "Cwymplen Iaith",
+    "leave_room_dialog": {
+        "last_person_warning": "Chi yw'r unig berson yma. Os byddwch yn gadael, ni fydd neb yn gallu ymuno yn y dyfodol, gan gynnwys chi.",
+        "leave_room_question": "Ydych chi'n siŵr eich bod am adael ystafell '%(roomName)s'?",
+        "leave_space_question": "Ydych chi'n siŵr eich bod am adael y bwlch '%(spaceName)s'?",
+        "room_leave_admin_warning": "Chi yw'r unig weinyddwr yn yr ystafell hon. Os byddwch yn gadael, ni fydd neb yn gallu newid gosodiadau ystafell na chymryd camau pwysig eraill.",
+        "room_leave_mod_warning": "Chi yw'r unig gymedrolwr yn yr ystafell hon. Os byddwch yn gadael, ni fydd neb yn gallu newid gosodiadau ystafell na chymryd camau pwysig eraill.",
+        "room_rejoin_warning": "Nid yw'r ystafell hon yn gyhoeddus. Fyddwch chi ddim yn gallu ailymuno heb wahoddiad.",
+        "space_rejoin_warning": "Nid yw'r gofod hwn yn gyhoeddus. Fyddwch chi ddim yn gallu ailymuno heb wahoddiad."
+    },
+    "left_panel": {
+        "open_dial_pad": "Agor y pad deialu"
+    },
+    "lightbox": {
+        "rotate_left": "Cylchdroi i'r chwith",
+        "rotate_right": "Cylchdroi i'r Dde",
+        "title": "Golwg delwedd"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "Nid yw'r gweinydd cartref hwn wedi'i ffurfweddu i ddangos mapiau.",
+        "MapStyleUrlNotReachable": "Nid yw'r gweinydd cartref hwn wedi'i ffurfweddu'n gywir i ddangos mapiau, neu efallai na fydd modd cyrraedd y gweinydd mapiau sydd wedi'i ffurfweddu.",
+        "WebGLNotEnabled": "Mae angen WebGL i ddangos mapiau, galluogwch ef yng ngosodiadau eich porwr.",
+        "click_drop_pin": "Cliciwch i ollwng pin",
+        "click_move_pin": "Cliciwch i symud y pin",
+        "close_sidebar": "Caewch y bar ochr",
+        "error_fetch_location": "Methu nôl lleoliad",
+        "error_no_perms_description": "Mae angen i chi gael y caniatâd cywir er mwyn rhannu lleoliadau yn yr ystafell hon.",
+        "error_no_perms_title": "Nid oes gennych ganiatâd i rannu lleoliadau",
+        "error_send_description": "Nid oedd modd i %(brand)s anfon eich lleoliad. Ceisiwch eto yn nes ymlaen.",
+        "error_send_title": "Nid oedd modd i ni anfon eich lleoliad",
+        "error_sharing_live_location": "Digwyddodd gwall wrth rannu eich lleoliad byw",
+        "error_stopping_live_location": "Digwyddodd gwall wrth atal eich lleoliad byw",
+        "expand_map": "Ehangu'r map",
+        "failed_generic": "Wedi methu â nôl eich lleoliad. Ceisiwch eto yn nes ymlaen.",
+        "failed_load_map": "Methu'r llwytho map",
+        "failed_permission": "Gwrthodwyd caniatâd i %(brand)s nol eich lleoliad. Caniatewch fynediad lleoliad yng ngosodiadau eich porwr.",
+        "failed_timeout": "Daeth yr amser i ben yn ceisio nôl eich lleoliad. Ceisiwch eto yn nes ymlaen.",
+        "failed_unknown": "Gwall anhysbys wrth nôl lleoliad. Ceisiwch eto yn nes ymlaen.",
+        "find_my_location": "Canfod fy lleoliad",
+        "live_description": "Lleoliad byw %(displayName)s",
+        "live_enable_description": "Sylwch: nodwedd Labs yw hon sy'n defnyddio gweithrediad dros dro. Mae hyn yn golygu na fyddwch yn gallu dileu hanes eich lleoliad, a bydd defnyddwyr uwch yn gallu gweld eich hanes lleoliad hyd yn oed ar ôl i chi roi'r gorau i rannu eich lleoliad byw gyda'r ystafell hon.",
+        "live_enable_heading": "Rhannu lleoliad byw",
+        "live_location_active": "Rydych chi'n rhannu eich lleoliad byw",
+        "live_location_enabled": "Lleoliad byw wedi'i alluogi",
+        "live_location_ended": "Daeth lleoliad byw i ben",
+        "live_location_error": "Gwall lleoliad byw",
+        "live_locations_empty": "Dim lleoliadau byw",
+        "live_share_button": "Rhannu ar gyfer %(duration)s",
+        "live_toggle_label": "Galluogi rhannu lleoliad byw",
+        "live_until": "Byw tan %(expiryTime)s",
+        "live_update_time": "Wedi diweddaru %(humanizedUpdateTime)s",
+        "loading_live_location": "Wrthi'n llwytho lleoliad byw…",
+        "location_not_available": "Nid yw'r lleoliad ar gael",
+        "map_feedback": "Adborth map",
+        "mapbox_logo": "Logo Mapbox",
+        "reset_bearing": "Ailosod y cyfeiriant i'r gogledd",
+        "share_button": "Rhannu lleoliad",
+        "share_type_live": "Fy lleoliad byw",
+        "share_type_own": "Fy lleoliad presennol",
+        "share_type_pin": "Gollwng Pin",
+        "share_type_prompt": "Pa fath o leoliad ydych chi am ei rannu?",
+        "toggle_attribution": "Toglo priodoli"
+    },
+    "member_list": {
+        "filter_placeholder": "Hidlo aelodau'r ystafell",
+        "invite_button_no_perms_tooltip": "Nid oes gennych ganiatâd i wahodd defnyddwyr",
+        "invited_label": "Gwahoddwyd",
+        "no_matches": "Dim cyfatebiaeth",
+        "power_label": "%(userName)s (power %(powerLevelNumber)s)"
+    },
+    "member_list_back_action_label": "Aelodau'r ystafell",
+    "message_edit_dialog_title": "Golygiadau neges",
+    "migrating_crypto": "Arhoswch... Rydym yn diweddaru %(brand)s i wneud amgryptio yn gyflymach ac yn fwy dibynadwy.",
+    "mobile_guide": {
+        "toast_accept": "Defnyddiwch yr ap",
+        "toast_description": "Mae %(brand)s yn arbrofol ar borwr gwe symudol. I gael profiad gwell a'r nodweddion diweddaraf, defnyddiwch ein app brodorol rhad ac am ddim.",
+        "toast_title": "Defnyddiwch yr ap i gael profiad gwell"
+    },
+    "name_and_id": "%(matere)s (%(userId)s)",
+    "no_more_results": "Dim mwy o ganlyniadau",
+    "notif_panel": {
+        "empty_description": "Nid oes gennych unrhyw hysbysiadau gweladwy.",
+        "empty_heading": "Rydych yn gyfredol"
+    },
+    "notifications": {
+        "all_messages": "Pob neges",
+        "all_messages_description": "Cael gwybod am bob neges",
+        "class_global": "Eang",
+        "class_other": "Arall",
+        "default": "Rhagosodedig",
+        "default_settings": "Cydweddu'r gosodiadau rhagosodedig",
+        "email_pusher_app_display_name": "Hysbysiadau E-bost",
+        "enable_prompt_toast_description": "Galluogi hysbysiadau bwrdd gwaith",
+        "enable_prompt_toast_title": "Hysbysiadau",
+        "enable_prompt_toast_title_from_message_send": "Peidiwch â cholli ateb",
+        "error_change_title": "Newid gosodiadau hysbysu",
+        "keyword": "Allweddair",
+        "keyword_new": "Allweddair newydd",
+        "level_activity": "Gweithgaredd",
+        "level_highlight": "Uchafbwyntiau",
+        "level_muted": "Wedi tewi",
+        "level_none": "Dim",
+        "level_notification": "Hysbysiad",
+        "level_unsent": "Heb ei anfon",
+        "mark_all_read": "Marcio'r cyfan wedi'u darllen",
+        "mentions_and_keywords": "@crybwylliadau ac allweddeiriau",
+        "mentions_and_keywords_description": "Dim ond gyda chyfeiriadau ac allweddeiriau fel y'u gosodwyd yn eich <a>gosodiadau</a> y cewch eich hysbysu",
+        "mentions_keywords": "Crybwylliadau ac allweddeiriau",
+        "message_didnt_send": "Heb anfon y neges. Cliciwch am wybodaeth.",
+        "mute_description": "Fyddwch chi ddim yn cael unrhyw hysbysiadau",
+        "mute_room": "Tewi'r ystafell"
+    },
+    "notifier": {
+        "m.key.verification.request": "Mae %(matere)s yn gofyn am ddilysiad"
+    },
+    "onboarding": {
+        "create_room": "Creu Sgwrs Grŵp",
+        "explore_rooms": "Archwilio Ystafelloedd Cyhoeddus",
+        "has_avatar_label": "Gwych, bydd hynny'n helpu pobl i wybod mai chi ydyw",
+        "intro_byline": "Yn berchen ar eich sgyrsiau.",
+        "intro_welcome": "Croeso i %(appName)s",
+        "no_avatar_label": "Ychwanegwch lun fel bod pobl yn gwybod mai chi sydd yno.",
+        "send_dm": "Anfon Neges Uniongyrchol",
+        "welcome_detail": "Nawr, gadewch i ni eich helpu i ddechrau",
+        "welcome_user": "Croeso %(name)s"
+    },
+    "pill": {
+        "permalink_other_room": "Neges yn %(room)s",
+        "permalink_this_room": "Neges gan %(user)s"
+    },
+    "poll": {
+        "create_poll_action": "Creu Arolwg",
+        "create_poll_title": "Creu arolwg barn",
+        "disclosed_notes": "Mae'r pleidleiswyr yn gweld canlyniadau cyn gynted ag y byddan nhw wedi pleidleisio",
+        "edit_poll_title": "Golygu arolwg barn",
+        "end_description": "Ydych chi'n siŵr eich bod am ddod â'r arolwg barn hwn i ben? Bydd hyn yn dangos canlyniadau terfynol y bleidlais ac yn atal pobl rhag gallu pleidleisio.",
+        "end_message": "Mae'r arolwg barn wedi dod i ben. Ateb gorau: %(topAnswer)s",
+        "end_message_no_votes": "Mae'r arolwg barn wedi dod i ben. Neb wedi pleidleisio.",
+        "end_title": "Diwedd yr Arolwg",
+        "error_ending_description": "Ymddiheuriadau, nid yw'r bleidlais wedi dod i ben. Ceisiwch eto.",
+        "error_ending_title": "Wedi methu â dod â'r bleidlais i ben",
+        "error_voting_description": "Ymddiheuriadau, nid yw eich pleidlais wedi'i chofrestru. Ceisiwch eto.",
+        "error_voting_title": "Pleidlais heb ei chofrestru",
+        "failed_send_poll_description": "Ymddiheuriadau, nid yw'ch arolwg wedi ei phostio.",
+        "failed_send_poll_title": "Wedi methu â phostio'r bleidlais",
+        "notes": "Dim ond pan fyddwch chi'n dod â'r bleidlais i ben y caiff canlyniadau eu datgelu",
+        "options_add_button": "Ychwanegu dewis",
+        "options_heading": "Creu dewisiadau",
+        "options_label": "Dewis %(number)s",
+        "options_placeholder": "Ysgrifennwch ddewis",
+        "topic_heading": "Beth yw cwestiwn neu bwnc eich arolwg barn?",
+        "topic_label": "Cwestiwn neu bwnc",
+        "topic_placeholder": "Ysgrifennu rhywbeth…",
+        "total_decryption_errors": "Oherwydd gwallau dadgryptio, efallai na fydd rhai pleidleisiau'n cael eu cyfrif",
+        "total_no_votes": "Dim pleidleisiau wedi'u bwrw",
+        "total_not_ended": "Bydd y canlyniadau i'w gweld pan ddaw'r bleidlais i ben",
+        "type_closed": "Arolwg wedi'i chau",
+        "type_heading": "Math o bleidlais",
+        "type_open": "Agor arolwg",
+        "unable_edit_description": "Ymddiheuriadau, allwch chi ddim golygu arolwg barn ar ôl i'r pleidleisiau gael eu bwrw.",
+        "unable_edit_title": "Methu golygu'r arolwg barn"
+    },
+    "power_level": {
+        "admin": "Gweinyddwr",
+        "custom": "(%(level)s) cyfaddas",
+        "custom_level": "Lefel Cyfaddas",
+        "default": "Rhagosodedig",
+        "label": "Lefel pŵer",
+        "moderator": "Cymedrolwr",
+        "restricted": "Cyfyngedig"
+    },
+    "powered_by_matrix": "Wedi'i bweru gan Matrix",
+    "powered_by_matrix_with_logo": "Sgwrs ddatganoledig, wedi'i hamgryptio &amp; cydweithrediad wedi'i bweru gan $matrixLogo",
+    "presence": {
+        "away": "I ffwrdd",
+        "busy": "Prysur",
+        "idle": "Yn segur",
+        "idle_for": "Segur am %(duration)s",
+        "offline": "All-lein",
+        "offline_for": "All-lein am %(duration)s",
+        "online": "Ar-lein",
+        "online_for": "Ar-lein am %(duration)s",
+        "unknown": "Anhysbys",
+        "unknown_for": "Anhysbys am %(duration)s",
+        "unreachable": "Nid oes modd cyrraedd gweinydd y defnyddiwr"
+    },
+    "quick_settings": {
+        "all_settings": "Pob gosodiad",
+        "metaspace_section": "Pinio i'r bar ochr",
+        "sidebar_settings": "Rhagor o ddewisiadau",
+        "title": "Gosodiadau cyflym"
+    },
+    "quit_warning": {
+        "call_in_progress": "Mae'n ymddangos eich bod mewn galwad, a ydych chi'n siŵr eich bod am roi'r gorau iddi?",
+        "file_upload_in_progress": "Mae'n ymddangos eich bod yn llwytho ffeiliau i fyny, a ydych yn siŵr eich bod am roi'r gorau iddi?"
+    },
+    "redact": {
+        "confirm_button": "Cadarnhau Tynnu",
+        "confirm_description": "Ydych chi'n siŵr eich bod am ddileu'r digwyddiad hwn?",
+        "confirm_description_state": "Sylwch y gall dileu newidiadau ystafell fel hyn ddadwneud y newid.",
+        "error": "Allwch chi ddim ddileu'r neges hon. (%(code)s)",
+        "ongoing": "Wrthi'n tynnu…",
+        "reason_label": "Rheswm (dewisol)"
+    },
+    "report_content": {
+        "description": "Bydd adrodd y neges hon yn anfon ei 'ID digwyddiad' unigryw at weinyddwr eich gweinyddwr cartref. Os yw negeseuon yn yr ystafell hon wedi'u hamgryptio, ni fydd gweinyddwr eich gweinydd cartref yn gallu darllen testun y neges na gweld unrhyw ffeiliau neu ddelweddau.",
+        "disagree": "Anghytuno",
+        "error_create_room_moderation_bot": "Methu creu ystafell gyda bot cymedroli",
+        "hide_messages_from_user": "Gwiriwch a ydych am guddio'r holl negeseuon presennol ac yn y dyfodol gan y defnyddiwr hwn.",
+        "ignore_user": "Anwybyddu defnyddiwr",
+        "illegal_content": "Cynnwys Anghyfreithlon",
+        "missing_reason": "Dywedwch pam eich bod yn adrodd.",
+        "nature": "Dewiswch natur a disgrifiwch beth sy'n gwneud y neges hon yn ddifrïol.",
+        "nature_disagreement": "Mae'r hyn y mae'r defnyddiwr hwn yn ei ysgrifennu yn anghywir.\nBydd hyn yn cael ei adrodd i gymedrolwyr yr ystafell.",
+        "nature_illegal": "Mae'r defnyddiwr hwn yn dangos ymddygiad anghyfreithlon, er enghraifft trwy fygwth pobl neu fygwth trais.\nBydd hyn yn cael ei adrodd i'r cymedrolwyr ystafell a all uwchgyfeirio hyn i awdurdodau cyfreithiol.",
+        "nature_nonstandard_admin": "Mae'r ystafell hon wedi'i neilltuo ar gyfer cynnwys anghyfreithlon neu wenwynig neu mae'r cymedrolwyr yn methu â chymedroli cynnwys anghyfreithlon neu wenwynig.\nBydd hyn yn cael ei adrodd i weinyddwyr %(homeserver)s.",
+        "nature_nonstandard_admin_encrypted": "Mae'r ystafell hon wedi'i neilltuo ar gyfer cynnwys anghyfreithlon neu wenwynig neu mae'r cymedrolwyr yn methu â chymedroli cynnwys anghyfreithlon neu wenwynig.\nBydd hyn yn cael ei adrodd i weinyddwyr %(homeserver)s. NI fydd y gweinyddwyr yn gallu darllen cynnwys yr ystafell hon wedi'i amgryptio.",
+        "nature_other": "Unrhyw reswm arall. Disgrifiwch y broblem.\nBydd hyn yn cael ei adrodd i'r cymedrolwyr ystafell.",
+        "nature_spam": "Mae'r defnyddiwr hwn yn sbamio'r ystafell gyda hysbysebion, dolenni i hysbysebion neu i bropaganda.\nBydd hyn yn cael ei adrodd i'r cymedrolwyr ystafell.",
+        "nature_toxic": "Mae'r defnyddiwr hwn yn arddangos ymddygiad gwenwynig, er enghraifft trwy sarhau defnyddwyr eraill neu rannu cynnwys oedolion yn unig mewn ystafell sy'n gyfeillgar i deuluoedd neu dorri rheolau'r ystafell hon fel arall.\nBydd hyn yn cael ei adrodd i'r cymedrolwyr ystafell.",
+        "other_label": "Arall",
+        "report_content_to_homeserver": "Adrodd Cynnwys i'ch Gweinyddwr Homeserver",
+        "report_entire_room": "Rhoi gwybod am yr ystafell gyfan",
+        "spam_or_propaganda": "Sbam neu bropaganda",
+        "toxic_behaviour": "Ymddygiad Gwenwynig"
+    },
+    "report_room": {
+        "description": "Adroddwch ar yr ystafell hon i weinyddwr eich gweinydd cartref. Bydd hyn yn anfon ID unigryw'r ystafell, ond os yw negeseuon wedi'u hamgryptio, fydd y gweinyddwr ddim yn gallu eu darllen na gweld ffeiliau sy'n cael eu rhannu.",
+        "reason_label": "Disgrifiwch y rheswm"
+    },
+    "restore_key_backup_dialog": {
+        "count_of_decryption_failures": "Wedi methu â dadgryptio %(failedCount)s sesiwn!",
+        "count_of_successfully_restored_keys": "Wedi adfer %(sessionCount)s allwedd yn llwyddiannus",
+        "enter_key_description": "Cyrchwch hanes eich negeseuon diogel a gosodwch negeseuon diogel trwy nodi'ch Allwedd Adfer.",
+        "enter_key_title": "Rhowch Allwedd Adfer",
+        "enter_phrase_description": "Cyrchwch hanes eich neges ddiogel a gosodwch negeseuon diogel trwy nodi'ch Ymadrodd Diogelwch.",
+        "enter_phrase_title": "Rhowch Ymadrodd Diogelwch",
+        "incorrect_security_phrase_dialog": "Nid oedd modd dadgryptio copi wrth gefn gyda'r Ymadrodd Diogelwch hwn: gwiriwch eich bod wedi rhoi'r Ymadrodd Diogelwch cywir.",
+        "incorrect_security_phrase_title": "Ymadrodd Diogelwch Anghywir",
+        "key_backup_warning": "<b>Rhybudd</b>: dim ond o gyfrifiadur rydych chi'n ymddiried ynddo y dylech chi greu allwedd wrth gefn.",
+        "key_fetch_in_progress": "Wrthi'n nôl allweddi o'r gweinydd…",
+        "key_forgotten_text": "Os ydych wedi anghofio eich Allwedd Adfer gallwch <button> greu dewisiadau adfer newydd</button>",
+        "key_is_invalid": "Ddim yn Allwedd Adfer ddilys",
+        "key_is_valid": "Mae hwn yn edrych fel Allwedd Adfer ddilys!",
+        "keys_restored_title": "Allweddi wedi'u hadfer",
+        "load_error_content": "Methu llwytho statws wrth gefn",
+        "load_keys_progress": "%(completed)s o %(total)s allweddi wedi'u hadfer",
+        "no_backup_error": "Heb ganfod copi wrth gefn!",
+        "phrase_forgotten_text": "Os ydych chi wedi anghofio eich Ymadrodd Diogelwch gallwch chi <button1>defnyddio eich Allwedd Adfer</button1> neu <button2>greu dewisiadau adfer newydd</button2>",
+        "recovery_key_mismatch_description": "Nid oedd modd dadgryptio copi wrth gefn gyda'r Allwedd Adfer hon: gwiriwch eich bod wedi rhoi'r Allwedd Adfer cywir.",
+        "recovery_key_mismatch_title": "Dim cyfatebiaeth Allwedd Adfer",
+        "restore_failed_error": "Methu adfer copi wrth gefn"
+    },
+    "right_panel": {
+        "add_integrations": "Ychwanegu estyniadau",
+        "add_topic": "Ychwanegu pwnc",
+        "extensions_button": "Estyniadau",
+        "extensions_empty_description": "Dewiswch “%(addIntegrations)s” i bori ac ychwanegu estyniadau i'r ystafell hon",
+        "extensions_empty_title": "Rhowch hwb i gynhyrchiant gyda mwy o offer, teclynnau a botiau",
+        "files_button": "Ffeiliau",
+        "pinned_messages": {
+            "empty_description": "Dewiswch neges a dewiswch “%(pinAction)s” i'w chynnwys yma.",
+            "empty_title": "Pinio negeseuon pwysig fel y mae modd eu darganfod yn hawdd",
+            "menu": "Agor dewislen",
+            "release_announcement": {
+                "close": "Iawn",
+                "description": "Dewch o hyd i'r holl negeseuon sydd wedi'u pinio yma. Rhedwch y cyrchwr dros unrhyw neges a dewiswch \"Pinio\" i'w hychwanegu.",
+                "title": "Pob neges newydd wedi'u pinio"
+            },
+            "reply_thread": "Ymateb i a <link>neges edefyn</link>",
+            "unpin_all": {
+                "button": "Dad-binio pob neges",
+                "content": "Gwnewch yn siŵr eich bod chi wir eisiau dileu'r holl negeseuon sydd wedi'u pinio. Nid oes modd dadwneud y weithred hon.",
+                "title": "Dad-binio pob neges?"
+            },
+            "view": "Gweld yn yr amserlen"
+        },
+        "pinned_messages_button": "Negeseuon wedi'u pinio",
+        "poll": {
+            "active_heading": "Arolygon gweithredol",
+            "empty_active": "Nid oes unrhyw arolygon gweithredol yn yr ystafell hon",
+            "empty_active_load_more": "Nid oes unrhyw arolygon gweithredol. Llwythwch fwy o arolygon barn y misoedd blaenorol",
+            "empty_past": "Nid oes arolygon o'r gorffennol yn yr ystafell hon",
+            "empty_past_load_more": "Nid oes unrhyw arolygon o'r gorffennol. Llwythwch fwy o arolygon barn ar gyfer y misoedd blaenorol",
+            "load_more": "Llwytho mwy o arolygon barn",
+            "loading": "Wrthi'n llwytho arolygon",
+            "past_heading": "Arolygon y gorffennol",
+            "view_in_timeline": "Gweld yr arolwg yn y llinell amser",
+            "view_poll": "Gweld yr arolwg"
+        },
+        "polls_button": "Arolygon",
+        "room_summary_card": {
+            "title": "Manylion yr ystafell"
+        },
+        "thread_list": {
+            "context_menu_label": "Dewisiadau edafedd"
+        },
+        "video_room_chat": {
+            "title": "Sgwrs"
+        }
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "Anfonwyd y gwahoddiad hwn at %(email)s nad yw'n gysylltiedig â'ch cyfrif",
+        "3pid_invite_email_not_found_account_room": "Anfonwyd y gwahoddiad hwn i %(roomName)s at %(email)s nad yw'n gysylltiedig â'ch cyfrif",
+        "3pid_invite_error_description": "Cafwyd gwall (%(errcode)s) wrth geisio dilysu'ch gwahoddiad. Gallech geisio trosglwyddo’r wybodaeth hon i’r sawl sydd wedi’ch gwahoddodd.",
+        "3pid_invite_error_invite_action": "Ceisiwch ymuno beth bynnag",
+        "3pid_invite_error_invite_subtitle": "Dim ond gyda gwahoddiad sy'ngweithio y gallwch ymuno ag ef.",
+        "3pid_invite_error_public_subtitle": "Gallwch chi ymuno yma o hyd.",
+        "3pid_invite_error_title": "Aeth rhywbeth o'i le gyda'ch gwahoddiad.",
+        "3pid_invite_error_title_room": "Aeth rhywbeth o'i le gyda'ch gwahoddiad i %(roomName)s",
+        "3pid_invite_no_is_subtitle": "Defnyddiwch weinydd hunaniaeth yn y Gosodiadau i dderbyn gwahoddiadau yn uniongyrchol yn %(brand)s.",
+        "banned_by": "Cawsoch eich gwahardd gan %(memberName)s",
+        "banned_from_room_by": "Cawsoch eich gwahardd o %(roomName)s gan %(memberName)s",
+        "context_menu": {
+            "copy_link": "Copïo dolen ystafell",
+            "favourite": "Ffefryn",
+            "forget": "Anghofio'r Ystafell",
+            "low_priority": "Blaenoriaeth Isel",
+            "mark_read": "Marcio fel wedi'i ddarllen",
+            "mark_unread": "Marcio fel heb ei ddarllen",
+            "notifications_default": "Cydweddu'r gosodiad rhagosodedig",
+            "notifications_mute": "Tewi ystafell",
+            "title": "Dewisiadau ystafell",
+            "unfavourite": "Ffafrio"
+        },
+        "creating_room_text": "Rydym yn creu ystafell gyda %(materes)s",
+        "dm_invite_action": "Dechrau sgwrsio",
+        "dm_invite_subtitle": "Mae <userName/>eisiau sgwrsio",
+        "dm_invite_title": "Ydych chi eisiau sgwrsio gyda %(user)s?",
+        "drop_file_prompt": "Gollwng ffeil yma i'w llwytho",
+        "edit_topic": "Golygu pwnc",
+        "error_3pid_invite_email_lookup": "Methu dod o hyd i ddefnyddiwr trwy e-bost",
+        "error_cancel_knock_title": "Wedi methu â diddymu",
+        "error_join_403": "Mae angen gwahoddiad arnoch i gael mynediad i'r ystafell hon.",
+        "error_join_404_1": "Fe wnaethoch chi geisio ymuno gan ddefnyddio ID ystafell heb ddarparu rhestr o weinyddion i ymuno â nhw. Dynodwyr mewnol yw rhifau adnabod ystafelloedd ac nid oes modd eu defnyddio i ymuno ag ystafell heb fanylion ychwanegol.",
+        "error_join_404_2": "Os ydych chi'n gwybod cyfeiriad ystafell, ceisiwch ymuno trwy hwnnw yn lle.",
+        "error_join_404_invite": "Mae'r person a'ch gwahoddodd eisoes wedi gadael, neu mae ei weinydd all-lein.",
+        "error_join_404_invite_same_hs": "Mae'r person a'ch gwahoddodd eisoes wedi gadael.",
+        "error_join_connection": "Bu gwall wrth ymuno.",
+        "error_join_incompatible_version_1": "Ymddiheuriadau, mae eich gweinydd cartref yn rhy hen i gymryd rhan yma.",
+        "error_join_incompatible_version_2": "Cysylltwch â gweinyddwr eich gweinydd cartref.",
+        "error_join_title": "Wedi methu ag ymuno",
+        "error_jump_to_date": "Dychwelodd y gweinydd %(statusCode)s gyda chod gwall %(errorCode)s",
+        "error_jump_to_date_connection": "Digwyddodd gwall rhwydwaith wrth geisio canfod a mynd i'r dyddiad a roddwyd. Mae'n bosibl bod eich gweinydd cartref i lawr neu fod problem dros dro gyda'ch cysylltiad rhyngrwyd. Ceisiwch eto. Os bydd hyn yn parhau, cysylltwch â gweinyddwr eich gweinyddwr cartref.",
+        "error_jump_to_date_details": "Manylion gwall",
+        "error_jump_to_date_not_found": "Nid oeddem yn gallu dod o hyd i ddigwyddiad yn edrych ymlaen o %(dateString)s. Ceisiwch ddewis dyddiad cynharach.",
+        "error_jump_to_date_send_logs_prompt": "Cyflwynwch <debugLogsLink>logiau dadfygio</debugLogsLink> i'n helpu i ddod o hyd i'r broblem.",
+        "error_jump_to_date_title": "Methu dod o hyd i ddigwyddiad ar y dyddiad hwnnw",
+        "face_pile_tooltip_shortcut": "Gan gynnwys %(commaSeparatedMembers)s",
+        "face_pile_tooltip_shortcut_joined": "Gan eich cynnwys chi, %(commaSeparatedMembers)s",
+        "failed_reject_invite": "Wedi methu â gwrthod gwahoddiad",
+        "forget_room": "Anghofiwch yr ystafell hon",
+        "forget_space": "Anghofiwch y gofod hwn",
+        "header": {
+            "room_is_public": "Mae'r ystafell hon yn gyhoeddus"
+        },
+        "header_avatar_open_settings_label": "Agor gosodiadau ystafell",
+        "header_face_pile_tooltip": "Pobl",
+        "header_untrusted_label": "Dim ymddiriedaeth",
+        "inaccessible": "Nid yw'r ystafell neu'r gofod hwn yn hygyrch ar hyn o bryd.",
+        "inaccessible_name": "Nid yw %(roomName)s yn hygyrch ar hyn o bryd.",
+        "inaccessible_subtitle_1": "Ceisiwch eto yn nes ymlaen, neu gofynnwch i weinyddwr ystafell neu ofod wirio a oes gennych fynediad.",
+        "inaccessible_subtitle_2": "Cafodd %(errcode)s ei ddychwelyd wrth geisio cael mynediad i'r ystafell neu'r gofod. Os ydych yn meddwl eich bod yn gweld y neges hon ar gam, <issueLink>cyflwynwch adroddiad gwall</issueLink>.",
+        "intro": {
+            "dm_caption": "Dim ond y ddau ohonoch sydd yn y sgwrs hon, oni bai bod y naill neu'r llall ohonoch yn gwahodd unrhyw un i ymuno.",
+            "enable_encryption_prompt": "Galluogi amgryptio yn y gosodiadau.",
+            "encrypted_3pid_dm_pending_join": "Unwaith y bydd pawb wedi ymuno, byddwch yn gallu sgwrsio",
+            "no_avatar_label": "Ychwanegwch lun, fel bod pobl yn gallu gweld eich ystafell yn hawdd.",
+            "no_topic": "<a>Ychwanegwch bwnc</a> i helpu pobl i wybod beth yw ei ddiben.",
+            "private_unencrypted_warning": "Mae eich negeseuon preifat yn cael eu hamgryptio fel arfer, ond nid yw'r ystafell hon. Fel arfer mae hyn oherwydd bod dyfais neu ddull heb ei gefnogi yn cael ei ddefnyddio, fel gwahoddiadau e-bost.",
+            "room_invite": "Gwahoddiad i'r ystafell hon yn unig",
+            "send_message_start_dm": "Anfonwch eich neges gyntaf i wahodd <displayName/> i sgwrsio",
+            "start_of_dm_history": "Dyma ddechrau eich hanes neges uniongyrchol gyda <displayName/>.",
+            "start_of_room": "Dyma ddechrau <roomName/>.",
+            "topic": "Pwnc: %(topic)s ",
+            "topic_edit": "Pwnc: %(topic)s ( <a>golygu</a> )",
+            "unencrypted_warning": "Nid yw amgryptio pen-i-ben wedi'i alluogi",
+            "user_created": "Creodd %(displayName)s yr ystafell hon.",
+            "you_created": "Chi sydd wedi creu'r ystafell hon."
+        },
+        "invite_email_mismatch_suggestion": "Rhannwch yr e-bost hwn yn y Gosodiadau i dderbyn gwahoddiadau yn uniongyrchol yn %(brand)s.",
+        "invite_sent_to_email": "Anfonwyd y gwahoddiad hwn at %(email)s",
+        "invite_sent_to_email_room": "Anfonwyd y gwahoddiad hwn i %(roomName)s at %(email)s",
+        "invite_subtitle": "Gwahoddwyd gan <userName/>",
+        "invite_this_room": "Gwahoddiad i'r ystafell hon",
+        "invite_title": "Ydych chi am ymuno â %(roomName)s?",
+        "inviter_unknown": "Anhysbys",
+        "invites_you_text": "Mae <inviter/> yn eich gwahodd",
+        "join_button_account": "Cofrestru",
+        "join_failed_needs_invite": "I weld %(roomName)s, mae angen gwahoddiad arnoch chi",
+        "join_the_discussion": "Ymunwch â'r drafodaeth",
+        "join_title": "Ymunwch â'r ystafell i gymryd rhan",
+        "join_title_account": "Ymunwch â'r sgwrs gyda chyfrif",
+        "joining": "Wrthi'n ymuno…",
+        "joining_room": "Wrthi'n ymuno â'r ystafell…",
+        "joining_space": "Wrthi'n ymuno â'r gofod…",
+        "jump_read_marker": "Symud i'r neges gyntaf heb ei darllen.",
+        "jump_to_bottom_button": "Sgrolio i'r negeseuon mwyaf diweddar",
+        "jump_to_date": "Symud i ddyddiad",
+        "jump_to_date_beginning": "Dechreuad yr ystafell",
+        "jump_to_date_prompt": "Dewiswch ddyddiad i fynd iddo",
+        "kick_reason": "Rheswm: %(reason)s",
+        "kicked_by": "Cawsoch eich tynnu gan %(memberName)s",
+        "kicked_from_room_by": "Cawsoch eich tynnu o %(roomName)s gan %(memberName)s",
+        "knock_cancel_action": "Diddymu cais",
+        "knock_denied_subtitle": "Gan nad ydych wedi cael mynediad, allwch chi ddim ailymuno oni bai eich bod yn cael gwahoddiad gan weinyddwr neu gymedrolwr y grŵp.",
+        "knock_denied_title": "Gwrthodwyd mynediad i chi",
+        "knock_message_field_placeholder": "Neges (dewisol)",
+        "knock_prompt": "Gofyn i ymuno?",
+        "knock_prompt_name": "Gofyn i ymuno â %(roomName)s?",
+        "knock_send_action": "Gofyn am fynediad",
+        "knock_sent": "Anfonwyd y cais i ymuno",
+        "knock_sent_subtitle": "Mae eich cais i ymuno yn yr aros.",
+        "knock_subtitle": "Mae angen i chi gael mynediad i'r ystafell hon er mwyn gweld neu gymryd rhan yn y sgwrs. Gallwch anfon cais i ymuno isod.",
+        "leave_error_title": "Gwall wrth adael yr ystafell",
+        "leave_server_notices_description": "Mae'r ystafell hon yn cael ei defnyddio ar gyfer negeseuon pwysig o'r Homeserver, felly allwch chi ddim ei gadael.",
+        "leave_server_notices_title": "Methu â gadael ystafell Hysbysiadau Gweinydd",
+        "leave_unexpected_error": "Gwall gweinydd annisgwyl wrth geisio gadael yr ystafell",
+        "link_email_to_receive_3pid_invite": "Cysylltwch yr e-bost hwn â'ch cyfrif yn y Gosodiadau i dderbyn gwahoddiadau yn uniongyrchol yn %(brand)s.",
+        "loading_preview": "Wrthi'n llwytho rhagolwg",
+        "no_peek_join_prompt": "Nid oes modd rhagweld %(roomName)s. Ydych chi am ymuno ag ef?",
+        "no_peek_no_name_join_prompt": "Does dim rhagolwg, hoffech chi ymuno?",
+        "not_found_subtitle": "Ydych chi'n siŵr eich bod chi yn y lle iawn?",
+        "not_found_title": "Nid yw'r ystafell neu'r gofod hwn yn bodoli.",
+        "not_found_title_name": "Nid yw %(roomName)s yn bodoli.",
+        "peek_join_prompt": "Rydych chi'n cael rhagolwg o %(roomName)s. Eisiau ymuno?",
+        "pinned_message_badge": "Neges wedi'i phinio",
+        "pinned_message_banner": {
+            "button_close_list": "Cau'r rhestr",
+            "button_view_all": "Gweld popeth",
+            "description": "Mae negeseuon wedi'u pinio yn yr ystafell hon. Cliciwch i'w gweld.",
+            "go_to_message": "Gweld y neges sydd wedi'i phinnio yn y llinell amser.",
+            "title": "<bold>%(index)s o %(length)s</bold> Neges wedi'u pinio"
+        },
+        "read_topic": "Cliciwch i ddarllen y pwnc",
+        "rejecting": "Wrthi'n gwrthod gwahoddiad…",
+        "rejoin_button": "Ail-ymuno",
+        "search": {
+            "all_rooms_button": "Chwiliwch bob ystafell",
+            "placeholder": "Chwilio negeseuon…",
+            "this_room_button": "Chwiliwch yr ystafell hon"
+        },
+        "status_bar": {
+            "delete_all": "Dileu popeth",
+            "exceeded_resource_limit": "Heb anfon eich neges oherwydd bod y gweinydd cartref hwn wedi mynd y tu hwnt i'r terfyn adnoddau. <a>Cysylltwch â gweinyddwr eich gwasanaeth</a> i barhau i ddefnyddio'r gwasanaeth.",
+            "homeserver_blocked": "Heb anfon eich neges oherwydd bod y gweinydd cartref hwn wedi'i rwystro gan ei weinyddwr. <a>Cysylltwch â gweinyddwr eich gwasanaeth</a> i barhau i ddefnyddio'r gwasanaeth.",
+            "monthly_user_limit_reached": "Heb anfoneich neges oherwydd bod y gweinydd cartref hwn wedi cyrraedd ei Derfyn Defnyddiwr Gweithredol Misol. <a>Cysylltwch â gweinyddwr eich gwasanaeth</a> i barhau i ddefnyddio'r gwasanaeth.",
+            "requires_consent_agreement": "Allwch chi ddim anfon unrhyw negeseuon nes i chi adolygu a chytuno i<consentLink> ein telerau ac amodau</consentLink>.",
+            "retry_all": "Rhowch gynnig arall arni",
+            "select_messages_to_retry": "Gallwch ddewis pob neges neu neges unigol i roi cynnig arall arni neu ei dileu",
+            "server_connectivity_lost_description": "Bydd negeseuon sy'n cael eu hanfon yn cael eu storio nes bod eich cysylltiad wedi dychwelyd.",
+            "server_connectivity_lost_title": "Mae cysylltedd â'r gweinydd wedi'i golli.",
+            "some_messages_not_sent": "Nid yw rhai o'ch negeseuon wedi'u hanfon"
+        },
+        "unknown_status_code_for_timeline_jump": "cod statws anhysbys",
+        "upgrade_error_description": "Gwiriwch ddwywaith bod eich gweinydd yn cefnogi'r fersiwn ystafell a ddewiswyd a rhowch gynnig arall arni.",
+        "upgrade_error_title": "Gwall wrth uwchraddio'r ystafell",
+        "upgrade_warning_bar": "Bydd uwchraddio'r ystafell hon yn cau enghraifft bresennol yr ystafell ac yn creu ystafell wedi'i huwchraddio gyda'r un enw.",
+        "upgrade_warning_bar_admins": "Dim ond gweinyddwyr ystafell fydd yn gweld y rhybudd hwn",
+        "upgrade_warning_bar_unstable": "Mae'r ystafell hon yn fersiwn ystafell redeg<roomVersion /> , y mae'r gweinydd cartref hwn wedi'i nodi'n <i>ansefydlog</i>.",
+        "upgrade_warning_bar_upgraded": "Mae'r ystafell hon eisoes wedi'i huwchraddio.",
+        "upload": {
+            "uploading_single_file": "Wrthi'n llwytho %(filematere)s"
+        },
+        "video_room": "Mae'r ystafell hon yn ystafell fideo",
+        "waiting_for_join_subtitle": "Unwaith y bydd defnyddwyr sydd wedi'u gwahodd wedi ymuno â %(brand)s, byddwch yn gallu sgwrsio a bydd yr ystafell yn cael ei hamgryptio pen-i-ben",
+        "waiting_for_join_title": "Yn aros i ddefnyddwyr ymuno â %(brand)s"
+    },
+    "room_list": {
+        "add_room_label": "Ychwanegu ystafell",
+        "add_space_label": "Ychwanegu gofod",
+        "appearance": "Gwedd",
+        "breadcrumbs_empty": "Dim ystafelloedd yr ymwelwyd â nhw yn ddiweddar",
+        "breadcrumbs_label": "Ymwelwyd ag ystafelloedd yn ddiweddar",
+        "empty": {
+            "no_chats": "Dim sgyrsiau eto",
+            "no_chats_description": "Dechreuwch drwy anfon neges at rywun neu drwy greu ystafell",
+            "no_chats_description_no_room_rights": "Dechreuwch trwy anfon neges at rywun",
+            "no_favourites": "Nid oes gennych hoff sgwrs eto",
+            "no_favourites_description": "Gallwch ychwanegu sgwrs at eich ffefrynnau yn y gosodiadau sgwrsio",
+            "no_invites": "Does gennych chi ddim gwahoddiadau heb eu darllen",
+            "no_mentions": "Does gennych chi ddim crybwylliadau heb eu darllen",
+            "no_people": "Nid oes gennych chi sgyrsiau uniongyrchol gydag unrhyw un eto",
+            "no_people_description": "Gallwch ddad-ddewis hidlwyr er mwyn gweld eich sgyrsiau eraill",
+            "no_rooms": "Nid ydych mewn unrhyw ystafell eto",
+            "no_rooms_description": "Gallwch ddad-ddewis hidlwyr er mwyn gweld eich sgyrsiau eraill",
+            "no_unread": "Llongyfarchiadau! Nid oes gennych unrhyw negeseuon heb eu darllen",
+            "show_activity": "Gweld yr holl weithgarwch",
+            "show_chats": "Dangos pob sgwrs"
+        },
+        "failed_add_tag": "Wedi methu ag ychwanegu tag %(tagName)s i'r ystafell",
+        "failed_remove_tag": "Wedi methu â thynnu'r tag %(tagName)s o'r ystafell",
+        "failed_set_dm_tag": "Wedi methu gosod tag neges uniongyrchol",
+        "filters": {
+            "favourite": "Ffefrynnau",
+            "invites": "Gwahoddiadau",
+            "mentions": "Crybwylliadau",
+            "people": "Pobl",
+            "rooms": "Ystafelloedd",
+            "unread": "Heb ei ddarllen"
+        },
+        "home_menu_label": "Dewisiadau cartref",
+        "join_public_room_label": "Ymuno â'r ystafell gyhoeddus",
+        "list_title": "Rhestr ystafelloedd",
+        "more_options": {
+            "copy_link": "Copïo dolen ystafell",
+            "favourited": "Ffafrio",
+            "leave_room": "Gadael yr ystafell",
+            "low_priority": "Blaenoriaeth isel",
+            "mark_read": "Marcio fel wedi'i ddarllen",
+            "mark_unread": "Marcio fel heb ei ddarllen"
+        },
+        "notification_options": "Dewisiadau hysbysu",
+        "open_space_menu": "Agor dewislen gofod",
+        "primary_filters": "Hidlau rhestr ystafelloedd",
+        "room": {
+            "more_options": "Rhagor o Ddewisiadau",
+            "open_room": "Agor ystafell %(roomName)s"
+        },
+        "room_options": "Dewisiadau Ystafelloedd",
+        "show_less": "Dangos llai",
+        "show_message_previews": "Dangos rhagolygon negeseuon",
+        "show_previews": "Dangos rhagolwg o negeseuon",
+        "sort": "Trefnu",
+        "sort_by": "Trefnu yn ôl",
+        "sort_by_activity": "Gweithgaredd",
+        "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Gweithgaredd",
+            "atoz": "A-Z"
+        },
+        "sort_unread_first": "Dangos ystafelloedd gyda negeseuon heb eu darllen yn gyntaf",
+        "space_menu": {
+            "home": "Cartref gofod",
+            "space_settings": "Gosodiadau Gofod"
+        },
+        "space_menu_label": "Dewislen %(spaceName)s",
+        "sublist_options": "Dewisiadau rhestr",
+        "suggested_rooms_heading": "Awgrym o Ystafelloedd"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "Penderfynwch pwy all weld ac ymuno â %(spaceName)s.",
+            "title": "Mynediad"
+        },
+        "advanced": {
+            "error_upgrade_description": "Ni fu modd cwblhau'r uwchraddio ystafell",
+            "error_upgrade_title": "Wedi methu ag uwchraddio'r ystafell",
+            "information_section_room": "Manylion ystafell",
+            "information_section_space": "Manylion gofod",
+            "room_id": "ID ystafell fewnol",
+            "room_predecessor": "Gweld negeseuon hŷn yn %(roomName)s.",
+            "room_upgrade_button": "Uwchraddio'r ystafell hon i'r fersiwn ystafell sy'n cael ei hargymell",
+            "room_upgrade_warning": "<b>Rhybudd</b> : <i>ni fydd uwchraddio ystafell yn mudo aelodau ystafell yn awtomatig i fersiwn newydd yr ystafell.</i> Byddwn yn postio dolen i'r ystafell newydd yn yr hen fersiwn o'r ystafell - bydd yn rhaid i aelodau'r ystafell glicio ar y ddolen hon i ymuno â'r ystafell newydd.",
+            "room_version": "Fersiwn ystafell:",
+            "room_version_section": "Fersiwn ystafell",
+            "space_predecessor": "Gweld fersiwn hŷn o %(spaceName)s.",
+            "space_upgrade_button": "Uwchraddio'r gofod hwn i'r fersiwn ystafell sy'n cael ei hargymell",
+            "unfederated": "Nid yw gweinyddion Matrix pell yn hygyrch i'r ystafell hon",
+            "upgrade_button": "Uwchraddio'r ystafell hon i fersiwn %(version)s",
+            "upgrade_dialog_description": "Mae uwchraddio'r ystafell hon yn gofyn am gau'r enghraifft bresennol o'r ystafell a chreu ystafell newydd yn ei lle. Er mwyn rhoi’r profiad gorau posibl i aelodau’r ystafell, byddwn yn:",
+            "upgrade_dialog_description_1": "Creu ystafell newydd gyda'r un enw, disgrifiad ac afatar",
+            "upgrade_dialog_description_2": "Diweddarwch unrhyw arallenwau ystafell leol i bwyntio at yr ystafell newydd",
+            "upgrade_dialog_description_3": "Atal defnyddwyr rhag siarad yn yr hen fersiwn o'r ystafell, a phostio neges yn cynghori defnyddwyr i symud i'r ystafell newydd",
+            "upgrade_dialog_description_4": "Rhowch ddolen yn ôl i'r hen ystafell ar ddechrau'r ystafell newydd er mwyn i bobl allu gweld hen negeseuon",
+            "upgrade_dialog_title": "Uwchraddio'r Fersiwn Ystafell",
+            "upgrade_dwarning_ialog_title_public": "Uwchraddio'r ystafell gyhoeddus",
+            "upgrade_warning_dialog_description": "Mae uwchraddio ystafell yn gam gweithredu datblygedig ac mae'n cael ei argymell fel arfer pan fydd ystafell yn ansefydlog oherwydd bygiau, nodweddion coll neu wendidau diogelwch.",
+            "upgrade_warning_dialog_explainer": "<b>Sylwch y bydd uwchraddio yn gwneud fersiwn newydd o'r ystafell</b>. Bydd yr holl negeseuon cyfredol yn aros yn yr ystafell hon sydd wedi'i harchifo.",
+            "upgrade_warning_dialog_footer": "Byddwch yn uwchraddio'r ystafell hon o <oldVersion /> i <newVersion />.",
+            "upgrade_warning_dialog_invite_label": "Gwahodd aelodau o'r ystafell hon i'r un newydd yn awtomatig",
+            "upgrade_warning_dialog_report_bug_prompt": "Mae hyn fel arfer ond yn effeithio ar sut mae'r ystafell yn cael ei phrosesu ar y gweinydd. Os ydych chi'n cael problemau gyda'ch %(brand)s, rhowch wybod am fater.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "Mae hyn fel arfer ond yn effeithio ar sut mae'r ystafell yn cael ei phrosesu ar y gweinydd. Os ydych chi'n cael problemau gyda'ch %(brand)s , rhowch <a>wybod am fater</a>.",
+            "upgrade_warning_dialog_title": "Uwchraddio ystafell",
+            "upgrade_warning_dialog_title_private": "Uwchraddio ystafell breifat"
+        },
+        "alias_not_specified": "heb ei nodi",
+        "bridges": {
+            "description": "Mae'r ystafell hon yn pontio negeseuon i'r llwyfannau canlynol. <a>Dysgu rhagor.</a>",
+            "empty": "Nid yw'r ystafell hon yn pontio negeseuon i unrhyw lwyfannau. <a>Dysgu rhagor.</a>",
+            "title": "Pontydd"
+        },
+        "delete_avatar_label": "Dileu afatar",
+        "general": {
+            "alias_field_has_domain_invalid": "Gwahanydd parth ar goll e.e. (:domain.org)",
+            "alias_field_has_localpart_invalid": "Enw ystafell ar goll neu wahanydd e.e. (fy-ystafell:domain.org)",
+            "alias_field_matches_invalid": "Nid yw'r cyfeiriad hwn yn pwyntio at yr ystafell hon",
+            "alias_field_placeholder_default": "e.e. fy-ystafell",
+            "alias_field_required_invalid": "Rhowch gyfeiriad",
+            "alias_field_safe_localpart_invalid": "Nid oes caniatâd i rai nodau",
+            "alias_field_taken_invalid": "Roedd gan y cyfeiriad hwn weinydd annilys neu mae eisoes yn cael ei ddefnyddio",
+            "alias_field_taken_invalid_domain": "Mae'r cyfeiriad hwn eisoes yn cael ei ddefnyddio",
+            "alias_field_taken_valid": "Mae'r cyfeiriad hwn ar gael i'w ddefnyddio",
+            "alias_heading": "Cyfeiriad yr ystafell",
+            "aliases_items_label": "Cyfeiriadau cyhoeddedig eraill:",
+            "aliases_no_items_label": "Dim cyfeiriadau cyhoeddedig eraill eto, ychwanegwch un isod",
+            "aliases_section": "Cyfeiriadau Ystafell",
+            "avatar_field_label": "Afatar ystafell",
+            "canonical_alias_field_label": "Prif gyfeiriad",
+            "default_url_previews_off": "Mae rhagolygon URL wedi'u hanalluogi fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon.",
+            "default_url_previews_on": "Mae rhagolygon URL wedi'u galluogi fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon.",
+            "description_space": "Golygu gosodiadau sy'n ymwneud â'ch gofod.",
+            "error_creating_alias_description": "Bu gwall wrth greu'r cyfeiriad hwnnw. Mae'n bosib na chaiff ei ganiatáu gan y gweinydd neu fe ddigwyddodd methiant dros dro.",
+            "error_creating_alias_title": "Gwall wrth greu cyfeiriad",
+            "error_deleting_alias_description": "Bu gwall wrth ddileu'r cyfeiriad hwnnw. Mae'n bosibl nad yw'n bodoli mwyach neu fod gwall dros dro wedi digwydd.",
+            "error_deleting_alias_description_forbidden": "Nid oes gennych ganiatâd i ddileu'r cyfeiriad.",
+            "error_deleting_alias_title": "Gwall wrth dynnu'r cyfeiriad",
+            "error_publishing": "Methu cyhoeddi ystafell",
+            "error_publishing_detail": "Bu gwall wrth gyhoeddi'r ystafell hon",
+            "error_save_space_settings": "Wedi methu cadw gosodiadau'r gofod.",
+            "error_updating_alias_description": "Bu gwall wrth ddiweddaru cyfeiriadau arall yr ystafell. Mae'n bosib na chaiff ei ganiatáu gan y gweinydd neu fe ddigwyddodd methiant dros dro.",
+            "error_updating_canonical_alias_description": "Bu gwall wrth ddiweddaru prif gyfeiriad yr ystafell. Mae'n bosib na chaiff ei ganiatáu gan y gweinydd neu fe ddigwyddodd methiant dros dro.",
+            "error_updating_canonical_alias_title": "Gwall wrth ddiweddaru'r prif gyfeiriad",
+            "leave_space": "Gadael y Gofod",
+            "local_alias_field_label": "Cyfeiriad lleol",
+            "local_aliases_explainer_room": "Gosodwch gyfeiriadau ar gyfer yr ystafell hon fel y gall defnyddwyr ddod o hyd i'r ystafell hon trwy eich gweinydd cartref (%(localDomain)s)",
+            "local_aliases_explainer_space": "Gosodwch gyfeiriadau ar gyfer y gofod hwn fel y gall defnyddwyr ddod o hyd i'r gofod hwn trwy eich gweinydd cartref (%(localDomain)s)",
+            "local_aliases_section": "Cyfeiriadau Lleol",
+            "name_field_label": "Enw'r Ystafell",
+            "new_alias_placeholder": "Cyfeiriad cyhoeddedig newydd (e.e. #alias:server)",
+            "no_aliases_room": "Nid oes gan yr ystafell hon unrhyw gyfeiriadau lleol",
+            "no_aliases_space": "Nid oes gan y gofod hwn unrhyw gyfeiriadau lleol",
+            "other_section": "Arall",
+            "publish_toggle": "Cyhoeddi'r ystafell hon i'r cyhoedd yng nghyfeiriadur ystafelloedd %(domain)s?",
+            "published_aliases_description": "I gyhoeddi cyfeiriad, mae angen ei osod fel cyfeiriad lleol yn gyntaf.",
+            "published_aliases_explainer_room": "Gall unrhyw un ar unrhyw weinydd ddefnyddio cyfeiriadau cyhoeddedig i ymuno â'ch ystafell.",
+            "published_aliases_explainer_space": "Gall unrhyw un ar unrhyw weinydd ddefnyddio cyfeiriadau cyhoeddedig i ymuno â'ch gofod.",
+            "published_aliases_section": "Cyfeiriadau Cyhoeddedig",
+            "save": "Cadw'r Newidiadau",
+            "topic_field_label": "Pwnc yr Ystafell",
+            "url_preview_encryption_warning": "Mewn ystafelloedd wedi'u hamgryptio, fel yr un hon, mae rhagolygon URL yn cael eu hanalluogi fel rhagosodiad i sicrhau na all eich gweinydd cartref (lle mae'r rhagolygon yn cael eu cynhyrchu) gasglu gwybodaeth am ddolenni welwch yn yr ystafell hon.",
+            "url_preview_explainer": "Pan fydd rhywun yn rhoi URL yn eu neges, mae modd dangos rhagolwg URL i roi mwy o wybodaeth am y ddolen honno fel y teitl, disgrifiad, a delwedd o'r wefan.",
+            "url_previews_section": "Rhagolygon URL",
+            "user_url_previews_default_off": "Rydych wedi <a>analluogi</a> rhagolygon URL fel rhagosodiad.",
+            "user_url_previews_default_on": "Rydych chi wedi <a>galluogi</a> rhagolygon URL fel rhagosodiad."
+        },
+        "notifications": {
+            "browse_button": "Pori",
+            "custom_sound_prompt": "Gosod sain cyfaddas newydd",
+            "notification_sound": "Sain hysbysu",
+            "settings_link": "Cael hysbysiadau fel wedi'u gosod yn eich <a>gosodiadau</a>",
+            "sounds_section": "Seiniau",
+            "upload_sound_label": "Llwythwch i fyny sain personol",
+            "uploaded_sound": "Sain wedi'i lwytho"
+        },
+        "people": {
+            "knock_empty": "Dim ceisiadau",
+            "knock_section": "Yn gofyn i ymuno",
+            "see_less": "Gweld llai",
+            "see_more": "Gweld mwy"
+        },
+        "permissions": {
+            "add_privileged_user_description": "Rhoi rhagor o freintiau i un neu fwy o ddefnyddwyr yn yr ystafell hon",
+            "add_privileged_user_filter_placeholder": "Chwilio defnyddwyr yn yr ystafell hon…",
+            "add_privileged_user_heading": "Ychwanegu defnyddwyr breintiedig",
+            "ban": "Gwahardd defnyddwyr",
+            "ban_reason": "Rheswm",
+            "banned_by": "Wedi'i wahardd gan %(displayName)s",
+            "banned_users_section": "Defnyddwyr wedi'u gwahardd",
+            "error_changing_pl_description": "Bu gwall wrth newid lefel pŵer y defnyddiwr. Sicrhewch fod gennych ddigon o ganiatâd a cheisiwch eto.",
+            "error_changing_pl_reqs_description": "Digwyddodd gwall wrth newid gofynion lefel pŵer yr ystafell. Sicrhewch fod gennych ddigon o ganiatâd a cheisiwch eto.",
+            "error_changing_pl_reqs_title": "Gwall wrth newid gofyniad lefel pŵer",
+            "error_changing_pl_title": "Gwall wrth newid lefel pŵer",
+            "error_unbanning": "Wedi methu â gwahardd",
+            "events_default": "Anfon negeseuon",
+            "invite": "Gwahodd defnyddwyr",
+            "kick": "Tynnu defnyddwyr",
+            "m.call": "Dechrau %(brand)s galwad",
+            "m.call.member": "Ymuno â galwadau %(brand)s",
+            "m.reaction": "Anfon adweithiau",
+            "m.room.avatar": "Newid afatar ystafell",
+            "m.room.avatar_space": "Newid afatar gofod",
+            "m.room.canonical_alias": "Newid prif gyfeiriad yr ystafell",
+            "m.room.canonical_alias_space": "Newid prif gyfeiriad y gofod",
+            "m.room.encryption": "Galluogi amgryptio ystafell",
+            "m.room.history_visibility": "Newid gwelededd hanes",
+            "m.room.name": "Newid enw'r ystafell",
+            "m.room.name_space": "Newid enw gofod",
+            "m.room.pinned_events": "Rheoli digwyddiadau wedi'u pinio",
+            "m.room.power_levels": "Newid caniatâd",
+            "m.room.redaction": "Dileu negeseuon anfonwyd gennyf",
+            "m.room.server_acl": "Newid ACLs gweinydd",
+            "m.room.tombstone": "Uwchraddio'r ystafell",
+            "m.room.topic": "Newid pwnc",
+            "m.room.topic_space": "Newid disgrifiad",
+            "m.space.child": "Rheoli ystafelloedd yn y gofod hwn",
+            "m.widget": "Addasu teclynnau",
+            "muted_users_section": "Defnyddwyr wedi'u tewi",
+            "no_privileged_users": "Nid oes gan unrhyw ddefnyddwyr freintiau penodol yn yr ystafell hon",
+            "notifications.room": "Hysbysu pawb",
+            "permissions_section": "Caniatâd",
+            "permissions_section_description_room": "Dewiswch y rolau sydd eu hangen i newid gwahanol rannau o'r ystafell",
+            "permissions_section_description_space": "Dewiswch y rolau sydd eu hangen i newid gwahanol rannau o'r gofod",
+            "privileged_users_section": "Defnyddwyr Breintiedig",
+            "redact": "Dileu negeseuon a anfonwyd gan eraill",
+            "send_event_type": "Anfon %(eventType)s digwyddiad",
+            "state_default": "Newid gosodiadau",
+            "title": "Rolau a Chaniatadau",
+            "users_default": "Rôl rhagosodedig"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "Unwaith y bydd wedi'i alluogi, nid oes modd analluogi amgryptio ar gyfer ystafell. Fydd y gweinydd ddim yn gallu gweld negeseuon wedi'u hanfon mewn ystafell wedi'i hamgryptio, dim ond cyfranogwyr yr ystafell. Gall galluogi amgryptio atal llawer o fotiau a phontydd rhag gweithio'n gywir. <a>Dysgu rhagor am amgryptio.</a>",
+            "enable_encryption_confirm_title": "Galluogi amgryptio?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Nid yw'n cael ei argymell i ychwanegu amgryptio i ystafelloedd cyhoeddus.</b> Gall unrhyw un ddod o hyd i ystafelloedd cyhoeddus ac ymuno â nhw, felly gall unrhyw un ddarllen negeseuon ynddynt. Chewch chi ddim unrhyw un o fanteision amgryptio, nac yn gallu ei ddiffodd yn ddiweddarach. Bydd amgryptio negeseuon mewn ystafell gyhoeddus yn gwneud derbyn ac anfon negeseuon yn arafach.",
+            "enable_encryption_public_room_confirm_description_2": "Er mwyn osgoi'r problemau hyn, crëwch <a>ystafell newydd wedi'i hamgryptio</a> ar gyfer y sgwrs rydych chi'n bwriadu ei chael.",
+            "enable_encryption_public_room_confirm_title": "Ydych chi'n siŵr eich bod am ychwanegu amgryptio i'r ystafell gyhoeddus hon?",
+            "encrypted_room_public_confirm_description_1": "<b>Nid yw'n cael ei argymell i wneud ystafelloedd wedi'u hamgryptio yn gyhoeddus.</b> Bydd yn golygu y gall unrhyw un ddod o hyd i'r ystafell ac ymuno â hi, fel y gall unrhyw un ddarllen negeseuon. Chewch chi ddim o fanteision amgryptio. Bydd amgryptio negeseuon mewn ystafell gyhoeddus yn gwneud derbyn ac anfon negeseuon yn arafach.",
+            "encrypted_room_public_confirm_description_2": "Er mwyn osgoi'r problemau hyn, crëwch <a>ystafell gyhoeddus newydd</a> ar gyfer y sgwrs rydych chi'n bwriadu ei chael.",
+            "encrypted_room_public_confirm_title": "A ydych yn siŵr eich bod am wneud yr ystafell hon wedi'i hamgryptio yn gyhoeddus?",
+            "encryption_forced": "Mae angen amgryptio ar eich gweinydd er mwyn ei analluogi.",
+            "encryption_permanent": "Unwaith y bydd wedi'i alluogi, nid oes modd analluogi amgryptio.",
+            "error_join_rule_change_title": "Wedi methu â diweddaru'r rheolau ymuno",
+            "error_join_rule_change_unknown": "Methiant anhysbys",
+            "guest_access_warning": "Bydd pobl â chleientiaid a gefnogir yn gallu ymuno â'r ystafell heb fod â chyfrif cofrestredig.",
+            "history_visibility_invited": "Aelodau yn unig (ers iddynt gael eu gwahodd)",
+            "history_visibility_joined": "Aelodau yn unig (ers iddynt ymuno)",
+            "history_visibility_legend": "Pwy all ddarllen hanes?",
+            "history_visibility_shared": "Aelodau yn unig (ers yr adeg pan ddewiswyd yr opsiwn hwn)",
+            "history_visibility_warning": "Dim ond i negeseuon yn y dyfodol yn yr ystafell hon y bydd newidiadau i bwy all ddarllen hanes yn berthnasol. Ni fydd amlygrwydd yr hanes presennol yn newid.",
+            "history_visibility_world_readable": "Unrhyw un",
+            "join_rule_description": "Penderfynwch pwy all ymuno â %(roomName)s.",
+            "join_rule_invite": "Preifat (gwahoddiad yn unig)",
+            "join_rule_invite_description": "Dim ond pobl wahoddedig all ymuno.",
+            "join_rule_knock": "Gofynnwch i ymuno",
+            "join_rule_knock_description": "Gall pobl ddim ymuno oni bai bod mynediad yn cael ei ganiatáu.",
+            "join_rule_public_description": "Gall unrhyw un ddod o hyd ac ymuno.",
+            "join_rule_restricted": "Aelodau gofod",
+            "join_rule_restricted_description": "Gall unrhyw un mewn gofod ganfod ac ymuno. <a>Golygu pa osodau sydd ar gael yma.</a>",
+            "join_rule_restricted_description_active_space": "Gall unrhyw un yn <spaceName/> allu dod o hyd ac ymuno. Gallwch hefyd ddewis ofodau eraill.",
+            "join_rule_restricted_description_prompt": "Gall unrhyw un mewn gofod ddod o hyd i ac ymuno. Gallwch ddewis bylchau lluosog.",
+            "join_rule_restricted_description_spaces": "Gofodau gyda mynediad",
+            "join_rule_restricted_dialog_description": "Penderfynwch pa ofodau all gael mynediad i'r ystafell hon. Os fydd gofod yn cael ei ddewis, gall ei aelodau ddod o hyd i iddo ac ymuno â <RoomName/>.",
+            "join_rule_restricted_dialog_empty_warning": "Rydych chi'n dileu pob gofod. Bydd mynediad fel rhagosodiad i wahoddiad yn unig",
+            "join_rule_restricted_dialog_filter_placeholder": "Chwilio gofodau",
+            "join_rule_restricted_dialog_heading_known": "Gofodau eraill rydych chi'n eu hadnabod",
+            "join_rule_restricted_dialog_heading_other": "Gofodau neu ystafelloedd eraill efallai nad ydych chi'n eu hadnabod",
+            "join_rule_restricted_dialog_heading_room": "Gofodau rydych yn eu hadnabod sy'n cynnwys yr ystafell hon",
+            "join_rule_restricted_dialog_heading_space": "Gofodau rydych yn eu hadnabod sy'n cynnwys y gofod hwn",
+            "join_rule_restricted_dialog_heading_unknown": "Mae'r rhain yn debygol o fod yn rhai y mae gweinyddwyr ystafell eraill yn rhan ohonyn nhw.",
+            "join_rule_restricted_dialog_title": "Dewiswch ofodau",
+            "join_rule_restricted_upgrade_description": "Bydd yr uwchraddiad hwn yn caniatáu i aelodau o ofodau penodol gael mynediad i'r ystafell hon heb wahoddiad.",
+            "join_rule_restricted_upgrade_warning": "Mae'r ystafell hon mewn rhai gofodau nad ydych chi'n weinyddwr iddynt. Yn y gofodau hynny, bydd yr hen ystafell yn dal i gael ei dangos, ond bydd pobl yn cael eu hannog i ymuno â'r un newydd.",
+            "join_rule_upgrade_awaiting_room": "Wrthi'n llwytho ystafell newydd",
+            "join_rule_upgrade_required": "Angen uwchraddio",
+            "join_rule_upgrade_upgrading_room": "Ystafell uwchraddio",
+            "public_without_alias_warning": "I gysylltu â'r ystafell hon, ychwanegwch gyfeiriad.",
+            "publish_room": "Gwnewch yr ystafell hon yn weladwy yn y cyfeiriadur ystafelloedd cyhoeddus.",
+            "publish_space": "Gwnewch y gofod hwn yn weladwy yn y cyfeiriadur ystafelloedd cyhoeddus.",
+            "strict_encryption": "Anfon negeseuon at ddefnyddwyr sydd wedi'u dilysu yn unig.",
+            "title": "Diogelwch a Phreifatrwydd"
+        },
+        "title": "Gosodiadau Ystafell - %(roomName)s",
+        "upload_avatar_label": "Llwytho afatar i fyny",
+        "visibility": {
+            "alias_section": "Cyfeiriad",
+            "error_failed_save": "Wedi methu â diweddaru gwelededd y gofod hwn",
+            "error_update_guest_access": "Wedi methu â diweddaru mynediad gwesteion y gofod hwn",
+            "error_update_history_visibility": "Wedi methu â diweddaru gwelededd hanes y gofod hwn",
+            "guest_access_explainer": "Gall gwesteion ymuno â gofod heb gyfrif.",
+            "guest_access_explainer_public_space": "Gall hyn fod yn ddefnyddiol ar gyfer gofodau cyhoeddus.",
+            "guest_access_label": "Galluogi mynediad gwestai",
+            "history_visibility_anyone_space": "Gofod Rhagolwg",
+            "history_visibility_anyone_space_description": "Caniatáu i bobl gael rhagolwg o'ch gofod cyn iddynt ymuno.",
+            "history_visibility_anyone_space_recommendation": "Argymhellion ar gyfer gofodau cyhoeddus.",
+            "title": "Gwelededd"
+        },
+        "voip": {
+            "call_type_section": "Math o alwad",
+            "enable_element_call_caption": "Mae %(brand)s wedi'i amgryptio pen-i-ben, ond ar hyn o bryd mae wedi'i gyfyngu i niferoedd llai o ddefnyddwyr.",
+            "enable_element_call_label": "Galluogi %(brand)s fel opsiwn galw ychwanegol yn yr ystafell hon",
+            "enable_element_call_no_permissions_tooltip": "Nid oes gennych ganiatâd digonol i newid hyn."
+        }
+    },
+    "room_summary_card_back_action_label": "Manylion ystafell",
+    "scalar": {
+        "error_create": "Methu creu teclyn.",
+        "error_membership": "Nid ydych yn yr ystafell hon.",
+        "error_missing_room_id": "Id ystafell ar goll.",
+        "error_missing_room_id_request": "ID room_ ar goll yn y cais",
+        "error_missing_user_id_request": "user_id ar goll yn y cais",
+        "error_permission": "Nid oes gennych ganiatâd i wneud hynny yn yr ystafell hon.",
+        "error_power_level_invalid": "Rhaid i lefel pŵer fod yn gyfanrif positif.",
+        "error_room_not_visible": "Nid yw ystafell %(roomId)s yn weladwy",
+        "error_room_unknown": "Nid yw'r ystafell hon yn cael ei chydnabod.",
+        "error_send_request": "Wedi methu ag anfon cais.",
+        "failed_read_event": "Wedi methu darllen digwyddiadau",
+        "failed_send_event": "Wedi methu ag anfon digwyddiad"
+    },
+    "server_offline": {
+        "description": "Nid yw eich gweinydd yn ymateb i rai o'ch ceisiadau. Isod mae rhai o'r rhesymau mwyaf tebygol.",
+        "description_1": "Cymerodd y gweinydd (%(serverName)s) ormod o amser i ymateb.",
+        "description_2": "Mae eich wal dân neu'ch gwrth-feirws yn rhwystro'r cais.",
+        "description_3": "Mae estyniad porwr yn atal y cais.",
+        "description_4": "Mae'r gweinydd all-lein.",
+        "description_5": "Mae'r gweinydd wedi gwrthod eich cais.",
+        "description_6": "Mae eich ardal yn cael anawsterau wrth gysylltu â'r rhyngrwyd.",
+        "description_7": "Digwyddodd gwall cysylltu wrth geisio cysylltu â'r gweinydd.",
+        "description_8": "Nid yw'r gweinydd wedi'i ffurfweddu i ddangos beth yw'r broblem (CORS).",
+        "empty_timeline": "Rydych chi'n gyfredol nawr.",
+        "recent_changes_heading": "Newidiadau diweddar sydd heb eu derbyn eto",
+        "title": "Nid yw'r gweinydd yn ymateb"
+    },
+    "service_worker_error": {
+        "description": "Mae angen gweithiwr gwasanaeth ar %(brand)s i lwytho cyfryngau dilys o storfeydd cynnwys Matrix. Nid yw hyn yn cael ei gefnogi gan eich porwr felly efallai y byddwch yn profi bod cyfryngau'n methu â llwytho.",
+        "title": "Methwyd llwytho gweithiwr gwasanaeth"
+    },
+    "seshat": {
+        "error_initialising": "Nid oedd modd cychwyn chwilio negeseuon, gwiriwch <a>eich gosodiadau</a> am ragor o wybodaeth",
+        "reset_button": "Ailosod storfa digwyddiad",
+        "reset_description": "Mae'n debyg nad ydych chi eisiau ailosod eich storfa mynegai digwyddiadau",
+        "reset_explainer": "Os felly, nodwch na fydd unrhyw un o'ch negeseuon yn cael eu dileu, ond efallai y bydd y profiad chwilio yn cael ei ddiraddio am ychydig eiliadau tra bod y mynegai yn cael ei ail-greu",
+        "reset_title": "Ailosod storfa digwyddiad?",
+        "warning_kind_files": "Nid yw'r fersiwn hon o %(brand)s yn cefnogi gwylio rhai ffeiliau wedi'u hamgryptio",
+        "warning_kind_files_app": "Defnyddiwch yr <a>ap Bwrdd Gwaith</a> i weld yr holl ffeiliau wedi'u hamgryptio",
+        "warning_kind_search": "Nid yw'r fersiwn hon o %(brand)s yn cefnogi chwilio negeseuon wedi'u hamgryptio",
+        "warning_kind_search_app": "Defnyddiwch yr <a>ap Bwrdd Gwaith</a> i chwilio am negeseuon wedi'u hamgryptio"
+    },
+    "setting": {
+        "help_about": {
+            "access_token_detail": "Mae eich tocyn mynediad yn rhoi mynediad llawn i'ch cyfrif. Peidiwch â'i rannu ag unrhyw un.",
+            "brand_version": "Fersiwn %(brand)s:",
+            "clear_cache_reload": "Clirio'r storfa ac ail-lwytho",
+            "crypto_version": "Fersiwn crypto:",
+            "dialog_title": "<strong>Gosodiadau:</strong> Cymorth ac Ynghylch",
+            "help_link": "I gael help i ddefnyddio %(brand)s, cliciwch <a>yma</a>.",
+            "homeserver": "Mae Homeserver yn <code>%(homeserverUrl)s</code>",
+            "identity_server": "Y gweinydd adnabod yw <code>%(identityServerUrl)s</code>",
+            "title": "Cymorth ac Ynghylch",
+            "versions": "Fersiynau"
+        }
+    },
+    "settings": {
+        "account": {
+            "dialog_title": "<strong>Gosodiadau:</strong> Cyfrif",
+            "title": "Cyfrif"
+        },
+        "all_rooms_home": "Dangos bob ystafell yn y Cartref",
+        "all_rooms_home_description": "Bydd pob ystafell rydych chi ynddi yn ymddangos yn y Cartref.",
+        "always_show_message_timestamps": "Dangos stampiau amser neges bob amser",
+        "appearance": {
+            "bundled_emoji_font": "Defnyddio ffont emoji wedi'i bwndelu",
+            "compact_layout": "Dangos testun cryno a negeseuon",
+            "compact_layout_description": "Rhaid dewis cynllun modern i ddefnyddio'r nodwedd hon.",
+            "custom_font": "Defnyddio ffont y system",
+            "custom_font_description": "Gosodwch enw ffont sydd wedi'i osod ar eich system a bydd %(brand)s yn ceisio ei ddefnyddio.",
+            "custom_font_name": "Enw ffont y system",
+            "custom_font_size": "Defnyddio maint cyfaddas",
+            "custom_theme_add": "Ychwanegu thema gyfaddas",
+            "custom_theme_downloading": "Wrthi'n llwytho i lawr thema cyfaddas…",
+            "custom_theme_error_downloading": "Gwall wrth lwytho'r thema i lawr",
+            "custom_theme_help": "Rhowch URL thema cyfaddas rydych chi am ei gosod.",
+            "custom_theme_invalid": "Sgema thema annilys.",
+            "dialog_title": "<strong>Gosodiadau:</strong> Gwedd",
+            "font_size": "Maint ffont",
+            "font_size_default": "%(fontSize)s (rhagosodedig)",
+            "high_contrast": "Cyferbyniad uchel",
+            "image_size_default": "Rhagosodedig",
+            "image_size_large": "Mawr",
+            "layout_bubbles": "Swigod negeseuon",
+            "layout_irc": "IRC (arbrofol)",
+            "match_system_theme": "Cydweddu thema system",
+            "timeline_image_size": "Maint y ddelwedd yn y llinell amser"
+        },
+        "automatic_language_detection_syntax_highlight": "Galluogi canfod iaith yn awtomatig ar gyfer amlygu cystrawen",
+        "autoplay_gifs": "Awtochwarae GIFs",
+        "autoplay_videos": "Autochwarae fideos",
+        "big_emoji": "Galluogi emoji mawr yn y sgwrs",
+        "code_block_expand_default": "Ehangu blociau cod fel rhagosodiad",
+        "code_block_line_numbers": "Dangos rifau llinell mewn blociau cod",
+        "disable_historical_profile": "Dangos llun proffil cyfredol ac enw ar gyfer defnyddwyr yn hanes neges",
+        "discovery": {
+            "title": "Sut i ddod o hyd i chi"
+        },
+        "emoji_autocomplete": "Galluogi awgrymiadau Emoji wrth deipio",
+        "enable_markdown": "Galluogi Markdown",
+        "enable_markdown_description": "Dechrau negeseuon gyda <code>/plain</code> i'w hanfon heb eu marcio i lawr.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Bydd manylion eich cyfrif, eich cysylltiadau, eich dewisiadau a'ch rhestr sgwrsio yn cael eu cadw",
+                "breadcrumb_page": "Ailosod amgryptio",
+                "breadcrumb_second_description": "Byddwch yn colli unrhyw hanes neges sydd wedi'i storio ar y gweinydd yn unig",
+                "breadcrumb_third_description": "Bydd angen i chi wirio'ch holl ddyfeisiau a chysylltiadau presennol eto",
+                "breadcrumb_title": "Ydych chi'n siŵr eich bod am ailosod eich hunaniaeth?",
+                "breadcrumb_title_forgot": "Wedi anghofio eich allwedd adfer? Bydd angen i chi ailosod eich hunaniaeth.",
+                "breadcrumb_title_sync_failed": "Wedi methu cysoni storfa allwedd. Mae angen i chi ailosod eich hunaniaeth.",
+                "breadcrumb_warning": "Gwnewch hyn dim ond os ydych chi'n credu bod eich cyfrif wedi'i beryglu.",
+                "details_title": "Manylion amgryptio",
+                "do_not_close_warning": "Peidiwch â chau'r ffenestr hon nes bod yr ailosod wedi'i orffen",
+                "export_keys": "Allforio allweddi",
+                "import_keys": "Mewnforio bysellau",
+                "other_people_device_description": "Rhybudd: fydd defnyddwyr sydd heb eu gwirio'n benodol gyda chi (e.e. gan ddefnyddio emoji) yn derbyn eich negeseuon wedi'u hamgryptio. Hefyd, fydd dyfeisiau defnyddwyr sydd wedi'u gwirio heb eu gwirio ddim yn derbyn eich negeseuon wedi'u hamgryptio.",
+                "other_people_device_label": "Peidiwch byth ag anfon negeseuon wedi'u hamgryptio i ddyfeisiau heb eu gwirio",
+                "other_people_device_title": "Dyfeisiau pobl eraill",
+                "reset_identity": "Ailosod hunaniaeth cryptograffig",
+                "reset_in_progress": "Wrthi'n ailosod...",
+                "session_id": "ID y sesiwn:",
+                "session_key": "Allwedd sesiwn:",
+                "title": "Uwch"
+            },
+            "confirm_key_storage_off": "Ydych chi'n siŵr eich bod chi eisiau cadw storfa allweddi wedi'i diffodd?",
+            "confirm_key_storage_off_description": "Os byddwch chi'n allgofnodi o'ch holl ddyfeisiau byddwch chi'n colli'ch hanes negeseuon a bydd angen i chi wirio'ch holl gysylltiadau presennol eto. <a>Dysgu mwy</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Dileu storfa allweddi",
+                "confirm": "Dileu storfa allweddi",
+                "description": "Bydd dileu storfa allweddi'n tynnu eich hunaniaeth cryptograffig a'ch allweddi neges o'r gweinydd ac yn diffodd y nodweddion diogelwch canlynol:",
+                "list_first": "Ni fydd gennych hanes negeseuon wedi'i amgryptio ar ddyfeisiau newydd",
+                "list_second": "Byddwch yn colli mynediad i'ch negeseuon wedi'u hamgryptio os ydych wedi'ch allgofnodi o %(brand)s ym mhobman",
+                "title": "Ydych chi'n siŵr eich bod am ddiffodd storfa allweddi a'i dileu?"
+            },
+            "device_not_verified_button": "Dilyswch y ddyfais hon",
+            "device_not_verified_description": "Mae angen i chi wirio'r ddyfais hon er mwyn gweld eich gosodiadau amgryptio.",
+            "device_not_verified_title": "Dyfais heb ei gwirio",
+            "dialog_title": "<strong>Gosodiadau:</strong> Amgryptio",
+            "key_storage": {
+                "allow_key_storage": "Caniatáu storio allweddi",
+                "description": "Cadwch eich hunaniaeth cryptograffig a'ch allweddi neges yn ddiogel ar y gweinydd. Bydd hyn yn caniatáu i chi weld hanes eich neges ar unrhyw ddyfeisiau newydd. <a>Dysgwch fwy</a>",
+                "title": "Storio allweddol"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Cadarnhau allwedd adfer newydd",
+                "change_recovery_confirm_description": "Rhowch eich allwedd adfer newydd isod i orffen. Ni fydd eich hen un yn gweithio mwyach.",
+                "change_recovery_confirm_title": "Rhowch eich allwedd adfer newydd",
+                "change_recovery_key": "Newid allwedd adfer",
+                "change_recovery_key_description": "Ysgrifennwch yr allwedd adfer newydd hon yn rhywle diogel. Yna cliciwch Parhau i gadarnhau'r newid.",
+                "change_recovery_key_title": "Newid allwedd adfer?",
+                "description": "Adfer eich hunaniaeth cryptograffig a hanes neges gydag allwedd adfer os ydych chi wedi colli'ch holl ddyfeisiau presennol.",
+                "enter_key_error": "Nid yw'r allwedd adfer a roesoch yn gywir.",
+                "enter_recovery_key": "Rhowch allwedd adfer",
+                "forgot_recovery_key": "Wedi anghofio allwedd adfer?",
+                "key_storage_warning": "Nid yw eich storfa allweddi wedi'i chydweddu. Cliciwch ar y botwm isod i ddatrys y broblem.",
+                "save_key_description": "Peidiwch â rhannu hwn gyda neb!",
+                "save_key_title": "Allwedd adfer",
+                "set_up_recovery": "Gosod adfer",
+                "set_up_recovery_confirm_button": "Gorffen adfer",
+                "set_up_recovery_confirm_description": "Rhowch yr allwedd adfer sy'n cael ei ddangos ar y sgrin flaenorol i orffen osod yr adfer.",
+                "set_up_recovery_confirm_title": "Rhowch eich allwedd adfer i gadarnhau",
+                "set_up_recovery_description": "Mae eich storfa allweddi wedi'i diogelu gan allwedd adfer. Os oes angen allwedd adfer newydd arnoch ar ôl gosod, gallwch ei hail-greu trwy ddewis ‘%(changeRecoveryKeyButton)s’.",
+                "set_up_recovery_save_key_description": "Ysgrifennwch yr allwedd adfer hon yn rhywle diogel, fel rheolwr cyfrinair, nodyn wedi'i amgryptio, neu mewn man dan glo.",
+                "set_up_recovery_save_key_title": "Cadwch eich allwedd adfer yn rhywle diogel",
+                "set_up_recovery_secondary_description": "Ar ôl clicio parhau, byddwn yn cynhyrchu allwedd adfer i chi.",
+                "title": "Adfer"
+            },
+            "title": "Amgryptio"
+        },
+        "general": {
+            "account_management_section": "Rheoli Cyfrifon",
+            "account_section": "Cyfrif",
+            "add_email_dialog_title": "Ychwanegu Cyfeiriad E-bost",
+            "add_email_failed_verification": "Wedi methu gwirio cyfeiriad e-bost: gwnewch yn siŵr eich bod wedi clicio'r ddolen yn yr e-bost",
+            "add_email_instructions": "Rydym wedi anfon e-bost atoch i wirio eich cyfeiriad. Dilynwch y cyfarwyddiadau yno ac yna cliciwch ar y botwm isod.",
+            "add_msisdn_confirm_body": "Cliciwch y botwm isod i gadarnhau ychwanegu'r rhif ffôn hwn.",
+            "add_msisdn_confirm_button": "Cadarnhewch ychwanegu rhif ffôn",
+            "add_msisdn_confirm_sso_button": "Cadarnhewch ychwanegu'r rhif ffôn hwn trwy ddefnyddio Mewngofnodi Sengl i brofi pwy ydych.",
+            "add_msisdn_dialog_title": "Ychwanegu Rhif Ffôn",
+            "add_msisdn_instructions": "Mae neges destun wedi'i hanfon at +%(msisdn)s. Rhowch y cod dilysu sydd ynddo.",
+            "add_msisdn_misconfigured": "Mae'r ychwanegu / rhwymo gyda llif MSISDN wedi'i gam ffurfweddu",
+            "allow_spellcheck": "Caniatáu gwirio sillafu",
+            "application_language": "Iaith y rhaglen",
+            "application_language_reload_hint": "Bydd yr ap yn ail-lwytho ar ôl dewis iaith arall",
+            "avatar_remove_progress": "Wrthi'n tynnu'r ddelwedd...",
+            "avatar_save_progress": "Wrthi'n llwytho delwedd...",
+            "avatar_upload_error_text": "Nid yw fformat y ffeil yn cael ei gefnodi neu mae'r ddelwedd yn fwy na %(size)s.",
+            "avatar_upload_error_text_generic": "Mae'n bosibl nad yw fformat y ffeil yn cael ei gefnogi.",
+            "avatar_upload_error_title": "Ni fu modd llwytho delwedd afatar",
+            "confirm_adding_email_body": "Cliciwch y botwm isod i gadarnhau ychwanegu'r cyfeiriad e-bost hwn.",
+            "confirm_adding_email_title": "Cadarnhewch ychwanegu e-bost",
+            "deactivate_confirm_body": "Ydych chi'n siŵr eich bod am gau'r cyfrif? Nid oes modd dadwneud hyn.",
+            "deactivate_confirm_body_sso": "Cadarnhewch fod eich cyfrif wedi'i chau trwy ddefnyddio Mewngofnodi Sengli brofi pwy ydych.",
+            "deactivate_confirm_content": "Cadarnhewch yr hoffech gau'ch cyfrif. Os ewch ymlaen:",
+            "deactivate_confirm_content_1": "Fyddwch chi ddim yn gallu ailgychwyn eich cyfrif",
+            "deactivate_confirm_content_2": "Fyddwch chi ddim yn gallu mewngofnodi mwyach",
+            "deactivate_confirm_content_3": "Fydd neb yn gallu ailddefnyddio eich enw defnyddiwr (MXID), gan eich cynnwys chi: fydd yr enw defnyddiwr hwn ddim ar gael i neb",
+            "deactivate_confirm_content_4": "Byddwch yn gadael pob ystafell a DM rydych ynddyn nhw",
+            "deactivate_confirm_content_5": "Byddwch yn cael eich tynnu oddi ar y gweinydd hunaniaeth: fydd eich ffrindiau bellach ddim yn gallu dod o hyd i chi gyda'ch e-bost neu rif ffôn",
+            "deactivate_confirm_content_6": "Bydd eich hen negeseuon yn dal i fod yn weladwy i bobl sydd wedi eu derbyn, yn union fel e-byst a anfonwyd gennych yn y gorffennol. Hoffech chi guddio'ch negeseuon a anfonwyd rhag pobl sy'n ymuno ag ystafelloedd yn y dyfodol?",
+            "deactivate_confirm_continue": "Cadarnhau cau'ch cyfrif",
+            "deactivate_confirm_erase_label": "Cuddio fy negeseuon oddi wrth y rhai sy'n ymuno o'r newydd",
+            "deactivate_section": "Cau'r Cyfrif",
+            "deactivate_warning": "Mae cau eich cyfrif yn weithred barhaol - byddwch yn ofalus!",
+            "discovery_email_empty": "Bydd dewisiadau darganfod yn ymddangos unwaith y byddwch wedi ychwanegu e-bost.",
+            "discovery_email_verification_instructions": "Gwiriwch y ddolen yn eich blwch derbyn",
+            "discovery_msisdn_empty": "Bydd dewisiadau darganfod yn ymddangos unwaith y byddwch wedi ychwanegu rhif ffôn.",
+            "discovery_needs_terms": "Cytunwch i Delerau Gwasanaeth y gweinydd hunaniaeth (%(serverName)s) i ganiatáu i chi'ch hun fod yn hawdd i'ch darganfod trwy gyfeiriad e-bost neu rif ffôn.",
+            "discovery_needs_terms_title": "Gadewch i bobl ddod o hyd i chi",
+            "display_name": "Enw Dangos",
+            "display_name_error": "Methu gosod enw dangos",
+            "email_adding_unsupported_by_hs": "Nid yw'r gweinydd cartref hwn yn cefnogi ychwanegu cyfeiriadau e-bost at eich cyfrif.",
+            "email_address_in_use": "Mae'r cyfeiriad e-bost hwn eisoes yn cael ei ddefnyddio",
+            "email_address_label": "Cyfeiriad e-bost",
+            "email_not_verified": "Nid yw eich cyfeiriad e-bost wedi'i wirio eto",
+            "email_verification_instructions": "Cliciwch ar y ddolen yn yr e-bost gawsoch i ddilysu ac yna cliciwch parhau eto.",
+            "emails_heading": "Cyfeiriadau e-bost",
+            "error_add_email": "Methu ychwanegu cyfeiriad e-bost",
+            "error_deactivate_communication": "Roedd problem wrth gyfathrebu gyda'r gweinydd. Ceisiwch eto.",
+            "error_deactivate_invalid_auth": "Nid yw'r gweinydd wedi dilysu'r manylion dilysu.",
+            "error_deactivate_no_auth": "Nid oedd angen unrhyw ddilysiad ar y gweinydd",
+            "error_email_verification": "Methu gwirio cyfeiriad e-bost.",
+            "error_invalid_email": "Cyfeiriad E-bost Annilys",
+            "error_invalid_email_detail": "Nid yw'n ymddangos bod hwn yn gyfeiriad e-bost dilys",
+            "error_msisdn_verification": "Methu gwirio'r rhif ffôn.",
+            "error_password_change_403": "Wedi methu newid y cyfrinair. Ydy'ch cyfrinair yn gywir?",
+            "error_password_change_http": "%(errorMessage)s (statws HTTP %(httpStatus)s)",
+            "error_password_change_title": "Gwall wrth newid cyfrinair",
+            "error_password_change_unknown": "Gwall newid cyfrinair anhysbys (%(stringifiedError)s)",
+            "error_remove_3pid": "Methu dileu manylion cyswllt",
+            "error_revoke_email_discovery": "Methu â dirymu rhannu ar gyfer cyfeiriad e-bost",
+            "error_revoke_msisdn_discovery": "Methu â dirymu rhannu ar gyfer rhif ffôn",
+            "error_share_email_discovery": "Methu rhannu cyfeiriad e-bost",
+            "error_share_msisdn_discovery": "Methu rhannu rhif ffôn",
+            "identity_server_no_token": "Heb ganfod docyn mynediad adnabod",
+            "identity_server_not_set": "Heb osod gweinydd hunaniaeth",
+            "invalid_phone_number": "Nid yw'n ymddangos bod y rhif ffôn a ddarparwyd yn ddilys.",
+            "language_section": "Iaith",
+            "msisdn_adding_unsupported_by_hs": "Nid yw'r gweinydd cartref hwn yn cefnogi ychwanegu rhifau ffôn at eich cyfrif.",
+            "msisdn_in_use": "Mae'r rhif ffôn hwn eisoes yn cael ei ddefnyddio",
+            "msisdn_label": "Rhif Ffôn",
+            "msisdn_verification_field_label": "Cod dilysu",
+            "msisdn_verification_instructions": "Rhowch y cod dilysu a anfonwyd trwy neges destun.",
+            "msisdns_heading": "Rhifau ffôn",
+            "oidc_manage_button": "Rheoli cyfrif",
+            "password_change_section": "Gosod cyfrinair cyfrif newydd…",
+            "password_change_success": "Cafodd eich cyfrinair ei newid yn llwyddiannus.",
+            "personal_info": "Manylion personol",
+            "profile_subtitle": "Dyma sut rydych chi'n ymddangos i eraill ar yr ap.",
+            "profile_subtitle_oidc": "Mae eich cyfrif yn cael ei reoli ar wahân gan ddarparwr hunaniaeth ac felly nid oes modd newid rhywfaint o'ch manylion personol yma.",
+            "remove_email_prompt": "Tynnu %(email)s?",
+            "remove_msisdn_prompt": "Tynnu %(phone)s?",
+            "spell_check_locale_placeholder": "Dewiswch locale",
+            "unable_to_load_emails": "Methu llwytho cyfeiriadau e-bost",
+            "unable_to_load_msisdns": "Methu llwytho rhifau ffôn",
+            "username": "Enw defnyddiwr"
+        },
+        "inline_url_previews_default": "Galluogi rhagolygon URL mewnol fel rhagosodiad",
+        "inline_url_previews_room": "Galluogi rhagolygon URL fel rhagosodiad ar gyfer cyfranogwyr yn yr ystafell hon",
+        "inline_url_previews_room_account": "Galluogi rhagolygon URL ar gyfer yr ystafell hon (yn effeithio arnoch chi yn unig)",
+        "insert_trailing_colon_mentions": "Mewnosod colon sy'n llusgo ar ôl i'r defnyddiwr sôn amdano ar ddechrau neges",
+        "jump_to_bottom_on_send": "Symud i waelod y llinell amser pan fyddwch chi'n anfon neges",
+        "key_backup": {
+            "backup_in_progress": "Mae copi wrth gefn o'ch allweddi (gallai'r copi wrth gefn cyntaf gymryd ychydig funudau).",
+            "backup_starting": "Yn dechrau gwneud copi wrth gefn…",
+            "backup_success": "Llwyddiant!",
+            "cannot_create_backup": "Methu creu copi wrth gefn o'r allwedd",
+            "create_title": "Creu copi wrth gefn allweddol",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "Mae'ch allweddi bellach yn cael eu gwneud wrth gefn o'r ddyfais hon.",
+                "backup_setup_success_title": "Copi Wrth Gefn Diogel yn llwyddiannus",
+                "cancel_warning": "Os byddwch yn diddymu nawr, efallai y byddwch yn colli negeseuon a data wedi'u hamgryptio os byddwch yn colli mynediad at eich mewngofnodi.",
+                "confirm_security_phrase": "Cadarnhewch eich Ymadrodd Diogelwch",
+                "description": "Diogelwch rhag colli mynediad i negeseuon a data wedi'u hamgryptio trwy wneud copïau wrth gefn o allweddi amgryptio ar eich gweinydd.",
+                "download_or_copy": "%(downloadButton)s neu %(copyButton)s",
+                "enter_phrase_description": "Rhowch Ymadrodd Diogelwch dim ond rydych chi'n ei wybod, gan ei fod yn cael ei ddefnyddio i ddiogelu'ch data. I fod yn ddiogel, ni ddylech ailddefnyddio cyfrinair eich cyfrif.",
+                "enter_phrase_title": "Rhowch Ymadrodd Diogelwch",
+                "enter_phrase_to_confirm": "Rhowch eich Ymadrodd Diogelwch yr eildro i'w gadarnhau.",
+                "generate_security_key_description": "Byddwn yn cynhyrchu Allwedd Adfer i chi ei storio yn rhywle diogel, fel rheolwr cyfrinair neu sêff.",
+                "generate_security_key_title": "Cynhyrchu Allwedd Adfer",
+                "pass_phrase_match_failed": "Nid yw hynny'n cyfateb.",
+                "pass_phrase_match_success": "Mae hynny'n cyfateb!",
+                "phrase_strong_enough": "Gwych! Mae'r Ymadrodd Diogelwch hwn yn edrych yn ddigon cryf.",
+                "secret_storage_query_failure": "Methu cwestiynu statws storio cyfrinachol",
+                "security_key_safety_reminder": "Storiwch eich Allwedd Adfer yn rhywle diogel, fel rheolwr cyfrinair neu sêff, gan ei fod yn cael ei ddefnyddio i ddiogelu eich data wedi'i amgryptio.",
+                "set_phrase_again": "Mynd yn ôl i'w osod eto.",
+                "settings_reminder": "Gallwch hefyd osod Copi wrth Gefn Diogel a rheoli'ch allweddi yn y Gosodiadau.",
+                "title_confirm_phrase": "Cadarnhau'r Ymadrodd Diogelwch",
+                "title_save_key": "Cadw eich Allwedd Adfer",
+                "title_set_phrase": "Gosod Ymadrodd Diogelwch",
+                "unable_to_setup": "Methu gosod storfa gyfrinachol",
+                "use_different_passphrase": "Defnyddio cyfrinair gwahanol?",
+                "use_phrase_only_you_know": "Defnyddiwch ymadrodd cyfrinachol yn unig rydych chi'n ei wybod, ac yn ddewisol arbedwch Allwedd Adfer i'w ddefnyddio fel copi wrth gefn."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "Cadarnhau'r cyfrinymadrodd",
+            "enter_passphrase": "Rhowch gyfrinymadrodd",
+            "export_description_1": "Mae'r broses hon yn eich galluogi i allforio'r allweddi ar gyfer negeseuon rydych wedi'u derbyn mewn ystafelloedd wedi'u hamgryptio i ffeil leol. Yna byddwch chi'n gallu mewnforio'r ffeil i gleient Matrics arall yn y dyfodol, fel y bydd y cleient hwnnw hefyd yn gallu dadgryptio'r negeseuon hyn.",
+            "export_description_2": "Bydd y ffeil a allforir yn caniatáu i unrhyw un sy'n gallu ei darllen i ddadgryptio unrhyw negeseuon wedi'u hamgryptio y gallwch eu gweld, felly dylech fod yn ofalus i'w gadw'n ddiogel. I helpu gyda hyn, dylech nodi cyfrinair unigryw isod, a fydd ond yn cael ei ddefnyddio i amgryptio'r data a allforiwyd. Dim ond trwy ddefnyddio'r un cyfrinair y bydd modd mewnforio'r data.",
+            "export_title": "Allforio allweddi ystafell",
+            "file_to_import": "Ffeil i'w mewnforio",
+            "import_description_1": "Mae'r broses hon yn caniatáu i chi fewnforio allweddi amgryptio yr oeddech wedi'u hallforio o'r blaen o gleient Matrix arall. Yna byddwch yn gallu dadgryptio unrhyw negeseuon y gallai'r cleient arall eu dadgryptio.",
+            "import_description_2": "Bydd y ffeil allforio yn cael ei diogelu gyda chyfrinair. Dylech roi'r cyfrinair yma, i ddadgryptio'r ffeil.",
+            "import_title": "Mewnforio allweddi ystafell",
+            "phrase_cannot_be_empty": "Rhaid i gyfrinymadrodd beidio â bod yn wag",
+            "phrase_must_match": "Rhaid i gyfrinymadoddion gyfateb",
+            "phrase_strong_enough": "Gwych! Mae'r cyfrinymadrodd hwn yn edrych yn ddigon cryf"
+        },
+        "keyboard": {
+            "dialog_title": "<strong>Gosodiadau:</strong> Bysellfwrdd",
+            "title": "Bysellfwrdd"
+        },
+        "labs": {
+            "dialog_title": "<strong>Gosodiadau:</strong> Labs"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Gosodiadau:</strong> Defnyddwyr wedi'u Hanwybyddu"
+        },
+        "media_preview": {
+            "hide_avatars": "Cuddio afatarau o'r ystafell a'r gwahoddwr",
+            "hide_media": "Cuddio bob tro",
+            "media_preview_description": "Mae modd dangos cyfrwng cudd trwy dapio arno",
+            "media_preview_label": "Dangos cyfryngau mewn llinell amser",
+            "show_in_private": "Mewn ystafelloedd preifat",
+            "show_media": "Dangos bob tro"
+        },
+        "notifications": {
+            "default_setting_description": "Bydd y gosodiad hwn yn cael ei osod fel rhagosodiad i'ch holl ystafelloedd.",
+            "default_setting_section": "Rwyf am gael fy hysbysu am (Gosodiad Rhagosodedig)",
+            "desktop_notification_message_preview": "Dangos rhagolwg neges mewn hysbysiad bwrdd gwaith",
+            "dialog_title": "<strong>Gosodiadau:</strong> Hysbysiadau",
+            "email_description": "Derbyn crynodeb e-bost o hysbysiadau a gollwyd",
+            "email_section": "Crynodeb e-bost",
+            "email_select": "Dewiswch pa e-byst rydych chi am anfon crynodebau atynt. Rheoli eich e-byst yn <button>Cyffredinol</button>.",
+            "enable_audible_notifications_session": "Galluogi hysbysiadau clywadwy ar gyfer y sesiwn hon",
+            "enable_desktop_notifications_session": "Galluogi hysbysiadau bwrdd gwaith ar gyfer y sesiwn hon",
+            "enable_email_notifications": "Galluogi hysbysiadau e-bost ar gyfer %(email)s",
+            "enable_notifications_account": "Galluogi hysbysiadau ar gyfer y cyfrif hwn",
+            "enable_notifications_account_detail": "Diffodd i analluogi hysbysiadau ar eich holl ddyfeisiau a sesiynau",
+            "enable_notifications_device": "Galluogi hysbysiadau ar gyfer y ddyfais hon",
+            "error_loading": "Bu gwall wrth lwytho eich gosodiadau hysbysu.",
+            "error_permissions_denied": "Nid oes gan %(brand)s ganiatâd i anfon hysbysiadau atoch - gwiriwch osodiadau eich porwr",
+            "error_permissions_missing": "Heb roi caniatâd i %(brand)s anfon hysbysiadau - ceisiwch eto",
+            "error_saving": "Gwall wrth gadw dewisiadau hysbysiad",
+            "error_saving_detail": "Digwyddodd gwall wrth gadw'ch dewisiadau hysbysu.",
+            "error_title": "Methu galluogi Hysbysiadau",
+            "error_updating": "Bu gwall wrth ddiweddaru eich dewisiadau hysbysu. Ceisiwch newid eich opsiwn eto.",
+            "invites": "Gwahoddiad i ystafell",
+            "keywords": "Dangos bathodyn<badge/> pan yn defnyddio allweddeiriau mewn ystafell.",
+            "keywords_prompt": "Rhowch allweddeiriau yma, neu defnyddiwch ar gyfer amrywiadau sillafu neu lysenwau",
+            "labs_notice_prompt": "<strong>Diweddariad:</strong> Rydym wedi symleiddio Gosodiadau Hysbysiadau i'w gwneud yn haws dod o hyd i dewisiadau. Nid yw rhai gosodiadau cyfaddas rydych chi wedi'u dewis yn y gorffennol yn cael eu dangos yma, ond maen nhw'n dal yn weithredol. Os ewch ymlaen, efallai y bydd rhai o'ch gosodiadau'n newid. <a>Dysgu rhagor</a>",
+            "mentions_keywords": "Crybwyll ac Allweddeiriau",
+            "mentions_keywords_only": "Crybwyll ac Allweddeiriau yn unig",
+            "messages_containing_keywords": "Negeseuon yn cynnwys allweddeiriau",
+            "noisy": "Swnllyd",
+            "notices": "Negeseuon a anfonwyd gan fotiau",
+            "notify_at_room": "Rhowch wybod pan fydd rhywun yn sôn am ddefnyddio @room",
+            "notify_keyword": "Rhowch wybod pan fydd rhywun yn defnyddio allweddair",
+            "notify_mention": "Rhoi gwybod pan fydd rhywun yn sôn am ddefnyddio @displaymatere neu %(mxid)s",
+            "other_section": "Pethau eraill y credwn y gallai fod gennych ddiddordeb ynddynt:",
+            "people_mentions_keywords": "Pobl, Crybwyll ac Allweddeiriau",
+            "play_sound_for_description": "Wedi'i osod fel rhagosodiad i bob ystafell ar bob dyfais.",
+            "play_sound_for_section": "Chwarae sain ar gyfer",
+            "push_targets": "Targedau hysbysu",
+            "quick_actions_mark_all_read": "Marciwch bob neges wedi'i darllen",
+            "quick_actions_reset": "Ailosod i osodiadau rhagosodedig",
+            "quick_actions_section": "Gweithredoedd Cyflym",
+            "room_activity": "Mae gweithgarwch ystafell newydd, uwchraddiadau a negeseuon statws yn digwydd",
+            "rule_call": "Galwad gwahoddiad",
+            "rule_contains_display_name": "Negeseuon yn cynnwys fy enw dangos",
+            "rule_contains_user_name": "Negeseuon yn cynnwys fy enw defnyddiwr",
+            "rule_encrypted": "Negeseuon wedi'u hamgryptio mewn sgyrsiau grŵp",
+            "rule_encrypted_room_one_to_one": "Negeseuon wedi'u hamgryptio mewn sgyrsiau un-i-un",
+            "rule_invite_for_me": "Pan yn cael fy ngwahodd i ystafell",
+            "rule_message": "Negeseuon mewn sgyrsiau grŵp",
+            "rule_room_one_to_one": "Negeseuon mewn sgyrsiau un-i-un",
+            "rule_roomnotif": "Negeseuon yn cynnwys @room",
+            "rule_suppress_notices": "Negeseuon a anfonwyd gan bot",
+            "rule_tombstone": "Pan fydd ystafelloedd yn cael eu huwchraddio",
+            "show_message_desktop_notification": "Dangos neges mewn hysbysiad bwrdd gwaith",
+            "voip": "Galwadau Sain a Fideo"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "Galluogi cyflymiad caledwedd (ailgychwyn %(appName)s i ddod i rym)",
+            "always_show_menu_bar": "Dangos far dewislen y ffenestr bob amser",
+            "autocomplete_delay": "Oedi awtogwblhau (ms)",
+            "code_blocks_heading": "Blociau cod",
+            "compact_modern": "Defnyddio cynllun 'Modern' mwy cryno",
+            "composer_heading": "Cyfansoddwr",
+            "default_timezone": "Porwr rhagosodedig (%(timezone)s)",
+            "dialog_title": "<strong>Gosodiadau:</strong> Dewisiadau",
+            "enable_hardware_acceleration": "Galluogi cyflymiad caledwedd",
+            "enable_tray_icon": "Dangos eicon hambwrdd a lleihau ffenestr iddo ar agos",
+            "keyboard_heading": "Bysellau brys",
+            "keyboard_view_shortcuts_button": "I weld yr holl lwybrau byr bysellfwrdd, <a>cliciwch yma</a>.",
+            "media_heading": "Delweddau, GIFs a fideos",
+            "presence_description": "Rhannu'ch gweithgarwch a'ch statws ag eraill.",
+            "publish_timezone": "Cyhoeddi cylchfa amser ar broffil cyhoeddus",
+            "rm_lifetime": "Darllen oes Marciwr (ms)",
+            "rm_lifetime_offscreen": "Darllen Marciwr oes oddi ar y sgrin (ms)",
+            "room_directory_heading": "Cyfeiriadur ystafelloedd",
+            "room_list_heading": "Rhestr ystafelloedd",
+            "show_avatars_pills": "Dangos rhithffurfiau mewn cyfeiriadau defnyddiwr, ystafell a digwyddiad",
+            "show_polls_button": "Dangos y botwm arolygon",
+            "surround_text": "Amgylchynu testun dethol wrth deipio nodau arbennig",
+            "time_heading": "Yn dangos amser",
+            "user_timezone": "Gosod cylchfa amser"
+        },
+        "prompt_invite": "Annog cyn anfon gwahoddiadau i IDau matrics a allai fod yn annilys",
+        "replace_plain_emoji": "Amnewid Emoji testun plaen yn awtomatig",
+        "security": {
+            "analytics_description": "Rhanwch ddata dienw i'n helpu i nodi problemau. Dim byd personol. Dim trydydd partïon.",
+            "bulk_options_accept_all_invites": "Derbyn pob %(invitedRooms)s gwahoddiad",
+            "bulk_options_reject_all_invites": "Gwrthod pob %(invitedRooms)s gwahoddiad",
+            "bulk_options_section": "Dewisiadau lluosog",
+            "dehydrated_device_description": "Mae'r nodwedd dyfais all-lein yn caniatáu ichi dderbyn negeseuon wedi'u hamgryptio hyd yn oed pan nad ydych wedi mewngofnodi i unrhyw ddyfeisiau",
+            "dehydrated_device_enabled": "Dyfais all-lein wedi'i galluogi",
+            "dialog_title": "<strong>Gosodiadau:</strong> Diogelwch a Phreifatrwydd",
+            "e2ee_default_disabled_warning": "Mae gweinyddwr eich gweinydd wedi analluogi amgryptio pen-i-ddiwedd fel rhagosodiad mewn ystafelloedd preifat a Negeseuon Uniongyrchol.",
+            "enable_message_search": "Galluogi chwiliad negeseuon mewn ystafelloedd wedi'u hamgryptio",
+            "encryption_section": "Amgryptio",
+            "ignore_users_empty": "Nid oes gennych unrhyw ddefnyddwyr sydd wedi'u hanwybyddu.",
+            "ignore_users_section": "Defnyddwyr wedi'u hanwybyddu",
+            "key_backup_algorithm": "Algorithm:",
+            "key_backup_connect": "Cysylltwch y sesiwn hon i Bysell Wrth Gefn",
+            "message_search_disable_warning": "Os yw wedi'i analluogi, ni fydd negeseuon o ystafelloedd wedi'u hamgryptio yn ymddangos yn y canlyniadau chwilio.",
+            "message_search_disabled": "Cadwch negeseuon wedi'u hamgryptio'n ddiogel yn lleol er mwyn iddyn nhw ymddangos yn y canlyniadau chwilio.",
+            "message_search_failed": "Methwyd cychwyn chwiliad neges",
+            "message_search_indexed_messages": "Negeseuon wedi'u mynegeio:",
+            "message_search_indexed_rooms": "Ystafelloedd wedi'u mynegeio:",
+            "message_search_indexing": "Yn mynegeio ar hyn o bryd: %(currentRoom)s",
+            "message_search_indexing_idle": "Ddim yn mynegeio negeseuon ar gyfer unrhyw ystafell ar hyn o bryd.",
+            "message_search_intro": "Mae %(brand)s yn cadw negeseuon wedi'u hamgryptio'n ddiogel yn lleol er mwyn iddynt ymddangos yn y canlyniadau chwilio:",
+            "message_search_room_progress": "%(doneRooms)s allan o %(totalRooms)s",
+            "message_search_section": "Chwilio am neges",
+            "message_search_sleep_time": "Pa mor gyflym dylai negeseuon gael eu llwytho i lawr.",
+            "message_search_space_used": "Gofod wedi'i ddefnyddio:",
+            "message_search_unsupported": "Mae %(brand)s ar goll o rai cydrannau sydd eu hangen i gadw negeseuon wedi'u hamgryptio'n ddiogel yn lleol. Os hoffech arbrofi gyda'r nodwedd hon, adeiladwch Bwrdd Gwaith %(brand)s wedi'i deilwra ag ef<nativeLink> cydrannau chwilio wedi'u hychwanegu</nativeLink>.",
+            "message_search_unsupported_web": "Ni all %(brand)s storio negeseuon wedi'u hamgryptio'n ddiogel yn lleol tra'n rhedeg mewn porwr gwe. Defnydd<desktopLink> %(brand)s Bwrdd Gwaith</desktopLink> i negeseuon wedi'u hamgryptio ymddangos yn y canlyniadau chwilio.",
+            "record_session_details": "Cofnodwch enw'r cleient, y fersiwn, a'r url i adnabod sesiynau'n haws yn y rheolwr sesiwn",
+            "send_analytics": "Anfon data dadansoddeg",
+            "strict_encryption": "Peidio byth anfon negeseuon wedi'u hamgryptio i sesiynau heb eu gwirio o'r sesiwn hon"
+        },
+        "send_read_receipts": "Anfon derbynebau darllen",
+        "send_read_receipts_unsupported": "Nid yw eich gweinydd yn cefnogi analluogi anfon derbynebau wedi'u darllen.",
+        "send_typing_notifications": "Anfon hysbysiadau teipio",
+        "sessions": {
+            "best_security_note": "Er mwyn sicrhau'r diogelwch gorau, gwiriwch eich sesiynau ac allgofnodwch o unrhyw sesiwn nad ydych yn ei hadnabod nac yn ei defnyddio mwyach.",
+            "browser": "Porwr",
+            "current_session": "Y sesiwn gyfredol",
+            "desktop_session": "Sesiwn bwrdd gwaith",
+            "details_heading": "Manylion y sesiwn",
+            "device_unverified_description": "Dilyswch neu allgofnodwch o'r sesiwn hon er mwyn sicrhau'r diogelwch a'r dibynadwyedd gorau.",
+            "device_unverified_description_current": "Dilyswch eich sesiwn gyfredol ar gyfer gwell negeseuon diogel.",
+            "device_verified_description": "Mae'r sesiwn hon yn barod ar gyfer negeseuon diogel.",
+            "device_verified_description_current": "Mae eich sesiwn gyfredol yn barod ar gyfer negeseuon diogel.",
+            "dialog_title": "<strong>Gosodiadau:</strong> Sesiynau",
+            "error_pusher_state": "Wedi methu â gosod cyflwr gwthio",
+            "error_set_name": "Wedi methu gosod enw'r sesiwn",
+            "filter_all": "Y Cyfan",
+            "filter_inactive": "Anweithredol",
+            "filter_inactive_description": "Anweithredol ers %(inactiveAgeDays)s diwrnod neu fwy",
+            "filter_label": "Hidlo dyfeisiau",
+            "filter_unverified_description": "Ddim yn barod ar gyfer negeseuon diogel",
+            "filter_verified_description": "Yn barod ar gyfer negeseuon diogel",
+            "hide_details": "Cuddio manylion",
+            "inactive_days": "Anweithredol ers %(inactiveAgeDays)s+ diwrnod",
+            "inactive_sessions": "Sesiynau anweithredol",
+            "inactive_sessions_explainer_1": "Mae sesiynau anweithredol yn sesiynau nad ydych wedi'u defnyddio ers peth amser, ond maent yn parhau i dderbyn allweddi amgryptio.",
+            "inactive_sessions_explainer_2": "Mae cael gwared ar sesiynau anweithredol yn gwella diogelwch a pherfformiad, ac yn ei gwneud yn haws i chi nodi a yw sesiwn newydd yn amheus.",
+            "inactive_sessions_list_description": "Ystyriwch allgofnodi o hen sesiynau (%(inactiveAgeDays)s diwrnod neu hŷn) nad ydych yn eu defnyddio mwyach.",
+            "ip": "Cyfeiriad IP",
+            "last_activity": "Gweithgaredd ddiwethaf",
+            "manage": "Rheoli'r sesiwn hon",
+            "mobile_session": "Sesiwn symudol",
+            "no_inactive_sessions": "Heb ganfod unrhyw sesiynau anweithredol.",
+            "no_sessions": "Heb ganfod sesiynau.",
+            "no_unverified_sessions": "Heb ganfod unrhyw sesiynau heb eu gwirio.",
+            "no_verified_sessions": "Heb ganfod unrhyw sesiynau wedi'u dilysu.",
+            "os": "System weithredu",
+            "other_sessions_heading": "Sesiynau eraill",
+            "push_heading": "Hysbysiadau gwthiadwy",
+            "push_subheading": "Derbyn hysbysiadau gwthio ar y sesiwn hon.",
+            "push_toggle": "Toglo hysbysiadau gwthio ar y sesiwn hon.",
+            "rename_form_caption": "Byddwch yn ymwybodol bod enwau sesiynau hefyd yn weladwy i'r bobl rydych chi'n cyfathrebu â nhw.",
+            "rename_form_heading": "Ail-enwi sesiwn",
+            "rename_form_learn_more": "Sesiynau ailenwi",
+            "rename_form_learn_more_description_1": "Mae defnyddwyr eraill mewn negeseuon uniongyrchol ac ystafelloedd yr ydych yn ymuno â nhw yn gallu gweld rhestr lawn o'ch sesiynau.",
+            "rename_form_learn_more_description_2": "Mae hyn yn rhoi hyder iddynt eu bod yn siarad â chi mewn gwirionedd, ond mae hefyd yn golygu y gallant weld enw'r sesiwn rydych chi'n ei nodi yma.",
+            "security_recommendations": "Argymhellion diogelwch",
+            "security_recommendations_description": "Gwella diogelwch eich cyfrif trwy ddilyn yr argymhellion hyn.",
+            "session_id": "ID y sesiwn",
+            "show_details": "Dangos manylion",
+            "sign_in_with_qr": "Cysylltu'r ddyfais newydd",
+            "sign_in_with_qr_button": "Dangos cod QR",
+            "sign_in_with_qr_description": "Defnyddio cod QR i fewngofnodi i ddyfais arall a gosod negeseuon diogel.",
+            "sign_in_with_qr_unsupported": "Nid yw'n cael ei gefnogi gan ddarparwr eich cyfrif",
+            "sign_out": "Allgofnodwch o'r sesiwn hon",
+            "sign_out_all_other_sessions": "Allgofnodi o bob sesiwn arall (%(otherSessionsCount)s)",
+            "title": "Sesiynau",
+            "unknown_session": "Math o sesiwn anhysbys",
+            "unverified_session": "Sesiwn heb ei wirio",
+            "unverified_session_explainer_1": "Nid yw'r sesiwn hon yn cefnogi amgryptio ac felly nid oes modd ei wirio.",
+            "unverified_session_explainer_2": "Fyddwch chi ddim yn gallu cymryd rhan mewn ystafelloedd lle mae amgryptio wedi'i alluogi wrth ddefnyddio'r sesiwn hon.",
+            "unverified_session_explainer_3": "Ar gyfer y diogelwch a phreifatrwydd gorau, argymhellir defnyddio cleientiaid Matrix sy'n cefnogi amgryptio.",
+            "unverified_sessions": "Sesiynau heb eu gwirio",
+            "unverified_sessions_explainer_1": "Mae sesiynau heb eu gwirio yn sesiynau sydd wedi mewngofnodi gyda'ch tystlythyrau ond nad ydynt wedi'u traws-wirio.",
+            "unverified_sessions_explainer_2": "Dylech wneud yn arbennig o sicr eich bod yn adnabod y sesiynau hyn gan y gallent gynrychioli defnydd anawdurdodedig o'ch cyfrif.",
+            "unverified_sessions_list_description": "Dilyswch eich sesiynau ar gyfer negeseuon diogel gwell neu allgofnodwch gan y rhai nad ydych yn eu hadnabod nac yn eu defnyddio mwyach.",
+            "url": "URL",
+            "verified_session": "Sesiwn wedi'i dilysu",
+            "verified_sessions": "Sesiynau wedi'u dilysu",
+            "verified_sessions_explainer_1": "Mae sesiynau wedi'u dilysu yn unrhyw le rydych chi'n defnyddio'r cyfrif hwn ar ôl nodi'ch cyfrinair neu gadarnhau pwy ydych chi gyda sesiwn arall sydd wedi'i dilysu.",
+            "verified_sessions_explainer_2": "Mae hyn yn golygu bod gennych yr holl allweddi sydd eu hangen i ddatgloi eich negeseuon wedi'u hamgryptio a chadarnhau i ddefnyddwyr eraill eich bod yn ymddiried yn y sesiwn hon.",
+            "verified_sessions_list_description": "Er mwyn sicrhau'r diogelwch gorau, allgofnodwch o unrhyw sesiwn nad ydych yn ei hadnabod nac yn ei defnyddio mwyach.",
+            "verify_session": "Dilysu sesiwn",
+            "web_session": "Sesiwn we"
+        },
+        "show_avatar_changes": "Dangos newidiadau llun proffil",
+        "show_breadcrumbs": "Dangos llwybrau byr i ystafelloedd a welwyd yn ddiweddar uwchben y rhestr ystafelloedd",
+        "show_chat_effects": "Dangos effeithiau sgwrsio (animeiddiadau wrth dderbyn e.e. conffeti)",
+        "show_displayname_changes": "Dangos newidiadau enw dangos",
+        "show_join_leave": "Dangos negeseuon ymuno/gadael (gwahoddiadau/dileu/gwaharddiadau heb eu heffeithio)",
+        "show_nsfw_content": "Dangos cynnwys NSFW",
+        "show_read_receipts": "Dangos derbynebau darllen a anfonwyd gan ddefnyddwyr eraill",
+        "show_redaction_placeholder": "Dangos dalfan ar gyfer negeseuon sydd wedi'u dileu",
+        "show_stickers_button": "Dangos botwm sticeri",
+        "show_typing_notifications": "Dangos hysbysiadau teipio",
+        "showbold": "Dangos pob gweithgaredd yn y rhestr ystafelloedd (smotiau neu nifer o negeseuon heb eu darllen)",
+        "sidebar": {
+            "dialog_title": "<strong>Gosodiadau:</strong> Bar Ochr",
+            "metaspaces_favourites_description": "Casglwch eich holl hoff ystafelloedd a phobl mewn un lle.",
+            "metaspaces_home_all_rooms": "Dangos bob ystafell",
+            "metaspaces_home_all_rooms_description": "Dangos eich holl ystafelloedd yn y Cartref, hyd yn oed os ydyn nhw mewn gofod.",
+            "metaspaces_home_description": "Mae Cartref yn ddefnyddiol ar gyfer cael trosolwg o bopeth.",
+            "metaspaces_orphans": "Ystafelloedd y tu allan i ofod",
+            "metaspaces_orphans_description": "Casglwch eich holl ystafelloedd nad ydyn nhw'n rhan o ofod mewn un lle.",
+            "metaspaces_people_description": "Casglwch eich holl bobl mewn un lle.",
+            "metaspaces_subsection": "Gofodau i'w dangos",
+            "metaspaces_video_rooms": "Ystafelloedd fideo a chynadleddau",
+            "metaspaces_video_rooms_description": "Casglwch yr holl ystafelloedd fideo preifat a chynadleddau.",
+            "metaspaces_video_rooms_description_invite_extension": "Mewn cynadleddau gallwch wahodd pobl y tu allan i'r matrics.",
+            "spaces_explainer": "Mae gofodau yn ffyrdd o grwpio ystafelloedd a phobl. Ochr yn ochr â'r lleoedd rydych chi ynddynt, gallwch chi ddefnyddio rhai sydd wedi'u hadeiladu ymlaen llaw hefyd.",
+            "title": "Bar Ochr"
+        },
+        "start_automatically": "Dechrau'n awtomatig ar ôl mewngofnodi i'r system",
+        "tac_only_notifications": "Dim ond dangos hysbysiadau yng nghanolfan gweithgaredd yr edefyn",
+        "use_12_hour_format": "Dangos stampiau amser mewn fformat 12 awr (e.e. 2:30pm)",
+        "use_command_enter_send_message": "Defnyddiwch Command + Enter i anfon neges",
+        "use_command_f_search": "Defnyddiwch Command + F i chwilio llinell amser",
+        "use_control_enter_send_message": "Defnyddio Ctrl + Enter i anfon neges",
+        "use_control_f_search": "Defnyddio Ctrl + F i chwilio llinell amser",
+        "voip": {
+            "allow_p2p": "Caniatáu Cyfoedion i Gyfoedion ar gyfer galwadau 1:1",
+            "allow_p2p_description": "Pan fydd wedi'i alluogi, efallai y bydd y parti arall yn gallu gweld eich cyfeiriad IP",
+            "audio_input_empty": "Dim meicroffonau wedi'u canfod",
+            "audio_output": "Allbwn Sain",
+            "audio_output_empty": "Dim Allbynnau Sain wedi'u canfod",
+            "auto_gain_control": "Rheolaeth ennill awtomatig",
+            "connection_section": "Cysylltiad",
+            "dialog_title": "<strong>Gosodiadau:</strong> Llais a Fideo",
+            "echo_cancellation": "Diddymu adlais",
+            "enable_fallback_ice_server": "Caniatáu gweinydd cymorth galwadau wrth gefn (%(server)s)",
+            "enable_fallback_ice_server_description": "Dim ond os nad yw eich gweinydd cartref yn cynnig un y mae'n berthnasol. Byddai eich cyfeiriad IP yn cael ei rannu yn ystod galwad.",
+            "mirror_local_feed": "Drychu'r ffrwd fideo lleol",
+            "missing_permissions_prompt": "Caniatâd cyfryngau ar goll, cliciwch y botwm isod i ofyn.",
+            "noise_suppression": "Lleihau sain",
+            "request_permissions": "Gofyn am ganiatâd cyfryngau",
+            "title": "Llais a Fideo",
+            "video_input_empty": "Dim camerau gwe wedi'u canfod",
+            "video_section": "Gosodiadau fideo",
+            "voice_agc": "Addaswch maint sain y meicroffon yn awtomatig",
+            "voice_processing": "Prosesu llais",
+            "voice_section": "Gosodiadau llais"
+        },
+        "warn_quit": "Rhybuddio cyn rhoi'r gorau iddi",
+        "warning": "<w>RHYBUDD:</w><description/>"
+    },
+    "share": {
+        "link_copied": "Dolen wedi'i gopïo",
+        "permalink_message": "Dolen i'r neges a ddewiswyd",
+        "permalink_most_recent": "Dolen i'r neges ddiweddaraf",
+        "share_call": "Dolen gwahoddiad i'r gynhadledd",
+        "share_call_subtitle": "Dolen i ddefnyddwyr allanol ymuno â'r alwad heb gyfrif matrics:",
+        "title_link": "Rhannu Dolen",
+        "title_message": "Neges Rhannu Ystafell",
+        "title_room": "Rhannu Ystafell",
+        "title_user": "Rhannu Defnyddiwr"
+    },
+    "slash_command": {
+        "addwidget": "Yn ychwanegu teclyn cyfaddas yn ôl URL i'r ystafell",
+        "addwidget_iframe_missing_src": "Nid oes gan iframe briodwedd src",
+        "addwidget_invalid_protocol": "Darparwch URL teclyn https:// neu http://",
+        "addwidget_missing_url": "Rhowch URL teclyn neu god mewnosod",
+        "addwidget_no_permissions": "Allwch chi ddim addasu teclynnau yn yr ystafell hon.",
+        "ban": "Gwahardd defnyddiwr gydag ID penodol",
+        "category_actions": "Gweithredoedd",
+        "category_admin": "Gweinyddwr",
+        "category_advanced": "Uwch",
+        "category_effects": "Effeithiau",
+        "category_messages": "Negeseuon",
+        "category_other": "Arall",
+        "command_error": "Gwall gorchymyn",
+        "converttodm": "Yn trosi'r ystafell yn DM",
+        "converttoroom": "Yn trosi'r DM yn ystafell",
+        "could_not_find_room": "Methu dod o hyd i le",
+        "deop": "Deops defnyddiwr gydag id wedi'i roi",
+        "devtools": "Yn agor y deialog Offer Datblygwr",
+        "discardsession": "Gorfodi'r sesiwn grŵp allanol gyfredol mewn ystafell wedi'i hamgryptio i gael ei thaflu",
+        "error_invalid_rendering_type": "Gwall gorchymyn: Methu dod o hyd i'r math o rendro (%(renderingType)s)",
+        "error_invalid_room": "Methodd y gorchymyn: Methu dod o hyd i ystafell (%(roomId)s)",
+        "error_invalid_runfn": "Gwall gorchymyn: Methu trin gorchymyn slaes.",
+        "error_invalid_user_in_room": "Methu dod o hyd i ddefnyddiwr yn yr ystafell",
+        "help": "Yn dangos rhestr o orchmynion gyda defnyddiau a disgrifiadau",
+        "help_dialog_title": "Cymorth Gorchymyn",
+        "holdcall": "Yn gohirio'r alwad yn yr ystafell bresennol",
+        "html": "Yn anfon neges fel html, heb ei ddehongli fel marcio i lawr",
+        "ignore": "Yn anwybyddu defnyddiwr, yn cuddio ei negeseuon oddi wrthych",
+        "ignore_dialog_description": "Rydych nawr yn anwybyddu %(userId)s",
+        "ignore_dialog_title": "Defnyddiwr wedi'i anwybyddu",
+        "invite": "Yn gwahodd defnyddiwr ag ID a roddwyd i'r ystafell gyfredol",
+        "invite_3pid_needs_is_error": "Defnyddiwch weinydd hunaniaeth i wahodd trwy e-bost. Rheoli mewn Gosodiadau.",
+        "invite_3pid_use_default_is_title": "Defnyddiwch weinydd hunaniaeth",
+        "invite_3pid_use_default_is_title_description": "Defnyddiwch weinydd hunaniaeth i wahodd trwy e-bost. Cliciwch parhau i ddefnyddio'r gweinydd hunaniaeth drhagosodedig (%(defaultIdentityServerName)s) neu rheoli yn y Gosodiadau.",
+        "invite_failed": "Nid oedd defnyddiwr (%(user)s) wedi'i wahodd i %(roomId)s yn y pen draw ond ni roddwyd gwall o gyfleustodau'r gwahoddwr",
+        "join": "Yn ymuno â'r ystafell gyda chyfeiriad a roddwyd",
+        "jumptodate": "Symud i'r dyddiad a roddwyd yn y llinell amser",
+        "jumptodate_invalid_input": "Nid oeddem yn gallu deall y dyddiad a roddwyd (%(inputDate)s). Ceisiwch ddefnyddio'r fformat BBBB-MM-DD.",
+        "lenny": "Rhagflaenu ( ͡° ͜ʖ ͡°) i neges destun plaen",
+        "me": "Yn dangos gweithredu",
+        "msg": "Yn anfon neges at y defnyddiwr a roddwyd",
+        "myavatar": "Yn newid eich llun proffil ym mhob ystafell",
+        "myroomavatar": "Yn newid eich llun proffil yn yr ystafell gyfredol hon yn unig",
+        "myroomnick": "Yn newid eich llysenw dangos yn yr ystafell bresennol yn unig",
+        "nick": "Yn newid eich llysenw dangos",
+        "no_active_call": "Dim galwad gweithredol yn yr ystafell hon",
+        "op": "Diffiniwch lefel pŵer defnyddiwr",
+        "part_unknown_alias": "Cyfeiriad ystafell heb ei adnabod: %(roomAlias)s",
+        "plain": "Yn anfon neges fel testun plaen, heb ei ddehongli fel marcio i lawr",
+        "query": "Yn agor sgwrs gyda'r defnyddiwr a roddwyd",
+        "query_not_found_phone_number": "Methu dod o hyd i ID Matrics ar gyfer rhif ffôn",
+        "rageshake": "Anfonwch adroddiad mater gyda logiau",
+        "rainbow": "Yn anfon y neges a roddwyd wedi'i lliwio fel enfys",
+        "rainbowme": "Yn anfon yr emote a roddir wedi'i liwio fel enfys",
+        "remove": "Yn tynnu'r defnyddiwr ag ID penodol o'r ystafell hon",
+        "roomavatar": "Yn newid rhithffurf yr ystafell bresennol",
+        "roomname": "Yn gosod enw'r ystafell",
+        "server_error": "Gwall gweinydd",
+        "server_error_detail": "Nid yw'r gweinydd ar gael, wedi'i orlwytho, neu aeth rhywbeth arall o'i le.",
+        "shrug": "Yn rhagdybio ¯\\_(ツ)_/¯ i neges destun plaen",
+        "spoiler": "Yn anfon y neges a roddwyd fel sbwyliwr",
+        "tableflip": "Yn rhagarwyddo (╯°□°)╯︵ ┻━┻ i neges testun plaen",
+        "topic": "Yn cael neu'n gosod pwnc yr ystafell",
+        "topic_none": "Nid oes gan yr ystafell hon unrhyw bwnc.",
+        "topic_room_error": "Wedi methu â chael pwnc ystafell: Methu dod o hyd i ystafell (%(roomId)s",
+        "unban": "Yn dad-wahardd defnyddiwr gydag ID penodol",
+        "unflip": "Yn rhagflaenu ┬──┬ ノ(゜-゜ノ) i neges testun plaen",
+        "unholdcall": "Yn cymryd yr alwad yn yr ystafell bresennol oddi ar y stop",
+        "unignore": "Yn stopio anwybyddu defnyddiwr, gan ddangos ei negeseuon wrth symud ymlaen",
+        "unignore_dialog_description": "Nid ydych yn anwybyddu %(userId)s bellach",
+        "unignore_dialog_title": "Defnyddiwr heb ei anwybyddu",
+        "unknown_command": "Gorchymyn Anhysbys",
+        "unknown_command_button": "Anfon fel neges",
+        "unknown_command_detail": "Gorchymyn heb ei gydnabod: %(commandText)s",
+        "unknown_command_help": "Gallwch ddefnyddio <code>/help</code> i restru gorchmynion sydd ar gael. Oeddech chi'n bwriadu anfon hwn fel neges?",
+        "unknown_command_hint": "Hint: Dechreuwch eich neges gyda <code>//</code> to start it with a slash.",
+        "upgraderoom": "Yn uwchraddio ystafell i fersiwn newydd",
+        "upgraderoom_permission_error": "Nid oes gennych y caniatâd gofynnol i ddefnyddio'r gorchymyn hwn.",
+        "usage": "Defnydd",
+        "view": "Ystafell golygfeydd gyda chyfeiriad a roddwyd",
+        "whois": "Yn arddangos gwybodaeth am ddefnyddiwr"
+    },
+    "sliding_sync_legacy_no_longer_supported": "Nid yw cydweddu hen lithro bellach yn cael ei gefnogi: allgofnodwch ac yna ail fewngofnodi i alluogi'r faner cydweddu llithro newydd",
+    "space": {
+        "add_existing_room_space": {
+            "create": "Eisiau ychwanegu ystafell newydd yn lle?",
+            "create_prompt": "Creu ystafell newydd",
+            "dm_heading": "Negeseuon Uniongyrchol",
+            "error_heading": "Ni ychwanegwyd pob un a ddewiswyd",
+            "space_dropdown_label": "Dewis gofod",
+            "space_dropdown_title": "Ychwanegu ystafelloedd presennol",
+            "subspace_moved_note": "Mae ychwanegu gofodau wedi symud."
+        },
+        "add_existing_subspace": {
+            "create_button": "Creu gofod newydd",
+            "create_prompt": "Eisiau ychwanegu gofod newydd yn lle?",
+            "filter_placeholder": "Chwilio am ofodau",
+            "space_dropdown_title": "Ychwanegu gofod presennol"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "Gweler llinell amser yr ystafell (devtools)",
+            "explore": "Archwilio ystafelloedd",
+            "home": "Gofod adref",
+            "manage_and_explore": "Rheoli ac archwilio ystafelloedd",
+            "options": "Opsiynau gofod"
+        },
+        "failed_load_rooms": "Wedi methu llwytho rhestr o ystafelloedd.",
+        "failed_remove_rooms": "Wedi methu tynnu rhai ystafelloedd. Ceisiwch eto yn nes ymlaen",
+        "incompatible_server_hierarchy": "Nid yw eich gweinydd yn cefnogi dangos hierarchaethau gofod.",
+        "invite": "Gwahodd pobl",
+        "invite_description": "Gwahodd gydag e-bost neu enw defnyddiwr",
+        "invite_link": "Rhannu dolen gwahoddiad",
+        "joining_space": "Yn ymuno",
+        "landing_welcome": "Croeso i <name/>",
+        "leave_dialog_action": "Gadael lle",
+        "leave_dialog_description": "Rydych chi ar fin gadael<spaceName/>.",
+        "leave_dialog_only_admin_room_warning": "Chi yw unig weinyddwr rhai o'r ystafelloedd neu'r gofodau yr hoffech eu gadael. Bydd eu gadael yn eu gadael heb unrhyw weinyddwyr.",
+        "leave_dialog_only_admin_warning": "Chi yw unig weinyddwr y gofod hwn. Bydd ei adael yn golygu nad oes gan neb reolaeth drosto.",
+        "leave_dialog_option_all": "Gadael pob ystafell",
+        "leave_dialog_option_intro": "Hoffech chi adael yr ystafelloedd yn y gofod hwn?",
+        "leave_dialog_option_none": "Peidiwch â gadael unrhyw ystafelloedd",
+        "leave_dialog_option_specific": "Gadael rhai ystafelloedd",
+        "leave_dialog_public_rejoin_warning": "Ni fyddwch yn gallu ailymuno oni bai eich bod yn cael eich ail wahoddiad.",
+        "leave_dialog_title": "Gadael %(spaceName)s",
+        "mark_suggested": "Marciwch fel yr awgrymwyd",
+        "no_search_result_hint": "Efallai y byddwch am roi cynnig ar chwiliad gwahanol neu wirio am deipos.",
+        "preferences": {
+            "sections_section": "Adrannau i ddangos",
+            "show_people_in_space": "Mae hwn yn grwpio'ch sgyrsiau ag aelodau'r gofod hwn. Bydd diffodd hwn yn cuddio'r sgyrsiau hynny o'ch golwg o %(spaceName)s."
+        },
+        "room_filter_placeholder": "Chwilio am ystafelloedd",
+        "search_children": "Chwilio %(spaceName)s",
+        "search_placeholder": "Chwilio enwau a disgrifiadau",
+        "select_room_below": "Dewis ystafell isod yn gyntaf",
+        "share_public": "Rhannwch eich gofod cyhoeddus",
+        "suggested": "Awgrym",
+        "suggested_tooltip": "Awgrymir yr ystafell hon fel un dda i ymuno â hi",
+        "title_when_query_available": "Canlyniadau",
+        "title_when_query_unavailable": "Ystafelloedd a gofodau",
+        "unmark_suggested": "Marciwch fel heb ei awgrymu",
+        "user_lacks_permission": "Nid oes gennych ganiatâd"
+    },
+    "space_settings": {
+        "title": "Gosodiadau - %(spaceName)s"
+    },
+    "spaces": {
+        "error_no_permission_add_room": "Nid oes gennych ganiatâd i ychwanegu ystafelloedd at y gofod hwn",
+        "error_no_permission_add_space": "Nid oes gennych ganiatâd i ychwanegu gofodau i'r gofod hwn",
+        "error_no_permission_create_room": "Nid oes gennych ganiatâd i greu ystafelloedd newydd yn y gofod hwn",
+        "error_no_permission_invite": "Nid oes gennych ganiatâd i wahodd pobl i'r gofod hwn"
+    },
+    "spotlight": {
+        "public_rooms": {
+            "network_dropdown_add_dialog_description": "Rhowch enw gweinydd newydd rydych chi am ei archwilio.",
+            "network_dropdown_add_dialog_placeholder": "Enw gweinydd",
+            "network_dropdown_add_dialog_title": "Ychwanegu gweinydd newydd",
+            "network_dropdown_add_server_option": "Ychwanegu gweinydd newydd…",
+            "network_dropdown_available_invalid": "Methu dod o hyd i'r gweinydd hwn na'i restr ystafelloedd",
+            "network_dropdown_available_invalid_forbidden": "Nid oes gennych hawl i weld rhestr ystafelloedd y gweinydd hwn",
+            "network_dropdown_available_valid": "Edrych yn dda",
+            "network_dropdown_remove_server_adornment": "Dileu gweinydd “%(roomServer)s”",
+            "network_dropdown_required_invalid": "Rhowch enw gweinydd",
+            "network_dropdown_selected_label": "Sioe: Ystafelloedd matrics",
+            "network_dropdown_selected_label_instance": "Dangos: ystafell %(instance)s (%(server)s)",
+            "network_dropdown_your_server_description": "Eich gweinydd"
+        }
+    },
+    "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Os na allwch weld pwy rydych yn chwilio amdano, anfonwch eich dolen wahoddiad atynt.",
+        "cant_find_room_helpful_hint": "Os na allwch ddod o hyd i'r ystafell yr ydych yn chwilio amdani, gofynnwch am wahoddiad neu crëwch ystafell newydd.",
+        "copy_link_text": "Copïo dolen y gwahoddiad",
+        "create_new_room_button": "Creu ystafell newydd",
+        "failed_querying_public_rooms": "Wedi methu â holi ystafelloedd cyhoeddus",
+        "failed_querying_public_spaces": "Wedi methu cwestiynu gofodau cyhoeddus",
+        "group_chat_section_title": "Dewisiadau eraill",
+        "heading_with_query": "Defnyddiwch \"%(query)s\" i chwilio",
+        "heading_without_query": "Chwilio am",
+        "join_button_text": "Ymuno â %(roomAddress)s",
+        "keyboard_scroll_hint": "Defnydd<arrows/> i sgrolio",
+        "messages_label": "Negeseuon",
+        "other_rooms_in_space": "Ystafelloedd eraill yn %(spaceName)s",
+        "public_rooms_label": "Ystafelloedd cyhoeddus",
+        "public_spaces_label": "Gofodau cyhoeddus",
+        "recent_searches_section_title": "Chwilio diweddar",
+        "recently_viewed_section_title": "Gwelwyd yn ddiweddar",
+        "remove_filter": "Tynnu'r hidlydd chwilio ar gyfer %(filter)s",
+        "result_may_be_hidden_privacy_warning": "Gall rhai canlyniadau gael eu cuddio er preifatrwydd",
+        "result_may_be_hidden_warning": "Efallai y bydd rhai canlyniadau wedi'u cuddio",
+        "search_dialog": "Deialog Chwilio",
+        "spaces_title": "Gofodau rydych chi ynddyn nhw",
+        "start_group_chat_button": "Dechreuwch sgwrs grŵp"
+    },
+    "stickers": {
+        "empty": "Nid oes gennych unrhyw becynnau sticeri wedi'u galluogi ar hyn o bryd",
+        "empty_add_prompt": "Ychwanegwch rai nawr"
+    },
+    "terms": {
+        "column_document": "Dogfen",
+        "column_service": "Gwasanaeth",
+        "column_summary": "Crynodeb",
+        "identity_server_no_terms_description_1": "Mae'r weithred hon yn gofyn am gyrchu'r gweinydd hunaniaeth drhagosodedig<server /> i ddilysu cyfeiriad e-bost neu rif ffôn, ond nid oes gan y gweinydd unrhyw delerau gwasanaeth.",
+        "identity_server_no_terms_description_2": "Parhewch dim ond os ydych yn ymddiried ym mherchennog y gweinydd.",
+        "identity_server_no_terms_title": "Nid oes gan weinydd hunaniaeth unrhyw delerau gwasanaeth",
+        "inline_intro_text": "Derbyn<policyLink /> i barhau:",
+        "integration_manager": "Defnyddio botiau, pontydd, teclynnau a phecynnau sticeri",
+        "intro": "I barhau mae angen i chi dderbyn telerau'r gwasanaeth hwn.",
+        "summary_identity_server_1": "Dod o hyd i eraill dros y ffôn neu e-bost",
+        "summary_identity_server_2": "Gellir dod o hyd iddo dros y ffôn neu e-bost",
+        "tac_button": "Adolygu telerau ac amodau",
+        "tac_description": "I barhau i ddefnyddio'r gweinydd cartref %(homeserverDomain)s rhaid i chi adolygu a chytuno i'n telerau ac amodau.",
+        "tac_title": "Telerau ac Amodau",
+        "tos": "Telerau Gwasanaeth"
+    },
+    "theme": {
+        "light_high_contrast": "Cyferbyniad uchel ysgafn",
+        "match_system": "System paru"
+    },
+    "thread_view_back_action_label": "Yn ôl i'r edefyn",
+    "threads": {
+        "all_threads": "Pob edefyn",
+        "all_threads_description": "Yn dangos pob edefyn o'r ystafell gyfredol",
+        "empty_description": "Defnyddio “%(replyInThread)s” wrth hofran dros neges.",
+        "empty_title": "Mae edafedd yn helpu i gadw'ch sgyrsiau ar y pwnc ac yn hawdd eu holrhain.",
+        "error_start_thread_existing_relation": "Methu â chreu edefyn o ddigwyddiad gyda pherthynas sy'n bodoli eisoes",
+        "mark_all_read": "Marcio'r cyfan wedi'u darllen",
+        "my_threads": "Fy edafedd",
+        "my_threads_description": "Yn dangos pob edefyn rydych chi wedi cymryd rhan ynddo",
+        "open_thread": "Edafedd agored",
+        "show_thread_filter": "Dangos:"
+    },
+    "threads_activity_centre": {
+        "header": "Gweithgaredd edafedd",
+        "no_rooms_with_threads_notifs": "Nid oes gennych chi ystafelloedd gyda hysbysiadau edafedd eto.",
+        "no_rooms_with_unread_threads": "Nid oes gennych chi ystafelloedd ag edafedd heb eu darllen eto."
+    },
+    "time": {
+        "about_day_ago": "tua diwrnod yn ôl",
+        "about_hour_ago": "tua awr yn ol",
+        "about_minute_ago": "tua munud yn ôl",
+        "date_at_time": "%(date)s am %(time)s",
+        "few_seconds_ago": "ychydig eiliadau yn ôl",
+        "hours_minutes_seconds_left": "%(hours)sa %(minutes)sm %(seconds)s ar ôl",
+        "in_about_day": "tua diwrnod o nawr",
+        "in_about_hour": "tuag awr o hyn",
+        "in_about_minute": "tua munud o nawr",
+        "in_few_seconds": "ychydig eiliadau o nawr",
+        "in_n_days": "%(num)s diwrnod o nawr",
+        "in_n_hours": "%(num)s awr o nawr",
+        "in_n_minutes": "%(num)s munud o nawr",
+        "left": "%(timeRemaining)s ar ôl",
+        "minutes_seconds_left": "%(minutes)sm %(seconds)ss ar ôl",
+        "n_days_ago": "%(num)s diwrnod yn ôl",
+        "n_hours_ago": "%(num)s awr yn ôl",
+        "n_minutes_ago": "%(num)s munud yn ôl",
+        "seconds_left": "Mae %(seconds)s ar ôl",
+        "short_days": "%(value)sch",
+        "short_days_hours_minutes_seconds": "%(days)sd %(hours)sa %(minutes)sm %(seconds)ss",
+        "short_hours": "%(value)sa",
+        "short_hours_minutes_seconds": "%(hours)sa %(minutes)sm %(seconds)ss",
+        "short_minutes": "%(value)sm",
+        "short_minutes_seconds": "%(minutes)sm %(seconds)ss",
+        "short_seconds": "%(value)sau"
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "Crebachu edefyn ateb",
+            "external_url": "URL ffynhonnell",
+            "open_in_osm": "Agor yn OpenStreetMap",
+            "report": "Adrodd",
+            "resent_unsent_reactions": "Anfon %(unsentCount)s adwaith(au) eto",
+            "show_url_preview": "Dangos rhagolwg",
+            "view_related_event": "Gweld digwyddiad cysylltiedig",
+            "view_source": "Gweld y cod ffynhonnell"
+        },
+        "creation_summary_dm": "Creodd %(creator)s y DM hwn.",
+        "creation_summary_room": "Mae %(creator)s wedi creu a ffurfweddu'r ystafell.",
+        "decryption_failure": {
+            "blocked": "Mae'r anfonwr wedi eich rhwystro rhag derbyn y neges hon oherwydd bod eich dyfais heb ei gwirio",
+            "historical_event_no_key_backup": "Nid yw negeseuon hanesyddol ar gael ar y ddyfais hon",
+            "historical_event_unverified_device": "Mae angen i chi wirio'r ddyfais hon ar gyfer mynediad i negeseuon hanesyddol",
+            "historical_event_user_not_joined": "Nid oes gennych fynediad i'r neges hon",
+            "sender_identity_previously_verified": "Mae hunaniaeth yr anfonwr wedi'i wirio wedi newid",
+            "sender_unsigned_device": "Wedi'i anfon o ddyfais ansicr.",
+            "unable_to_decrypt": "Methu dadgryptio'r neges"
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
+        "download_action_decrypting": "Dadgryptio",
+        "download_action_downloading": "Yn llwytho i lawr",
+        "download_failed": "Methodd y llwytho i lawr",
+        "download_failed_description": "Bu gwall wrth lawrlwytho'r ffeil hon",
+        "e2e_state": "Cyflwr yr amgryptio o'r dechrau i'r diwedd",
+        "edits": {
+            "tooltip_label": "Wedi'i olygu am %(date)s. Cliciwch i weld golygiadau.",
+            "tooltip_sub": "Cliciwch i weld golygiadau",
+            "tooltip_title": "Wedi'i olygu am %(date)s"
+        },
+        "error_no_renderer": "Nid oedd modd dangos y digwyddiad hwn",
+        "error_rendering_message": "Methu llwytho'r neges hon",
+        "historical_messages_unavailable": "Allwch chi ddim weld negeseuon cynharach",
+        "in_room_name": " yn <strong>%(room)s</strong>",
+        "io.element.widgets.layout": "Mae %(senderName)s wedi diweddaru cynllun yr ystafell",
+        "late_event_separator": "Anfonwyd %(dateTime)s yn wreiddiol",
+        "load_error": {
+            "no_permission": "Wedi ceisio llwytho pwynt penodol yn llinell amser yr ystafell hon, ond nid oes gennych ganiatâd i weld y neges dan sylw.",
+            "title": "Wedi methu llwytho safle llinell amser",
+            "unable_to_find": "Wedi ceisio llwytho pwynt penodol yn llinell amser yr ystafell hon, ond ni lwyddodd i ddod o hyd iddo."
+        },
+        "m.audio": {
+            "error_downloading_audio": "Gwall wrth llwytho i lawrsain",
+            "error_processing_audio": "Gwall wrth brosesu neges sain",
+            "error_processing_voice_message": "Gwall wrth brosesu neges llais",
+            "unnamed_audio": "Sain dienw"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Gweld lleoliad byw"
+        },
+        "m.call": {
+            "video_call_ended": "Daeth galwad fideo i ben",
+            "video_call_started": "Dechreuwyd galwad fideo yn %(roomName)s.",
+            "video_call_started_text": "Dechreuodd %(matere)s alwad fideo",
+            "video_call_started_unsupported": "Dechreuwyd galwad fideo yn %(roomName)s. (heb ei gefnogi gan y porwr hwn)"
+        },
+        "m.call.hangup": {
+            "dm": "Galwad i ben"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "Atebwyd yn rhywle arall",
+            "call_back_prompt": "Galwch yn ôl",
+            "declined": "Galwad wedi'i wrthod",
+            "failed_connect_media": "Methu cysylltu cyfryngau",
+            "failed_connection": "Methodd y cysylltiad",
+            "failed_opponent_media": "Ni allai eu dyfais gychwyn y camera na'r meicroffon",
+            "missed_call": "Galwad coll",
+            "no_answer": "Dim ateb",
+            "unknown_error": "Digwyddodd gwall anhysbys",
+            "unknown_failure": "Methiant anhysbys: %(reason)s",
+            "unknown_state": "Mae'r alwad mewn cyflwr anhysbys!",
+            "video_call": "Gosododd %(senderName)s alwad fideo.",
+            "video_call_unsupported": "Gosododd %(senderName)s alwad fideo. (heb ei gefnogi gan y porwr hwn)",
+            "voice_call": "Gosododd %(senderName)s alwad llais.",
+            "voice_call_unsupported": "Gosododd %(senderName)s alwad llais. (heb ei gefnogi gan y porwr hwn)"
+        },
+        "m.file": {
+            "error_decrypting": "Gwall wrth ddadgryptio atodiad",
+            "error_invalid": "Ffeil annilys"
+        },
+        "m.image": {
+            "error": "Methu dangos delwedd oherwydd gwall",
+            "error_decrypting": "Gwall wrth ddadgryptio delwedd",
+            "error_downloading": "Gwall wrth lwytho'r ddelwedd",
+            "sent": "Anfonodd %(senderDisplayName)s ddelwedd.",
+            "show_image": "Dangos delwedd"
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "Mae %(matere)s eisiau gwirio",
+            "you_started": "Rydych wedi anfon cais dilysu"
+        },
+        "m.location": {
+            "full": "Mae %(senderName)s wedi rhannu eu lleoliad",
+            "location": "Wedi rhannu lleoliad: ",
+            "self_location": "Wedi rhannu eu lleoliad: "
+        },
+        "m.poll.end": {
+            "ended": "Daeth arolwg i ben",
+            "sender_ended": "Mae %(senderName)s wedi dod ag arolwg i ben"
+        },
+        "m.poll.start": "Mae %(senderName)s wedi dechrau arolwg - %(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "Mae %(senderDisplayName)s wedi newid afatar yr ystafell.",
+            "changed_img": "Mae %(senderDisplayName)s wedi newid yr afatar ystafell i<img/>",
+            "lightbox_title": "Newidiodd %(senderDisplayName)s yr afatar ar gyfer %(roomName)s",
+            "removed": "Mae %(senderDisplayName)s wedi tynnu afatar yr ystafell."
+        },
+        "m.room.canonical_alias": {
+            "changed": "Newidiodd %(senderName)s y cyfeiriadau ar gyfer yr ystafell hon.",
+            "changed_alternative": "Mae %(senderName)s wedi newid y cyfeiriadau amgen ar gyfer yr ystafell hon.",
+            "changed_main_and_alternative": "Newidiodd %(senderName)s y prif gyfeiriadau a chyfeiriadau amgen ar gyfer yr ystafell hon.",
+            "removed": "Mae %(senderName)s wedi dileu prif gyfeiriad yr ystafell hon.",
+            "set": "Gosododd %(senderName)s y prif gyfeiriad ar gyfer yr ystafell hon i %(address)s."
+        },
+        "m.room.create": {
+            "continuation": "Mae'r ystafell hon yn barhad o sgwrs arall.",
+            "see_older_messages": "Cliciwch yma i weld negeseuon hŷn.",
+            "unknown_predecessor": "Methu â dod o hyd i'r hen fersiwn o'r ystafell hon (ID ystafell: %(roomId)s), ac nid ydym wedi cael 'via_servers' i chwilio amdano.",
+            "unknown_predecessor_guess_server": "Methu â dod o hyd i'r hen fersiwn o'r ystafell hon (ID ystafell: %(roomId)s), ac nid ydym wedi cael 'via_servers' i chwilio amdano. Mae'n bosibl y bydd dyfalu'r gweinydd o ID yr ystafell yn gweithio. Os ydych chi eisiau ceisio, cliciwch ar y ddolen hon:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "Wedi anwybyddu ymgais i analluogi amgryptio",
+            "disabled": "Nid yw amgryptio wedi'i alluogi",
+            "enabled": "Mae negeseuon yn yr ystafell hon wedi'u hamgryptio pen-i-ben. Pan fydd pobl yn ymuno, gallwch chi eu gwirio yn eu proffil, dim ond tapio ar eu llun proffil.",
+            "enabled_dm": "Mae negeseuon yma wedi'u hamgryptio o'r dechrau i'r diwedd. Dilyswch %(displayName)s yn eu proffil - tapiwch ar eu llun proffil.",
+            "enabled_local": "Bydd negeseuon yn y sgwrs hon yn cael eu hamgryptio o'r dechrau i'r diwedd.",
+            "parameters_changed": "Mae rhai paramedrau amgryptio wedi'u newid.",
+            "unsupported": "Nid yw'r amgryptio sy'n cael ei ddefnyddiogan yr ystafell hon yn cael ei gefnogi."
+        },
+        "m.room.guest_access": {
+            "can_join": "Mae %(senderDisplayName)s wedi caniatáu i westeion ymuno â'r ystafell.",
+            "forbidden": "Mae %(senderDisplayName)s wedi atal gwesteion rhag ymuno â'r ystafell.",
+            "unknown": "Mae %(senderDisplayName)s wedi newid mynediad gwestai i %(rule)s"
+        },
+        "m.room.history_visibility": {
+            "invited": "Gwnaeth %(senderName)s hanes ystafell y dyfodol yn weladwy i bob aelod ystafell, o'r pwynt y maen nhw'n cael eu gwahodd.",
+            "joined": "Gwnaeth %(senderName)s hanes ystafell yn y dyfodol yn weladwy i bob aelod ystafell, o'r pwynt y gwnaetho'n nhw ymuno.",
+            "shared": "Gwnaeth %(senderName)s hanes ystafell y dyfodol yn weladwy i bob aelod ystafell.",
+            "unknown": "Gwnaeth %(senderName)s hanes ystafell y dyfodol yn weladwy i anhysbys (%(visibility)s).",
+            "world_readable": "Gwnaeth %(senderName)s hanes ystafell y dyfodol yn weladwy i unrhyw un."
+        },
+        "m.room.join_rules": {
+            "invite": "Gwnaeth %(senderDisplayName)s wahoddiad i'r ystafell yn unig.",
+            "knock": "Newidiodd %(senderDisplayName)s y rheol ymuno i ofyn am gael ymuno.",
+            "public": "Gwnaeth %(senderDisplayName)s yr ystafell yn gyhoeddus i bwy bynnag sy'n gwybod y ddolen.",
+            "restricted": "Mae %(senderDisplayName)s wedi newid pwy all ymuno â'r ystafell hon.",
+            "restricted_settings": "Mae %(senderDisplayName)s wedi newid pwy all ymuno â'r ystafell hon. <a>Gweld gosodiadau</a>.",
+            "unknown": "Newidiodd %(senderDisplayName)s y rheol ymuno i %(rule)s"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "Derbyniodd %(targetName)s y gwahoddiad ar gyfer %(displayName)s",
+            "accepted_invite": "Derbyniodd %(targetName)s wahoddiad",
+            "ban": "Mae %(senderName)s wedi gwahardd %(targetName)s",
+            "ban_reason": "Mae %(senderName)s wedi gwahardd %(targetName)s: %(reason)s",
+            "change_avatar": "Mae %(senderName)s wedi newid eu llun proffil",
+            "change_name": "Mae %(oldDisplayName)s wedi newid ei enw dangos i %(displayName)s",
+            "change_name_avatar": "Mae %(oldDisplayName)s wedi newid ei enw dangos a llun proffil",
+            "invite": "Mae %(senderName)s wedi gwahodd %(targetName)s",
+            "join": "Ymunodd %(targetName)s â'r ystafell",
+            "kick": "Mae %(senderName)s wedi tynnu %(targetName)s",
+            "kick_reason": "Mae %(senderName)s wedi tynnu %(targetName)s: %(reason)s",
+            "left": "Gadawodd %(targetName)s yr ystafell",
+            "left_reason": "Gadawodd %(targetName)s yr ystafell: %(reason)s",
+            "no_change": "Nid yw %(senderName)s wedi gwneud unrhyw newid",
+            "reject_invite": "Mae %(targetName)s wedi gwrthod y gwahoddiad",
+            "reject_invite_reason": "Mae %(targetName)s wedi gwrthod y gwahoddiad: %(reason)s",
+            "remove_avatar": "Mae %(senderName)s wedi tynnu eu llun proffil",
+            "remove_name": "Mae %(senderName)s wedi tynnu eu henw dangos (%(oldDisplayName)s)",
+            "set_avatar": "Mae %(senderName)s wedi gosod llun proffil",
+            "set_name": "Mae %(senderName)s wedi gosod eu henw dangos i %(displayName)s",
+            "unban": "Mae %(senderName)s wedi ei ddad wahardd %(targetName)s",
+            "withdrew_invite": "Tynnodd %(senderName)s wahoddiad %(targetName)s yn ôl",
+            "withdrew_invite_reason": "Tynnodd %(senderName)s wahoddiad %(targetName)s yn ôl: %(reason)s"
+        },
+        "m.room.name": {
+            "change": "Newidiodd %(senderDisplayName)s enw'r ystafell o %(oldRoomName)s i %(newRoomName)s.",
+            "remove": "Mae %(senderDisplayName)s wedi tynnu enw'r ystafell.",
+            "set": "Newidiodd %(senderDisplayName)s enw'r ystafell i %(roomName)s."
+        },
+        "m.room.pinned_events": {
+            "changed": "Newidiodd %(senderName)s y negeseuon sydd wedi'u pinio ar gyfer yr ystafell.",
+            "changed_link": "Newidiodd %(senderName)s y <a>negeseuon sydd wedi'u pinio</a> ar gyfer yr ystafell.",
+            "pinned": "Mae %(senderName)s wedi pinio neges i'r ystafell hon. Gweld yr holl negeseuon sydd wedi'u pinio.",
+            "pinned_link": "Mae %(senderName)s wedi pinio <a>neges</a> i'r ystafell hon. Gweld yr holl <b>negeseuon sydd wedi'u pinio</b>.",
+            "unpinned": "Mae %(senderName)s wedi dad-binio neges o'r ystafell hon. Gweld yr holl negeseuon sydd wedi'u pinio.",
+            "unpinned_link": "Mae %(senderName)s wedi dad-binio <a>neges</a> o'r ystafell hon. Gweld yr holl <b>negeseuon sydd wedi'u pinio</b>."
+        },
+        "m.room.power_levels": {
+            "changed": "Mae %(senderName)s wedi newid lefel pŵer %(powerLevelDiffText)s.",
+            "user_from_to": "%(userId)s o %(fromPowerLevel)s i %(toPowerLevel)s"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 Mae pob gweinydd wedi'i wahardd rhag cymryd rhan! Nid oes modd defnyddio'r ystafell hon mwyach.",
+            "changed": "Newidiodd %(senderDisplayName)s y gweinydd ACLs ar gyfer yr ystafell hon.",
+            "set": "Gosododd %(senderDisplayName)s y gweinydd ACLs ar gyfer yr ystafell hon."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "Dirymodd %(senderName)s y gwahoddiad i %(targetDisplayName)s ymuno â'r ystafell.",
+            "sent": "Anfonodd %(senderName)s wahoddiad at %(targetDisplayName)s i ymuno â'r ystafell."
+        },
+        "m.room.tombstone": "Mae %(senderDisplayName)s wedi uwchraddio'r ystafell hon.",
+        "m.room.topic": {
+            "changed": "Newidiodd %(senderDisplayName)s y pwnc i \"%(topic)s\".",
+            "removed": "Mae %(senderDisplayName)s wedi dileu'r pwnc."
+        },
+        "m.sticker": "Anfonodd %(senderDisplayName)s sticer.",
+        "m.video": {
+            "error_decrypting": "Gwall wrth ddadgryptio fideo",
+            "show_video": "Dangos fideo"
+        },
+        "m.widget": {
+            "added": "Mae %(widgetName)s wedi'i ychwanegu gan %(senderName)s",
+            "jitsi_ended": "Daeth cynhadledd fideo i ben gan %(senderName)s",
+            "jitsi_join_right_prompt": "Ymunwch â'r gynhadledd o'r cerdyn gwybodaeth ystafell ar y dde",
+            "jitsi_join_top_prompt": "Ymunwch â'r gynhadledd ar frig yr ystafell hon",
+            "jitsi_started": "Cynhadledd fideo wedi'i chychwyn gan %(senderName)s",
+            "jitsi_updated": "Cynhadledd fideo wedi'i diweddaru gan %(senderName)s",
+            "modified": "Mae teclyn %(widgetName)s wedi'i addasu gan %(senderName)s",
+            "removed": "Tynnwyd y teclyn %(widgetName)s gan %(senderName)s"
+        },
+        "mab": {
+            "collapse_reply_chain": "Crebachu dyfyniadau",
+            "copy_link_thread": "Copïo dolen i'r edefyn",
+            "expand_reply_chain": "Ehangu dyfyniadau",
+            "label": "Camau Gweithredu Neges",
+            "view_in_room": "Golwg yn yr ystafell"
+        },
+        "message_timestamp_received_at": "Derbyniwyd am: %(dateTime)s",
+        "message_timestamp_sent_at": "Anfonwyd am: %(dateTime)s",
+        "mjolnir": {
+            "changed_rule_glob": "Mae %(senderName)s wedi diweddaru rheol gwahardd oedd yn cyfateb %(oldGlob)s i %(newGlob)s ar gyfer %(reason)s",
+            "changed_rule_rooms": "Newidiodd %(senderName)s reol a oedd yn gwahardd ystafelloedd sy'n cyfateb i %(oldGlob)s i gyfateb %(newGlob)s am %(reason)s",
+            "changed_rule_servers": "Newidiodd %(senderName)s reol a oedd yn gwahardd gweinyddwyr sy'n cyfateb %(oldGlob)s i gyfateb %(newGlob)s am %(reason)s",
+            "changed_rule_users": "Newidiodd %(senderName)s reol a oedd yn gwahardd defnyddwyr rhag paru %(oldGlob)s â chyfateb %(newGlob)s am %(reason)s",
+            "created_rule": "Creodd %(senderName)s reol gwahardd sy'n cyfateb i %(glob)s ar gyfer %(reason)s",
+            "created_rule_rooms": "Creodd %(senderName)s reol yn gwahardd ystafelloedd sy'n cyfateb i %(glob)s am %(reason)s",
+            "created_rule_servers": "Creodd %(senderName)s reol yn gwahardd gweinyddwyr sy'n cyfateb i %(glob)s am %(reason)s",
+            "created_rule_users": "Creodd %(senderName)s reol yn gwahardd defnyddwyr rhag paru %(glob)s am %(reason)s",
+            "message_hidden": "Rydych chi wedi anwybyddu'r defnyddiwr hwn, felly mae eu neges wedi'i chuddio. <a>Dangos beth bynnag.</a>",
+            "removed_rule": "Mae %(senderName)s wedi dileu rheol gwahardd sy'n cyfateb i %(glob)s",
+            "removed_rule_rooms": "Mae %(senderName)s wedi dileu'r rheol sy'n gwahardd ystafelloedd sy'n cyfateb i %(glob)s",
+            "removed_rule_servers": "Mae %(senderName)s wedi dileu'r rheol sy'n gwahardd gweinyddwyr sy'n cyfateb i %(glob)s",
+            "removed_rule_users": "Mae %(senderName)s wedi dileu'r rheol sy'n gwahardd defnyddwyr rhag paru %(glob)s",
+            "updated_invalid_rule": "Mae %(senderName)s wedi diweddaru rheol gwahardd annilys",
+            "updated_rule": "Mae %(senderName)s wedi diweddaru rheol gwahardd sy'n cyfateb i %(glob)s ar gyfer %(reason)s",
+            "updated_rule_rooms": "Mae %(senderName)s wedi diweddaru'r rheol gwahardd ystafelloedd sy'n cyfateb i %(glob)s am %(reason)s",
+            "updated_rule_servers": "Mae %(senderName)s wedi diweddaru'r rheol sy'n gwahardd gweinyddwyr sy'n cyfateb i %(glob)s am %(reason)s",
+            "updated_rule_users": "Mae %(senderName)s wedi diweddaru'r rheol sy'n gwahardd defnyddwyr rhag paru %(glob)s am %(reason)s"
+        },
+        "no_permission_messages_before_invite": "Nid oes gennych ganiatâd i weld negeseuon oddi cyn i chi gael eich gwahodd.",
+        "no_permission_messages_before_join": "Nid oes gennych ganiatâd i weld negeseuon oddi cyn i chi ymuno.",
+        "pending_moderation": "Neges yn aros i'w gymedroli",
+        "pending_moderation_reason": "Neges yn aros i'w gymedroli: %(reason)s",
+        "reactions": {
+            "add_reaction_prompt": "Ychwanegu ymateb",
+            "custom_reaction_fallback_label": "Ymateb cyfaddas",
+            "label": "Ymatebodd %(reactors)s gyda %(content)s",
+            "tooltip_caption": "wedi ymateb gyda %(shortName)s"
+        },
+        "read_receipts_label": "Derbynebau darllen",
+        "redacted": {
+            "tooltip": "Neges wedi'i dileu ar %(date)s"
+        },
+        "redaction": "Neges wedi'i dileu gan %(matere)s",
+        "reply": {
+            "error_loading": "Methu llwytho digwyddiad yr atebwyd iddo, naill ai nid yw'n bodoli neu nid oes gennych ganiatâd i'w weld.",
+            "in_reply_to": "<a>Yn ymateb i</a><pill>",
+            "in_reply_to_for_export": "Yn ymateb i'r <a>neges hon</a>"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "Rydych ar fin cael eich tywys i wefan trydydd parti er mwyn i chi allu dilysu'ch cyfrif i'w ddefnyddio gyda %(integrationsUrl)s. Ydych chi am barhau?",
+            "dialog_title": "Ychwanegu Integreiddiad"
+        },
+        "self_redaction": "Neges wedi'i dileu",
+        "send_state_encrypting": "Wrthi'n amgryptio'ch neges…",
+        "send_state_failed": "Methwyd anfon",
+        "send_state_sending": "Wrthi'n anfon eich neges…",
+        "send_state_sent": "Anfonwyd eich neges",
+        "summary": {
+            "format": "%(matereList)s %(transitionList)s"
+        },
+        "thread_info_basic": "O edefyn",
+        "typing_indicator": {
+            "one_user": "Mae %(displayName)s yn teipio…",
+            "two_users": "Mae %(materes)s a %(lastPerson)s yn teipio…"
+        },
+        "undecryptable_tooltip": "Nid oedd modd dadgryptio'r neges hon",
+        "url_preview": {
+            "close": "Cau rhagolwg"
+        }
+    },
+    "unsupported_browser": {
+        "description": "Os byddwch yn parhau, efallai na fydd rhai nodweddion yn gweithio ac mae risg y gallech golli data yn y dyfodol. Diweddarwch eich porwr i barhau i ddefnyddio %(brand)s.",
+        "title": "Nid yw %(brand)s yn cefnogi'r porwr hwn"
+    },
+    "unsupported_server_description": "Mae'r gweinydd hwn yn defnyddio fersiwn hŷn o Matrix. Uwchraddiwch i Matrics %(version)s i ddefnyddio %(brand)s heb wallau.",
+    "unsupported_server_title": "Nid yw eich gweinydd yn cael ei gefnogi",
+    "update": {
+        "changelog": "Cofnod Newid",
+        "check_action": "Gwiriwch am ddiweddariad",
+        "checking": "Wrthi'n gwirio am ddiweddariad…",
+        "downloading": "Wrthi'n llwytho diweddariad i lawr…",
+        "error_encountered": "Y gwall gafwyd (%(errorDetail)s).",
+        "error_unable_load_commit": "Methu llwytho manylion cyflwyno: %(msg)s",
+        "new_version_available": "Fersiwn newydd ar gael. <a>Diweddaru nawr.</a>",
+        "no_update": "Dim diweddariad ar gael.",
+        "release_notes_toast_title": "Beth sy'n Newydd",
+        "see_changes_button": "Beth sy'n newydd?",
+        "toast_description": "Mae fersiwn newydd o %(brand)s ar gael",
+        "toast_title": "Diweddaru %(brand)s",
+        "unavailable": "Dim ar gael"
+    },
+    "update_room_access_modal": {
+        "description": "I greu dolen rhannu, mae angen i chi ganiatáu i westeion ymuno â'r ystafell hon. Gall hyn wneud yr ystafell yn llai diogel. Pan fyddwch wedi gorffen gyda'r alwad, gallwch wneud yr ystafell yn breifat eto.",
+        "dont_change_description": "Fel arall, gallwch gynnal yr alwad mewn ystafell ar wahân.",
+        "no_change": "Dydw i ddim eisiau newid y lefel mynediad.",
+        "revert_access_description": "(Mae modddychwelyd hwn i&#39;r gwerth blaenorol yng Ngosodiadau&#39;r Ystafell: <b>Diogelwch a Phreifatrwydd</b> / <b>Mynediad</b> )",
+        "title": "Newid lefel mynediad yr ystafell"
+    },
+    "upload_failed_generic": "Methodd y ffeil '%(fileName)s' â llwytho i fyny.",
+    "upload_failed_size": "Mae'r ffeil '%(fileName)s' yn fwy na chyfyngiad maint llwytho'r gweinydd cartref",
+    "upload_failed_title": "Methodd y Llwytho",
+    "upload_file": {
+        "cancel_all_button": "Diddymu'r Cyfan",
+        "error_file_too_large": "Mae'r ffeil hon yn <b>rhy fawr</b> i'w llwytho. Y terfyn maint ffeil yw %(limit)s ond y ffeil hon yw %(sizeOfThisFile)s.",
+        "error_files_too_large": "Mae'r ffeiliau hyn yn <b>rhy fawr</b> i'w llwytho. Y terfyn maint ffeil yw %(limit)s.",
+        "error_some_files_too_large": "Mae rhai ffeiliau'n <b>rhy fawr</b> i'w llwytho. Y terfyn maint ffeil yw %(limit)s.",
+        "error_title": "Gwall Llwytho",
+        "not_image": "Nid yw'r ffeil rydych chi wedi'i dewis yn ffeil delwedd ddilys.",
+        "title": "Llwytho ffeiliau",
+        "title_progress": "Llwytho ffeiliau (%(current)s o %(total)s)",
+        "upload_all_button": "Llwytho'r cyfan"
+    },
+    "user_info": {
+        "admin_tools_section": "Offer Gweinyddol",
+        "ban_button_room": "Gwaharddiad o ystafell",
+        "ban_button_space": "Gwaharddiad o'r gofod",
+        "ban_room_confirm_title": "Gwaharddiad o %(roomName)s",
+        "ban_space_everything": "Gwaharddwch nhw o bopeth rydw i'n gallu",
+        "ban_space_specific": "Gwaharddwch nhw o bethau penodol y gallaf eu gwneud",
+        "deactivate_confirm_action": "Analluogi defnyddiwr",
+        "deactivate_confirm_description": "Bydd cau'r defnyddiwr hwn yn eu hallgofnodi a'u hatal rhag mewngofnodi eto. Yn ogystal, byddant yn gadael yr holl ystafelloedd y mae ynddynt. Nid oes modd gwrthdroi'r weithred hon. Ydych chi'n siŵr eich bod am cau'r defnyddiwr hwn?",
+        "deactivate_confirm_title": "Analluogi defnyddiwr?",
+        "demote_button": "Diraddio",
+        "demote_self_confirm_description_space": "Fyddwch chi ddim yn gallu dadwneud y newid hwn gan eich bod yn israddio eich hun, os mai chi yw'r defnyddiwr breintiedig olaf yn y gofod bydd yn amhosibl adennill breintiau.",
+        "demote_self_confirm_room": "Fyddwch chi ddim yn gallu dadwneud y newid hwn gan eich bod yn israddio eich hun, os mai chi yw'r defnyddiwr breintiedig olaf yn yr ystafell bydd yn amhosibl adennill breintiau.",
+        "demote_self_confirm_title": "Israddio eich hun?",
+        "disinvite_button_room": "Dadwahodd o'r ystafell",
+        "disinvite_button_room_name": "Dadwahodd o %(roomName)s",
+        "disinvite_button_space": "Dadwahodd o'r gofod",
+        "error_ban_user": "Wedi methu â gwahardd defnyddiwr",
+        "error_deactivate": "Wedi methu ag analluogi defnyddiwr",
+        "error_kicking_user": "Wedi methu tynnu'r defnyddiwr",
+        "error_mute_user": "Wedi methu â thewi'r defnyddiwr",
+        "error_revoke_3pid_invite_description": "Methu â dirymu'r gwahoddiad. Mae'n bosib bod y gweinydd yn cael problem dros dro neu nad oes gennych chi ddigon o hawliau i ddiddymu'r gwahoddiad.",
+        "error_revoke_3pid_invite_title": "Wedi methu â dirymu gwahoddiad",
+        "ignore_button": "Anwybyddu",
+        "ignore_confirm_description": "Bydd pob neges a gwahoddiad gan y defnyddiwr hwn yn cael eu cuddio. Ydych chi'n siŵr eich bod am eu hanwybyddu?",
+        "ignore_confirm_title": "Anwybyddu %(user)s",
+        "invited_by": "Wedi'i wahodd gan %(sender)s",
+        "jump_to_rr_button": "Mynd i ddarllen y dderbynneb",
+        "kick_button_room": "Tynnu o'r ystafell",
+        "kick_button_room_name": "Tynnu o %(roomName)s",
+        "kick_button_space": "Tynnu o'r gofod",
+        "kick_button_space_everything": "Tynnwch nhw o bopeth rydw i'n gallu",
+        "kick_space_specific": "Tynnwch nhw o bethau penodol rydw i'n gallu",
+        "kick_space_warning": "Byddan nhw'n dal i allu cael mynediad at beth bynnag nad ydych yn weinyddwr iddo.",
+        "promote_warning": "Fyddwch chi ddim yn gallu dadwneud y newid hwn gan eich bod yn hyrwyddo'r defnyddiwr i gael yr un lefel pŵer â chi'ch hun.",
+        "redact": {
+            "confirm_description_2": "Ar gyfer llawer iawn o negeseuon, gallai hyn gymryd peth amser. Peidiwch ag adnewyddu eich cleient yn y cyfamser.",
+            "confirm_keep_state_explainer": "Dad-diciwch a ydych hefyd am ddileu negeseuon system ar y defnyddiwr hwn (e.e. newid aelodaeth, newid proffil…)",
+            "confirm_keep_state_label": "Cadw negeseuon system",
+            "confirm_title": "Dileu negeseuon diweddar gan %(user)s",
+            "no_recent_messages_description": "Ceisiwch sgrolio i fyny yn y llinell amser i weld a oes unrhyw rai cynharach.",
+            "no_recent_messages_title": "Heb ganfod unrhyw negeseuon diweddar gan %(user)s"
+        },
+        "redact_button": "Dileu negeseuon",
+        "revoke_invite": "Diddymu gwahoddiad",
+        "room_encrypted": "Mae negeseuon yn yr ystafell hon wedi'u hamgryptio pen-i-ben.",
+        "room_encrypted_detail": "Mae eich negeseuon yn ddiogel a dim ond chi a'r derbynnydd sydd â'r allweddi unigryw i'w datgloi.",
+        "room_unencrypted": "Nid yw negeseuon yn yr ystafell hon wedi'u hamgryptio pen-i-ben.",
+        "room_unencrypted_detail": "Mewn ystafelloedd wedi'u hamgryptio, mae'ch negeseuon yn ddiogel a dim ond chi a'r derbynnydd sydd â'r allweddi unigryw i'w datgloi.",
+        "send_message": "Anfon neges",
+        "share_button": "Rhannu proffil",
+        "unban_button_room": "Dad-wahardd o'r ystafell",
+        "unban_button_space": "Dad-wahardd o'r gofod",
+        "unban_room_confirm_title": "Dad-wahardd o %(roomName)s",
+        "unban_space_everything": "Dad-wahardd nhw o bopeth y gallaf",
+        "unban_space_specific": "Gwahardd nhw o bethau penodol y gallaf",
+        "unban_space_warning": "Ffyddan nhw ddim yn gallu cael mynediad at beth bynnag nad ydych yn weinyddwr iddo.",
+        "unignore_button": "Anwybyddu",
+        "verification_unavailable": "Nid yw dilysu defnyddiwr ar gael",
+        "verify_button": "Dilysu Defnyddiwr",
+        "verify_explainer": "Ar gyfer diogelwch ychwanegol, gwiriwch y defnyddiwr hwn trwy wirio cod un-amser ar eich dwy ddyfais."
+    },
+    "user_menu": {
+        "link_new_device": "Cysylltu'r ddyfais newydd",
+        "settings": "Pob gosodiad",
+        "switch_theme_dark": "Newid i'r modd tywyll",
+        "switch_theme_light": "Newid i'r modd golau"
+    },
+    "voip": {
+        "already_in_call": "Eisoes mewn galwad",
+        "already_in_call_person": "Rydych chi eisoes mewn galwad gyda'r person hwn.",
+        "answered_elsewhere": "Atebwyd mewn Man Arall",
+        "answered_elsewhere_description": "Atebwyd yr alwad ar ddyfais arall.",
+        "call_failed": "Galwad wedi Methu",
+        "call_failed_description": "Nid oes modd gosod yr alwad",
+        "call_failed_media": "Methodd yr alwad oherwydd nid oedd modd cael at gamera gwe neu feicroffon. Gwiriwch fod:",
+        "call_failed_media_applications": "Nid oes unrhyw raglen arall yn defnyddio'r camera gwe",
+        "call_failed_media_connected": "Mae meicroffon a camera gwe wedi'u plygio i mewn ac wedi'u gosod yn gywir",
+        "call_failed_media_permissions": "Rhoi caniatâd i ddefnyddio'r camera gwe",
+        "call_failed_microphone": "Methodd yr alwad oherwydd ni fu modd cyrchu'r meicroffon. Gwiriwch fod meicroffon wedi'i blygio i mewn a'i osod yn gywir.",
+        "call_held": "Cynhaliodd %(peerName)s yr alwad",
+        "call_held_resume": "Chi sy'n cynnal yr alwad <a>Ailddechrau</a>",
+        "call_held_switch": "Chi sy'n cynnal yr alwad <a>Newid</a>",
+        "call_toast_unknown_room": "Ystafell anhysbys",
+        "camera_disabled": "Mae eich camera wedi'i ddiffodd",
+        "camera_enabled": "Mae eich camera wedi'i alluogi o hyd",
+        "cannot_call_yourself_description": "Allwch chi ddim roi galwad i chi'ch hun.",
+        "close_lobby": "Caewch y lobi",
+        "connecting": "Yn cysylltu",
+        "connection_lost": "Mae cysylltedd â'r gweinydd wedi'i golli",
+        "connection_lost_description": "Allwch chi ddim osod galwadau heb gysylltiad â'r gweinydd.",
+        "consulting": "Ymgynghori â %(transferTarget)s. <a>Trosglwyddo i %(transferee)s</a>",
+        "default_device": "Dyfais Rhagosodedig",
+        "dial": "Deial",
+        "dialpad": "Pad Deialu",
+        "disable_camera": "Diffoddwch y camera",
+        "disable_microphone": "Tewi meicroffon",
+        "disabled_no_one_here": "Nid oes neb yma i alw",
+        "disabled_no_perms_start_video_call": "Nid oes gennych ganiatâd i ddechrau galwadau fideo",
+        "disabled_no_perms_start_voice_call": "Nid oes gennych ganiatâd i ddechrau galwadau llais",
+        "disabled_ongoing_call": "Galwad cyfredol",
+        "element_call": "Galwad Element",
+        "enable_camera": "Trowch y camera ymlaen",
+        "enable_microphone": "Dad-dewi meicroffon",
+        "expand": "Dychwelyd i'r alwad",
+        "get_call_link": "Rhannu cyswllt galwad",
+        "hangup": "Rhoi'r gorau iddi",
+        "hide_sidebar_button": "Cuddio bar ochr",
+        "input_devices": "Dyfeisiau mewnbwn",
+        "jitsi_call": "Cynhadledd Jitsi",
+        "join_button_tooltip_call_full": "Ymddiheuriadau - mae'r alwad hon yn llawn ar hyn o bryd",
+        "legacy_call": "Hen Alwad",
+        "maximise": "Llanw'r Sgrin",
+        "maximise_call": "Mwyhau galwad",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Cynadleddau"
+        },
+        "minimise_call": "Lleihau galwad",
+        "misconfigured_server": "Methodd yr alwad oherwydd y gweinydd wedi'i gamgyflunio",
+        "misconfigured_server_description": "Gofynnwch i weinyddwr eich gweinydd cartref ( <code>%(homeserverDomain)s</code> ) i ffurfweddu gweinydd TURN er mwyn i alwadau weithio'n ddibynadwy.",
+        "misconfigured_server_fallback": "Fel arall, gallwch geisio defnyddio'r gweinydd cyhoeddus yn<server/> , ond ni fydd hyn mor ddibynadwy, a bydd yn rhannu eich cyfeiriad IP gyda'r gweinydd hwnnw. Gallwch hefyd reoli hyn yn y Gosodiadau.",
+        "misconfigured_server_fallback_accept": "Ceisiwch ddefnyddio %(server)s",
+        "more_button": "Rhagor",
+        "msisdn_lookup_failed": "Methu chwilio am y rhif ffôn",
+        "msisdn_lookup_failed_description": "Bu gwall wrth chwilio am y rhif ffôn",
+        "msisdn_transfer_failed": "Methu trosglwyddo galwad",
+        "no_audio_input_description": "Heb ddod o hyd i feicroffon ar eich dyfais. Gwiriwch eich gosodiadau a cheisiwch eto.",
+        "no_audio_input_title": "Heb ganfod meicroffon",
+        "no_media_perms_description": "Mae'n bosibl y bydd angen i chi ganiatáu i %(brand)s gael mynediad i'ch meicroffon/gwegamera â llaw",
+        "no_media_perms_title": "Dim caniatâd cyfryngau",
+        "no_permission_conference": "Angen Caniatâd",
+        "no_permission_conference_description": "Nid oes gennych ganiatâd i ddechrau galwad cynadledda yn yr ystafell hon",
+        "on_hold": "%(matere)s ar stop",
+        "output_devices": "Dyfeisiau allbwn",
+        "screenshare_monitor": "Rhannu sgrin gyfan",
+        "screenshare_title": "Rhannu cynnwys",
+        "screenshare_window": "Ffenestr cais",
+        "show_sidebar_button": "Dangos y bar ochr",
+        "silence": "Distewi galwad",
+        "silenced": "Hysbysiadau wedi'u distewi",
+        "start_screenshare": "Dechreuwch rannu'ch sgrin",
+        "stop_screenshare": "Stopiwch rannu'ch sgrin",
+        "too_many_calls": "Gormod o Alwadau",
+        "too_many_calls_description": "Rydych chi wedi cyrraedd y nifer uchaf o alwadau cydamserol.",
+        "transfer_consult_first_label": "Ymgynghori yn gyntaf",
+        "transfer_failed": "Methodd y Trosglwyddo",
+        "transfer_failed_description": "Wedi methu trosglwyddo galwad",
+        "unable_to_access_audio_input_description": "Methu cael mynediad i'ch meicroffon. Gwiriwch osodiadau eich porwr a cheisiwch eto.",
+        "unable_to_access_audio_input_title": "Methu cael mynediad i'ch meicroffon",
+        "unable_to_access_media": "Methu cyrchu camera gwe / meicroffon",
+        "unable_to_access_microphone": "Methu cyrchu meicroffon",
+        "unknown_caller": "Galwr anhysbys",
+        "unknown_person": "person anhysbys",
+        "unsilence": "Sain ymlaen",
+        "unsupported": "Nid yw galwadau'n cael eu cefnogi",
+        "unsupported_browser": "Allwch chi ddim gosod galwadau yn y porwr hwn.",
+        "user_busy": "Defnyddiwr yn Brysur",
+        "user_busy_description": "Mae'r defnyddiwr rydych yn ei alw yn brysur.",
+        "user_is_presenting": "Mae %(sharerName)s yn cyflwyno",
+        "video_call": "Galwad fideo",
+        "video_call_started": "Galwad fideo wedi dechrau",
+        "video_call_using": "Galwad fideo gan ddefnyddio:",
+        "voice_call": "Galwad llais",
+        "you_are_presenting": "Rydych chi'n cyflwyno"
+    },
+    "web_default_device_name": "%(appName)s: %(browserName)s ar %(osName)s",
+    "welcome_to_element": "Croeso i Element",
+    "widget": {
+        "added_by": "Ychwanegwyd teclyn gan",
+        "capabilities_dialog": {
+            "content_starting_text": "Hoffai'r teclyn hwn:",
+            "decline_all_permission": "Gwrthod Pawb",
+            "remember_Selection": "Cofio fy newis ar gyfer y teclyn hwn",
+            "title": "Cymeradwyo hawliau teclyn"
+        },
+        "capability": {
+            "always_on_screen_generic": "Arhos ar eich sgrin wrth redeg",
+            "always_on_screen_viewing_another_room": "Arhos ar eich sgrin wrth edrych ar ystafell arall, wrth redeg",
+            "any_room": "Yr uchod, ond mewn unrhyw ystafell rydych yn cael eich ymuno wahodd hefyd",
+            "byline_empty_state_key": "gydag allwedd cyflwr gwag",
+            "byline_state_key": "gydag allwedd cyflwr %(stateKey)s",
+            "capability": "Y gallu <b>%(capability)s</b>",
+            "change_avatar_active_room": "Newidiwch afatar eich ystafell weithredol",
+            "change_avatar_this_room": "Newid afatar yr ystafell hon",
+            "change_name_active_room": "Newidiwch enw eich ystafell weithredol",
+            "change_name_this_room": "Newidiwch enw'r ystafell hon",
+            "change_topic_active_room": "Newidiwch bwnc eich ystafell weithredol",
+            "change_topic_this_room": "Newid pwnc yr ystafell hon",
+            "receive_membership_active_room": "Gweld pryd mae pobl yn ymuno, yn gadael, neu'n cael eu gwahodd i'ch ystafell weithredol",
+            "receive_membership_this_room": "Gweld pryd mae pobl yn ymuno, yn gadael neu'n cael eu gwahodd i'r ystafell hon",
+            "remove_ban_invite_leave_active_room": "Dileu, gwahardd, neu wahodd pobl i'ch ystafell weithredol, a gwneud i chi adael",
+            "remove_ban_invite_leave_this_room": "Dileu, gwahardd, neu wahodd pobl i'r ystafell hon, a gwneud i chi adael",
+            "see_avatar_change_active_room": "Gweld pryd mae'r afatar yn newid yn eich ystafell weithredol",
+            "see_avatar_change_this_room": "Gweld pryd mae'r afatar yn newid yn yr ystafell hon",
+            "see_event_type_sent_active_room": "Gweld <b>%(eventType)s</b> digwyddiad wedi'u postio i'ch ystafell weithredol",
+            "see_event_type_sent_this_room": "Gweld <b>%(eventType)s</b> digwyddiad wedi'u postio i'r ystafell hon",
+            "see_images_sent_active_room": "Gweld delweddau sy'n cael eu postio i'ch ystafell weithredol",
+            "see_images_sent_this_room": "Gweld delweddau a bostiwyd i'r ystafell hon",
+            "see_messages_sent_active_room": "Gweld negeseuon sy'n cael eu postio i'ch ystafell weithredol",
+            "see_messages_sent_this_room": "Gweld negeseuon sy'n cael eu postio i'r ystafell hon",
+            "see_msgtype_sent_active_room": "Gweld <b>%(msgtype)s</b> neges wedi'u postio i'ch ystafell weithredol",
+            "see_msgtype_sent_this_room": "Gweld <b>%(msgtype)s</b> neges wedi'u postio i'r ystafell hon",
+            "see_name_change_active_room": "Gweld pryd mae'r enw'n newid yn eich ystafell weithredol",
+            "see_name_change_this_room": "Gweld pryd mae'r enw'n newid yn yr ystafell hon",
+            "see_sent_emotes_active_room": "Gweld emosiynau sy'n cael eu postio i'ch ystafell weithredol",
+            "see_sent_emotes_this_room": "Gweld emosiynau wedi'u postio i'r ystafell hon",
+            "see_sent_files_active_room": "Gweld ffeiliau cyffredinol sy'n cael eu postio i'ch ystafell weithredol",
+            "see_sent_files_this_room": "Gweld ffeiliau cyffredinol sy'n cael eu postio i'r ystafell hon",
+            "see_sticker_posted_active_room": "Gweld pryd mae unrhyw un yn postio sticer i'ch ystafell weithredol",
+            "see_sticker_posted_this_room": "Gweld pryd mae sticer yn cael ei bostio yn yr ystafell hon",
+            "see_text_messages_sent_active_room": "Gweld negeseuon testun sy'n cael eu postio i'ch ystafell weithredol",
+            "see_text_messages_sent_this_room": "Gweld negeseuon testun sy'n cael eu postio i'r ystafell hon",
+            "see_topic_change_active_room": "Gweld pryd mae'r pwnc yn newid yn eich ystafell weithredol",
+            "see_topic_change_this_room": "Gweld pryd mae'r pwnc yn newid yn yr ystafell hon",
+            "see_videos_sent_active_room": "Gweld fideos sy'n cael eu postio i'ch ystafell weithredol",
+            "see_videos_sent_this_room": "Gweld fideos sy'n cael eu postio i'r ystafell hon",
+            "send_emotes_active_room": "Anfonwch emosiynau wrth i chi yn eich ystafell weithredol",
+            "send_emotes_this_room": "Anfon emotes wrth i chi yn yr ystafell hon",
+            "send_event_type_active_room": "Anfon <b>%(eventType)s</b> digwyddiad fel chi yn eich ystafell weithredol",
+            "send_event_type_this_room": "Anfon <b>%(eventType)s</b> digwyddiad fel chi yn yr ystafell hon",
+            "send_files_active_room": "Anfon ffeiliau cyffredinol fel chi yn eich ystafell weithredol",
+            "send_files_this_room": "Anfon ffeiliau cyffredinol fel chi yn yr ystafell hon",
+            "send_images_active_room": "Anfon delweddau fel chi yn eich ystafell weithredol",
+            "send_images_this_room": "Anfon lluniau fel chi yn yr ystafell hon",
+            "send_messages_active_room": "Anfon negeseuon fel chi yn eich ystafell weithredol",
+            "send_messages_this_room": "Anfon negeseuon fel chi yn yr ystafell hon",
+            "send_msgtype_active_room": "Anfon <b>%(msgtype)s</b> neges fel chi yn eich ystafell weithredol",
+            "send_msgtype_this_room": "Anfon <b>%(msgtype)s</b> neges fel chi yn yr ystafell hon",
+            "send_stickers_active_room": "Anfon sticeri i'ch ystafell weithredol",
+            "send_stickers_active_room_as_you": "Anfon sticeri i'ch ystafell weithredol fel chi",
+            "send_stickers_this_room": "Anfon sticeri i'r ystafell hon",
+            "send_stickers_this_room_as_you": "Anfon sticeri i'r ystafell hon fel chi",
+            "send_text_messages_active_room": "Anfon negeseuon testun fel chi yn eich ystafell weithredol",
+            "send_text_messages_this_room": "Anfon negeseuon testun fel chi yn yr ystafell hon",
+            "send_videos_active_room": "Anfon fideos fel chi yn eich ystafell weithredol",
+            "send_videos_this_room": "Anfon fideos fel chi yn yr ystafell hon",
+            "specific_room": "Yr uchod, ond yn <Room /> yn ogystal",
+            "switch_room": "Newid pa ystafell rydych chi'n edrych arni",
+            "switch_room_message_user": "Newid pa ystafell, neges neu ddefnyddiwr rydych chi'n edrych arno"
+        },
+        "close_to_view_right_panel": "Caewch y teclyn hwn i'w weld yn y panel hwn",
+        "context_menu": {
+            "delete": "Dileu teclyn",
+            "delete_warning": "Mae dileu teclyn yn ei ddileu ar gyfer pob defnyddiwr yn yr ystafell hon. Ydych chi'n siŵr eich bod am ddileu'r teclyn hwn?",
+            "move_left": "Symud i'r chwith",
+            "move_right": "Symud i'r dde",
+            "remove": "Tynnwch i bawb",
+            "revoke": "Diddymu caniatâd",
+            "screenshot": "Cymryd llun",
+            "start_audio_stream": "Dechrau ffrwd sain"
+        },
+        "cookie_warning": "Gall y teclyn hwn ddefnyddio cwcis.",
+        "error_hangup_description": "Rydych wedi'ch datgysylltu o'r alwad. (Gwall: %(message)s)",
+        "error_hangup_title": "Cysylltiad wedi'i golli",
+        "error_loading": "Gwall wrth lwytho'r teclyn",
+        "error_mixed_content": "Gwall - Cynnwys cymysg",
+        "error_need_invite_permission": "Mae angen i chi allu gwahodd defnyddwyr i wneud hynny.",
+        "error_need_kick_permission": "Mae angen i chi allu cicio defnyddwyr i wneud hynny.",
+        "error_need_to_be_logged_in": "Mae angen i chi fod wedi mewngofnodi.",
+        "error_unable_start_audio_stream_description": "Methu dechrau ffrydio sain.",
+        "error_unable_start_audio_stream_title": "Methu cychwyn llif byw",
+        "modal_data_warning": "Mae data ar y sgrin hon yn cael ei rannu â %(widgetDomain)s",
+        "modal_title_default": "Teclyn Modal",
+        "no_name": "Ap Anhysbys",
+        "open_id_permissions_dialog": {
+            "remember_selection": "Cofio hyn",
+            "starting_text": "Bydd y teclyn yn gwirio eich ID defnyddiwr, ond ni fydd yn gallu cyflawni gweithredoedd ar eich rhan:",
+            "title": "Caniatáu i'r teclyn hwn wirio'ch hunaniaeth"
+        },
+        "popout": "Teclyn llamu allan",
+        "set_room_layout": "Gosodwch y cynllun i bawb",
+        "shared_data_avatar": "URL eich llun proffil",
+        "shared_data_device_id": "ID eich dyfais",
+        "shared_data_lang": "Eich iaith",
+        "shared_data_mxid": "Eich ID defnyddiwr",
+        "shared_data_name": "Eich enw dangos",
+        "shared_data_room_id": "ID ystafell",
+        "shared_data_theme": "Eich thema",
+        "shared_data_url": "URL %(brand)s",
+        "shared_data_warning": "Gall defnyddio'r teclyn hwn rannu data<helpIcon /> gyda %(widgetDomain)s.",
+        "shared_data_warning_im": "Gall defnyddio&#39;r teclyn hwn rannu data<helpIcon /> gyda %(widgetDomain)s a&#39;ch rheolwr integreiddio.",
+        "shared_data_widget_id": "ID teclyn",
+        "unencrypted_warning": "Nid yw teclynnau'n defnyddio amgryptio negeseuon.",
+        "unmaximise": "Dad-wneud mwyaf",
+        "unpin_to_view_right_panel": "Dad-binio'r teclyn hwn i'w weld yn y panel hwn"
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "Mae prif lythrennau bron mor hawdd i'w ddyfalu â llythrennau bach",
+            "anotherWord": "Ychwanegwch air neu ddau arall. Mae geiriau anghyffredin yn well.",
+            "associatedYears": "Osgowch flynyddoedd sy'n gysylltiedig â chi",
+            "capitalization": "Nid yw priflythrennau'n helpu llawer",
+            "dates": "Osgowch ddyddiadau a blynyddoedd sy'n gysylltiedig â chi",
+            "l33t": "Nid yw dirprwyon rhagweladwy fel '@' yn lle 'a' yn helpu llawer",
+            "longerKeyboardPattern": "Defnyddio patrwm bysellfwrdd hirach gyda mwy o droeon",
+            "noNeed": "Nid oes angen symbolau, digidau na llythrennau mawr",
+            "pwned": "Os ydych chi'n defnyddio'r cyfrinair hwn yn rhywle arall, dylech ei newid.",
+            "recentYears": "Osgowch flynyddoedd diweddar",
+            "repeated": "Osgowch eiriau a nodau ailadroddus",
+            "reverseWords": "Nid yw geiriau wedi'u gwrthdroi yn llawer anoddach i'w dyfalu",
+            "sequences": "Osgowch ddilyniannau",
+            "useWords": "Defnyddio ychydig o eiriau, osgowch ymadroddion cyffredin"
+        },
+        "warnings": {
+            "common": "Mae hwn yn gyfrinair cyffredin iawn",
+            "commonNames": "Mae enwau a chyfenwau cyffredin yn hawdd i'w dyfalu",
+            "dates": "Mae dyddiadau yn aml yn hawdd i'w dyfalu",
+            "extendedRepeat": "Mae ailadroddiadau fel \"abcabcabc\" ychydig yn anoddach i'w dyfalu nag \"abc\"",
+            "keyPattern": "Mae patrymau bysellfwrdd byr yn hawdd i'w dyfalu",
+            "namesByThemselves": "Mae'n hawdd dyfalu enwau a chyfenwau eu hunain",
+            "pwned": "Datgelwyd eich cyfrinair gan dor-data ar y Rhyngrwyd.",
+            "recentYears": "Mae'r blynyddoedd diwethaf yn hawdd i'w dyfalu",
+            "sequences": "Mae dilyniannau fel abc neu 6543 yn hawdd i'w dyfalu",
+            "similarToCommon": "Mae hyn yn debyg i gyfrinair sy'n cael ei ddefnyddio'n gyffredin",
+            "simpleRepeat": "Mae ailadroddiadau fel \"aaa\" yn hawdd i'w dyfalu",
+            "straightRow": "Mae rhesi syth o allweddi yn hawdd i'w dyfalu",
+            "topHundred": "Mae hwn yn gyfrinair cyffredin o'r 100 uchaf",
+            "topTen": "Mae hwn yn gyfrinair cyffredin o'r 10 uchaf",
+            "userInputs": "Dylai fod dim data personol neu ddata cysylltiedig â thudalennau.",
+            "wordByItself": "Mae gair ynddo'i hun yn hawdd i'w ddyfalu"
+        }
+    }
+}
diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json
index e59aa517663b8e690b21fd452c929030df7fd0ba..e1de0986623939c191c60e0773d5cffdb58e2f20 100644
--- a/src/i18n/strings/de_DE.json
+++ b/src/i18n/strings/de_DE.json
@@ -1,6 +1,6 @@
 {
     "a11y": {
-        "emoji_picker": "Emoji-Auswahl",
+        "emoji_picker": "Emoji-Picker",
         "jump_first_invite": "Zur ersten Einladung springen.",
         "message_composer": "Nachrichteneingabe-Feld",
         "n_unread_messages": {
@@ -12,6 +12,16 @@
             "one": "1 ungelesene Erwähnung."
         },
         "recent_rooms": "Kürzlich besuchte Chatrooms",
+        "room_messsage_not_sent": "Öffnen Sie den Chatroom %(roomName)s mit einer nicht gesendeten Nachricht.",
+        "room_n_unread_invite": "%(roomName)s Einladung zum Raum öffnen.",
+        "room_n_unread_messages": {
+            "one": "Raum %(roomName)s öffnen mit 1 ungelesenen Nachricht.",
+            "other": "Raum %(roomName)s öffnen mit %(count)s ungelesene Nachrichten."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Öffnen Sie den Raum %(roomName)s mit 1 ungelesenen Erwähnung.",
+            "other": "Öffnen Sie den Raum %(roomName)s mit %(count)s ungelesenen Nachrichten einschließlich Erwähnungen."
+        },
         "room_name": "Raum %(name)s",
         "room_status_bar": "Statusleiste des Chatrooms",
         "seek_bar_label": "Audio-Suchleiste",
@@ -45,6 +55,8 @@
         "create_a_room": "Raum erstellen",
         "create_account": "Konto erstellen",
         "decline": "Ablehnen",
+        "decline_and_block": "Ablehnen und blockieren",
+        "decline_invite": "Einladung ablehnen",
         "delete": "Löschen",
         "deny": "Ablehnen",
         "disable": "Deaktivieren",
@@ -64,6 +76,7 @@
         "go": "Los",
         "go_back": "Zurück",
         "got_it": "Verstanden",
+        "hide": "Verstecken",
         "hide_advanced": "Erweiterte Einstellungen ausblenden",
         "hold": "Halten",
         "ignore": "Blockieren",
@@ -80,12 +93,14 @@
         "maximise": "Maximieren",
         "mention": "Erwähnen",
         "minimise": "Minimieren",
+        "new_message": "Neue Nachricht",
         "new_room": "Neuer Raum",
         "new_video_room": "Neuer Videoraum",
         "next": "Weiter",
         "no": "Nein",
         "ok": "Ok",
         "open": "Öffnen",
+        "open_menu": "Menu öffnen",
         "pause": "Pausieren",
         "pin": "Anheften",
         "play": "Abspielen",
@@ -94,13 +109,13 @@
         "react": "Reagieren",
         "refresh": "Neu laden",
         "register": "Registrieren",
-        "reject": "Ablehnen",
         "reload": "Neu laden",
         "remove": "Entfernen",
         "rename": "Umbenennen",
         "reply": "Antworten",
         "reply_in_thread": "In Thread antworten",
         "report_content": "Inhalt melden",
+        "report_room": "Raum melden",
         "resend": "Erneut senden",
         "reset": "Zurücksetzen",
         "resume": "Fortsetzen",
@@ -142,6 +157,7 @@
         "view_message": "Nachricht anzeigen",
         "view_source": "Rohdaten anzeigen",
         "yes": "Ja",
+        "yes_dismiss": "Ja, verwerfen",
         "zoom_in": "Vergrößern",
         "zoom_out": "Verkleinern"
     },
@@ -371,6 +387,7 @@
             "fallback_button": "Authentifizierung beginnen",
             "mas_cross_signing_reset_cta": "Gehen Sie zu Ihren Konto",
             "mas_cross_signing_reset_description": "Setzen Sie Ihre Identität über Ihren Kontoanbieter zurück. Kommen Sie dann zurück und klicken Sie auf „Wiederholen“.",
+            "mas_cross_signing_reset_title": "Gehen sie zu ihrem Konto, um ihre Identität zurückzusetzen",
             "msisdn": "Eine Textnachricht wurde an %(msisdn)s gesendet",
             "msisdn_token_incorrect": "Token fehlerhaft",
             "msisdn_token_prompt": "Bitte gib den darin enthaltenen Code ein:",
@@ -405,7 +422,15 @@
         "download_logs": "Protokolle herunterladen",
         "downloading_logs": "Lade Protokolle herunter",
         "error_empty": "Bitte teile uns mit, was schief lief - oder besser, beschreibe das Problem auf GitHub in einem \"Issue\".",
-        "failed_send_logs": "Senden von Protokolldateien fehlgeschlagen: ",
+        "failed_download_logs": "Debug-Protokolle konnten nicht heruntergeladen werden: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Der Fehlerbericht wurde abgelehnt. Der Rageshake-Server unterstützt diese Anwendung nicht.",
+            "rejected_generic": "Der Bugreport wurde abgelehnt. Der Rageshake-Server hat den Inhalt des Berichts aufgrund einer Richtlinie abgelehnt.",
+            "rejected_recovery_key": "Der Fehlerbericht wurde aus Sicherheitsgründen abgelehnt, da er einen Wiederherstellungsschlüssel enthielt.",
+            "rejected_version": "Dein Fehlerbericht wurde abgelehnt, da die verwendete Version veraltet ist.",
+            "server_unknown_error": "Der Rageshake-Server hat einen unbekannten Fehler festgestellt und konnte den Bericht nicht verarbeiten.",
+            "unknown_error": "Protokolle konnten nicht gesendet werden."
+        },
         "github_issue": "GitHub-Problem",
         "introduction": "Wenn du uns einen Bug auf GitHub gemeldet hast, können uns Debug-Logs helfen, das Problem zu finden. ",
         "log_request": "Um uns zu helfen, dies in Zukunft zu vermeiden, <a>sende uns bitte die Protokolldateien</a>.",
@@ -445,7 +470,7 @@
         "access_token": "Zugriffstoken",
         "accessibility": "Barrierefreiheit",
         "advanced": "Erweitert",
-        "all_rooms": "Alle Räume",
+        "all_chats": "Alle Chats",
         "analytics": "Analysedaten",
         "and_n_others": {
             "other": "und %(count)s weitere …",
@@ -464,7 +489,6 @@
         "capabilities": "Funktionen",
         "copied": "Kopiert!",
         "credits": "Danksagungen",
-        "cross_signing": "Quersignierung",
         "dark": "Dunkel",
         "description": "Beschreibung",
         "deselect_all": "Alle abwählen",
@@ -495,7 +519,6 @@
         "legal": "Rechtliches",
         "light": "Hell",
         "loading": "Lade …",
-        "lobby": "Lobby",
         "location": "Standort",
         "low_priority": "Niedrige Priorität",
         "matrix": "Matrix",
@@ -504,6 +527,7 @@
         "message_timestamp_invalid": "Ungültiger Zeitstempel",
         "microphone": "Mikrofon",
         "model": "Modell",
+        "moderation_and_safety": "Moderation und Sicherheit",
         "modern": "Modern",
         "mute": "Stummschalten",
         "n_members": {
@@ -539,6 +563,7 @@
         "qr_code": "QR-Code",
         "random": "Ohne Thema",
         "reactions": "Reaktionen",
+        "recommended": "Empfohlen",
         "report_a_bug": "Einen Fehler melden",
         "room": "Raum",
         "room_name": "Raumname",
@@ -547,7 +572,6 @@
         "saved": "Gespeichert",
         "saving": "Speichere …",
         "secure_backup": "Verschlüsselte Sicherung",
-        "security": "Sicherheit",
         "select_all": "Alle auswählen",
         "server": "Server",
         "settings": "Einstellungen",
@@ -566,7 +590,6 @@
         "thread": "Thema",
         "threads": "Threads",
         "timeline": "Verlauf",
-        "trusted": "Vertrauenswürdig",
         "unavailable": "Nicht verfügbar",
         "unencrypted": "Unverschlüsselt",
         "unmute": "Stummschalten aufheben",
@@ -726,6 +749,13 @@
         "twemoji": "Die <twemoji>Twemoji</twemoji>-Emojis sind © <author>Twitter, Inc und weitere Mitwirkende</author> und wird unter den Bedingungen von <terms>CC-BY 4.0</terms> verwendet.",
         "twemoji_colr": "Die Schriftart <colr>twemoji-colr</colr> ist © <author>Mozilla Foundation</author> und wird unter den Bedingungen von <terms>Apache 2.0</terms> verwendet."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Sind Sie sicher, dass Sie die Einladung zur Teilnahme an \"%(roomName)s\" ablehnen möchten?",
+        "ignore_user_help": "Von diesem Benutzer werden Ihnen keine Nachrichten oder Chatroomeinladungen angezeigt.",
+        "reason_description": "Beschreiben Sie den Grund für die Meldung dieses Chatrooms.",
+        "report_room_description": "Melden Sie diesen Raum Ihrem Kontoanbieter.",
+        "title": "Einladung ablehnen"
+    },
     "desktop_default_device_name": "%(brand)s Desktop: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktive Widgets",
@@ -733,6 +763,44 @@
         "category_room": "Raum",
         "caution_colon": "Vorsicht:",
         "client_versions": "Anwendungsversionen",
+        "crypto": {
+            "4s_public_key_in_account_data": "in den Kontodaten",
+            "4s_public_key_not_in_account_data": "nicht gefunden",
+            "4s_public_key_status": "Öffentlicher Schlüssel des geheimen Speichers:",
+            "backup_key_cached": "lokal zwischengespeichert",
+            "backup_key_cached_status": "Backup-Schlüssel zwischengespeichert:",
+            "backup_key_not_stored": "nicht gespeichert",
+            "backup_key_stored": "im geheimen Speicher",
+            "backup_key_stored_status": "Backup-Schlüssel gespeichert:",
+            "backup_key_unexpected_type": "unerwarteter Typ",
+            "backup_key_well_formed": "wohlgeformt",
+            "cross_signing": "Kreuzsignatur",
+            "cross_signing_cached": "lokal zwischengespeichert",
+            "cross_signing_not_ready": "Kreuzsignatur ist nicht eingerichtet.",
+            "cross_signing_private_keys_in_storage": "im geheimen Speicher",
+            "cross_signing_private_keys_in_storage_status": "Überkreuzsignierung privater Schlüssel:",
+            "cross_signing_private_keys_not_in_storage": "nicht gefunden im Speicher",
+            "cross_signing_public_keys_on_device": "im Speicher",
+            "cross_signing_public_keys_on_device_status": "Überkreuzsignierung öffentlicher Schlüssel:",
+            "cross_signing_ready": "Kreuzsignatur ist einsatzbereit.",
+            "cross_signing_status": "Status der Kreuzsignatur",
+            "cross_signing_untrusted": "Ihr Konto hat eine Cross-Signing-Identität im sicheren Speicher, der von dieser Sitzung jedoch noch nicht als vertrauenswürdig eingestuft wird.",
+            "crypto_not_available": "Das kryptografische Modul ist nicht verfügbar",
+            "key_backup_active_version": "Aktive Backup Version:",
+            "key_backup_active_version_none": "Keine",
+            "key_backup_inactive_warning": "Für die Schlüssel dieser Session gibt es kein Backup",
+            "key_backup_latest_version": "Aktuelle Backup-Version auf dem Server:",
+            "key_storage": "Schlüsselspeicher",
+            "master_private_key_cached_status": "Privater Hauptschlüssel:",
+            "not_found": "nicht gefunden",
+            "not_found_locally": "nicht lokal gefunden",
+            "secret_storage_not_ready": "nicht bereit",
+            "secret_storage_ready": "bereit",
+            "secret_storage_status": "Geheimer Speicher:",
+            "self_signing_private_key_cached_status": "Selbstsignierender privater Schlüssel:",
+            "title": "Ende-zu-Ende Verschlüsselung",
+            "user_signing_private_key_cached_status": "Privater Schlüssel zur Benutzersignatur:"
+        },
         "developer_mode": "Entwicklungsmodus",
         "developer_tools": "Entwicklungswerkzeuge",
         "edit_setting": "Einstellung bearbeiten",
@@ -788,6 +856,9 @@
         "setting_colon": "Einstellung:",
         "setting_definition": "Definition der Einstellung:",
         "setting_id": "Einstellungs-ID",
+        "settings": {
+            "elementCallUrl": "Element Call URL"
+        },
         "settings_explorer": "Einstellungsübersicht",
         "show_hidden_events": "Versteckte Ereignisse im Verlauf anzeigen",
         "spaces": {
@@ -840,52 +911,35 @@
     "empty_room_was_name": "Leerer Raum (war %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Um fortzufahren gib die Sicherheitsphrase ein oder <button>verwende deinen Sicherheitsschlüssel</button>.",
             "key_validation_text": {
-                "invalid_security_key": "Ungültiger Sicherheitsschlüssel",
-                "recovery_key_is_correct": "Sieht gut aus!",
-                "wrong_file_type": "Falscher Dateityp",
-                "wrong_security_key": "Falscher Sicherheitsschlüssel"
-            },
-            "reset_title": "Alles zurücksetzen",
-            "reset_warning_1": "Verwende es nur, wenn du kein Gerät, mit dem du dich verifizieren kannst, bei dir hast.",
-            "reset_warning_2": "Wenn du alles zurücksetzt, beginnst du ohne verifizierte Sitzungen und Benutzende von Neuem und siehst eventuell keine alten Nachrichten.",
+                "wrong_security_key": "Falscher Wiederherstellungsschlüssel"
+            },
             "restoring": "Schlüssel aus der Sicherung wiederherstellen",
-            "security_key_title": "Sicherheitsschlüssel",
-            "security_phrase_incorrect_error": "Zugriff auf sicheren Speicher nicht möglich. Bitte überprüfe, ob du die richtige Sicherheitsphrase eingegeben hast.",
-            "security_phrase_title": "Sicherheitsphrase",
-            "separator": "%(securityKey)s oder %(recoveryFile)s",
-            "use_security_key_prompt": "Benutze deinen Sicherheitsschlüssel um fortzufahren."
+            "security_key_title": "Wiederherstellungsschlüssel"
         },
         "bootstrap_title": "Schlüssel werden eingerichtet",
         "cancel_entering_passphrase_description": "Bist du sicher, dass du die Eingabe der Passphrase abbrechen möchtest?",
         "cancel_entering_passphrase_title": "Eingabe der Passphrase abbrechen?",
         "confirm_encryption_setup_body": "Klick die Schaltfläche unten um die Einstellungen der Verschlüsselung zu bestätigen.",
         "confirm_encryption_setup_title": "Bestätige die Einrichtung der Verschlüsselung",
-        "cross_signing_not_ready": "Quersignierung wurde nicht eingerichtet.",
-        "cross_signing_ready": "Quersignaturen sind bereits in Anwendung.",
-        "cross_signing_ready_no_backup": "Quersignatur ist bereit, die Schlüssel sind aber nicht gesichert.",
         "cross_signing_room_normal": "Dieser Raum ist Ende-zu-Ende verschlüsselt",
         "cross_signing_room_verified": "Alle in diesem Raum sind verifiziert",
         "cross_signing_room_warning": "Jemand verwendet eine unbekannte Sitzung",
-        "cross_signing_unsupported": "Dein Heim-Server unterstützt keine Quersignierung.",
-        "cross_signing_untrusted": "Dein Konto hat eine Quersignaturidentität im sicheren Speicher, der von dieser Sitzung jedoch noch nicht vertraut wird.",
         "cross_signing_user_normal": "Du hast diesen Nutzer nicht verifiziert.",
         "cross_signing_user_verified": "Du hast diesen Nutzer verifiziert. Der Nutzer hat alle seine Sitzungen verifiziert.",
         "cross_signing_user_warning": "Dieser Benutzer hat nicht alle seine Sitzungen verifiziert.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Cross-Signing-Schlüssel löschen",
-            "title": "Cross-Signing-Schlüssel zerstören?",
-            "warning": "Das Löschen von Quersignierungsschlüsseln ist dauerhaft. Alle, mit dem du dich verifiziert hast, werden Sicherheitswarnungen angezeigt bekommen. Du möchtest dies mit ziemlicher Sicherheit nicht tun, es sei denn, du hast jedes Gerät verloren, von dem aus du quersignieren kannst."
-        },
+        "enter_recovery_key": "Geben Sie den Wiederherstellungsschlüssel ein",
         "event_shield_reason_authenticity_not_guaranteed": "Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden.",
         "event_shield_reason_mismatched_sender_key": "Von einer nicht verifizierten Sitzung verschlüsselt",
         "event_shield_reason_unknown_device": "Durch ein unbekanntes oder gelöschtes Gerät verschlüsselt.",
         "event_shield_reason_unsigned_device": "Durch ein von Besitzer nicht verifiziertes Gerät verschlüsselt.",
         "event_shield_reason_unverified_identity": "Durch einen nicht verifizierten Benutzer verschlüsselt.",
         "export_unsupported": "Dein Browser unterstützt die benötigten Verschlüsselungserweiterungen nicht",
+        "forgot_recovery_key": "Wiederherstellungsschlüssel vergessen?",
         "import_invalid_keyfile": "Keine gültige %(brand)s-Schlüsseldatei",
         "import_invalid_passphrase": "Authentifizierung fehlgeschlagen: Falsches Passwort?",
+        "key_storage_out_of_sync": "Ihr Schlüsselspeicher ist nicht synchronisiert.",
+        "key_storage_out_of_sync_description": "Bestätigen Sie Ihren Wiederherstellungsschlüssel, um weiterhin auf Ihren Schlüsselspeicher und den Nachrichtenverlauf zugreifen zu können.",
         "messages_not_secure": {
             "cause_1": "Dein Heim-Server",
             "cause_2": "Der Heim-Server der Person, die du verifizierst",
@@ -900,9 +954,8 @@
             "title": "Neue Wiederherstellungsmethode",
             "warning": "Wenn du die neue Wiederherstellungsmethode nicht festgelegt hast, versucht ein Angreifer möglicherweise, auf dein Konto zuzugreifen. Ändere dein Kontopasswort und lege sofort eine neue Wiederherstellungsmethode in den Einstellungen fest."
         },
-        "not_supported": "<nicht unterstützt>",
-        "pinned_identity_changed": "Die Identität von %(displayName)s (<b>%(userId)s)</b>) hat sich geändert. <a>Mehr erfahren</a>",
-        "pinned_identity_changed_no_displayname": "Die Identität von <b>%(userId)s</b> hat sich geändert. <a>Mehr erfahren</a>",
+        "pinned_identity_changed": "%(displayName)s's (<b>%(userId)s</b>) Identität wurde zurückgesetzt. <a>Mehr erfahren </a>",
+        "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>Die Identität wurde zurückgesetzt. <a>Erfahre mehr </a>",
         "recovery_method_removed": {
             "description_1": "In dieser Sitzung wurde festgestellt, dass deine Sicherheitsphrase und dein Schlüssel für sichere Nachrichten entfernt wurden.",
             "description_2": "Wenn du dies versehentlich getan hast, kannst du in dieser Sitzung \"sichere Nachrichten\" einrichten, die den Nachrichtenverlauf dieser Sitzung mit einer neuen Wiederherstellungsmethode erneut verschlüsseln.",
@@ -916,9 +969,10 @@
         "set_up_toast_description": "Schütze dich vor dem Verlust verschlüsselter Nachrichten und Daten",
         "set_up_toast_title": "Schlüsselsicherung einrichten",
         "setup_secure_backup": {
-            "explainer": "Um deine Schlüssel nicht zu verlieren, musst du sie vor der Abmeldung sichern.",
-            "title": "Einrichten"
+            "explainer": "Um deine Schlüssel nicht zu verlieren, musst du sie vor der Abmeldung sichern."
         },
+        "turn_on_key_storage": "Schlüsselspeicher einschalten",
+        "turn_on_key_storage_description": "Die kryptografische Identität und die Schlüssel deiner Nachrichten sicher auf dem Server speichern. So kann der Nachrichtenverlauf auf allen neuen Geräten angezeigt werden.",
         "udd": {
             "interactive_verification_button": "Interaktiv per Emoji verifizieren",
             "other_ask_verify_text": "Bitte diesen Nutzer, seine Sitzung zu verifizieren, oder verifiziere diese unten manuell.",
@@ -928,12 +982,10 @@
             "title": "Nicht vertraut"
         },
         "unable_to_setup_keys_error": "Schlüssel können nicht eingerichtet werden",
-        "unsupported": "Diese Anwendung unterstützt keine Ende-zu-Ende-Verschlüsselung.",
         "verification": {
             "accepting": "Annehmen…",
             "after_new_login": {
                 "device_verified": "Gerät verifiziert",
-                "reset_confirmation": "Willst du deine Verifizierungsschlüssel wirklich zurücksetzen?",
                 "skip_verification": "Verifizierung vorläufig überspringen",
                 "unable_to_verify": "Gerät konnte nicht verifiziert werden",
                 "verify_this_device": "Dieses Gerät verifizieren"
@@ -955,7 +1007,7 @@
             "incoming_sas_dialog_waiting": "Warte auf Bestätigung des Gesprächspartners …",
             "incoming_sas_user_dialog_text_1": "Überprüfe diesen Benutzer, um ihn als vertrauenswürdig zu kennzeichnen. Benutzern zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende-verschlüsselten Nachrichten.",
             "incoming_sas_user_dialog_text_2": "Wenn du diesen Benutzer verifizierst werden seine Sitzungen für dich und deine Sitzungen für ihn als vertrauenswürdig markiert.",
-            "no_key_or_device": "Es sieht so aus, als hättest du keinen Sicherheitsschlüssel oder andere Geräte, mit denen du dich verifizieren könntest. Dieses Gerät wird keine alten verschlüsselten Nachrichten lesen können. Um deine Identität auf diesem Gerät zu verifizieren musst du deine Verifizierungsschlüssel zurücksetzen.",
+            "no_key_or_device": "Es sieht so aus, als hätten Sie weder einen Wiederherstellungsschlüssel noch andere Geräte, mit denen Sie Ihre Identität bestätigen können. Dieses Gerät kann nicht auf alte verschlüsselte Nachrichten zugreifen. Um Ihre Identität auf diesem Gerät zu bestätigen, müssen Sie Ihre Bestätigungsschlüssel zurücksetzen.",
             "no_support_qr_emoji": "Das Gerät unterstützt weder Verifizieren mittels QR-Code noch Emoji-Verifizierung. %(brand)s benötigt dies jedoch. Bitte verwende eine andere Anwendung.",
             "other_party_cancelled": "Die Gegenstelle hat die Überprüfung abgebrochen.",
             "prompt_encrypted": "Verifiziere alle Benutzer in einem Raum um die vollständige Sicherheit zu gewährleisten.",
@@ -977,8 +1029,8 @@
             "sas_description": "Vergleiche eine einmalige Reihe von Emojis, sofern du an keinem Gerät eine Kamera hast",
             "sas_emoji_caption_self": "Bestätige, dass die folgenden Emoji auf beiden Geräten in der gleichen Reihenfolge angezeigt werden:",
             "sas_emoji_caption_user": "Verifiziere diesen Nutzer, indem du bestätigst, dass folgende Emojis auf dessen Bildschirm erscheinen.",
-            "sas_match": "Sie passen zueinander",
-            "sas_no_match": "Sie passen nicht zueinander",
+            "sas_match": "Sie stimmen überein",
+            "sas_no_match": "Sie stimmen nicht überein",
             "sas_prompt": "Vergleiche einzigartige Emojis",
             "scan_qr": "Verifizierung durch Scannen eines QR-Codes",
             "scan_qr_explainer": "Bitte %(displayName)s, deinen Code zu scannen:",
@@ -1004,19 +1056,20 @@
             "verify_emoji_prompt": "Durch den Vergleich einzigartiger Emojis verifizieren.",
             "verify_emoji_prompt_qr": "Wenn du obigen Code nicht erfassen kannst, verifiziere stattdessen durch den Vergleich von Emojis.",
             "verify_later": "Später verifizieren",
-            "verify_reset_warning_1": "Das Zurücksetzen deiner Sicherheitsschlüssel kann nicht rückgängig gemacht werden. Nach dem Zurücksetzen wirst du alte Nachrichten nicht mehr lesen können und Freunde, die dich vorher verifiziert haben werden Sicherheitswarnungen bekommen, bis du dich erneut mit ihnen verifizierst.",
-            "verify_reset_warning_2": "Bitte fahre nur fort, wenn du sicher bist, dass du alle anderen Geräte und deinen Sicherheitsschlüssel verloren hast.",
             "verify_using_device": "Mit anderem Gerät verifizieren",
-            "verify_using_key": "Mit Sicherheitsschlüssel verifizieren",
-            "verify_using_key_or_phrase": "Mit Sicherheitsschlüssel oder Sicherheitsphrase verifizieren",
+            "verify_using_key": "Mit Wiederherstellungsschlüssel verifizieren",
+            "verify_using_key_or_phrase": "Mit Wiederherstellungsschlüssel oder Wiederherstellungsphrase verifizieren",
             "waiting_for_user_accept": "Warte auf die Annahme von %(displayName)s …",
             "waiting_other_device": "Warten darauf, dass du das auf deinem anderen Gerät bestätigst…",
             "waiting_other_device_details": "Warten, dass du auf deinem anderen Gerät %(deviceName)s (%(deviceId)s) verifizierst…",
             "waiting_other_user": "Warte darauf, dass %(displayName)s bestätigt…"
         },
         "verification_requested_toast_title": "Verifizierung angefragt",
+        "verified_identity_changed": "%(displayName)s's (<b>%(userId)s</b>) Identität wurde zurückgesetzt. <a>Erfahren Sie mehr </a>",
+        "verified_identity_changed_no_displayname": "<b>%(userId)s</b>Die Identität wurde zurückgesetzt. <a>Erfahren Sie mehr </a>",
         "verify_toast_description": "Andere Benutzer vertrauen ihr vielleicht nicht",
-        "verify_toast_title": "Sitzung verifizieren"
+        "verify_toast_title": "Sitzung verifizieren",
+        "withdraw_verification_action": "Verifizierung zurückziehen"
     },
     "error": {
         "admin_contact": "Bitte <a>kontaktiere deinen Systemadministrator</a> um diesen Dienst weiter zu nutzen.",
@@ -1071,11 +1124,7 @@
             "title": "Raumlink konnte nicht kopiert werden"
         },
         "error_loading_user_profile": "Konnte Nutzerprofil nicht laden",
-        "forget_room_failed": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Der Server ist entweder nicht verfügbar, überlastet oder die Suche wurde wegen Zeitüberschreitung abgebrochen :(",
-            "title": "Suche ist fehlgeschlagen"
-        }
+        "forget_room_failed": "Das Entfernen des Raums ist fehlgeschlagen %(errCode)s"
     },
     "error_user_not_logged_in": "Benutzer ist nicht angemeldet",
     "event_preview": {
@@ -1200,6 +1249,7 @@
         "change": "Identitäts-Server wechseln",
         "change_prompt": "Vom Identitäts-Server <current /> trennen, und stattdessen mit <new /> verbinden?",
         "change_server_prompt": "Wenn du <server /> nicht verwenden willst, um Kontakte zu finden und von anderen gefunden zu werden, trage unten einen anderen Identitäts-Server ein.",
+        "changed": "Ihr Identitätsserver wurde geändert",
         "checking": "Überprüfe Server",
         "description_connected": "Zurzeit verwendest du <server></server>, um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitäts-Server nachfolgend wechseln.",
         "description_disconnected": "Zurzeit benutzt du keinen Identitäts-Server. Trage unten einen Server ein, um Kontakte zu finden und von anderen gefunden zu werden.",
@@ -1243,7 +1293,8 @@
         "use_desktop_heading": "Verwende stattdessen %(brand)s Desktop",
         "use_mobile_heading": "Stattdessen %(brand)s am Smartphone benutzen",
         "use_mobile_heading_after_desktop": "Oder verwende die mobile App",
-        "windows": "Windows (%(bits)s-bit)"
+        "windows_64bit": "Windows (64-Bit)",
+        "windows_arm_64bit": "Windows (ARM 64-Bit)"
     },
     "info_tooltip_title": "Information",
     "integration_manager": {
@@ -1252,6 +1303,7 @@
         "error_connecting_heading": "Verbindung zum Integrationsassistenten fehlgeschlagen",
         "explainer": "Integrationsassistenten erhalten Konfigurationsdaten und können Widgets modifizieren, Raumeinladungen verschicken und in deinem Namen Berechtigungslevel setzen.",
         "manage_title": "Integrationen verwalten",
+        "toggle_label": "Aktivieren Sie den Integrationsmanager",
         "use_im": "Verwende einen Integrations-Server, um Bots, Widgets und Sticker-Pakete zu verwalten.",
         "use_im_default": "Nutze einen Integrations-Server <b>(%(serverName)s)</b>, um Bots, Widgets und Sticker-Pakete zu verwalten."
     },
@@ -1453,6 +1505,7 @@
         "location_share_live_description": "Vorläufige Implementierung: Standorte verbleiben im Raumverlauf.",
         "mjolnir": "Neue Methoden, Personen zu blockieren",
         "msc3531_hide_messages_pending_moderation": "Erlaube Moderatoren, noch nicht moderierte Nachrichten auszublenden.",
+        "new_room_list": "Neue Chatroomliste aktivieren",
         "notification_settings": "Neue Benachrichtigungseinstellungen",
         "notification_settings_beta_caption": "Einen einfacheren Weg um die Einstellungen für Benachrichtigungen zu ändern vorstellen. Passe Deine %(brand)s so an wie Du willst.",
         "notification_settings_beta_title": "Benachrichtigungseinstellungen",
@@ -1606,6 +1659,7 @@
         "class_global": "Global",
         "class_other": "Sonstiges",
         "default": "Standard",
+        "default_settings": "Standardeinstellungen verwenden",
         "email_pusher_app_display_name": "E-Mail-Benachrichtigungen",
         "enable_prompt_toast_description": "Aktiviere Desktopbenachrichtigungen",
         "enable_prompt_toast_title": "Benachrichtigungen",
@@ -1624,7 +1678,8 @@
         "mentions_and_keywords_description": "Nur bei Erwähnungen und Schlüsselwörtern benachrichtigen, die du in den <a>Einstellungen</a> konfigurieren kannst",
         "mentions_keywords": "Erwähnungen und Schlüsselwörter",
         "message_didnt_send": "Nachricht nicht gesendet. Klicke für Details.",
-        "mute_description": "Du wirst keine Benachrichtigungen erhalten"
+        "mute_description": "Du wirst keine Benachrichtigungen erhalten",
+        "mute_room": "Raum stummschalten"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s fordert eine Verifizierung an"
@@ -1726,11 +1781,6 @@
         "ongoing": "Löschen…",
         "reason_label": "Grund (optional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Bist du sicher, dass du die Einladung ablehnen willst?",
-        "failed": "Einladung konnte nicht abgelehnt werden",
-        "title": "Einladung ablehnen"
-    },
     "report_content": {
         "description": "Wenn du diese Nachricht meldest, wird die eindeutige Ereignis-ID an die Administration deines Heim-Servers übermittelt. Wenn die Nachrichten in diesem Raum verschlüsselt sind, wird deine Heim-Server-Administration nicht in der Lage sein, Nachrichten zu lesen oder Medien einzusehen.",
         "disagree": "Ablehnen",
@@ -1753,26 +1803,30 @@
         "spam_or_propaganda": "Spam oder Propaganda",
         "toxic_behaviour": "Toxisches Verhalten"
     },
+    "report_room": {
+        "description": "Melden Sie diesen Raum Ihrem Kontoanbieter. Wenn die Nachrichten verschlüsselt sind, kann Ihr Administrator sie nicht lesen.",
+        "reason_label": "Beschreiben Sie den Grund"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Konnte %(failedCount)s Sitzungen nicht entschlüsseln!",
         "count_of_successfully_restored_keys": "%(sessionCount)s Schlüssel erfolgreich wiederhergestellt",
-        "enter_key_description": "Greife auf deinen verschlüsselten Nachrichtenverlauf zu und richte die sichere Kommunikation ein, indem du deinen Sicherheitsschlüssel eingibst.",
-        "enter_key_title": "Sicherheitsschlüssel eingeben",
+        "enter_key_description": "Greifen Sie auf Ihren verschlüsselten Nachrichtenverlauf zu und richten Sie sichere Nachrichtensendung ein, indem Sie Ihren Wiederherstellungsschlüssel eingeben.",
+        "enter_key_title": "Geben Sie den Wiederherstellungsschlüssel ein",
         "enter_phrase_description": "Greife auf deinen verschlüsselten Nachrichtenverlauf zu und richte die sichere Kommunikation ein, indem du deine Sicherheitsphrase eingibst.",
         "enter_phrase_title": "Sicherheitsphrase eingeben",
         "incorrect_security_phrase_dialog": "Das Backup konnte mit dieser Sicherheitsphrase nicht entschlüsselt werden: Bitte überprüfe, ob du die richtige eingegeben hast.",
         "incorrect_security_phrase_title": "Falsche Sicherheitsphrase",
         "key_backup_warning": "<b>Warnung</b>: Du solltest die Schlüsselsicherung nur auf einem vertrauenswürdigen Gerät einrichten.",
         "key_fetch_in_progress": "Lade Schlüssel vom Server …",
-        "key_forgotten_text": "Wenn du deinen Sicherheitsschlüssel vergessen hast, kannst du <button>neue Wiederherstellungsoptionen einrichten</button>",
-        "key_is_invalid": "Kein gültiger Sicherheisschlüssel",
-        "key_is_valid": "Dies sieht aus wie ein gültiger Sicherheitsschlüssel!",
+        "key_forgotten_text": "Für den Fall, dass Sie Ihren Wiederherstellungsschlüssel vergessen haben, können Sie <button> Neue Wiederherstellungsoptionen einrichten </button>",
+        "key_is_invalid": "Kein gültiger Wiederherstellungsschlüssel",
+        "key_is_valid": "Das sieht nach einem gültigen Wiederherstellungsschlüssel aus!",
         "keys_restored_title": "Schlüssel wiederhergestellt",
         "load_error_content": "Konnte Sicherungsstatus nicht laden",
         "load_keys_progress": "%(completed)s von %(total)s Schlüsseln wiederhergestellt",
         "no_backup_error": "Keine Schlüsselsicherung gefunden!",
-        "phrase_forgotten_text": "Wenn du deine Sicherheitsphrase vergessen hast, kannst du <button1>deinen Sicherheitsschlüssel nutzen</button1> oder <button2>neue Wiederherstellungsoptionen einrichten</button2>",
-        "recovery_key_mismatch_description": "Das Backup konnte mit diesem Sicherheitsschlüssel nicht entschlüsselt werden: Bitte überprüfe, ob du den richtigen Sicherheitsschlüssel eingegeben hast.",
+        "phrase_forgotten_text": "Wenn Sie Ihre Sicherheitsphrase vergessen haben, können Sie Ihren Wiederherstellungsschlüssel <button1> verwenden </button1> oder neue Wiederherstellungsoptionen <button2> einrichten </button2>",
+        "recovery_key_mismatch_description": "Das Backup konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfen Sie, ob Sie den richtigen Wiederherstellungsschlüssel eingegeben haben.",
         "recovery_key_mismatch_title": "Nicht übereinstimmende Sicherheitsschlüssel",
         "restore_failed_error": "Konnte Schlüsselsicherung nicht wiederherstellen"
     },
@@ -1910,7 +1964,7 @@
             },
             "room_is_public": "Dieser Raum ist öffentlich"
         },
-        "header_avatar_open_settings_label": "Chatroomeinstellungen öffnen",
+        "header_avatar_open_settings_label": "Raumeinstellungen öffnen",
         "header_face_pile_tooltip": "Personen",
         "header_untrusted_label": "Nicht vertrauenswürdig",
         "inaccessible": "Dieser Raum oder Space ist im Moment nicht zugänglich.",
@@ -1935,7 +1989,6 @@
             "you_created": "Du hast diesen Raum erstellt."
         },
         "invite_email_mismatch_suggestion": "Teile diese E-Mail-Adresse in den Einstellungen, um Einladungen direkt in %(brand)s zu erhalten.",
-        "invite_reject_ignore": "Ablehnen und Benutzer blockieren",
         "invite_sent_to_email": "Einladung an %(email)s gesendet",
         "invite_sent_to_email_room": "Diese Einladung zu %(roomName)s wurde an %(email)s gesendet",
         "invite_subtitle": "Eingeladen von <userName/>",
@@ -2031,38 +2084,90 @@
             },
             "uploading_single_file": "%(filename)s wird hochgeladen"
         },
+        "video_room": "Dieser Raum ist ein Videoraum",
         "waiting_for_join_subtitle": "Sobald eingeladene Benutzer %(brand)s beigetreten sind, werdet ihr euch unterhalten können und der Raum wird Ende-zu-Ende-verschlüsselt sein",
         "waiting_for_join_title": "Warte darauf, dass Benutzer %(brand)s beitreten"
     },
     "room_list": {
         "add_room_label": "Raum hinzufügen",
         "add_space_label": "Space hinzufügen",
+        "appearance": "Erscheinungsbild",
         "breadcrumbs_empty": "Keine kürzlich besuchten Räume",
         "breadcrumbs_label": "Kürzlich besuchte Räume",
+        "empty": {
+            "no_chats": "Noch keine Chats",
+            "no_chats_description": "Beginnen Sie, indem Sie jemandem eine Nachricht senden oder einen Raum erstellen",
+            "no_chats_description_no_room_rights": "Beginnen Sie damit, jemandem eine Nachricht zu senden",
+            "no_favourites": "Sie haben noch keinen Lieblingschat",
+            "no_favourites_description": "Sie können einen Chat in den Chat-Einstellungen zu Ihren Favoriten hinzufügen",
+            "no_invites": "Sie haben keine ungelesenen Einladungen",
+            "no_mentions": "Sie haben keine ungelesenen Erwähnungen",
+            "no_people": "Sie haben noch keine direkten Chats",
+            "no_people_description": "Sie können Filter deaktivieren, um Ihre anderen Chats anzuzeigen",
+            "no_rooms": "Sie sind noch in keinem Raum",
+            "no_rooms_description": "Sie können Filter deaktivieren, um Ihre anderen Chats anzuzeigen",
+            "no_unread": "Glückwunsch! Sie haben keine ungelesenen Nachrichten",
+            "show_activity": "Alle Aktivitäten ansehen",
+            "show_chats": "Alle Chats anzeigen"
+        },
         "failed_add_tag": "Fehler beim Hinzufügen des \"%(tagName)s\"-Tags an dem Raum",
         "failed_remove_tag": "Entfernen der Raum-Kennzeichnung %(tagName)s fehlgeschlagen",
         "failed_set_dm_tag": "Fehler beim Setzen der Nachrichtenmarkierung",
+        "filters": {
+            "favourite": "Favoriten",
+            "invites": "Einladungen",
+            "mentions": "Erwähnungen",
+            "people": "Personen",
+            "rooms": "Räume",
+            "unread": "Ungelesene"
+        },
         "home_menu_label": "Startseiteneinstellungen",
         "join_public_room_label": "Öffentlichen Raum betreten",
         "joining_rooms_status": {
             "one": "Betrete %(count)s Raum",
             "other": "Betrete %(count)s Räume"
         },
+        "list_title": "Raumliste",
+        "more_options": {
+            "copy_link": "Raumlink kopieren",
+            "favourited": "Favorisiert",
+            "leave_room": "Raum verlassen",
+            "low_priority": "Niedrige Priorität",
+            "mark_read": "Als gelesen markieren",
+            "mark_unread": "Als ungelesen markieren"
+        },
         "notification_options": "Benachrichtigungsoptionen",
+        "open_space_menu": "Menü „Raum öffnen“",
+        "primary_filters": "Filter für die Raumliste",
         "redacting_messages_status": {
             "one": "Entferne Nachrichten in %(count)s Raum",
             "other": "Entferne Nachrichten in %(count)s Räumen"
         },
+        "room": {
+            "more_options": "Weitere Optionen",
+            "open_room": "Raum öffnen %(roomName)s"
+        },
+        "room_options": "Chatroomoptionen",
         "show_less": "Weniger anzeigen",
+        "show_message_previews": "Nachrichtenvorschau anzeigen",
         "show_n_more": {
             "other": "%(count)s weitere anzeigen",
             "one": "%(count)s weitere anzeigen"
         },
         "show_previews": "Nachrichtenvorschau anzeigen",
+        "sort": "Sortieren",
         "sort_by": "Sortieren nach",
         "sort_by_activity": "Aktivität",
         "sort_by_alphabet": "A–Z",
+        "sort_type": {
+            "activity": "Aktivität",
+            "atoz": "A–Z"
+        },
         "sort_unread_first": "Räume mit ungelesenen Nachrichten zuerst zeigen",
+        "space_menu": {
+            "home": "Space-Übersicht",
+            "space_settings": "Raumeinstellungen"
+        },
         "space_menu_label": "%(spaceName)s-Menü",
         "sublist_options": "Optionen anzeigen",
         "suggested_rooms_heading": "Vorgeschlagene Räume"
@@ -2291,7 +2396,7 @@
             "public_without_alias_warning": "Um den Raum zu verlinken, füge bitte eine Adresse hinzu.",
             "publish_room": "Diesen Raum im Raumverzeichnis veröffentlichen.",
             "publish_space": "Diesen Space im Raumverzeichnis veröffentlichen.",
-            "strict_encryption": "Niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen in diesem Raum senden",
+            "strict_encryption": "Senden Sie Nachrichten nur an verifizierte Benutzer.",
             "title": "Sicherheit"
         },
         "title": "Raumeinstellungen - %(roomName)s",
@@ -2345,6 +2450,10 @@
         "recent_changes_heading": "Letzte Änderungen, die noch nicht eingegangen sind",
         "title": "Server reagiert nicht"
     },
+    "service_worker_error": {
+        "description": "%(brand)s benötigt einen Service Worker zum Laden authentifizierter Medien aus Matrix-Inhaltsrepositorys. Dies wird von Ihrem Browser nicht unterstützt, sodass Medien möglicherweise nicht geladen werden können.",
+        "title": "Service Worker konnte nicht geladen werden"
+    },
     "seshat": {
         "error_initialising": "Initialisierung der Suche fehlgeschlagen, für weitere Informationen öffne <a>deine Einstellungen</a>",
         "reset_button": "Ereignisspeicher zurück setzen",
@@ -2415,6 +2524,74 @@
         "emoji_autocomplete": "Emoji-Vorschläge während Eingabe",
         "enable_markdown": "Markdown aktivieren",
         "enable_markdown_description": "Beginne Nachrichten mit <code>/plain</code>, um sie ohne Markdown zu senden.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Ihre Kontodaten, Kontakte, Einstellungen und Chat-Liste werden gespeichert",
+                "breadcrumb_page": "Verschlüsselung zurücksetzen",
+                "breadcrumb_second_description": "Sie verlieren jeglichen Nachrichtenverlauf, der nur auf dem Server gespeichert ist",
+                "breadcrumb_third_description": "Sie müssen alle Ihre vorhandenen Geräte und Kontakte erneut verifizieren",
+                "breadcrumb_title": "Sind Sie sicher, dass Sie Ihre Identität zurücksetzen möchten?",
+                "breadcrumb_title_forgot": "Haben Sie Ihren Wiederherstellungsschlüssel vergessen? Sie müssen Ihre Identität zurücksetzen.",
+                "breadcrumb_title_sync_failed": "Der Schlüsselspeicher konnte nicht synchronisiert werden. Sie müssen Ihre Identität zurücksetzen.",
+                "breadcrumb_warning": "Tun Sie dies nur, wenn Sie glauben, dass Ihr Konto kompromittiert wurde.",
+                "details_title": "Angaben zur Verschlüsselung",
+                "do_not_close_warning": "Schließen Sie dieses Fenster nicht, bis der Reset abgeschlossen ist",
+                "export_keys": "Schlüssel exportieren",
+                "import_keys": "Schlüssel importieren",
+                "other_people_device_description": "Warnung: Benutzer, die sich nicht explizit bei Ihnen verifiziert haben (z. B. mit Emojis), erhalten Ihre verschlüsselten Nachrichten nicht. Außerdem erhalten nicht verifizierte Geräte von verifizierten Benutzern Ihre verschlüsselten Nachrichten nicht.",
+                "other_people_device_label": "Senden Sie in verschlüsselten Räumen Nachrichten nur an verifizierte Benutzer",
+                "other_people_device_title": "Geräte anderer Personen",
+                "reset_identity": "Kryptografische Identität zurücksetzen",
+                "reset_in_progress": "Der Reset wird ausgeführt",
+                "session_id": "Sitzungs-ID:",
+                "session_key": "Sitzungsschlüssel:",
+                "title": "Advanced"
+            },
+            "confirm_key_storage_off": "Soll die Speicherung der Schlüssel wirklich deaktiviert sein?",
+            "confirm_key_storage_off_description": "Wenn Sie sich von all Ihren Geräten abmelden, verlieren Sie Ihren Nachrichtenverlauf und müssen alle Ihre vorhandenen Kontakte erneut überprüfen. <a>Erfahre mehr </a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Schlüsselspeicher löschen",
+                "confirm": "Schlüsselspeicher löschen",
+                "description": "Durch das Löschen des Schlüsselspeichers werden Ihre kryptografische Identität und Ihre Nachrichtenschlüssel vom Server entfernt und die folgenden Sicherheitsfunktionen werden deaktiviert:",
+                "list_first": "Sie werden auf neuen Geräten keinen verschlüsselten Nachrichtenverlauf haben",
+                "list_second": "Sie verlieren den Zugriff auf Ihre verschlüsselten Nachrichten, wenn Sie von %(brand)s überall abgemeldet sind",
+                "title": "Möchten Sie den Schlüsselspeicher wirklich ausschalten und löschen?"
+            },
+            "device_not_verified_button": "Dieses Gerät verifizieren",
+            "device_not_verified_description": "Sie müssen dieses Gerät verifizieren, um Ihre Verschlüsselungseinstellungen einsehen zu können.",
+            "device_not_verified_title": "Gerät nicht verifiziert",
+            "dialog_title": "<strong>Einstellungen:</strong> Verschlüsselung",
+            "key_storage": {
+                "allow_key_storage": "Schlüsselspeicherung zulassen",
+                "description": "Speichern Sie Ihre kryptografische Identität und Ihre Nachrichtenschlüssel sicher auf dem Server. Auf diese Weise können Sie Ihren Nachrichtenverlauf auf allen neuen Geräten einsehen. <a>Erfahre mehr </a>",
+                "title": "Schlüsselspeicher"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Bestätigen Sie den neuen Wiederherstellungsschlüssel",
+                "change_recovery_confirm_description": "Geben Sie unten Ihren neuen Wiederherstellungsschlüssel ein, um den Vorgang abzuschließen. Ihr alter Wiederherstellungsschlüssel funktioniert nicht mehr.",
+                "change_recovery_confirm_title": "Geben Sie Ihren neuen Wiederherstellungsschlüssel ein",
+                "change_recovery_key": "Wiederherstellungsschlüssel ändern",
+                "change_recovery_key_description": "Notieren Sie sich diesen neuen Wiederherstellungsschlüssel an einem sicheren Ort. Klicken Sie anschließend auf Weiter, um die Änderung zu bestätigen.",
+                "change_recovery_key_title": "Wiederherstellungsschlüssel ändern?",
+                "description": "Falls Sie alle Ihre Geräte verloren haben. stellen Sie Ihre kryptografische Identität und Ihren Nachrichtenverlauf mit einem Wiederherstellungsschlüssel wieder her.",
+                "enter_key_error": "Der eingegebene Wiederherstellungsschlüssel ist nicht korrekt.",
+                "enter_recovery_key": "Geben Sie den Wiederherstellungsschlüssel ein",
+                "forgot_recovery_key": "Wiederherstellungsschlüssel vergessen?",
+                "key_storage_warning": "Ihr Schlüsselspeicher ist nicht synchronisiert. Klicken Sie auf die Schaltfläche unten, um das Problem zu beheben.",
+                "save_key_description": "Teilen Sie dies mit niemandem!",
+                "save_key_title": "Wiederherstellungsschlüssel",
+                "set_up_recovery": "Wiederherstellung einrichten",
+                "set_up_recovery_confirm_button": "Einrichtung abschließen",
+                "set_up_recovery_confirm_description": "Geben Sie den auf dem vorherigen Bildschirm angezeigten Wiederherstellungsschlüssel ein, um die Einrichtung der Wiederherstellung abzuschließen.",
+                "set_up_recovery_confirm_title": "Geben Sie zur Bestätigung Ihren Wiederherstellungsschlüssel ein",
+                "set_up_recovery_description": "Ihr Schlüsselspeicher ist durch einen Wiederherstellungsschlüssel geschützt. Wenn Sie nach der Installation einen neuen Wiederherstellungsschlüssel benötigen, können Sie ihn neu erstellen, indem Sie '%(changeRecoveryKeyButton)s' auswählen.",
+                "set_up_recovery_save_key_description": "Notieren Sie sich diesen Wiederherstellungsschlüssel an einem sicheren Ort, z. B. in einem Passwort-Manager, einer verschlüsselten Notiz oder einem Safe.",
+                "set_up_recovery_save_key_title": "Bewahren Sie Ihren Wiederherstellungsschlüssel an einem sicheren Ort auf",
+                "set_up_recovery_secondary_description": "Nachdem Sie auf Weiter geklickt haben, generieren wir einen Wiederherstellungsschlüssel für Sie.",
+                "title": "Wiederherstellung"
+            },
+            "title": "Verschlüsselung"
+        },
         "general": {
             "account_management_section": "Benutzerkontenverwaltung",
             "account_section": "Benutzerkonto",
@@ -2457,6 +2634,7 @@
             "discovery_needs_terms_title": "Lassen Sie sich von anderen finden",
             "display_name": "Anzeigename",
             "display_name_error": "Anzeigename konnte nicht gesetzt werden",
+            "email_adding_unsupported_by_hs": "Dieser Homeserver unterstützt das Hinzufügen von E-Mail-Adressen zu Ihrem Konto nicht.",
             "email_address_in_use": "Diese E-Mail-Adresse wird bereits verwendet",
             "email_address_label": "E-Mail-Adresse",
             "email_not_verified": "Deine E-Mail-Adresse wurde noch nicht verifiziert",
@@ -2481,7 +2659,9 @@
             "error_share_msisdn_discovery": "Teilen der Telefonnummer nicht möglich",
             "identity_server_no_token": "Kein Identitäts-Zugangs-Token gefunden",
             "identity_server_not_set": "Kein Identitäts-Server festgelegt",
+            "invalid_phone_number": "Die angegebene Telefonnummer scheint nicht gültig zu sein.",
             "language_section": "Sprache",
+            "msisdn_adding_unsupported_by_hs": "Dieser Homeserver unterstützt das Hinzufügen von Telefonnummern zu Ihrem Konto nicht.",
             "msisdn_in_use": "Diese Telefonnummer wird bereits verwendet",
             "msisdn_label": "Telefonnummer",
             "msisdn_verification_field_label": "Bestätigungscode",
@@ -2500,7 +2680,6 @@
             "unable_to_load_msisdns": "Telefonnummern können nicht geladen werden",
             "username": "Benutzername"
         },
-        "image_thumbnails": "Vorschauen für Bilder",
         "inline_url_previews_default": "URL-Vorschau standardmäßig aktivieren",
         "inline_url_previews_room": "URL-Vorschau für Raummitglieder",
         "inline_url_previews_room_account": "URL-Vorschau für dich in diesem Raum",
@@ -2522,21 +2701,21 @@
                 "enter_phrase_description": "Gib eine nur dir bekannte Sicherheitsphrase ein, die dem Schutz deiner Daten dient. Um die Sicherheit zu gewährleisten, sollte dies nicht dein Kontopasswort sein.",
                 "enter_phrase_title": "Sicherheitsphrase eingeben",
                 "enter_phrase_to_confirm": "Gib dein Kennwort ein zweites Mal zur Bestätigung ein.",
-                "generate_security_key_description": "Wir generieren einen Sicherheitsschlüssel für dich, den du in einem Passwort-Manager oder Safe sicher aufbewahren solltest.",
-                "generate_security_key_title": "Sicherheitsschlüssel generieren",
+                "generate_security_key_description": "Wir generieren einen Wiederherstellungsschlüssel, den Sie an einem sicheren Ort aufbewahren können, z. B. in einem Passwort-Manager oder einem Safe.",
+                "generate_security_key_title": "Generieren Sie einen Wiederherstellungsschlüssel",
                 "pass_phrase_match_failed": "Das passt nicht.",
                 "pass_phrase_match_success": "Das passt!",
                 "phrase_strong_enough": "Großartig! Diese Sicherheitsphrase sieht stark genug aus.",
                 "secret_storage_query_failure": "Status des sicheren Speichers kann nicht gelesen werden",
-                "security_key_safety_reminder": "Bewahre deinen Sicherheitsschlüssel sicher auf, etwa in einem Passwortmanager oder einem Safe, da er verwendet wird, um deine Daten zu sichern.",
+                "security_key_safety_reminder": "Bewahren Sie Ihren Wiederherstellungsschlüssel an einem sicheren Ort auf, z. B. in einem Passwort-Manager oder einem Safe, da er zum Schutz Ihrer verschlüsselten Daten verwendet wird.",
                 "set_phrase_again": "Gehe zurück und setze es erneut.",
                 "settings_reminder": "Du kannst auch in den Einstellungen Sicherungen einrichten und deine Schlüssel verwalten.",
                 "title_confirm_phrase": "Sicherheitsphrase bestätigen",
-                "title_save_key": "Sicherungsschlüssel sichern",
+                "title_save_key": "Speichern Sie Ihren Wiederherstellungsschlüssel",
                 "title_set_phrase": "Sicherheitsphrase setzen",
                 "unable_to_setup": "Sicherer Speicher kann nicht eingerichtet werden",
                 "use_different_passphrase": "Eine andere Passphrase verwenden?",
-                "use_phrase_only_you_know": "Verwende für deine Sicherung eine geheime Phrase, die nur du kennst, und speichere optional einen Sicherheitsschlüssel."
+                "use_phrase_only_you_know": "Verwenden Sie eine geheime Phrase, die nur Sie kennen, und speichern Sie optional einen Wiederherstellungsschlüssel, um ihn für Backups zu verwenden."
             }
         },
         "key_export_import": {
@@ -2563,6 +2742,14 @@
         "labs_mjolnir": {
             "dialog_title": "<strong>Einstellungen:</strong> Blockierte Benutzer"
         },
+        "media_preview": {
+            "hide_avatars": "Verstecken Sie die Avatare von Chatroom und Einladung",
+            "hide_media": "Verstecke Sie sich immer",
+            "media_preview_description": "Ein verstecktes Medium kann immer angezeigt werden, indem Sie darauf tippen",
+            "media_preview_label": "Medien in der Zeitleiste anzeigen",
+            "show_in_private": "In privaten Chatrooms",
+            "show_media": "Immer anzeigen"
+        },
         "notifications": {
             "default_setting_description": "Diese Einstellung wird standardmäßig für all deine Räume übernommen.",
             "default_setting_section": "Ich möchte benachrichtigt werden für (Standardeinstellung)",
@@ -2648,57 +2835,20 @@
         "prompt_invite": "Warnen, bevor du Einladungen zu ungültigen Matrix-IDs sendest",
         "replace_plain_emoji": "Klartext-Emoji automatisch ersetzen",
         "security": {
-            "4s_public_key_in_account_data": "in den Kontodaten",
-            "4s_public_key_status": "Öffentlicher Schlüssel des sicheren Speichers:",
             "analytics_description": "Anonym Daten teilen um bei der Identifizierung von Fehlern zu helfen. Keine persönlichen Daten. Keine Daten von Dritt-Anbietern.",
-            "backup_key_cached_status": "Sicherungsschlüssel zwischengespeichert:",
-            "backup_key_stored_status": "Sicherungsschlüssel gespeichert:",
-            "backup_key_unexpected_type": "unbekannter Typ",
-            "backup_key_well_formed": "wohlgeformt",
-            "backup_keys_description": "Sichere deine Schlüssel mit deinen Kontodaten, für den Fall, dass du den Zugriff auf deine Sitzungen verlierst. Deine Schlüssel werden mit einem eindeutigen Sicherheitsschlüssel geschützt.",
             "bulk_options_accept_all_invites": "Akzeptiere alle %(invitedRooms)s Einladungen",
             "bulk_options_reject_all_invites": "Alle %(invitedRooms)s Einladungen ablehnen",
             "bulk_options_section": "Sammeloptionen",
-            "cross_signing_cached": "lokal zwischengespeichert",
-            "cross_signing_homeserver_support": "Unterstützung des Heim-Servers:",
-            "cross_signing_homeserver_support_exists": "existiert",
-            "cross_signing_in_4s": "im Schlüsselspeicher",
-            "cross_signing_in_memory": "im Speicher",
-            "cross_signing_master_private_Key": "Privater Hauptschlüssel:",
-            "cross_signing_not_cached": "lokal nicht gefunden",
-            "cross_signing_not_found": "nicht gefunden",
-            "cross_signing_not_in_4s": "nicht im Speicher gefunden",
-            "cross_signing_not_stored": "nicht gespeichert",
-            "cross_signing_private_keys": "Private Quersignaturschlüssel:",
-            "cross_signing_public_keys": "Öffentlicher Quersignaturschlüssel:",
-            "cross_signing_self_signing_private_key": "Selbst signierter privater Schlüssel:",
-            "cross_signing_user_signing_private_key": "Privater Benutzerschlüssel:",
-            "cryptography_section": "Verschlüsselung",
             "dehydrated_device_description": "Die Offline-Gerätefunktion ermöglicht es Ihnen, verschlüsselte Nachrichten zu empfangen, auch wenn Sie an keinem Gerät angemeldet sind",
             "dehydrated_device_enabled": "Offline-Gerät aktiviert",
-            "delete_backup": "Lösche Sicherung",
-            "delete_backup_confirm_description": "Bist du sicher? Du wirst alle deine verschlüsselten Nachrichten verlieren, wenn deine Schlüssel nicht gut gesichert sind.",
             "dialog_title": "<strong>Einstellungen </strong> Sicherheit & Datenschutz",
             "e2ee_default_disabled_warning": "Deine Server-Administration hat die Ende-zu-Ende-Verschlüsselung für private Räume und Direktnachrichten standardmäßig deaktiviert.",
             "enable_message_search": "Nachrichtensuche in verschlüsselten Räumen aktivieren",
             "encryption_section": "Verschlüsselung",
-            "error_loading_key_backup_status": "Konnte Status der Schlüsselsicherung nicht laden",
-            "export_megolm_keys": "E2E-Raumschlüssel exportieren",
             "ignore_users_empty": "Sie haben keinen Benutzer blockiert.",
             "ignore_users_section": "Blockierte Benutzer",
-            "import_megolm_keys": "E2E-Raumschlüssel importieren",
-            "key_backup_active": "Diese Sitzung sichert deine Schlüssel.",
-            "key_backup_active_version": "Aktive Backup-Version:",
-            "key_backup_active_version_none": "Nichts",
             "key_backup_algorithm": "Algorithmus:",
-            "key_backup_can_be_restored": "Dieses Backup kann in dieser Sitzung wiederhergestellt werden",
-            "key_backup_complete": "Alle Schlüssel gesichert",
             "key_backup_connect": "Verbinde diese Sitzung mit einer Schlüsselsicherung",
-            "key_backup_connect_prompt": "Verbinde diese Sitzung mit deiner Schlüsselsicherung bevor du dich abmeldest, um den Verlust von Schlüsseln zu vermeiden.",
-            "key_backup_in_progress": "Sichere %(sessionsRemaining)s Schlüssel …",
-            "key_backup_inactive": "Diese Sitzung <b>sichert deine Schlüssel nicht</b>, aber du hast eine vorhandene Sicherung, die du wiederherstellen und in Zukunft hinzufügen kannst.",
-            "key_backup_inactive_warning": "Deine Schlüssel werden von dieser Sitzung <b>nicht gesichert</b>.",
-            "key_backup_latest_version": "Letzte Backup-Version auf diesen Server:",
             "message_search_disable_warning": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.",
             "message_search_disabled": "Speichere verschlüsselte Nachrichten lokal, sodass sie deinen Suchergebnissen erscheinen können.",
             "message_search_enabled": {
@@ -2718,14 +2868,8 @@
             "message_search_unsupported": "Um verschlüsselte Nachrichten lokal zu durchsuchen, benötigt %(brand)s weitere Komponenten. Wenn du diese Funktion testen möchtest, kannst du dir deine eigene Version von %(brand)s Desktop mit der <nativeLink>integrierten Suchfunktion kompilieren</nativeLink>.",
             "message_search_unsupported_web": "Das Durchsuchen von verschlüsselten Nachrichten wird aus Sicherheitsgründen nur von %(brand)s Desktop unterstützt. <desktopLink>Hier gehts zum Download</desktopLink>.",
             "record_session_details": "Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist",
-            "restore_key_backup": "Von Sicherung wiederherstellen",
-            "secret_storage_not_ready": "nicht bereit",
-            "secret_storage_ready": "bereit",
-            "secret_storage_status": "Sicherer Speicher:",
             "send_analytics": "Analysedaten senden",
-            "session_id": "Sitzungs-ID:",
-            "session_key": "Sitzungsschlüssel:",
-            "strict_encryption": "Niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen senden"
+            "strict_encryption": "Senden Sie Nachrichten nur an verifizierte Benutzer"
         },
         "send_read_receipts": "Sende Lesebestätigungen",
         "send_read_receipts_unsupported": "Dein Server unterstützt das Deaktivieren von Lesebestätigungen nicht.",
@@ -2967,8 +3111,6 @@
         "topic": "Raumthema anzeigen oder ändern",
         "topic_none": "Dieser Raum hat kein Thema.",
         "topic_room_error": "Thema des Raums konnte nicht ermittelt werden: Raum kann nicht gefunden werden (%(roomId)s",
-        "tovirtual": "Zum virtuellen Raum dieses Raums wechseln, sofern vorhanden",
-        "tovirtual_not_found": "Kein virtueller Raum für diesen Raum",
         "unban": "Entsperrt den Benutzer mit der angegebenen ID",
         "unflip": "Stellt ┬──┬ ノ( ゜-゜ノ) einer Klartextnachricht voran",
         "unholdcall": "Beendet das Halten des Anrufs",
@@ -2986,6 +3128,7 @@
         "view": "Raum mit angegebener Adresse betrachten",
         "whois": "Zeigt Informationen über Benutzer"
     },
+    "sliding_sync_legacy_no_longer_supported": "Legacy Sliding Sync wird nicht mehr unterstützt: Bitte melden Sie sich ab und wieder an, um das neue Sliding Sync-Flag zu aktivieren",
     "space": {
         "add_existing_room_space": {
             "create": "Willst du einen neuen Raum hinzufügen?",
@@ -3090,7 +3233,7 @@
         "heading_without_query": "Suche nach",
         "join_button_text": "%(roomAddress)s betreten",
         "keyboard_scroll_hint": "Benutze <arrows/> zum Scrollen",
-        "message_search_section_title": "Andere Suchen",
+        "messages_label": "Nachrichten",
         "other_rooms_in_space": "Andere Räume in %(spaceName)s",
         "public_rooms_label": "Öffentliche Räume",
         "public_spaces_label": "Öffentliche Spaces",
@@ -3100,7 +3243,6 @@
         "result_may_be_hidden_privacy_warning": "Einige Vorschläge könnten aus Gründen der Privatsphäre ausgeblendet sein",
         "result_may_be_hidden_warning": "Einige Ergebnisse können ausgeblendet sein",
         "search_dialog": "Suchdialog",
-        "search_messages_hint": "Wenn du Nachrichten durchsuchen willst, klicke auf das <icon/> Icon oberhalb des Raumes",
         "spaces_title": "Spaces, in denen du Mitglied bist",
         "start_group_chat_button": "Gruppenunterhaltung beginnen"
     },
@@ -3149,9 +3291,7 @@
     "threads_activity_centre": {
         "header": "Thread-Aktivität",
         "no_rooms_with_threads_notifs": "Sie haben noch keine Chatrooms mit Thread-Benachrichtigungen.",
-        "no_rooms_with_unread_threads": "Sie haben noch keine Chatrooms mit ungelesenen Threads.",
-        "release_announcement_description": "Die Thread-Benachrichtigungen wurden verschoben. Sie finden sie ab sofort hier.",
-        "release_announcement_header": "Thread Aktivitätszentrum"
+        "no_rooms_with_unread_threads": "Sie haben noch keine Chatrooms mit ungelesenen Threads."
     },
     "time": {
         "about_day_ago": "vor etwa einem Tag",
@@ -3199,7 +3339,7 @@
             "historical_event_no_key_backup": "Der historische Nachrichtenverlauf ist auf diesem Gerät nicht verfügbar.",
             "historical_event_unverified_device": "Sie müssen dieses Gerät verifizieren, um auf den Nachrichtenverlauf zugreifen zu können",
             "historical_event_user_not_joined": "Sie haben keinen Zugriff auf diese Nachricht",
-            "sender_identity_previously_verified": "Die verifizierte Identität des Absenders hat sich geändert",
+            "sender_identity_previously_verified": "Die verifizierte Identität des Absenders wurde zurückgesetzt",
             "sender_unsigned_device": "Von einem unsicheren Gerät verschickt.",
             "unable_to_decrypt": "Entschlüsselung der Nachricht nicht möglich"
         },
@@ -3363,6 +3503,7 @@
             "left_reason": "%(targetName)s hat den Raum verlassen: %(reason)s",
             "no_change": "%(senderName)s hat keine Änderungen gemacht",
             "reject_invite": "%(targetName)s hat die Einladung abgelehnt",
+            "reject_invite_reason": "%(targetName)slehnte die Einladung ab: %(reason)s",
             "remove_avatar": "%(senderName)s hat das Profilbild entfernt",
             "remove_name": "%(senderName)s hat den alten Anzeigenamen %(oldDisplayName)s entfernt",
             "set_avatar": "%(senderName)s hat das Profilbild gesetzt",
@@ -3398,10 +3539,14 @@
             "sent": "%(senderName)s hat %(targetDisplayName)s in diesen Raum eingeladen."
         },
         "m.room.tombstone": "%(senderDisplayName)s hat diesen Raum aktualisiert.",
-        "m.room.topic": "%(senderDisplayName)s hat das Thema geändert in \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s hat das Thema geändert in \"%(topic)s\".",
+            "removed": "%(senderDisplayName)shat das Thema entfernt."
+        },
         "m.sticker": "%(senderDisplayName)s hat einen Sticker gesendet.",
         "m.video": {
-            "error_decrypting": "Videoentschlüsselung fehlgeschlagen"
+            "error_decrypting": "Videoentschlüsselung fehlgeschlagen",
+            "show_video": "Video anzeigen"
         },
         "m.widget": {
             "added": "%(senderName)s hat das Widget %(widgetName)s hinzugefügt",
@@ -3657,10 +3802,11 @@
         "unavailable": "Nicht verfügbar"
     },
     "update_room_access_modal": {
-        "description": "Um einen Link zum Teilen zu erstellen, müssen Sie Gästen erlauben, diesem Chatroom beizutreten. Dadurch kann der Chatroom weniger sicher werden. Wenn Sie mit dem Anruf fertig sind, können Sie den Raum wieder privat machen.",
-        "dont_change_description": "Sie können den Anruf auch in einem separaten Chatroom führen.",
+        "description": "Um einen Link zum Teilen zu erstellen, mache diesen Chatroom <b> öffentlich </b> oder erlaube <b>Beitrittsanfragen</b>. Auf diese Weise können Gäste beitreten, ohne eingeladen zu werden.",
+        "dont_change_description": "Andernfalls, wenn Sie den Zugang dieses Raumes nicht anpassen möchten, können sie einen neuen Raum für den Anruflink erstellen.",
         "no_change": "Ich möchte die Zugriffsebene nicht ändern.",
-        "title": "Ändern Sie die Zugriffsebene des Chatrooms."
+        "revert_access_description": "(Dies kann in den Raumeinstellungen auf den vorherigen Wert zurückgesetzt werden: <b> Sicherheit & Datenschutz</b>/<b>Zutritt</b>)",
+        "title": "Gastbenutzern erlauben, diesem Raum beizutreten"
     },
     "upload_failed_generic": "Die Datei „%(fileName)s“ konnte nicht hochgeladen werden.",
     "upload_failed_size": "Die Datei „%(fileName)s“ überschreitet das Hochladelimit deines Heim-Servers",
@@ -3687,18 +3833,9 @@
         "ban_room_confirm_title": "Aus %(roomName)s verbannen",
         "ban_space_everything": "Überall sperren, wo ich die Rechte dazu habe",
         "ban_space_specific": "Sperre sie in ausgewählten Chatrooms und Spaces",
-        "count_of_sessions": {
-            "other": "%(count)s Sitzungen",
-            "one": "%(count)s Sitzung"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s verifizierte Sitzungen",
-            "one": "Eine verifizierte Sitzung"
-        },
         "deactivate_confirm_action": "Konto deaktivieren",
         "deactivate_confirm_description": "Beim Deaktivieren wirst du abgemeldet und ein erneutes Anmelden verhindert. Zusätzlich wirst du aus allen Räumen entfernt. Diese Aktion kann nicht rückgängig gemacht werden. Bist du sicher, dass du dieses Konto deaktivieren willst?",
         "deactivate_confirm_title": "Konto deaktivieren?",
-        "dehydrated_device_enabled": "Offline-Gerät aktiviert",
         "demote_button": "Zurückstufen",
         "demote_self_confirm_description_space": "Das Entfernen von Rechten kann nicht rückgängig gemacht werden. Falls sie dir niemand anderer zurückgeben kann, kannst du sie nie wieder erhalten.",
         "demote_self_confirm_room": "Du wirst nicht in der Lage sein, die Änderung zurückzusetzen, da du dich degradierst. Wenn du der letze Nutzer mit Berechtigungen bist, wird es unmöglich sein die Privilegien zurückzubekommen.",
@@ -3706,15 +3843,12 @@
         "disinvite_button_room": "Einladung zurückziehen",
         "disinvite_button_room_name": "Einladung für %(roomName)s zurückziehen",
         "disinvite_button_space": "Einladung zurückziehen",
-        "edit_own_devices": "Sitzungen anzeigen",
         "error_ban_user": "Verbannen des Benutzers fehlgeschlagen",
         "error_deactivate": "Benutzer konnte nicht deaktiviert werden",
         "error_kicking_user": "Fehler beim entfernen des Nutzers",
         "error_mute_user": "Stummschalten des Nutzers fehlgeschlagen",
         "error_revoke_3pid_invite_description": "Die Einladung konnte nicht zurückgezogen werden. Der Server hat möglicherweise ein vorübergehendes Problem oder du hast nicht ausreichende Berechtigungen, um die Einladung zurückzuziehen.",
         "error_revoke_3pid_invite_title": "Einladung konnte nicht zurückgezogen werden",
-        "hide_sessions": "Sitzungen ausblenden",
-        "hide_verified_sessions": "Verifizierte Sitzungen ausblenden",
         "ignore_button": "Blockieren",
         "ignore_confirm_description": "Alle Nachrichten und Einladungen der Person werden verborgen. Bist du sicher, dass du sie ignorieren möchtest?",
         "ignore_confirm_title": "%(user)s ignorieren",
@@ -3758,6 +3892,7 @@
         "unban_space_specific": "In ausgewählten Chatrooms und Spaces die Sperrung für sie aufheben",
         "unban_space_warning": "Die Person wird keinen Zutritt zu Bereichen haben, in denen du nicht administrierst.",
         "unignore_button": "Nicht mehr ignorieren",
+        "verification_unavailable": "Benutzerverifizierung nicht verfügbar",
         "verify_button": "Nutzer verifizieren",
         "verify_explainer": "Für zusätzliche Sicherheit, verifiziere diesen Nutzer, durch Vergleichen eines Einmal-Codes auf euren beiden Geräten."
     },
@@ -3810,7 +3945,6 @@
         "input_devices": "Eingabegeräte",
         "jitsi_call": "Jitsi-Konferenz",
         "join_button_tooltip_call_full": "Entschuldigung — dieser Anruf ist aktuell besetzt",
-        "join_button_tooltip_connecting": "Verbinden",
         "legacy_call": "Legacy-Anruf",
         "maximise": "Bildschirm füllen",
         "maximise_call": "Anruf maximieren",
@@ -3965,7 +4099,7 @@
         "error_need_to_be_logged_in": "Du musst angemeldet sein.",
         "error_unable_start_audio_stream_description": "Audiostream kann nicht gestartet werden.",
         "error_unable_start_audio_stream_title": "Livestream konnte nicht gestartet werden",
-        "modal_data_warning": "Daten auf diesem Bildschirm werden mit %(widgetDomain)s geteilt",
+        "modal_data_warning": "Die unten aufgeführten Daten werden mit %(widgetDomain)s geteilt.",
         "modal_title_default": "Modales Widget",
         "no_name": "Unbekannte App",
         "open_id_permissions_dialog": {
diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json
index 1aca5d0776752e543300c0c9b1459e84980f32bc..662d56b6d50b88207ab7c1e23b3113fb55900d42 100644
--- a/src/i18n/strings/el.json
+++ b/src/i18n/strings/el.json
@@ -9,17 +9,20 @@
             "one": "1 μη αναγνωσμένη αναφορά.",
             "other": "%(count)s μη αναγνωσμένα μηνύματα συμπεριλαμβανομένων των αναφορών."
         },
-        "room_name": "Δωμάτιο %(name)s",
+        "recent_rooms": "Πρόσφατες αίθουσες",
+        "room_name": "Αίθουσα %(name)s",
         "unread_messages": "Μη αναγνωσμένα μηνύματα.",
         "user_menu": "Μενού χρήστη"
     },
-    "a11y_jump_first_unread_room": "Μετάβαση στο πρώτο μη αναγνωσμένο δωμάτιο.",
+    "a11y_jump_first_unread_room": "Μετάβαση στην πρώτη μη αναγνωσμένη αίθουσα.",
     "action": {
         "accept": "Αποδοχή",
         "add": "Προσθήκη",
-        "add_existing_room": "Προσθήκη υπάρχοντος δωματίου",
+        "add_existing_room": "Προσθήκη υπάρχουσας αίθουσας",
         "add_people": "Προσθήκη ατόμων",
+        "apply": "Εφαρμογή",
         "approve": "Έγκριση",
+        "ask_to_join": "Αίτημα συμμετοχής",
         "back": "Πίσω",
         "call": "Κλήση",
         "cancel": "Ακύρωση",
@@ -35,10 +38,11 @@
         "copy": "Αντιγραφή",
         "copy_link": "Αντιγραφή συνδέσμου",
         "create": "Δημιουργία",
-        "create_a_room": "Δημιουργήστε ένα δωμάτιο",
+        "create_a_room": "Δημιουργήστε μία αίθουσα",
         "create_account": "Δημιουργία Λογαριασμού",
         "decline": "Απόρριψη",
         "delete": "Διαγραφή",
+        "deny": "Άρνηση",
         "disable": "Απενεργοποίηση",
         "disconnect": "Αποσύνδεση",
         "dismiss": "Απόρριψη",
@@ -47,8 +51,8 @@
         "edit": "Επεξεργασία",
         "enable": "Ενεργοποίηση",
         "expand": "Επέκταση",
-        "explore_public_rooms": "Εξερευνήστε δημόσια δωμάτια",
-        "explore_rooms": "Εξερευνήστε δωμάτια",
+        "explore_public_rooms": "Εξερευνήστε δημόσιες αίθουσες",
+        "explore_rooms": "Εξερευνήστε αίθουσες",
         "export": "Εξαγωγή",
         "forward": "Προώθηση",
         "go": "Μετάβαση",
@@ -64,14 +68,14 @@
         "join": "Συμμετοχή",
         "learn_more": "Μάθετε περισσότερα",
         "leave": "Αποχώρηση",
-        "leave_room": "Αποχώρηση από το δωμάτιο",
+        "leave_room": "Αποχώρηση από την αίθουσα",
         "logout": "Αποσύνδεση",
         "manage": "Διαχειριστείτε",
         "maximise": "Μεγιστοποίηση",
         "mention": "Αναφορά",
         "minimise": "Ελαχιστοποίηση",
-        "new_room": "Νέο δωμάτιο",
-        "new_video_room": "Νέο δωμάτιο βίντεο",
+        "new_room": "Νέα αίθουσα",
+        "new_video_room": "Νέα αίθουσα βίντεο",
         "next": "Επόμενο",
         "no": "Όχι",
         "ok": "Εντάξει",
@@ -79,11 +83,12 @@
         "pause": "Παύση",
         "pin": "Καρφίτσα",
         "play": "Αναπαραγωγή",
+        "proceed": "Συνέχεια",
         "quote": "Παράθεση",
         "react": "Αντίδραση",
         "refresh": "Ανανέωση",
         "register": "Εγγραφή",
-        "reject": "Απόρριψη",
+        "reload": "Επαναφόρτωση",
         "remove": "Αφαίρεση",
         "rename": "Μετονομασία",
         "reply": "Απάντηση",
@@ -99,6 +104,7 @@
         "search": "Αναζήτηση",
         "send_report": "Αποστολή αναφοράς",
         "share": "Διαμοιρασμός",
+        "show": "Εμφάνιση",
         "show_advanced": "Εμφάνιση προχωρημένων",
         "show_all": "Εμφάνιση όλων",
         "sign_in": "Σύνδεση",
@@ -122,6 +128,7 @@
         "upload": "Μεταφόρτωση",
         "verify": "Επαλήθευση",
         "view": "Προβολή",
+        "view_all": "Προβολή Όλων",
         "view_list": "Προβολή λίστας",
         "view_message": "Προβολή μηνύματος",
         "view_source": "Προβολή κώδικα",
@@ -200,7 +207,7 @@
         },
         "misconfigured_body": "Ζητήστε από τον %(brand)s διαχειριστή σας να ελέγξει <a>τις ρυθμίσεις σας</a> για λανθασμένες ή διπλότυπες καταχωρίσεις.",
         "misconfigured_title": "Οι παράμετροι του %(brand)s σας είναι λανθασμένα ρυθμισμένοι",
-        "msisdn_field_description": "Άλλοι χρήστες μπορούν να σας προσκαλέσουν σε δωμάτια χρησιμοποιώντας τα στοιχεία επικοινωνίας σας",
+        "msisdn_field_description": "Άλλοι χρήστες μπορούν να σας προσκαλέσουν σε αίθουσες χρησιμοποιώντας τα στοιχεία επικοινωνίας σας",
         "msisdn_field_label": "Τηλέφωνο",
         "msisdn_field_number_invalid": "Αυτός ο αριθμός τηλεφώνου δε φαίνεται σωστός, ελέγξτε και δοκιμάστε ξανά",
         "msisdn_field_required_invalid": "Εισάγετε αριθμό τηλεφώνου",
@@ -275,7 +282,7 @@
         "sso_complete_in_browser_dialog_title": "Μεταβείτε στο πρόγραμμα περιήγησής σας για να ολοκληρώσετε τη σύνδεση",
         "sso_failed_missing_storage": "Ζητήσαμε από το πρόγραμμα περιήγησης να θυμάται τον διακομιστή που χρησιμοποιείτε για να συνδέεστε, αλλά το πρόγραμμα περιήγησης δεν το έχει αποθηκεύσει. Πηγαίνετε στην σελίδα σύνδεσεις για να προσπαθήσετε ξανά.",
         "sso_or_username_password": "%(ssoButtons)s Ή %(usernamePassword)s",
-        "sync_footer_subtitle": "Εάν έχετε συμμετάσχει σε πολλά δωμάτια, αυτό μπορεί να διαρκέσει λίγο",
+        "sync_footer_subtitle": "Εάν έχετε συμμετάσχει σε πολλές αίθουσες, αυτό μπορεί να διαρκέσει λίγο",
         "uia": {
             "code": "Κωδικός",
             "email_auth_header": "Ελέγξτε το email σας για να συνεχίσετε",
@@ -300,16 +307,15 @@
         "username_in_use": "Κάποιος έχει ήδη αυτό το όνομα χρήστη, δοκιμάστε ένα άλλο."
     },
     "bug_reporting": {
-        "additional_context": "Εάν υπάρχουν πρόσθετες ππληροφορίες που θα βοηθούσαν στην ανάλυση του ζητήματος, όπως τι κάνατε εκείνη τη στιγμή, αναγνωριστικά δωματίων, αναγνωριστικά χρηστών κ.λπ., συμπεριλάβετε τα εδώ.",
+        "additional_context": "Εάν υπάρχουν πρόσθετες πληροφορίες που θα βοηθούσαν στην ανάλυση του ζητήματος, όπως τι κάνατε εκείνη τη στιγμή, αναγνωριστικά αιθουσών, αναγνωριστικά χρηστών κ.λπ., συμπεριλάβετέ τα εδώ.",
         "before_submitting": "Προτού υποβάλετε αρχεία καταγραφής, πρέπει να <a>δημιουργήσετε ένα ζήτημα GitHub</a> για να περιγράψετε το πρόβλημά σας.",
         "collecting_information": "Συγκέντρωση πληροφοριών σχετικά με την έκδοση της εφαρμογής",
         "collecting_logs": "Συγκέντρωση πληροφοριών",
         "create_new_issue": "Παρακαλούμε <newIssueLink>δημιουργήστε ένα νέο issue</newIssueLink> στο GitHub ώστε να μπορέσουμε να διερευνήσουμε αυτό το σφάλμα.",
-        "description": "Τα αρχεία καταγραφής εντοπισμού σφαλμάτων περιέχουν δεδομένα χρήσης εφαρμογών, συμπεριλαμβανομένου του ονόματος χρήστη σας, των αναγνωριστικών ή των ψευδωνύμων των δωματίων που έχετε επισκεφτεί, των στοιχείων διεπαφής χρήστη με τα οποία αλληλεπιδράσατε τελευταία και των ονομάτων χρήστη άλλων χρηστών. Δεν περιέχουν μηνύματα.",
+        "description": "Τα αρχεία καταγραφής εντοπισμού σφαλμάτων περιέχουν δεδομένα χρήσης εφαρμογών, συμπεριλαμβανομένου του ονόματος χρήστη σας, των αναγνωριστικών ή των ψευδωνύμων των αιθουσών που έχετε επισκεφτεί, των στοιχείων διεπαφής χρήστη με τα οποία αλληλεπιδράσατε τελευταία και των ονομάτων χρήστη άλλων χρηστών. Δεν περιέχουν μηνύματα.",
         "download_logs": "Λήψη αρχείων καταγραφής",
         "downloading_logs": "Λήψη αρχείων καταγραφής",
         "error_empty": "Πείτε μας τι πήγε στραβά ή, καλύτερα, δημιουργήστε ένα ζήτημα στο GitHub που να περιγράφει το πρόβλημα.",
-        "failed_send_logs": "Αποτυχία αποστολής αρχείων καταγραφής: ",
         "github_issue": "Ζήτημα GitHub",
         "introduction": "Εάν έχετε υποβάλει ένα σφάλμα μέσω του GitHub, τα αρχεία καταγραφής εντοπισμού σφαλμάτων μπορούν να μας βοηθήσουν να εντοπίσουμε το πρόβλημα. ",
         "log_request": "Για να μας βοηθήσετε να το αποτρέψουμε αυτό στο μέλλον, <a>στείλτε μας τα αρχεία καταγραφής</a>.",
@@ -346,25 +352,26 @@
         "access_token": "Διακριτικό πρόσβασης",
         "accessibility": "Προσβασιμότητα",
         "advanced": "Προχωρημένες",
-        "all_rooms": "Όλα τα δωμάτια",
         "analytics": "Αναλυτικά δεδομένα",
         "and_n_others": {
             "one": "και ένας ακόμα...",
             "other": "και %(count)s άλλοι..."
         },
         "appearance": "Εμφάνιση",
+        "application": "Εφαρμογή",
         "are_you_sure": "Είστε σίγουροι;",
         "attachment": "Επισύναψη",
         "authentication": "Πιστοποίηση",
+        "beta": "Beta",
         "camera": "Κάμερα",
         "cameras": "Κάμερες",
         "capabilities": "Δυνατότητες",
         "copied": "Αντιγράφηκε!",
         "credits": "Συντελεστές",
-        "cross_signing": "Διασταυρούμενη υπογραφή",
         "dark": "Σκούρο",
         "description": "Περιγραφή",
         "deselect_all": "Αποεπιλογή όλων",
+        "device": "Συσκευή",
         "edited": "επεξεργάστηκε",
         "email_address": "Ηλεκτρονική διεύθυνση",
         "emoji": "Εικονίδια",
@@ -390,11 +397,14 @@
         "labs": "Πειραματικά",
         "legal": "Νομικό",
         "light": "Ανοιχτό",
+        "loading": "Φόρτωση...",
         "location": "Τοποθεσία",
         "low_priority": "Χαμηλής προτεραιότητας",
+        "matrix": "Matrix",
         "message": "Μήνυμα",
         "message_layout": "Διάταξη μηνύματος",
         "microphone": "Μικρόφωνο",
+        "model": "Μοντέλο",
         "modern": "Μοντέρνο",
         "mute": "Σίγαση",
         "n_members": {
@@ -413,28 +423,28 @@
         "offline": "Εκτός σύνδεσης",
         "on": "Ενεργό",
         "options": "Επιλογές",
-        "orphan_rooms": "Άλλα δωμάτια",
+        "orphan_rooms": "Άλλες αίθουσες",
         "password": "Κωδικός πρόσβασης",
         "people": "Άτομα",
         "preferences": "Προτιμήσεις",
+        "presence": "Παρουσία",
         "preview_message": "Είσαι ο καλύτερος!",
         "privacy": "Ιδιωτικότητα",
         "private": "Ιδιωτικό",
-        "private_room": "Ιδιωτικό δωμάτιο",
+        "private_room": "Ιδιωτική αίθουσα",
         "private_space": "Ιδιωτικός χώρος",
         "profile": "Προφίλ",
         "public": "Δημόσιο",
-        "public_room": "Δημόσιο δωμάτιο",
+        "public_room": "Δημόσια αίθουσα",
         "public_space": "Δημόσιος χώρος",
         "qr_code": "Κωδικός QR",
         "random": "Τυχαία",
         "reactions": "Αντιδράσεις",
         "report_a_bug": "Αναφορά σφάλματος",
-        "room": "Δωμάτιο",
-        "room_name": "Όνομα δωματίου",
-        "rooms": "Δωμάτια",
+        "room": "Αίθουσα",
+        "room_name": "Όνομα αίθουσας",
+        "rooms": "Αίθουσες",
         "secure_backup": "Ασφαλές αντίγραφο ασφαλείας",
-        "security": "Ασφάλεια",
         "select_all": "Επιλογή όλων",
         "server": "Διακομιστής",
         "settings": "Ρυθμίσεις",
@@ -452,23 +462,26 @@
         "theme": "Θέμα",
         "thread": "Νήμα",
         "threads": "Νήμα εκτέλεσης",
-        "timeline": "Χρονοδιάγραμμα",
-        "trusted": "Έμπιστο",
+        "timeline": "Χρονολόγιο",
         "unencrypted": "Μη κρυπτογραφημένο",
         "unmute": "Άρση σίγασης",
-        "unnamed_room": "Ανώνυμο δωμάτιο",
+        "unnamed_room": "Ανώνυμη αίθουσα",
         "unnamed_space": "Χώρος χωρίς όνομα",
+        "unverified": "Μη επαληθευμένη",
+        "user": "Χρήστης",
         "user_avatar": "Εικόνα προφίλ",
         "username": "Όνομα χρήστη",
         "verification_cancelled": "Η επαλήθευση ακυρώθηκε",
+        "verified": "Επαληθευμένη",
+        "version": "Έκδοση",
         "video": "Βίντεο",
-        "video_room": "Δωμάτια βίντεο",
+        "video_room": "Αίθουσα βίντεο",
         "view_message": "Προβολή μηνύματος",
         "warning": "Προειδοποίηση"
     },
     "composer": {
         "autocomplete": {
-            "@room_description": "Ειδοποιήστε όλο το δωμάτιο",
+            "@room_description": "Ειδοποιήστε όλη την αίθουσα",
             "command_a11y": "Αυτόματη συμπλήρωση εντολών",
             "command_description": "Εντολές",
             "emoji_a11y": "Αυτόματη συμπλήρωση Emoji",
@@ -483,10 +496,17 @@
         "edit_composer_label": "Επεξεργασία μηνύματος",
         "format_bold": "Έντονα",
         "format_code_block": "Μπλοκ κώδικα",
+        "format_decrease_indent": "Μείωση εσοχής",
+        "format_increase_indent": "Αύξηση εσοχής",
         "format_inline_code": "Κωδικός",
         "format_insert_link": "Εισαγωγή συνδέσμου",
+        "format_italic": "Πλάγια",
         "format_italics": "Πλάγια",
+        "format_link": "Σύνδεσμος",
+        "format_ordered_list": "Αριθμημένη λίστα",
         "format_strikethrough": "Διαγράμμιση",
+        "format_underline": "Υπογράμμιση",
+        "format_unordered_list": "Λίστα με κουκκκίδες",
         "no_perms_notice": "Δεν έχετε δικαιώματα για να δημοσιεύσετε σε αυτό το δωμάτιο",
         "placeholder": "Στείλτε ένα μήνυμα…",
         "placeholder_encrypted": "Αποστολή κρυπτογραφημένου μηνύματος…",
@@ -523,7 +543,7 @@
         "join_rule_public_label": "Οποιοσδήποτε θα μπορεί να βρει και να εγγραφεί σε αυτό το δωμάτιο.",
         "join_rule_public_parent_space_label": "Οποιοσδήποτε θα μπορεί να βρει και να εγγραφεί σε αυτόν τον χώρο, όχι μόνο μέλη του <SpaceName/>.",
         "join_rule_restricted": "Ορατό στα μέλη του χώρου",
-        "join_rule_restricted_label": "Όλοι στο <SpaceName/> θα μπορούν να βρουν και να συμμετάσχουν σε αυτό το δωμάτιο.",
+        "join_rule_restricted_label": "Όλοι στο <SpaceName/> θα μπορούν να βρουν και να συμμετάσχουν σε αυτήν την αίθουσα.",
         "name_validation_required": "Εισάγετε ένα όνομα για το δωμάτιο",
         "room_visibility_label": "Ορατότητα δωματίου",
         "title_private_room": "Δημιουργήστε ένα ιδιωτικό δωμάτιο",
@@ -579,6 +599,11 @@
         "subspace_join_rule_public_description": "Οποιοσδήποτε θα μπορεί να βρει και να εγγραφεί σε αυτόν τον χώρο, όχι μόνο μέλη του <SpaceName/>.",
         "subspace_join_rule_restricted_description": "Οποιοσδήποτε στο <SpaceName/> θα μπορεί να βρει και να συμμετάσχει σε αυτό το δωμάτιο."
     },
+    "credits": {
+        "default_cover_photo": "Η <photo>προεπιλεγμένη φωτογραφία εξωφύλλου</photo> είναι ο © <author>Jesús Roncero</author> που χρησιμοποιείται σύμφωνα με τους όρους του <terms>CC-BY-SA 4.0.</terms>",
+        "twemoji": "Η τέχνη εμότζι <twemoji>Twemoji</twemoji> είναι © <author>Twitter, Inc and other contributors</author> υπό τους όρους του <terms>CC-BY 4.0</terms>.",
+        "twemoji_colr": "Η γραμματοσειρά <colr>twemoji-colr </colr> είναι του © <author>Ιδρύματος Mozilla</author> που χρησιμοποιείται σύμφωνα με τους όρους του <terms>Apache 2.0</terms>."
+    },
     "devtools": {
         "active_widgets": "Ενεργές Μικροεφαρμογές",
         "category_other": "Άλλα",
@@ -589,6 +614,7 @@
         "developer_tools": "Εργαλεία προγραμματιστή",
         "edit_setting": "Επεξεργασία ρύθμισης",
         "edit_values": "Επεξεργασία τιμών",
+        "empty_string": "<empty string>",
         "event_content": "Περιεχόμενο συμβάντος",
         "event_id": "ID συμβάντος: %(eventId)s",
         "event_sent": "Το συμβάν στάλθηκε!",
@@ -600,12 +626,32 @@
         "failed_to_load": "Αποτυχία φόρτωσης.",
         "failed_to_save": "Αποτυχία αποθήκευσης ρυθμίσεων.",
         "failed_to_send": "Αποτυχία αποστολής συμβάντος!",
+        "id": "ID: ",
         "invalid_json": "Δε μοιάζει με έγκυρο JSON.",
         "level": "Επίπεδο",
+        "main_timeline": "Κύριο χρονολόγιο",
+        "no_receipt_found": "Δεν βρέθηκε απόδειξη",
+        "notification_state": "Η κατάσταση ειδοποίησης είναι <strong>%(notificationState)s</strong>",
+        "notifications_debug": "Αποσφαλμάτωση ειδοποιήσεων",
         "number_of_users": "Αριθμός χρηστών",
         "original_event_source": "Αρχική πηγή συμβάντος",
+        "room_encrypted": "Η αίθουσα είναι <strong>κρυπτογραφημένη ✅</strong>",
         "room_id": "ID δωματίου: %(roomId)s",
+        "room_not_encrypted": "Η αίθουσα <strong>δεν είναι κρυπτογραφημένη 🚨</strong>",
+        "room_notifications_dot": "Σημείο: ",
+        "room_notifications_highlight": "Αποκορύφωμα: ",
+        "room_notifications_last_event": "Τελευταίο γεγονός:",
+        "room_notifications_sender": "Αποστολέας: ",
+        "room_notifications_thread_id": "Αναγνωριστικό νήματος: ",
+        "room_notifications_total": "Σύνολο: ",
+        "room_notifications_type": "Τύπος: ",
+        "room_status": "Κατάσταση δωματίου",
+        "room_unread_status_count": {
+            "one": "Κατάσταση μη αναγνωσμένων δωματίου: <strong>%(status)s</strong>, πλήθος: <strong>%(count)s</strong>",
+            "other": "Κατάσταση μη αναγνωσμένων δωματίου: <strong>%(status)s</strong>, πλήθος: <strong>%(count)s</strong>"
+        },
         "save_setting_values": "Αποθήκευση τιμών ρύθμισης",
+        "see_history": "Εμφάνιση ιστορικού",
         "send_custom_account_data_event": "Αποστολή προσαρμοσμένου συμβάντος δεδομένων λογαριασμού",
         "send_custom_room_account_data_event": "Αποστολή προσαρμοσμένου συμβάντος δεδομένων λογαριασμού δωματίου",
         "send_custom_state_event": "Αποστολή προσαρμοσμένου συμβάντος κατάστασης",
@@ -618,24 +664,30 @@
         "setting_definition": "Ορισμός ρύθμισης:",
         "setting_id": "Ρύθμιση αναγνωριστικού",
         "settings_explorer": "Εξερεύνηση ρυθμίσεων",
-        "show_hidden_events": "Εμφάνιση κρυφών συμβάντων στη γραμμή χρόνου",
+        "show_hidden_events": "Εμφάνιση κρυφών συμβάντων στο χρονολόγιο",
         "spaces": {
             "one": "<χώρος>",
             "other": "<%(count)s χώροι>"
         },
         "state_key": "Κλειδί κατάστασης",
+        "thread_root_id": "Thread Root ID: %(threadRootId)s",
+        "threads_timeline": "Χρονολόγιο νημάτων",
         "title": "Εργαλεία προγραμματιστή",
         "toggle_event": "μεταβολή συμβάντος",
         "toolbox": "Εργαλειοθήκη",
         "use_at_own_risk": "Αυτό το UI ΔΕΝ ελέγχει τους τύπους των τιμών. Χρησιμοποιήστε το με δική σας ευθύνη.",
+        "user_read_up_to": "Ο χρήστης διάβασε μέχρι: ",
+        "user_read_up_to_ignore_synthetic": "Ο χρήστης διάβασε έως (ignoreSynthetic): ",
+        "user_read_up_to_private": "Ο χρήστης διάβασε ως (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "Ο χρήστης διάβασε ως (m.read.private;ignoreSynthetic): ",
         "value": "Τιμή",
         "value_colon": "Τιμή:",
-        "value_in_this_room": "Τιμή σε αυτό το δωμάτιο",
-        "value_this_room_colon": "Τιμή σε αυτό το δωμάτιο:",
+        "value_in_this_room": "Τιμή σε αυτήν την αίθουσα",
+        "value_this_room_colon": "Τιμή σε αυτήν την αίθουσα:",
         "values_explicit": "Αξίες σε σαφής επίπεδα",
         "values_explicit_colon": "Τιμές σε σαφή επίπεδα:",
         "values_explicit_room": "Αξίες σε σαφής επίπεδα σε αυτό το δωμάτιο",
-        "values_explicit_this_room_colon": "Τιμές σε σαφή επίπεδα σε αυτό το δωμάτιο:",
+        "values_explicit_this_room_colon": "Τιμές σε ρητά επίπεδα σε αυτήν την αίθουσα:",
         "view_servers_in_room": "Προβολή διακομιστών στο δωμάτιο",
         "view_source_decrypted_event_source": "Αποκρυπτογραφημένη πηγή συμβάντος",
         "widget_screenshots": "Ενεργοποίηση στιγμιότυπων οθόνης μικροεφαρμογών σε υποστηριζόμενες μικροεφαρμογές"
@@ -661,43 +713,23 @@
     "empty_room": "Άδειο δωμάτιο",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Εισαγάγετε τη Φράση Ασφαλείας ή <button>χρησιμοποιήστε το Κλειδί Ασφαλείας</button> για να συνεχίσετε.",
             "key_validation_text": {
-                "invalid_security_key": "Μη έγκυρο Κλειδί Ασφαλείας",
-                "recovery_key_is_correct": "Φαίνεται καλό!",
-                "wrong_file_type": "Λάθος τύπος αρχείου",
                 "wrong_security_key": "Λάθος Κλειδί Ασφαλείας"
             },
-            "reset_title": "Επαναφορά όλων",
-            "reset_warning_1": "Κάντε αυτό μόνο όταν δεν έχετε άλλη συσκευή για να ολοκληρώσετε την επαλήθευση.",
-            "reset_warning_2": "Εάν επαναφέρετε τα πάντα, θα κάνετε επανεκκίνηση χωρίς αξιόπιστες συνεδρίες, χωρίς αξιόπιστους χρήστες και ενδέχεται να μην μπορείτε να δείτε προηγούμενα μηνύματα.",
             "restoring": "Επαναφορά κλειδιών από αντίγραφο ασφαλείας",
-            "security_key_title": "Κλειδί Ασφαλείας",
-            "security_phrase_incorrect_error": "Δεν είναι δυνατή η πρόσβαση στον κρυφό χώρο αποθήκευσης. Βεβαιωθείτε ότι έχετε εισαγάγει τη σωστή Φράση Ασφαλείας.",
-            "security_phrase_title": "Φράση Ασφαλείας",
-            "use_security_key_prompt": "Χρησιμοποιήστε το Κλειδί Ασφαλείας σας για να συνεχίσετε."
+            "security_key_title": "Κλειδί Ασφαλείας"
         },
         "bootstrap_title": "Ρύθμιση κλειδιών",
         "cancel_entering_passphrase_description": "Είστε σίγουρος/η ότι θέλετε να ακυρώσετε την εισαγωγή κωδικού;",
         "cancel_entering_passphrase_title": "Ακύρωση εισαγωγής κωδικού;",
         "confirm_encryption_setup_body": "Κάντε κλικ στο κουμπί παρακάτω για να επιβεβαιώσετε τη ρύθμιση της κρυπτογράφησης.",
         "confirm_encryption_setup_title": "Επιβεβαιώστε τη ρύθμιση κρυπτογράφησης",
-        "cross_signing_not_ready": "Η διασταυρούμενη υπογραφή δεν έχει ρυθμιστεί.",
-        "cross_signing_ready": "Η διασταυρούμενη υπογραφή είναι έτοιμη για χρήση.",
-        "cross_signing_ready_no_backup": "Η διασταυρούμενη υπογραφή είναι έτοιμη, αλλά δεν δημιουργούνται αντίγραφα ασφαλείας κλειδιών.",
         "cross_signing_room_normal": "Αυτό το δωμάτιο έχει κρυπτογράφηση από άκρο σε άκρο",
-        "cross_signing_room_verified": "Όλοι σε αυτό το δωμάτιο έχουν επαληθευτεί",
+        "cross_signing_room_verified": "Όλοι σε αυτήν την αίθουσα έχουν επαληθευτεί",
         "cross_signing_room_warning": "Κάποιος χρησιμοποιεί μια άγνωστη συνεδρία",
-        "cross_signing_unsupported": "Ο κεντρικός σας διακομιστής δεν υποστηρίζει διασταυρούμενη σύνδεση.",
-        "cross_signing_untrusted": "Ο λογαριασμός σας έχει ταυτότητα διασταυρούμενης υπογραφής σε μυστικό χώρο αποθήκευσης, αλλά δεν είναι ακόμη αξιόπιστος από αυτήν την συνεδρία.",
         "cross_signing_user_normal": "Δεν έχετε επαληθεύσει αυτόν τον χρήστη.",
         "cross_signing_user_verified": "Έχετε επαληθεύσει αυτόν τον χρήστη. Αυτός ο χρήστης έχει επαληθεύσει όλες τις συνεδρίες του.",
         "cross_signing_user_warning": "Αυτός ο χρήστης δεν έχει επαληθεύσει όλες τις συνεδρίες του.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Διαγράψτε τα κλειδιά διασταυρούμενης υπογραφής",
-            "title": "Να καταστραφούν τα κλειδιά διασταυρούμενης υπογραφής;",
-            "warning": "Η διαγραφή κλειδιών διασταυρούμενης υπογραφής είναι μόνιμη. Οποιοσδήποτε με τον οποίο έχετε επαληθευτεί θα λάβει ειδοποιήσεις ασφαλείας. Πιθανότατα δεν θέλετε να το κάνετε αυτό, εκτός και αν έχετε χάσει όλες τις συσκευές από τις οποίες μπορείτε να υπογράψετε."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "Η αυθεντικότητα αυτού του κρυπτογραφημένου μηνύματος δεν είναι εγγυημένη σε αυτήν τη συσκευή.",
         "event_shield_reason_mismatched_sender_key": "Κρυπτογραφήθηκε από μια μη επαληθευμένη συνεδρία",
         "export_unsupported": "Ο περιηγητής σας δεν υποστηρίζει τα απαιτούμενα πρόσθετα κρυπτογράφησης",
@@ -717,7 +749,6 @@
             "title": "Νέα Μέθοδος Ανάκτησης",
             "warning": "Εάν δεν έχετε ορίσει τη νέα μέθοδο ανάκτησης, ένας εισβολέας μπορεί να προσπαθεί να αποκτήσει πρόσβαση στον λογαριασμό σας. Αλλάξτε τον κωδικό πρόσβασης του λογαριασμού σας και ορίστε μια νέα μέθοδο ανάκτησης αμέσως στις Ρυθμίσεις."
         },
-        "not_supported": "<δεν υποστηρίζεται>",
         "recovery_method_removed": {
             "description_1": "Αυτή η συνεδρία εντόπισε ότι η φράση ασφαλείας σας και το κλειδί για τα ασφαλή μηνύματα σας έχουν αφαιρεθεί.",
             "description_2": "Εάν το κάνατε κατά λάθος, μπορείτε να ρυθμίσετε τα Ασφαλή Μηνύματα σε αυτήν τη συνεδρία, τα οποία θα κρυπτογραφούν εκ νέου το ιστορικό μηνυμάτων αυτής της συνεδρίας με μια νέα μέθοδο ανάκτησης.",
@@ -728,8 +759,7 @@
         "set_up_toast_description": "Προστατευτείτε από την απώλεια πρόσβασης σε κρυπτογραφημένα μηνύματα και δεδομένα",
         "set_up_toast_title": "Ρυθμίστε το αντίγραφο ασφαλείας",
         "setup_secure_backup": {
-            "explainer": "Δημιουργήστε αντίγραφα ασφαλείας των κλειδιών σας πριν αποσυνδεθείτε για να μην τα χάσετε.",
-            "title": "Εγκατάσταση"
+            "explainer": "Δημιουργήστε αντίγραφα ασφαλείας των κλειδιών σας πριν αποσυνδεθείτε για να μην τα χάσετε."
         },
         "udd": {
             "other_ask_verify_text": "Ζητήστε από αυτόν τον χρήστη να επιβεβαιώσει την συνεδρία του, ή επιβεβαιώστε την χειροκίνητα παρακάτω.",
@@ -739,12 +769,10 @@
             "title": "Μη Έμπιστο"
         },
         "unable_to_setup_keys_error": "Δεν είναι δυνατή η ρύθμιση των κλειδιών",
-        "unsupported": "Αυτό το πρόγραμμα-πελάτης δεν υποστηρίζει κρυπτογράφηση από άκρο σε άκρο.",
         "verification": {
             "accepting": "Αποδοχή …",
             "after_new_login": {
                 "device_verified": "Η συσκευή επαληθεύτηκε",
-                "reset_confirmation": "Είστε σίγουρος ότι θέλετε να επαναφέρετε τα κλειδιά επαλήθευσης;",
                 "skip_verification": "Παράβλεψη επαλήθευσης προς το παρόν",
                 "unable_to_verify": "Αδυναμία επαλήθευσης αυτής της συσκευής",
                 "verify_this_device": "Επαληθεύστε αυτήν τη συσκευή"
@@ -770,6 +798,7 @@
             "prompt_self": "Ξεκινήστε ξανά την επαλήθευση από την ειδοποίηση.",
             "prompt_unencrypted": "Σε κρυπτογραφημένα δωμάτια, επαληθεύστε όλους τους χρήστες για να βεβαιωθείτε ότι είναι ασφαλές.",
             "prompt_user": "Ξεκινήστε ξανά την επαλήθευση από το προφίλ τους.",
+            "qr_or_sas": "%(qrCode)s ή %(emojiCompare)s",
             "qr_or_sas_header": "Επαληθεύστε αυτήν τη συσκευή συμπληρώνοντας ένα από τα παρακάτω:",
             "qr_prompt": "Σαρώστε αυτόν τον μοναδικό κωδικό",
             "qr_reciprocate_same_shield_device": "Σχεδόν έτοιμοι! Εμφανίζεται η ίδια ασπίδα και στην άλλη συσκευή σας;",
@@ -806,7 +835,6 @@
             "verify_emoji_prompt": "Επαληθεύστε συγκρίνοντας μοναδικά emoji.",
             "verify_emoji_prompt_qr": "Εάν δεν μπορείτε να σαρώσετε τον παραπάνω κώδικα, επαληθεύστε το συγκρίνοντας μοναδικά emoji.",
             "verify_later": "Θα επαληθεύσω αργότερα",
-            "verify_reset_warning_1": "Δεν είναι δυνατή η αναίρεση της επαναφοράς των κλειδιών επαλήθευσης. Μετά την επαναφορά, δε θα έχετε πρόσβαση σε παλιά κρυπτογραφημένα μηνύματα και όλοι οι φίλοι που σας έχουν προηγουμένως επαληθεύσει θα βλέπουν προειδοποιήσεις ασφαλείας μέχρι να επαληθεύσετε ξανά μαζί τους.",
             "verify_using_device": "Επαλήθευση με άλλη συσκευή",
             "verify_using_key": "Επαλήθευση με Κλειδί ασφαλείας",
             "verify_using_key_or_phrase": "Επαλήθευση με Κλειδί Ασφαλείας ή Φράση Ασφαλείας",
@@ -861,11 +889,7 @@
             "title": "Αδυναμία αντιγραφής του συνδέσμου δωματίου"
         },
         "error_loading_user_profile": "Αδυναμία φόρτωσης του προφίλ χρήστη",
-        "forget_room_failed": "Δεν ήταν δυνατή η διαγραφή του δωματίου (%(errCode)s)",
-        "search_failed": {
-            "server_unavailable": "Ο διακομιστής μπορεί να είναι μη διαθέσιμος, υπερφορτωμένος, ή να έχει λήξει η αναζήτηση :(",
-            "title": "Η αναζήτηση απέτυχε"
-        }
+        "forget_room_failed": "Δεν ήταν δυνατή η διαγραφή του δωματίου (%(errCode)s)"
     },
     "event_preview": {
         "m.call.answer": {
@@ -882,12 +906,21 @@
             "dm_send": "Αναμονή απάντησης",
             "user": "Ο %(senderName)s ξεκίνησε μια κλήση",
             "you": "Ξεκινήσατε μία κλήση"
-        }
+        },
+        "m.emote": "* %(senderName)s %(emote)s",
+        "m.reaction": {
+            "user": "Ο χρήστης %(sender)s αντέδρασε με %(reaction)s στο %(message)s",
+            "you": "Αντέδρασες με %(reaction)s στο %(message)s"
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s"
     },
     "export_chat": {
         "cancelled": "Η Εξαγωγή ακυρώθηκε",
         "cancelled_detail": "Η εξαγωγή ακυρώθηκε με επιτυχία",
         "confirm_stop": "Είστε βέβαιοι ότι θέλετε να διακόψετε την εξαγωγή των δεδομένων σας; Εάν το κάνετε, θα πρέπει να ξεκινήσετε από την αρχή.",
+        "creating_html": "Δημιουργία HTML...",
+        "creating_output": "Δημιουργία εξόδου...",
         "creator_summary": "Ο %(creatorName)s δημιούργησε αυτό το δωμάτιο.",
         "current_timeline": "Τρέχον χρονοδιάγραμμα",
         "enter_number_between_min_max": "Εισαγάγετε έναν αριθμό μεταξύ %(min)s και %(max)s",
@@ -911,21 +944,28 @@
             "one": "Ανακτήθηκαν %(count)s συμβάντα από %(total)s",
             "other": "Ανακτήθηκαν %(count)s συμβάντα από %(total)s"
         },
+        "fetching_events": "Ανάκτηση συμβάντων...",
         "file_attached": "Tο αρχείο επισυνάφθηκε",
         "format": "Μορφή",
         "from_the_beginning": "Από την αρχή",
         "generating_zip": "Δημιουργία ZIP",
+        "html": "HTML",
+        "html_title": "Εξαγμένα δεδομένα",
         "include_attachments": "Συμπεριλάβετε Συνημμένα",
+        "json": "JSON",
         "media_omitted": "Τα μέσα παραλείφθηκαν",
         "media_omitted_file_size": "Τα μέσα παραλείφθηκαν - υπέρβαση του ορίου μεγέθους αρχείου",
         "messages": "Μηνύματα",
+        "next_page": "Επόμενη ομάδα μηνυμάτων",
         "num_messages": "Αριθμός μηνυμάτων",
         "num_messages_min_max": "Ο αριθμός των μηνυμάτων μπορεί να είναι μόνο ένας αριθμός μεταξύ %(min)s και %(max)s",
         "number_of_messages": "Καθορίστε έναν αριθμό μηνυμάτων",
+        "previous_page": "Προηγούμενη ομάδα μηνυμάτων",
         "processing_event_n": "Επεξεργασία συμβάντος %(number)s από %(total)s",
         "select_option": "Επιλέξτε από τις παρακάτω επιλογές για να εξαγάγετε συνομιλίες από το χρονολόγιό σας",
         "size_limit": "Όριο Μεγέθους",
         "size_limit_min_max": "Το μέγεθος μπορεί να είναι μόνο ένας αριθμός μεταξύ %(min)s MB και %(max)s MB",
+        "starting_export": "Έναρξη εξαγωγής...",
         "successful": "Επιτυχής Εξαγωγή",
         "successful_detail": "Η εξαγωγή σας ήταν επιτυχής. Βρείτε τη στο φάκελο Λήψεις.",
         "text": "Απλό κείμενο",
@@ -1057,10 +1097,12 @@
     },
     "keyboard": {
         "activate_button": "Ενεργοποίηση επιλεγμένου κουμπιού",
+        "alt": "Alt",
         "autocomplete_cancel": "Ακύρωση αυτόματης συμπλήρωσης",
         "autocomplete_force": "Εξαναγκασμός ολοκλήρωσης",
         "autocomplete_navigate_next": "Επόμενη πρόταση αυτόματης συμπλήρωσης",
         "autocomplete_navigate_prev": "Προηγούμενη πρόταση αυτόματης συμπλήρωσης",
+        "backspace": "Πίσω διάστημα",
         "cancel_reply": "Ακύρωση απάντησης σε μήνυμα",
         "category_autocomplete": "Αυτόματη συμπλήρωση",
         "category_calls": "Κλήσεις",
@@ -1079,7 +1121,11 @@
         "composer_toggle_link": "Σύνδεσμος",
         "composer_toggle_quote": "Εναλλαγή Παράθεσης",
         "composer_undo": "Αναίρεση επεξεργασίας",
+        "control": "Ctrl",
         "dismiss_read_marker_and_jump_bottom": "Παραβλέψτε το δείκτη ανάγνωσης και μεταβείτε στο τέλος",
+        "end": "Τέλος",
+        "enter": "Enter",
+        "escape": "Esc",
         "go_home_view": "Μεταβείτε στην Αρχική προβολή",
         "home": "Αρχική",
         "jump_first_message": "Μετάβαση στο πρώτο μήνυμα",
@@ -1095,17 +1141,20 @@
         "next_unread_room": "Επόμενο μη αναγνωσμένο δωμάτιο ή ΑΜ",
         "number": "[αριθμός]",
         "open_user_settings": "Άνοιγμα ρυθμίσεων χρήστη",
+        "page_down": "Σελίδα προς τα κάτω",
+        "page_up": "Σελίδα προς τα πάνω",
         "prev_room": "Προηγούμενο δωμάτιο ή ΑΜ",
         "prev_unread_room": "Προηγούμενο μη αναγνωσμένο δωμάτιο ή ΑΜ",
-        "room_list_collapse_section": "Σύμπτυξη ενότητας λίστας δωματίων",
+        "room_list_collapse_section": "Σύμπτυξη ενότητας λίστας αιθουσών",
         "room_list_expand_section": "Ανάπτυξη ενότητας λίστας δωματίων",
         "room_list_navigate_down": "Πλοήγηση προς τα κάτω στη λίστα δωματίων",
         "room_list_navigate_up": "Πλοήγηση προς τα πάνω στη λίστα δωματίων",
         "room_list_select_room": "Επιλέξτε δωμάτιο από τη λίστα δωματίων",
-        "scroll_down_timeline": "Κύλιση προς τα κάτω στη γραμμή χρόνου",
-        "scroll_up_timeline": "Κύλιση προς τα πάνω στη γραμμή χρόνου",
+        "scroll_down_timeline": "Κύλιση προς τα κάτω στο χρονολόγιο",
+        "scroll_up_timeline": "Κύλιση προς τα πάνω στο χρονολόγιο",
         "search": "Αναζήτηση (πρέπει να είναι ενεργοποιημένη)",
         "send_sticker": "Αποστολή αυτοκόλλητου",
+        "shift": "Shift",
         "space": "Χώρος",
         "switch_to_space": "Εναλλαγή σε χώρο με αριθμό",
         "toggle_hidden_events": "Εναλλαγή ορατότητας κρυφού συμβάντος",
@@ -1117,6 +1166,8 @@
         "upload_file": "Μεταφόρτωση αρχείου"
     },
     "labs": {
+        "allow_screen_share_only_mode": "Να επιτρέπεται μόνο η λειτουργία κοινής χρήσης οθόνης",
+        "ask_to_join": "Ενεργοποίηση αίτησης συμμετοχής",
         "automatic_debug_logs": "Αυτόματη αποστολή αρχείων καταγραφής εντοπισμού σφαλμάτων για οποιοδήποτε σφάλμα",
         "automatic_debug_logs_decryption": "Αυτόματη αποστολή αρχείων καταγραφής εντοπισμού σφαλμάτων για σφάλματα αποκρυπτογράφησης",
         "automatic_debug_logs_key_backup": "Αυτόματη αποστολή αρχείων καταγραφής εντοπισμού σφαλμάτων όταν η δημιουργία αντίγραφου κλειδιού ασφαλείας δεν λειτουργεί",
@@ -1128,7 +1179,12 @@
         "bridge_state_manager": "Αυτή τη γέφυρα τη διαχειρίζεται ο <user />.",
         "bridge_state_workspace": "Χώρος εργασίας: <networkLink/>",
         "click_for_info": "Κλικ για περισσότερες πληροφορίες",
+        "currently_experimental": "Προς το παρόν πειραματικό.",
         "custom_themes": "Υποστήριξη προσθήκης προσαρμοσμένων θεμάτων",
+        "dynamic_room_predecessors": "Δυναμικοί προκάτοχοι δωματίων",
+        "element_call_video_rooms": "Δωμάτια βίντεο κλήσεων Element",
+        "feature_wysiwyg_composer_description": "Χρήση εμπλουτισμένου κειμένου αντί για Markdown στον συντάκτη μηνυμάτων.",
+        "group_calls": "Νέα εμπειρία ομαδικής κλήσης",
         "group_developer": "Προγραμματιστής",
         "group_encryption": "Κρυπτογράφηση",
         "group_experimental": "Πειραματικό",
@@ -1141,18 +1197,31 @@
         "group_threads": "Νήματα",
         "group_voip": "Φωνή & Βίντεο",
         "group_widgets": "Μικροεφαρμογές",
+        "hidebold": "Απόκρυψη κουκκίδας ειδοποίησης (εμφάνιση μόνο σημάτων αριθμών)",
+        "html_topic": "Εμφάνιση HTML αναπαράστασης θεμάτων δωματίου",
         "join_beta": "Συμμετοχή στη beta",
         "jump_to_date": "Μετάβαση στην ημερομηνία (προσθέτει /μετάβαση στην ημερομηνία και μετάβαση στις κεφαλίδες ημερομηνίας)",
+        "jump_to_date_msc_support": "Απαιτεί ο διακομιστής σου να υποστηρίζει το MSC3030",
         "latex_maths": "Εμφανίστε μαθηματικά LaTeX σε μηνύματα",
         "leave_beta": "Αποχώρηση από τη beta",
+        "location_share_live": "Κοινή χρήση τρέχουσας τοποθεσίας",
+        "location_share_live_description": "Προσωρινή υλοποίηση. Οι τοποθεσίες παραμένουν στο ιστορικό δωματίων.",
+        "mjolnir": "Νέοι τρόποι να αγνοείς τους ανθρώπους",
         "msc3531_hide_messages_pending_moderation": "Επιτρέψτε στους επόπτες να αποκρύψουν μηνύματα που βρίσκονται σε εκκρεμότητα.",
+        "notification_settings": "Νέες Ρυθμίσεις Ειδοποιήσεων",
+        "report_to_moderators": "Αναφορά στους συντονιστές",
+        "sliding_sync": "Λειτουργία Sliding Sync",
+        "sliding_sync_description": "Υπό ενεργή ανάπτυξη, δεν μπορεί να απενεργοποιηθεί.",
+        "under_active_development": "Υπό ενεργή ανάπτυξη.",
         "video_rooms": "Δωμάτια βίντεο",
         "video_rooms_a_new_way_to_chat": "Ένας νέος τρόπος για συνομιλία μέσω φωνής και βίντεο με το %(brand)s.",
+        "video_rooms_always_on_voip_channels": "Οι αίθουσες βίντεο είναι πάντα-ενεργά κανάλια VoIP ενσωματωμένα σε ένα δωμάτιο στο %(brand)s",
         "video_rooms_beta": "Οι αίθουσες βίντεο είναι μια λειτουργία beta",
-        "video_rooms_faq1_answer": "Χρησιμοποιήστε το κουμπί “+” στην ενότητα δωματίων του αριστερού πάνελ.",
+        "video_rooms_faq1_answer": "Χρησιμοποιήστε το κουμπί “+” στην ενότητα αιθουσών του αριστερού πάνελ.",
         "video_rooms_faq1_question": "Πώς μπορώ να δημιουργήσω ένα δωμάτιο βίντεο;",
-        "video_rooms_faq2_answer": "Ναι, το χρονοδιάγραμμα της συνομιλίας εμφανίζεται δίπλα στο βίντεο.",
-        "video_rooms_faq2_question": "Μπορώ να χρησιμοποιήσω τη συνομιλία κειμένου παράλληλα με τη βιντεοκλήση;"
+        "video_rooms_faq2_answer": "Ναι, το χρονολόγιο της συνομιλίας εμφανίζεται δίπλα στο βίντεο.",
+        "video_rooms_faq2_question": "Μπορώ να χρησιμοποιήσω τη συνομιλία κειμένου παράλληλα με τη βιντεοκλήση;",
+        "wysiwyg_composer": "Συντάκτης εμπλουτισμένου κειμένου"
     },
     "labs_mjolnir": {
         "advanced_warning": "⚠ Αυτές οι ρυθμίσεις προορίζονται για προχωρημένους χρήστες.",
@@ -1169,7 +1238,7 @@
         "lists_description_1": "Η εγγραφή σε μια λίστα απαγορεύσων θα σας κάνει να εγγραφείτε σε αυτήν!",
         "lists_description_2": "Εάν αυτό δεν είναι αυτό που θέλετε, χρησιμοποιήστε ένα διαφορετικό εργαλείο για να αγνοήσετε τους χρήστες.",
         "lists_heading": "Εγγεγραμμένες λίστες",
-        "lists_new_label": "Ταυτότητα δωματίου ή διεύθυνση της λίστας απαγορευσων",
+        "lists_new_label": "Ταυτότητα αίθουσας ή διεύθυνση της λίστας απαγορεύσεων",
         "no_lists": "Δεν είστε εγγεγραμμένοι σε καμία λίστα",
         "personal_empty": "Δεν έχετε αγνοήσει κανέναν.",
         "personal_heading": "Προσωπική λίστα απαγορεύσεων",
@@ -1354,11 +1423,6 @@
         "ongoing": "Αφαίρεση…",
         "reason_label": "Αιτία (προαιρετικό)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Είστε σίγουροι ότι θέλετε να απορρίψετε την πρόσκληση;",
-        "failed": "Δεν ήταν δυνατή η απόρριψη της πρόσκλησης",
-        "title": "Απόρριψη πρόσκλησης"
-    },
     "report_content": {
         "description": "Η αναφορά αυτού του μηνύματος θα στείλει το μοναδικό «αναγνωριστικό συμβάντος» στον διαχειριστή του διακομιστή σας. Εάν τα μηνύματα σε αυτό το δωμάτιο είναι κρυπτογραφημένα, ο διαχειριστής του διακομιστή σας δε θα μπορεί να διαβάσει το κείμενο του μηνύματος ή να προβάλει αρχεία και εικόνες.",
         "disagree": "Διαφωνώ",
@@ -1403,14 +1467,16 @@
         "pinned_messages": {
             "limits": {
                 "other": "Μπορείτε να καρφιτσώσετε μόνο έως %(count)s μικρεοεφαρμογές"
-            }
+            },
+            "view": "Εμφάνιση στο χρονολόγιο"
         },
         "pinned_messages_button": "Καρφιτσωμένο",
         "poll": {
             "final_result": {
                 "one": "Τελικό αποτέλεσμα με βάση %(count)s ψήφο",
                 "other": "Τελικό αποτέλεσμα με βάση %(count)s ψήφους"
-            }
+            },
+            "view_in_timeline": "Εμφάνιση δημοσκόπισης στο χρονολόγιο"
         },
         "thread_list": {
             "context_menu_label": "Επιλογές νήματος συζήτησης"
@@ -1469,13 +1535,13 @@
         "inaccessible": "Αυτό το δωμάτιο ή ο χώρος δεν είναι προσβάσιμος αυτήν τη στιγμή.",
         "inaccessible_name": "Το %(roomName)s δεν είναι προσβάσιμο αυτή τη στιγμή.",
         "inaccessible_subtitle_1": "Δοκιμάστε ξανά αργότερα ή ζητήστε από έναν διαχειριστή δωματίου ή χώρου να ελέγξει εάν έχετε πρόσβαση.",
-        "inaccessible_subtitle_2": "Το %(errcode)s επιστράφηκε κατά την προσπάθεια πρόσβασης στο δωμάτιο ή στο χώρο. Εάν πιστεύετε ότι βλέπετε αυτό το μήνυμα κατά λάθος, <issueLink>υποβάλετε μια αναφορά σφάλματος</issueLink>.",
+        "inaccessible_subtitle_2": "Το %(errcode)s επιστράφηκε κατά την προσπάθεια πρόσβασης στην αίθουσα ή στο χώρο. Εάν πιστεύετε ότι βλέπετε αυτό το μήνυμα κατά λάθος, <issueLink>υποβάλετε μια αναφορά σφάλματος</issueLink>.",
         "intro": {
             "dm_caption": "Μόνο οι δυο σας συμμετέχετε σε αυτήν τη συνομιλία, εκτός εάν κάποιος από εσάς προσκαλέσει κάποιον να συμμετάσχει.",
             "enable_encryption_prompt": "Ενεργοποιήστε την κρυπτογράφηση στις ρυθμίσεις.",
             "no_avatar_label": "Προσθέστε μια φωτογραφία, ώστε οι χρήστες να μπορούν εύκολα να εντοπίσουν το δωμάτιό σας.",
             "no_topic": "<a>Προσθέστε ένα θέμα</a> για να βοηθήσετε τους χρήστες να γνωρίζουν περί τίνος πρόκειται.",
-            "private_unencrypted_warning": "Τα προσωπικά σας μηνύματα είναι συνήθως κρυπτογραφημένα, αλλά αυτό το δωμάτιο δεν είναι. Συνήθως αυτό οφείλεται σε μια μη υποστηριζόμενη συσκευή ή μέθοδο που χρησιμοποιείται, όπως προσκλήσεις μέσω email.",
+            "private_unencrypted_warning": "Τα προσωπικά σας μηνύματα είναι συνήθως κρυπτογραφημένα, αλλά αυτή η αίθουσα δεν είναι. Συνήθως αυτό οφείλεται σε μια μη υποστηριζόμενη συσκευή ή μέθοδο που χρησιμοποιείται, όπως προσκλήσεις μέσω email.",
             "room_invite": "Προσκαλέστε μόνο σε αυτό το δωμάτιο",
             "start_of_dm_history": "Αυτή είναι η αρχή του ιστορικού των άμεσων μηνυμάτων σας με <displayName/>.",
             "start_of_room": "Αυτή είναι η αρχή του <roomName/>.",
@@ -1486,7 +1552,6 @@
             "you_created": "Δημιουργήσατε αυτό το δωμάτιο."
         },
         "invite_email_mismatch_suggestion": "Μοιραστείτε αυτό το μήνυμα ηλεκτρονικού ταχυδρομείου στις Ρυθμίσεις για να λαμβάνετε προσκλήσεις απευθείας σε %(brand)s.",
-        "invite_reject_ignore": "Απόρριψη & Παράβλεψη χρήστη",
         "invite_sent_to_email": "Αυτή η πρόσκληση στάλθηκε στο %(email)s",
         "invite_sent_to_email_room": "Αυτή η πρόσκληση στο %(roomName)s στάλθηκε στο %(email)s",
         "invite_subtitle": "Ο <userName/> σας προσκάλεσε",
@@ -1507,7 +1572,7 @@
         "kick_reason": "Αιτία: %(reason)s",
         "kicked_by": "Αφαιρεθήκατε από %(memberName)s",
         "kicked_from_room_by": "Αφαιρεθήκατε από το %(roomName)s από τον %(memberName)s",
-        "leave_error_title": "Σφάλμα στην έξοδο από το δωμάτιο",
+        "leave_error_title": "Σφάλμα κατά την έξοδο από την αίθουσα",
         "leave_server_notices_description": "Αυτό το δωμάτιο χρησιμοποιείται για σημαντικά μηνύματα από τον κεντρικό διακομιστή, επομένως δεν μπορείτε να το αφήσετε.",
         "leave_server_notices_title": "Δεν είναι δυνατή η έξοδος από την αίθουσα ειδοποιήσεων διακομιστή",
         "leave_unexpected_error": "Μη αναμενόμενο σφάλμα διακομιστή κατά την προσπάθεια εξόδου από το δωμάτιο",
@@ -1519,6 +1584,9 @@
         "not_found_title": "Αυτό το δωμάτιο ή ο χώρος δεν υπάρχει.",
         "not_found_title_name": "Το %(roomName)s δεν υπάρχει.",
         "peek_join_prompt": "Κάνετε προεπισκόπηση στο %(roomName)s. Θέλετε να συμμετάσχετε;",
+        "pinned_message_banner": {
+            "go_to_message": "Εμφάνιση καρφιτσωμένου μηνύματος στο χρονολόγιο."
+        },
         "rejoin_button": "Επανασύνδεση",
         "status_bar": {
             "delete_all": "Διαγραφή όλων",
@@ -1536,7 +1604,7 @@
             "other": "Έχετε %(count)s μη αναγνωσμένες ειδοποιήσεις σε προηγούμενη έκδοση αυτού του δωματίου."
         },
         "upgrade_error_description": "Επανελέγξτε ότι ο διακομιστής σας υποστηρίζει την έκδοση δωματίου που επιλέξατε και προσπαθήστε ξανά.",
-        "upgrade_error_title": "Σφάλμα αναβάθμισης δωματίου",
+        "upgrade_error_title": "Σφάλμα αναβάθμισης αίθουσας",
         "upgrade_warning_bar": "Η αναβάθμιση αυτού του δωματίου θα τερματίσει το δωμάτιο και θα δημιουργήσει ένα αναβαθμισμένο δωμάτιο με το ίδιο όνομα.",
         "upgrade_warning_bar_admins": "Μόνο οι διαχειριστές δωματίων θα βλέπουν αυτήν την προειδοποίηση",
         "upgrade_warning_bar_unstable": "Αυτό το δωμάτιο τρέχει την έκδοση <roomVersion />, την οποία ο κεντρικός διακομιστής έχει επισημάνει ως <i>ασταθής</i>.",
@@ -1655,7 +1723,7 @@
             "local_aliases_explainer_room": "Ορίστε διευθύνσεις για αυτό το δωμάτιο, ώστε οι χρήστες να μπορούν να το βρίσκουν μέσω του κεντρικού σας διακομιστή (%(localDomain)s)",
             "local_aliases_explainer_space": "Ορίστε διευθύνσεις για αυτόν τον χώρο, ώστε οι χρήστες να μπορούν να τον βρίσκουν μέσω του κεντρικού σας διακομιστή (%(localDomain)s)",
             "local_aliases_section": "Τοπική Διεύθυνση",
-            "name_field_label": "Όνομα Δωματίου",
+            "name_field_label": "Όνομα Αίθουσας",
             "new_alias_placeholder": "Νέα δημοσιευμένη διεύθυνση (π.χ. #alias:server)",
             "no_aliases_room": "Αυτό το δωμάτιο δεν έχει τοπικές διευθύνσεις",
             "no_aliases_space": "Αυτός ο χώρος δεν έχει τοπικές διευθύνσεις",
@@ -1694,6 +1762,8 @@
             "events_default": "Στείλτε μηνύματα",
             "invite": "Προσκαλέστε χρήστες",
             "kick": "Καταργήστε χρήστες",
+            "m.call": "Έναρξη κλήσεων %(brand)s",
+            "m.call.member": "Συμμετοχή σε κλήσεις %(brand)s",
             "m.reaction": "Στείλτε αντιδράσεις",
             "m.room.avatar": "Αλλαγή εικόνας δωματίου",
             "m.room.avatar_space": "Αλλαγή εικόνας Χώρου",
@@ -1736,7 +1806,7 @@
             "encryption_permanent": "Αφού ενεργοποιηθεί, η κρυπτογράφηση δεν μπορεί να απενεργοποιηθεί.",
             "error_join_rule_change_title": "Αποτυχία ενημέρωσης των κανόνων συμμετοχής",
             "error_join_rule_change_unknown": "Άγνωστο σφάλμα",
-            "guest_access_warning": "Τα άτομα με υποστηριζόμενους πελάτες θα μπορούν να εγγραφούν στο δωμάτιο χωρίς να έχουν εγγεγραμμένο λογαριασμό.",
+            "guest_access_warning": "Τα άτομα με υποστηριζόμενες εφαρμογές θα μπορούν να συμμετάσχουν στην αίθουσα χωρίς να έχουν εγγεγραμμένο λογαριασμό.",
             "history_visibility_invited": "Μόνο μέλη (από τη στιγμή που προσκλήθηκαν)",
             "history_visibility_joined": "Μόνο μέλη (από τη στιγμή που έγιναν μέλη)",
             "history_visibility_legend": "Ποιος μπορεί να διαβάσει το ιστορικό;",
@@ -1756,7 +1826,7 @@
             "join_rule_restricted_dialog_empty_warning": "Καταργείτε όλους τους χώρους. Η πρόσβαση θα είναι προεπιλεγμένη μόνο για πρόσκληση",
             "join_rule_restricted_dialog_filter_placeholder": "Αναζήτηση χώρων",
             "join_rule_restricted_dialog_heading_other": "Άλλοι χώροι ή δωμάτια που ίσως δε γνωρίζετε",
-            "join_rule_restricted_dialog_heading_room": "Χώροι που γνωρίζετε ότι περιέχουν αυτό το δωμάτιο",
+            "join_rule_restricted_dialog_heading_room": "Χώροι που γνωρίζετε ότι περιέχουν αυτήν την αίθουσα",
             "join_rule_restricted_dialog_heading_space": "Χώροι που γνωρίζετε ότι περιέχουν αυτόν το χώρο",
             "join_rule_restricted_dialog_heading_unknown": "Πιθανότατα αυτά είναι μέρος στα οποία συμμετέχουν και άλλοι διαχειριστές δωματίου.",
             "join_rule_restricted_dialog_title": "Επιλέξτε χώρους",
@@ -1770,7 +1840,7 @@
             },
             "join_rule_restricted_upgrade_description": "Αυτή η αναβάθμιση θα επιτρέψει σε μέλη επιλεγμένων Χώρων πρόσβαση σε αυτό το δωμάτιο χωρίς πρόσκληση.",
             "join_rule_restricted_upgrade_warning": "Αυτό το δωμάτιο βρίσκεται σε ορισμένους Χώρους στους οποίους δεν είστε διαχειριστής. Σε αυτούς τους Χώρους, το παλιό δωμάτιο θα εξακολουθεί να εμφανίζεται, αλλά τα άτομα θα κληθούν να συμμετάσχουν στο νέο.",
-            "join_rule_upgrade_awaiting_room": "Φόρτωση νέου δωματίου",
+            "join_rule_upgrade_awaiting_room": "Φόρτωση νέας αίθουσας",
             "join_rule_upgrade_required": "Απαιτείται αναβάθμιση",
             "join_rule_upgrade_sending_invites": {
                 "one": "Αποστολή πρόσκλησης...",
@@ -1810,7 +1880,7 @@
         "error_missing_user_id_request": "Λείπει το user_id στο αίτημα",
         "error_permission": "Δεν έχετε την άδεια να το κάνετε αυτό σε αυτό το δωμάτιο.",
         "error_power_level_invalid": "Το επίπεδο δύναμης πρέπει να είναι ένας θετικός ακέραιος.",
-        "error_room_not_visible": "Το δωμάτιο %(roomId)s δεν είναι ορατό",
+        "error_room_not_visible": "Η αίθουσα %(roomId)s δεν είναι ορατή",
         "error_room_unknown": "Αυτό το δωμάτιο δεν αναγνωρίζεται.",
         "error_send_request": "Δεν ήταν δυνατή η αποστολή αιτήματος."
     },
@@ -1851,14 +1921,14 @@
     },
     "settings": {
         "all_rooms_home": "Εμφάνιση όλων των δωματίων στην Αρχική",
-        "all_rooms_home_description": "Όλα τα δωμάτια στα οποία συμμετέχετε θα εμφανίζονται στην Αρχική σελίδα.",
+        "all_rooms_home_description": "Όλες οι αίθουσες στις οποίες συμμετέχετε θα εμφανίζονται στην Αρχική σελίδα.",
         "always_show_message_timestamps": "Εμφάνιση πάντα της ένδειξης ώρας στα μηνύματα",
         "appearance": {
             "custom_font": "Χρήση μιας γραμματοσειρά συστήματος",
             "custom_font_description": "Ορίστε το όνομα μιας γραμματοσειράς που είναι εγκατεστημένη στο σύστημά σας και o %(brand)s θα προσπαθήσει να τη χρησιμοποιήσει.",
             "custom_font_name": "Όνομα γραμματοσειράς συστήματος",
             "custom_font_size": "Χρησιμοποιήστε προσαρμοσμένο μέγεθος",
-            "custom_theme_error_downloading": "Σφάλμα κατά τη λήψη πληροφοριών θέματος.",
+            "custom_theme_error_downloading": "Σφάλμα κατά τη λήψη θέματος.",
             "custom_theme_invalid": "Μη έγκυρο σχήμα θέματος.",
             "font_size": "Μέγεθος γραμματοσειράς",
             "image_size_default": "Προεπιλογή",
@@ -1866,7 +1936,7 @@
             "layout_bubbles": "Συννεφάκια μηνυμάτων",
             "layout_irc": "IRC (Πειραματικό)",
             "match_system_theme": "Αντιστοίχιση θέματος συστήματος",
-            "timeline_image_size": "Μέγεθος εικόνας στη γραμμή χρόνου"
+            "timeline_image_size": "Μέγεθος εικόνας στο χρονολόγιο"
         },
         "automatic_language_detection_syntax_highlight": "Ενεργοποίηση αυτόματης ανίχνευσης γλώσσας για επισήμανση σύνταξης",
         "autoplay_gifs": "Αυτόματη αναπαραγωγή GIFs",
@@ -1874,8 +1944,10 @@
         "big_emoji": "Ενεργοποίηση μεγάλων emoji στη συνομιλία",
         "code_block_expand_default": "Αναπτύξτε τα μπλοκ κώδικα από προεπιλογή",
         "code_block_line_numbers": "Εμφάνιση αριθμών γραμμής σε μπλοκ κώδικα",
+        "disable_historical_profile": "Εμφάνιση τρέχουσας εικόνας προφίλ και ονόματος για χρήστες στο ιστορικό μηνυμάτων",
         "emoji_autocomplete": "Ενεργοποιήστε τις προτάσεις Emoji κατά την πληκτρολόγηση",
         "enable_markdown": "Ενεργοποίηση Markdown",
+        "enable_markdown_description": "Έναρξη μηνυμάτων με <code>/plain</code> για αποστολή χωρίς markdown.",
         "general": {
             "account_management_section": "Διαχείριση λογαριασμών",
             "account_section": "Λογαριασμός",
@@ -1887,6 +1959,7 @@
             "add_msisdn_confirm_sso_button": "Επιβεβαιώστε την προσθήκη αυτού του αριθμού τηλεφώνου με την χρήση Single Sign On για να επικυρώσετε την ταυτότητα σας.",
             "add_msisdn_dialog_title": "Προσθήκη Τηλεφωνικού Αριθμού",
             "add_msisdn_instructions": "Ένα μήνυμα sms έχει σταλεί στο +%(msisdn)s. Παρακαλώ εισαγάγετε τον κωδικό επαλήθευσης που περιέχει.",
+            "add_msisdn_misconfigured": "Η ροή προσθήκης / δέσμευσης με MSISDN δεν έχει ρυθμιστεί σωστά",
             "confirm_adding_email_body": "Πιέστε το κουμπί από κάτω για να επιβεβαιώσετε την προσθήκη της διεύθυνσης ηλ. ταχυδρομείου.",
             "confirm_adding_email_title": "Επιβεβαιώστε την προσθήκη διεύθυνσης ηλ. ταχυδρομείου",
             "deactivate_confirm_body": "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε τον λογαριασμό σας; Αυτό είναι μη αναστρέψιμο.",
@@ -1896,12 +1969,15 @@
             "deactivate_confirm_content_2": "Δεν θα μπορείτε πλέον να συνδεθείτε",
             "deactivate_confirm_content_3": "Κανείς δε θα μπορεί να επαναχρησιμοποιήσει το όνομα χρήστη σας (MXID), συμπεριλαμβανομένου εσάς: αυτό το όνομα χρήστη θα παραμείνει μη διαθέσιμο",
             "deactivate_confirm_content_4": "Θα αποχωρήσετε από όλα τα δωμάτια και τις συνομιλίες σας",
+            "deactivate_confirm_content_5": "Θα αφαιρεθείς από τον διακομιστή ταυτότητας: οι φίλοι σου δεν θα μπορούν πλέον να σε βρίσκουν με το email ή τον αριθμό τηλεφώνου σου",
+            "deactivate_confirm_content_6": "Τα παλιά σας μηνύματα θα εξακολουθούν να είναι ορατά σε άτομα που τα έλαβαν, όπως τα email που στείλατε στο παρελθόν. Θα θέλατε να αποκρύψετε τα απεσταλμένα σας μηνύματα από άτομα που συμμετέχουν στις αίθουσες στο μέλλον;",
             "deactivate_confirm_continue": "Επιβεβαίωση απενεργοποίησης λογαριασμού",
+            "deactivate_confirm_erase_label": "Απόκρυψη των μηνυμάτων μου από νέους συμμετέχοντες",
             "deactivate_section": "Απενεργοποίηση λογαριασμού",
             "deactivate_warning": "Η απενεργοποίηση του λογαριασμού σας είναι μια μόνιμη ενέργεια — να είστε προσεκτικοί!",
-            "discovery_email_empty": "Οι επιλογές εντοπισμού θα εμφανιστούν μόλις προσθέσετε ένα email παραπάνω.",
+            "discovery_email_empty": "Οι επιλογές εντοπισμού θα εμφανιστούν μόλις προσθέσεις ένα email παραπάνω.",
             "discovery_email_verification_instructions": "Επαληθεύστε τον σύνδεσμο στα εισερχόμενα σας",
-            "discovery_msisdn_empty": "Οι επιλογές εντοπισμού θα εμφανιστούν μόλις προσθέσετε έναν αριθμό τηλεφώνου παραπάνω.",
+            "discovery_msisdn_empty": "Οι επιλογές ανακάλυψης θα εμφανιστούν μόλις προσθέσεις έναν αριθμό τηλεφώνου.",
             "discovery_needs_terms": "Αποδεχτείτε τους Όρους χρήσης του διακομιστή ταυτότητας (%(serverName)s), ώστε να μπορείτε να είστε ανιχνεύσιμοι μέσω της διεύθυνσης ηλεκτρονικού ταχυδρομείου ή του αριθμού τηλεφώνου.",
             "email_address_in_use": "Η διεύθυνση ηλ. αλληλογραφίας χρησιμοποιείται ήδη",
             "email_address_label": "Διεύθυνση Email",
@@ -1917,27 +1993,34 @@
             "error_invalid_email_detail": "Δεν μοιάζει με μια έγκυρη διεύθυνση ηλεκτρονικής αλληλογραφίας",
             "error_msisdn_verification": "Αδυναμία επαλήθευσης του αριθμού τηλεφώνου.",
             "error_password_change_403": "Δεν ήταν δυνατή η αλλαγή του κωδικού πρόσβασης. Είναι σωστός ο κωδικός πρόσβασης;",
+            "error_password_change_http": "%(errorMessage)s (Κατάσταση HTTP %(httpStatus)s)",
+            "error_password_change_title": "Σφάλμα αλλαγής κωδικού πρόσβασης",
+            "error_password_change_unknown": "Άγνωστο σφάλμα αλλαγής κωδικού πρόσβασης (%(stringifiedError)s)",
             "error_remove_3pid": "Αδυναμία αφαίρεσης πληροφοριών επαφής",
             "error_revoke_email_discovery": "Δεν είναι δυνατή η ανάκληση της κοινής χρήσης για τη διεύθυνση ηλεκτρονικού ταχυδρομείου",
             "error_revoke_msisdn_discovery": "Αδυναμία ανάκληση της κοινής χρήσης για τον αριθμό τηλεφώνου",
             "error_share_email_discovery": "Δεν είναι δυνατή η κοινή χρήση της διεύθυνσης email",
             "error_share_msisdn_discovery": "Αδυναμία κοινής χρήσης του αριθμού τηλεφώνου",
-            "language_section": "Γλώσσα και περιοχή",
+            "identity_server_no_token": "Δεν βρέθηκε διακριτικό πρόσβασης ταυτότητας",
+            "identity_server_not_set": "Ο διακομιστής ταυτότητας δεν έχει οριστεί",
+            "language_section": "Γλώσσα",
             "msisdn_in_use": "Αυτός ο αριθμός τηλεφώνου είναι ήδη σε χρήση",
             "msisdn_label": "Αριθμός Τηλεφώνου",
             "msisdn_verification_field_label": "Κωδικός επαλήθευσης",
             "msisdn_verification_instructions": "Εισαγάγετε τον κωδικό επαλήθευσης που εστάλη μέσω μηνύματος sms.",
             "msisdns_heading": "Τηλεφωνικοί αριθμοί",
+            "oidc_manage_button": "Διαχείριση λογαριασμού",
+            "password_change_section": "Ορίστε έναν νέο κωδικό πρόσβασης λογαριασμού...",
             "password_change_success": "Ο κωδικός πρόσβασης σας άλλαξε με επιτυχία.",
             "remove_email_prompt": "Κατάργηση %(email)s;",
-            "remove_msisdn_prompt": "Κατάργηση %(phone)s;"
+            "remove_msisdn_prompt": "Κατάργηση %(phone)s;",
+            "spell_check_locale_placeholder": "Επιλογή τοπικών ρυθμίσεων"
         },
-        "image_thumbnails": "Εμφάνιση προεπισκοπήσεων/μικρογραφιών για εικόνες",
         "inline_url_previews_default": "Ενεργοποιήστε τις ενσωματωμένες προεπισκοπήσεις URL από προεπιλογή",
         "inline_url_previews_room": "Ενεργοποιήστε τις προεπισκοπήσεις URL από προεπιλογή για τους συμμετέχοντες σε αυτό το δωμάτιο",
         "inline_url_previews_room_account": "Ενεργοποίηση προεπισκόπισης URL για αυτό το δωμάτιο (επηρεάζει μόνο εσάς)",
         "insert_trailing_colon_mentions": "Εισαγάγετε άνω και κάτω τελεία μετά την αναφορά του χρήστη στην αρχή ενός μηνύματος",
-        "jump_to_bottom_on_send": "Μεταβείτε στο τέλος του χρονοδιαγράμματος όταν στέλνετε ένα μήνυμα",
+        "jump_to_bottom_on_send": "Μεταβείτε στο τέλος του χρονολογίου όταν στέλνετε ένα μήνυμα",
         "key_backup": {
             "backup_in_progress": "Δημιουργούνται αντίγραφα ασφαλείας των κλειδιών σας (το πρώτο αντίγραφο ασφαλείας μπορεί να διαρκέσει μερικά λεπτά).",
             "backup_success": "Επιτυχία!",
@@ -1981,33 +2064,66 @@
         "keyboard": {
             "title": "Πληκτρολόγιο"
         },
+        "media_preview": {
+            "media_preview_label": "Εμφάνιση μέσων στο χρονολόγιο"
+        },
         "notifications": {
+            "default_setting_description": "Αυτή η ρύθμιση θα εφαρμοστεί από προεπιλογή σε όλα τα δωμάτιά σου.",
+            "default_setting_section": "Θέλω να ειδοποιούμαι για (Προεπιλεγμένη Ρύθμιση)",
+            "desktop_notification_message_preview": "Εμφάνιση προεπισκόπησης μηνύματος στην ειδοποίηση επιφάνειας εργασίας",
+            "email_description": "Λάβε μια περίληψη των αναπάντητων ειδοποιήσεων μέσω email",
+            "email_section": "Σύνοψη email",
+            "email_select": "Επέλεξε σε ποια email θες να στείλεις περιλήψεις. Διαχειρίσου τα email σου στα <button>Γενικά</button>.",
             "enable_audible_notifications_session": "Ενεργοποιήστε τις ηχητικές ειδοποιήσεις για αυτήν τη συνεδρία",
             "enable_desktop_notifications_session": "Ενεργοποιήστε τις ειδοποιήσεις στον υπολογιστή για αυτήν τη συνεδρία",
             "enable_email_notifications": "Ενεργοποίηση ειδοποιήσεων email για %(email)s",
+            "enable_notifications_account": "Ενεργοποίηση ειδοποιήσεων για αυτόν τον λογαριασμό",
+            "enable_notifications_account_detail": "Απενεργοποίησε για να απενεργοποιήσεις τις ειδοποιήσεις σε όλες τις συσκευές και συνεδρίες σου",
+            "enable_notifications_device": "Ενεργοποίηση ειδοποιήσεων για αυτήν τη συσκευή",
             "error_loading": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των ρυθμίσεων ειδοποιήσεων σας.",
             "error_permissions_denied": "Το %(brand)s δεν έχει δικαιώματα για αποστολή ειδοποιήσεων - παρακαλούμε ελέγξτε τις ρυθμίσεις του περιηγητή σας",
             "error_permissions_missing": "Δεν δόθηκαν δικαιώματα αποστολής ειδοποιήσεων στο %(brand)s - παρακαλούμε προσπαθήστε ξανά",
             "error_saving": "Σφάλμα κατά την αποθήκευση των προτιμήσεων ειδοποιήσεων",
             "error_saving_detail": "Παρουσιάστηκε σφάλμα κατά την αποθήκευση των προτιμήσεων ειδοποίησης.",
             "error_title": "Αδυναμία ενεργοποίησης των ειδοποιήσεων",
+            "error_updating": "Παρουσιάστηκε σφάλμα κατά την ενημέρωση των προτιμήσεων ειδοποίησης. Προσπάθησε να αλλάξεις ξανά την επιλογή σου.",
+            "invites": "Προσκλήθηκε σ' ένα δωμάτιο",
+            "keywords": "Εμφάνιση ενός σήματος <badge/> όταν χρησιμοποιούνται λέξεις-κλειδιά σε ένα δωμάτιο.",
+            "keywords_prompt": "Εισήγαγε λέξεις-κλειδιά εδώ ή χρησιμοποίησε για παραλλαγές ορθογραφίας ή ψευδώνυμα",
+            "labs_notice_prompt": "<strong>Ενημέρωση:</strong> Απλοποιήσαμε τις Ρυθμίσεις ειδοποιήσεων για να διευκολύνουμε την εύρεση επιλογών. Ορισμένες προσαρμοσμένες ρυθμίσεις που έχεις επιλέξει στο παρελθόν δεν εμφανίζονται εδώ, αλλά εξακολουθούν να είναι ενεργές. Εάν προχωρήσεις, ορισμένες από τις ρυθμίσεις σου ενδέχεται να αλλάξουν. <a>Μάθε περισσότερα</a>",
+            "mentions_keywords": "Επισημάνσεις και Λέξεις-κλειδιά",
+            "mentions_keywords_only": "Μόνο επισημάνσεις και λέξεις-κλειδιά",
             "messages_containing_keywords": "Μηνύματα που περιέχουν λέξεις-κλειδιά",
             "noisy": "Δυνατά",
+            "notices": "Μηνύματα που αποστέλλονται από bots",
+            "notify_at_room": "Ειδοποίησέ όταν κάποιος αναφέρει χρησιμοποιώντας @room",
+            "notify_keyword": "Ειδοποίηση όταν κάποιος χρησιμοποιεί μια λέξη-κλειδί",
+            "notify_mention": "Ειδοποίηση όταν κάποιος επισημαίνει με @displayname ή %(mxid)s",
+            "other_section": "Άλλα πράγματα που πιστεύουμε ότι μπορεί να σε ενδιαφέρουν:",
+            "people_mentions_keywords": "Άτομα, Επισημάνσεις και Λέξεις-κλειδιά",
+            "play_sound_for_description": "Εφαρμόζεται από προεπιλογή σε όλα τα δωμάτια σε όλες τις συσκευές.",
+            "play_sound_for_section": "Αναπαραγωγή ήχου για",
             "push_targets": "Στόχοι ειδοποιήσεων",
+            "quick_actions_mark_all_read": "Επισήμανση όλων των μηνυμάτων ως αναγνωσμένων",
+            "quick_actions_reset": "Επαναφορά στις προεπιλεγμένες ρυθμίσεις",
+            "quick_actions_section": "Γρήγορες Ενέργειες",
+            "room_activity": "Νέα δραστηριότητα δωματίου, αναβαθμίσεις και μηνύματα κατάστασης",
             "rule_call": "Πρόσκληση σε κλήση",
             "rule_contains_display_name": "Μηνύματα που περιέχουν το όνομα μου",
             "rule_contains_user_name": "Μηνύματα που περιέχουν το όνομα χρήστη μου",
             "rule_encrypted": "Κρυπτογραφημένα μηνύματα σε ομαδικές συνομιλίες",
             "rule_encrypted_room_one_to_one": "Κρυπτογραφημένα μηνύματα σε συνομιλίες ένας προς έναν",
-            "rule_invite_for_me": "Όταν με προσκαλούν σ' ένα δωμάτιο",
+            "rule_invite_for_me": "Όταν με προσκαλούν σε μία αίθουσα",
             "rule_message": "Μηνύματα σε ομαδικές συνομιλίες",
             "rule_room_one_to_one": "Μηνύματα σε 1-προς-1 συνομιλίες",
             "rule_roomnotif": "Μηνύματα που περιέχουν @δωμάτιο",
             "rule_suppress_notices": "Μηνύματα από bots",
-            "rule_tombstone": "Όταν τα δωμάτια αναβαθμίζονται",
-            "show_message_desktop_notification": "Εμφάνιση του μηνύματος στην ειδοποίηση στον υπολογιστή"
+            "rule_tombstone": "Όταν οι αίθουσες αναβαθμίζονται",
+            "show_message_desktop_notification": "Εμφάνιση του μηνύματος στην ειδοποίηση στον υπολογιστή",
+            "voip": "Κλήσεις ήχου και Βίντεο"
         },
         "preferences": {
+            "Electron.enableHardwareAcceleration": "Ενεργοποίηση επιτάχυνσης υλικού (επανεκκίνηση %(appName)s για να τεθεί σε ισχύ)",
             "always_show_menu_bar": "Να εμφανίζεται πάντα η μπάρα μενού παραθύρου",
             "autocomplete_delay": "Καθυστέρηση αυτόματης συμπλήρωσης (ms)",
             "code_blocks_heading": "Μπλοκ κώδικα",
@@ -2018,9 +2134,12 @@
             "keyboard_heading": "Συντομεύσεις πληκτρολογίου",
             "keyboard_view_shortcuts_button": "Για να δείτε όλες τις συντομεύσεις πληκτρολογίου, <a>κάντε κλικ εδώ</a>.",
             "media_heading": "Εικόνες, GIF και βίντεο",
+            "presence_description": "Μοιράσου τη δραστηριότητα και την κατάστασή σου με άλλους.",
             "rm_lifetime": "Διάρκεια του Δείκτη Ανάγνωσης (ms)",
             "rm_lifetime_offscreen": "Διάρκεια Δείκτη εκτός οθόνης (ms)",
+            "room_directory_heading": "Κατάλογος δωματίων",
             "room_list_heading": "Λίστα δωματίων",
+            "show_avatars_pills": "Εμφάνιση άβαταρ σε αναφορές χρηστών, δωματίων και εκδηλώσεων",
             "show_polls_button": "Εμφάνιση κουμπιού δημοσκοπήσεων",
             "surround_text": "Περιτριγυριστείτε το επιλεγμένο κείμενο κατά την πληκτρολόγηση ειδικών χαρακτήρων",
             "time_heading": "Εμφάνιση ώρας"
@@ -2028,48 +2147,16 @@
         "prompt_invite": "Ερώτηση πριν από την αποστολή προσκλήσεων σε δυνητικά μη έγκυρα αναγνωριστικά matrix",
         "replace_plain_emoji": "Αυτόματη αντικατάσταση απλού κειμένου Emoji",
         "security": {
-            "4s_public_key_in_account_data": "στα δεδομένα λογαριασμού",
-            "4s_public_key_status": "Δημόσιο κλειδί μυστικής αποθήκευσης:",
-            "backup_key_cached_status": "Αποθηκευμένο εφεδρικό κλειδί στην κρυφή μνήμη:",
-            "backup_key_stored_status": "Αποθηκευμένο εφεδρικό κλειδί:",
-            "backup_key_unexpected_type": "μη αναμενόμενος τύπος",
-            "backup_key_well_formed": "καλοσχηματισμένο",
-            "backup_keys_description": "Δημιουργήστε αντίγραφα ασφαλείας των κλειδιών κρυπτογράφησης με τα δεδομένα του λογαριασμού σας σε περίπτωση που χάσετε την πρόσβαση στις συνεδρίες σας. Τα κλειδιά σας θα ασφαλιστούν με ένα μοναδικό κλειδί ασφαλείας.",
             "bulk_options_accept_all_invites": "Αποδεχτείτε όλες τις %(invitedRooms)sπροσκλήσεις",
             "bulk_options_reject_all_invites": "Απόρριψη όλων των προσκλήσεων %(invitedRooms)s",
             "bulk_options_section": "Μαζικές επιλογές",
-            "cross_signing_cached": "αποθηκευμένο τοπικά",
-            "cross_signing_homeserver_support": "Υποστήριξη λειτουργιών κεντρικού διακομιστή:",
-            "cross_signing_homeserver_support_exists": "υπάρχει",
-            "cross_signing_in_4s": "σε μυστική αποθήκευση",
-            "cross_signing_in_memory": "στη μνήμη",
-            "cross_signing_master_private_Key": "Κύριο ιδιωτικό κλειδί:",
-            "cross_signing_not_cached": "δεν βρέθηκε τοπικά",
-            "cross_signing_not_found": "δε βρέθηκε",
-            "cross_signing_not_in_4s": "δεν βρέθηκε στην αποθήκευση",
-            "cross_signing_not_stored": "μη αποθηκευμένο",
-            "cross_signing_private_keys": "Διασταυρούμενη υπογραφή ιδιωτικών κλειδιών:",
-            "cross_signing_public_keys": "Διασταυρούμενη υπογραφή δημόσιων κλειδιών:",
-            "cross_signing_self_signing_private_key": "Αυτόματη υπογραφή ιδιωτικού κλειδιού:",
-            "cross_signing_user_signing_private_key": "Ιδιωτικό κλειδί για υπογραφή χρήστη:",
-            "cryptography_section": "Κρυπτογραφία",
-            "delete_backup": "Διαγραφή Αντιγράφου ασφαλείας",
-            "delete_backup_confirm_description": "Είσαι σίγουρος? Θα χάσετε τα κρυπτογραφημένα μηνύματά σας εάν δε δημιουργηθούν σωστά αντίγραφα ασφαλείας των κλειδιών σας.",
             "e2ee_default_disabled_warning": "Ο διαχειριστής του διακομιστή σας έχει απενεργοποιήσει την κρυπτογράφηση από άκρο σε άκρο από προεπιλογή σε ιδιωτικά δωμάτια & άμεσα μηνύματα.",
             "enable_message_search": "Ενεργοποίηση αναζήτησης μηνυμάτων σε κρυπτογραφημένα δωμάτια",
             "encryption_section": "Κρυπτογράφηση",
-            "error_loading_key_backup_status": "Δεν είναι δυνατή η φόρτωση της κατάστασης του αντιγράφου ασφαλείας κλειδιού",
-            "export_megolm_keys": "Εξαγωγή κλειδιών κρυπτογράφησης για το δωμάτιο",
             "ignore_users_empty": "Δεν έχετε χρήστες που έχετε αγνοήσει.",
             "ignore_users_section": "Χρήστες που αγνοήθηκαν",
-            "import_megolm_keys": "Εισαγωγή κλειδιών E2E",
-            "key_backup_active_version_none": "Κανένα",
             "key_backup_algorithm": "Αλγόριθμος:",
-            "key_backup_complete": "Δημιουργήθηκαν αντίγραφα ασφαλείας όλων των κλειδιών",
             "key_backup_connect": "Συνδέστε αυτήν την συνεδρία με το αντίγραφο ασφαλείας κλειδιού",
-            "key_backup_connect_prompt": "Συνδέστε αυτήν την συνεδρία με το αντίγραφο ασφαλείας κλειδιού πριν αποσυνδεθείτε για να αποφύγετε την απώλεια κλειδιών που μπορεί να υπάρχουν μόνο σε αυτήν την συνεδρία.",
-            "key_backup_inactive": "Αυτή η συνεδρία <b>δεν δημιουργεί αντίγραφα ασφαλείας των κλειδιών σας</b>, αλλά έχετε ένα υπάρχον αντίγραφο ασφαλείας από το οποίο μπορείτε να επαναφέρετε και να προσθέσετε στη συνέχεια.",
-            "key_backup_inactive_warning": "<b>Δεν δημιουργούνται αντίγραφα ασφαλείας των κλειδιών σας από αυτήν την συνεδρία</b>.",
             "message_search_disable_warning": "Εάν απενεργοποιηθεί, τα μηνύματα από κρυπτογραφημένα δωμάτια δε θα εμφανίζονται στα αποτελέσματα αναζήτησης.",
             "message_search_disabled": "Αποθηκεύστε με ασφάλεια κρυπτογραφημένα μηνύματα τοπικά για να εμφανίζονται στα αποτελέσματα αναζήτησης.",
             "message_search_enabled": {
@@ -2088,15 +2175,12 @@
             "message_search_space_used": "Χώρος που χρησιμοποιείται:",
             "message_search_unsupported": "Λείπουν ορισμένα στοιχεία από το %(brand)s που απαιτούνται για την ασφαλή αποθήκευση κρυπτογραφημένων μηνυμάτων τοπικά. Εάν θέλετε να πειραματιστείτε με αυτό το χαρακτηριστικό, δημιουργήστε μια προσαρμοσμένη %(brand)s επιφάνεια εργασίαςμε <nativeLink>προσθήκη στοιχείων αναζήτησης</nativeLink>.",
             "message_search_unsupported_web": "Το %(brand)s δεν μπορεί να αποθηκεύσει με ασφάλεια κρυπτογραφημένα μηνύματα τοπικά ενώ εκτελείται σε πρόγραμμα περιήγησης ιστού. Χρησιμοποιήστε την <desktopLink>%(brand)s Επιφάνεια εργασίας</desktopLink> για να εμφανίζονται κρυπτογραφημένα μηνύματα στα αποτελέσματα αναζήτησης.",
-            "restore_key_backup": "Επαναφορά από Αντίγραφο ασφαλείας",
-            "secret_storage_not_ready": "δεν είναι έτοιμο",
-            "secret_storage_ready": "έτοιμο",
-            "secret_storage_status": "Μυστική αποθήκευση:",
+            "record_session_details": "Κατέγραψε το όνομα του πελάτη, την έκδοση και τη διεύθυνση URL για να αναγνωρίζεις τις συνεδρίες πιο εύκολα στον διαχειριστή συνεδρίας",
             "send_analytics": "Αποστολή δεδομένων αναλυτικών στοιχείων",
-            "session_id": "Αναγνωριστικό συνεδρίας:",
-            "session_key": "Κλειδί συνεδρίας:",
             "strict_encryption": "Μη στέλνετε ποτέ κρυπτογραφημένα μηνύματα σε μη επαληθευμένες συνεδρίες από αυτήν τη συνεδρία"
         },
+        "send_read_receipts": "Αποστολή αποδείξεων ανάγνωσης",
+        "send_read_receipts_unsupported": "Ο διακομιστής σου δεν υποστηρίζει την απενεργοποίηση αποστολής αποδείξεων ανάγνωσης.",
         "send_typing_notifications": "Αποστολή ειδοποιήσεων πληκτρολόγησης",
         "sessions": {
             "confirm_sign_out": {
@@ -2118,10 +2202,12 @@
             "session_id": "Αναγνωριστικό συνεδρίας",
             "verify_session": "Επαλήθευση συνεδρίας"
         },
+        "show_avatar_changes": "Εμφάνιση αλλαγών εικόνας προφίλ",
         "show_breadcrumbs": "Εμφάνιση συντομεύσεων σε δωμάτια που προβλήθηκαν πρόσφατα πάνω από τη λίστα δωματίων",
         "show_chat_effects": "Εμφάνιση εφέ συνομιλίας (κινούμενα σχέδια κατά τη λήψη π.χ. κομφετί)",
         "show_displayname_changes": "Εμφάνιση αλλαγών εμφανιζόμενου ονόματος",
         "show_join_leave": "Εμφάνιση μηνυμάτων συμμετοχής/αποχώρησης (προσκλήσεις/αφαιρέσεις/απαγορεύσεις δεν επηρεάζονται)",
+        "show_nsfw_content": "Εμφάνιση περιεχομένου NSFW",
         "show_read_receipts": "Εμφάνιση αποδείξεων ανάγνωσης που έχουν αποσταλεί από άλλους χρήστες",
         "show_redaction_placeholder": "Εμφάνιση πλαισίου θέσης για μηνύματα που έχουν αφαιρεθεί",
         "show_stickers_button": "Εμφάνιση κουμπιού αυτοκόλλητων",
@@ -2140,18 +2226,30 @@
         "start_automatically": "Αυτόματη έναρξη μετά τη σύνδεση",
         "use_12_hour_format": "Εμφάνιση χρονικών σημάνσεων σε 12ωρη μορφή ώρας (π.χ. 2:30 μ.μ.)",
         "use_command_enter_send_message": "Χρησιμοποιήστε Command + Enter για να στείλετε ένα μήνυμα",
-        "use_command_f_search": "Χρησιμοποιήστε το Command + F για αναζήτηση στο χρονοδιάγραμμα",
+        "use_command_f_search": "Χρησιμοποιήστε τα πλήκτρα Command + F για αναζήτηση στο χρονολόγιο",
         "use_control_enter_send_message": "Χρησιμοποιήστε Ctrl + Enter για να στείλετε ένα μήνυμα",
-        "use_control_f_search": "Χρησιμοποιήστε τα πλήκτρα Ctrl + F για αναζήτηση στο χρονοδιάγραμμα",
+        "use_control_f_search": "Χρησιμοποιήστε τα πλήκτρα Ctrl + F για αναζήτηση στο χρονολόγιο",
         "voip": {
+            "allow_p2p": "Να επιτρέπεται η χρήση Peer-to-Peer για κλήσεις 1:1",
+            "allow_p2p_description": "Όταν είναι ενεργό, το άλλο άτομο ενδέχεται να μπορεί να δει τη διεύθυνση IP σου",
             "audio_input_empty": "Δεν εντοπίστηκε μικρόφωνο",
             "audio_output": "Έξοδος ήχου",
             "audio_output_empty": "Δεν εντοπίστηκαν Έξοδοι Ήχου",
+            "auto_gain_control": "Αυτόματος έλεγχος gain",
+            "connection_section": "Σύνδεση",
+            "echo_cancellation": "Ακύρωση ηχούς",
+            "enable_fallback_ice_server": "Να επιτρέπεται ο εναλλακτικής διακομιστής υποβοήθησης κλήσης (%(server)s )",
+            "enable_fallback_ice_server_description": "Ισχύει μόνο εάν ο οικιακός διακομιστής σου δεν προσφέρει ένα. Η διεύθυνση IP σου θα κοινοποιηθεί κατά τη διάρκεια μιας κλήσης.",
             "mirror_local_feed": "Αντικατοπτρίστε την τοπική ροή βίντεο",
             "missing_permissions_prompt": "Λείπουν δικαιώματα πολυμέσων, κάντε κλικ στο κουμπί παρακάτω για να αιτηθείτε.",
+            "noise_suppression": "Καταστολή θορύβου",
             "request_permissions": "Ζητήστε άδειες πολυμέσων",
             "title": "Φωνή & Βίντεο",
-            "video_input_empty": "Δεν εντοπίστηκε κάμερα"
+            "video_input_empty": "Δεν εντοπίστηκε κάμερα",
+            "video_section": "Ρυθμίσεις βίντεο",
+            "voice_agc": "Αυτόματη ρύθμιση της έντασης του μικροφώνου",
+            "voice_processing": "Επεξεργασία φωνής",
+            "voice_section": "Ρυθμίσεις φωνής"
         },
         "warn_quit": "Προειδοποιήστε πριν την παραίτηση"
     },
@@ -2193,12 +2291,14 @@
         "invite_3pid_needs_is_error": "Χρησιμοποιήστε έναν διακομιστή ταυτοτήτων για να προσκαλέσετε μέσω email. Μπορείτε να κάνετε διαχείριση στις Ρυθμίσεις.",
         "invite_3pid_use_default_is_title": "Χρησιμοποιήστε ένα διακομιστή ταυτοτήτων",
         "invite_3pid_use_default_is_title_description": "Χρησιμοποιήστε έναν διακομιστή ταυτοτήτων για να προσκαλέσετε μέσω email. Πατήστε συνέχεια για να χρησιμοποιήσετε τον προεπιλεγμένο διακομιστή ταυτοτήτων (%(defaultIdentityServerName)s) ή μπείτε στην διαχείριση στις Ρυθμίσεις.",
-        "join": "Σύνδεση στο δωμάτιο με την δοθείσα διεύθυνση",
-        "jumptodate": "Μεταβείτε στη δεδομένη ημερομηνία στη γραμμή χρόνου",
+        "join": "Σύνδεση στην αίθουσα με την δοθείσα διεύθυνση",
+        "jumptodate": "Μεταβείτε στη δεδομένη ημερομηνία στο χρονολόγιο",
         "jumptodate_invalid_input": "Αδυναμία κατανόησης της δοθείσας ημερομηνίας (%(inputDate)s). Προσπαθήστε να χρησιμοποιήσετε την μορφή YYYY-MM-DD.",
         "lenny": "Προ-εισάγει ( ͡° ͜ʖ ͡°) σε ένα μήνυμα απλού κειμένου",
         "me": "Εμφανίζει την ενέργεια",
         "msg": "Στέλνει ένα μήνυμα στον δοσμένο χρήστη",
+        "myavatar": "Αλλάζει την εικόνα προφίλ σου σ' όλα τα δωμάτια",
+        "myroomavatar": "Αλλάζει την εικόνα προφίλ σου μόνο στο τρέχον δωμάτιο",
         "myroomnick": "Αλλάζει το εμφανιζόμενο ψευδώνυμο μόνο στο παρόν δωμάτιο",
         "nick": "Αλλάζει το ψευδώνυμο χρήστη",
         "no_active_call": "Δεν υπάρχει ενεργή κλήση σε αυτό το δωμάτιο",
@@ -2219,10 +2319,8 @@
         "spoiler": "Στέλνει το δοθέν μήνυμα ως spoiler",
         "tableflip": "Προ-εισάγει (╯°□°)╯︵ ┻━┻ σε ένα μήνυμα απλού κειμένου",
         "topic": "Λαμβάνει ή θέτει το θέμα του δωματίου",
-        "topic_none": "Το δωμάτιο αυτό δεν έχει κανένα θέμα.",
+        "topic_none": "Αυτή η αίθουσα δεν έχει κάποιο θέμα.",
         "topic_room_error": "Αποτυχία λήψης θέματος δωματίου: Αδυναμία εύρεσης δωματίου (%(roomId)s",
-        "tovirtual": "Μεταβαίνει στο εικονικό δωμάτιο αυτού του δωματίου, εάν υπάρχει",
-        "tovirtual_not_found": "Δεν υπάρχει εικονικό δωμάτιο για αυτό το δωμάτιο",
         "unban": "Άρση αποκλεισμού χρήστη με το συγκεκριμένο αναγνωριστικό",
         "unflip": "Προ-εισάγει ┬──┬ ノ( ゜-゜ノ) σε ένα μήνυμα απλού κειμένου",
         "unholdcall": "Επαναφέρει την κλήση στο τρέχον δωμάτιο από την αναμονή",
@@ -2260,7 +2358,7 @@
             "space_dropdown_title": "Προσθήκη υπάρχοντος χώρου"
         },
         "context_menu": {
-            "devtools_open_timeline": "Εμφάνιση χρονοδιαγράμματος δωματίου (develtools)",
+            "devtools_open_timeline": "Εμφάνιση χρονολογίου αίθουσας (develtools)",
             "explore": "Εξερευνήστε δωμάτια",
             "home": "Αρχική σελίδα χώρου",
             "manage_and_explore": "Διαχειριστείτε και εξερευνήστε δωμάτια",
@@ -2337,7 +2435,6 @@
         "heading_without_query": "Αναζήτηση για",
         "join_button_text": "Συμμετοχή στο %(roomAddress)s",
         "keyboard_scroll_hint": "Χρησιμοποιήστε τα <arrows/> για κύλιση",
-        "message_search_section_title": "'Άλλες αναζητήσεις",
         "other_rooms_in_space": "Άλλα δωμάτιο στο %(spaceName)s",
         "public_rooms_label": "Δημόσια δωμάτια",
         "recent_searches_section_title": "Πρόσφατες αναζητήσεις",
@@ -2345,7 +2442,6 @@
         "result_may_be_hidden_privacy_warning": "Ορισμένα αποτελέσματα ενδέχεται να είναι κρυφά για λόγους απορρήτου",
         "result_may_be_hidden_warning": "Ορισμένα αποτελέσματα ενδέχεται να είναι κρυμμένα",
         "search_dialog": "Παράθυρο Αναζήτησης",
-        "search_messages_hint": "Για να αναζητήσετε μηνύματα, βρείτε αυτό το εικονίδιο στην κορυφή ενός δωματίου <icon/>",
         "spaces_title": "Χώροι που ανήκετε",
         "start_group_chat_button": "Ξεκινήστε μια ομαδική συνομιλία"
     },
@@ -2394,6 +2490,7 @@
         "about_minute_ago": "σχεδόν ένα λεπτό πριν",
         "date_at_time": "%(date)s στις %(time)s",
         "few_seconds_ago": "λίγα δευτερόλεπτα πριν",
+        "hours_minutes_seconds_left": "απομένουν %(hours)sώ %(minutes)sλ %(seconds)sδλ",
         "in_about_day": "περίπου μια μέρα από τώρα",
         "in_about_hour": "περίπου μία ώρα από τώρα",
         "in_about_minute": "περίπου ένα λεπτό από τώρα",
@@ -2402,13 +2499,17 @@
         "in_n_hours": "%(num)s ώρες από τώρα",
         "in_n_minutes": "%(num)s λεπτά από τώρα",
         "left": "%(timeRemaining)s απομένουν",
+        "minutes_seconds_left": "απομένουν %(minutes)sλ %(seconds)sδλ",
         "n_days_ago": "%(num)s μέρες πριν",
         "n_hours_ago": "%(num)s ώρες πριν",
         "n_minutes_ago": "%(num)s λεπτά πριν",
         "seconds_left": "%(seconds)ss απομένουν",
         "short_days": "%(value)sμέρες",
+        "short_days_hours_minutes_seconds": "%(days)sη %(hours)sώ %(minutes)sλ %(seconds)sδλ",
         "short_hours": "%(value)sώρες",
+        "short_hours_minutes_seconds": "%(hours)sώ %(minutes)sλ %(seconds)sδλ",
         "short_minutes": "%(value)s'",
+        "short_minutes_seconds": "%(minutes)sλ %(seconds)sδλ",
         "short_seconds": "%(value)s\""
     },
     "timeline": {
@@ -2435,9 +2536,9 @@
         "historical_messages_unavailable": "Δεν μπορείτε να δείτε προηγούμενα μηνύματα",
         "io.element.widgets.layout": "%(senderName)s έχει ενημερώσει τη διάταξη του δωματίου",
         "load_error": {
-            "no_permission": "Προσπαθήσατε να φορτώσετε ένα συγκεκριμένο σημείο στο χρονολόγιο του δωματίου, αλλά δεν έχετε δικαίωμα να δείτε το εν λόγω μήνυμα.",
+            "no_permission": "Έγινε προσπάθεια φόρτωσης ενός συγκεκριμένου σημείου στο χρονολόγιο της αίθουσας, αλλά δεν έχετε δικαίωμα να δείτε το εν λόγω μήνυμα.",
             "title": "Δεν ήταν δυνατή η φόρτωση της θέσης του χρονολόγιου",
-            "unable_to_find": "Προσπαθήσατε να φορτώσετε ένα συγκεκριμένο σημείο στο χρονολόγιο του δωματίου, αλλά δεν καταφέρατε να το βρείτε."
+            "unable_to_find": "Έγινε προσπάθεια φόρτωσης ενός συγκεκριμένου σημείου στο χρονολόγιο της αίθουσας, αλλά δε βρέθηκε."
         },
         "m.audio": {
             "error_downloading_audio": "Σφάλμα λήψης ήχου",
@@ -2445,6 +2546,10 @@
             "error_processing_voice_message": "Σφάλμα επεξεργασίας του φωνητικού μηνύματος",
             "unnamed_audio": "Ήχος χωρίς όνομα"
         },
+        "m.call": {
+            "video_call_started": "Ξεκίνησε βιντεοκλήση στο %(roomName)s",
+            "video_call_started_unsupported": "Ξεκίνησε βιντεοκλήση στο %(roomName)s. (δεν υποστηρίζεται απ' αυτόν τον περιηγητή)"
+        },
         "m.call.hangup": {
             "dm": "Τέλος κλήσης"
         },
@@ -2537,6 +2642,7 @@
         },
         "m.room.join_rules": {
             "invite": "Ο %(senderDisplayName)s άλλαξε το δωμάτιο σε \"μόνο με πρόσκληση\".",
+            "knock": "Ο χρήστης %(senderDisplayName)s άλλαξε τον κανόνα σύνδεσης για αίτημα συμμετοχής.",
             "public": "Ο %(senderDisplayName)s έκανε το δωμάτιο δημόσιο για όποιον γνωρίζει τον σύνδεσμο.",
             "restricted": "Ο %(senderDisplayName)s άλλαξε τους κανόνες σύνδεσης στο δωμάτιο.",
             "restricted_settings": "Ο %(senderDisplayName)s άλλαξε τους κανόνες σύνδεσης στο δωμάτιο. <a>Δείτε τις ρυθμίσεις</a>.",
@@ -2549,6 +2655,7 @@
             "ban_reason": "Ο %(senderName)s απέκλεισε τον/την %(targetName)s: %(reason)s",
             "change_avatar": "Ο %(senderName)s άλλαξε τη φωτογραφία του προφίλ του",
             "change_name": "Ο/η %(oldDisplayName)s άλλαξε το εμφανιζόμενο όνομα σε %(displayName)s",
+            "change_name_avatar": "Ο χρήστης %(oldDisplayName)s άλλαξε το εμφανιζόμενο όνομα και την εικόνα προφίλ του",
             "invite": "Ο/η %(senderName)s προσκάλεσε τον/την %(targetName)s",
             "join": "Ο/η %(targetName)s συνδέθηκε στο δωμάτιο",
             "kick": "%(senderName)s αφαιρέθηκε %(targetName)s",
@@ -2583,7 +2690,7 @@
             "user_from_to": "%(userId)s από %(fromPowerLevel)s σε %(toPowerLevel)s"
         },
         "m.room.server_acl": {
-            "all_servers_banned": "🎉 Όλοι οι διακομιστές αποκλείστηκαν από την συμμετοχή! Αυτό το δωμάτιο δεν μπορεί να χρησιμοποιηθεί πλέον.",
+            "all_servers_banned": "🎉 Όλοι οι διακομιστές αποκλείστηκαν από την συμμετοχή! Αυτή η αίθουσα δεν μπορεί να χρησιμοποιηθεί πλέον.",
             "changed": "Ο %(senderDisplayName)s άλλαξε τα ACLs του διακομιστή για αυτό το δωμάτιο.",
             "set": "Ο %(senderDisplayName)s όρισε τα ACLs του διακομιστή για αυτό το δωμάτιο."
         },
@@ -2592,7 +2699,9 @@
             "sent": "Ο %(senderName)s έστειλε μια πρόσκληση στον %(targetDisplayName)s για να συνδεθεί στο δωμάτιο."
         },
         "m.room.tombstone": "Ο %(senderDisplayName)s αναβάθμισε αυτό το δωμάτιο.",
-        "m.room.topic": "Ο %(senderDisplayName)s άλλαξε το θέμα σε \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "Ο %(senderDisplayName)s άλλαξε το θέμα σε \"%(topic)s\"."
+        },
         "m.sticker": "Ο %(senderDisplayName)s έστειλε ένα αυτοκόλλητο.",
         "m.video": {
             "error_decrypting": "Σφάλμα κατά την αποκρυπτογράφηση του βίντεο"
@@ -2601,7 +2710,7 @@
             "added": "Προστέθηκε η μικροεφαρμογή %(widgetName)s από τον/την %(senderName)s",
             "jitsi_ended": "Η τηλεδιάσκεψη τερματίστηκε από %(senderName)s",
             "jitsi_join_right_prompt": "Συμμετάσχετε στην τηλεδιάσκεψη από την κάρτα πληροφοριών στα δεξιά",
-            "jitsi_join_top_prompt": "Συμμετάσχετε στην τηλεδιάσκεψη από την κορυφή του δωματίου αυτού",
+            "jitsi_join_top_prompt": "Συμμετάσχετε στην τηλεδιάσκεψη από την κορυφή αυτής της αίθουσας",
             "jitsi_started": "Η τηλεδιάσκεψη ξεκίνησε από %(senderName)s",
             "jitsi_updated": "Η τηλεδιάσκεψη ενημερώθηκε από %(senderName)s",
             "modified": "Έγινε αλλαγή στη μικροεφαρμογή %(widgetName)s από τον/την %(senderName)s",
@@ -2671,6 +2780,14 @@
                 "one": "αποκλείστηκαν",
                 "other": "αποκλείστηκαν %(count)s φορές"
             },
+            "changed_avatar": {
+                "one": "Ο χρήστης %(oneUser)s άλλαξε την εικόνα προφίλ του",
+                "other": "Οι %(oneUser)s άλλαξαν τις φωτογραφίες προφίλ τους %(count)s φορές"
+            },
+            "changed_avatar_multiple": {
+                "one": "Ο χρήστης %(severalUsers)sάλλαξε την εικόνα του προφίλ του",
+                "other": "Οι %(severalUsers)sάλλαξαν τις φωτογραφίες προφίλ τους %(count)s φορές"
+            },
             "changed_name": {
                 "one": "%(oneUser)sάλλαξε το όνομα τους",
                 "other": "%(oneUser)sάλλαξε το όνομα τους %(count)s φορές"
@@ -2679,6 +2796,7 @@
                 "one": "%(severalUsers)sάλλαξαν το όνομα τους",
                 "other": "%(severalUsers)sάλλαξαν το όνομα τους %(count)s φορές"
             },
+            "format": "%(nameList)s %(transitionList)s",
             "hidden_event": {
                 "one": "%(oneUser)sέστειλε ένα κρυφό μήνυμα",
                 "other": "%(oneUser)sέστειλε %(count)s κρυφά μηνύματα"
@@ -2848,14 +2966,6 @@
         "ban_room_confirm_title": "Αποκλεισμός από %(roomName)s",
         "ban_space_everything": "Αποκλεισμός από οτιδήποτε έχω δικαίωμα",
         "ban_space_specific": "Αποκλεισμός από συγκεκριμένες λειτουργίες που έχω δικαίωμα",
-        "count_of_sessions": {
-            "one": "%(count)s συνεδρία",
-            "other": "%(count)s συνεδρίες"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 επαληθευμένη συνεδρία",
-            "other": "%(count)s επαληθευμένες συνεδρίες"
-        },
         "deactivate_confirm_action": "Απενεργοποίηση χρήστη",
         "deactivate_confirm_description": "Η απενεργοποίηση αυτού του χρήστη θα τον αποσυνδεθεί και θα αποτραπεί η επανασύνδεσή του. Επιπλέον, θα αποχωρήσει από όλα τα δωμάτια στα οποία συμμετέχει. Αυτή η ενέργεια δεν μπορεί να αντιστραφεί. Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε αυτόν τον χρήστη;",
         "deactivate_confirm_title": "Απενεργοποίηση χρήστη;",
@@ -2866,15 +2976,12 @@
         "disinvite_button_room": "Ακύρωση πρόσκλησης από το δωμάτιο",
         "disinvite_button_room_name": "Κατάργηση πρόσκλησης από %(roomName)s",
         "disinvite_button_space": "Ακύρωση πρόσκλησης από τον χώρο",
-        "edit_own_devices": "Επεξεργασία συσκευών",
         "error_ban_user": "Δεν ήταν δυνατό ο αποκλεισμός του χρήστη",
         "error_deactivate": "Η απενεργοποίηση χρήστη απέτυχε",
         "error_kicking_user": "Αποτυχία κατάργησης χρήστη",
         "error_mute_user": "Δεν ήταν δυνατή η σίγαση του χρήστη",
         "error_revoke_3pid_invite_description": "Δεν ήταν δυνατή η ανάκληση της πρόσκλησης. Ο διακομιστής μπορεί να αντιμετωπίζει ένα προσωρινό πρόβλημα ή δεν έχετε επαρκή δικαιώματα για να ανακαλέσετε την πρόσκληση.",
         "error_revoke_3pid_invite_title": "Αποτυχία ανάκλησης πρόσκλησης",
-        "hide_sessions": "Απόκρυψη συνεδριών",
-        "hide_verified_sessions": "Απόκρυψη επαληθευμένων συνεδριών",
         "invited_by": "Προσκεκλημένος από %(sender)s",
         "jump_to_rr_button": "Μετάβαση στο αποδεικτικό ανάγνωσης",
         "kick_button_room": "Αφαίρεση από το δωμάτιο",
@@ -2897,14 +3004,14 @@
             "confirm_keep_state_explainer": "Καταργήστε την επιλογή εάν θέλετε επίσης να καταργήσετε τα μηνύματα συστήματος σε αυτόν τον χρήστη (π.χ. αλλαγή μέλους, αλλαγή προφίλ…)",
             "confirm_keep_state_label": "Διατήρηση μηνυμάτων συστήματος",
             "confirm_title": "Καταργήστε πρόσφατα μηνύματα από %(user)s",
-            "no_recent_messages_description": "Δοκιμάστε να κάνετε κύλιση στη γραμμή χρόνου για να δείτε αν υπάρχουν παλαιότερα.",
+            "no_recent_messages_description": "Δοκιμάστε να κάνετε κύλιση στο χρονολόγιο για να δείτε αν υπάρχουν παλαιότερα.",
             "no_recent_messages_title": "Δε βρέθηκαν πρόσφατα μηνύματα από %(user)s"
         },
         "redact_button": "Κατάργηση πρόσφατων μηνυμάτων",
         "revoke_invite": "Ανάκληση πρόσκλησης",
-        "room_encrypted": "Τα μηνύματα σε αυτό το δωμάτιο είναι κρυπτογραφημένα από άκρο σε άκρο.",
+        "room_encrypted": "Τα μηνύματα σε αυτήν την αίθουσα είναι κρυπτογραφημένα από άκρο σε άκρο.",
         "room_encrypted_detail": "Τα μηνύματά σας είναι ασφαλή και μόνο εσείς και ο παραλήπτης έχετε τα μοναδικά κλειδιά για να τα ξεκλειδώσετε.",
-        "room_unencrypted": "Τα μηνύματα σε αυτό το δωμάτιο δεν είναι κρυπτογραφημένα από άκρο σε άκρο.",
+        "room_unencrypted": "Τα μηνύματα σε αυτήν την αίθουσα δεν είναι κρυπτογραφημένα από άκρο σε άκρο.",
         "room_unencrypted_detail": "Σε κρυπτογραφημένα δωμάτια, τα μηνύματά σας είναι ασφαλή και μόνο εσείς και ο παραλήπτης έχετε τα μοναδικά κλειδιά για να τα ξεκλειδώσετε.",
         "share_button": "Κοινή χρήση Συνδέσμου με Χρήστη",
         "unban_button_space": "Αναίρεση αποκλεισμού από τον χώρο",
@@ -2953,7 +3060,7 @@
         "hangup": "Κλείσιμο",
         "hide_sidebar_button": "Απόκρυψη πλαϊνής μπάρας",
         "input_devices": "Συσκευές εισόδου",
-        "join_button_tooltip_connecting": "Συνδέεται",
+        "maximise": "Γέμισμα οθόνης",
         "misconfigured_server": "Η κλήση απέτυχε λόγω της λανθασμένης διάρθρωσης του διακομιστή",
         "misconfigured_server_description": "Παρακαλείστε να ρωτήσετε τον διαχειριστή του κεντρικού διακομιστή σας (<code>%(homeserverDomain)s</code>) να ρυθμίσουν έναν διακομιστή πρωτοκόλλου TURN ώστε οι κλήσεις να λειτουργούν απρόσκοπτα.",
         "more_button": "Περισσότερα",
@@ -2973,6 +3080,7 @@
         "screenshare_window": "Παράθυρο εφαρμογής",
         "show_sidebar_button": "Εμφάνιση πλαϊνής μπάρας",
         "silence": "Σίγαση",
+        "silenced": "Οι ειδοποιήσεις σιωπήθηκαν",
         "start_screenshare": "Ξεκινήστε να μοιράζεστε την οθόνη σας",
         "stop_screenshare": "Σταματήστε να μοιράζεστε την οθόνη σας",
         "too_many_calls": "Πάρα Πολλές Κλήσεις",
@@ -2993,6 +3101,7 @@
         "user_busy_description": "Ο χρήστης που καλέσατε είναι απασχολημένος.",
         "user_is_presenting": "%(sharerName)s παρουσιάζει",
         "video_call": "Βιντεοκλήση",
+        "video_call_started": "Ξεκίνησε η βιντεοκλήση",
         "voice_call": "Φωνητική κλήση",
         "you_are_presenting": "Παρουσιάζετε"
     },
@@ -3008,7 +3117,7 @@
         "capability": {
             "always_on_screen_generic": "Παραμονή στην οθόνη σας ενώ τρέχετε",
             "always_on_screen_viewing_another_room": "Παραμονή στην οθόνη σας όταν βλέπετε άλλο δωμάτιο, όταν τρέχετε",
-            "any_room": "Τα παραπάνω, αλλά και σε οποιοδήποτε δωμάτιο είστε μέλος ή προσκεκλημένοι",
+            "any_room": "Τα παραπάνω, αλλά και σε οποιαδήποτε αίθουσα είστε μέλος ή προσκεκλημένοι",
             "byline_empty_state_key": "με ένα κενό κλειδί κατάστασης",
             "byline_state_key": "με κλειδί κατάστασης %(stateKey)s",
             "capability": "Η <b>%(capability)s</b> ικανότητα",
@@ -3050,22 +3159,22 @@
             "send_emotes_this_room": "Στείλτε emotes, ως εσείς, σε αυτό το δωμάτιο",
             "send_event_type_active_room": "Στείλετε <b>%(eventType)s</b> γεγονότα, ως εσείς, στο ενεργό δωμάτιό σας",
             "send_event_type_this_room": "Στείλτε <b>%(eventType)s</b> γεγονότα σε αυτό το δωμάτιο",
-            "send_files_active_room": "Στείλτε γενικά αρχεία, ως εσείς, στο ενεργό δωμάτιό σας",
-            "send_files_this_room": "Στείλτε γενικά αρχεία, ως εσείς, σε αυτό το δωμάτιο",
-            "send_images_active_room": "Στείλτε εικόνες, ως εσείς, στο ενεργό δωμάτιό σας",
-            "send_images_this_room": "Στείλτε εικόνες, ως εσείς, σε αυτό το δωμάτιο",
-            "send_messages_active_room": "Στείλτε μηνύματα, ως εσείς, στο ενεργό δωμάτιό σας",
-            "send_messages_this_room": "Στείλτε μηνύματα, ως εσείς, σε αυτό το δωμάτιο",
+            "send_files_active_room": "Στείλτε γενικά αρχεία, ως εσείς, στην ενεργή σας αίθουσα",
+            "send_files_this_room": "Στείλτε γενικά αρχεία, ως εσείς, σε αυτήν την αίθουσα",
+            "send_images_active_room": "Στείλτε εικόνες, ως εσείς, στην ενεργή σας αίθουσα",
+            "send_images_this_room": "Στείλτε εικόνες, ως εσείς, σε αυτήν την αίθουσα",
+            "send_messages_active_room": "Στείλτε μηνύματα, ως εσείς, στην ενεργή σας αίθουσα",
+            "send_messages_this_room": "Στείλτε μηνύματα, ως εσείς, σε αυτήν την αίθουσα",
             "send_msgtype_active_room": "Στείλτε <b>%(msgtype)s</b> μηνύματα, ώς εσείς, στο ενεργό δωμάτιό σας",
             "send_msgtype_this_room": "Στείλτε <b>%(msgtype)s</b> μηνύματα, ως εσείς, σε αυτό το δωμάτιο",
-            "send_stickers_active_room": "Στείλτε αυτοκόλλητα στο ενεργό δωμάτιο",
-            "send_stickers_active_room_as_you": "Στείλτε αυτοκόλλητα στο ενεργό δωμάτιό σας",
-            "send_stickers_this_room": "Στείλτε αυτοκόλλητα σε αυτό το δωμάτιο",
+            "send_stickers_active_room": "Στείλτε αυτοκόλλητα στην ενεργή σας αίθουσα",
+            "send_stickers_active_room_as_you": "Στείλτε αυτοκόλλητα στην ενεργή σας αίθουσας, ως εσείς",
+            "send_stickers_this_room": "Στείλτε αυτοκόλλητα σε αυτήν την αίθουσα",
             "send_stickers_this_room_as_you": "Στείλτε αυτοκόλλητα σε αυτό το δωμάτιο",
-            "send_text_messages_active_room": "Στείλτε μηνύματα κειμένου, ως εσείς, στο ενεργό δωμάτιό σας",
-            "send_text_messages_this_room": "Στείλτε μηνύματα κειμένου, ως εσείς, σε αυτό το δωμάτιο",
-            "send_videos_active_room": "Στείλτε βίντεο, ως εσείς, στο ενεργό δωμάτιό σας",
-            "send_videos_this_room": "Στείλτε βίντεο, ως εσείς, σε αυτό το δωμάτιο",
+            "send_text_messages_active_room": "Στείλτε μηνύματα κειμένου, ως εσείς, στην ενεργή σας αίθουσα",
+            "send_text_messages_this_room": "Στείλτε μηνύματα κειμένου, ως εσείς, σε αυτήν την αίθουσα",
+            "send_videos_active_room": "Στείλτε βίντεο, ως εσείς, στην ενεργή σας αίθουσα",
+            "send_videos_this_room": "Στείλτε βίντεο, ως εσείς, σε αυτήν την αίθουσα",
             "specific_room": "Τα παραπάνω, αλλά και μέσα <Room />",
             "switch_room": "Αλλάξτε το δωμάτιο που βλέπετε",
             "switch_room_message_user": "Αλλάξτε το δωμάτιο, το μήνυμα ή τον χρήστη που βλέπετε"
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 875da43f14e34a75d5e3f0f66aba9b64d3594ab4..d848f839a53ea063bc57d958ca6a352ffa3afdfc 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -12,6 +12,16 @@
             "other": "%(count)s unread messages including mentions."
         },
         "recent_rooms": "Recent rooms",
+        "room_messsage_not_sent": "Open room %(roomName)s with an unsent message.",
+        "room_n_unread_invite": "Open room %(roomName)s invitation.",
+        "room_n_unread_messages": {
+            "one": "Open room %(roomName)s with 1 unread message.",
+            "other": "Open room %(roomName)s with %(count)s unread messages."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Open room %(roomName)s with 1 unread mention.",
+            "other": "Open room %(roomName)s with %(count)s unread messages including mentions."
+        },
         "room_name": "Room %(name)s",
         "room_status_bar": "Room status bar",
         "seek_bar_label": "Audio seek bar",
@@ -45,6 +55,8 @@
         "create_a_room": "Create a room",
         "create_account": "Create Account",
         "decline": "Decline",
+        "decline_and_block": "Decline and block",
+        "decline_invite": "Decline invite",
         "delete": "Delete",
         "deny": "Deny",
         "disable": "Disable",
@@ -64,6 +76,7 @@
         "go": "Go",
         "go_back": "Go back",
         "got_it": "Got it",
+        "hide": "Hide",
         "hide_advanced": "Hide advanced",
         "hold": "Hold",
         "ignore": "Ignore",
@@ -80,12 +93,14 @@
         "maximise": "Maximise",
         "mention": "Mention",
         "minimise": "Minimise",
+        "new_message": "New message",
         "new_room": "New room",
         "new_video_room": "New video room",
         "next": "Next",
         "no": "No",
         "ok": "OK",
         "open": "Open",
+        "open_menu": "Open menu",
         "pause": "Pause",
         "pin": "Pin",
         "play": "Play",
@@ -94,13 +109,13 @@
         "react": "React",
         "refresh": "Refresh",
         "register": "Register",
-        "reject": "Reject",
         "reload": "Reload",
         "remove": "Remove",
         "rename": "Rename",
         "reply": "Reply",
         "reply_in_thread": "Reply in thread",
         "report_content": "Report Content",
+        "report_room": "Report room",
         "resend": "Resend",
         "reset": "Reset",
         "resume": "Resume",
@@ -142,6 +157,7 @@
         "view_message": "View message",
         "view_source": "View Source",
         "yes": "Yes",
+        "yes_dismiss": "Yes, dismiss",
         "zoom_in": "Zoom in",
         "zoom_out": "Zoom out"
     },
@@ -369,8 +385,9 @@
             "email_resend_prompt": "Did not receive it? <a>Resend it</a>",
             "email_resent": "Resent!",
             "fallback_button": "Start authentication",
-            "mas_cross_signing_reset_cta": "Go to your account",
-            "mas_cross_signing_reset_description": "Reset your identity through your account provider and then come back and click “Retry”.",
+            "mas_cross_signing_reset_cta": "Continue to account",
+            "mas_cross_signing_reset_description": "You're about to go to your %(serverName)s account to reset your identity. Once you have completed reset on your account, please return here and click Retry.",
+            "mas_cross_signing_reset_title": "Go to your account to reset your identity",
             "msisdn": "A text message has been sent to %(msisdn)s",
             "msisdn_token_incorrect": "Token incorrect",
             "msisdn_token_prompt": "Please enter the code it contains:",
@@ -405,7 +422,15 @@
         "download_logs": "Download logs",
         "downloading_logs": "Downloading logs",
         "error_empty": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
-        "failed_send_logs": "Failed to send logs: ",
+        "failed_download_logs": "Failed to download debug logs: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Your bug report was rejected. The rageshake server does not support this application.",
+            "rejected_generic": "Your bug report was rejected. The rageshake server rejected the contents of the report due to a policy.",
+            "rejected_recovery_key": "Your bug report was rejected for safety reasons, as it contained a recovery key.",
+            "rejected_version": "Your bug report was rejected as the version you are running is too old.",
+            "server_unknown_error": "The rageshake server encountered an unknown error and could not handle the report.",
+            "unknown_error": "Failed to send logs."
+        },
         "github_issue": "GitHub issue",
         "introduction": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. ",
         "log_request": "To help us prevent this in future, please <a>send us logs</a>.",
@@ -445,7 +470,7 @@
         "access_token": "Access Token",
         "accessibility": "Accessibility",
         "advanced": "Advanced",
-        "all_rooms": "All rooms",
+        "all_chats": "All Chats",
         "analytics": "Analytics",
         "and_n_others": {
             "one": "and one other...",
@@ -464,7 +489,6 @@
         "capabilities": "Capabilities",
         "copied": "Copied!",
         "credits": "Credits",
-        "cross_signing": "Cross-signing",
         "dark": "Dark",
         "description": "Description",
         "deselect_all": "Deselect all",
@@ -495,7 +519,6 @@
         "legal": "Legal",
         "light": "Light",
         "loading": "Loading…",
-        "lobby": "Lobby",
         "location": "Location",
         "low_priority": "Low priority",
         "matrix": "Matrix",
@@ -504,6 +527,7 @@
         "message_timestamp_invalid": "Invalid timestamp",
         "microphone": "Microphone",
         "model": "Model",
+        "moderation_and_safety": "Moderation and safety",
         "modern": "Modern",
         "mute": "Mute",
         "n_members": {
@@ -548,7 +572,6 @@
         "saved": "Saved",
         "saving": "Saving…",
         "secure_backup": "Secure Backup",
-        "security": "Security",
         "select_all": "Select all",
         "server": "Server",
         "settings": "Settings",
@@ -567,7 +590,6 @@
         "thread": "Thread",
         "threads": "Threads",
         "timeline": "Timeline",
-        "trusted": "Trusted",
         "unavailable": "unavailable",
         "unencrypted": "Not encrypted",
         "unmute": "Unmute",
@@ -727,6 +749,13 @@
         "twemoji": "The <twemoji>Twemoji</twemoji> emoji art is © <author>Twitter, Inc and other contributors</author> used under the terms of <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "The <colr>twemoji-colr</colr> font is © <author>Mozilla Foundation</author> used under the terms of <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Are you sure you want to decline the invitation to join \"%(roomName)s\"?",
+        "ignore_user_help": "You will not see any messages or room invites from this user.",
+        "reason_description": "Describe the reason for reporting the room.",
+        "report_room_description": "Report this room to your account provider.",
+        "title": "Decline invitation"
+    },
     "desktop_default_device_name": "%(brand)s Desktop: %(platformName)s",
     "devtools": {
         "active_widgets": "Active Widgets",
@@ -734,6 +763,44 @@
         "category_room": "Room",
         "caution_colon": "Caution:",
         "client_versions": "Client Versions",
+        "crypto": {
+            "4s_public_key_in_account_data": "in account data",
+            "4s_public_key_not_in_account_data": "not found",
+            "4s_public_key_status": "Secret storage public key:",
+            "backup_key_cached": "cached locally",
+            "backup_key_cached_status": "Backup key cached:",
+            "backup_key_not_stored": "not stored",
+            "backup_key_stored": "in secret storage",
+            "backup_key_stored_status": "Backup key stored:",
+            "backup_key_unexpected_type": "unexpected type",
+            "backup_key_well_formed": "well formed",
+            "cross_signing": "Cross-signing",
+            "cross_signing_cached": "cached locally",
+            "cross_signing_not_ready": "Cross-signing is not set up.",
+            "cross_signing_private_keys_in_storage": "in secret storage",
+            "cross_signing_private_keys_in_storage_status": "Cross-signing private keys:",
+            "cross_signing_private_keys_not_in_storage": "not found in storage",
+            "cross_signing_public_keys_on_device": "in memory",
+            "cross_signing_public_keys_on_device_status": "Cross-signing public keys:",
+            "cross_signing_ready": "Cross-signing is ready for use.",
+            "cross_signing_status": "Cross-signing status:",
+            "cross_signing_untrusted": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
+            "crypto_not_available": "Cryptographic module is not available",
+            "key_backup_active_version": "Active backup version:",
+            "key_backup_active_version_none": "None",
+            "key_backup_inactive_warning": "Your keys are not being backed up from this session.",
+            "key_backup_latest_version": "Latest backup version on server:",
+            "key_storage": "Key Storage",
+            "master_private_key_cached_status": "Master private key:",
+            "not_found": "not found",
+            "not_found_locally": "not found locally",
+            "secret_storage_not_ready": "not ready",
+            "secret_storage_ready": "ready",
+            "secret_storage_status": "Secret storage:",
+            "self_signing_private_key_cached_status": "Self signing private key:",
+            "title": "End-to-end encryption",
+            "user_signing_private_key_cached_status": "User signing private key:"
+        },
         "developer_mode": "Developer mode",
         "developer_tools": "Developer Tools",
         "edit_setting": "Edit setting",
@@ -789,6 +856,9 @@
         "setting_colon": "Setting:",
         "setting_definition": "Setting definition:",
         "setting_id": "Setting ID",
+        "settings": {
+            "elementCallUrl": "Element Call URL"
+        },
         "settings_explorer": "Settings explorer",
         "show_hidden_events": "Show hidden events in timeline",
         "spaces": {
@@ -841,44 +911,25 @@
     "empty_room_was_name": "Empty room (was %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Enter your Security Phrase or <button>use your Security Key</button> to continue.",
+            "alternatives": "If you have a security key or security phrase, this will work too.",
             "key_validation_text": {
-                "invalid_security_key": "Invalid Security Key",
-                "recovery_key_is_correct": "Looks good!",
-                "wrong_file_type": "Wrong file type",
-                "wrong_security_key": "Wrong Security Key"
-            },
-            "reset_title": "Reset everything",
-            "reset_warning_1": "Only do this if you have no other device to complete verification with.",
-            "reset_warning_2": "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.",
+                "wrong_security_key": "The recovery key you entered is not correct."
+            },
+            "privacy_warning": "Make sure nobody can see this screen!",
             "restoring": "Restoring keys from backup",
-            "security_key_title": "Security Key",
-            "security_phrase_incorrect_error": "Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
-            "security_phrase_title": "Security Phrase",
-            "separator": "%(securityKey)s or %(recoveryFile)s",
-            "use_security_key_prompt": "Use your Security Key to continue."
+            "security_key_title": "Recovery key"
         },
         "bootstrap_title": "Setting up keys",
         "cancel_entering_passphrase_description": "Are you sure you want to cancel entering passphrase?",
         "cancel_entering_passphrase_title": "Cancel entering passphrase?",
         "confirm_encryption_setup_body": "Click the button below to confirm setting up encryption.",
         "confirm_encryption_setup_title": "Confirm encryption setup",
-        "cross_signing_not_ready": "Cross-signing is not set up.",
-        "cross_signing_ready": "Cross-signing is ready for use.",
-        "cross_signing_ready_no_backup": "Cross-signing is ready but keys are not backed up.",
         "cross_signing_room_normal": "This room is end-to-end encrypted",
         "cross_signing_room_verified": "Everyone in this room is verified",
         "cross_signing_room_warning": "Someone is using an unknown session",
-        "cross_signing_unsupported": "Your homeserver does not support cross-signing.",
-        "cross_signing_untrusted": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
         "cross_signing_user_normal": "You have not verified this user.",
         "cross_signing_user_verified": "You have verified this user. This user has verified all of their sessions.",
         "cross_signing_user_warning": "This user has not verified all of their sessions.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Clear cross-signing keys",
-            "title": "Destroy cross-signing keys?",
-            "warning": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from."
-        },
         "enter_recovery_key": "Enter recovery key",
         "event_shield_reason_authenticity_not_guaranteed": "The authenticity of this encrypted message can't be guaranteed on this device.",
         "event_shield_reason_mismatched_sender_key": "Encrypted by an unverified session",
@@ -905,9 +956,8 @@
             "title": "New Recovery Method",
             "warning": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings."
         },
-        "not_supported": "<not supported>",
-        "pinned_identity_changed": "%(displayName)s's (<b>%(userId)s</b>) identity appears to have changed. <a>Learn more</a>",
-        "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>'s identity appears to have changed. <a>Learn more</a>",
+        "pinned_identity_changed": "%(displayName)s's (<b>%(userId)s</b>) identity was reset. <a>Learn more</a>",
+        "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>'s identity was reset. <a>Learn more</a>",
         "recovery_method_removed": {
             "description_1": "This session has detected that your Security Phrase and key for Secure Messages have been removed.",
             "description_2": "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.",
@@ -921,9 +971,10 @@
         "set_up_toast_description": "Safeguard against losing access to encrypted messages & data",
         "set_up_toast_title": "Set up Secure Backup",
         "setup_secure_backup": {
-            "explainer": "Back up your keys before signing out to avoid losing them.",
-            "title": "Set up"
+            "explainer": "Back up your keys before signing out to avoid losing them."
         },
+        "turn_on_key_storage": "Turn on key storage",
+        "turn_on_key_storage_description": "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices.",
         "udd": {
             "interactive_verification_button": "Interactively verify by emoji",
             "other_ask_verify_text": "Ask this user to verify their session, or manually verify it below.",
@@ -933,12 +984,10 @@
             "title": "Not Trusted"
         },
         "unable_to_setup_keys_error": "Unable to set up keys",
-        "unsupported": "This client does not support end-to-end encryption.",
         "verification": {
             "accepting": "Accepting…",
             "after_new_login": {
                 "device_verified": "Device verified",
-                "reset_confirmation": "Really reset verification keys?",
                 "skip_verification": "Skip verification for now",
                 "unable_to_verify": "Unable to verify this device",
                 "verify_this_device": "Verify this device"
@@ -960,7 +1009,7 @@
             "incoming_sas_dialog_waiting": "Waiting for partner to confirm…",
             "incoming_sas_user_dialog_text_1": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
             "incoming_sas_user_dialog_text_2": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
-            "no_key_or_device": "It looks like you don't have a Security Key or any other devices you can verify against.  This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.",
+            "no_key_or_device": "It looks like you don't have a Recovery Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.",
             "no_support_qr_emoji": "The device you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
             "other_party_cancelled": "The other party cancelled the verification.",
             "prompt_encrypted": "Verify all users in a room to ensure it's secure.",
@@ -1009,19 +1058,20 @@
             "verify_emoji_prompt": "Verify by comparing unique emoji.",
             "verify_emoji_prompt_qr": "If you can't scan the code above, verify by comparing unique emoji.",
             "verify_later": "I'll verify later",
-            "verify_reset_warning_1": "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.",
-            "verify_reset_warning_2": "Please only proceed if you're sure you've lost all of your other devices and your Security Key.",
             "verify_using_device": "Verify with another device",
-            "verify_using_key": "Verify with Security Key",
-            "verify_using_key_or_phrase": "Verify with Security Key or Phrase",
+            "verify_using_key": "Verify with Recovery Key",
+            "verify_using_key_or_phrase": "Verify with Recovery Key or Phrase",
             "waiting_for_user_accept": "Waiting for %(displayName)s to accept…",
             "waiting_other_device": "Waiting for you to verify on your other device…",
             "waiting_other_device_details": "Waiting for you to verify on your other device, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Waiting for %(displayName)s to verify…"
         },
         "verification_requested_toast_title": "Verification requested",
+        "verified_identity_changed": "%(displayName)s's (<b>%(userId)s</b>) identity was reset. <a>Learn more</a>",
+        "verified_identity_changed_no_displayname": "<b>%(userId)s</b>'s identity was reset. <a>Learn more</a>",
         "verify_toast_description": "Other users may not trust it",
-        "verify_toast_title": "Verify this session"
+        "verify_toast_title": "Verify this session",
+        "withdraw_verification_action": "Withdraw verification"
     },
     "error": {
         "admin_contact": "Please <a>contact your service administrator</a> to continue using this service.",
@@ -1076,11 +1126,7 @@
             "title": "Unable to copy room link"
         },
         "error_loading_user_profile": "Could not load user profile",
-        "forget_room_failed": "Failed to forget room %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Server may be unavailable, overloaded, or search timed out :(",
-            "title": "Search failed"
-        }
+        "forget_room_failed": "Failed to forget room %(errCode)s"
     },
     "error_user_not_logged_in": "User is not logged in",
     "event_preview": {
@@ -1205,6 +1251,7 @@
         "change": "Change identity server",
         "change_prompt": "Disconnect from the identity server <current /> and connect to <new /> instead?",
         "change_server_prompt": "If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.",
+        "changed": "Your identity server has been changed",
         "checking": "Checking server",
         "description_connected": "You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.",
         "description_disconnected": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
@@ -1248,7 +1295,8 @@
         "use_desktop_heading": "Use %(brand)s Desktop instead",
         "use_mobile_heading": "Use %(brand)s on mobile instead",
         "use_mobile_heading_after_desktop": "Or use our mobile app",
-        "windows": "Windows (%(bits)s-bit)"
+        "windows_64bit": "Windows (64-bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
     },
     "info_tooltip_title": "Information",
     "integration_manager": {
@@ -1257,6 +1305,7 @@
         "error_connecting_heading": "Cannot connect to integration manager",
         "explainer": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
         "manage_title": "Manage integrations",
+        "toggle_label": "Enable the integration manager",
         "use_im": "Use an integration manager to manage bots, widgets, and sticker packs.",
         "use_im_default": "Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs."
     },
@@ -1458,6 +1507,7 @@
         "location_share_live_description": "Temporary implementation. Locations persist in room history.",
         "mjolnir": "New ways to ignore people",
         "msc3531_hide_messages_pending_moderation": "Let moderators hide messages pending moderation.",
+        "new_room_list": "Enable new room list",
         "notification_settings": "New Notification Settings",
         "notification_settings_beta_caption": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.",
         "notification_settings_beta_title": "Notification Settings",
@@ -1585,7 +1635,7 @@
             "one": "%(count)s Member",
             "other": "%(count)s Members"
         },
-        "filter_placeholder": "Filter room members",
+        "filter_placeholder": "Search room members",
         "invite_button_no_perms_tooltip": "You do not have permission to invite users",
         "invited_label": "Invited",
         "no_matches": "No matches",
@@ -1611,6 +1661,7 @@
         "class_global": "Global",
         "class_other": "Other",
         "default": "Default",
+        "default_settings": "Match default settings",
         "email_pusher_app_display_name": "Email Notifications",
         "enable_prompt_toast_description": "Enable desktop notifications",
         "enable_prompt_toast_title": "Notifications",
@@ -1627,9 +1678,10 @@
         "mark_all_read": "Mark all as read",
         "mentions_and_keywords": "@mentions & keywords",
         "mentions_and_keywords_description": "Get notified only with mentions and keywords as set up in your <a>settings</a>",
-        "mentions_keywords": "Mentions & keywords",
+        "mentions_keywords": "Mentions and keywords",
         "message_didnt_send": "Message didn't send. Click for info.",
-        "mute_description": "You won't get any notifications"
+        "mute_description": "You won't get any notifications",
+        "mute_room": "Mute room"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s is requesting verification"
@@ -1731,11 +1783,6 @@
         "ongoing": "Removing…",
         "reason_label": "Reason (optional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Are you sure you want to reject the invitation?",
-        "failed": "Failed to reject invitation",
-        "title": "Reject invitation"
-    },
     "report_content": {
         "description": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.",
         "disagree": "Disagree",
@@ -1758,27 +1805,31 @@
         "spam_or_propaganda": "Spam or propaganda",
         "toxic_behaviour": "Toxic Behaviour"
     },
+    "report_room": {
+        "description": "Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them.",
+        "reason_label": "Describe the reason"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Failed to decrypt %(failedCount)s sessions!",
         "count_of_successfully_restored_keys": "Successfully restored %(sessionCount)s keys",
-        "enter_key_description": "Access your secure message history and set up secure messaging by entering your Security Key.",
-        "enter_key_title": "Enter Security Key",
+        "enter_key_description": "Access your secure message history and set up secure messaging by entering your Recovery Key.",
+        "enter_key_title": "Enter Recovery Key",
         "enter_phrase_description": "Access your secure message history and set up secure messaging by entering your Security Phrase.",
         "enter_phrase_title": "Enter Security Phrase",
         "incorrect_security_phrase_dialog": "Backup could not be decrypted with this Security Phrase: please verify that you entered the correct Security Phrase.",
         "incorrect_security_phrase_title": "Incorrect Security Phrase",
         "key_backup_warning": "<b>Warning</b>: you should only set up key backup from a trusted computer.",
         "key_fetch_in_progress": "Fetching keys from server…",
-        "key_forgotten_text": "If you've forgotten your Security Key you can <button>set up new recovery options</button>",
-        "key_is_invalid": "Not a valid Security Key",
-        "key_is_valid": "This looks like a valid Security Key!",
+        "key_forgotten_text": "If you've forgotten your Recovery Key you can <button>set up new recovery options</button>",
+        "key_is_invalid": "Not a valid Recovery Key",
+        "key_is_valid": "This looks like a valid Recovery Key!",
         "keys_restored_title": "Keys restored",
         "load_error_content": "Unable to load backup status",
         "load_keys_progress": "%(completed)s of %(total)s keys restored",
         "no_backup_error": "No backup found!",
-        "phrase_forgotten_text": "If you've forgotten your Security Phrase you can <button1>use your Security Key</button1> or <button2>set up new recovery options</button2>",
-        "recovery_key_mismatch_description": "Backup could not be decrypted with this Security Key: please verify that you entered the correct Security Key.",
-        "recovery_key_mismatch_title": "Security Key mismatch",
+        "phrase_forgotten_text": "If you've forgotten your Security Phrase you can <button1>use your Recovery Key</button1> or <button2>set up new recovery options</button2>",
+        "recovery_key_mismatch_description": "Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key.",
+        "recovery_key_mismatch_title": "Recovery Key mismatch",
         "restore_failed_error": "Unable to restore backup"
     },
     "right_panel": {
@@ -1941,7 +1992,6 @@
             "you_created": "You created this room."
         },
         "invite_email_mismatch_suggestion": "Share this email in Settings to receive invites directly in %(brand)s.",
-        "invite_reject_ignore": "Reject & Ignore user",
         "invite_sent_to_email": "This invite was sent to %(email)s",
         "invite_sent_to_email_room": "This invite to %(roomName)s was sent to %(email)s",
         "invite_subtitle": "Invited by <userName/>",
@@ -2037,38 +2087,90 @@
             },
             "uploading_single_file": "Uploading %(filename)s"
         },
+        "video_room": "This room is a video room",
         "waiting_for_join_subtitle": "Once invited users have joined %(brand)s, you will be able to chat and the room will be end-to-end encrypted",
         "waiting_for_join_title": "Waiting for users to join %(brand)s"
     },
     "room_list": {
         "add_room_label": "Add room",
         "add_space_label": "Add space",
+        "appearance": "Appearance",
         "breadcrumbs_empty": "No recently visited rooms",
         "breadcrumbs_label": "Recently visited rooms",
+        "empty": {
+            "no_chats": "No chats yet",
+            "no_chats_description": "Get started by messaging someone or by creating a room",
+            "no_chats_description_no_room_rights": "Get started by messaging someone",
+            "no_favourites": "You don't have favourite chat yet",
+            "no_favourites_description": "You can add a chat to your favourites in the chat settings",
+            "no_invites": "You don't have any unread invites",
+            "no_mentions": "You don't have any unread mentions",
+            "no_people": "You don’t have direct chats with anyone yet",
+            "no_people_description": "You can deselect filters in order to see your other chats",
+            "no_rooms": "You’re not in any room yet",
+            "no_rooms_description": "You can deselect filters in order to see your other chats",
+            "no_unread": "Congrats! You don’t have any unread messages",
+            "show_activity": "See all activity",
+            "show_chats": "Show all chats"
+        },
         "failed_add_tag": "Failed to add tag %(tagName)s to room",
         "failed_remove_tag": "Failed to remove tag %(tagName)s from room",
         "failed_set_dm_tag": "Failed to set direct message tag",
+        "filters": {
+            "favourite": "Favourites",
+            "invites": "Invites",
+            "mentions": "Mentions",
+            "people": "People",
+            "rooms": "Rooms",
+            "unread": "Unreads"
+        },
         "home_menu_label": "Home options",
         "join_public_room_label": "Join public room",
         "joining_rooms_status": {
             "one": "Currently joining %(count)s room",
             "other": "Currently joining %(count)s rooms"
         },
+        "list_title": "Room list",
+        "more_options": {
+            "copy_link": "Copy room link",
+            "favourited": "Favourited",
+            "leave_room": "Leave room",
+            "low_priority": "Low priority",
+            "mark_read": "Mark as read",
+            "mark_unread": "Mark as unread"
+        },
         "notification_options": "Notification options",
+        "open_space_menu": "Open space menu",
+        "primary_filters": "Room list filters",
         "redacting_messages_status": {
             "one": "Currently removing messages in %(count)s room",
             "other": "Currently removing messages in %(count)s rooms"
         },
+        "room": {
+            "more_options": "More Options",
+            "open_room": "Open room %(roomName)s"
+        },
+        "room_options": "Room Options",
         "show_less": "Show less",
+        "show_message_previews": "Show message previews",
         "show_n_more": {
             "one": "Show %(count)s more",
             "other": "Show %(count)s more"
         },
         "show_previews": "Show previews of messages",
+        "sort": "Sort",
         "sort_by": "Sort by",
         "sort_by_activity": "Activity",
         "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Activity",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Show rooms with unread messages first",
+        "space_menu": {
+            "home": "Space home",
+            "space_settings": "Space Settings"
+        },
         "space_menu_label": "%(spaceName)s menu",
         "sublist_options": "List options",
         "suggested_rooms_heading": "Suggested Rooms"
@@ -2297,7 +2399,7 @@
             "public_without_alias_warning": "To link to this room, please add an address.",
             "publish_room": "Make this room visible in the public room directory.",
             "publish_space": "Make this space visible in the public room directory.",
-            "strict_encryption": "Never send encrypted messages to unverified sessions in this room from this session",
+            "strict_encryption": "Only send messages to verified users.",
             "title": "Security & Privacy"
         },
         "title": "Room Settings - %(roomName)s",
@@ -2322,7 +2424,7 @@
             "enable_element_call_no_permissions_tooltip": "You do not have sufficient permissions to change this."
         }
     },
-    "room_summary_card_back_action_label": "Room information",
+    "room_summary_card_back_action_label": "Room info",
     "scalar": {
         "error_create": "Unable to create widget.",
         "error_membership": "You are not in this room.",
@@ -2351,6 +2453,10 @@
         "recent_changes_heading": "Recent changes that have not yet been received",
         "title": "Server isn't responding"
     },
+    "service_worker_error": {
+        "description": "%(brand)s requires a service worker for loading authenticated media from Matrix content repositories. This is not supported by your browser so you may experience media failing to load.",
+        "title": "Failed to load service worker"
+    },
     "seshat": {
         "error_initialising": "Message search initialisation failed, check <a>your settings</a> for more information",
         "reset_button": "Reset event store",
@@ -2422,10 +2528,47 @@
         "enable_markdown": "Enable Markdown",
         "enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
         "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Your account details, contacts, preferences, and chat list will be kept",
+                "breadcrumb_page": "Reset encryption",
+                "breadcrumb_second_description": "You will lose any message history that’s stored only on the server",
+                "breadcrumb_third_description": "You will need to verify all your existing devices and contacts again",
+                "breadcrumb_title": "Are you sure you want to reset your identity?",
+                "breadcrumb_title_forgot": "Forgot your recovery key? You’ll need to reset your identity.",
+                "breadcrumb_title_sync_failed": "Failed to sync key storage. You need to reset your identity.",
+                "breadcrumb_warning": "Only do this if you believe your account has been compromised.",
+                "details_title": "Encryption details",
+                "do_not_close_warning": "Do not close this window until the reset is finished",
+                "export_keys": "Export keys",
+                "import_keys": "Import keys",
+                "other_people_device_description": "Warning: users who have not explicitly verified with you (e.g. using emoji) will not receive your encrypted messages. Also, unverified devices of verified users will not receive your encrypted messages.",
+                "other_people_device_label": "In encrypted rooms, only send messages to verified users",
+                "other_people_device_title": "Other people’s devices",
+                "reset_identity": "Reset cryptographic identity",
+                "reset_in_progress": "Reset in progress...",
+                "session_id": "Session ID:",
+                "session_key": "Session key:",
+                "title": "Advanced"
+            },
+            "confirm_key_storage_off": "Are you sure you want to keep key storage turned off?",
+            "confirm_key_storage_off_description": "If you sign out of all your devices you will lose your message history and will need to verify all your existing contacts again. <a>Learn more</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Delete key storage",
+                "confirm": "Delete key storage",
+                "description": "Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:",
+                "list_first": "You will not have encrypted message history on new devices",
+                "list_second": "You will lose access to your encrypted messages if you are signed out of %(brand)s everywhere",
+                "title": "Are you sure you want to turn off key storage and delete it?"
+            },
             "device_not_verified_button": "Verify this device",
             "device_not_verified_description": "You need to verify this device in order to view your encryption settings.",
             "device_not_verified_title": "Device not verified",
             "dialog_title": "<strong>Settings:</strong> Encryption",
+            "key_storage": {
+                "allow_key_storage": "Allow key storage",
+                "description": "Store your cryptographic identity and message keys securely on the server. This will allow you to view your message history on any new devices. <a>Learn more</a>",
+                "title": "Key storage"
+            },
             "recovery": {
                 "change_recovery_confirm_button": "Confirm new recovery key",
                 "change_recovery_confirm_description": "Enter your new recovery key below to finish. Your old one will no longer work.",
@@ -2436,7 +2579,8 @@
                 "description": "Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.",
                 "enter_key_error": "The recovery key you entered is not correct.",
                 "enter_recovery_key": "Enter recovery key",
-                "key_storage_warning": "Your key storage is out of sync. Click the button below to fix the problem.",
+                "forgot_recovery_key": "Forgot recovery key?",
+                "key_storage_warning": "Your key storage is out of sync. Click one of the buttons below to fix the problem.",
                 "save_key_description": "Do not share this with anyone!",
                 "save_key_title": "Recovery key",
                 "set_up_recovery": "Set up recovery",
@@ -2493,6 +2637,7 @@
             "discovery_needs_terms_title": "Let people find you",
             "display_name": "Display Name",
             "display_name_error": "Unable to set display name",
+            "email_adding_unsupported_by_hs": "This homeserver does not support adding email addresses to your account.",
             "email_address_in_use": "This email address is already in use",
             "email_address_label": "Email Address",
             "email_not_verified": "Your email address hasn't been verified yet",
@@ -2517,7 +2662,9 @@
             "error_share_msisdn_discovery": "Unable to share phone number",
             "identity_server_no_token": "No identity access token found",
             "identity_server_not_set": "Identity server not set",
+            "invalid_phone_number": "The phone number supplied does not appear to be valid.",
             "language_section": "Language",
+            "msisdn_adding_unsupported_by_hs": "This homeserver does not support adding phone numbers to your account.",
             "msisdn_in_use": "This phone number is already in use",
             "msisdn_label": "Phone Number",
             "msisdn_verification_field_label": "Verification code",
@@ -2536,7 +2683,6 @@
             "unable_to_load_msisdns": "Unable to load phone numbers",
             "username": "Username"
         },
-        "image_thumbnails": "Show previews/thumbnails for images",
         "inline_url_previews_default": "Enable inline URL previews by default",
         "inline_url_previews_room": "Enable URL previews by default for participants in this room",
         "inline_url_previews_room_account": "Enable URL previews for this room (only affects you)",
@@ -2558,21 +2704,21 @@
                 "enter_phrase_description": "Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.",
                 "enter_phrase_title": "Enter a Security Phrase",
                 "enter_phrase_to_confirm": "Enter your Security Phrase a second time to confirm it.",
-                "generate_security_key_description": "We'll generate a Security Key for you to store somewhere safe, like a password manager or a safe.",
-                "generate_security_key_title": "Generate a Security Key",
+                "generate_security_key_description": "We'll generate a Recovery Key for you to store somewhere safe, like a password manager or a safe.",
+                "generate_security_key_title": "Generate a Recovery Key",
                 "pass_phrase_match_failed": "That doesn't match.",
                 "pass_phrase_match_success": "That matches!",
                 "phrase_strong_enough": "Great! This Security Phrase looks strong enough.",
                 "secret_storage_query_failure": "Unable to query secret storage status",
-                "security_key_safety_reminder": "Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
+                "security_key_safety_reminder": "Store your Recovery Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
                 "set_phrase_again": "Go back to set it again.",
                 "settings_reminder": "You can also set up Secure Backup & manage your keys in Settings.",
                 "title_confirm_phrase": "Confirm Security Phrase",
-                "title_save_key": "Save your Security Key",
+                "title_save_key": "Save your Recovery Key",
                 "title_set_phrase": "Set a Security Phrase",
                 "unable_to_setup": "Unable to set up secret storage",
                 "use_different_passphrase": "Use a different passphrase?",
-                "use_phrase_only_you_know": "Use a secret phrase only you know, and optionally save a Security Key to use for backup."
+                "use_phrase_only_you_know": "Use a secret phrase only you know, and optionally save a Recovery Key to use for backup."
             }
         },
         "key_export_import": {
@@ -2599,6 +2745,14 @@
         "labs_mjolnir": {
             "dialog_title": "<strong>Settings:</strong> Ignored Users"
         },
+        "media_preview": {
+            "hide_avatars": "Hide avatars of room and inviter",
+            "hide_media": "Always hide",
+            "media_preview_description": "A hidden media can always be shown by tapping on it",
+            "media_preview_label": "Show media in timeline",
+            "show_in_private": "In private rooms",
+            "show_media": "Always show"
+        },
         "notifications": {
             "default_setting_description": "This setting will be applied by default to all your rooms.",
             "default_setting_section": "I want to be notified for (Default Setting)",
@@ -2684,57 +2838,20 @@
         "prompt_invite": "Prompt before sending invites to potentially invalid matrix IDs",
         "replace_plain_emoji": "Automatically replace plain text Emoji",
         "security": {
-            "4s_public_key_in_account_data": "in account data",
-            "4s_public_key_status": "Secret storage public key:",
             "analytics_description": "Share anonymous data to help us identify issues. Nothing personal. No third parties.",
-            "backup_key_cached_status": "Backup key cached:",
-            "backup_key_stored_status": "Backup key stored:",
-            "backup_key_unexpected_type": "unexpected type",
-            "backup_key_well_formed": "well formed",
-            "backup_keys_description": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.",
             "bulk_options_accept_all_invites": "Accept all %(invitedRooms)s invites",
             "bulk_options_reject_all_invites": "Reject all %(invitedRooms)s invites",
             "bulk_options_section": "Bulk options",
-            "cross_signing_cached": "cached locally",
-            "cross_signing_homeserver_support": "Homeserver feature support:",
-            "cross_signing_homeserver_support_exists": "exists",
-            "cross_signing_in_4s": "in secret storage",
-            "cross_signing_in_memory": "in memory",
-            "cross_signing_master_private_Key": "Master private key:",
-            "cross_signing_not_cached": "not found locally",
-            "cross_signing_not_found": "not found",
-            "cross_signing_not_in_4s": "not found in storage",
-            "cross_signing_not_stored": "not stored",
-            "cross_signing_private_keys": "Cross-signing private keys:",
-            "cross_signing_public_keys": "Cross-signing public keys:",
-            "cross_signing_self_signing_private_key": "Self signing private key:",
-            "cross_signing_user_signing_private_key": "User signing private key:",
-            "cryptography_section": "Cryptography",
             "dehydrated_device_description": "The offline device feature allows you to receive encrypted messages even when you are not logged in to any devices",
             "dehydrated_device_enabled": "Offline device enabled",
-            "delete_backup": "Delete Backup",
-            "delete_backup_confirm_description": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
             "dialog_title": "<strong>Settings:</strong> Security & Privacy",
             "e2ee_default_disabled_warning": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
             "enable_message_search": "Enable message search in encrypted rooms",
             "encryption_section": "Encryption",
-            "error_loading_key_backup_status": "Unable to load key backup status",
-            "export_megolm_keys": "Export E2E room keys",
             "ignore_users_empty": "You have no ignored users.",
             "ignore_users_section": "Ignored users",
-            "import_megolm_keys": "Import E2E room keys",
-            "key_backup_active": "This session is backing up your keys.",
-            "key_backup_active_version": "Active backup version:",
-            "key_backup_active_version_none": "None",
             "key_backup_algorithm": "Algorithm:",
-            "key_backup_can_be_restored": "This backup can be restored on this session",
-            "key_backup_complete": "All keys backed up",
             "key_backup_connect": "Connect this session to Key Backup",
-            "key_backup_connect_prompt": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.",
-            "key_backup_in_progress": "Backing up %(sessionsRemaining)s keys…",
-            "key_backup_inactive": "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.",
-            "key_backup_inactive_warning": "Your keys are <b>not being backed up from this session</b>.",
-            "key_backup_latest_version": "Latest backup version on server:",
             "message_search_disable_warning": "If disabled, messages from encrypted rooms won't appear in search results.",
             "message_search_disabled": "Securely cache encrypted messages locally for them to appear in search results.",
             "message_search_enabled": {
@@ -2754,14 +2871,8 @@
             "message_search_unsupported": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.",
             "record_session_details": "Record the client name, version, and url to recognise sessions more easily in session manager",
-            "restore_key_backup": "Restore from Backup",
-            "secret_storage_not_ready": "not ready",
-            "secret_storage_ready": "ready",
-            "secret_storage_status": "Secret storage:",
             "send_analytics": "Send analytics data",
-            "session_id": "Session ID:",
-            "session_key": "Session key:",
-            "strict_encryption": "Never send encrypted messages to unverified sessions from this session"
+            "strict_encryption": "Only send messages to verified users"
         },
         "send_read_receipts": "Send read receipts",
         "send_read_receipts_unsupported": "Your server doesn't support disabling sending read receipts.",
@@ -3003,8 +3114,6 @@
         "topic": "Gets or sets the room topic",
         "topic_none": "This room has no topic.",
         "topic_room_error": "Failed to get room topic: Unable to find room (%(roomId)s",
-        "tovirtual": "Switches to this room's virtual room, if it has one",
-        "tovirtual_not_found": "No virtual room for this room",
         "unban": "Unbans user with given ID",
         "unflip": "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message",
         "unholdcall": "Takes the call in the current room off hold",
@@ -3022,6 +3131,7 @@
         "view": "Views room with given address",
         "whois": "Displays information about a user"
     },
+    "sliding_sync_legacy_no_longer_supported": "Legacy sliding sync is no longer supported: please log out and back in to enable the new sliding sync flag",
     "space": {
         "add_existing_room_space": {
             "create": "Want to add a new room instead?",
@@ -3126,7 +3236,7 @@
         "heading_without_query": "Search for",
         "join_button_text": "Join %(roomAddress)s",
         "keyboard_scroll_hint": "Use <arrows/> to scroll",
-        "message_search_section_title": "Other searches",
+        "messages_label": "Messages",
         "other_rooms_in_space": "Other rooms in %(spaceName)s",
         "public_rooms_label": "Public rooms",
         "public_spaces_label": "Public spaces",
@@ -3136,7 +3246,6 @@
         "result_may_be_hidden_privacy_warning": "Some results may be hidden for privacy",
         "result_may_be_hidden_warning": "Some results may be hidden",
         "search_dialog": "Search Dialog",
-        "search_messages_hint": "To search messages, look for this icon at the top of a room <icon/>",
         "spaces_title": "Spaces you're in",
         "start_group_chat_button": "Start a group chat"
     },
@@ -3185,9 +3294,7 @@
     "threads_activity_centre": {
         "header": "Threads activity",
         "no_rooms_with_threads_notifs": "You don't have rooms with thread notifications yet.",
-        "no_rooms_with_unread_threads": "You don't have rooms with unread threads yet.",
-        "release_announcement_description": "Threads notifications have moved, find them here from now on.",
-        "release_announcement_header": "Threads Activity Centre"
+        "no_rooms_with_unread_threads": "You don't have rooms with unread threads yet."
     },
     "time": {
         "about_day_ago": "about a day ago",
@@ -3235,7 +3342,7 @@
             "historical_event_no_key_backup": "Historical messages are not available on this device",
             "historical_event_unverified_device": "You need to verify this device for access to historical messages",
             "historical_event_user_not_joined": "You don't have access to this message",
-            "sender_identity_previously_verified": "Sender's verified identity has changed",
+            "sender_identity_previously_verified": "Sender's verified identity was reset",
             "sender_unsigned_device": "Sent from an insecure device.",
             "unable_to_decrypt": "Unable to decrypt message"
         },
@@ -3399,6 +3506,7 @@
             "left_reason": "%(targetName)s left the room: %(reason)s",
             "no_change": "%(senderName)s made no change",
             "reject_invite": "%(targetName)s rejected the invitation",
+            "reject_invite_reason": "%(targetName)s rejected the invitation: %(reason)s",
             "remove_avatar": "%(senderName)s removed their profile picture",
             "remove_name": "%(senderName)s removed their display name (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s set a profile picture",
@@ -3434,10 +3542,14 @@
             "sent": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room."
         },
         "m.room.tombstone": "%(senderDisplayName)s upgraded this room.",
-        "m.room.topic": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s removed the topic."
+        },
         "m.sticker": "%(senderDisplayName)s sent a sticker.",
         "m.video": {
-            "error_decrypting": "Error decrypting video"
+            "error_decrypting": "Error decrypting video",
+            "show_video": "Show video"
         },
         "m.widget": {
             "added": "%(widgetName)s widget added by %(senderName)s",
@@ -3693,10 +3805,11 @@
         "unavailable": "Unavailable"
     },
     "update_room_access_modal": {
-        "description": "To create a share link, you need to allow guests to join this room. This may make the room less secure. When you're done with the call, you can make the room private again.",
-        "dont_change_description": "Alternatively, you can hold the call in a separate room.",
+        "description": "To create a share link, make this room <b>public</b> or enable the option for users to <b>ask to join</b>. This allows guests to join without being invited.",
+        "dont_change_description": "If you don't want to change the access of this room, you can create a new room for the call link.",
         "no_change": "I don't want to change the access level.",
-        "title": "Change the room access level"
+        "revert_access_description": "(This can be reverted to the previous value in the Room Settings: <b>Security & Privacy</b> / <b>Access</b>)",
+        "title": "Allow guest users to join this room"
     },
     "upload_failed_generic": "The file '%(fileName)s' failed to upload.",
     "upload_failed_size": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
@@ -3723,18 +3836,9 @@
         "ban_room_confirm_title": "Ban from %(roomName)s",
         "ban_space_everything": "Ban them from everything I'm able to",
         "ban_space_specific": "Ban them from specific things I'm able to",
-        "count_of_sessions": {
-            "one": "%(count)s session",
-            "other": "%(count)s sessions"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 verified session",
-            "other": "%(count)s verified sessions"
-        },
         "deactivate_confirm_action": "Deactivate user",
         "deactivate_confirm_description": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?",
         "deactivate_confirm_title": "Deactivate user?",
-        "dehydrated_device_enabled": "Offline device enabled",
         "demote_button": "Demote",
         "demote_self_confirm_description_space": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.",
         "demote_self_confirm_room": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.",
@@ -3742,15 +3846,12 @@
         "disinvite_button_room": "Disinvite from room",
         "disinvite_button_room_name": "Disinvite from %(roomName)s",
         "disinvite_button_space": "Disinvite from space",
-        "edit_own_devices": "Edit devices",
         "error_ban_user": "Failed to ban user",
         "error_deactivate": "Failed to deactivate user",
         "error_kicking_user": "Failed to remove user",
         "error_mute_user": "Failed to mute user",
         "error_revoke_3pid_invite_description": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
         "error_revoke_3pid_invite_title": "Failed to revoke invite",
-        "hide_sessions": "Hide sessions",
-        "hide_verified_sessions": "Hide verified sessions",
         "ignore_button": "Ignore",
         "ignore_confirm_description": "All messages and invites from this user will be hidden. Are you sure you want to ignore them?",
         "ignore_confirm_title": "Ignore %(user)s",
@@ -3794,6 +3895,7 @@
         "unban_space_specific": "Unban them from specific things I'm able to",
         "unban_space_warning": "They won't be able to access whatever you're not an admin of.",
         "unignore_button": "Unignore",
+        "verification_unavailable": "User verification unavailable",
         "verify_button": "Verify User",
         "verify_explainer": "For extra security, verify this user by checking a one-time code on both of your devices."
     },
@@ -3846,7 +3948,6 @@
         "input_devices": "Input devices",
         "jitsi_call": "Jitsi Conference",
         "join_button_tooltip_call_full": "Sorry — this call is currently full",
-        "join_button_tooltip_connecting": "Connecting",
         "legacy_call": "Legacy Call",
         "maximise": "Fill screen",
         "maximise_call": "Maximise call",
@@ -4001,7 +4102,7 @@
         "error_need_to_be_logged_in": "You need to be logged in.",
         "error_unable_start_audio_stream_description": "Unable to start audio streaming.",
         "error_unable_start_audio_stream_title": "Failed to start livestream",
-        "modal_data_warning": "Data on this screen is shared with %(widgetDomain)s",
+        "modal_data_warning": "The data below is shared with %(widgetDomain)s",
         "modal_title_default": "Modal Widget",
         "no_name": "Unknown App",
         "open_id_permissions_dialog": {
diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json
index 91a5a024446c7082cfef9a1d2f4f693bf644c465..a6f28167ea2af3f3b9fb4b0fc702d050767b267d 100644
--- a/src/i18n/strings/eo.json
+++ b/src/i18n/strings/eo.json
@@ -75,7 +75,6 @@
         "react": "Reagi",
         "refresh": "Aktualigi",
         "register": "Registri",
-        "reject": "Rifuzi",
         "remove": "Forigi",
         "reply": "Respondi",
         "report_content": "Raporti enhavon",
@@ -301,7 +300,6 @@
         "download_logs": "Elŝuti protokolon",
         "downloading_logs": "Elŝutante protokolon",
         "error_empty": "Bonvolu diri al ni kio misokazis, aŭ pli bone raporti problemon per GitHub.",
-        "failed_send_logs": "Malsukcesis sendi protokolon: ",
         "github_issue": "Problemo per GitHub",
         "log_request": "Por malhelpi tion ose, bonvolu <a>sendi al ni protokolon</a>.",
         "logs_sent": "Protokolo sendiĝis",
@@ -334,7 +332,6 @@
         "access_token": "Alirpeco",
         "accessibility": "Alirebleco",
         "advanced": "Altnivela",
-        "all_rooms": "Ĉiuj ĉambroj",
         "analytics": "Analizo",
         "and_n_others": {
             "other": "kaj %(count)s aliaj…",
@@ -349,7 +346,6 @@
         "camera": "Kamerao",
         "copied": "Kopiita!",
         "credits": "Dankoj",
-        "cross_signing": "Delegaj subskriboj",
         "dark": "Malhela",
         "description": "Priskribo",
         "edited": "redaktita",
@@ -419,7 +415,6 @@
         "room_name": "Nomo de ĉambro",
         "rooms": "Ĉambroj",
         "secure_backup": "Sekura savkopiado",
-        "security": "Sekureco",
         "settings": "Agordoj",
         "setup_secure_messages": "Agordi Sekurajn mesaĝojn",
         "show_more": "Montri pli",
@@ -436,7 +431,6 @@
         "thread": "Fadeno",
         "threads": "Fadenoj",
         "timeline": "Historio",
-        "trusted": "Fidata",
         "unencrypted": "Neĉifrita",
         "unmute": "Malsilentigi",
         "unnamed_room": "Sennoma Ĉambro",
@@ -604,41 +598,22 @@
     "encryption": {
         "access_secret_storage_dialog": {
             "key_validation_text": {
-                "invalid_security_key": "Nevalida Sekureca ŝlosilo",
-                "recovery_key_is_correct": "Ŝajnas bona!",
-                "wrong_file_type": "Neĝusta dosiertipo",
                 "wrong_security_key": "Malĝusta Sekureca ŝlosilo"
             },
-            "reset_title": "Restarigi ĉion",
-            "reset_warning_1": "Faru tion ĉi nur se vi ne havas alian aparaton, per kiu vi kontrolus ceterajn.",
-            "reset_warning_2": "Se vi restarigos ĉion, vi rekomencos sen fidataj salutaĵoj, uzantoj, kaj eble ne povos vidi antaŭajn mesaĝojn.",
             "restoring": "Rehavo de ŝlosiloj el savkopio",
-            "security_key_title": "Sekureca ŝlosilo",
-            "security_phrase_incorrect_error": "Ne povas akiri sekretandeponejon. Bonvolu kontroli, ĉu vi enigis la ĝustan Sekurecan frazon.",
-            "security_phrase_title": "Sekureca frazo",
-            "use_security_key_prompt": "Uzu vian sekurecan ŝlosilon por daŭrigi."
+            "security_key_title": "Sekureca ŝlosilo"
         },
         "bootstrap_title": "Agordo de klavoj",
         "cancel_entering_passphrase_description": "Ĉu vi certe volas nuligi enigon de pasfrazo?",
         "cancel_entering_passphrase_title": "Ĉu nuligi enigon de pasfrazo?",
         "confirm_encryption_setup_body": "Klaku sube la butonon por konfirmi agordon de ĉifrado.",
         "confirm_encryption_setup_title": "Konfirmi agordon de ĉifrado",
-        "cross_signing_not_ready": "Delegaj subskriboj ne estas agorditaj.",
-        "cross_signing_ready": "Delegaj subskriboj estas pretaj por uzado.",
-        "cross_signing_ready_no_backup": "Delegaj subskriboj pretas, sed ŝlosiloj ne estas savkopiitaj.",
         "cross_signing_room_normal": "Ĉi tiu ĉambro uzas tutvojan ĉifradon",
         "cross_signing_room_verified": "Ĉiu en la ĉambro estas kontrolita",
         "cross_signing_room_warning": "Iu uzas nekonatan salutaĵon",
-        "cross_signing_unsupported": "Via hejmservilo ne subtenas delegajn subskribojn.",
-        "cross_signing_untrusted": "Via konto havas identecon por delegaj subskriboj en sekreta deponejo, sed ĉi tiu salutaĵo ankoraŭ ne fidas ĝin.",
         "cross_signing_user_normal": "Vi ne kontrolis tiun ĉi uzanton.",
         "cross_signing_user_verified": "Vi kontrolis tiun ĉi uzanton. Ĝi kontrolis ĉiomon da siaj salutaĵoj.",
         "cross_signing_user_warning": "Ĉi tiu uzanto ne kontrolis ĉiomon da siaj salutaĵoj.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Vakigi delege ĉifrajn ŝlosilojn",
-            "title": "Ĉu detrui delege ĉifrajn ŝlosilojn?",
-            "warning": "Forigo de delege ĉifraj ŝlosiloj estas porĉiama. Ĉiu, kun kiu vi interkontrolis, vidos avertojn pri sekureco. Vi preskaŭ certe ne volas ĉi tion fari, malse vi perdis ĉiun aparaton, el kiu vi povus delege subskribadi."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "La aŭtentikeco de ĉi tiu ĉifrita mesaĝo ne povas esti garantiita sur ĉi tiu aparato.",
         "event_shield_reason_mismatched_sender_key": "Ĉifrita de nekontrolita salutaĵo",
         "export_unsupported": "Via foliumilo ne subtenas la bezonatajn ĉifrajn kromprogramojn",
@@ -655,7 +630,6 @@
             "title": "Nova rehava metodo",
             "warning": "Se vi ne agordis la novan rehavan metodon, eble atakanto provas aliri vian konton. Vi tuj ŝanĝu la pasvorton de via konto, kaj agordu novan rehavan metodon en la agordoj."
         },
-        "not_supported": "<nesubtenata>",
         "recovery_method_removed": {
             "description_1": "Tiu ĉi salutaĵo trovis, ke viaj Sekureca frazo kaj ŝlosilo por Sekuraj mesaĝoj foriĝis.",
             "description_2": "Se vi faris tion akcidente, vi povas agordi Sekurajn mesaĝojn en ĉi tiu salutaĵo, kio reĉifros la historion de mesaj de ĉi tiu salutaĵo kun nova rehava metodo.",
@@ -666,8 +640,7 @@
         "set_up_toast_description": "Malhelpu perdon de aliro al ĉifritaj mesaĝoj kaj datumoj",
         "set_up_toast_title": "Agordi Sekuran savkopiadon",
         "setup_secure_backup": {
-            "explainer": "Savkopiu viajn ŝlosilojn antaŭ adiaŭo, por ilin ne perdi.",
-            "title": "Agordi"
+            "explainer": "Savkopiu viajn ŝlosilojn antaŭ adiaŭo, por ilin ne perdi."
         },
         "udd": {
             "other_ask_verify_text": "Petu, ke ĉi tiu la uzanto kontrolu sian salutaĵon, aŭ kontrolu ĝin permane sube.",
@@ -677,7 +650,6 @@
             "title": "Nefidata"
         },
         "unable_to_setup_keys_error": "Ne povas agordi ŝlosilojn",
-        "unsupported": "Ĉi tiu kliento ne subtenas tutvojan ĉifradon.",
         "verification": {
             "accepting": "Akceptante…",
             "cancelled": "Vi nuligis kontrolon.",
@@ -782,11 +754,7 @@
             "title": "Ne povas kopii ligilon al ĉambro"
         },
         "error_loading_user_profile": "Ne povis enlegi profilon de uzanto",
-        "forget_room_failed": "Malsukcesis forgesi ĉambron %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Aŭ la servilo estas neatingebla aŭ troŝarĝita, aŭ la serĉo eltempiĝis :(",
-            "title": "Serĉo malsukcesis"
-        }
+        "forget_room_failed": "Malsukcesis forgesi ĉambron %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1224,11 +1192,6 @@
         "ongoing": "Forigante…",
         "reason_label": "Kialo (malnepra)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Ĉu vi certe volas rifuzi la inviton?",
-        "failed": "Malsukcesis rifuzi la inviton",
-        "title": "Rifuzi inviton"
-    },
     "report_content": {
         "description": "Per raporto de ĉi tiu mesaĝo vi sendos ĝian unikan «identigilon de okazo» al la administranto de via hejmservilo. Se mesaĝoj en ĉi tiu ĉambro estas ĉifrataj, la administranto de via hejmservilo ne povos legi la tekston de la mesaĝo, nek rigardi dosierojn aŭ bildojn.",
         "disagree": "Malkonsento",
@@ -1329,7 +1292,6 @@
             "you_created": "Vi kreis ĉi tiun ĉambron."
         },
         "invite_email_mismatch_suggestion": "Havigu ĉi tiun retpoŝtadreson per Agordoj por ricevadi invitojn rekte per %(brand)s.",
-        "invite_reject_ignore": "Rifuzi kaj malatenti uzanton",
         "invite_sent_to_email_room": "La invito al %(roomName)s sendiĝis al %(email)s",
         "invite_subtitle": "<userName/> vin invitis",
         "invite_this_room": "Inviti al ĉi tiu ĉambro",
@@ -1731,7 +1693,6 @@
             "remove_email_prompt": "Ĉu forigi %(email)s?",
             "remove_msisdn_prompt": "Ĉu forigi %(phone)s?"
         },
-        "image_thumbnails": "Montri antaŭrigardojn/bildetojn por bildoj",
         "inline_url_previews_default": "Ŝalti entekstan antaŭrigardon al retadresoj",
         "inline_url_previews_room": "Ŝalti URL-antaŭrigardon por anoj de ĉi tiu ĉambro",
         "inline_url_previews_room_account": "Ŝalti URL-antaŭrigardon en ĉi tiu ĉambro (nur por vi)",
@@ -1823,48 +1784,16 @@
         "prompt_invite": "Averti antaŭ ol sendi invitojn al eble nevalidaj Matrix-identigiloj",
         "replace_plain_emoji": "Memfare anstataŭigi tekstajn mienetojn",
         "security": {
-            "4s_public_key_in_account_data": "en datumoj de konto",
-            "4s_public_key_status": "Publika ŝlosilo de sekreta deponejo:",
-            "backup_key_cached_status": "Kaŝmemorita Savkopia ŝlosilo:",
-            "backup_key_stored_status": "Deponita Savkopia ŝlosilo:",
-            "backup_key_unexpected_type": "neatendita tipo",
-            "backup_key_well_formed": "bone formita",
-            "backup_keys_description": "Savkopiu viajn čifrajn šlosilojn kune kun la datumoj de via konto, okaze ke vi perdos aliron al viaj salutaĵoj. Viaj ŝlosiloj sekuriĝos per unika Sekureca ŝlosilo.",
             "bulk_options_accept_all_invites": "Akcepti ĉiujn %(invitedRooms)s invitojn",
             "bulk_options_reject_all_invites": "Rifuzi ĉiujn %(invitedRooms)s invitojn",
             "bulk_options_section": "Amasaj elektebloj",
-            "cross_signing_cached": "kaŝmemorita loke",
-            "cross_signing_homeserver_support": "Funkciaj kapabloj de hejmservilo:",
-            "cross_signing_homeserver_support_exists": "ekzistas",
-            "cross_signing_in_4s": "en sekreta deponejo",
-            "cross_signing_in_memory": "en memoro",
-            "cross_signing_master_private_Key": "Ĉefa privata ŝlosilo:",
-            "cross_signing_not_cached": "ne trovita loke",
-            "cross_signing_not_found": "ne trovita",
-            "cross_signing_not_in_4s": "netrovite en deponejo",
-            "cross_signing_not_stored": "ne deponita",
-            "cross_signing_private_keys": "Delegaj privataj ŝlosiloj:",
-            "cross_signing_public_keys": "Delegaj publikaj ŝlosiloj:",
-            "cross_signing_self_signing_private_key": "Memsubskriba privata ŝlosilo:",
-            "cross_signing_user_signing_private_key": "Uzantosubskriba privata ŝlosilo:",
-            "cryptography_section": "Ĉifroteĥnikaro",
-            "delete_backup": "Forigi savkopion",
-            "delete_backup_confirm_description": "Ĉu vi certas? Vi perdos ĉiujn viajn ĉifritajn mesaĝojn, se viaj ŝlosiloj ne estas savkopiitaj.",
             "e2ee_default_disabled_warning": "La administranto de via servilo malŝaltis implicitan tutvojan ĉifradon en privataj kaj individuaj ĉambroj.",
             "enable_message_search": "Ŝalti serĉon de mesaĝoj en ĉifritaj ĉambroj",
             "encryption_section": "Ĉifrado",
-            "error_loading_key_backup_status": "Ne povas enlegi staton de ŝlosila savkopio",
-            "export_megolm_keys": "Elporti tutvoje ĉifrajn ŝlosilojn de la ĉambro",
             "ignore_users_empty": "Vi malatentas neniujn uzantojn.",
             "ignore_users_section": "Malatentaj uzantoj",
-            "import_megolm_keys": "Enporti tutvoje ĉifrajn ĉambrajn ŝlosilojn",
-            "key_backup_active_version_none": "Neniu",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_complete": "Ĉiuj ŝlosiloj estas savkopiitaj",
             "key_backup_connect": "Konekti ĉi tiun salutaĵon al Savkopiado de ŝlosiloj",
-            "key_backup_connect_prompt": "Konektu ĉi tiun salutaĵon al savkopiado de ŝlosiloj antaŭ ol vi adiaŭos, por ne perdi ŝlosilojn, kiuj povus troviĝi nur en ĉi tiu salutaĵo.",
-            "key_backup_inactive": "Ĉi tiu salutaĵo <b>ne savkopias viajn ŝlosilojn</b>, sed vi jam havas savkopion, el kiu vi povas rehavi datumojn, kaj ilin kreskigi plue.",
-            "key_backup_inactive_warning": "Viaj ŝlosiloj <b>ne estas savkopiataj el ĉi tiu salutaĵo</b>.",
             "message_search_disable_warning": "Post malŝalto, mesaĝoj el ĉifritaj ĉambroj ne aperos en serĉorezultoj.",
             "message_search_disabled": "Sekure kaŝmemori ĉifritajn mesaĝojn loke, por aperigi ilin en serĉrezultoj.",
             "message_search_enabled": {
@@ -1883,13 +1812,7 @@
             "message_search_space_used": "Spaco uzita:",
             "message_search_unsupported": "%(brand)s malhavas kelkajn partojn bezonajn por sekura loka kaŝmemorado de ĉirfitaj mesaĝoj. Se vi volas eksperimenti pri ĉi tiu kapablo, kunmetu propran klienton «%(brand)s Dekstop» kun <nativeLink>aldonitaj serĉopartoj</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s ne povas sekure kaŝkopii ĉifritajn mesaĝojn loke, funkciante per foliumilo. Uzu <desktopLink>%(brand)s Desktop</desktopLink> por aperigi ĉifritajn mesaĝojn en serĉrezultoj.",
-            "restore_key_backup": "Rehavi el savkopio",
-            "secret_storage_not_ready": "neprete",
-            "secret_storage_ready": "prete",
-            "secret_storage_status": "Sekreta deponejo:",
             "send_analytics": "Sendi statistikajn datumojn",
-            "session_id": "Identigilo de salutaĵo:",
-            "session_key": "Ŝlosilo de salutaĵo:",
             "strict_encryption": "Neniam sendi ĉifritajn mesaĝojn al nekontrolitaj salutaĵoj de ĉi tiu salutaĵo"
         },
         "send_read_receipts": "Sendi legitajn kvitanojn",
@@ -2020,8 +1943,6 @@
         "topic": "Ekhavas aŭ agordas la temon de la ĉambro",
         "topic_none": "Ĉi tiu ĉambro ne havas temon.",
         "topic_room_error": "Malsukcesis akiri temo de ĉambro: Ne povas trovi ĉambron (%(roomId)s)",
-        "tovirtual": "Iri al virtuala ĉambro de tiu ĉambro, se la virtuala ĉambro ekzistas",
-        "tovirtual_not_found": "Tiu ĉambro ne havas virtuala ĉambro",
         "unban": "Malforbaras uzanton kun la donita identigilo",
         "unflip": "Antaŭmetas ┬──┬ ノ( ゜-゜ノ) al platteksta mesaĝo",
         "unholdcall": "Malpaŭzigas la vokon en la nuna ĉambro",
@@ -2343,7 +2264,9 @@
             "sent": "%(senderName)s sendis ĉambran inviton al %(targetDisplayName)s."
         },
         "m.room.tombstone": "%(senderDisplayName)s gradaltigis ĉi tiun ĉambron.",
-        "m.room.topic": "%(senderDisplayName)s ŝanĝis la temon al « %(topic)s ».",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s ŝanĝis la temon al « %(topic)s »."
+        },
         "m.sticker": "%(senderDisplayName)s sendis glumarkon.",
         "m.video": {
             "error_decrypting": "Malĉifro de filmo eraris"
@@ -2549,14 +2472,6 @@
     },
     "user_info": {
         "admin_tools_section": "Estriloj",
-        "count_of_sessions": {
-            "other": "%(count)s salutaĵoj",
-            "one": "%(count)s salutaĵo"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s kontrolitaj salutaĵoj",
-            "one": "1 kontrolita salutaĵo"
-        },
         "deactivate_confirm_action": "Malaktivigi uzanton",
         "deactivate_confirm_description": "Malaktivigo de ĉi tiu uzanto adiaŭigos ĝin, kaj malebligos, ke ĝi resalutu. Plie, ĝi foriros de ĉiuj enataj ĉambroj. Tiu ago ne povas malfariĝi. Ĉu vi certe volas malaktivigi ĉi tiun uzanton?",
         "deactivate_confirm_title": "Ĉu malaktivigi uzanton?",
@@ -2564,14 +2479,11 @@
         "demote_self_confirm_description_space": "Vi ne povos malfari ĉi tiun ŝanĝon, ĉar vi malrangaltigas vin mem; se vi estas la lasta altranga uzanto de la aro, vi ne plu povos rehavi viajn rajtojn.",
         "demote_self_confirm_room": "Vi ne povos malfari tiun ŝanĝon, ĉar vi malrangaltigas vin mem; se vi estas la lasta povohava uzanto en la ĉambro, estos neeble vian povon rehavi.",
         "demote_self_confirm_title": "Ĉu malrangaltigi vin mem?",
-        "edit_own_devices": "Redakti aparatojn",
         "error_ban_user": "Malsukcesis forbari uzanton",
         "error_deactivate": "Malsukcesis malaktivigi uzanton",
         "error_mute_user": "Malsukcesis silentigi uzanton",
         "error_revoke_3pid_invite_description": "Ne povis senvalidigi inviton. Aŭ la servilo nun trairas problemon, aŭ vi ne havas sufiĉajn permesojn.",
         "error_revoke_3pid_invite_title": "Malsukcesis senvalidigi inviton",
-        "hide_sessions": "Kaŝi salutaĵojn",
-        "hide_verified_sessions": "Kaŝi kontrolitajn salutaĵojn",
         "invited_by": "Invitita de %(sender)s",
         "jump_to_rr_button": "Salti al legokonfirmo",
         "promote_warning": "Tiun ĉi ŝanĝon vi ne povos malfari, ĉar vi donas al la uzanto la saman povnivelon, kiun havas vi mem.",
@@ -2632,7 +2544,6 @@
         "expand": "Reveni al voko",
         "hangup": "Fini vokon",
         "hide_sidebar_button": "Kaŝi flankan breton",
-        "join_button_tooltip_connecting": "Konektante",
         "misconfigured_server": "Voko malsukcesis pro misagordita servilo",
         "misconfigured_server_description": "Bonvolu peti la administranton de via hejmservilo (<code>%(homeserverDomain)s</code>) agordi TURN-servilon, por ke vokoj funkciu dependeble.",
         "more_button": "Pli",
diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json
index c0dbba66a4235fe46a86a79365377ed34246d1a9..6abbdbe8198e20642e8441eb3cbd752d7988ce70 100644
--- a/src/i18n/strings/es.json
+++ b/src/i18n/strings/es.json
@@ -89,7 +89,6 @@
         "react": "Reaccionar",
         "refresh": "Refrescar",
         "register": "Crear cuenta",
-        "reject": "Rechazar",
         "reload": "Recargar",
         "remove": "Eliminar",
         "rename": "Cambiar nombre",
@@ -349,7 +348,6 @@
         "download_logs": "Descargar registros",
         "downloading_logs": "Descargando registros",
         "error_empty": "Por favor, cuéntanos qué ha fallado o, mejor aún, crea una incidencia en GitHub describiendo el problema.",
-        "failed_send_logs": "Error al enviar registros: ",
         "github_issue": "Incidencia de GitHub",
         "introduction": "Si ya has informado de un fallo a través de GitHub, los registros de depuración nos pueden ayudar a investigar mejor el problema. ",
         "log_request": "Para ayudarnos a prevenir esto en el futuro, por favor, <a>envíanos logs</a>.",
@@ -388,7 +386,6 @@
         "access_token": "Token de acceso",
         "accessibility": "Accesibilidad",
         "advanced": "Avanzado",
-        "all_rooms": "Todas las salas",
         "analytics": "Analítica de datos",
         "and_n_others": {
             "other": "y %(count)s más…",
@@ -406,7 +403,6 @@
         "capabilities": "Funcionalidades",
         "copied": "¡Copiado!",
         "credits": "Créditos",
-        "cross_signing": "Firma cruzada",
         "dark": "Oscuro",
         "description": "Descripción",
         "deselect_all": "Deseleccionar todo",
@@ -482,7 +478,6 @@
         "rooms": "Salas",
         "saving": "Guardando…",
         "secure_backup": "Copia de seguridad segura",
-        "security": "Seguridad",
         "select_all": "Seleccionar todo",
         "server": "Servidor",
         "settings": "Ajustes",
@@ -501,7 +496,6 @@
         "thread": "Hilo",
         "threads": "Hilos",
         "timeline": "Línea de tiempo",
-        "trusted": "De confianza",
         "unavailable": "no disponible",
         "unencrypted": "Sin cifrar",
         "unmute": "Dejar de silenciar",
@@ -747,44 +741,23 @@
     "empty_room_was_name": "Sala vacía (antes era %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Escribe tu frase de seguridad o <button>usa tu clave de seguridad</button> para continuar.",
             "key_validation_text": {
-                "invalid_security_key": "Clave de seguridad inválida",
-                "recovery_key_is_correct": "¡Se ve bien!",
-                "wrong_file_type": "Tipo de archivo incorrecto",
                 "wrong_security_key": "Clave de seguridad incorrecta"
             },
-            "reset_title": "Restablecer todo",
-            "reset_warning_1": "Solo haz esto si no tienes ningún otro dispositivo con el que completar la verificación.",
-            "reset_warning_2": "Si restableces todo, volverás a empezar sin sesiones ni usuarios de confianza, y puede que no puedas ver mensajes anteriores.",
             "restoring": "Restaurando las claves desde copia de seguridad",
-            "security_key_title": "Clave de seguridad",
-            "security_phrase_incorrect_error": "No se ha podido acceder al almacenamiento seguro. Por favor, comprueba que la frase de seguridad es correcta.",
-            "security_phrase_title": "Frase de seguridad",
-            "separator": "%(securityKey)s o %(recoveryFile)s",
-            "use_security_key_prompt": "Usa tu llave de seguridad para continuar."
+            "security_key_title": "Clave de seguridad"
         },
         "bootstrap_title": "Configurando claves",
         "cancel_entering_passphrase_description": "¿Estas seguro que quieres cancelar el ingresar tu contraseña de recuperación?",
         "cancel_entering_passphrase_title": "¿Cancelar el ingresar tu contraseña de recuperación?",
         "confirm_encryption_setup_body": "Haz clic en el botón de abajo para confirmar la configuración del cifrado.",
         "confirm_encryption_setup_title": "Confirmar la configuración de cifrado",
-        "cross_signing_not_ready": "La firma cruzada no está configurada.",
-        "cross_signing_ready": "La firma cruzada está lista para su uso.",
-        "cross_signing_ready_no_backup": "La firma cruzada está lista, pero no hay copia de seguridad de las claves.",
         "cross_signing_room_normal": "Esta sala usa cifrado de extremo a extremo",
         "cross_signing_room_verified": "Todos los participantes en esta sala están verificados",
         "cross_signing_room_warning": "Alguien está usando una sesión desconocida",
-        "cross_signing_unsupported": "Tu servidor base no soporta las firmas cruzadas.",
-        "cross_signing_untrusted": "Su cuenta tiene una identidad de firma cruzada en un almacenamiento secreto, pero aún no es confiada en esta sesión.",
         "cross_signing_user_normal": "No has verificado a este usuario.",
         "cross_signing_user_verified": "Usted ha verificado este usuario. Este usuario ha verificado todas sus sesiones.",
         "cross_signing_user_warning": "Este usuario no ha verificado todas sus sesiones.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Borrar claves de firma cruzada",
-            "title": "¿Destruir las claves de firma cruzada?",
-            "warning": "La eliminación de claves de firma cruzada es definitiva. Cualquiera con el que lo hayas verificado verá alertas de seguridad. Es casi seguro que no quieres hacer esto, a menos que hayas perdido todos los dispositivos puedas usar hacer una firma cruzada."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "La autenticidad de este mensaje cifrado no puede ser garantizada en este dispositivo.",
         "event_shield_reason_mismatched_sender_key": "Cifrado por una sesión no verificada",
         "export_unsupported": "Su navegador no soporta las extensiones de criptografía requeridas",
@@ -804,7 +777,6 @@
             "title": "Nuevo método de recuperación",
             "warning": "Si no configuró el nuevo método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración."
         },
-        "not_supported": "<no soportado>",
         "recovery_method_removed": {
             "description_1": "Esta sesión ha detectado que tu frase de seguridad y clave para mensajes seguros ha sido eliminada.",
             "description_2": "Si hizo esto accidentalmente, puede configurar Mensajes seguros en esta sesión que volverá a cifrar el historial de mensajes de esta sesión con un nuevo método de recuperación.",
@@ -815,8 +787,7 @@
         "set_up_toast_description": "Evita perder acceso a datos y mensajes cifrados",
         "set_up_toast_title": "Configurar copia de seguridad segura",
         "setup_secure_backup": {
-            "explainer": "Haz copia de seguridad de tus claves antes de cerrar sesión para evitar perderlas.",
-            "title": "Configurar"
+            "explainer": "Haz copia de seguridad de tus claves antes de cerrar sesión para evitar perderlas."
         },
         "udd": {
             "interactive_verification_button": "Verificar interactivamente usando emojis",
@@ -827,12 +798,10 @@
             "title": "No es de confianza"
         },
         "unable_to_setup_keys_error": "No se han podido configurar las claves",
-        "unsupported": "Este cliente no es compatible con el cifrado de extremo a extremo.",
         "verification": {
             "accepting": "Aceptando…",
             "after_new_login": {
                 "device_verified": "Dispositivo verificado",
-                "reset_confirmation": "¿De verdad quieres restablecer las claves de verificación?",
                 "skip_verification": "Saltar la verificación por ahora",
                 "unable_to_verify": "No se ha podido verificar el dispositivo",
                 "verify_this_device": "Verificar este dispositivo"
@@ -902,7 +871,6 @@
             "verify_emoji_prompt": "Verifica comparando emoji únicos.",
             "verify_emoji_prompt_qr": "Si no puedes escanear el código de arriba, verifica comparando emoji únicos.",
             "verify_later": "La verificaré en otro momento",
-            "verify_reset_warning_1": "Una vez restableces las claves de verificación, no lo podrás deshacer. Después de restablecerlas, no podrás acceder a los mensajes cifrados antiguos, y cualquier persona que te haya verificado verá avisos de seguridad hasta que vuelvas a hacer la verificación con ella.",
             "verify_using_device": "Verificar con otro dispositivo",
             "verify_using_key": "Verificar con una clave de seguridad",
             "verify_using_key_or_phrase": "Verificar con una clave o frase de seguridad",
@@ -959,11 +927,7 @@
             "title": "No se ha podido copiar el enlace a la sala"
         },
         "error_loading_user_profile": "No se pudo cargar el perfil de usuario",
-        "forget_room_failed": "No se pudo olvidar la sala %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "El servidor podría estar saturado o desconectado, o la búsqueda caducó :(",
-            "title": "Falló la búsqueda"
-        }
+        "forget_room_failed": "No se pudo olvidar la sala %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1538,11 +1502,6 @@
         "ongoing": "Quitando…",
         "reason_label": "Motivo (opcional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "¿Estás seguro que quieres rechazar la invitación?",
-        "failed": "Falló al rechazar la invitación",
-        "title": "Rechazar invitación"
-    },
     "report_content": {
         "description": "Denunciar este mensaje enviará su único «event ID al administrador de tu servidor base. Si los mensajes en esta sala están cifrados, el administrador de tu servidor no podrá leer el texto del mensaje ni ver ningún archivo o imagen.",
         "disagree": "No estoy de acuerdo",
@@ -1692,7 +1651,6 @@
             "you_created": "Creaste esta sala."
         },
         "invite_email_mismatch_suggestion": "Comparte este correo electrónico en Configuración para recibir invitaciones directamente en %(brand)s.",
-        "invite_reject_ignore": "Rechazar e ignorar usuario",
         "invite_sent_to_email": "Esta invitación se envió a %(email)s",
         "invite_sent_to_email_room": "Esta invitación a %(roomName)s fue enviada a %(email)s",
         "invite_subtitle": "<userName/> te ha invitado",
@@ -2176,7 +2134,6 @@
             "remove_msisdn_prompt": "¿Eliminar %(phone)s?",
             "spell_check_locale_placeholder": "Elige un idioma"
         },
-        "image_thumbnails": "Mostrar vistas previas para las imágenes",
         "inline_url_previews_default": "Activar la vista previa de URLs en línea por defecto",
         "inline_url_previews_room": "Activar la vista previa de URLs por defecto para los participantes de esta sala",
         "inline_url_previews_room_account": "Activar la vista previa de URLs en esta sala (solo para ti)",
@@ -2279,48 +2236,16 @@
         "prompt_invite": "Preguntarme antes de enviar invitaciones a IDs de matrix que no parezcan válidas",
         "replace_plain_emoji": "Sustituir automáticamente caritas de texto por sus emojis equivalentes",
         "security": {
-            "4s_public_key_in_account_data": "en datos de cuenta",
-            "4s_public_key_status": "Clave pública del almacén secreto:",
-            "backup_key_cached_status": "Clave de respaldo almacenada en caché:",
-            "backup_key_stored_status": "Clave de respaldo almacenada:",
-            "backup_key_unexpected_type": "tipo inesperado",
-            "backup_key_well_formed": "bien formado",
-            "backup_keys_description": "Haz una copia de seguridad de tus claves de cifrado con los datos de tu cuenta por si pierdes acceso a tus sesiones. Las clave serán aseguradas con una clave de seguridad única.",
             "bulk_options_accept_all_invites": "Aceptar todas las invitaciones de %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Rechazar todas las invitaciones a %(invitedRooms)s",
             "bulk_options_section": "Opciones generales",
-            "cross_signing_cached": "almacenado localmente",
-            "cross_signing_homeserver_support": "Características compatibles con tu servidor base:",
-            "cross_signing_homeserver_support_exists": "existe",
-            "cross_signing_in_4s": "en almacén secreto",
-            "cross_signing_in_memory": "en memoria",
-            "cross_signing_master_private_Key": "Clave privada maestra:",
-            "cross_signing_not_cached": "no encontrado localmente",
-            "cross_signing_not_found": "no encontrado",
-            "cross_signing_not_in_4s": "no se ha encontrado en la memoria",
-            "cross_signing_not_stored": "no almacenado",
-            "cross_signing_private_keys": "Firmando las llaves privadas de manera cruzada:",
-            "cross_signing_public_keys": "Firmando las llaves públicas de manera cruzada:",
-            "cross_signing_self_signing_private_key": "Clave privada autofirmada:",
-            "cross_signing_user_signing_private_key": "Usuario firmando llave privada:",
-            "cryptography_section": "Criptografía",
-            "delete_backup": "Borrar copia de seguridad",
-            "delete_backup_confirm_description": "¿Estás seguro? Perderás tus mensajes cifrados si las claves no se copian adecuadamente.",
             "e2ee_default_disabled_warning": "El administrador de tu servidor base ha desactivado el cifrado de extremo a extremo en salas privadas y mensajes directos.",
             "enable_message_search": "Activar la búsqueda de mensajes en salas cifradas",
             "encryption_section": "Cifrado",
-            "error_loading_key_backup_status": "No se pudo cargar el estado de la copia de la clave",
-            "export_megolm_keys": "Exportar claves de salas con cifrado de extremo a extremo",
             "ignore_users_empty": "No has ignorado a nadie.",
             "ignore_users_section": "Usuarios ignorados",
-            "import_megolm_keys": "Importar claves de salas con cifrado de extremo a extremo",
-            "key_backup_active_version_none": "Ninguno",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_complete": "Se han copiado todas las claves",
             "key_backup_connect": "Conecta esta sesión a la copia de respaldo de tu clave",
-            "key_backup_connect_prompt": "Conecte esta sesión a la copia de seguridad de las claves antes de firmar y así evitar perder las claves que sólo existen en esta sesión.",
-            "key_backup_inactive": "Esta sesión no <b> ha creado una copia de seguridad de tus llaves</b>, pero tienes una copia de seguridad existente de la que puedes restaurar y añadir para proceder.",
-            "key_backup_inactive_warning": "<b>No se está haciendo una copia de seguridad de tus claves en esta sesión</b>.",
             "message_search_disable_warning": "Si está desactivado, los mensajes de las salas cifradas no aparecerán en los resultados de búsqueda.",
             "message_search_disabled": "Almacenar localmente, de manera segura, a los mensajes cifrados localmente para que aparezcan en los resultados de búsqueda.",
             "message_search_enabled": {
@@ -2340,13 +2265,7 @@
             "message_search_unsupported": "A %(brand)s le faltan algunos componentes necesarios para el almacenamiento seguro de mensajes cifrados a nivel local. Si quieres experimentar con esta característica, construye un Escritorio %(brand)s personalizado con <nativeLink> componentes de búsqueda añadidos</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s no puede almacenar en caché de forma segura mensajes cifrados localmente mientras se ejecuta en un navegador web. Usa <desktopLink> %(brand)s Escritorio</desktopLink> para que los mensajes cifrados aparezcan en los resultados de búsqueda.",
             "record_session_details": "Registrar el nombre del cliente, la versión y URL para reconocer de forma más fácil las sesiones en el gestor",
-            "restore_key_backup": "Restaurar una copia de seguridad",
-            "secret_storage_not_ready": "no está listo",
-            "secret_storage_ready": "Listo",
-            "secret_storage_status": "Almacenamiento secreto:",
             "send_analytics": "Enviar datos estadísticos de uso",
-            "session_id": "Identidad (ID) de sesión:",
-            "session_key": "Código de sesión:",
             "strict_encryption": "No enviar nunca mensajes cifrados a sesiones sin verificar desde esta sesión"
         },
         "send_read_receipts": "Enviar acuses de recibo",
@@ -2552,8 +2471,6 @@
         "topic": "Ver o cambiar el asunto de la sala",
         "topic_none": "Esta sala no tiene asunto.",
         "topic_room_error": "Fallo al obtener el asunto de la sala: No se ha podido encontrar la sala (%(roomId)s",
-        "tovirtual": "Cambia a la sala virtual de esta sala, si tiene una",
-        "tovirtual_not_found": "Esta sala no tiene una sala virtual",
         "unban": "Desbloquea el usuario con ese ID",
         "unflip": "Pone «┬──┬ ノ( ゜-゜ノ)» después de un mensaje de texto",
         "unholdcall": "Quita la llamada de la sala actual de espera",
@@ -2672,7 +2589,6 @@
         "heading_without_query": "Buscar",
         "join_button_text": "Unirte a %(roomAddress)s",
         "keyboard_scroll_hint": "Usa <arrows/> para desplazarte",
-        "message_search_section_title": "Otras búsquedas",
         "other_rooms_in_space": "Otras salas en %(spaceName)s",
         "public_rooms_label": "Salas públicas",
         "recent_searches_section_title": "Búsquedas recientes",
@@ -2681,7 +2597,6 @@
         "result_may_be_hidden_privacy_warning": "Algunos resultados pueden estar ocultos por motivos de privacidad",
         "result_may_be_hidden_warning": "Algunos resultados pueden estar ocultos",
         "search_dialog": "Ventana de búsqueda",
-        "search_messages_hint": "Para buscar contenido de mensajes, usa este icono en la parte de arriba de una sala: <icon/>",
         "spaces_title": "Tus espacios",
         "start_group_chat_button": "Crear conversación en grupo"
     },
@@ -2950,7 +2865,9 @@
             "sent": "%(senderName)s invitó a %(targetDisplayName)s a unirse a la sala."
         },
         "m.room.tombstone": "%(senderDisplayName)s actualizó esta sala.",
-        "m.room.topic": "%(senderDisplayName)s cambió el asunto a «%(topic)s».",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s cambió el asunto a «%(topic)s»."
+        },
         "m.sticker": "%(senderDisplayName)s envió una pegatina.",
         "m.video": {
             "error_decrypting": "Error al descifrar el vídeo"
@@ -3213,14 +3130,6 @@
         "ban_room_confirm_title": "Vetar de %(roomName)s",
         "ban_space_everything": "Vetar de todos los sitios donde tenga los permisos suficientes",
         "ban_space_specific": "Vetar de algunos sitios donde tenga los permisos suficientes",
-        "count_of_sessions": {
-            "other": "%(count)s sesiones",
-            "one": "%(count)s sesión"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sesiones verificadas",
-            "one": "1 sesión verificada"
-        },
         "deactivate_confirm_action": "Desactivar usuario",
         "deactivate_confirm_description": "Desactivar este usuario le cerrará la sesión y desconectará. No podrá volver a iniciar sesión. Además, saldrá de todas las salas a que se había unido. Esta acción no puede deshacerse. ¿Desactivar este usuario?",
         "deactivate_confirm_title": "¿Desactivar usuario?",
@@ -3231,15 +3140,12 @@
         "disinvite_button_room": "Retirar la invitación a la sala",
         "disinvite_button_room_name": "Anular la invitación a %(roomName)s",
         "disinvite_button_space": "Retirar la invitación al espacio",
-        "edit_own_devices": "Gestionar dispositivos",
         "error_ban_user": "Bloqueo del usuario falló",
         "error_deactivate": "Error en desactivar usuario",
         "error_kicking_user": "No se ha podido sacar al usuario",
         "error_mute_user": "No se pudo silenciar al usuario",
         "error_revoke_3pid_invite_description": "No se logró revocar la invitación. El servidor puede sufrir un problema temporal o usted no tiene los permisos suficientes para revocar la invitación.",
         "error_revoke_3pid_invite_title": "Error al revocar la invitación",
-        "hide_sessions": "Ocultar sesiones",
-        "hide_verified_sessions": "Ocultar sesiones verificadas",
         "ignore_confirm_title": "Ignorar a %(user)s",
         "invited_by": "Invitado por %(sender)s",
         "jump_to_rr_button": "Saltar al último mensaje sin leer",
@@ -3326,7 +3232,6 @@
         "hide_sidebar_button": "Ocultar menú lateral",
         "input_devices": "Dispositivos de entrada",
         "join_button_tooltip_call_full": "Lo sentimos — la llamada está llena",
-        "join_button_tooltip_connecting": "Conectando",
         "maximise": "Llenar la pantalla",
         "misconfigured_server": "La llamada ha fallado debido a una mala configuración del servidor",
         "misconfigured_server_description": "Por favor, pídele al administrador de tu servidor base (<code>%(homeserverDomain)s</code>) que configure un servidor TURN para que las llamadas funcionen correctamente.",
diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json
index c61df1d003024931d8247099faf59219941d8965..666ed60825a4a67c151277a9f57d5915a866c394 100644
--- a/src/i18n/strings/et.json
+++ b/src/i18n/strings/et.json
@@ -12,6 +12,16 @@
             "other": "%(count)s lugemata sõnumit kaasa arvatud mainimised."
         },
         "recent_rooms": "Hiljuti kasutatud jututoad",
+        "room_messsage_not_sent": "Ava „%(roomName)s“ jututuba saatmata sõnumiga.",
+        "room_n_unread_invite": "Ava %(roomName)s jututoa kutse.",
+        "room_n_unread_messages": {
+            "one": "Ava %(roomName)s jututuba 1 lugemata sõnumiga.",
+            "other": "Ava %(roomName)s jututuba %(count)s lugemata sõnumiga."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Ava %(roomName)s jututuba 1 lugemata mainimisega.",
+            "other": "Ava %(roomName)s jututuba %(count)s lugemata sõnumiga, mille hulgas on ka mainimisi."
+        },
         "room_name": "Jututuba %(name)s",
         "room_status_bar": "Jututoa olekuriba",
         "seek_bar_label": "Heli kerimisriba",
@@ -45,6 +55,8 @@
         "create_a_room": "Loo jututuba",
         "create_account": "Loo konto",
         "decline": "Keeldu",
+        "decline_and_block": "Keeldu ja blokeeri",
+        "decline_invite": "Lükka kutse tagasi",
         "delete": "Kustuta",
         "deny": "Keeldu",
         "disable": "Lülita välja",
@@ -64,6 +76,7 @@
         "go": "Mine",
         "go_back": "Mine tagasi",
         "got_it": "Selge lugu",
+        "hide": "Peida",
         "hide_advanced": "Peida lisaseadistused",
         "hold": "Pane ootele",
         "ignore": "Eira",
@@ -80,27 +93,29 @@
         "maximise": "Suurenda maksimaalseks",
         "mention": "Maini",
         "minimise": "Väike vaade",
+        "new_message": "Uus sõnum",
         "new_room": "Uus jututuba",
         "new_video_room": "Uus videotuba",
         "next": "Järgmine",
         "no": "Ei",
         "ok": "Sobib",
         "open": "Ava",
+        "open_menu": "Ava menüü",
         "pause": "Peata",
-        "pin": "Nööpnõel",
+        "pin": "Tõsta esile",
         "play": "Esita",
         "proceed": "Jätka",
         "quote": "Tsiteeri",
         "react": "Reageeri",
         "refresh": "Värskenda",
         "register": "Registreeru",
-        "reject": "Hülga",
         "reload": "Laadi uuesti",
         "remove": "Eemalda",
         "rename": "Muuda nime",
         "reply": "Vasta",
         "reply_in_thread": "Vasta jutulõngas",
         "report_content": "Teata sisust haldurile",
+        "report_room": "Teata jututoast",
         "resend": "Saada uuesti",
         "reset": "Taasta algolek",
         "resume": "Jätka",
@@ -110,6 +125,7 @@
         "save": "Salvesta",
         "search": "Otsing",
         "send_report": "Saada veateade",
+        "set_avatar": "Seadista profiilipilt",
         "share": "Jaga",
         "show": "Näita",
         "show_advanced": "Näita lisaseadistusi",
@@ -128,11 +144,12 @@
         "try_again": "Proovi uuesti",
         "unban": "Taasta ligipääs",
         "unignore": "Lõpeta eiramine",
-        "unpin": "Eemalda klammerdus",
+        "unpin": "Eemalda esiletõstmine",
         "unsubscribe": "Lõpeta liitumine",
         "update": "Uuenda",
         "upgrade": "Uuenda",
         "upload": "Laadi üles",
+        "upload_file": "Laadi fail üles",
         "verify": "Verifitseeri",
         "view": "Näita",
         "view_all": "Näita kõiki",
@@ -140,6 +157,7 @@
         "view_message": "Vaata sõnumit",
         "view_source": "Vaata lähtekoodi",
         "yes": "Jah",
+        "yes_dismiss": "Jah, loobu",
         "zoom_in": "Suumi sisse",
         "zoom_out": "Suumi välja"
     },
@@ -227,6 +245,7 @@
         },
         "misconfigured_body": "Palu, et sinu %(brand)s'u haldur kontrolliks <a>sinu seadistusi</a> võimalike vigaste või topeltkirjete osas.",
         "misconfigured_title": "Sinu %(brand)s'i seadistused on paigast ära",
+        "mobile_create_account_title": "Sa oled loomas kasutajakontot koduserveris %(hsName)s",
         "msisdn_field_description": "Teades sinu kontaktinfot võivad teised kutsuda sind osalema jututubades",
         "msisdn_field_label": "Telefon",
         "msisdn_field_number_invalid": "See telefoninumber ei tundu õige olema, palun kontrolli ta üle ja proovi uuesti",
@@ -244,12 +263,40 @@
         "phone_label": "Telefon",
         "phone_optional_label": "Telefoninumber (kui soovid)",
         "qr_code_login": {
+            "check_code_explainer": "Sellega verifitseerime, et ühendus sinu teise seadmega on turvaline.",
+            "check_code_heading": "Sisesta teises seadmes kuvatav number",
+            "check_code_input_label": "2-kohaline kood",
+            "check_code_mismatch": "Numbrid ei klapi",
             "completing_setup": "Lõpetame uue seadme seadistamise",
+            "error_etag_missing": "Tekkis ootamatu viga. Selle põhjuseks võivad olla brauseri lisamoodul, proksiserveri seadistused või koduserveri vigased seadistused.",
+            "error_expired": "Sisselogimine aegus. Palun proovi uuesti.",
+            "error_expired_title": "Sisselogimine ei jõudnud õigeaegselt lõpule",
+            "error_insecure_channel_detected": "Uue seadmega ei saanud turvalist ühendust luua. Sinu teised seadmed on endiselt turvalised ja nende pärast ei pea muretsema.",
+            "error_insecure_channel_detected_instructions": "Mis nüüd?",
+            "error_insecure_channel_detected_instructions_1": "Proovi QR-koodiga teise seadmesse uuesti sisse logida, juhuks kui tegemist oli võrguprobleemiga",
+            "error_insecure_channel_detected_instructions_2": "Kui sul tekib sama probleem uuesti, proovi teist WiFi-võrku või kasuta wifi asemel mobiilset andmesidet",
+            "error_insecure_channel_detected_instructions_3": "Kui see ei aita, logi sisse käsitsi",
+            "error_insecure_channel_detected_title": "Ühendus pole turvaline",
+            "error_other_device_already_signed_in": "Sa ei pea enam midagi muud tegema.",
+            "error_other_device_already_signed_in_title": "Sinu muu seade on juba sisse logitud",
             "error_rate_limited": "Liiga palju päringuid napis ajavahemikus. Enne uuesti proovimist palun oota veidi.",
-            "error_unexpected": "Tekkis teadmata viga.",
-            "scan_code_instruction": "Loe QR-koodi seadmega, kus sa oled Matrix'i võrgust välja loginud.",
-            "scan_qr_code": "Loe QR-koodi",
-            "select_qr_code": "Vali „%(scanQRCode)s“",
+            "error_unexpected": "Tekkis teadmata viga. Päring sinu muu seadme ühendamiseks on katkestatud.",
+            "error_unsupported_protocol": "See seade ei võimalda teise seadmesse sisse logida QR-koodi alusel.",
+            "error_unsupported_protocol_title": "Muu seade ei ühildu selle funktsionaalsusega",
+            "error_user_cancelled": "Sisselogimine on teises seadmes katkestatud.",
+            "error_user_cancelled_title": "Sisselogimispäring on tühistatud",
+            "error_user_declined": "Sa keeldusid teises seadmes sisselogimispäringust.",
+            "error_user_declined_title": "Sa keeldusid sisselogimast",
+            "follow_remaining_instructions": "Teise seadme verifitseerimiseks järgi ülejäänud juhiseid",
+            "open_element_other_device": "Ava %(brand)s oma teises seadmes",
+            "point_the_camera": "Suuna kaamera siin näidatud QR-koodi peale",
+            "scan_code_instruction": "Skaneeri QR-koodi teise seadmega",
+            "scan_qr_code": "Logi sisse QR-koodi alusel",
+            "security_code": "Turvakood",
+            "security_code_prompt": "Kui seda küsitakse, sisesta teises seadmes allolev kood.",
+            "select_qr_code": "Val i „%(scanQRCode)s“",
+            "unsupported_explainer": "Sinu teenusepakkuja ei toeta võimalust logida sisse QR-koodi abil.",
+            "unsupported_heading": "QR-koodi kasutamine pole toetatud",
             "waiting_for_device": "Ootame, et teine seade logiks võrku"
         },
         "register_action": "Loo konto",
@@ -278,7 +325,7 @@
             "sign_out_other_devices": "Logi kõik oma seadmed võrgust välja"
         },
         "reset_password_action": "Lähtesta salasõna",
-        "reset_password_button": "Unustasid parooli?",
+        "reset_password_button": "Unustasid salasõna?",
         "reset_password_email_field_description": "Kasuta e-posti aadressi ligipääsu taastamiseks oma kontole",
         "reset_password_email_field_required_invalid": "Sisesta e-posti aadress (nõutav selles koduserveris)",
         "reset_password_email_not_associated": "Sinu e-posti aadress ei tundu olema selles koduserveris seotud Matrixi kasutajatunnusega.",
@@ -338,6 +385,9 @@
             "email_resend_prompt": "Sa pole kirja saanud? <a>Saada uuesti</a>",
             "email_resent": "Uuesti saadetud!",
             "fallback_button": "Alusta autentimist",
+            "mas_cross_signing_reset_cta": "Mine oma kasutajakonto andmete juurde",
+            "mas_cross_signing_reset_description": "Lähtesta oma võrguidentiteet oma teenusepakkuja abil ning tule siis siia tagasi ja vajuta „Proovi uuesti“.",
+            "mas_cross_signing_reset_title": "Oma võrguidentiteedi lähtestamiseks ava oma kasutajakonto vaade",
             "msisdn": "Saatsime tekstisõnumi telefoninumbrile %(msisdn)s",
             "msisdn_token_incorrect": "Vigane tunnusluba",
             "msisdn_token_prompt": "Palun sisesta seal kuvatud kood:",
@@ -372,7 +422,15 @@
         "download_logs": "Laadi logikirjed alla",
         "downloading_logs": "Laadin logisid alla",
         "error_empty": "Palun kirjelda seda, mis läks valesti ja loo GitHub'is veateade.",
-        "failed_send_logs": "Logikirjete saatmine ei õnnestunud: ",
+        "failed_download_logs": "Silumislogide allalaadimine ei õnnestunud: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Sinu veateadet ei võetud vastu. Ägedal raputamisel põhinev vigadest teatamise teenus ei toeta seda rakendust.",
+            "rejected_generic": "Sinu veateadet ei võetud vastu. Ägedal raputamisel põhinev vigadest teatamise teenus keeldus päringu sisust oma reeglite alusel.",
+            "rejected_recovery_key": "Kuna veateade sisaldas sinu taastevõtit, siis turvakaalutlustel sinu veateadet ei võetud vastu.",
+            "rejected_version": "Kuna kasutad rakenduse liiga vana versiooni, siis sinu veateadet ei võetud vastu.",
+            "server_unknown_error": "Ägedal raputamisel põhinev vigadest teatamise teenuses tekkis teadmata probleem, mille käigus ta ei osanud seda veateadet töödelda.",
+            "unknown_error": "Logikirjete saatmine ei õnnestunud."
+        },
         "github_issue": "Veateade GitHub'is",
         "introduction": "Kui sa oled GitHub'is teinud meile veateate, siis silumislogid võivad aidata vea lahendamisel. ",
         "log_request": "Tagamaks et sama ei juhtuks tulevikus, palun <a>saada meile salvestatud logid</a>.",
@@ -412,7 +470,7 @@
         "access_token": "Pääsuluba",
         "accessibility": "Ligipääsetavus",
         "advanced": "Teave arendajatele",
-        "all_rooms": "Kõik jututoad",
+        "all_chats": "Kõik vestlused",
         "analytics": "Analüütika",
         "and_n_others": {
             "other": "ja %(count)s muud...",
@@ -427,10 +485,10 @@
         "beta": "Beetaversioon",
         "camera": "Kaamera",
         "cameras": "Kaamerad",
+        "cancel": "Loobu",
         "capabilities": "Funktsionaalsused ja võimed",
         "copied": "Kopeeritud!",
         "credits": "Tänuavaldused",
-        "cross_signing": "Risttunnustamine",
         "dark": "Tume",
         "description": "Kirjeldus",
         "deselect_all": "Eemalda kõik valikud",
@@ -466,8 +524,10 @@
         "matrix": "Matrix",
         "message": "Sõnum",
         "message_layout": "Sõnumite paigutus",
+        "message_timestamp_invalid": "Vigane ajatempel",
         "microphone": "Mikrofon",
         "model": "Mudel",
+        "moderation_and_safety": "Modereerimine ja turvalisus",
         "modern": "Moodne",
         "mute": "Summuta",
         "n_members": {
@@ -503,13 +563,15 @@
         "qr_code": "QR kood",
         "random": "Juhuslik",
         "reactions": "Reageerimised",
+        "recommended": "Soovitatud",
         "report_a_bug": "Teata veast",
         "room": "Jututuba",
         "room_name": "Jututoa nimi",
         "rooms": "Jututoad",
+        "save": "Salvesta",
+        "saved": "Salvestatud",
         "saving": "Salvestame…",
         "secure_backup": "Turvaline varundus",
-        "security": "Turvalisus",
         "select_all": "Vali kõik",
         "server": "Server",
         "settings": "Seadistused",
@@ -524,17 +586,17 @@
         "suggestions": "Soovitused",
         "support": "Toeta",
         "system_alerts": "Süsteemi teated",
-        "theme": "Teema",
+        "theme": "Kujundus",
         "thread": "Jutulõng",
         "threads": "Jutulõngad",
         "timeline": "Ajajoon",
-        "trusted": "Usaldusväärne",
         "unavailable": "pole saadaval",
         "unencrypted": "Krüptimata",
         "unmute": "Eemalda summutamine",
         "unnamed_room": "Ilma nimeta jututuba",
         "unnamed_space": "Nimetu kogukonnakeskus",
         "unverified": "Verifitseerimata",
+        "updating": "Uuendame...",
         "user": "Kasutaja",
         "user_avatar": "Profiilipilt",
         "username": "Kasutajanimi",
@@ -663,7 +725,7 @@
         "private_space_description": "Privaatne kogukonnakeskus sinu ja sinu kaasteeliste jaoks",
         "public_description": "Avaliku ligipääsuga kogukonnakeskus",
         "public_heading": "Sinu avalik kogukonnakeskus",
-        "search_public_button": "Avalike ruumide otsing",
+        "search_public_button": "Avalike kogukondade otsing",
         "setup_rooms_community_description": "Teeme siis iga teema jaoks oma jututoa.",
         "setup_rooms_community_heading": "Mida sa sooviksid arutada %(spaceName)s kogukonnakeskuses?",
         "setup_rooms_description": "Sa võid ka hiljem siia luua uusi jututubasid või lisada olemasolevaid.",
@@ -687,6 +749,13 @@
         "twemoji": "<twemoji>Twemoji</twemoji> emotikonide autoriõiguste omanik on <author>Twitter, Inc koos kaasautoritega</author> ning neid kasutame vastavalt <terms>CC-BY 4.0</terms> litsentsi tingimustele.",
         "twemoji_colr": "<colr>Twemoji-colr</colr> kirjatüübi autoriõiguste omanik on <author>Mozilla Foundation</author> seda kasutame vastavalt <terms>Apache 2.0</terms> litsentsi tingimustele."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Kas sa oled kindel, et soovid keelduda kutsest liituda „%(roomName)s“ jututoaga?",
+        "ignore_user_help": "Sa ei näe enam selle kasutaja saadetud sõnumeid või jututubade kutseid",
+        "reason_description": "Kirjelda jututoast teatamise põhjust",
+        "report_room_description": "Teata sellest jututoast oma teenusepakkujale.",
+        "title": "Lükka kutse tagasi"
+    },
     "desktop_default_device_name": "%(brand)s töölaud: %(platformName)s",
     "devtools": {
         "active_widgets": "Kasutusel vidinad",
@@ -694,6 +763,44 @@
         "category_room": "Jututuba",
         "caution_colon": "Hoiatus:",
         "client_versions": "Klientrakenduste versioonid",
+        "crypto": {
+            "4s_public_key_in_account_data": "kasutajakonto andmete juures",
+            "4s_public_key_not_in_account_data": "ei leidu",
+            "4s_public_key_status": "Turvalise andmeruumi avalik võti:",
+            "backup_key_cached": "puhverdatud kohalikus seadmes",
+            "backup_key_cached_status": "Varukoopia võtmed on puhverdatud:",
+            "backup_key_not_stored": "pole salvestatud",
+            "backup_key_stored": "krüptitud salvestusruumis",
+            "backup_key_stored_status": "Varukoopia võti on salvestatud:",
+            "backup_key_unexpected_type": "ebatavalist tüüpi",
+            "backup_key_well_formed": "reeglipärane",
+            "cross_signing": "Risttunnustamine",
+            "cross_signing_cached": "puhverdatud kohalikus seadmes",
+            "cross_signing_not_ready": "Risttunnustamine on seadistamata.",
+            "cross_signing_private_keys_in_storage": "turvalises andmeruumis",
+            "cross_signing_private_keys_in_storage_status": "Risttunnustamise privaatvõtmed:",
+            "cross_signing_private_keys_not_in_storage": "ei leidu turvalises andmeruumis",
+            "cross_signing_public_keys_on_device": "mälus",
+            "cross_signing_public_keys_on_device_status": "Risttunnustamise avalikud võtmed:",
+            "cross_signing_ready": "Risttunnustamine on kasutusvalmis.",
+            "cross_signing_status": "Risttunnustamise olek:",
+            "cross_signing_untrusted": "Sinu kasutajakonto risttunnustamise identiteet on krüptitud andmehoidlas olemas, aga see sessioon teda veel ei usalda.",
+            "crypto_not_available": "Krüptomoodul pole saadaval",
+            "key_backup_active_version": "Varukoopia aktiivne versioon:",
+            "key_backup_active_version_none": "Puudub",
+            "key_backup_inactive_warning": "See sessioon ei varunda sinu krüptovõtmeid.",
+            "key_backup_latest_version": "Varukoopia viimane versioon serveris:",
+            "key_storage": "Võtmete krüptitud andmeruum",
+            "master_private_key_cached_status": "Üldine privaatvõti:",
+            "not_found": "ei leidu",
+            "not_found_locally": "ei leidu kohalikus seadmes",
+            "secret_storage_not_ready": "pole valmis",
+            "secret_storage_ready": "on valmis",
+            "secret_storage_status": "Krüptitud andmeruum:",
+            "self_signing_private_key_cached_status": "Privaatvõti allkirjastamiseks sinu nimel:",
+            "title": "Läbiv krüptimine",
+            "user_signing_private_key_cached_status": "Kasutaja privaatvõti allkirjastamiseks:"
+        },
         "developer_mode": "Arendusrežiim",
         "developer_tools": "Arendusvahendid",
         "edit_setting": "Muuda seadistust",
@@ -710,7 +817,7 @@
         "failed_to_load": "Laadimine ei õnnestunud.",
         "failed_to_save": "Seadistuste salvestamine ei õnnestunud.",
         "failed_to_send": "Päringu või sündmuse saatmine ei õnnestunud!",
-        "id": "ID:",
+        "id": "ID: ",
         "invalid_json": "See ei tundu olema korrektse json-andmestikuna.",
         "level": "Tase",
         "low_bandwidth_mode": "Vähese ribalaiusega režiim",
@@ -733,6 +840,7 @@
         "room_notifications_type": "Tüüp: ",
         "room_status": "Jututoa sõnumite olek",
         "room_unread_status_count": {
+            "one": "Lugemata sõnumite olek jututoas: <strong>%(status)s</strong>, kokku: <strong>%(count)s</strong>",
             "other": "Lugemata sõnumite olek jututoas: <strong>%(status)s</strong>, kokku: <strong>%(count)s</strong>"
         },
         "save_setting_values": "Salvesta seadistuste väärtused",
@@ -748,6 +856,9 @@
         "setting_colon": "Seadistus:",
         "setting_definition": "Seadistuse määratlus:",
         "setting_id": "Seadistuse tunnus",
+        "settings": {
+            "elementCallUrl": "Element Calli võrguaadress"
+        },
         "settings_explorer": "Seadistuste haldur",
         "show_hidden_events": "Näita peidetud sündmusi ajajoonel",
         "spaces": {
@@ -800,52 +911,37 @@
     "empty_room_was_name": "Tühi jututuba (varasema nimega %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Jätkamiseks sisesta oma turvafraas või <button>kasuta oma turvavõtit</button>.",
+            "alternatives": "Kui sul on turvavõti või turvafraas, siis need toimivad ka.",
             "key_validation_text": {
-                "invalid_security_key": "Vigane turvavõti",
-                "recovery_key_is_correct": "Tundub õige!",
-                "wrong_file_type": "Vale failitüüp",
-                "wrong_security_key": "Vale turvavõti"
-            },
-            "reset_title": "Alusta kõigega algusest",
-            "reset_warning_1": "Toimi nii vaid siis, kui sul pole jäänud ühtegi seadet, millega verifitseerimist lõpuni teha.",
-            "reset_warning_2": "Kui sa kõik krüptoseosed lähtestad, siis sul esimese hooga pole ühtegi usaldusväärseks tunnistatud sessiooni ega kasutajat ning ilmselt ei saa sa lugeda vanu sõnumeid.",
+                "wrong_security_key": "Sinu sisestatud taastevõti pole korrektne."
+            },
+            "privacy_warning": "Palun vaata, et keegi teine ei näeks seda ekraanivaadet!",
             "restoring": "Taastan võtmed varundusest",
-            "security_key_title": "Turvavõti",
-            "security_phrase_incorrect_error": "Ei õnnestu saada ligipääsu turvahoidlale. Palun kontrolli, et sa oleksid sisestanud õige turvafraasi.",
-            "security_phrase_title": "Turvafraas",
-            "separator": "%(securityKey)s või %(recoveryFile)s",
-            "use_security_key_prompt": "Jätkamiseks kasuta turvavõtit."
+            "security_key_title": "Taastevõti"
         },
         "bootstrap_title": "Võtame krüptovõtmed kasutusele",
         "cancel_entering_passphrase_description": "Kas oled kindel et sa soovid katkestada paroolifraasi sisestamise?",
         "cancel_entering_passphrase_title": "Kas katkestame paroolifraasi sisestamise?",
         "confirm_encryption_setup_body": "Kinnitamaks, et soovid krüptimist seadistada, klõpsi järgnevat nuppu.",
         "confirm_encryption_setup_title": "Krüptimise seadistuse kinnitamine",
-        "cross_signing_not_ready": "Risttunnustamine on seadistamata.",
-        "cross_signing_ready": "Risttunnustamine on kasutamiseks valmis.",
-        "cross_signing_ready_no_backup": "Risttunnustamine on töövalmis, aga krüptovõtmed on varundamata.",
         "cross_signing_room_normal": "See jututuba on läbivalt krüptitud",
         "cross_signing_room_verified": "Kõik kasutajad siin jututoas on verifitseeritud",
         "cross_signing_room_warning": "Keegi kasutab tundmatut sessiooni",
-        "cross_signing_unsupported": "Sinu koduserver ei toeta risttunnustamist.",
-        "cross_signing_untrusted": "Sinu kontol on turvahoidlas olemas risttunnustamise identiteet, kuid seda veel ei loeta antud sessioonis usaldusväärseks.",
         "cross_signing_user_normal": "Sa ei ole seda kasutajat verifitseerinud.",
         "cross_signing_user_verified": "Sa oled selle kasutaja verifitseerinud. See kasutaja on verifitseerinud kõik nende sessioonid.",
         "cross_signing_user_warning": "See kasutaja ei ole verifitseerinud kõiki oma sessioone.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Eemalda risttunnustamise võtmed",
-            "title": "Kas hävitame risttunnustamise võtmed?",
-            "warning": "Risttunnustamise võtmete kustutamine on tegevus, mida ei saa tagasi pöörata. Kõik sinu verifitseeritud vestluskaaslased näevad seejärel turvateateid. Kui sa just pole kaotanud ligipääsu kõikidele oma seadmetele, kust sa risttunnustamist oled teinud, siis sa ilmselgelt ei peaks kustutamist ette võtma."
-        },
+        "enter_recovery_key": "Sisesta taastevõti",
         "event_shield_reason_authenticity_not_guaranteed": "Selle krüptitud sõnumi autentsus pole selles seadmes tagatud.",
         "event_shield_reason_mismatched_sender_key": "Krüptitud verifitseerimata sessiooni poolt",
-        "event_shield_reason_unknown_device": "Krüpteeritud tundmatu või kustutatud seadme poolt.",
-        "event_shield_reason_unsigned_device": "Krüpteeritud seadme poolt, mida selle omanik ei ole verifitseerinud.",
-        "event_shield_reason_unverified_identity": "Krüpteeritud verifitseerimata kasutaja poolt.",
+        "event_shield_reason_unknown_device": "Krüptitud tundmatu või kustutatud seadme poolt.",
+        "event_shield_reason_unsigned_device": "Krüptitud seadme poolt, mida selle omanik ei ole verifitseerinud.",
+        "event_shield_reason_unverified_identity": "Krüptitud verifitseerimata kasutaja poolt.",
         "export_unsupported": "Sinu brauser ei toeta vajalikke krüptoteeke",
+        "forgot_recovery_key": "Kas unustasid taastevõtme?",
         "import_invalid_keyfile": "See ei ole sobilik võtmefail %(brand)s'i jaoks",
         "import_invalid_passphrase": "Autentimine ebaõnnestus: kas salasõna pole õige?",
+        "key_storage_out_of_sync": "Sinu krüptovõtmete hoidla pole sünkroonis.",
+        "key_storage_out_of_sync_description": "Säilitamaks ligipääsu vestluste ja krüptovõtmete varukoopiale, palun sisesta kinnituseks oma taastevõti.",
         "messages_not_secure": {
             "cause_1": "Sinu koduserver",
             "cause_2": "Sinu poolt verifitseeritava kasutaja koduserver",
@@ -860,7 +956,8 @@
             "title": "Uus taastamise meetod",
             "warning": "Kui sa ei ole ise uusi taastamise meetodeid lisanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod."
         },
-        "not_supported": "<ei ole toetatud>",
+        "pinned_identity_changed": "Kasutaja %(displayName)s (<b>%(userId)s</b>) võrguidentiteet on lähtestatud. <a>Lisateave</a>",
+        "pinned_identity_changed_no_displayname": "Kasutaja <b>%(userId)s</b> võrguidentiteet on lähtestatud. <a>Lisateave</a>",
         "recovery_method_removed": {
             "description_1": "Oleme tuvastanud, et selles sessioonis ei leidu turvafraasi ega krüptitud sõnumite turvavõtit.",
             "description_2": "Kui sa tegid seda juhuslikult, siis sa võid selles sessioonis uuesti seadistada sõnumite krüptimise, mille tulemusel krüptime uuesti kõik sõnumid ja loome uue taastamise meetodi.",
@@ -868,12 +965,16 @@
             "warning": "Kui sa ei ole ise taastamise meetodeid eemaldanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod."
         },
         "reset_all_button": "Unustasid või oled kaotanud kõik võimalused ligipääsu taastamiseks? <a>Lähtesta kõik ühe korraga</a>",
+        "set_up_recovery": "Seadista krüptovõtmete taastamine",
+        "set_up_recovery_later": "Mitte praegu",
+        "set_up_recovery_toast_description": "Kui peaksid kaotama ligipääsu oma seadmetele, siis siinloodava taastevõtmega saad taastada ligipääsu oma krüptitud sõnumitele.",
         "set_up_toast_description": "Hoia ära, et kaotad ligipääsu krüptitud sõnumitele ja andmetele",
         "set_up_toast_title": "Võta kasutusele turvaline varundus",
         "setup_secure_backup": {
-            "explainer": "Vältimaks nende kaotamist, varunda krüptovõtmed enne väljalogimist.",
-            "title": "Võta kasutusele"
+            "explainer": "Vältimaks nende kaotamist, varunda krüptovõtmed enne väljalogimist."
         },
+        "turn_on_key_storage": "Võta krüptovõtmete hoidla kasutusele",
+        "turn_on_key_storage_description": "Salvesta oma krüptoidentiteet ja sõnumite krüptovõtmed turvaliselt serveris. See tagab, et sinu sõnumite ajalugu on alati loetav, ka kõikides uutes seadmetes.",
         "udd": {
             "interactive_verification_button": "Verifitseeri interaktiivselt emoji abil",
             "other_ask_verify_text": "Palu nimetatud kasutajal verifitseerida see sessioon või tee seda alljärgnevaga käsitsi.",
@@ -883,12 +984,10 @@
             "title": "Ei ole usaldusväärne"
         },
         "unable_to_setup_keys_error": "Krüptovõtmete kasutuselevõtmine ei õnnestu",
-        "unsupported": "See klient ei toeta läbivat krüptimist.",
         "verification": {
             "accepting": "Nõustun …",
             "after_new_login": {
                 "device_verified": "Seade on verifitseeritud",
-                "reset_confirmation": "Kas tõesti kustutame kõik verifitseerimisvõtmed?",
                 "skip_verification": "Jäta verifitseerimine praegu vahele",
                 "unable_to_verify": "Selle seadme verifitseerimine ei õnnestunud",
                 "verify_this_device": "Verifitseeri see seade"
@@ -910,7 +1009,7 @@
             "incoming_sas_dialog_waiting": "Ootan teise osapoole kinnitust…",
             "incoming_sas_user_dialog_text_1": "Selle kasutaja usaldamiseks peaksid ta verifitseerima. Kui sa pruugid läbivalt krüptitud sõnumeid, siis kasutajate verifitseerimine tagab sulle täiendava meelerahu.",
             "incoming_sas_user_dialog_text_2": "Selle kasutaja verifitseerimisel märgitakse tema sessioon usaldusväärseks ning samuti märgitakse sinu sessioon tema jaoks usaldusväärseks.",
-            "no_key_or_device": "Tundub, et sul ei ole ei turvavõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmed pead oma vanad verifitseerimisvõtmed kustutama.",
+            "no_key_or_device": "Tundub, et sul ei ole ei taastevõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmes pead oma vanad verifitseerimisvõtmed kustutama.",
             "no_support_qr_emoji": "See seade, mida sa tahad verifitseerida ei toeta QR-koodi ega emoji-põhist verifitseerimist, aga just neid %(brand)s oskab kasutada. Proovi mõne muu Matrix'i kliendiga.",
             "other_party_cancelled": "Teine osapool tühistas verifitseerimise.",
             "prompt_encrypted": "Tagamaks, et jututuba on turvaline, verifitseeri kõik selle kasutajad.",
@@ -923,6 +1022,7 @@
             "qr_reciprocate_same_shield_device": "Peaaegu valmis! Kas sinu teine seade kuvab sama kilpi?",
             "qr_reciprocate_same_shield_user": "Peaaegu valmis! Kas %(displayName)s kuvab sama kilpi?",
             "request_toast_accept": "Verifitseeri sessioon",
+            "request_toast_accept_user": "Verifitseeri kasutaja",
             "request_toast_decline_counter": "Eira (%(counter)s)",
             "request_toast_detail": "%(deviceId)s ip-aadressil %(ip)s",
             "reset_proceed_prompt": "Jätka kustutamisega",
@@ -948,7 +1048,7 @@
             "unverified_sessions_toast_description": "Tagamaks, et su konto on sinu kontrolli all, vaata andmed üle",
             "unverified_sessions_toast_reject": "Hiljem",
             "unverified_sessions_toast_title": "Sul on verifitseerimata sessioone",
-            "verification_description": "Tagamaks ligipääsu oma krüptitud sõnumitele ja tõestamaks oma isikut teistele kasutajatale, verifitseeri end.",
+            "verification_description": "Tagamaks ligipääsu oma krüptitud sõnumitele ja tõestamaks oma isikut teistele kasutajatale, verifitseeri end. Kui kasutad mobiilirakendust, siis palun ava see enne jätkamist.",
             "verification_dialog_title_device": "Verifitseeri oma teine seade",
             "verification_dialog_title_user": "Verifitseerimispäring",
             "verification_skip_warning": "Ilma verifitseerimiseta sul puudub ligipääs kõikidele oma sõnumitele ning teised ei näe sinu kasutajakontot usaldusväärsena.",
@@ -958,19 +1058,20 @@
             "verify_emoji_prompt": "Verifitseeri unikaalsete emoji'de võrdlemise teel.",
             "verify_emoji_prompt_qr": "Kui sa ei saa skaneerida eespool kuvatud koodi, siis verifitseeri unikaalsete emoji'de võrdlemise teel.",
             "verify_later": "Ma verifitseerin hiljem",
-            "verify_reset_warning_1": "Verifitseerimisvõtmete kustutamist ei saa hiljem tagasi võtta. Peale seda sul puudub ligipääs vanadele krüptitud sõnumitele ja kõik sinu verifitseeritud sõbrad-tuttavad näevad turvahoiatusi seni kuni sa uuesti nad verifitseerid.",
-            "verify_reset_warning_2": "Palun jätka ainult siis, kui sa oled kaotanud ligipääsu kõikidele oma seadmetele ning oma turvavõtmele.",
             "verify_using_device": "Verifitseeri teise seadmega",
-            "verify_using_key": "Verifitseeri turvavõtmega",
-            "verify_using_key_or_phrase": "Verifitseeri turvavõtme või turvafraasiga",
+            "verify_using_key": "Verifitseeri taastevõtmega",
+            "verify_using_key_or_phrase": "Verifitseeri taastevõtme või -fraasiga",
             "waiting_for_user_accept": "Ootan, et %(displayName)s nõustuks…",
             "waiting_other_device": "Ootan, et sa verifitseeriksid oma teises seadmes…",
             "waiting_other_device_details": "Ootan, et sa verifitseerid oma teises seadmes: %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Ootan kasutaja %(displayName)s verifitseerimist…"
         },
         "verification_requested_toast_title": "Verifitseerimistaotlus on saadetud",
+        "verified_identity_changed": "Kasutaja %(displayName)s (<b>%(userId)s</b>) verifitseeritud identiteet on muutunud. <a>Lisateave</a>",
+        "verified_identity_changed_no_displayname": "Kasutaja <b>%(userId)s</b> verifitseeritud identiteet on muutunud. <a>Lisateave</a>",
         "verify_toast_description": "Teised kasutajad ei pruugi seda usaldada",
-        "verify_toast_title": "Verifitseeri see sessioon"
+        "verify_toast_title": "Verifitseeri see sessioon",
+        "withdraw_verification_action": "Eemalda verifitseerimine"
     },
     "error": {
         "admin_contact": "Jätkamaks selle teenuse kasutamist palun <a>võta ühendust oma teenuse haldajaga</a>.",
@@ -1015,7 +1116,8 @@
     "error_app_open_in_another_tab_title": "%(brand)s'i on kasutatav teisel vahekaardil",
     "error_app_opened_in_another_window": "%(brand)s on avatud teises aknas. Klõpsa \"%(label)s\", et kasutada siin %(brand)s ja katkestada teise akna ühendus.",
     "error_database_closed_description": {
-        "for_desktop": "Andmekandja maht võib olla täis saanud. Palun tee ruumi juurde ja laadi leht uuesti."
+        "for_desktop": "Andmekandja maht võib olla täis saanud. Palun tee ruumi juurde ja laadi leht uuesti.",
+        "for_web": "Kui sa kustutasid brauseris puhverdatud andmed, siis selline teade on ootuspärane. Lisaks on võimalik, et %(brand)s on avatud mõnes teises vahekaardis või sinu seadme kõvakettaruum on otsas. Palun tee seadmesse ruumi ja laadi uuesti"
     },
     "error_database_closed_title": "%(brand)s lõpetas ootamatult töö",
     "error_dialog": {
@@ -1024,11 +1126,7 @@
             "title": "Jututoa lingi kopeerimine ei õnnestu"
         },
         "error_loading_user_profile": "Kasutajaprofiili laadimine ei õnnestunud",
-        "forget_room_failed": "Jututoa unustamine ei õnnestunud %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Server kas pole leitav, on ülekoormatud või otsing aegus :(",
-            "title": "Otsing ebaõnnestus"
-        }
+        "forget_room_failed": "Jututoa unustamine ei õnnestunud %(errCode)s"
     },
     "error_user_not_logged_in": "Kasutaja pole võrku loginud",
     "event_preview": {
@@ -1053,7 +1151,15 @@
             "you": "Sa reageerisid %(message)s sõnumile %(reaction)s'ga"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Helifail",
+            "file": "Fail",
+            "image": "Pilt",
+            "poll": "Küsitlus",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Eksport on katkestatud",
@@ -1145,6 +1251,7 @@
         "change": "Muuda isikutuvastusserverit",
         "change_prompt": "Kas katkestame ühenduse <current /> isikutuvastusserveriga ning selle asemel loome uue ühenduse serveriga <new />?",
         "change_server_prompt": "Kui sa ei soovi kasutada <server /> serverit, et olla leitav ja ise leida sinule teadaolevaid inimesi, siis sisesta alljärgnevalt mõni teine isikutuvastusserver.",
+        "changed": "Sinu kasutatav isikutuvastusserver on muutunud",
         "checking": "Kontrollin serverit",
         "description_connected": "Sa hetkel kasutad <server></server> serverit, et olla leitav ja ise leida sinule teadaolevaid inimesi. Alljärgnevalt saad sa muuta oma isikutuvastusserverit.",
         "description_disconnected": "Sa hetkel ei kasuta isikutuvastusserverit. Et olla leitav ja ise leida sinule teadaolevaid inimesi seadista ta alljärgnevalt.",
@@ -1176,7 +1283,20 @@
         "other": "Kogukonnas %(spaceName)s ja %(count)s's muus kogukonnas."
     },
     "incompatible_browser": {
-        "title": "Sellele brauserile puudub tugi"
+        "continue": "Jätka ikkagi",
+        "description": "%(brand)s kasutab sellist funktsionaalsust, mida ei leidu sinu brauseris. %(detail)s",
+        "detail_can_continue": "Kui jätkad, siis mingi osa funktsionaalsusest ei pruugi enam toimida ja tekib risk, et kaotad tulevikus osa andmetest.",
+        "detail_no_continue": "Kui sa ei pruugi brauseri viimast versiooni, siis proovi teda uuendada ja katseta uuesti.",
+        "learn_more": "Lisateave",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Kõige paremini toimib veebirakendus brauserites <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge> ja <Safari>Safari</Safari>.",
+        "title": "Sellele brauserile puudub tugi",
+        "use_desktop_heading": "Selle asemel kasuta %(brand)s töölauaversiooni",
+        "use_mobile_heading": "Selle asemel kasuta %(brand)s nutiseadmeversiooni",
+        "use_mobile_heading_after_desktop": "Või kasuta meie rakendust nutiseadmetele",
+        "windows_64bit": "Windows (64-bitine)",
+        "windows_arm_64bit": "Windows (64-bitine ARM-platvormil)"
     },
     "info_tooltip_title": "Teave",
     "integration_manager": {
@@ -1185,6 +1305,7 @@
         "error_connecting_heading": "Ei saa ühendust lõiminguhalduriga",
         "explainer": "Lõiminguhalduritel on laiad volitused - nad võivad sinu nimel lugeda seadistusi, kohandada vidinaid, saata jututubade kutseid ning määrata õigusi.",
         "manage_title": "Halda lõiminguid",
+        "toggle_label": "Kasuta lõimingute haldurit",
         "use_im": "Robotite, vidinate ja kleepsupakkide seadistamiseks kasuta lõiminguhaldurit.",
         "use_im_default": "Robotite, vidinate ja kleepsupakkide jaoks kasuta lõiminguhaldurit <b>(%(serverName)s)</b>."
     },
@@ -1216,8 +1337,8 @@
         "error_permissions_space": "Sul pole õigusi siia kogukonda osalejate kutsumiseks.",
         "error_profile_undisclosed": "Kasutaja võib olla, aga ka võib mitte olla olemas",
         "error_transfer_multiple_target": "Kõnet on võimalik edasi suunata vaid ühele kasutajale.",
-        "error_unfederated_room": "See jututuba on föderatsioonita. Te ei saa kutsuda inimesi välistest serveritest.",
-        "error_unfederated_space": "See ruum on föderatsioonita. Te ei saa kutsuda inimesi välistest serveritest.",
+        "error_unfederated_room": "See jututuba on föderatsioonita. Sa ei saa kutsuda inimesi välistest serveritest.",
+        "error_unfederated_space": "See kogukond on föderatsioonita. Sa ei saa kutsuda inimesi välistest serveritest.",
         "error_unknown": "Tundmatu serveriviga",
         "error_user_not_found": "Sellist kasutajat pole olemas",
         "error_version_unsupported_room": "Kasutaja koduserver ei toeta selle jututoa versiooni.",
@@ -1300,12 +1421,14 @@
         "navigate_next_message_edit": "Muutmiseks liigu järgmise sõnumi juurde",
         "navigate_prev_history": "Eelmine viimati külastatud jututuba või kogukond",
         "navigate_prev_message_edit": "Muutmiseks liigu eelmise sõnumi juurde",
+        "next_landmark": "Mine kasutajaliidese järgmise olulise tähise juurde",
         "next_room": "Järgmine otsevestlus või jututuba",
         "next_unread_room": "Järgmine lugemata otsevestlus või jututuba",
         "number": "[number]",
         "open_user_settings": "Ava kasutaja seadistused",
         "page_down": "Page Down",
         "page_up": "Page Up",
+        "prev_landmark": "Mine kasutajaliidese eelmise olulise tähise juurde",
         "prev_room": "Eelmine otsevestlus või jututuba",
         "prev_unread_room": "Eelmine lugemata otsevestlus või jututuba",
         "room_list_collapse_section": "Ahenda jututubade loendi valikut",
@@ -1346,12 +1469,15 @@
         "bridge_state_workspace": "Tööruum: <networkLink/>",
         "click_for_info": "Lisateabe jaoks klõpsi",
         "currently_experimental": "Parasjagu katsejärgus.",
-        "custom_themes": "Toeta kohandatud teemade lisamist",
+        "custom_themes": "Kohandatud kujunduste lisamise võimalus",
         "dynamic_room_predecessors": "Jututoa dünaamilised eellased",
         "dynamic_room_predecessors_description": "Võta kasutusele MSC3946 (jututoa ajaloo aeglane laadimine)",
         "element_call_video_rooms": "Element Call videotoad",
+        "exclude_insecure_devices": "Sõnumite saatmisel ja vastuvõtmisel välista ebaturvalised seadmed",
+        "exclude_insecure_devices_description": "Kui see režiim on kasutusel, siis krüptitud sõnumeid ei jagata verifitseerimata seadmetega ja verifitseerimata seadmetest saabunud sõnumite puhul näidatakse vaid veateadet. Palun arvesta, et selle töörežiimi puhul sa ilmselt ei saa suhelda kasutajatega, kes pole kõiki oma seadmeid korrektselt verifitseerinud.",
         "experimental_description": "Soovid katsetada? Proovi meie uusimaid arendusmõtteid. Need funktsionaalsused pole üldsegi veel valmis, nad võivad toimida puudulikult, võivad muutuda või sootuks lõpetamata jääda. <a>Lisateavet leiad siit</a>.",
         "experimental_section": "Varased arendusjärgud",
+        "extended_profiles_msc_support": "See eeldab, et koduserver toetab MSC4133 spetsifikatsiooni",
         "feature_disable_call_per_sender_encryption": "Lülita Element Call'i kasutamisel krüptimine kasutajakohaselt välja",
         "feature_wysiwyg_composer_description": "Sõnumite kirjutamisel kasuta Markdown'i asemel täisfunktsionaalset küljendust.",
         "group_calls": "Uus rühmakõnede lahendus",
@@ -1363,8 +1489,9 @@
         "group_profile": "Profiil",
         "group_rooms": "Jututoad",
         "group_spaces": "Kogukonnakeskused",
-        "group_themes": "Teemad",
+        "group_themes": "Kujundused",
         "group_threads": "Jutulõngad",
+        "group_ui": "Kasutajaliides",
         "group_voip": "Heli ja video",
         "group_widgets": "Vidinad",
         "hidebold": "Peida teavituse täpp (ja näita loendure)",
@@ -1380,16 +1507,22 @@
         "location_share_live_description": "Tegemist on ajutise ja esialgse lahendusega: asukohad on jututoa ajaloos näha.",
         "mjolnir": "Uued võimalused osalejate eiramiseks",
         "msc3531_hide_messages_pending_moderation": "Luba modereerimist ootavate sõnumite peitmist.",
+        "new_room_list": "Võta kasutusele uus jututubade loend",
         "notification_settings": "Uued teavituste seadistused",
+        "notification_settings_beta_caption": "Võtame kasutusele senisest lihtsama viisi teavituste seadistamiseks. Kohanda rakendust %(brand)s nii nagu soovid.",
         "notification_settings_beta_title": "Teavituste seadistused",
         "notifications": "Kasuta jututoa päises teavituste riba",
+        "release_announcement": "Teave uue versiooni kohta",
+        "render_reaction_images": "Kujuta reaktsioonides ka kohandatud pilte",
+        "render_reaction_images_description": "Mõnikord nimetatakse neid ka „kohandatud emotikonideks“.",
         "report_to_moderators": "Teata moderaatoritele",
         "report_to_moderators_description": "Kui jututoas on modereerimine kasutusel, siis nupust „Teata sisust“ avaneva vormi abil saad jututoa reegleid rikkuvast sisust teatada moderaatoritele.",
         "sliding_sync": "Järkjärgulise sünkroniseerimise režiim",
         "sliding_sync_description": "Aktiivselt arendamisel ega ole võimalik välja lülitada.",
         "sliding_sync_disabled_notice": "Väljalülitamiseks logi Matrix'i võrgust välja ja seejärel tagasi",
-        "sliding_sync_server_no_support": "Selle funktsionaalsuse tugi on sinu koduserveris puudu",
+        "sliding_sync_server_no_support": "Selle funktsionaalsuse tugi on sinu koduserveris puudu!",
         "under_active_development": "Aktiivselt arendamisel.",
+        "unrealiable_e2e": "Krüptitud jututubades pole see töökindel",
         "video_rooms": "Videotoad",
         "video_rooms_a_new_way_to_chat": "Uus võimalus videovestlusteks rakenduses %(brand)s.",
         "video_rooms_always_on_voip_channels": "Videotoad on kogu aeg saadaval VoIP kanalid, mis on lõimitud jututubadega ja kasutatavad rakenduses %(brand)s.",
@@ -1398,6 +1531,7 @@
         "video_rooms_faq1_question": "Kuidas ma saan luua videotoa?",
         "video_rooms_faq2_answer": "Jah, tekstivestluse ajajoon on kuvatud videovaate kõrval.",
         "video_rooms_faq2_question": "Kas ma saan videokõne ajal ka tekstisõnumeid saata?",
+        "video_rooms_feedbackSubheading": "Täname, et proovid beetaversiooni, palun kirjelda nii palju üksikasju kui võimalik, et saaksime seda funktsionaalsust täiustada.",
         "wysiwyg_composer": "Kujundatud teksti toimeti"
     },
     "labs_mjolnir": {
@@ -1438,6 +1572,8 @@
         "last_person_warning": "Sa oled siin viimane osaleja. Kui sa nüüd lahkud, siis mitte keegi, kaasa arvatud sa ise, ei saa hiljem enam liituda.",
         "leave_room_question": "Kas oled kindel, et soovid lahkuda jututoast „%(roomName)s“?",
         "leave_space_question": "Kas oled kindel, et soovid lahkuda kogukonnakeskusest „%(spaceName)s“?",
+        "room_leave_admin_warning": "Sa oled ainus selle jututoa haldaja. Kui sa siit lahkud, ei saa keegi teine jututoa seadistusi muuta ega muid olulisi toiminguid teha.",
+        "room_leave_mod_warning": "Sa oled ainus moderaator selles jututoas. Kui sa siis lahkud, ei saa keegi teine jututoa seadistusi muuta ega muid olulisi toiminguid teha.",
         "room_rejoin_warning": "See ei ole avalik jututuba. Ilma kutseta sa ei saa uuesti liituda.",
         "space_rejoin_warning": "See ei ole avalik kogukonnakeskus. Ilma kutseta sa ei saa uuesti liituda."
     },
@@ -1495,12 +1631,19 @@
         "toggle_attribution": "Lülita omistamine sisse või välja"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s liige",
+            "other": "%(count)s liiget"
+        },
         "filter_placeholder": "Filtreeri jututoa liikmeid",
         "invite_button_no_perms_tooltip": "Sul pole õigusi kutse saatmiseks teistele kasutajatele",
+        "invited_label": "Kutsutud",
+        "no_matches": "Vasteid pole",
         "power_label": "%(userName)s (õigused %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Jututoa liikmed",
     "message_edit_dialog_title": "Sõnumite muutmised",
+    "migrating_crypto": "Oota veel üks viiv. Meil on pooleli %(brand)s uuendamine, misjärel kasutatav krüpto on kiirem ja töökindlam.",
     "mobile_guide": {
         "toast_accept": "Kasuta rakendust",
         "toast_description": "%(brand)s toimib nutiseadme veebibrauseris kastseliselt. Parima kasutajakogemuse ja uusima funktsionaalsuse jaoks kasuta meie rakendust.",
@@ -1518,6 +1661,7 @@
         "class_global": "Üldised",
         "class_other": "Muud",
         "default": "Tavaline",
+        "default_settings": "Sobita vaikimisi seadistustega",
         "email_pusher_app_display_name": "E-posti teel saadetavad teavitused",
         "enable_prompt_toast_description": "Võta kasutusele töölauakeskkonna teavitused",
         "enable_prompt_toast_title": "Teavitused",
@@ -1526,14 +1670,18 @@
         "keyword": "Märksõnad",
         "keyword_new": "Uus märksõna",
         "level_activity": "Aktiivsuse alusel",
+        "level_highlight": "Tõsta esile",
+        "level_muted": "Summutatud",
         "level_none": "Ei ühelgi juhul",
+        "level_notification": "Teavitus",
         "level_unsent": "Saatmata",
         "mark_all_read": "Märgi kõik loetuks",
         "mentions_and_keywords": "@mainimiste ja võtmesõnade puhul",
         "mentions_and_keywords_description": "Soovin teavitusi sellisena mainimiste ja võtmesõnade puhul, nagu ma neid olen <a>seadistanud</a>",
         "mentions_keywords": "Mainimised ja märksõnad",
         "message_didnt_send": "Sõnum jäi saatmata. Lisateabe saamiseks klõpsi.",
-        "mute_description": "Sa ei saa üldse teavitusi"
+        "mute_description": "Sa ei saa üldse teavitusi",
+        "mute_room": "Summuta jututuba"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s soovib verifitseerimist"
@@ -1614,7 +1762,8 @@
         "online": "Võrgus",
         "online_for": "Võrgus %(duration)s",
         "unknown": "Teadmata olek",
-        "unknown_for": "Teadmata olek viimased %(duration)s"
+        "unknown_for": "Teadmata olek viimased %(duration)s",
+        "unreachable": "Kasutaja koduserver pole kättesaadav"
     },
     "quick_settings": {
         "all_settings": "Kõik seadistused",
@@ -1634,14 +1783,10 @@
         "ongoing": "Eemaldan…",
         "reason_label": "Põhjus (kui soovid lisada)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Kas sa oled kindel, et soovid lükata kutse tagasi?",
-        "failed": "Kutse tagasi lükkamine ei õnnestunud",
-        "title": "Lükka kutse tagasi"
-    },
     "report_content": {
         "description": "Sellest sõnumist teatamine saadab tema unikaalse sõnumi tunnuse sinu koduserveri haldurile. Kui selle jututoa sõnumid on krüptitud, siis sinu koduserveri haldur ei saa lugeda selle sõnumi teksti ega vaadata seal leiduvaid faile ja pilte.",
         "disagree": "Ma ei nõustu sisuga",
+        "error_create_room_moderation_bot": "Modereerimisbotiga ei saa jututuba luua",
         "hide_messages_from_user": "Selle valikuga peidad kõik antud kasutaja praegused ja tulevased sõnumid.",
         "ignore_user": "Eira kasutajat",
         "illegal_content": "Seadustega keelatud sisu",
@@ -1649,6 +1794,8 @@
         "nature": "Palun vali rikkumise olemus ja kirjelda mis teeb selle sõnumi kuritahtlikuks.",
         "nature_disagreement": "Selle kasutaja loodud sisu on vale.\nJututoa moderaatorid saavad selle kohta teate.",
         "nature_illegal": "Selle kasutaja tegevus on seadusevastane, milleks võib olla doksimine ehk teiste eraeluliste andmete avaldamine või vägivallaga ähvardamine.\nJututoa moderaatorid saavad selle kohta teate ning nad võivad sellest teatada ka ametivõimudele.",
+        "nature_nonstandard_admin": "See jututoa on pühendatud illegaalsele või mürgisele sisule või moderaatorid ei suuda sellist sisu ohjeldada.\nSellest teatatakse %(homeserver)s haldajatele.",
+        "nature_nonstandard_admin_encrypted": "See jututoa on pühendatud illegaalsele või mürgisele sisule või moderaatorid ei suuda sellist sisu ohjeldada.\nSellest teatatakse %(homeserver)s haldajatele. Haldajatel EI ole võimalik lugeda selle jututoa krüpteeritud sisu.",
         "nature_other": "Mõni muu põhjus. Palun kirjelda seda detailsemalt.\nJututoa moderaatorid saavad selle kohta teate.",
         "nature_spam": "See kasutaja spämmib jututuba reklaamidega, reklaamlinkidega või propagandaga.\nJututoa moderaatorid saavad selle kohta teate.",
         "nature_toxic": "Selle kasutaja tegevus on äärmiselt ebasobilik, milleks võib olla teiste jututoas osalejate solvamine, peresõbralikku jututuppa täiskasvanutele mõeldud sisu lisamine või muul viisil jututoa reeglite rikkumine.\nJututoa moderaatorid saavad selle kohta teate.",
@@ -1658,38 +1805,66 @@
         "spam_or_propaganda": "Spämm või propaganda",
         "toxic_behaviour": "Ebasobilik käitumine"
     },
+    "report_room": {
+        "description": "Teata sellest jututoast oma koduserveri haldajale. Kui sõnumid on krüptitud, ei saa haldaja neid lugeda ega jagatud faile vaadata.",
+        "reason_label": "Palun kirjelda põhjust"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "%(failedCount)s sessiooni dekrüptimine ei õnnestunud!",
         "count_of_successfully_restored_keys": "%(sessionCount)s sessiooni võtme taastamine õnnestus",
-        "enter_key_description": "Sisestades turvavõtme pääsed ligi oma turvatud sõnumitele ning sätid tööle krüptitud sõnumivahetuse.",
-        "enter_key_title": "Sisesta turvavõti",
+        "enter_key_description": "Sisestades taastevõtme pääsed ligi oma turvatud sõnumitele ning sätid tööle krüptitud sõnumivahetuse.",
+        "enter_key_title": "Sisesta taastevõti",
         "enter_phrase_description": "Sisestades turvafraasi, saad ligipääsu oma turvatud sõnumitele ning sätid toimima krüptitud sõnumivahetuse.",
         "enter_phrase_title": "Sisesta turvafraas",
         "incorrect_security_phrase_dialog": "Selle turvafraasiga ei õnnestunud varundust dekrüptida: palun kontrolli, kas sa kasutad õiget turvafraasi.",
         "incorrect_security_phrase_title": "Vigane turvafraas",
         "key_backup_warning": "<b>Hoiatus</b>: sa peaksid võtmete varunduse seadistama vaid usaldusväärsest arvutist.",
         "key_fetch_in_progress": "Laadin serverist võtmeid…",
-        "key_forgotten_text": "Kui sa oled unustanud oma turvavõtme, siis sa võid <button>seadistada uued taastamise võimalused</button>",
-        "key_is_invalid": "Vigane turvavõti",
-        "key_is_valid": "See tundub olema õige turvavõti!",
+        "key_forgotten_text": "Kui sa oled unustanud oma taastevõtme, siis sa võid <button>seadistada uued taastamise võimalused</button>",
+        "key_is_invalid": "See pole korrektne taastevõti",
+        "key_is_valid": "See tundub olema õige taastevõti!",
         "keys_restored_title": "Krüptimise võtmed on taastatud",
         "load_error_content": "Varunduse oleku laadimine ei õnnestunud",
         "load_keys_progress": "%(completed)s / %(total)s võtit taastatud",
         "no_backup_error": "Varukoopiat ei leidunud!",
-        "phrase_forgotten_text": "Kui sa oled unustanud turvafraasi, siis sa saad <button1>kasutada oma turvavõtit</button1> või <button2>seadistada uued taastamise võimalused</button2>",
-        "recovery_key_mismatch_description": "Selle turvavõtmega ei õnnestunud varundust dekrüptida: palun kontrolli, kas sa kasutad õiget turvavõtit.",
-        "recovery_key_mismatch_title": "Turvavõtmed ei klapi",
+        "phrase_forgotten_text": "Kui sa oled unustanud turvafraasi, siis sa saad <button1>kasutada oma taastevõtit</button1> või <button2>seadistada uued taastamise võimalused</button2>",
+        "recovery_key_mismatch_description": "Selle taastevõtmega ei õnnestunud varukoopiat dekrüptida: palun kontrolli, kas sa kasutad õiget taastevõtit.",
+        "recovery_key_mismatch_title": "Taastevõtmed ei klapi",
         "restore_failed_error": "Varukoopiast taastamine ei õnnestu"
     },
     "right_panel": {
-        "add_integrations": "Lisa vidinaid, võrgusildu ja roboteid",
+        "add_integrations": "Lisa laiendusi",
+        "add_topic": "Lisa teema",
+        "extensions_button": "Laiendused",
+        "extensions_empty_description": "Laienduste otsimiseks ja siia jututuppa lisamiseks klõpsi linki „%(addIntegrations)s“",
+        "extensions_empty_title": "Paranda oma tõhusust lisatarvikute, vidinate, võrgusildade ja robotite lisamise abil",
         "files_button": "Failid",
         "pinned_messages": {
+            "empty_description": "Siia lisamiseks vali sõnumi ning vajuta nuppu „%(pinAction)s“",
+            "empty_title": "Et olulisi sõnumeid oleks lihtsam leida, tõsta nad esile",
+            "header": {
+                "one": "1 esiletõstetud sõnum",
+                "other": "%(count)s esiletõstetud sõnumit"
+            },
             "limits": {
-                "other": "Sa saad kinnitada kuni %(count)s vidinat"
-            }
+                "one": "",
+                "other": "Sa saad esile tõsta kuni %(count)s vidinat"
+            },
+            "menu": "Ava menüü",
+            "release_announcement": {
+                "close": "Sobib",
+                "description": "Leiad kõik esiletõstetud sõnumid siit. Uute sõnumite siia lisamiseks liigu vajaliku sõnumi kohale ja vali „Tõsta esile“.",
+                "title": "Kõik uued esiletõstetud sõnumid"
+            },
+            "reply_thread": "Vasta <link>jutulõngas</link>",
+            "unpin_all": {
+                "button": "Eemalda kõik esiletõstetud sõnumid",
+                "content": "Kas sa oled kindel, et soovid kõik esiletõstetud sõnumid eemaldad? Seda tegevust ei saa tagasi pöörata.",
+                "title": "Kas eemaldame kõik esiletõstetud sõnumid?"
+            },
+            "view": "Vaata ajajoonel"
         },
-        "pinned_messages_button": "Klammerdatud",
+        "pinned_messages_button": "Esiletõstetud sõnumid",
         "poll": {
             "active_heading": "Käimasolevad küsitlused",
             "empty_active": "Selles jututoas pole käimasolevaid küsitlusi",
@@ -1714,7 +1889,7 @@
             "view_in_timeline": "Näita küsitlust ajajoonel",
             "view_poll": "Vaata küsitlust"
         },
-        "polls_button": "Küsitluste ajalugu",
+        "polls_button": "Küsitlused",
         "room_summary_card": {
             "title": "Jututoa teave"
         },
@@ -1743,6 +1918,7 @@
             "forget": "Unusta jututuba ära",
             "low_priority": "Vähetähtis",
             "mark_read": "Märgi loetuks",
+            "mark_unread": "Märgi mitteloetuks",
             "notifications_default": "Sobita vaikimisi seadistusega",
             "notifications_mute": "Summuta jututuba",
             "title": "Jututoa eelistused",
@@ -1785,8 +1961,15 @@
         "forget_room": "Unusta see jututuba",
         "forget_space": "Unusta see kogukond",
         "header": {
+            "n_people_asking_to_join": {
+                "one": "Üks huviline soovib liituda",
+                "other": "%(count)s huvilist soovivad liituda"
+            },
             "room_is_public": "See jututuba on avalik"
         },
+        "header_avatar_open_settings_label": "Ava jututoa seadistused",
+        "header_face_pile_tooltip": "Lülita liikmete nimekiri sisse/välja",
+        "header_untrusted_label": "Pole usaldusväärne",
         "inaccessible": "See jututuba või kogukond pole hetkel ligipääsetav.",
         "inaccessible_name": "Jututuba %(roomName)s ei ole parasjagu kättesaadav.",
         "inaccessible_subtitle_1": "Proovi hiljem uuesti või küsi jututoa või kogukonna haldurilt, kas sul on ligipääs olemas.",
@@ -1809,10 +1992,9 @@
             "you_created": "Sa lõid selle jututoa."
         },
         "invite_email_mismatch_suggestion": "Selleks, et saada kutseid otse %(brand)s'isse, jaga oma seadetes seda e-posti aadressi.",
-        "invite_reject_ignore": "Hülga ja eira kasutaja",
         "invite_sent_to_email": "See kutse saadeti e-posti aadressile %(email)s",
         "invite_sent_to_email_room": "Kutse %(roomName)s jututuppa saadeti %(email)s e-posti aadressile",
-        "invite_subtitle": "<userName/> kutsus sind",
+        "invite_subtitle": "<userName/> saatis sulle kutse",
         "invite_this_room": "Kutsu siia jututuppa",
         "invite_title": "Kas sa soovid liitud jututoaga %(roomName)s?",
         "inviter_unknown": "Teadmata olek",
@@ -1834,6 +2016,8 @@
         "kicked_by": "%(memberName)s eemaldas sinu liikmelisuse",
         "kicked_from_room_by": "%(memberName)s eemaldas sind %(roomName)s jututoast",
         "knock_cancel_action": "Tühista liitumissoov",
+        "knock_denied_subtitle": "Kuna sulle on ligipääs keelatud, siis sa ei saa uuesti liituda ilma jututoa haldaja või moderaatori kutseta.",
+        "knock_denied_title": "Sulle on ligipääs keelatud",
         "knock_message_field_placeholder": "Sõnum (kui soovid lisada)",
         "knock_prompt": "Küsi võimalust liitumiseks?",
         "knock_prompt_name": "Küsi luba liitumiseks jututoaga %(roomName)s?",
@@ -1853,11 +2037,24 @@
         "not_found_title": "Seda jututuba või kogukonda pole olemas.",
         "not_found_title_name": "Jututuba %(roomName)s ei ole olemas.",
         "peek_join_prompt": "Sa vaatad jututoa %(roomName)s eelvaadet. Kas soovid sellega liituda?",
+        "pinned_message_badge": "Esiletõstetud sõnum",
+        "pinned_message_banner": {
+            "button_close_list": "Sulge loend",
+            "button_view_all": "Vaata kõiki",
+            "description": "Selles jututoas on esiletõstetud sõnumeid. Nende vaatamiseks klõpsi.",
+            "go_to_message": "Vaata esiletõstetud sõnumit ajajoonel.",
+            "title": "<bold>%(index)s of %(length)s</bold> Esiletõstetud sõnumid"
+        },
         "read_topic": "Teema lugemiseks klõpsi",
         "rejecting": "Hülgan kutset…",
         "rejoin_button": "Liitu uuesti",
         "search": {
             "all_rooms_button": "Otsi kõikidest jututubadest",
+            "placeholder": "Otsi sõnumeid…",
+            "summary": {
+                "one": "„<query/>“ päringule leidub 1 vastus",
+                "other": "„<query/>“ päringule leidub %(count)s vastust"
+            },
             "this_room_button": "Otsi sellest jututoast"
         },
         "status_bar": {
@@ -1890,38 +2087,90 @@
             },
             "uploading_single_file": "Laadin üles %(filename)s"
         },
+        "video_room": "See jututuba on mõeldud videokohtumiste jaoks",
         "waiting_for_join_subtitle": "Kui kutse saanud kasutajad on liitunud %(brand)s'ga, siis saad sa nendega suhelda ja jututuba on läbivalt krüptitud",
         "waiting_for_join_title": "Kasutajate liitumise ootel %(brand)s'ga"
     },
     "room_list": {
         "add_room_label": "Lisa jututuba",
         "add_space_label": "Lisa kogukonnakeskus",
+        "appearance": "Välimus",
         "breadcrumbs_empty": "Hiljuti külastatud jututubasid ei leidu",
         "breadcrumbs_label": "Hiljuti külastatud jututoad",
+        "empty": {
+            "no_chats": "Vestlusi veel ei leidu",
+            "no_chats_description": "Alusta sellest, et leia mõni vestluspartner või loo oma jututuba",
+            "no_chats_description_no_room_rights": "Alusta sellest, et leia mõni vestluspartner",
+            "no_favourites": "Sa pole veel ühtegi vestlust märkinud lemmikuks",
+            "no_favourites_description": "Vestluse saad märkida lemmikuks tema seadistustest",
+            "no_invites": "Sul pole lugemata kutseid",
+            "no_mentions": "Sul pole lugemata mainimisi",
+            "no_people": "Sul pole veel ühtegi otsevestlust kellegagi",
+            "no_people_description": "Kõikide muude vestluste nägemiseks eemalda otsingufiltrid",
+            "no_rooms": "Sa veel ei osale mitte üheski jututoas",
+            "no_rooms_description": "Kõikide oma muude vestluste nägemiseks eemalda otsingufiltrid",
+            "no_unread": "Õnnitlused! Sul pole ühtegi lugemata sõnumit",
+            "show_activity": "Vaata kõiki tegevusi",
+            "show_chats": "Näita kõiki vestlusi"
+        },
         "failed_add_tag": "Sildi %(tagName)s lisamine jututoale ebaõnnestus",
         "failed_remove_tag": "Sildi %(tagName)s eemaldamine jututoast ebaõnnestus",
         "failed_set_dm_tag": "Otsevestluse sildi seadmine ei õnnestunud",
+        "filters": {
+            "favourite": "Lemmikud",
+            "invites": "Kutsed",
+            "mentions": "Mainimised",
+            "people": "Inimesed",
+            "rooms": "Jututoad",
+            "unread": "Lugemata"
+        },
         "home_menu_label": "Avalehe valikud",
         "join_public_room_label": "Liitu avaliku jututoaga",
         "joining_rooms_status": {
             "other": "Parasjagu liitun %(count)s jututoaga",
             "one": "Parasjagu liitun %(count)s jututoaga"
         },
+        "list_title": "Jututubade loend",
+        "more_options": {
+            "copy_link": "Kopeeri jututoa link",
+            "favourited": "Määratud lemmikuks",
+            "leave_room": "Lahku jututoast",
+            "low_priority": "Vähetähtis",
+            "mark_read": "Märgi loetuks",
+            "mark_unread": "Märgi mitteloetuks"
+        },
         "notification_options": "Teavituste eelistused",
+        "open_space_menu": "Ava kogukonna menüü",
+        "primary_filters": "Jututubade loendi filtrid",
         "redacting_messages_status": {
             "other": "Kustutame sõnumeid %(count)s jututoas",
             "one": "Kustutame sõnumeid %(count)s jututoas"
         },
+        "room": {
+            "more_options": "Täiendavad seadistused",
+            "open_room": "Ava jututuba: %(roomName)s"
+        },
+        "room_options": "Jututoa valikud",
         "show_less": "Näita vähem",
+        "show_message_previews": "Näita sõnumite eelvaateid",
         "show_n_more": {
-            "other": "Näita veel %(count)s sõnumit",
-            "one": "Näita veel %(count)s sõnumit"
+            "one": "Näita veel %(count)s vestlust",
+            "other": "Näita veel %(count)s vestlust"
         },
         "show_previews": "Näita sõnumite eelvaateid",
+        "sort": "Järjesta",
         "sort_by": "Järjestamisviis",
         "sort_by_activity": "Aktiivsuse alusel",
         "sort_by_alphabet": "Tähestiku järjekorras",
+        "sort_type": {
+            "activity": "Aktiivsuse alusel",
+            "atoz": "Tähestiku alusel"
+        },
         "sort_unread_first": "Näita lugemata sõnumitega jututubasid esimesena",
+        "space_menu": {
+            "home": "Kogukonna avaleht",
+            "space_settings": "Kogukonna seadistused"
+        },
         "space_menu_label": "%(spaceName)s menüü",
         "sublist_options": "Loendi valikud",
         "suggested_rooms_heading": "Soovitatud jututoad"
@@ -1993,6 +2242,8 @@
             "error_deleting_alias_description": "Selle aadressi kustutamisel tekkis viga. See kas juba on kustutatud või tekkis ajutine tõrge.",
             "error_deleting_alias_description_forbidden": "Sinul pole õigusi selle aadressi kustutamiseks.",
             "error_deleting_alias_title": "Viga aadresi kustutamisel",
+            "error_publishing": "Jututoa avaldamine ei õnnestunud",
+            "error_publishing_detail": "Jututoa avaldamisel tekkis viga",
             "error_save_space_settings": "Kogukonnakeskuse seadistuste salvestamine ei õnnestunud.",
             "error_updating_alias_description": "Jututoa lisaaadressi uuendamisel tekkis viga. See kas pole serveris lubatud või tekkis mingi ajutine viga.",
             "error_updating_canonical_alias_description": "Jututoa põhiaadressi uuendamisel tekkis viga. See kas pole serveris lubatud või tekkis mingi ajutine viga.",
@@ -2029,6 +2280,12 @@
             "upload_sound_label": "Laadi üles oma helifail",
             "uploaded_sound": "Üleslaaditud heli"
         },
+        "people": {
+            "knock_empty": "Päringuid pole",
+            "knock_section": "Soovides liitumist",
+            "see_less": "Näita vähem",
+            "see_more": "Näita rohkem"
+        },
         "permissions": {
             "add_privileged_user_description": "Lisa selles jututoas ühele või mitmele kasutajale täiendavaid õigusi",
             "add_privileged_user_filter_placeholder": "Vali kasutajad sellest jututoast…",
@@ -2056,7 +2313,7 @@
             "m.room.history_visibility": "Muuda vestlusajaloo nähtavust",
             "m.room.name": "Muuda jututoa nime",
             "m.room.name_space": "Muuda kogukonna nime",
-            "m.room.pinned_events": "Halda klammerdatud sündmusi",
+            "m.room.pinned_events": "Halda esiletõstetud sündmusi",
             "m.room.power_levels": "Muuda õigusi",
             "m.room.redaction": "Eemalda minu saadetud sõnumid",
             "m.room.server_acl": "Muuda serveri ligipääsuõigusi",
@@ -2142,7 +2399,7 @@
             "public_without_alias_warning": "Sellele jututoale viitamiseks palun lisa talle aadress.",
             "publish_room": "Tee see jututuba nähtavaks avalikus jututubade kataloogis.",
             "publish_space": "Tee see kogukond nähtavaks avalikus jututubade kataloogis.",
-            "strict_encryption": "Ära iialgi saada sellest sessioonist krüptitud sõnumeid verifitseerimata sessioonidesse selles jututoas",
+            "strict_encryption": "Saada sõnumeid vaid verifitseeritud kasutajatele.",
             "title": "Turvalisus ja privaatsus"
         },
         "title": "Jututoa seadistused - %(roomName)s",
@@ -2196,6 +2453,10 @@
         "recent_changes_heading": "Hiljutised muudatused, mis pole veel alla laetud või saabunud",
         "title": "Server ei vasta päringutele"
     },
+    "service_worker_error": {
+        "description": "Selleks, et autenditud meediafailide laadimine Matrixi sisuhoidlast toimiks, eeldab %(brand)s taustal toimiva teenuse töötleja kasutamist. See funktsionaalsus pole sinu veebibrauseris toetatud ja meediafailid võivad jääda laadimata.",
+        "title": "Teenuse töötleja laadimine ei õnnestunud"
+    },
     "seshat": {
         "error_initialising": "Sõnumite otsingu ettevalmistamine ei õnnestunud, lisateavet leiad <a>rakenduse seadistustest</a>",
         "reset_button": "Lähtesta sündmuste andmekogu",
@@ -2212,6 +2473,8 @@
             "access_token_detail": "Sinu pääsuluba annab täismahulise ligipääsu sinu kasutajakontole. Palun ära jaga seda teistega.",
             "brand_version": "%(brand)s'i versioon:",
             "clear_cache_reload": "Tühjenda puhver ja laadi uuesti",
+            "crypto_version": "Krüpto versioon:",
+            "dialog_title": "<strong>Seadistused:</strong> Abiteave ja info meie kohta",
             "help_link": "Kui otsid lisateavet %(brand)s'i kasutamise kohta, palun vaata <a>siia</a>.",
             "homeserver": "Koduserveri aadress <code>%(homeserverUrl)s</code>",
             "identity_server": "Isikutuvastusserveri aadress <code>%(identityServerUrl)s</code>",
@@ -2220,22 +2483,35 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Seadistused:</strong> Kasutajakonto",
+            "title": "Kasutajakonto"
+        },
         "all_rooms_home": "Näita kõiki jututubasid avalehel",
         "all_rooms_home_description": "Kõik sinu jututoad on nähtavad avalehel.",
         "always_show_message_timestamps": "Alati näita sõnumite ajatempleid",
         "appearance": {
+            "bundled_emoji_font": "Kasuta rakendusega kaasa pandud emotikonide kirjatüüpi",
+            "compact_layout": "Näita teksti ja sõnumeid kompaktsena",
+            "compact_layout_description": "Selle eelistuse jaoks pead kasutama moodsat paigutust.",
             "custom_font": "Kasuta süsteemset fonti",
             "custom_font_description": "Vali sinu seadmes leiduv fondi nimi ning %(brand)s proovib seda kasutada.",
             "custom_font_name": "Süsteemse fondi nimi",
             "custom_font_size": "Kasuta kohandatud suurust",
-            "custom_theme_error_downloading": "Viga teema teabefaili allalaadimisel.",
-            "custom_theme_invalid": "Vigane teemafail.",
+            "custom_theme_add": "Lisa kohandatud kujundus",
+            "custom_theme_downloading": "Laadime alla kohandatud kujundust…",
+            "custom_theme_error_downloading": "Viga kujunduse allalaadimisel",
+            "custom_theme_help": "Sisesta kohandatud kujunduse aadress.",
+            "custom_theme_invalid": "Vigane kujundusefail.",
+            "dialog_title": "<strong>Seadistused:</strong> Välimus",
             "font_size": "Fontide suurus",
+            "font_size_default": "%(fontSize)s (vaikimisi)",
+            "high_contrast": "Kontrastne kujundus",
             "image_size_default": "Tavaline",
             "image_size_large": "Suur",
             "layout_bubbles": "Jutumullid",
-            "layout_irc": "IRC (katseline)",
-            "match_system_theme": "Kasuta süsteemset teemat",
+            "layout_irc": "IRC (katseline )",
+            "match_system_theme": "Kasuta süsteemset kujundust",
             "timeline_image_size": "Piltide suurus ajajoonel"
         },
         "automatic_language_detection_syntax_highlight": "Kasuta süntaksi esiletõstmisel automaatset keeletuvastust",
@@ -2245,9 +2521,80 @@
         "code_block_expand_default": "Vaikimisi kuva koodiblokid tervikuna",
         "code_block_line_numbers": "Näita koodiblokkides reanumbreid",
         "disable_historical_profile": "Sõnumite ajaloos leiduvate kasutajate puhul näita kehtivat tunnuspilti ning nime",
+        "discovery": {
+            "title": "Kuidas on võimalik sind leida"
+        },
         "emoji_autocomplete": "Näita kirjutamise ajal emoji-soovitusi",
         "enable_markdown": "Kasuta Markdown-süntaksit",
         "enable_markdown_description": "Vormindamata teksti koostamiseks alusta sõnumeid <code>/plain</code> käsuga.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Sinu kasutajakonto andmed, kontaktid, eelistused ja vestluste loend säiluvad",
+                "breadcrumb_page": "Lähtesta krüptimine",
+                "breadcrumb_second_description": "Sa kaotad ligipääsu sõnumite ajalooole, mis on salvestatud vaid serveris",
+                "breadcrumb_third_description": "Sa pead kõik oma olemasolevad seadmed ja kontaktid uuesti verifitseerima",
+                "breadcrumb_title": "Kas sa oled kindel, et soovid oma krüptoidentiteeti lähtestada?",
+                "breadcrumb_title_forgot": "Kas unustasid oma taastevõtme? Pead oma identiteedi lähtestama.",
+                "breadcrumb_title_sync_failed": "Võtmehoidla sünkroniseerimine ei õnnestunud. Sa pead võrguidentiteedi lähtestama.",
+                "breadcrumb_warning": "Tee seda ainult siis, kui arvad, et sinu kasutajakonto võib olla ohustatud kolmandate osapoolet poolt.",
+                "details_title": "Krüptimise üksikasjad",
+                "do_not_close_warning": "Ära sulge seda akent enne, kui lähtestamine on lõppenud",
+                "export_keys": "Ekspordi võtmed",
+                "import_keys": "Impordi võtmed",
+                "other_people_device_description": "Hoiatus: kui kasutaja pole sinuga verifitseerimist läbi teinud (näiteks emojide võrdlemise abil), siis ta ei saa sinu krüptitud sõnumeid. Lisaks ei saadeta krüptitud sõnumeid verifitseeritud kasutajate verifitseerimata seadmetesse.",
+                "other_people_device_label": "Krüptitud jututubades saada sõnumeid vaid verifitseeritud kasutajatele",
+                "other_people_device_title": "Teiste kasutajate seadmed",
+                "reset_identity": "Lähtesta krüptoidentiteet",
+                "reset_in_progress": "Lähtestamine on töös...",
+                "session_id": "Sessiooni tunnus:",
+                "session_key": "Sessioonivõti:",
+                "title": "Täiendav teave"
+            },
+            "confirm_key_storage_off": "Kas sa oled kindel, et tahad krüptovõtmete hoidlat mitte kasutada?",
+            "confirm_key_storage_off_description": "Kui sa logid välja kõikidest oma seadmetest, siis kaotad ligipääsu oma sõnumite ajaloole ning pead kõik olemasolevad kontaktid uuesti verifitseerima. <a>Lisateave</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Kustuta krüptovõtmete hoidla",
+                "confirm": "Kustuta krüptovõtmete hoidla",
+                "description": "Krüptovõtmete hoidla hoidla kustutamisega eemaldad serverist oma krüptoidentiteedi ja sõnumite võtmed ning lülitad välja järgnevad turvalisusega seotud funktsionaalsused:",
+                "list_first": "Sa ei saa uutes seadmetes lugeda varasemaid krüptitud sõnumeid",
+                "list_second": "Kui logid kõikjal %(brand)s rakendusest välja, siis kaotad ligipääsu kõikidele oma krüptitud sõnumitele",
+                "title": "Kas sa oled kindel, et soovid krüptovõtmete hoidla välja lülitada ning seejärel kustutada?"
+            },
+            "device_not_verified_button": "Verifitseeri see seade",
+            "device_not_verified_description": "Oma krüptoseadistuste nägemiseks palun verifitseeri see seade.",
+            "device_not_verified_title": "Seade on verifitseerimata",
+            "dialog_title": "<strong>Seadistused:</strong>Krüptimine",
+            "key_storage": {
+                "allow_key_storage": "Kasuta krüptovõtmete hoidlat",
+                "description": "Hoia oma krüptoidentiteeti ja sõnumite krüptovõtmeid turvaliselt serveris. See võimaldab sul lugeda oma varasemaid sõnumeid kõikides uutes seadmetes. <a>Lisateave</a>",
+                "title": "Krüptovõtmete hoidla"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Korda uut taastevõtit",
+                "change_recovery_confirm_description": "Toimingu lõpetamiseks palun sisesta alljärgnevalt oma uus taastevõti. Senine taastevõti enam ei toimi.",
+                "change_recovery_confirm_title": "Sisesta oma uus taastevõti",
+                "change_recovery_key": "Muuda taastevõtit",
+                "change_recovery_key_description": "Palun salvesta see taastevõti turvalisel viisil. Muutuse kinnitamiseks klõpsi „Jätka“.",
+                "change_recovery_key_title": "Kas muudame taastevõtit?",
+                "description": "Kui sa oled kaotanud ligipääsu kõikidele oma olemasolevatele seadmetele, siis sa saad taastevõtme abil taastada ligipääsu oma krüptoidentiteedile ja sõnumite ajaloole.",
+                "enter_key_error": "Sinu sisestatud taastevõti pole korrektne.",
+                "enter_recovery_key": "Sisesta taastevõti",
+                "forgot_recovery_key": "Kas unustasid taastevõtme?",
+                "key_storage_warning": "Sinu võtmehoidla pole sünkroonis. Vea parandamiseks palun klõpsi ühte järgnevatest nuppudest.",
+                "save_key_description": "Ära jaga seda mitte kellegagi!",
+                "save_key_title": "Taastevõti",
+                "set_up_recovery": "Seadista taastamine",
+                "set_up_recovery_confirm_button": "Lõpeta seadistamine",
+                "set_up_recovery_confirm_description": "Taastamise seadistamise lõpetamiseks palun sisesta eelmises vaates näidatud taastevõti.",
+                "set_up_recovery_confirm_title": "Kinnitamiseks sisesta oma taastevõti",
+                "set_up_recovery_description": "Sinu krüptovõtmete hoidlat kaitseb taastevõti. Kui peale seadistamist peaksid vajama uut taastevõtit, siis saad ta uuesti luua valikust „%(changeRecoveryKeyButton)s“.",
+                "set_up_recovery_save_key_description": "Palun märgi see taastevõti üles ja hoia teda turvaliselt, näiteks digitaalses salasõnalaekas, krüptitud märkmetes või vana kooli seifis.",
+                "set_up_recovery_save_key_title": "Salvesta oma taastevõti turvalisel viisil",
+                "set_up_recovery_secondary_description": "Kui klõpsid nuppu „Jätka“, loome me sulle uue taastevõtme.",
+                "title": "Taastamine"
+            },
+            "title": "Krüptimine"
+        },
         "general": {
             "account_management_section": "Kontohaldus",
             "account_section": "Kasutajakonto",
@@ -2260,6 +2607,14 @@
             "add_msisdn_dialog_title": "Lisa telefoninumber",
             "add_msisdn_instructions": "Saatsime tekstisõnumi numbrile +%(msisdn)s. Palun sisesta seal kuvatud kontrollkood.",
             "add_msisdn_misconfigured": "„Add“ ja „bind“ meetodid MSISDN jaoks on valesti seadistatud",
+            "allow_spellcheck": "Kasuta õigekontrolli",
+            "application_language": "Rakenduse keel",
+            "application_language_reload_hint": "Teise keele valimisel rakendus käivitub uuesti",
+            "avatar_remove_progress": "Eemaldame pilti...",
+            "avatar_save_progress": "Laadime pilti üles...",
+            "avatar_upload_error_text": "Failivormingu tugi puudub või fail on suurem, kui %(size)s.",
+            "avatar_upload_error_text_generic": "See failivorming ei pruugi olla toetatud.",
+            "avatar_upload_error_title": "Tunnuspildi faili üleslaadimine ei õnnestunud",
             "confirm_adding_email_body": "Klõpsi järgnevat nuppu e-posti aadressi lisamise kinnitamiseks.",
             "confirm_adding_email_title": "Kinnita e-posti aadressi lisamine",
             "deactivate_confirm_body": "Kas sa oled kindel, et soovid oma konto sulgeda? Seda tegevust ei saa hiljem tagasi pöörata.",
@@ -2275,10 +2630,14 @@
             "deactivate_confirm_erase_label": "Peida minu sõnumid uute liitujate eest",
             "deactivate_section": "Deaktiveeri konto",
             "deactivate_warning": "Kuna kasutajakonto dektiveerimist ei saa tagasi pöörata, siis palun ole ettevaatlik!",
-            "discovery_email_empty": "Otsinguvõimaluste loend kuvatakse, kui oled ülale sisestanud e-posti aadressi.",
+            "discovery_email_empty": "Otsinguvõimaluste loend kuvatakse, kui oled sisestanud e-posti aadressi.",
             "discovery_email_verification_instructions": "Verifitseeri klõpsides viidet saabunud e-kirjas",
-            "discovery_msisdn_empty": "Otsinguvõimaluste loend kuvatakse, kui oled ülale sisestanud telefoninumbri.",
+            "discovery_msisdn_empty": "Otsinguvõimaluste loend kuvatakse, kui oled sisestanud telefoninumbri.",
             "discovery_needs_terms": "Selleks, et sind võiks leida e-posti aadressi või telefoninumbri alusel, nõustu isikutuvastusserveri (%(serverName)s) kasutustingimustega.",
+            "discovery_needs_terms_title": "Võimalda teistel Matrixi võrgu kasutajatel sind leida",
+            "display_name": "Kuvatav nimi",
+            "display_name_error": "Kuvatava nime määramine ei õnnestu",
+            "email_adding_unsupported_by_hs": "See koduserver ei toeta kasutajakonto juurde e-posti aadressi lisamise võimalust.",
             "email_address_in_use": "See e-posti aadress on juba kasutusel",
             "email_address_label": "E-posti aadress",
             "email_not_verified": "Sinu e-posti aadress pole veel verifitseeritud",
@@ -2303,7 +2662,9 @@
             "error_share_msisdn_discovery": "Telefoninumbri jagamine ei õnnestunud",
             "identity_server_no_token": "Ei leidu tunnusluba isikutuvastusserveri jaoks",
             "identity_server_not_set": "Isikutuvastusserver on määramata",
-            "language_section": "Keel ja piirkond",
+            "invalid_phone_number": "Sisestatud telefoninumber ei tundu olema korrektne",
+            "language_section": "Keel",
+            "msisdn_adding_unsupported_by_hs": "See koduserver ei toeta kasutajakonto juurde telefoninumbri lisamise võimalust.",
             "msisdn_in_use": "See telefoninumber on juba kasutusel",
             "msisdn_label": "Telefoninumber",
             "msisdn_verification_field_label": "Verifikatsioonikood",
@@ -2312,11 +2673,16 @@
             "oidc_manage_button": "Halda kasutajakontot",
             "password_change_section": "Määra kontole uus salasõna…",
             "password_change_success": "Sinu salasõna muutmine õnnestus.",
+            "personal_info": "Isiklik teave",
+            "profile_subtitle": "Nii kuvatakse sind teistele selle rakenduse ja Matrixi võrgu kasutajatele.",
+            "profile_subtitle_oidc": "Sinu kasutajakontot hallatakse eraldi isikutuvastusserveris ning kõiki sinu isiklikke andmeid ei saa siin muuta.",
             "remove_email_prompt": "Eemalda %(email)s?",
             "remove_msisdn_prompt": "Eemalda %(phone)s?",
-            "spell_check_locale_placeholder": "Vali lokaat"
+            "spell_check_locale_placeholder": "Vali lokaat",
+            "unable_to_load_emails": "E-posti aadresside laadimine ei õnnestu",
+            "unable_to_load_msisdns": "Telefoninumbrite laadimine ei õnnestu",
+            "username": "Kasutajanimi"
         },
-        "image_thumbnails": "Näita piltide eelvaateid või väikepilte",
         "inline_url_previews_default": "Luba URL'ide vaikimisi eelvaated",
         "inline_url_previews_room": "Luba URL'ide vaikimisi eelvaated selles jututoas osalejate jaoks",
         "inline_url_previews_room_account": "Luba URL'ide eelvaated selle jututoa jaoks (mõjutab vaid sind)",
@@ -2338,21 +2704,21 @@
                 "enter_phrase_description": "Andmete kaitsmiseks sisesta turvafraas, mida vaid sina tead. Ole mõistlik ja palun ära kasuta selleks oma tavalist konto salasõna.",
                 "enter_phrase_title": "Sisesta turvafraas",
                 "enter_phrase_to_confirm": "Kinnitamiseks palun sisesta turvafraas teist korda.",
-                "generate_security_key_description": "Me loome turvavõtme, mida sa peaksid hoidma turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.",
-                "generate_security_key_title": "Loo turvavõti",
+                "generate_security_key_description": "Me loome taastevõtme, mida sa peaksid hoidma turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.",
+                "generate_security_key_title": "Loo taastevõti",
                 "pass_phrase_match_failed": "Ei klapi mitte.",
                 "pass_phrase_match_success": "Klapib!",
                 "phrase_strong_enough": "Suurepärane! Turvafraas on piisavalt kange.",
                 "secret_storage_query_failure": "Ei õnnestu tuvastada turvahoidla olekut",
-                "security_key_safety_reminder": "Kuna seda kasutatakse sinu krüptitud andmete kaitsmiseks, siis hoia oma turvavõtit kaitstud ja turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.",
+                "security_key_safety_reminder": "Kuna seda kasutatakse sinu krüptitud andmete kaitsmiseks, siis hoia oma taastevõtit kaitstud ja turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.",
                 "set_phrase_again": "Mine tagasi ja sisesta nad uuesti.",
                 "settings_reminder": "Samuti võid sa seadetes võtta kasutusse turvalise varunduse ning hallata oma krüptovõtmeid.",
                 "title_confirm_phrase": "Kinnita turvafraas",
-                "title_save_key": "Salvesta turvavõti",
+                "title_save_key": "Salvesta oma taastevõti",
                 "title_set_phrase": "Määra turvafraas",
                 "unable_to_setup": "Turvahoidla kasutuselevõtmine ei õnnestu",
                 "use_different_passphrase": "Kas kasutame muud paroolifraasi?",
-                "use_phrase_only_you_know": "Sisesta turvafraas, mida vaid sina tead ning lisaks võid salvestada varunduse turvavõtme."
+                "use_phrase_only_you_know": "Sisesta turvafraas, mida vaid sina tead ning lisaks võid salvestada varunduse taastevõtme."
             }
         },
         "key_export_import": {
@@ -2370,12 +2736,28 @@
             "phrase_strong_enough": "Suurepärane! See paroolifraas on piisavalt kange"
         },
         "keyboard": {
+            "dialog_title": "<strong>Seadistused:</strong> Klaviatuur",
             "title": "Klaviatuur"
         },
+        "labs": {
+            "dialog_title": "<strong>Seadistused:</strong> Katsed"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Seadistused:</strong> Eiratud kasutajad"
+        },
+        "media_preview": {
+            "hide_avatars": "Peida jututoa ja kutsuja tunnuspildid",
+            "hide_media": "Peida alati",
+            "media_preview_description": "Peidetud meediumi saad alati näha temal klõpsides",
+            "media_preview_label": "Näita ajajoonel meediat",
+            "show_in_private": "Privaatsetes jututubades",
+            "show_media": "Alati"
+        },
         "notifications": {
             "default_setting_description": "See seadistus kehtib vaikimisi kõikides sinu jututubades.",
             "default_setting_section": "Soovin teavitusi (vaikimisi seadistused)",
             "desktop_notification_message_preview": "Näita sõnumi eelvaadet töölauakeskkonnale omases teavituses",
+            "dialog_title": "<strong>Seadistused:</strong> Teavitused",
             "email_description": "Palu saata e-posti teel ülevaade märkamata teavitustest",
             "email_section": "E-kirja kokkuvõte",
             "email_select": "Vali e-posti aadressid, millele soovid kokkuvõtet saada. E-posti aadresse saad hallata seadistuste alajaotuses <button>Üldist</button>.",
@@ -2428,17 +2810,21 @@
             "voip": "Kõned ja videokõned"
         },
         "preferences": {
+            "Electron.enableHardwareAcceleration": "Kasuta riistvaralist kiirendust (jõustamiseks käivita %(appName)s uuesti)",
             "always_show_menu_bar": "Näita aknas alati menüüriba",
             "autocomplete_delay": "Viivitus automaatsel sõnalõpetusel (ms)",
             "code_blocks_heading": "Lähtekoodi lõigud",
             "compact_modern": "Kasuta kompaktsemat moodsat kasutajaliidest",
             "composer_heading": "Sõnumite kirjutamine",
+            "default_timezone": "Brauseri vaikimisi ajavöönd (%(timezone)s)",
+            "dialog_title": "<strong>Seadistused:</strong> Eelistused",
             "enable_hardware_acceleration": "Kasuta riistvaralist kiirendust",
             "enable_tray_icon": "Näita süsteemisalve ikooni ja Element'i akna sulgemisel minimeeri ta salve",
             "keyboard_heading": "Kiirklahvid",
             "keyboard_view_shortcuts_button": "<a>Vaata siit</a> kõiki kiirklahve.",
             "media_heading": "Pildid, gif'id ja videod",
             "presence_description": "Jaga teistega oma olekut ja tegevusi.",
+            "publish_timezone": "Avalda oma ajavööd oma avalikus profiilis",
             "rm_lifetime": "Lugemise markeri iga (ms)",
             "rm_lifetime_offscreen": "Lugemise markeri iga, kui Element pole fookuses (ms)",
             "room_directory_heading": "Jututubade loend",
@@ -2446,55 +2832,26 @@
             "show_avatars_pills": "Näita tunnuspilte kasutajate, jututubade ja sündmuste mainimistes",
             "show_polls_button": "Näita küsitluste nuppu",
             "surround_text": "Erimärkide sisestamisel märgista valitud tekst",
-            "time_heading": "Aegade kuvamine"
+            "time_heading": "Aegade kuvamine",
+            "user_timezone": "Seadista ajavöönd"
         },
         "prompt_invite": "Hoiata enne kutse saatmist võimalikule vigasele Matrix'i kasutajatunnusele",
         "replace_plain_emoji": "Automaatelt asenda vormindamata tekst emotikoniga",
         "security": {
-            "4s_public_key_in_account_data": "kasutajakonto andmete hulgas",
-            "4s_public_key_status": "Turvahoidla avalik võti:",
-            "backup_key_cached_status": "Varukoopia võti on puhverdatud:",
-            "backup_key_stored_status": "Varukoopia võti on salvestatud:",
-            "backup_key_unexpected_type": "tundmatut tüüpi",
-            "backup_key_well_formed": "korrektses vormingus",
-            "backup_keys_description": "Selleks puhuks, kui sa kaotad ligipääsu kõikidele oma sessioonidele, tee varukoopia oma krüptovõtmetest ja kasutajakonto seadistustest. Unikaalse turvavõtmega tagad selle, et sinu varukoopia on kaitstud.",
+            "analytics_description": "Vigade tuvastamiseks palun jaga meiega anonüümseid andmeid. Isiklikke andmeid me ei kogu ja kolmandad osapooled ei ole sellega seotud.",
             "bulk_options_accept_all_invites": "Võta vastu kõik %(invitedRooms)s kutsed",
             "bulk_options_reject_all_invites": "Lükka tagasi kõik %(invitedRooms)s kutsed",
             "bulk_options_section": "Masstoimingute seadistused",
-            "cross_signing_cached": "on puhverdatud kohalikus seadmes",
-            "cross_signing_homeserver_support": "Koduserver on tugi sellele funktsionaalusele:",
-            "cross_signing_homeserver_support_exists": "olemas",
-            "cross_signing_in_4s": "turvahoidlas",
-            "cross_signing_in_memory": "on mälus",
-            "cross_signing_master_private_Key": "Üldine privaatvõti:",
-            "cross_signing_not_cached": "ei leidu kohalikus seadmes",
-            "cross_signing_not_found": "pole leitavad",
-            "cross_signing_not_in_4s": "ei leidunud turvahoidlas",
-            "cross_signing_not_stored": "ei ole salvestatud",
-            "cross_signing_private_keys": "Privaatvõtmed risttunnustamise jaoks:",
-            "cross_signing_public_keys": "Avalikud võtmed risttunnustamise jaoks:",
-            "cross_signing_self_signing_private_key": "Sinu privaatvõtmed:",
-            "cross_signing_user_signing_private_key": "Kasutaja privaatvõti:",
-            "cryptography_section": "Krüptimine",
-            "delete_backup": "Kustuta varukoopia",
-            "delete_backup_confirm_description": "Kas sa oled kindel? Kui sul muud varundust pole, siis kaotad ligipääsu oma krüptitud sõnumitele.",
+            "dehydrated_device_description": "Võrguühenduseta seadme funktsionaalsus võimaldab saada krüptitud sõnumeid ka siis, kui sa pole ühtegi seadmesse sisse loginud",
+            "dehydrated_device_enabled": "Võrguühenduseta seadme funktsionaalsus on sisse lülitatud",
+            "dialog_title": "<strong>Seadistused:</strong> Turvalisus ja privaatsus",
             "e2ee_default_disabled_warning": "Sinu serveri haldur on lülitanud läbiva krüptimise omavahelistes jututubades ja otsesõnumites välja.",
             "enable_message_search": "Võta kasutusele sõnumite otsing krüptitud jututubades",
             "encryption_section": "Krüptimine",
-            "error_loading_key_backup_status": "Võtmete varunduse oleku laadimine ei õnnestunud",
-            "export_megolm_keys": "Ekspordi jututubade läbiva krüptimise võtmed",
             "ignore_users_empty": "Sa ei ole veel kedagi eiranud.",
             "ignore_users_section": "Eiratud kasutajad",
-            "import_megolm_keys": "Impordi E2E läbiva krüptimise võtmed jututubade jaoks",
-            "key_backup_active": "See sessioon varundab sinu krüptovõtmeid.",
-            "key_backup_active_version_none": "Ei ühelgi juhul",
             "key_backup_algorithm": "Algoritm:",
-            "key_backup_complete": "Kõik krüptovõtmed on varundatud",
             "key_backup_connect": "Seo see sessioon krüptovõtmete varundusega",
-            "key_backup_connect_prompt": "Enne väljalogimist seo see sessioon krüptovõtmete varundusega. Kui sa seda ei tee, siis võid kaotada võtmed, mida kasutatakse vaid siin sessioonis.",
-            "key_backup_in_progress": "Varundan %(sessionsRemaining)s krüptovõtmeid…",
-            "key_backup_inactive": "See sessioon <b>ei varunda sinu krüptovõtmeid</b>, aga sul on olemas varundus, millest saad taastada ning millele saad võtmeid lisada.",
-            "key_backup_inactive_warning": "Sinu selle sessiooni krüptovõtmeid <b>ei varundata</b>.",
             "message_search_disable_warning": "Kui see seadistus pole kasutusel, siis krüptitud jututubade sõnumeid otsing ei vaata.",
             "message_search_disabled": "Turvaliselt puhverda krüptitud sõnumid kohalikku arvutisse ja võimalda kasutada neid otsingus.",
             "message_search_enabled": {
@@ -2514,19 +2871,14 @@
             "message_search_unsupported": "%(brand)s'is on puudu need komponendid, mis võimaldavad otsida kohalikest turvaliselt puhverdatud krüptitud sõnumitest. Kui sa tahaksid sellist funktsionaalsust katsetada, siis pead kompileerima %(brand)s'i variandi, kus <nativeLink>need komponendid on lisatud</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s ei võimalda veebibrauseris töötades krüptitud sõnumeid turvaliselt puhverdada. Selleks, et krüptitud sõnumeid saaks otsida, kasuta <desktopLink>%(brand)s Desktop</desktopLink> rakendust Matrix'i kliendina.",
             "record_session_details": "Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi",
-            "restore_key_backup": "Taasta varukoopiast",
-            "secret_storage_not_ready": "ei ole valmis",
-            "secret_storage_ready": "valmis",
-            "secret_storage_status": "Turvahoidla:",
             "send_analytics": "Saada arendajatele analüütikat",
-            "session_id": "Sessiooni tunnus:",
-            "session_key": "Sessiooni võti:",
-            "strict_encryption": "Ära iialgi saada sellest sessioonist krüptitud sõnumeid verifitseerimata sessioonidesse"
+            "strict_encryption": "Saada sõnumeid vaid verifitseeritud kasutajatele"
         },
         "send_read_receipts": "Saada lugemisteatiseid",
         "send_read_receipts_unsupported": "Sinu koduserver ei võimalda lugemisteatiste keelamist.",
         "send_typing_notifications": "Anna märku teisele osapoolele, kui mina sõnumit kirjutan",
         "sessions": {
+            "best_security_note": "Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta või ei tunne ära.",
             "browser": "Brauser",
             "confirm_sign_out": {
                 "one": "Kinnita selle seadme väljalogimine",
@@ -2551,6 +2903,7 @@
             "device_unverified_description_current": "Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon.",
             "device_verified_description": "See sessioon on valmis turvaliseks sõnumivahetuseks.",
             "device_verified_description_current": "Sinu praegune sessioon on valmis turvaliseks sõnumivahetuseks.",
+            "dialog_title": "<strong>Seadistused:</strong> Sessioonid",
             "error_pusher_state": "Tõuketeavituste teenuse oleku määramine ei õnnestunud",
             "error_set_name": "Sessiooni nime määramine ei õnnestunud",
             "filter_all": "Kõik",
@@ -2567,6 +2920,7 @@
             "inactive_sessions_list_description": "Võimalusel logi välja vanadest seanssidest (%(inactiveAgeDays)s päeva või vanemad), mida sa enam ei kasuta.",
             "ip": "IP-aadress",
             "last_activity": "Viimati kasutusel",
+            "manage": "Halda seda sessiooni",
             "mobile_session": "Nutirakendus",
             "n_sessions_selected": {
                 "one": "%(count)s sessioon valitud",
@@ -2590,9 +2944,10 @@
             "security_recommendations_description": "Kui järgid neid soovitusi, siis sa parandad oma kasutajakonto turvalisust.",
             "session_id": "Sessiooni tunnus",
             "show_details": "Näita üksikasju",
-            "sign_in_with_qr": "Logi sisse QR-koodi abil",
+            "sign_in_with_qr": "Seosta uus seade",
             "sign_in_with_qr_button": "Näita QR-koodi",
-            "sign_in_with_qr_description": "Sa saad kasutada seda seadet mõne muu seadme logimiseks Matrix'i võrku QR-koodi alusel. Selleks skaneeri võrgust väljalogitud seadmega seda QR-koodi.",
+            "sign_in_with_qr_description": "Kasuta QR-koodi teise seadmesse sisse logimiseks ja turvalise sõnumivahetuse seadistamiseks.",
+            "sign_in_with_qr_unsupported": "Seda võimalust ei toeta sinu teenusepakkuja",
             "sign_out": "Logi sellest sessioonist välja",
             "sign_out_all_other_sessions": "Logi kõikidest ülejäänud sessioonidest välja: %(otherSessionsCount)s sessioon(i)",
             "sign_out_confirm_description": {
@@ -2613,6 +2968,7 @@
             "unverified_sessions_explainer_1": "Kontrollimata sessioonid on sessioonid, kuhu on sinu volitustega sisse logitud, kuid mida ei ole risttuvastamisega kontrollitud.",
             "unverified_sessions_explainer_2": "Kuna nende näol võib olla tegemist võimaliku konto volitamata kasutamisega, siis palun tee kindlaks, et need sessioonid on sulle tuttavad.",
             "unverified_sessions_list_description": "Turvalise sõnumvahetuse nimel verifitseeri kõik oma sessioonid ning logi neist välja, mida sa enam ei kasuta või ei tunne enam ära.",
+            "url": "URL",
             "verified_session": "Verifitseeritud sessioon",
             "verified_sessions": "Verifitseeritud sessioonid",
             "verified_sessions_explainer_1": "Verifitseeritud sessioonideks loetakse Element'is või mõnes muus Matrix'i rakenduses selliseid sessioone, kus sa kas oled sisestanud oma salafraasi või tuvastanud end mõne teise oma verifitseeritud sessiooni abil.",
@@ -2631,7 +2987,9 @@
         "show_redaction_placeholder": "Näita kustutatud sõnumite asemel kohatäidet",
         "show_stickers_button": "Näita kleepsude nuppu",
         "show_typing_notifications": "Anna märku, kui teine osapool sõnumit kirjutab",
+        "showbold": "Näita üldist aktiivsust jututubade loendis (punktidena või lugemata sõnumite arvuna)",
         "sidebar": {
+            "dialog_title": "<strong>Seadistused:</strong> Külgpaan",
             "metaspaces_favourites_description": "Koonda oma olulised sõbrad ning lemmikjututoad ühte kohta.",
             "metaspaces_home_all_rooms": "Näita kõiki jututubasid",
             "metaspaces_home_all_rooms_description": "Näita kõiki oma jututubasid avalehel ka siis kui nad on osa mõnest kogukonnast.",
@@ -2640,9 +2998,14 @@
             "metaspaces_orphans_description": "Koonda ühte kohta kõik oma jututoad, mis ei kuulu mõnda kogukonda.",
             "metaspaces_people_description": "Koonda oma olulised sõbrad ühte kohta.",
             "metaspaces_subsection": "Näidatavad kogukonnakeskused",
+            "metaspaces_video_rooms": "Videojututoad ja -konverentsid",
+            "metaspaces_video_rooms_description": "Rühmita kõik privaatsed videotoad ja -konverentsid",
+            "metaspaces_video_rooms_description_invite_extension": "Konverentsiosalejaid võid kutsuda ka väljastpoolt Matrix'i võrku",
+            "spaces_explainer": "Kogukonnad on võimalus jututubade ja inimeste ühendamiseks. Lisaks nendele, mille liige sa juba olev, võid kasutada süsteemi poolt loodud kogukondi.",
             "title": "Külgpaan"
         },
         "start_automatically": "Käivita Element automaatselt peale arvutisse sisselogimist",
+        "tac_only_notifications": "Näita teavitusi vaid jutulõngade ülevaates",
         "use_12_hour_format": "Näita ajatempleid 12-tunnises vormingus (näiteks 2:30pl)",
         "use_command_enter_send_message": "Sõnumi saatmiseks vajuta Command + Enter klahve",
         "use_command_f_search": "Ajajoonelt otsimiseks kasuta Command+F klahve",
@@ -2656,6 +3019,7 @@
             "audio_output_empty": "Ei leidnud ühtegi heliväljundit",
             "auto_gain_control": "Automaatne esitusvaljuse tundlikkus",
             "connection_section": "Ühendus",
+            "dialog_title": "<strong>Seadistused:</strong> Heli ja video",
             "echo_cancellation": "Kaja eemaldamine",
             "enable_fallback_ice_server": "Varuvariandina luba kasutada ka teist kõnehõlbustusserverit (%(server)s)",
             "enable_fallback_ice_server_description": "On kasutusel vaid siis, kui sinu koduserver sellist teenust ei võimalda. Seeläbi jagatakse kõne ajal sinu seadme IP-aadressi.",
@@ -2674,8 +3038,12 @@
         "warning": "<w>HOIATUS:</w> <description/>"
     },
     "share": {
+        "link_copied": "Link on kopeeritud",
         "permalink_message": "Viide valitud sõnumile",
         "permalink_most_recent": "Viide kõige viimasele sõnumile",
+        "share_call": "Konverentsikõne kutse",
+        "share_call_subtitle": "Link välistele kasutajatele, kes saavad kõnega liituda nii, et neil ei pea olema Matrix'i kontot:",
+        "title_link": "Jaga linki",
         "title_message": "Jaga jututoa sõnumit",
         "title_room": "Jaga jututuba",
         "title_user": "Jaga viidet kasutaja kohta"
@@ -2701,7 +3069,9 @@
         "devtools": "Avab arendusvahendite akna",
         "discardsession": "Sunnib loobuma praeguse krüptitud jututoa rühmavestluse seansist",
         "error_invalid_rendering_type": "Viga käsu täitmisel: visualiseerimise tüüpi ei leidu (%(renderingType)s)",
+        "error_invalid_room": "Käsu täitmine ei õnnestunud: Ei suuda leida jututuba (%(roomId)s)",
         "error_invalid_runfn": "Viga käsu täitmisel: Kaldkriipsuga käsku ei ole võimalik töödelda.",
+        "error_invalid_user_in_room": "Jututoast ei õnnestu leida kasutajat",
         "help": "Näitab käskude loendit koos kirjeldustega",
         "help_dialog_title": "Abiteave käskude kohta",
         "holdcall": "Jätab kõne selles jututoas ootele",
@@ -2744,8 +3114,6 @@
         "topic": "Otsib või määrab jututoa teema",
         "topic_none": "Sellel jututoal puudub teema.",
         "topic_room_error": "Jututoa teema laadimine ei õnnestu: jututuba ei õnnestu leida (%(roomId)s)",
-        "tovirtual": "Kui jututoal on virtuaalne olek, siis kasuta seda",
-        "tovirtual_not_found": "Sellel jututoal pole virtuaalset olekut",
         "unban": "Taasta ligipääs antud tunnusega kasutajale",
         "unflip": "Lisab vormindamata sõnumi ette ┬──┬ ノ( ゜-゜ノ)",
         "unholdcall": "Võtab selles jututoas ootel oleva kõne",
@@ -2763,6 +3131,7 @@
         "view": "Vaata sellise aadressiga jututuba",
         "whois": "Näitab teavet kasutaja kohta"
     },
+    "sliding_sync_legacy_no_longer_supported": "Järkjärgulise sünkroniseerimise (Sliding sync) varasem lahendus pole enam toetatud: uue lahenduse kasutamiseks palun logi rakendusest välja ja uuesti sisse",
     "space": {
         "add_existing_room_space": {
             "create": "Kas sa selle asemel soovid lisada jututuba?",
@@ -2846,7 +3215,7 @@
             "network_dropdown_available_valid": "Tundub õige",
             "network_dropdown_remove_server_adornment": "Eemalda server „%(roomServer)s“",
             "network_dropdown_required_invalid": "Sisesta serveri nimi",
-            "network_dropdown_selected_label": "Näita: Matrix'i jututoad",
+            "network_dropdown_selected_label": "Näita: Matrixi jututube",
             "network_dropdown_selected_label_instance": "Näita: %(instance)s jututuba %(server)s serveris",
             "network_dropdown_your_server_description": "Sinu server"
         }
@@ -2861,21 +3230,22 @@
         },
         "create_new_room_button": "Loo uus jututuba",
         "failed_querying_public_rooms": "Avalike jututubade tuvastamise päring ei õnnestunud",
+        "failed_querying_public_spaces": "Päring avalike kogukondade tuvastamiseks ei õnnestunud",
         "group_chat_section_title": "Muud valikud",
         "heading_with_query": "Otsinguks kasuta „%(query)s“",
         "heading_without_query": "Otsingusõna",
         "join_button_text": "Liitu %(roomAddress)s jututoaga",
         "keyboard_scroll_hint": "Kerimiseks kasuta <arrows/>",
-        "message_search_section_title": "Muud otsingud",
+        "messages_label": "Sõnumid",
         "other_rooms_in_space": "Muud jututoad %(spaceName)s kogukonnad",
         "public_rooms_label": "Avalikud jututoad",
+        "public_spaces_label": "Avalikud kogukonnad",
         "recent_searches_section_title": "Hiljutised otsingud",
         "recently_viewed_section_title": "Hiljuti vaadatud",
         "remove_filter": "Eemalda otsingufilter „%(filter)s“",
         "result_may_be_hidden_privacy_warning": "Mõned tulemused võivad privaatsusseadistuste tõttu olla peidetud",
         "result_may_be_hidden_warning": "Mõned tulemused võivad olla peidetud",
         "search_dialog": "Otsinguvaade",
-        "search_messages_hint": "Sõnumite otsimiseks klõpsi <icon/> ikooni jututoa ülaosas",
         "spaces_title": "Kogukonnad, mille liige sa oled",
         "start_group_chat_button": "Alusta rühmavestlust"
     },
@@ -2912,12 +3282,20 @@
             "one": "%(count)s vastus",
             "other": "%(count)s vastust"
         },
+        "empty_description": "Sõnumi kohtmenüüst vali „%(replyInThread)s“",
+        "empty_title": "Jutulõngad aitavad hoida vestlusi teemakohastena ja hallatavatena.",
         "error_start_thread_existing_relation": "Jutulõnga ei saa luua sõnumist, mis juba on jutulõnga osa",
+        "mark_all_read": "Märgi kõik loetuks",
         "my_threads": "Minu jutulõngad",
         "my_threads_description": "Näitab kõiki jutulõngasid, kus sa oled osalenud",
         "open_thread": "Ava jutulõng",
         "show_thread_filter": "Näita:"
     },
+    "threads_activity_centre": {
+        "header": "Jutulõngade ülevaade",
+        "no_rooms_with_threads_notifs": "Pole veel ühtegi jutulõngakohase teavitusega jututuba.",
+        "no_rooms_with_unread_threads": "Pole veel ühtegi lugemata jutulõngaga jututuba."
+    },
     "time": {
         "about_day_ago": "umbes päev tagasi",
         "about_hour_ago": "umbes tund aega tagasi",
@@ -2959,8 +3337,21 @@
         },
         "creation_summary_dm": "%(creator)s alustas seda otsesuhtlust.",
         "creation_summary_room": "%(creator)s lõi ja seadistas jututoa.",
+        "decryption_failure": {
+            "blocked": "Saatja on blokeerinud võimaluse, et sa saaksid selle sõnumi",
+            "historical_event_no_key_backup": "Varasemad sõnumid pole selles seadmes loetavad",
+            "historical_event_unverified_device": "Varasemate sõnumite nägemiseks pead selle seadme verifitseerima",
+            "historical_event_user_not_joined": "Sul puudub ligipääs sellele sõnumile",
+            "sender_identity_previously_verified": "Saatja verifitseeritud võrguidentiteet on lähtestatud",
+            "sender_unsigned_device": "Krüptitud seadme poolt, mida tema omanik pole verifitseerinud.",
+            "unable_to_decrypt": "Sõnumi dekrüptimine ei õnnestu"
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Dekrüptin sisu",
         "download_action_downloading": "Laadin alla",
+        "download_failed": "Allalaadimine ei õnnestunud",
+        "download_failed_description": "Selle faili allalaadimisel tekkis viga",
+        "e2e_state": "Läbiva krüptimise olek",
         "edits": {
             "tooltip_label": "Muudetud %(date)s. Klõpsi et näha varasemaid versioone.",
             "tooltip_sub": "Muudatuste nägemiseks klõpsi",
@@ -2971,6 +3362,7 @@
         "historical_messages_unavailable": "Sa ei saa näha varasemaid sõnumeid",
         "in_room_name": " <strong>%(room)s</strong> jututoas",
         "io.element.widgets.layout": "%(senderName)s on uuendanud jututoa välimust",
+        "late_event_separator": "Algselt saadetud %(dateTime)s",
         "load_error": {
             "no_permission": "Üritasin laadida teatud hetke selle jututoa ajajoonelt, kuid sul ei ole õigusi selle sõnumi nägemiseks.",
             "title": "Asukoha laadimine ajajoonel ei õnnestunud",
@@ -3013,7 +3405,7 @@
         },
         "m.file": {
             "error_decrypting": "Viga manuse dekrüptimisel",
-            "error_invalid": "Vigane fail %(extra)s"
+            "error_invalid": "Vigane fail"
         },
         "m.image": {
             "error": "Vea tõttu ei ole võimalik pilti kuvada",
@@ -3114,6 +3506,7 @@
             "left_reason": "%(targetName)s lahkus jututoast: %(reason)s",
             "no_change": "%(senderName)s ei teinud muutusi",
             "reject_invite": "%(targetName)s lükkas kutse tagasi",
+            "reject_invite_reason": "%(targetName)s lükkas kutse tagasi: %(reason)s",
             "remove_avatar": "%(senderName)s eemaldas oma profiilipildi",
             "remove_name": "%(senderName)s eemaldas oma kuvatava nime (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s määras oma profiilipildi",
@@ -3136,7 +3529,7 @@
             "unpinned_link": "%(senderName)s eemaldas siin jututoas klammerduse <a>ühelt sõnumilt</a>. Vaata kõiki <b>klammerdatud sõnumeid</b>."
         },
         "m.room.power_levels": {
-            "changed": "%(senderName)s muutis %(powerLevelDiffText)s õigusi.",
+            "changed": "%(senderName)s muutis õiguseid: %(powerLevelDiffText)s.",
             "user_from_to": "%(userId)s õigused muutusid: %(fromPowerLevel)s -> %(toPowerLevel)s"
         },
         "m.room.server_acl": {
@@ -3149,10 +3542,14 @@
             "sent": "%(senderName)s saatis %(targetDisplayName)s'le kutse jututoaga liitumiseks."
         },
         "m.room.tombstone": "%(senderDisplayName)s uuendas seda jututuba.",
-        "m.room.topic": "%(senderDisplayName)s muutis uueks teemaks „%(topic)s“.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s muutis uueks teemaks „%(topic)s“.",
+            "removed": "%(senderDisplayName)s eemaldas teema."
+        },
         "m.sticker": "%(senderDisplayName)s saatis kleepsu.",
         "m.video": {
-            "error_decrypting": "Viga videovoo dekrüptimisel"
+            "error_decrypting": "Viga videovoo dekrüptimisel",
+            "show_video": "Näita videot"
         },
         "m.widget": {
             "added": "%(senderName)s lisas vidina %(widgetName)s",
@@ -3171,6 +3568,8 @@
             "label": "Tegevused sõnumitega",
             "view_in_room": "Vaata jututoas"
         },
+        "message_timestamp_received_at": "Saabumise aeg: %(dateTime)s",
+        "message_timestamp_sent_at": "Saatmise aeg: %(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s muutis %(reason)s tõttu ligipääsukeelu reegli algset tingimust %(oldGlob)s uueks tingimuseks %(newGlob)s",
             "changed_rule_rooms": "%(senderName)s muutis %(reason)s tõttu jututubade ligipääsukeelu reegli algset tingimust %(oldGlob)s uueks tingimuseks %(newGlob)s",
@@ -3197,7 +3596,9 @@
         "pending_moderation_reason": "Sõnum on modereerimise ootel: %(reason)s",
         "reactions": {
             "add_reaction_prompt": "Lisa reaktsioon",
-            "label": "%(reactors)s kasutajat reageeris järgnevalt: %(content)s"
+            "custom_reaction_fallback_label": "Kohandatud reaktsioon",
+            "label": "%(reactors)s kasutajat reageeris järgnevalt: %(content)s",
+            "tooltip_caption": "kasutas reageerimiseks %(shortName)s"
         },
         "read_receipt_title": {
             "one": "Seda nägi %(count)s lugeja",
@@ -3247,6 +3648,7 @@
                 "other": "Mitu kasutajat %(severalUsers)s muutsid oma nime %(count)s korda",
                 "one": "Mitu kasutajat %(severalUsers)s muutsid oma nime"
             },
+            "format": "%(nameList)s %(transitionList)s",
             "hidden_event": {
                 "one": "%(oneUser)s saatis ühe peidetud sõnumi",
                 "other": "%(oneUser)s saatis %(count)s peidetud sõnumit"
@@ -3312,12 +3714,12 @@
                 "one": "%(severalUsers)s ei teinud muudatusi"
             },
             "pinned_events": {
-                "one": "%(oneUser)s muutis selle jututoa <a>klammerdatud sõnumeid</a>",
-                "other": "%(oneUser)s muutis jututoa <a>klammerdatud sõnumeid</a> %(count)s korda"
+                "one": "%(oneUser)s muutis selle jututoa <a>esiletõstetud sõnumeid</a>",
+                "other": "%(oneUser)s muutis selle jututoa <a>esiletõstetud sõnumeid</a> %(count)s korda"
             },
             "pinned_events_multiple": {
-                "one": "%(severalUsers)s muutsid selle jututoa <a>klammerdatud sõnumeid</a>",
-                "other": "%(severalUsers)s muutsid jututoa <a>klammerdatud sõnumeid</a> %(count)s korda"
+                "one": "%(severalUsers)s muutsid selle jututoa <a>esiletõstetud sõnumeid</a>",
+                "other": "%(severalUsers)s muutsid selle jututoa <a>esiletõstetud sõnumeid</a> %(count)s korda"
             },
             "redacted": {
                 "one": "%(oneUser)s kustutas sõnumi",
@@ -3381,6 +3783,10 @@
     "truncated_list_n_more": {
         "other": "Ja %(count)s muud..."
     },
+    "unsupported_browser": {
+        "description": "Kui sa jätkad, siis mõni funktsionaalsus ei pruugi toimida ja on märgatav risk, et sa võid tulevikus andmeid kaotada. Kasutamaks veebirakendust %(brand)s, palun uuenda on veebibrauserit.",
+        "title": "%(brand)s ei toimi selle veebibrauseriga"
+    },
     "unsupported_server_description": "See server kasutab Matrixi vanemat versiooni. Selleks, et %(brand)s'i kasutamisel vigu ei tekiks palun uuenda serverit nii, et kasutusel oleks Matrixi %(version)s.",
     "unsupported_server_title": "Sinu server ei ole toetatud",
     "update": {
@@ -3398,6 +3804,13 @@
         "toast_title": "Uuenda %(brand)s rakendust",
         "unavailable": "Ei ole saadaval"
     },
+    "update_room_access_modal": {
+        "description": "Kui soovid luua jagatavat linki, siis pead selle jututoa tegema <b>avalikuks</b> või pead seadistustest lubama huvilistel <b>paluda võimalust liitumiseks</b>. See võimaldab külalistel liituda ilma kutseta.",
+        "dont_change_description": "Kui sa ei soovi selle jututoa ligipääsuõigusi muuta, siis teise võimalusena saad helistamiseks luua eraldi jututoa",
+        "no_change": "Ma ei soovi muuta õigusi jututoas.",
+        "revert_access_description": "(Eelmise väärtuse saad taastada jututoa seadistustest: <b>Turvalisus & Privaatsus</b> / <b>Ligipääs</b>)",
+        "title": "Luba külalistel selle jututoaga liituda"
+    },
     "upload_failed_generic": "Faili '%(fileName)s' üleslaadimine ei õnnestunud.",
     "upload_failed_size": "Faili '%(fileName)s' suurus ületab serveris seadistatud üleslaadimise piiri",
     "upload_failed_title": "Üleslaadimine ei õnnestunud",
@@ -3407,6 +3820,7 @@
         "error_files_too_large": "Need failid on üleslaadimiseks <b>liiga suured</b>. Failisuuruse piir on %(limit)s.",
         "error_some_files_too_large": "Mõned failid on üleslaadimiseks <b>liiga suured</b>. Failisuuruse piir on %(limit)s.",
         "error_title": "Üleslaadimise viga",
+        "not_image": "Sinu valitud fail pole korrektne pildifail.",
         "title": "Laadi failid üles",
         "title_progress": "Laadin faile üles (%(current)s / %(total)s)",
         "upload_all_button": "Laadi kõik üles",
@@ -3422,14 +3836,6 @@
         "ban_room_confirm_title": "Määra suhtluskeeld %(roomName)s jututoas",
         "ban_space_everything": "Määra kasutajale suhtluskeeld kõikjal, kus ma saan",
         "ban_space_specific": "Määra kasutajale suhtluskeeld valitud kohtades, kust ma saan",
-        "count_of_sessions": {
-            "other": "%(count)s sessiooni",
-            "one": "%(count)s sessioon"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s verifitseeritud sessiooni",
-            "one": "1 verifitseeritud sessioon"
-        },
         "deactivate_confirm_action": "Deaktiveeri kasutaja",
         "deactivate_confirm_description": "Kasutaja deaktiveerimisel logitakse ta automaatselt välja ning ei lubata enam sisse logida. Lisaks lahkub ta kõikidest jututubadest, mille liige ta parasjagu on. Seda tegevust ei saa tagasi pöörata. Kas sa oled ikka kindel, et soovid selle kasutaja kõijkalt eemaldada?",
         "deactivate_confirm_title": "Kas deaktiveerime kasutajakonto?",
@@ -3440,15 +3846,13 @@
         "disinvite_button_room": "Eemalda kutse jututuppa",
         "disinvite_button_room_name": "Võta tagasi %(roomName)s jututoa kutse",
         "disinvite_button_space": "Eemalda kutse kogukonda",
-        "edit_own_devices": "Muuda seadmeid",
         "error_ban_user": "Kasutaja ligipääsu keelamine ei õnnestunud",
         "error_deactivate": "Kasutaja deaktiveerimine ei õnnestunud",
         "error_kicking_user": "Kasutaja eemaldamine ebaõnnestus",
         "error_mute_user": "Kasutaja summutamine ebaõnnestus",
         "error_revoke_3pid_invite_description": "Kutse tühistamine ei õnnestunud. Serveri töös võib olla ajutine tõrge või sul pole piisavalt õigusi kutse tühistamiseks.",
         "error_revoke_3pid_invite_title": "Kutse tühistamine ei õnnestunud",
-        "hide_sessions": "Peida sessioonid",
-        "hide_verified_sessions": "Peida verifitseeritud sessioonid",
+        "ignore_button": "Eira",
         "ignore_confirm_description": "Kõik selle kasutaja sõnumid ja kutsed saava olema peidetud. Kas sa oled kindel, et soovid teda eirata?",
         "ignore_confirm_title": "Eira kasutajat %(user)s",
         "invited_by": "Kutsutud %(sender)s poolt",
@@ -3476,26 +3880,30 @@
             "no_recent_messages_description": "Vaata kas ajajoonel ülespool leidub varasemaid sõnumeid.",
             "no_recent_messages_title": "Kasutajalt %(user)s ei leitud hiljutisi sõnumeid"
         },
-        "redact_button": "Eemalda hiljutised sõnumid",
+        "redact_button": "Eemalda sõnumid",
         "revoke_invite": "Tühista kutse",
         "room_encrypted": "See jututuba on läbivalt krüptitud.",
         "room_encrypted_detail": "Sinu sõnumid on turvatud ning ainult sinul ja saaja(te)l on unikaalsed võtmed selliste sõnumite lugemiseks.",
         "room_unencrypted": "See jututuba ei ole läbivalt krüptitud.",
         "room_unencrypted_detail": "Krüptitud jututubades sinu sõnumid on turvatud ning vaid sinul ja sõnumi saajal on unikaalsed võtmed nende kuvamiseks.",
-        "share_button": "Jaga viidet kasutaja kohta",
+        "send_message": "Saada sõnum",
+        "share_button": "Jaga profiili",
         "unban_button_room": "Eemalda suhtluskeeld jututoas",
         "unban_button_space": "Eemalda suhtluskeeld kogukonnas",
         "unban_room_confirm_title": "Eemalda suhtluskeeld %(roomName)s jututoas",
         "unban_space_everything": "Eemalda kasutajalt suhtluskeeld kõikjalt, kust ma saan",
         "unban_space_specific": "Eemalda kasutajalt suhtluskeeld valitud kohtadest, kust ma saan",
         "unban_space_warning": "Kasutaja ei saa ligi kohtadele, kus sul pole peakasutaja õigusi.",
+        "unignore_button": "Lõpeta eiramine",
+        "verification_unavailable": "Kasutaja verifitseerimine pole saadaval",
         "verify_button": "Verifitseeri kasutaja",
         "verify_explainer": "Lisaturvalisus mõttes verifitseeri see kasutaja võrreldes selleks üheks korraks loodud koodi mõlemas seadmes."
     },
     "user_menu": {
+        "link_new_device": "Seo uus seade",
         "settings": "Kõik seadistused",
-        "switch_theme_dark": "Kasuta tumedat teemat",
-        "switch_theme_light": "Kasuta heledat teemat"
+        "switch_theme_dark": "Kasuta tumedat kujundust",
+        "switch_theme_light": "Kasuta heledat kujundust"
     },
     "voip": {
         "already_in_call": "Kõne on juba pooleli",
@@ -3516,6 +3924,7 @@
         "camera_disabled": "Sinu seadme kaamera on välja lülitatud",
         "camera_enabled": "Sinu seadme kaamera on jätkuvalt kasutusel",
         "cannot_call_yourself_description": "Sa ei saa iseendale helistada.",
+        "close_lobby": "Sulge ooteruum",
         "connecting": "Kõne on ühendamisel",
         "connection_lost": "Ühendus sinu serveriga on katkenud",
         "connection_lost_description": "Kui ühendus sinu serveriga on katkenud, siis sa ei saa helistada.",
@@ -3529,15 +3938,23 @@
         "disabled_no_perms_start_video_call": "Sul ei ole piisavalt õigusi videokõne alustamiseks",
         "disabled_no_perms_start_voice_call": "Sul ei ole piisavalt õigusi häälkõne alustamiseks",
         "disabled_ongoing_call": "Kõne on pooleli",
+        "element_call": "Element Call",
         "enable_camera": "Lülita kaamera sisse",
         "enable_microphone": "Eemalda mikrofoni summutamine",
         "expand": "Pöördu tagasi kõne juurde",
+        "get_call_link": "Jaga kõne linki",
         "hangup": "Katkesta kõne",
         "hide_sidebar_button": "Peida külgpaan",
         "input_devices": "Sisendseadmed",
+        "jitsi_call": "Jitsi-põhine kõne",
         "join_button_tooltip_call_full": "Vabandust, selles kõnes ei saa rohkem osalejaid olla",
-        "join_button_tooltip_connecting": "Kõne on ühendamisel",
+        "legacy_call": "Vana lahendusega kõne",
         "maximise": "Täida ekraan",
+        "maximise_call": "Tee kõneaken suureks",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konverentsid"
+        },
+        "minimise_call": "Tee kõneaken väikeseks",
         "misconfigured_server": "Kõne ebaõnnestus valesti seadistatud serveri tõttu",
         "misconfigured_server_description": "Palu oma koduserveri haldajat (<code>%(homeserverDomain)s</code>), et ta seadistaks kõnede kindlamaks toimimiseks TURN serveri.",
         "misconfigured_server_fallback": "Alternatiivina võid sa kasutada avalikku serverit <server/>, kuid see ei pruugi olla piisavalt töökindel ning sa jagad ka oma IP-aadressi selle serveriga. Täpsemalt saad seda määrata seadistustes.",
@@ -3585,6 +4002,7 @@
         "user_is_presenting": "%(sharerName)s esitab",
         "video_call": "Videokõne",
         "video_call_started": "Videokõne algas",
+        "video_call_using": "Videokõne, kus on kasutusel:",
         "voice_call": "Häälkõne",
         "you_are_presenting": "Sina esitad"
     },
@@ -3684,7 +4102,7 @@
         "error_need_to_be_logged_in": "Sa peaksid olema sisse loginud.",
         "error_unable_start_audio_stream_description": "Audiovoo käivitamine ei õnnestu.",
         "error_unable_start_audio_stream_title": "Videovoo käivitamine ei õnnestu",
-        "modal_data_warning": "Andmeid selles vaates jagatakse %(widgetDomain)s serveriga",
+        "modal_data_warning": "Alljärgnevaid andmeid jagatakse %(widgetDomain)s serveriga",
         "modal_title_default": "Modaalne vidin",
         "no_name": "Tundmatu rakendus",
         "open_id_permissions_dialog": {
@@ -3693,14 +4111,14 @@
             "title": "Luba sellel vidinal sinu isikut verifitseerida"
         },
         "popout": "Ava rakendus eraldi aknas",
-        "set_room_layout": "Kasuta minu jututoa paigutust kõigi jaoks",
+        "set_room_layout": "Kasuta paigutust kõigi jaoks",
         "shared_data_avatar": "Sinu tunnuspildi URL",
         "shared_data_device_id": "Sinu seadme tunnus",
         "shared_data_lang": "Sinu keel",
         "shared_data_mxid": "Sinu kasutajatunnus",
         "shared_data_name": "Sinu kuvatav nimi",
         "shared_data_room_id": "Jututoa tunnus",
-        "shared_data_theme": "Sinu teema",
+        "shared_data_theme": "Sinu kujundus",
         "shared_data_url": "%(brand)s'i aadress",
         "shared_data_warning": "Selle vidina kasutamisel võidakse jagada andmeid <helpIcon /> saitidega %(widgetDomain)s.",
         "shared_data_warning_im": "Selle vidina kasutamisel võidakse jagada andmeid <helpIcon /> %(widgetDomain)s saitidega ning sinu lõiminguhalduriga.",
diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json
index 39ea21eb5813942e1148947af8badcfe1743dd53..7b375d4db0e873f23451a0aede13cd1c80260c4f 100644
--- a/src/i18n/strings/fa.json
+++ b/src/i18n/strings/fa.json
@@ -73,7 +73,6 @@
         "react": "واکنش",
         "refresh": "رفرش",
         "register": "ایجاد حساب کاربری",
-        "reject": "پس زدن",
         "reload": "بارگذاری مجدد",
         "remove": "حذف کن",
         "rename": "تغییر نام",
@@ -287,7 +286,6 @@
         "download_logs": "دانلود گزارش‌ها",
         "downloading_logs": "در حال دریافت لاگ‌ها",
         "error_empty": "لطفاً به ما بگویید چه مشکلی پیش آمد و یا اینکه لطف کنید و یک مسئله GitHub ایجاد کنید که مشکل را توصیف کند.",
-        "failed_send_logs": "ارسال گزارش با خطا مواجه شد: ",
         "github_issue": "مسئله GitHub",
         "log_request": "برای کمک به ما در جلوگیری از این امر در آینده ، لطفا <a>لاگ‌ها را برای ما ارسال کنید</a>.",
         "logs_sent": "گزارش‌های مربوط ارسال شد",
@@ -322,7 +320,6 @@
         "access_token": "توکن دسترسی",
         "accessibility": "دسترسی",
         "advanced": "پیشرفته",
-        "all_rooms": "همه اتاق‌ها",
         "analytics": "تجزیه و تحلیل",
         "and_n_others": {
             "one": "و یکی دیگر ...",
@@ -337,7 +334,6 @@
         "camera": "دوربین",
         "copied": "رونوشت گرفته شد!",
         "credits": "اعتبارها",
-        "cross_signing": "امضاء متقابل",
         "dark": "تاریک",
         "description": "توضیحات",
         "edited": "ویرایش شده",
@@ -401,7 +397,6 @@
         "room_name": "نام اتاق",
         "rooms": "اتاق‌ها",
         "secure_backup": "پشتیبان‌گیری امن",
-        "security": "امنیت",
         "settings": "تنظیمات",
         "setup_secure_messages": "پیام‌رسانی امن را تنظیم کنید",
         "show_more": "نمایش بیشتر",
@@ -417,7 +412,6 @@
         "theme": "پوسته",
         "threads": "موضوعات",
         "timeline": "سیر زمان گفتگو‌ها",
-        "trusted": "قابل اعتماد",
         "unencrypted": "رمزگذاری نشده",
         "unmute": "صدادار",
         "unnamed_room": "اتاق بدون نام",
@@ -563,42 +557,23 @@
     "empty_room_was_name": "اتاق خالی (نام قبلی: %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "عبارت امنیتی خود را وارد کنید و یا <button>کلید امنیتی خود را استفاده کنید</button>.",
             "key_validation_text": {
-                "invalid_security_key": "کلید امنیتی نامعتبر است",
-                "recovery_key_is_correct": "به نظر خوب میاد!",
-                "wrong_file_type": "نوع فایل اشتباه است",
                 "wrong_security_key": "کلید امنیتی اشتباه است"
             },
-            "reset_title": "همه چیز را بازراه‌اندازی (reset) کنید",
-            "reset_warning_1": "این کار را فقط درصورتی انجام دهید که دستگاه دیگری برای تکمیل فرآیند تأیید ندارید.",
-            "reset_warning_2": "اگر همه موارد را بازراه‌اندازی (reset) کنید، دیگر هیچ نشست تائید شده‌ای و هیچ کاربر تائيد‌ شده‌ای نخواهید داشت و ممکن است نتوانید پیام‌های گذشته‌ی خود را مشاهده نمائید.",
             "restoring": "بازیابی کلیدها از نسخه پشتیبان",
-            "security_key_title": "کلید امنیتی",
-            "security_phrase_incorrect_error": "دسترسی به حافظه نهان امکان‌پذیر نیست. لطفاً تأیید کنید که عبارت امنیتی صحیح را وارد کرده‌اید.",
-            "security_phrase_title": "عبارت امنیتی",
-            "use_security_key_prompt": "برای ادامه از کلید امنیتی خود استفاده کنید."
+            "security_key_title": "کلید امنیتی"
         },
         "bootstrap_title": "تنظیم کلیدها",
         "cancel_entering_passphrase_description": "آیا مطمئن هستید که می خواهید وارد کردن عبارت امنیتی را لغو کنید؟",
         "cancel_entering_passphrase_title": "وارد کردن عبارت امنیتی لغو شود؟",
         "confirm_encryption_setup_body": "برای تأیید و فعال‌سازی رمزگذاری ، روی دکمه زیر کلیک کنید.",
         "confirm_encryption_setup_title": "راه‌اندازی رمزگذاری را تأیید کنید",
-        "cross_signing_not_ready": "امضاء متقابل تنظیم نشده‌است.",
-        "cross_signing_ready": "امضاء متقابل برای استفاده در دسترس است.",
         "cross_signing_room_normal": "این اتاق به صورت سرتاسر رمزشده است",
         "cross_signing_room_verified": "همه‌ی اعضای این اتاق تائید شده‌اند",
         "cross_signing_room_warning": "فردی از یک نشست ناشناس استفاده می‌کند",
-        "cross_signing_unsupported": "سرور شما امضاء متقابل را پشتیبانی نمی‌کند.",
-        "cross_signing_untrusted": "حساب کاربری شما یک هویت برای امضاء متقابل در حافظه‌ی نهان دارد، اما این هویت هنوز توسط این نشست تائید نشده‌است.",
         "cross_signing_user_normal": "شما این کاربر را تائید نکرده‌اید.",
         "cross_signing_user_verified": "شما این کاربر را تائید کرده‌اید. این کاربر تمام نشست‌های خود را تائيد کرده‌است.",
         "cross_signing_user_warning": "این کاربر هیچ‌کدام از نشست‌های خود را تائید نکرده است.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "کلیدهای امضای متقابل را پاک کن",
-            "title": "کلیدهای امضای متقابل نابود شود؟",
-            "warning": "حذف کلیدهای امضای متقابل دائمی است. هرکسی که او را تائید کرده‌باشید، هشدارهای امنیتی را مشاهده خواهد کرد. به احتمال زیاد نمی‌خواهید این کار را انجام دهید ، مگر هیچ دستگاهی برای امضاء متقابل از طریق آن نداشته باشید."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "صحت این پیام رمزگذاری شده در این دستگاه تضمین نمی شود.",
         "event_shield_reason_mismatched_sender_key": "توسط یک نشست تأیید نشده رمزگذاری شده است",
         "export_unsupported": "مرورگر شما از افزونه‌های رمزنگاری مورد نیاز پشتیبانی نمی‌کند",
@@ -615,7 +590,6 @@
             "title": "روش بازیابی جدید",
             "warning": "اگر روش بازیابی جدیدی را تنظیم نکرده‌اید، ممکن است حمله‌کننده‌ای تلاش کند به حساب کاربری شما دسترسی پیدا کند. لطفا گذرواژه حساب کاربری خود را تغییر داده و فورا یک روش جدیدِ بازیابی در بخش تنظیمات انتخاب کنید."
         },
-        "not_supported": "<پشتیبانی نمی‌شود>",
         "recovery_method_removed": {
             "description_1": "نشست فعلی تشخیص داده که عبارت امنیتی و کلید لازم شما برای پیام‌رسانی امن حذف شده‌است.",
             "description_2": "اگر این کار را به صورت تصادفی انجام دادید، می‌توانید سازوکار پیام امن را برای این نشست تنظیم کرده که باعث می‌شود تمام تاریخچه‌ی این نشست با استفاده از یک روش جدیدِ بازیابی، مجددا رمزشود.",
@@ -626,8 +600,7 @@
         "set_up_toast_description": "محافظ در برابر از دست‌دادن داده‌ها و پیام‌های رمزشده",
         "set_up_toast_title": "پشتیبان‌گیری امن را انجام دهید",
         "setup_secure_backup": {
-            "explainer": "پیش از خروج از حساب کاربری، از کلید‌های خود پشتیبان بگیرید تا آن‌ها را از دست ندهید.",
-            "title": "برپایی"
+            "explainer": "پیش از خروج از حساب کاربری، از کلید‌های خود پشتیبان بگیرید تا آن‌ها را از دست ندهید."
         },
         "udd": {
             "other_ask_verify_text": "از این کاربر بخواهید نشست خود را تأیید کرده و یا آن را به صورت دستی تأیید کنید.",
@@ -637,7 +610,6 @@
             "title": "قابل اعتماد نیست"
         },
         "unable_to_setup_keys_error": "تنظیم کلیدها امکان پذیر نیست",
-        "unsupported": "این کلاینت از رمزگذاری سرتاسر پشتیبانی نمی کند.",
         "verification": {
             "accepting": "پذیرش…",
             "cancelled": "شما تأیید هویت را لغو کردید.",
@@ -728,11 +700,7 @@
     "error_database_closed_title": "پایگاه داده به طور غیرمنتظره ای بسته شد",
     "error_dialog": {
         "error_loading_user_profile": "امکان نمایش پروفایل کاربر میسر نیست",
-        "forget_room_failed": "فراموش کردن اتاق با خطا مواجه شد %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "سرور ممکن است در دسترس نباشد ، بار زیادی روی آن قرار گرفته یا زمان جستجو به پایان رسیده‌باشد :(",
-            "title": "جستجو موفیت‌آمیز نبود"
-        }
+        "forget_room_failed": "فراموش کردن اتاق با خطا مواجه شد %(errCode)s"
     },
     "error_user_not_logged_in": "کاربر وارد نشده است",
     "event_preview": {
@@ -1118,11 +1086,6 @@
         "ongoing": "در حال حذف…",
         "reason_label": "دلیل (اختیاری)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "آیا مطمئن هستید که می خواهید دعوت را رد کنید؟",
-        "failed": "رد دعوتنامه موفقیت‌آمیز نبود",
-        "title": "ردکردن دعوت"
-    },
     "report_content": {
         "description": "گزارش این پیام شناسه‌ی منحصر به فرد رخداد آن را برای مدیر سرور ارسال می‌کند. اگر پیام‌های این اتاق رمزشده باشند، مدیر سرور شما امکان خواندن متن آن پیام یا مشاهده‌ی عکس یا فایل‌های دیگر را نخواهد داشت.",
         "missing_reason": "لطفا توضیح دهید که چرا گزارش می‌دهید.",
@@ -1211,7 +1174,6 @@
             "you_created": "شما این اتاق را ایجاد کردید."
         },
         "invite_email_mismatch_suggestion": "برای دریافت مستقیم دعوت در %(brand)s این ایمیل را در تنظیمات به اشتراک بگذارید.",
-        "invite_reject_ignore": "رد کردن و نادیده گرفتن کاربر",
         "invite_sent_to_email_room": "این دعوت به %(roomName)s به %(email)s ارسال شد",
         "invite_subtitle": "<userName/> شما را دعوت کرد",
         "invite_this_room": "دعوت به این گپ",
@@ -1529,7 +1491,6 @@
             "remove_email_prompt": "%(email)s را پاک می‌کنید؟",
             "remove_msisdn_prompt": "%(phone)s را پاک می‌کنید؟"
         },
-        "image_thumbnails": "پیش‌نمایش تصاویر را نشان بده",
         "inline_url_previews_default": "فعال‌سازی پیش‌نمایش URL به صورت پیش‌فرض",
         "inline_url_previews_room": "امکان پیش‌نمایش URL را به صورت پیش‌فرض برای اعضای این اتاق فعال کن",
         "inline_url_previews_room_account": "فعال‌سازی پیش‌نمایش URL برای این اتاق (تنها شما را تحت تاثیر قرار می‌دهد)",
@@ -1610,48 +1571,16 @@
         "prompt_invite": "قبل از ارسال دعوت‌نامه برای کاربری که شناسه‌ی او احتمالا معتبر نیست، هشدا بده",
         "replace_plain_emoji": "متن ساده را به صورت خودکار با شکلک جایگزین کن",
         "security": {
-            "4s_public_key_in_account_data": "در داده‌های حساب کاربری",
-            "4s_public_key_status": "کلید عمومی حافظه نهان:",
-            "backup_key_cached_status": "کلید پشتیبان ذخیره شد:",
-            "backup_key_stored_status": "کلید پشتیبان ذخیره شد:",
-            "backup_key_unexpected_type": "تایپ (نوع) غیرمنتظره",
-            "backup_key_well_formed": "خوش‌ساخت",
-            "backup_keys_description": "در صورت از دست رفتن دسترسی به نشست‌هایتان، از کلیدهای رمزنگاری و داده‌های حساب کاربری خود نسخه‌ی پشتیبان تهیه نمائید. کلیدهای شما توسط کلید منحضر به فرد امنیتی (Security Key) امن خواهند ماند.",
             "bulk_options_accept_all_invites": "همه‌ی دعوت‌های %(invitedRooms)s را قبول کن",
             "bulk_options_reject_all_invites": "همه‌ی دعوت‌های %(invitedRooms)s را رد کن",
             "bulk_options_section": "گزینه‌های دسته‌جمعی",
-            "cross_signing_cached": "به صورت محلی کش شده‌است",
-            "cross_signing_homeserver_support": "قابلیت‌های پشتیبانی‌شده سمت سرور:",
-            "cross_signing_homeserver_support_exists": "وجود دارد",
-            "cross_signing_in_4s": "بر روی حافظه نهان",
-            "cross_signing_in_memory": "بر روی حافظه",
-            "cross_signing_master_private_Key": "شاه‌کلید خصوصی:",
-            "cross_signing_not_cached": "به صورت محلی یافت نشد",
-            "cross_signing_not_found": "یافت نشد",
-            "cross_signing_not_in_4s": "در حافظه یافت نشد",
-            "cross_signing_not_stored": "ذخیره نشد",
-            "cross_signing_private_keys": "کلیدهای خصوصی امضاء متقابل:",
-            "cross_signing_public_keys": "کلیدهای عمومی امضاء متقابل:",
-            "cross_signing_self_signing_private_key": "کلید خصوصی self-sign:",
-            "cross_signing_user_signing_private_key": "کلید امضاء خصوصی کاربر:",
-            "cryptography_section": "رمزنگاری",
-            "delete_backup": "پاک‌کردن نسخه پشتیبان (Backup)",
-            "delete_backup_confirm_description": "آیا اطمینان دارید؟ در صورتی که از کلیدهای شما به درستی پشتیبان‌گیری نشده باشد، تمام پیام‌های رمزشده‌ی خود را از دست خواهید داد.",
             "e2ee_default_disabled_warning": "مدیر سرور شما قابلیت رمزنگاری سرتاسر برای اتاق‌ها و گفتگوهای خصوصی را به صورت پیش‌فرض غیرفعال کرده‌است.",
             "enable_message_search": "فعال‌سازی قابلیت جستجو در اتاق‌های رمزشده",
             "encryption_section": "رمزنگاری",
-            "error_loading_key_backup_status": "امکان بارگیری و نمایش وضعیت کلید پشتیبان وجود ندارد",
-            "export_megolm_keys": "استخراج (Export) کلیدهای رمزنگاری اتاق‌ها",
             "ignore_users_empty": "شما هیچ کاربری را نادیده نگرفته‌اید.",
             "ignore_users_section": "کاربران نادیده‌گرفته‌شده",
-            "import_megolm_keys": "واردکردن کلیدهای رمزنگاری اتاق‌ها",
-            "key_backup_active_version_none": "هیچ‌کدام",
             "key_backup_algorithm": "الگوریتم:",
-            "key_backup_complete": "از همه کلیدها نسخه‌ی پشتیبان گرفته شد",
             "key_backup_connect": "این نشست را به کلید پشتیبان‌گیر متصل کن",
-            "key_backup_connect_prompt": "پیش از خروج از حساب کاربری، این نشست را به کلید پشتیبان‌گیر متصل نمائید. با این کار مانع از گم‌شدن کلیدهای که فقط بر روی این نشست وجود دارند می‌شوید.",
-            "key_backup_inactive": "این نشست <b>از کلیدهای شما پشتیبان‌گیری نمی‌کند</b>، با این حال شما یک نسخه‌ی پشتیبان موجود دارید که می‌توانید آن را بازیابی کنید.",
-            "key_backup_inactive_warning": "کلید‌های شما <b>از این نشست پشتیبان‌گیری نمی‌شود</b>.",
             "message_search_disable_warning": "اگر غیر فعال شود، پیام‌های اتاق‌های رمزشده در نتایج جستجوها نمایش داده نمی‌شوند.",
             "message_search_disabled": "پیام‌های رمزشده را به صورتی محلی و امن ذخیره کرده تا در نتایج جستجو ظاهر شوند.",
             "message_search_enabled": {
@@ -1670,13 +1599,7 @@
             "message_search_space_used": "فضای مصرفی:",
             "message_search_unsupported": "%(brand)s بعضی از مولفه‌های مورد نیاز برای ذخیره امن پیام‌های رمزشده به صورت محلی را ندارد. اگر تمایل به استفاده از این قابلیت دارید، یک نسخه‌ی دلخواه از %(brand)s با <nativeLink> مولفه‌های مورد نظر</nativeLink> بسازید.",
             "message_search_unsupported_web": "%(brand)s نمی‌تواند پیام‌های رمزشده را به شکل امن و به صورت محلی در هنگامی که مرورگر در حال فعالیت است ذخیره کند. از <desktopLink>%(brand)s نسخه‌ی دسکتاپ</desktopLink> برای نمایش پیام‌های رمزشده در نتایج جستجو استفاده نمائید.",
-            "restore_key_backup": "بازیابی از نسخه‌ی پشتیبان",
-            "secret_storage_not_ready": "آماده نیست",
-            "secret_storage_ready": "آماده",
-            "secret_storage_status": "حافظه نهان:",
             "send_analytics": "ارسال داده‌های تجزیه و تحلیلی",
-            "session_id": "شناسه‌ی نشست:",
-            "session_key": "کلید نشست:",
             "strict_encryption": "هرگز از این نشست، پیام‌های رمزشده را به نشست‌های تائید نشده ارسال مکن"
         },
         "send_typing_notifications": "ارسال اعلان «در حال نوشتن»",
@@ -1779,8 +1702,6 @@
         "topic": "موضوع اتاق را دریافت یا تنظیم می‌کند",
         "topic_none": "این اتاق هیچ موضوعی ندارد.",
         "topic_room_error": "خطا در دریافت عنوان اتاق: اتاق %(roomId)s یافت نشد",
-        "tovirtual": "جابجایی به اتاق مجازی این اتاق، اگر یکی وجود داشت",
-        "tovirtual_not_found": "اتاق مجازی برای این اتاق وجود ندارد",
         "unban": "رفع تحریم کاربر با شناسه‌ی مذکور",
         "unflip": "┬──┬ ノ (゜ - ゜ ノ) را به ابتدای یک پیام متنی ساده اضافه می‌کند",
         "unholdcall": "تماس را در اتاق فعلی خاموش نگه می دارد",
@@ -2049,7 +1970,9 @@
             "sent": "%(senderName)s %(targetDisplayName)s را به اتاق دعوت کرد."
         },
         "m.room.tombstone": "%(senderDisplayName)s این اتاق را ارتقا داد.",
-        "m.room.topic": "%(senderDisplayName)s موضوع را به %(topic)s تغییر داد.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s موضوع را به %(topic)s تغییر داد."
+        },
         "m.sticker": "%(senderDisplayName)s یک برچسب فرستاد.",
         "m.video": {
             "error_decrypting": "خطا در رمزگشایی ویدیو"
@@ -2243,14 +2166,6 @@
     },
     "user_info": {
         "admin_tools_section": "ابزارهای مدیریت",
-        "count_of_sessions": {
-            "one": "%(count)s نشست",
-            "other": "%(count)s نشست"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 نشست تأیید شده",
-            "other": "%(count)s نشست تایید شده"
-        },
         "deactivate_confirm_action": "غیرفعال کردن کاربر",
         "deactivate_confirm_description": "با غیرفعال کردن این کاربر، او از سیستم خارج شده و از ورود مجدد وی جلوگیری می‌شود. علاوه بر این، او تمام اتاق هایی را که در آن هست ترک می کند. این عمل قابل برگشت نیست. آیا مطمئن هستید که می خواهید این کاربر را غیرفعال کنید؟",
         "deactivate_confirm_title": "کاربر غیرفعال شود؟",
@@ -2258,14 +2173,11 @@
         "demote_self_confirm_description_space": "شما نمی توانید این تغییر را لغو کنید زیرا در حال تنزل خود هستید، اگر آخرین کاربر ممتاز در فضای کاری باشید، بازپس گیری امتیازات غیرممکن است.",
         "demote_self_confirm_room": "شما نمی توانید این تغییر را لغو کنید زیرا در حال تنزل خود هستید، اگر آخرین کاربر ممتاز در اتاق باشید بازپس گیری امتیازات غیرممکن است.",
         "demote_self_confirm_title": "خودتان را تنزل می‌دهید؟",
-        "edit_own_devices": "ویرایش دستگاه‌ها",
         "error_ban_user": "کاربر مسدود نشد",
         "error_deactivate": "غیرفعال کردن کاربر انجام نشد",
         "error_mute_user": "کاربر بی صدا نشد",
         "error_revoke_3pid_invite_description": "دعوت لغو نشد. ممکن است سرور با یک مشکل موقتی روبرو شده باشد و یا اینکه شما مجوز کافی برای لغو دعوت را نداشته باشید.",
         "error_revoke_3pid_invite_title": "دعوت لغو نشد",
-        "hide_sessions": "مخفی کردن نشست‌ها",
-        "hide_verified_sessions": "مخفی کردن نشست‌های تأیید شده",
         "invited_by": "دعوت شده توسط %(sender)s",
         "jump_to_rr_button": "پرش به آخرین پیام خوانده شده",
         "promote_warning": "شما نمی توانید این تغییر را باطل کنید زیرا در حال ارتقا سطح قدرت یک کاربر به سطح قدرت خود هستید.",
@@ -2319,7 +2231,6 @@
         "expand": "بازگشت به تماس",
         "hangup": "قطع",
         "hide_sidebar_button": "پنهان سازی نوار کناری",
-        "join_button_tooltip_connecting": "در حال اتصال",
         "misconfigured_server": "تماس به دلیل پیکربندی نادرست سرور موفقیت‌آمیز نبود",
         "misconfigured_server_description": "لطفا برای برقراری تماس، از مدیر <code>%(homeserverDomain)s</code> بخواهید سرور TURN را پیکربندی نماید.",
         "misconfigured_server_fallback": "از طرف دیگر، می‌توانید سعی کنید از سرور عمومی در <server/> استفاده کنید، اما این به آن اندازه قابل اعتماد نخواهد بود و آدرس IP شما را با آن سرور به اشتراک می‌گذارد. شما همچنین می توانید این قابلیت را در تنظیمات مدیریت کنید.",
diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json
index f42a1af5696c409967e2a88c0144c7ab8a33e028..291b460f2e15a1bad0dec9df7a3a1dc328c1266d 100644
--- a/src/i18n/strings/fi.json
+++ b/src/i18n/strings/fi.json
@@ -1,6 +1,8 @@
 {
     "a11y": {
+        "emoji_picker": "Emoji-valitsin",
         "jump_first_invite": "Siirry ensimmäiseen kutsuun.",
+        "message_composer": "Viestin kirjoitusikkuna",
         "n_unread_messages": {
             "other": "%(count)s lukematonta viestiä.",
             "one": "Yksi lukematon viesti."
@@ -9,7 +11,11 @@
             "other": "%(count)s lukematonta viestiä, sisältäen maininnat.",
             "one": "Yksi lukematon maininta."
         },
+        "recent_rooms": "Viimeisimmät huoneet",
+        "room_n_unread_invite": "Avaa huoneen %(roomName)s kutsu.",
         "room_name": "Huone %(name)s",
+        "room_status_bar": "Huoneen tilapalkki",
+        "seek_bar_label": "Äänen siirtymispalkki",
         "unread_messages": "Lukemattomat viestit.",
         "user_menu": "Käyttäjän valikko"
     },
@@ -21,11 +27,13 @@
         "add_people": "Lisää ihmisiä",
         "apply": "Toteuta",
         "approve": "Hyväksy",
+        "ask_to_join": "Pyydä liittyä",
         "back": "Takaisin",
         "call": "Soita",
         "cancel": "Peruuta",
         "change": "Muuta",
         "clear": "Tyhjennä",
+        "click": "Napsauta",
         "click_to_copy": "Kopioi napsauttamalla",
         "close": "Sulje",
         "collapse": "Supista",
@@ -39,6 +47,7 @@
         "create_account": "Luo tili",
         "decline": "Hylkää",
         "delete": "Poista",
+        "deny": "Estä",
         "disable": "Poista käytöstä",
         "disconnect": "Katkaise yhteys",
         "dismiss": "Hylkää",
@@ -56,6 +65,7 @@
         "go": "Mene",
         "go_back": "Takaisin",
         "got_it": "Asia selvä",
+        "hide": "Piilota",
         "hide_advanced": "Piilota lisäasetukset",
         "hold": "Pidä",
         "ignore": "Sivuuta",
@@ -72,25 +82,29 @@
         "maximise": "Suurenna",
         "mention": "Mainitse",
         "minimise": "Pienennä",
+        "new_message": "Uusi viesti",
         "new_room": "Uusi huone",
         "new_video_room": "Uusi videohuone",
         "next": "Seuraava",
         "no": "Ei",
+        "ok": "OK",
         "open": "Avaa",
+        "open_menu": "Avaa valikko",
         "pause": "Keskeytä",
-        "pin": "Nuppineula",
+        "pin": "Kiinnitä",
         "play": "Toista",
+        "proceed": "Jatka",
         "quote": "Lainaa",
         "react": "Reagoi",
         "refresh": "Päivitä",
         "register": "Rekisteröidy",
-        "reject": "Hylkää",
         "reload": "Lataa uudelleen",
         "remove": "Poista",
         "rename": "Nimeä uudelleen",
         "reply": "Vastaa",
         "reply_in_thread": "Vastaa ketjuun",
         "report_content": "Ilmoita sisällöstä",
+        "report_room": "Ilmoita huoneesta",
         "resend": "Lähetä uudelleen",
         "reset": "Palauta alkutilaan",
         "resume": "Jatka",
@@ -100,6 +114,7 @@
         "save": "Tallenna",
         "search": "Haku",
         "send_report": "Lähetä ilmoitus",
+        "set_avatar": "Aseta profiilikuva",
         "share": "Jaa",
         "show": "Näytä",
         "show_advanced": "Näytä lisäasetukset",
@@ -123,6 +138,7 @@
         "update": "Päivitä",
         "upgrade": "Päivitä",
         "upload": "Lähetä",
+        "upload_file": "Lähetä tiedosto",
         "verify": "Varmenna",
         "view": "Näytä",
         "view_all": "Näytä kaikki",
@@ -213,6 +229,7 @@
         },
         "misconfigured_body": "Pyydä %(brand)s-ylläpitäjääsi tarkistamaan, onko <a>asetuksissasi</a>virheellisiä tai toistettuja merkintöjä.",
         "misconfigured_title": "%(brand)sin asetukset ovat pielessä",
+        "mobile_create_account_title": "Olet aikeissa luoda tilin palveluun %(hsName)s",
         "msisdn_field_description": "Muut voivat kutsua sinut huoneisiin yhteystietojesi avulla",
         "msisdn_field_label": "Puhelin",
         "msisdn_field_number_invalid": "Tämä puhelinnumero ei näytä oikealta, tarkista se ja yritä uudelleen",
@@ -220,6 +237,7 @@
         "no_hs_url_provided": "Kotipalvelimen osoite puuttuu",
         "oidc": {
             "error_title": "Emme voineet kirjata sinua sisään",
+            "generic_auth_error": "Jokin meni pieleen tunnistautumisen aikana. Siirry kirjautumissivulle ja yritä uudelleen.",
             "missing_or_invalid_stored_state": "Pyysimme selainta muistamaan kirjautumista varten mitä kotipalvelinta käytät, mutta selain on unohtanut sen. Mene kirjautumissivulle ja yritä uudelleen."
         },
         "password_field_keep_going_prompt": "Jatka…",
@@ -229,8 +247,40 @@
         "phone_label": "Puhelin",
         "phone_optional_label": "Puhelin (valinnainen)",
         "qr_code_login": {
+            "check_code_explainer": "Tämä varmistaa, että yhteys toiseen laitteeseen on turvallinen.",
+            "check_code_heading": "Kirjoita toisessa laitteessa näkyvä numero",
+            "check_code_input_label": "2-numeroinen koodi",
+            "check_code_mismatch": "Numerot eivät täsmää",
+            "completing_setup": "Viimeistellään uuden laitteesi käyttöönottoa",
+            "error_etag_missing": "Tapahtui odottamaton virhe. Tämä voi johtua selaimen laajennuksesta, välityspalvelimesta tai palvelimen virheellisestä konfiguroinnista.",
+            "error_expired": "Kirjautuminen vanhentui. Yritä uudelleen.",
+            "error_expired_title": "Kirjautumista ei suoritettu ajoissa",
+            "error_insecure_channel_detected": "Turvallista yhteyttä uuteen laitteeseen ei voitu muodostaa. Olemassa olevat laitteesi ovat edelleen turvassa, eikä sinun tarvitse huolehtia niistä.",
+            "error_insecure_channel_detected_instructions": "Mitä nyt?",
+            "error_insecure_channel_detected_instructions_1": "Yritä kirjautua toiseen laitteeseen uudelleen QR-koodilla, jos kyseessä oli verkko-ongelma",
+            "error_insecure_channel_detected_instructions_2": "Jos kohtaat saman ongelman, kokeile toista wifi-verkkoa tai käytä mobiilidataa wifi-yhteyden sijaan",
+            "error_insecure_channel_detected_instructions_3": "Jos tämä ei auta, kirjaudu sisään manuaalisesti",
+            "error_insecure_channel_detected_title": "Yhteys ei ole turvallinen",
+            "error_other_device_already_signed_in": "Sinun ei tarvitse tehdä mitään muuta.",
+            "error_other_device_already_signed_in_title": "Toinen laitteesi on jo kirjautunut sisään",
             "error_rate_limited": "Liikaa yrityksiä lyhyessä ajassa. Odota hetki, ennen kuin yrität uudelleen.",
-            "error_unexpected": "Tapahtui odottamaton virhe.",
+            "error_unexpected": "Tapahtui odottamaton virhe. Toisen laitteen yhdistämispyyntö on peruttu.",
+            "error_unsupported_protocol": "Tämä laite ei tue kirjautumista toiseen laitteeseen QR-koodilla.",
+            "error_unsupported_protocol_title": "Toinen laite ei ole yhteensopiva",
+            "error_user_cancelled": "Kirjautuminen peruutettiin toisella laitteella.",
+            "error_user_cancelled_title": "Kirjautumispyyntö peruutettu",
+            "error_user_declined": "Sinä tai palveluntarjoajasi hylkäsi kirjautumispyynnön.",
+            "error_user_declined_title": "Kirjautuminen hylätty",
+            "follow_remaining_instructions": "Noudata jäljellä olevia ohjeita",
+            "open_element_other_device": "Avaa %(brand)s toisella laitteellasi",
+            "point_the_camera": "Skannaa tässä näkyvä QR-koodi",
+            "scan_code_instruction": "Skannaa QR-koodi toisella laitteella",
+            "scan_qr_code": "Kirjaudu sisään QR-koodilla",
+            "security_code": "Turvakoodi",
+            "security_code_prompt": "Anna pyydettäessä alla oleva koodi toisella laitteellasi.",
+            "select_qr_code": "Valitse \"%(scanQRCode)s\"",
+            "unsupported_explainer": "Palveluntarjoajasi ei tue kirjautumista uuteen laitteeseen QR-koodilla.",
+            "unsupported_heading": "QR-koodia ei tueta",
             "waiting_for_device": "Odotetaan laitteen sisäänkirjautumista"
         },
         "register_action": "Luo tili",
@@ -257,6 +307,7 @@
             "sign_out_other_devices": "Kirjaudu ulos kaikista laitteista"
         },
         "reset_password_action": "Nollaa salasana",
+        "reset_password_button": "Unohditko salasanan?",
         "reset_password_email_field_description": "Voit palauttaa tilisi sähköpostiosoitteen avulla",
         "reset_password_email_field_required_invalid": "Syötä sähköpostiosoite (vaaditaan tällä kotipalvelimella)",
         "reset_password_email_not_found_title": "Sähköpostiosoitetta ei löytynyt",
@@ -283,6 +334,7 @@
         },
         "set_email_prompt": "Haluatko asettaa sähköpostiosoitteen?",
         "sign_in_description": "Käytä tiliäsi jatkaaksesi.",
+        "sign_in_instead": "Kirjaudu sen sijaan",
         "sign_in_instead_prompt": "Onko sinulla jo tili? <a>Kirjaudu tästä</a>",
         "sign_in_or_register": "Kirjaudu sisään tai luo tili",
         "sign_in_or_register_description": "Käytä tiliäsi tai luo uusi jatkaaksesi.",
@@ -312,11 +364,13 @@
             "email_resend_prompt": "Etkö saanut sitä? <a>Lähetä uudelleen</a>",
             "email_resent": "Lähetetty uudelleen!",
             "fallback_button": "Aloita tunnistus",
+            "mas_cross_signing_reset_cta": "Siirry tilillesi",
             "msisdn": "Tekstiviesti lähetetty numeroon %(msisdn)s",
             "msisdn_token_incorrect": "Väärä tunniste",
             "msisdn_token_prompt": "Ole hyvä ja syötä sen sisältämä koodi:",
             "password_prompt": "Vahvista henkilöllisyytesi syöttämällä tilisi salasana alle.",
             "recaptcha_missing_params": "Captchan julkinen avain puuttuu kotipalvelimen asetuksista. Ilmoita tämä kotipalvelimesi ylläpitäjälle.",
+            "registration_token_label": "Rekisteröitymispoletti",
             "sso_body": "Vahvista tämän sähköpostiosoitteen lisääminen todistamalla henkilöllisyytesi kertakirjautumista käyttäen.",
             "sso_failed": "Jokin meni pieleen henkilöllisyyttä vahvistaessa. Peruuta ja yritä uudelleen.",
             "sso_postauth_body": "Napsauta alapuolella olevaa painiketta varmistaaksesi identiteettisi.",
@@ -342,7 +396,9 @@
         "download_logs": "Lataa lokit",
         "downloading_logs": "Ladataan lokeja",
         "error_empty": "Kerro mikä meni pieleen, tai, mikä parempaa, luo GitHub-issue joka kuvailee ongelman.",
-        "failed_send_logs": "Lokien lähettäminen epäonnistui: ",
+        "failed_send_logs_causes": {
+            "unknown_error": "Lokien lähettäminen epäonnistui."
+        },
         "github_issue": "GitHub-issue",
         "introduction": "Jos olet tehnyt ilmoituksen ohjelmistovirheestä GitHubiin, vianjäljityslokit voivat auttaa ongelman selvittämisessä. ",
         "log_request": "Voit auttaa meitä estämään tämän toistumisen <a>lähettämällä meille lokeja</a>.",
@@ -380,7 +436,7 @@
         "access_token": "Käyttöpoletti",
         "accessibility": "Saavutettavuus",
         "advanced": "Lisäasetukset",
-        "all_rooms": "Kaikki huoneet",
+        "all_chats": "Kaikki keskustelut",
         "analytics": "Analytiikka",
         "and_n_others": {
             "other": "ja %(count)s muuta...",
@@ -394,16 +450,17 @@
         "beta": "Beeta",
         "camera": "Kamera",
         "cameras": "Kamerat",
+        "cancel": "Peruuta",
         "capabilities": "Kyvykkyydet",
         "copied": "Kopioitu!",
         "credits": "Maininnat",
-        "cross_signing": "Ristiinvarmennus",
         "dark": "Tumma",
         "description": "Kuvaus",
         "deselect_all": "Älä valitse mitään",
         "device": "Laite",
         "edited": "muokattu",
         "email_address": "Sähköpostiosoite",
+        "emoji": "Emoji",
         "encrypted": "Salattu",
         "encryption_enabled": "Salaus käytössä",
         "error": "Virhe",
@@ -429,8 +486,10 @@
         "loading": "Ladataan…",
         "location": "Sijainti",
         "low_priority": "Matala prioriteetti",
+        "matrix": "Matrix",
         "message": "Viesti",
         "message_layout": "Viestien asettelu",
+        "message_timestamp_invalid": "Virheellinen aikaleima",
         "microphone": "Mikrofoni",
         "model": "Malli",
         "modern": "Moderni",
@@ -468,13 +527,15 @@
         "qr_code": "QR-koodi",
         "random": "Satunnainen",
         "reactions": "Reaktiot",
+        "recommended": "Suositeltu",
         "report_a_bug": "Raportoi virheestä",
         "room": "Huone",
         "room_name": "Huoneen nimi",
         "rooms": "Huoneet",
+        "save": "Tallenna",
+        "saved": "Tallennettu",
         "saving": "Tallennetaan…",
         "secure_backup": "Turvallinen varmuuskopio",
-        "security": "Tietoturva",
         "select_all": "Valitse kaikki",
         "server": "Palvelin",
         "settings": "Asetukset",
@@ -493,19 +554,20 @@
         "thread": "Ketju",
         "threads": "Ketjut",
         "timeline": "Aikajana",
-        "trusted": "Luotettu",
         "unavailable": "ei saatavilla",
         "unencrypted": "Suojaamaton",
         "unmute": "Poista mykistys",
         "unnamed_room": "Nimeämätön huone",
         "unnamed_space": "Nimetön avaruus",
         "unverified": "Vahvistamaton",
+        "updating": "Päivitetään...",
         "user": "Käyttäjä",
         "user_avatar": "Profiilikuva",
         "username": "Käyttäjätunnus",
         "verification_cancelled": "Varmennus peruutettu",
         "verified": "Vahvistettu",
         "version": "Versio",
+        "video": "Video",
         "video_room": "Videohuone",
         "view_message": "Näytä viesti",
         "warning": "Varoitus"
@@ -534,8 +596,10 @@
         "format_italic": "Kursivointi",
         "format_italics": "Kursivoitu",
         "format_link": "Linkki",
+        "format_ordered_list": "Numeroitu luettelo",
         "format_strikethrough": "Yliviivattu",
         "format_underline": "Alleviivaus",
+        "format_unordered_list": "Järjestämätön luettelo",
         "formatting_toolbar_label": "Muotoilu",
         "link_modal": {
             "link_field_label": "Linkki",
@@ -621,11 +685,13 @@
         "private_space_description": "Yksityinen avaruus sinulle ja tiimikavereille",
         "public_description": "Avoin avaruus kaikille, paras yhteisöille",
         "public_heading": "Julkinen avaruutesi",
+        "search_public_button": "Etsi julkisia avaruuksia",
         "setup_rooms_community_description": "Tehdään huone jokaiselle.",
         "setup_rooms_community_heading": "Mistä asioista haluat puhua avaruudessa %(spaceName)s?",
         "setup_rooms_description": "Voit lisätä niitä myöhemmin, mukaan lukien olemassa olevia.",
         "setup_rooms_private_description": "Luomme huoneet jokaiselle niistä.",
         "setup_rooms_private_heading": "Minkä projektien parissa tiimisi työskentelee?",
+        "share_description": "Vain sinä tällä hetkellä, vielä parempi muiden kanssa.",
         "share_heading": "Jaa %(name)s",
         "skip_action": "Ohita tältä erää",
         "subspace_adding": "Lisätään…",
@@ -642,6 +708,9 @@
         "default_cover_photo": "<photo>Oletuskansikuva</photo> © <author>Jesús Roncero</author>, käytössä <terms>CC-BY-SA 4.0</terms>:n ehtojen mukaisesti.",
         "twemoji_colr": "<colr>twemoji-colr</colr>-fontti © <author>Mozilla Foundation</author>, käytössä <terms>Apache 2.0</terms>:n ehtojen mukaisesti."
     },
+    "decline_invitation_dialog": {
+        "reason_description": "Kerro syy huoneen ilmoittamiseen."
+    },
     "desktop_default_device_name": "%(brand)sin työpöytäversio: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktiiviset sovelmat",
@@ -649,6 +718,9 @@
         "category_room": "Huone",
         "caution_colon": "Varoitus:",
         "client_versions": "Asiakasversiot",
+        "crypto": {
+            "title": "Päästä päähän -salaus"
+        },
         "developer_mode": "Kehittäjätila",
         "developer_tools": "Kehittäjätyökalut",
         "edit_setting": "Muokkaa asetusta",
@@ -667,8 +739,13 @@
         "no_receipt_found": "Kuittausta ei löytynyt",
         "number_of_users": "Käyttäjämäärä",
         "original_event_source": "Alkuperäinen tapahtumalähde",
+        "room_encrypted": "Huone on <strong>salattu ✅</strong>",
         "room_id": "Huoneen ID-tunniste: %(roomId)s",
+        "room_not_encrypted": "Huone <strong>ei ole salattu 🚨</strong>",
         "room_notifications_sender": "Lähettäjä: ",
+        "room_notifications_total": "Yhteensä: ",
+        "room_notifications_type": "Tyyppi: ",
+        "room_status": "Huoneen tila",
         "save_setting_values": "Tallenna asetusarvot",
         "server_info": "Palvelimen tiedot",
         "server_versions": "Palvelinversiot",
@@ -712,43 +789,28 @@
     "empty_room_was_name": "Tyhjä huone (oli %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Kirjoita turvalause tai <button>käytä turva-avain</button> jatkaaksesi.",
             "key_validation_text": {
-                "invalid_security_key": "Virheellinen turva-avain",
-                "recovery_key_is_correct": "Hyvältä näyttää!",
-                "wrong_file_type": "Väärä tiedostotyyppi",
-                "wrong_security_key": "Väärä turva-avain"
+                "wrong_security_key": "Väärä palautusavain"
             },
             "restoring": "Palautetaan avaimia varmuuskopiosta",
-            "security_key_title": "Turva-avain",
-            "security_phrase_title": "Turvalause",
-            "separator": "%(securityKey)s tai %(recoveryFile)s",
-            "use_security_key_prompt": "Käytä turva-avain jatkaaksesi."
+            "security_key_title": "Palautusavain"
         },
         "bootstrap_title": "Otetaan avaimet käyttöön",
         "cancel_entering_passphrase_description": "Haluatko varmasti peruuttaa salasanan syöttämisen?",
         "cancel_entering_passphrase_title": "Peruuta salasanan syöttäminen?",
         "confirm_encryption_setup_body": "Napsauta alla olevaa painiketta vahvistaaksesi salauksen asettamisen.",
         "confirm_encryption_setup_title": "Vahvista salauksen asetukset",
-        "cross_signing_not_ready": "Ristiinvarmennusta ei ole asennettu.",
-        "cross_signing_ready": "Ristiinvarmennus on käyttövalmis.",
-        "cross_signing_ready_no_backup": "Ristiinvarmennus on valmis, mutta avaimia ei ole varmuuskopioitu.",
         "cross_signing_room_normal": "Tämä huone käyttää päästä päähän -salausta",
         "cross_signing_room_verified": "Kaikki tämän huoneen käyttäjät on varmennettu",
         "cross_signing_room_warning": "Joku käyttää tuntematonta istuntoa",
-        "cross_signing_unsupported": "Kotipalvelimesi ei tue ristiinvarmennusta.",
-        "cross_signing_untrusted": "Tililläsi on ristiinvarmennuksen identiteetti salaisessa tallennustilassa, mutta tämä istunto ei vielä luota siihen.",
         "cross_signing_user_normal": "Et ole varmentanut tätä käyttäjää.",
         "cross_signing_user_verified": "Olet varmentanut tämän käyttäjän. Tämä käyttäjä on varmentanut kaikki istuntonsa.",
         "cross_signing_user_warning": "Tämä käyttäjä ei ole varmentanut kaikkia istuntojaan.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Tyhjennä ristiinvarmennuksen avaimet",
-            "title": "Tuhoa ristiinvarmennuksen avaimet?",
-            "warning": "Ristiinvarmennuksen avainten tuhoamista ei voi kumota. Jokainen, jonka olet varmentanut, tulee näkemään turvallisuushälytyksiä. Et todennäköisesti halua tehdä tätä, ellet ole hukannut kaikkia laitteitasi, joista pystyit ristiinvarmentamaan."
-        },
+        "enter_recovery_key": "Kirjoita palautusavain",
         "event_shield_reason_authenticity_not_guaranteed": "Tämän salatun viestin aitoutta ei voida taata tällä laitteella.",
         "event_shield_reason_mismatched_sender_key": "Salattu varmentamattoman istunnon toimesta",
         "export_unsupported": "Selaimesi ei tue vaadittuja kryptografisia laajennuksia",
+        "forgot_recovery_key": "Unohditko palautusavaimen?",
         "import_invalid_keyfile": "Ei kelvollinen %(brand)s-avaintiedosto",
         "import_invalid_passphrase": "Autentikointi epäonnistui: virheellinen salasana?",
         "messages_not_secure": {
@@ -763,17 +825,18 @@
             "title": "Uusi palautustapa",
             "warning": "Jos et ottanut käyttöön uutta palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi."
         },
-        "not_supported": "<ei tuettu>",
         "recovery_method_removed": {
             "title": "Palautustapa poistettu",
             "warning": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi."
         },
         "reset_all_button": "Unohtanut tai kadottanut kaikki palautustavat? <a>Nollaa kaikki</a>",
+        "set_up_recovery": "Määritä palautus",
+        "set_up_recovery_later": "Ei nyt",
+        "set_up_recovery_toast_description": "Luo palautusavain, jota voit käyttää salatun viestihistorian palauttamiseen, jos menetät pääsyn laitteisiisi.",
         "set_up_toast_description": "Suojaudu salattuihin viesteihin ja tietoihin pääsyn menettämiseltä",
         "set_up_toast_title": "Määritä turvallinen varmuuskopio",
         "setup_secure_backup": {
-            "explainer": "Varmuuskopioi avaimesi ennen kuin kirjaudut ulos välttääksesi avainten menetyksen.",
-            "title": "Ota käyttöön"
+            "explainer": "Varmuuskopioi avaimesi ennen kuin kirjaudut ulos välttääksesi avainten menetyksen."
         },
         "udd": {
             "interactive_verification_button": "Vahvista vuorovaikutteisesti emojilla",
@@ -783,7 +846,6 @@
             "own_new_session_text": "Olet kirjautunut uuteen istuntoon varmentamatta sitä:",
             "title": "Ei luotettu"
         },
-        "unsupported": "Tämä asiakasohjelma ei tue päästä päähän -salausta.",
         "verification": {
             "accepting": "Hyväksytään…",
             "after_new_login": {
@@ -815,6 +877,7 @@
             "qr_reciprocate_same_shield_device": "Melkein valmista! Näyttääkö toinen laitteesi saman kilven?",
             "qr_reciprocate_same_shield_user": "Melkein valmista! Näyttääkö %(displayName)s saman kilven?",
             "request_toast_accept": "Vahvista istunto",
+            "request_toast_accept_user": "Vahvista käyttäjä",
             "request_toast_decline_counter": "Sivuuta (%(counter)s)",
             "request_toast_detail": "%(deviceId)s osoitteesta %(ip)s",
             "sas_caption_self": "Vahvista tämä laite toteamalla, että seuraava numero näkyy sen näytöllä.",
@@ -848,7 +911,7 @@
             "verify_emoji_prompt_qr": "Jos et pysty skannaamaan yläpuolella olevaa koodia, varmenna vertaamalla emojia.",
             "verify_later": "Vahvistan myöhemmin",
             "verify_using_device": "Vahvista toisella laitteella",
-            "verify_using_key": "Vahvista turva-avaimella",
+            "verify_using_key": "Vahvista palautusavaimella",
             "verify_using_key_or_phrase": "Vahvista turva-avaimella tai turvalauseella",
             "waiting_for_user_accept": "Odotetaan, että %(displayName)s hyväksyy…",
             "waiting_other_device": "Odotetaan vahvistustasi toiselta laitteelta…",
@@ -896,18 +959,14 @@
         "unknown_error_code": "tuntematon virhekoodi",
         "update_power_level": "Oikeustason muuttaminen epäonnistui"
     },
-    "error_database_closed_title": "Tietokanta sulkeutui odottamattomasti",
+    "error_database_closed_title": "%(brand)s lakkasi toimimasta",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Huoneen linkin kopiointi leikepöydälle ei onnistu.",
             "title": "Huoneen linkin kopiointi ei onnistu"
         },
         "error_loading_user_profile": "Käyttäjäprofiilia ei voitu ladata",
-        "forget_room_failed": "Huoneen unohtaminen epäonnistui %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Palvelin saattaa olla saavuttamattomissa, ylikuormitettu tai haku kesti liian kauan :(",
-            "title": "Haku epäonnistui"
-        }
+        "forget_room_failed": "Huoneen unohtaminen epäonnistui %(errCode)s"
     },
     "error_user_not_logged_in": "Käyttäjä ei ole sisäänkirjautunut",
     "event_preview": {
@@ -925,7 +984,15 @@
             "dm_send": "Odotetaan vastausta",
             "user": "%(senderName)s aloitti puhelun",
             "you": "Aloitit puhelun"
-        }
+        },
+        "prefix": {
+            "audio": "Ääni",
+            "file": "Tiedosto",
+            "image": "Kuva",
+            "poll": "Kysely",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Vienti peruttu",
@@ -958,15 +1025,20 @@
         },
         "fetching_events": "Noudetaan tapahtumia…",
         "file_attached": "Tiedosto liitetty",
+        "format": "Muoto",
         "from_the_beginning": "Alusta lähtien",
         "generating_zip": "Luodaan ZIPiä",
+        "html": "HTML",
         "include_attachments": "Sisällytä liitteet",
+        "json": "JSON",
         "media_omitted": "Media jätetty pois",
         "media_omitted_file_size": "Media jätetty pois – tiedoston kokoraja ylitetty",
         "messages": "Viestit",
+        "next_page": "Seuraava viestiryhmä",
         "num_messages": "Viestien määrä",
         "num_messages_min_max": "Viestimäärän täytyy olla luku väliltä %(min)s…%(max)s",
         "number_of_messages": "Määritä viestien lukumäärä",
+        "previous_page": "Edellinen viestiryhmä",
         "processing": "Käsitellään…",
         "processing_event_n": "Käsitellään tapahtumaa %(number)s / %(total)s",
         "size_limit": "Kokoraja",
@@ -1041,7 +1113,15 @@
         "other": "Avaruudessa %(spaceName)s ja %(count)s muussa avaruudessa."
     },
     "incompatible_browser": {
-        "title": "Selainta ei tueta"
+        "continue": "Jatka silti",
+        "detail_no_continue": "Yritä päivittää tämä selain, jos et käytä uusinta versiota, ja yritä uudelleen.",
+        "learn_more": "Lue lisää",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Saat parhaan käyttökokemuksen käyttämällä <Chrome>Chromea</Chrome>, <Firefox>Firefoxia</Firefox>, <Edge>Edgeä</Edge> tai <Safari>Safaria</Safari>.",
+        "title": "Selainta ei tueta",
+        "windows_64bit": "Windows (64-bittinen)",
+        "windows_arm_64bit": "Windows (ARM 64-bittinen)"
     },
     "info_tooltip_title": "Tiedot",
     "integration_manager": {
@@ -1115,6 +1195,7 @@
     },
     "keyboard": {
         "activate_button": "Aktivoi valittu painike",
+        "alt": "Alt",
         "autocomplete_cancel": "Peruuta automaattinen täydennys",
         "autocomplete_force": "Pakota täydennys",
         "autocomplete_navigate_next": "Seuraava automaattitäydennyksen ehdotus",
@@ -1138,7 +1219,10 @@
         "composer_toggle_link": "Linkki päälle/pois",
         "composer_toggle_quote": "Lainaus päälle/pois",
         "composer_undo": "Kumoa muokkaus",
+        "control": "Ctrl",
         "dismiss_read_marker_and_jump_bottom": "Hylkää lukumerkki ja hyppää pohjaan",
+        "enter": "Enter",
+        "escape": "Esc",
         "go_home_view": "Siirry kotinäkymään",
         "home": "Etusivu",
         "jump_first_message": "Siirry ensimmäiseen viestiin",
@@ -1178,6 +1262,7 @@
     "labs": {
         "automatic_debug_logs": "Lähetä vianjäljityslokit automaattisesti minkä tahansa virheen tapahtuessa",
         "automatic_debug_logs_decryption": "Lähetä vianjäljityslokit automaattisesti salauksen purkuun liittyvien virheiden tapahtuessa",
+        "beta_description": "Mitä uutta %(brand)s sisältää seuraavaksi? Laboratoriot ovat paras tapa tutustua asioihin aikaisin, testata uusia ominaisuuksia ja auttaa muokkaamaan niitä ennen julkaisua.",
         "beta_feature": "Tämä on beetaominaisuus",
         "beta_feedback_leave_button": "Poistu beetasta asetuksista.",
         "beta_feedback_title": "Ominaisuuden %(featureName)s beetapalaute",
@@ -1191,7 +1276,9 @@
         "currently_experimental": "Tällä hetkellä kokeellinen.",
         "custom_themes": "Tue mukaututettujen teemojen lisäämistä",
         "dynamic_room_predecessors_description": "Ota käyttöön MSC3946 (viiveellä saapuvien huonearkistojen tukemiseksi)",
+        "element_call_video_rooms": "Element Call -videohuoneet",
         "experimental_section": "Ennakot",
+        "extended_profiles_msc_support": "Edellyttää, että palvelimesi tukee MSC4133:a",
         "group_calls": "Uusi ryhmäpuhelukokemus",
         "group_developer": "Kehittäjä",
         "group_encryption": "Salaus",
@@ -1203,6 +1290,7 @@
         "group_spaces": "Avaruudet",
         "group_themes": "Teemat",
         "group_threads": "Ketjut",
+        "group_ui": "Käyttöliittymä",
         "group_voip": "Ääni ja video",
         "group_widgets": "Sovelmat",
         "html_topic": "Näytä huoneiden aiheiden HTML-esitys",
@@ -1215,12 +1303,15 @@
         "location_share_live_description": "Tilapäinen toteutus. Sijainnit säilyvät huoneen historiassa.",
         "mjolnir": "Uusia tapoja jättää ihmiset huomiotta",
         "msc3531_hide_messages_pending_moderation": "Anna moderaattorien piilottaa moderointia odottavia viestejä.",
+        "notification_settings": "Uudet ilmoitusasetukset",
+        "notification_settings_beta_title": "Ilmoitusasetukset",
+        "release_announcement": "Julkaisutiedote",
         "report_to_moderators": "Ilmoita moderaattoreille",
         "report_to_moderators_description": "Moderointia tukevissa huoneissa väärinkäytökset voi ilmoittaa Ilmoita-painikkeella huoneen moderaattoreille.",
         "sliding_sync": "Liukuvan synkronoinnin tila",
         "sliding_sync_description": "Työn alla, käytöstä poistaminen ei ole mahdollista.",
         "sliding_sync_disabled_notice": "Poista käytöstä kirjautumalla ulos ja takaisin sisään",
-        "sliding_sync_server_no_support": "Palvelimellasi ei ole natiivitukea",
+        "sliding_sync_server_no_support": "Palvelimellasi ei ole tukea",
         "under_active_development": "Aktiivisen kehityksen kohteena.",
         "video_rooms": "Videohuoneet",
         "video_rooms_a_new_way_to_chat": "Uusi tapa keskustella äänen ja videon välityksellä %(brand)sissä.",
@@ -1306,8 +1397,13 @@
         "share_type_prompt": "Minkä sijaintityypin haluat jakaa?"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s jäsen",
+            "other": "%(count)s jäsentä"
+        },
         "filter_placeholder": "Suodata huoneen jäseniä",
         "invite_button_no_perms_tooltip": "Sinulla ei ole lupaa kutsua käyttäjiä",
+        "invited_label": "Kutsuttu",
         "power_label": "%(userName)s (oikeustaso %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Huoneen jäsenet",
@@ -1317,6 +1413,7 @@
         "toast_description": "%(brand)s on mobiiliselaimissa kokeellinen. Paremman kokemuksen ja uusimmat ominaisuudet saat ilmaisella mobiilisovelluksellamme.",
         "toast_title": "Parempi kokemus sovelluksella"
     },
+    "name_and_id": "%(name)s (%(userId)s)",
     "no_more_results": "Ei enempää tuloksia",
     "notif_panel": {
         "empty_description": "Sinulla ei ole näkyviä ilmoituksia.",
@@ -1328,6 +1425,7 @@
         "class_global": "Yleiset",
         "class_other": "Muut",
         "default": "Oletus",
+        "email_pusher_app_display_name": "Sähköposti-ilmoitukset",
         "enable_prompt_toast_description": "Ota työpöytäilmoitukset käyttöön",
         "enable_prompt_toast_title": "Ilmoitukset",
         "enable_prompt_toast_title_from_message_send": "Älä jätä vastauksia huomiotta",
@@ -1336,12 +1434,14 @@
         "keyword_new": "Uusi avainsana",
         "level_activity": "Aktiivisuus",
         "level_none": "Ei mitään",
+        "level_notification": "Ilmoitus",
         "mark_all_read": "Merkitse kaikki luetuiksi",
         "mentions_and_keywords": "@maininnat & asiasanat",
         "mentions_and_keywords_description": "Vastaanota ilmoitukset maininnoista ja asiasanoista <a>asetuksissa</a> määrittämälläsi tavalla",
         "mentions_keywords": "Maininnat ja avainsanat",
         "message_didnt_send": "Viestiä ei lähetetty. Lisätietoa napsauttamalla.",
-        "mute_description": "Et saa ilmoituksia"
+        "mute_description": "Et saa ilmoituksia",
+        "mute_room": "Mykistä huone"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s pyytää varmennusta"
@@ -1435,15 +1535,11 @@
     },
     "redact": {
         "confirm_button": "Varmista poistaminen",
+        "confirm_description": "Haluatko varmasti poistaa tämän tapahtuman?",
         "error": "Et voi poistaa tätä viestiä. (%(code)s)",
         "ongoing": "Poistetaan…",
         "reason_label": "Syy (valinnainen)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Oletko varma että haluat hylätä kutsun?",
-        "failed": "Kutsun hylkääminen epäonnistui",
-        "title": "Hylkää kutsu"
-    },
     "report_content": {
         "description": "Tämän viestin ilmoittaminen lähettää sen yksilöllisen tapahtumatunnuksen (event ID) kotipalvelimesi ylläpitäjälle. Jos tämän huoneen viestit on salattu, kotipalvelimesi ylläpitäjä ei voi lukea viestin tekstiä tai nähdä tiedostoja tai kuvia.",
         "ignore_user": "Sivuuta käyttäjä",
@@ -1455,16 +1551,19 @@
         "spam_or_propaganda": "Roskapostitusta tai propagandaa",
         "toxic_behaviour": "Myrkyllinen käyttäytyminen"
     },
+    "report_room": {
+        "reason_label": "Kuvaile syy"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "%(failedCount)s istunnon purkaminen epäonnistui!",
         "count_of_successfully_restored_keys": "%(sessionCount)s avaimen palautus onnistui",
-        "enter_key_title": "Anna turva-avain",
+        "enter_key_title": "Anna palautusavain",
         "enter_phrase_title": "Kirjoita turvalause",
         "incorrect_security_phrase_title": "Virheellinen turvalause",
         "key_backup_warning": "<b>Varoitus</b>: sinun pitäisi ottaa avainvarmuuskopio käyttöön vain luotetulla tietokoneella.",
         "key_fetch_in_progress": "Noudetaan avaimia palvelimelta…",
-        "key_is_invalid": "Ei kelvollinen turva-avain",
-        "key_is_valid": "Tämä vaikuttaa kelvolliselta turva-avaimelta!",
+        "key_is_invalid": "Ei kelvollinen palautusavain",
+        "key_is_valid": "Tämä näyttää kelvolliselta palautusavaimelta!",
         "keys_restored_title": "Avaimet palautettu",
         "load_error_content": "Varmuuskopioinnin tilan lataaminen epäonnistui",
         "load_keys_progress": "%(completed)s / %(total)s avainta palautettu",
@@ -1472,14 +1571,34 @@
         "restore_failed_error": "Varmuuskopion palauttaminen ei onnistu"
     },
     "right_panel": {
-        "add_integrations": "Lisää sovelmia, siltoja ja botteja",
+        "add_integrations": "Lisää laajennuksia",
+        "add_topic": "Lisää aihe",
+        "extensions_button": "Laajennukset",
+        "extensions_empty_description": "Valitse ”%(addIntegrations)s” selataksesi ja lisätäksesi laajennuksia tähän huoneeseen",
+        "extensions_empty_title": "Paranna tuottavuutta työkaluilla, widgeteillä ja boteilla",
         "files_button": "Tiedostot",
         "pinned_messages": {
+            "empty_title": "Kiinnitä tärkeät viestit, jotta ne löytyvät helposti",
+            "header": {
+                "one": "1 kiinnitetty viesti",
+                "other": "%(count)s kiinnitettyä viestiä"
+            },
             "limits": {
                 "other": "Voit kiinnittää enintään %(count)s sovelmaa"
-            }
+            },
+            "menu": "Avaa valikko",
+            "release_announcement": {
+                "close": "OK",
+                "description": "Kiinnitetyt viestit löytyvät täältä. Mene minkä tahansa viestin päälle ja valitse \"Kiinnitä\" lisätäksesi viestin tänne.",
+                "title": "Kiinnitetyt viestit"
+            },
+            "unpin_all": {
+                "button": "Poista kaikkien viestien kiinnitys",
+                "title": "Poistetaanko kaikkien viestien kiinnitys?"
+            },
+            "view": "Näytä aikajanalla"
         },
-        "pinned_messages_button": "Kiinnitetty",
+        "pinned_messages_button": "Kiinnitetyt viestit",
         "poll": {
             "active_heading": "Aktiiviset kyselyt",
             "empty_active": "Tässä huoneessa ei ole aktiivisia kyselyitä",
@@ -1504,7 +1623,7 @@
             "view_in_timeline": "Näytä kysely aikajanalla",
             "view_poll": "Näytä kysely"
         },
-        "polls_button": "Kyselyhistoria",
+        "polls_button": "Kyselyt",
         "room_summary_card": {
             "title": "Huoneen tiedot"
         },
@@ -1533,6 +1652,7 @@
             "forget": "Unohda huone",
             "low_priority": "Matala prioriteetti",
             "mark_read": "Merkitse luetuksi",
+            "mark_unread": "Merkitse lukemattomaksi",
             "notifications_mute": "Mykistä huone",
             "title": "Huoneen asetukset",
             "unfavourite": "Suositut"
@@ -1542,6 +1662,7 @@
         "dm_invite_title": "Haluatko keskustella käyttäjän %(user)s kanssa?",
         "drop_file_prompt": "Pudota tiedosto tähän lähettääksesi sen palvelimelle",
         "edit_topic": "Muokkaa aihetta",
+        "error_join_403": "Tarvitset kutsun päästäksesi tähän huoneeseen.",
         "error_join_404_2": "Jos tiedät huoneen osoitteen, yritä liittyä sen kautta.",
         "error_join_404_invite": "Henkilö, joka kutsui sinut on jo poistunut tai hänen palvelimensa on poissa verkosta.",
         "error_join_404_invite_same_hs": "Henkilö, joka kutsui sinut on jo poistunut.",
@@ -1567,6 +1688,7 @@
         "header": {
             "room_is_public": "Tämä huone on julkinen"
         },
+        "header_avatar_open_settings_label": "Avaa huoneen asetukset",
         "inaccessible": "Tämä huone tai avaruus ei ole käytettävissä juuri tällä hetkellä.",
         "inaccessible_name": "%(roomName)s ei ole saatavilla tällä hetkellä.",
         "intro": {
@@ -1587,10 +1709,9 @@
             "you_created": "Loit tämän huoneen."
         },
         "invite_email_mismatch_suggestion": "Jaa tämä sähköposti asetuksissa saadaksesi kutsuja suoraan %(brand)sissa.",
-        "invite_reject_ignore": "Hylkää ja sivuuta käyttäjä",
         "invite_sent_to_email": "Tämä kutsu lähetettiin osoitteeseen %(email)s",
         "invite_sent_to_email_room": "Tämä kutsu huoneeseen %(roomName)s lähetettiin sähköpostiosoitteeseen %(email)s",
-        "invite_subtitle": "<userName/> kutsui sinut",
+        "invite_subtitle": "Kutsunut <userName/>",
         "invite_this_room": "Kutsu käyttäjiä",
         "invite_title": "Haluatko liittyä huoneeseen %(roomName)s?",
         "inviter_unknown": "Tuntematon",
@@ -1611,6 +1732,12 @@
         "kick_reason": "Syy: %(reason)s",
         "kicked_by": "%(memberName)s poisti sinut",
         "kicked_from_room_by": "%(memberName)s poisti sinut huoneesta %(roomName)s",
+        "knock_cancel_action": "Peruuta pyyntö",
+        "knock_message_field_placeholder": "Viesti (valinnainen)",
+        "knock_prompt": "Pyydetäänkö liittymistä?",
+        "knock_prompt_name": "Pyydetäänkö liittymistä huoneeseen %(roomName)s ?",
+        "knock_send_action": "Pyydä pääsyä",
+        "knock_sent": "Liittymispyyntö lähetetty",
         "leave_error_title": "Virhe poistuessa huoneesta",
         "leave_server_notices_description": "Tämä huone on kotipalvelimen tärkeille viesteille, joten ei voi poistua siitä.",
         "leave_server_notices_title": "Palvelinilmoitushuonetta ei voitu jättää",
@@ -1623,9 +1750,25 @@
         "not_found_title": "Tätä huonetta tai avaruutta ei ole olemassa.",
         "not_found_title_name": "Huonetta %(roomName)s ei ole olemassa.",
         "peek_join_prompt": "Esikatselet huonetta %(roomName)s. Haluatko liittyä siihen?",
+        "pinned_message_badge": "Kiinnitetty viesti",
+        "pinned_message_banner": {
+            "button_view_all": "Näytä kaikki",
+            "description": "Tässä huoneessa on kiinnitettyjä viestejä. Napsauta nähdäksesi ne.",
+            "go_to_message": "Näytä kiinnitetty viesti aikajanalla.",
+            "title": "<bold>%(index)s/%(length)s</bold> kiinnitettyä viestiä"
+        },
         "read_topic": "Lue aihe napsauttamalla",
         "rejecting": "Hylätään kutsua…",
         "rejoin_button": "Liity uudelleen",
+        "search": {
+            "all_rooms_button": "Etsi kaikista huoneista",
+            "placeholder": "Etsi viestejä...",
+            "summary": {
+                "one": "1 tulos haulle “<query/>”",
+                "other": "%(count)s tulosta haulle “<query/>”"
+            },
+            "this_room_button": "Etsi tästä huoneesta"
+        },
         "status_bar": {
             "delete_all": "Poista kaikki",
             "exceeded_resource_limit": "Viestiäsi ei lähetetty, koska tämä kotipalvelin on ylittänyt resurssirajan. <a>Ota yhteyttä palvelun ylläpitäjään</a> jatkaaksesi palvelun käyttämistä.",
@@ -1654,27 +1797,55 @@
                 "other": "Lähetetään %(filename)s ja %(count)s muuta"
             },
             "uploading_single_file": "Lähetetään %(filename)s"
-        }
+        },
+        "video_room": "Tämä huone on videohuone"
     },
     "room_list": {
         "add_room_label": "Lisää huone",
         "add_space_label": "Lisää avaruus",
+        "appearance": "Ulkoasu",
         "breadcrumbs_empty": "Ei hiljattain vierailtuja huoneita",
         "breadcrumbs_label": "Hiljattain vieraillut huoneet",
+        "empty": {
+            "no_chats": "Ei keskusteluja vielä",
+            "no_chats_description": "Aloita lähettämällä viestejä jollekin henkilölle tai luomalla huone",
+            "no_chats_description_no_room_rights": "Aloita lähettämällä viesti jollekin",
+            "no_favourites": "Sinulla ei ole vielä suosikkikeskustelua",
+            "no_rooms": "Et ole vielä missään huoneessa",
+            "no_unread": "Onnittelut! Sinulla ei ole lukemattomia viestejä",
+            "show_chats": "Näytä kaikki keskustelut"
+        },
         "failed_add_tag": "Tagin %(tagName)s lisääminen huoneeseen epäonnistui",
         "failed_remove_tag": "Tagin %(tagName)s poistaminen huoneesta epäonnistui",
+        "filters": {
+            "favourite": "Suosikit",
+            "people": "Ihmiset",
+            "rooms": "Huoneet"
+        },
         "home_menu_label": "Etusivun valinnat",
         "join_public_room_label": "Liity julkiseen huoneeseen",
         "joining_rooms_status": {
             "one": "Liitytään parhaillaan %(count)s huoneeseen",
             "other": "Liitytään parhaillaan %(count)s huoneeseen"
         },
+        "list_title": "Huoneluettelo",
+        "more_options": {
+            "copy_link": "Kopioi huoneen linkki",
+            "leave_room": "Poistu huoneesta",
+            "mark_read": "Merkitse luetuksi",
+            "mark_unread": "Merkitse lukemattomaksi"
+        },
         "notification_options": "Ilmoitusasetukset",
+        "open_space_menu": "Avaa avaruusvalikko",
         "redacting_messages_status": {
             "one": "Poistetaan parhaillaan viestejä yhdessä huoneessa",
             "other": "Poistetaan parhaillaan viestejä %(count)s huoneesta"
         },
+        "room": {
+            "open_room": "Avoin huone %(roomName)s"
+        },
         "show_less": "Näytä vähemmän",
+        "show_message_previews": "Näytä viestien esikatselut",
         "show_n_more": {
             "one": "Näytä %(count)s lisää",
             "other": "Näytä %(count)s lisää"
@@ -1683,7 +1854,13 @@
         "sort_by": "Lajittelutapa",
         "sort_by_activity": "Aktiivisuus",
         "sort_by_alphabet": "A-Ö",
+        "sort_type": {
+            "atoz": "A-Ö"
+        },
         "sort_unread_first": "Näytä ensimmäisenä huoneet, joissa on lukemattomia viestejä",
+        "space_menu": {
+            "space_settings": "Avaruuden asetukset"
+        },
         "space_menu_label": "%(spaceName)s-valikko",
         "sublist_options": "Lajittele",
         "suggested_rooms_heading": "Ehdotetut huoneet"
@@ -1719,6 +1896,7 @@
             "upgrade_warning_dialog_footer": "Olat päivittämässä tätä huonetta versiosta <oldVersion/> versioon <newVersion/>.",
             "upgrade_warning_dialog_invite_label": "Kutsu jäsenet tästä huoneesta automaattisesti uuteen huoneeseen",
             "upgrade_warning_dialog_report_bug_prompt_link": "Tämä yleensä vaikuttaa siihen, miten huonetta käsitellään palvelimella. Jos sinulla on ongelmia %(brand)stisi kanssa, <a>ilmoita virheestä</a>.",
+            "upgrade_warning_dialog_title": "Päivitä huone",
             "upgrade_warning_dialog_title_private": "Päivitä yksityinen huone"
         },
         "alias_not_specified": "ei määritetty",
@@ -1751,6 +1929,8 @@
             "error_deleting_alias_description": "Osoitetta poistaessa tapahtui virhe. Osoitetta ei ehkä ole enää olemassa tai kyseessä oli tilapäinen virhe.",
             "error_deleting_alias_description_forbidden": "Sinulla ei ole oikeutta poistaa osoitetta.",
             "error_deleting_alias_title": "Virhe osoitetta poistettaessa",
+            "error_publishing": "Huonetta ei voi julkaista",
+            "error_publishing_detail": "Tämän huoneen julkaisemisessa tapahtui virhe",
             "error_save_space_settings": "Avaruuden asetusten tallentaminen epäonnistui.",
             "error_updating_alias_description": "Huoneen vaihtoehtoisten osoitteiden päivittämisessä tapahtui virhe. Palvelin ei ehkä salli sitä tai kyseessä oli tilapäinen virhe.",
             "error_updating_canonical_alias_description": "Huoneen pääosoitteen päivityksessä tapahtui virhe. Se ei välttämättä ole sallittua tällä palvelimella tai kyseessä on väliaikainen virhe.",
@@ -1783,8 +1963,14 @@
             "notification_sound": "Ilmoitusääni",
             "settings_link": "Vastaanota ilmoitukset <a>asetuksissa</a> määrittämälläsi tavalla",
             "sounds_section": "Äänet",
+            "upload_sound_label": "Lähetä mukautettu ääni",
             "uploaded_sound": "Asetettu ääni"
         },
+        "people": {
+            "knock_empty": "Ei pyyntöjä",
+            "see_less": "Näytä vähemmän",
+            "see_more": "Näytä enemmän"
+        },
         "permissions": {
             "add_privileged_user_filter_placeholder": "Etsi käyttäjiä tästä huoneesta…",
             "ban": "Anna porttikieltoja",
@@ -1888,7 +2074,9 @@
             },
             "join_rule_upgrade_upgrading_room": "Päivitetään huonetta",
             "public_without_alias_warning": "Lisää osoite linkittääksesi tähän huoneeseen.",
-            "strict_encryption": "Älä lähetä salattuja viestejä vahvistamattomiin istuntoihin tässä huoneessa tässä istunnossa",
+            "publish_room": "Aseta tämä huone näkyväksi julkisten huoneiden hakemistossa.",
+            "publish_space": "Tee tämä avaruus näkyväksi julkisten huoneiden hakemistossa.",
+            "strict_encryption": "Lähetä viestejä vain vahvistetuille käyttäjille.",
             "title": "Tietoturva ja yksityisyys"
         },
         "title": "Huoneen asetukset — %(roomName)s",
@@ -1954,6 +2142,7 @@
             "access_token_detail": "Käyttöpolettisi (ns. token) antaa täyden pääsyn tilillesi. Älä jaa sitä kenenkään kanssa.",
             "brand_version": "%(brand)s-versio:",
             "clear_cache_reload": "Tyhjennä välimuisti ja lataa uudelleen",
+            "dialog_title": "<strong>Asetukset:</strong> Ohje ja tietoja",
             "help_link": "Saadaksesi apua %(brand)sin käyttämisessä, napsauta <a>tästä</a>.",
             "homeserver": "Kotipalvelin on <code>%(homeserverUrl)s</code>",
             "identity_server": "Identiteettipalvelin on <code>%(identityServerUrl)s</code>",
@@ -1962,17 +2151,27 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Asetukset:</strong> Tili",
+            "title": "Tili"
+        },
         "all_rooms_home": "Näytä kaikki huoneet etusivulla",
         "all_rooms_home_description": "Kaikki huoneet, joissa olet, näkyvät etusivulla.",
         "always_show_message_timestamps": "Näytä aina viestien aikaleimat",
         "appearance": {
+            "compact_layout": "Näytä tiiviit tekstit ja viestit",
             "custom_font": "Käytä järjestelmän fonttia",
             "custom_font_description": "Aseta käyttöjärjestelmääsi asennetun fontin nimi, niin %(brand)s pyrkii käyttämään sitä.",
             "custom_font_name": "Järjestelmän fontin nimi",
             "custom_font_size": "Käytä mukautettua kokoa",
-            "custom_theme_error_downloading": "Virhe ladattaessa teematietoa.",
+            "custom_theme_add": "Lisää mukautettu teema",
+            "custom_theme_downloading": "Ladataan mukautettua teemaa...",
+            "custom_theme_error_downloading": "Virhe ladattaessa teemaa",
             "custom_theme_invalid": "Epäkelpo teeman skeema.",
+            "dialog_title": "<strong>Asetukset:</strong> Ulkoasu",
             "font_size": "Fontin koko",
+            "font_size_default": "%(fontSize)s (oletus)",
+            "high_contrast": "Suuri kontrasti",
             "image_size_default": "Oletus",
             "image_size_large": "Suuri",
             "layout_bubbles": "Viestikuplat",
@@ -1988,6 +2187,38 @@
         "code_block_line_numbers": "Näytä rivinumerot koodilohkoissa",
         "emoji_autocomplete": "Näytä emoji-ehdotuksia kirjoittaessa",
         "enable_markdown": "Ota Markdown käyttöön",
+        "enable_markdown_description": "Aloita viestit kirjoittamalla <code>/plain</code> lähettääksesi ilman markdownia.",
+        "encryption": {
+            "advanced": {
+                "details_title": "Salauksen tiedot",
+                "export_keys": "Vie avaimet",
+                "import_keys": "Tuo avaimet",
+                "session_id": "Istunnon tunniste:",
+                "session_key": "Istunnon avain:"
+            },
+            "device_not_verified_button": "Vahvista tämä laite",
+            "device_not_verified_description": "Sinun on vahvistettava tämä laite, jotta voit tarkastella salausasetuksiasi.",
+            "device_not_verified_title": "Laitetta ei ole vahvistettu",
+            "dialog_title": "<strong>Asetukset:</strong> Salaus",
+            "recovery": {
+                "change_recovery_confirm_button": "Vahvista uusi palautusavain",
+                "change_recovery_confirm_title": "Anna uusi palautusavain",
+                "change_recovery_key": "Vaihda palautusavain",
+                "change_recovery_key_title": "Vaihdetaanko palautusavain?",
+                "description": "Palauta kryptografinen identiteettisi ja viestihistoriasi palautusavaimella, jos olet kadottanut kaikki olemassa olevat laitteesi.",
+                "enter_key_error": "Kirjoittamasi palautusavain ei ole oikein.",
+                "enter_recovery_key": "Kirjoita palautusavain",
+                "forgot_recovery_key": "Unohditko palautusavaimen?",
+                "save_key_description": "Älä jaa tätä kenenkään kanssa!",
+                "save_key_title": "Palautusavain",
+                "set_up_recovery": "Määritä palautus",
+                "set_up_recovery_confirm_title": "Anna palautusavain vahvistaaksesi",
+                "set_up_recovery_save_key_title": "Tallenna palautusavain turvalliseen paikkaan",
+                "set_up_recovery_secondary_description": "Kun napsautat Jatka, sinulle luodaan palautusavain.",
+                "title": "Palautuminen"
+            },
+            "title": "Salaus"
+        },
         "general": {
             "account_management_section": "Tilin hallinta",
             "account_section": "Tili",
@@ -1999,6 +2230,13 @@
             "add_msisdn_confirm_sso_button": "Vahvista tämän puhelinnumeron lisääminen todistamalla henkilöllisyytesi kertakirjautumista käyttäen.",
             "add_msisdn_dialog_title": "Lisää puhelinnumero",
             "add_msisdn_instructions": "Tekstiviesti on lähetetty numeroon +%(msisdn)s. Syötä siinä oleva varmistuskoodi.",
+            "allow_spellcheck": "Salli oikeinkirjoituksen tarkistus",
+            "application_language": "Sovelluksen kieli",
+            "application_language_reload_hint": "Sovellus käynnistyy uudelleen, kun valitset toisen kielen",
+            "avatar_remove_progress": "Poistetaan kuva...",
+            "avatar_save_progress": "Lähetetään kuva…",
+            "avatar_upload_error_text": "Tiedostomuoto ei ole tuettu tai kuva on suurempi kuin %(size)s.",
+            "avatar_upload_error_text_generic": "Tiedostomuotoa ei ehkä tueta.",
             "confirm_adding_email_body": "Napsauta alapuolella olevaa painiketta lisätäksesi tämän sähköpostiosoitteen.",
             "confirm_adding_email_title": "Vahvista sähköpostin lisääminen",
             "deactivate_confirm_body": "Haluatko varmasti poistaa tilisi pysyvästi?",
@@ -2017,6 +2255,10 @@
             "discovery_email_verification_instructions": "Varmista sähköpostiisi saapunut linkki",
             "discovery_msisdn_empty": "Etsinnän asetukset näkyvät sen jälkeen, kun olet lisännyt puhelinnumeron.",
             "discovery_needs_terms": "Hyväksy identiteettipalvelimen (%(serverName)s) käyttöehdot, jotta sinut voi löytää sähköpostiosoitteen tai puhelinnumeron perusteella.",
+            "discovery_needs_terms_title": "Anna ihmisten löytää sinut",
+            "display_name": "Näyttönimi",
+            "display_name_error": "Näyttönimeä ei voi asettaa",
+            "email_adding_unsupported_by_hs": "Tämä kotipalvelin ei tue sähköpostiosoitteiden lisäämistä tiliisi.",
             "email_address_in_use": "Tämä sähköpostiosoite on jo käytössä",
             "email_address_label": "Sähköpostiosoite",
             "email_not_verified": "Sähköpostiosoitettasi ei ole vielä varmistettu",
@@ -2031,6 +2273,7 @@
             "error_invalid_email_detail": "Tämä ei vaikuta olevan kelvollinen sähköpostiosoite",
             "error_msisdn_verification": "Puhelinnumeron vahvistaminen epäonnistui.",
             "error_password_change_403": "Salasanan vaihtaminen epäonnistui. Onko salasanasi oikein?",
+            "error_password_change_http": "%(errorMessage)s (HTTP-tila %(httpStatus)s)",
             "error_password_change_title": "Virhe salasanan vaihtamisessa",
             "error_password_change_unknown": "Tuntematon salasananvaihtovirhe (%(stringifiedError)s)",
             "error_remove_3pid": "Yhteystietojen poistaminen epäonnistui",
@@ -2039,7 +2282,9 @@
             "error_share_email_discovery": "Sähköpostiosoitetta ei voi jakaa",
             "error_share_msisdn_discovery": "Puhelinnumeroa ei voi jakaa",
             "identity_server_not_set": "Identiteettipalvelinta ei ole asetettu",
-            "language_section": "Kieli ja alue",
+            "invalid_phone_number": "Annettu puhelinnumero ei vaikuta olevan kelvollinen.",
+            "language_section": "Kieli",
+            "msisdn_adding_unsupported_by_hs": "Tämä kotipalvelin ei tue puhelinnumeroiden lisäämistä tilillesi.",
             "msisdn_in_use": "Puhelinnumero on jo käytössä",
             "msisdn_label": "Puhelinnumero",
             "msisdn_verification_field_label": "Varmennuskoodi",
@@ -2048,11 +2293,15 @@
             "oidc_manage_button": "Hallitse tiliä",
             "password_change_section": "Aseta uusi tilin salasana…",
             "password_change_success": "Salasanasi vaihtaminen onnistui.",
+            "personal_info": "Henkilökohtaiset tiedot",
+            "profile_subtitle": "Näytät tältä muille sovelluksessa.",
             "remove_email_prompt": "Poista %(email)s?",
             "remove_msisdn_prompt": "Poista %(phone)s?",
-            "spell_check_locale_placeholder": "Valitse maa-asetusto"
+            "spell_check_locale_placeholder": "Valitse maa-asetusto",
+            "unable_to_load_emails": "Sähköpostiosoitteita ei voi ladata",
+            "unable_to_load_msisdns": "Puhelinnumeroita ei voi ladata",
+            "username": "Käyttäjätunnus"
         },
-        "image_thumbnails": "Näytä kuvien esikatselut/pienoiskuvat",
         "inline_url_previews_default": "Ota linkkien esikatselu käyttöön oletusarvoisesti",
         "inline_url_previews_room": "Ota linkkien esikatselu käyttöön kaikille huoneen jäsenille",
         "inline_url_previews_room_account": "Ota linkkien esikatselut käyttöön tässä huoneessa (koskee ainoastaan sinua)",
@@ -2060,6 +2309,7 @@
         "jump_to_bottom_on_send": "Siirry aikajanan pohjalle, kun lähetät viestin",
         "key_backup": {
             "backup_in_progress": "Avaimiasi varmuuskopioidaan (ensimmäinen varmuuskopio voi viedä muutaman minuutin).",
+            "backup_starting": "Aloitetaan varmuuskopiointia…",
             "backup_success": "Onnistui!",
             "cannot_create_backup": "Avaimen varmuuskopiota ei voi luoda",
             "create_title": "Luo avaimen varmuuskopio",
@@ -2071,7 +2321,7 @@
                 "enter_phrase_title": "Kirjoita turvalause",
                 "enter_phrase_to_confirm": "Kirjoita turvalause toistamiseen vahvistaaksesi sen.",
                 "generate_security_key_description": "Luomme sinulle turva-vaimen talletettavaksi jonnekin turvalliseen paikkaan, kuten salasanojen hallintasovellukseen tai kassakaappiin.",
-                "generate_security_key_title": "Luo turva-avain",
+                "generate_security_key_title": "Luo palautusavain",
                 "pass_phrase_match_failed": "Ei täsmää.",
                 "pass_phrase_match_success": "Täsmää!",
                 "phrase_strong_enough": "Hienoa! Tämä turvalause vaikuttaa riittävän vahvalta.",
@@ -2080,7 +2330,7 @@
                 "set_phrase_again": "Palaa asettamaan se uudelleen.",
                 "settings_reminder": "Voit myös ottaa käyttöön suojatun varmuuskopioinnin ja hallita avaimia asetuksista.",
                 "title_confirm_phrase": "Vahvista turvalause",
-                "title_save_key": "Tallenna turva-avain",
+                "title_save_key": "Tallenna palautusavain",
                 "title_set_phrase": "Aseta turvalause",
                 "unable_to_setup": "Salavaraston käyttöönotto epäonnistui",
                 "use_different_passphrase": "Käytä eri salalausetta?"
@@ -2096,12 +2346,28 @@
             "import_description_2": "Viety tiedosto suojataan salasanalla. Syötä salasana tähän purkaaksesi tiedoston salauksen.",
             "import_title": "Tuo huoneen avaimet",
             "phrase_cannot_be_empty": "Salasana ei saa olla tyhjä",
-            "phrase_must_match": "Salasanojen on täsmättävä"
+            "phrase_must_match": "Salasanojen on täsmättävä",
+            "phrase_strong_enough": "Hienoa! Tämä tunnuslause näyttää riittävän vahvalta"
         },
         "keyboard": {
+            "dialog_title": "<strong>Asetukset:</strong> Näppäimistö",
             "title": "Näppäimistö"
         },
+        "labs": {
+            "dialog_title": "<strong>Asetukset:</strong> Laboratorio"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Asetukset:</strong> Ohitetut käyttäjät"
+        },
+        "media_preview": {
+            "hide_media": "Piilota aina",
+            "media_preview_description": "Piilotetun median voi aina näyttää napauttamalla sitä",
+            "media_preview_label": "Näytä media aikajanalla",
+            "show_media": "Näytä aina"
+        },
         "notifications": {
+            "desktop_notification_message_preview": "Näytä viestin esikatselu työpöytäilmoituksessa",
+            "dialog_title": "<strong>Asetukset:</strong> Ilmoitukset",
             "enable_audible_notifications_session": "Ota käyttöön ääni-ilmoitukset tälle istunnolle",
             "enable_desktop_notifications_session": "Ota käyttöön työpöytäilmoitukset tälle istunnolle",
             "enable_email_notifications": "Sähköposti-ilmoitukset osoitteeseen %(email)s",
@@ -2114,9 +2380,15 @@
             "error_saving": "Virhe tallentaessa ilmoitusasetuksia",
             "error_saving_detail": "Ilmoitusasetuksia tallentaessa tapahtui virhe.",
             "error_title": "Ilmoitusten käyttöönotto epäonnistui",
+            "mentions_keywords": "Maininnat ja avainsanat",
+            "mentions_keywords_only": "Vain maininnat ja avainsanat",
             "messages_containing_keywords": "Viestit, jotka sisältävät avainsanoja",
             "noisy": "Äänekäs",
+            "people_mentions_keywords": "Ihmiset, maininnat ja avainsanat",
             "push_targets": "Ilmoituksen kohteet",
+            "quick_actions_mark_all_read": "Merkitse kaikki viestit luetuiksi",
+            "quick_actions_reset": "Palauta oletusasetukset",
+            "quick_actions_section": "Pikatoiminnot",
             "rule_call": "Puhelukutsu",
             "rule_contains_display_name": "Viestit, jotka sisältävät näyttönimeni",
             "rule_contains_user_name": "Viestit, jotka sisältävät käyttäjätunnukseni",
@@ -2128,7 +2400,8 @@
             "rule_roomnotif": "Viestit, jotka sisältävät sanan ”@room”",
             "rule_suppress_notices": "Bottien lähettämät viestit",
             "rule_tombstone": "Kun huoneet päivitetään",
-            "show_message_desktop_notification": "Näytä viestit ilmoituskeskuksessa"
+            "show_message_desktop_notification": "Näytä viestit ilmoituskeskuksessa",
+            "voip": "Ääni- ja videopuhelut"
         },
         "preferences": {
             "always_show_menu_bar": "Näytä aina ikkunan valikkorivi",
@@ -2136,61 +2409,38 @@
             "code_blocks_heading": "Koodilohkot",
             "compact_modern": "Käytä entistä kompaktimpaa, \"Modernia\", asettelua",
             "composer_heading": "Viestin kirjoitus",
+            "default_timezone": "Selaimen oletus (%(timezone)s)",
+            "dialog_title": "<strong>Asetukset:</strong> Valinnat",
             "enable_hardware_acceleration": "Ota laitteistokiihdytys käyttöön",
             "enable_tray_icon": "Näytä ilmaisinalueen kuvake ja pienennä ikkuna siihen suljettaessa",
             "keyboard_heading": "Pikanäppäimet",
             "keyboard_view_shortcuts_button": "Katso kaikki pikanäppäimet <a>napsauttamalla tästä</a>.",
             "media_heading": "Kuvat, GIF:t ja videot",
             "presence_description": "Jaa toimintasi ja tilasi muiden kanssa.",
+            "publish_timezone": "Julkaise aikavyöhyke julkisessa profiilissa",
             "rm_lifetime": "Viestin luetuksi merkkaamisen kesto (ms)",
             "rm_lifetime_offscreen": "Viestin luetuksi merkkaamisen kesto, kun Element ei ole näkyvissä (ms)",
             "room_directory_heading": "Huoneluettelo",
             "room_list_heading": "Huoneluettelo",
             "show_polls_button": "Näytä kyselypainike",
-            "time_heading": "Ajan näyttäminen"
+            "time_heading": "Ajan näyttäminen",
+            "user_timezone": "Aseta aikavyöhyke"
         },
         "prompt_invite": "Kysy varmistus ennen kutsujen lähettämistä mahdollisesti epäkelpoihin Matrix ID:hin",
         "replace_plain_emoji": "Korvaa automaattisesti teksimuotoiset emojit",
         "security": {
-            "4s_public_key_in_account_data": "tilin tiedoissa",
-            "4s_public_key_status": "Salavaraston julkinen avain:",
-            "backup_key_cached_status": "Välimuistissa oleva varmuuskopioavain:",
-            "backup_key_stored_status": "Varmuuskopioavain tallennettu:",
-            "backup_key_unexpected_type": "odottamaton tyyppi",
-            "backup_key_well_formed": "hyvin muotoiltu",
-            "backup_keys_description": "Varmuuskopioi salausavaimesi tilisi datan kanssa siltä varalta, että menetät pääsyn istuntoihisi. Avaimesi turvataan yksilöllisellä turva-avaimella.",
+            "analytics_description": "Jaa nimettömiä tietoja auttaaksesi meitä tunnistamaan ongelmia. Ei mitään henkilökohtaista. Ei kolmansia osapuolia.",
             "bulk_options_accept_all_invites": "Hyväksy kaikki %(invitedRooms)s kutsua",
             "bulk_options_reject_all_invites": "Hylkää kaikki %(invitedRooms)s kutsua",
             "bulk_options_section": "Massatoimintoasetukset",
-            "cross_signing_cached": "paikallisessa välimuistissa",
-            "cross_signing_homeserver_support": "Kotipalvelimen ominaisuuksien tuki:",
-            "cross_signing_homeserver_support_exists": "on olemassa",
-            "cross_signing_in_4s": "salavarastossa",
-            "cross_signing_in_memory": "muistissa",
-            "cross_signing_not_cached": "ei paikallisessa välimuistissa",
-            "cross_signing_not_found": "ei löydetty",
-            "cross_signing_not_in_4s": "ei löytynyt muistista",
-            "cross_signing_not_stored": "ei tallennettu",
-            "cross_signing_private_keys": "Ristiinvarmennuksen salaiset avaimet:",
-            "cross_signing_public_keys": "Ristiinvarmennuksen julkiset avaimet:",
-            "cryptography_section": "Salaus",
-            "delete_backup": "Poista varmuuskopio",
-            "delete_backup_confirm_description": "Oletko varma? Et voi lukea salattuja viestejäsi, mikäli avaimesi eivät ole kunnolla varmuuskopioituna.",
+            "dialog_title": "<strong>Asetukset:</strong> Tietoturva ja yksityisyys",
             "e2ee_default_disabled_warning": "Palvelimesi ylläpitäjä on poistanut päästä päähän -salauksen oletuksena käytöstä yksityisissä huoneissa ja yksityisviesteissä.",
             "enable_message_search": "Ota viestihaku salausta käyttävissä huoneissa käyttöön",
             "encryption_section": "Salaus",
-            "error_loading_key_backup_status": "Avainten varmuuskopionnin tilan lukeminen epäonnistui",
-            "export_megolm_keys": "Tallenna osapuolten välisen salauksen huoneavaimet",
             "ignore_users_empty": "Et ole sivuuttanut käyttäjiä.",
             "ignore_users_section": "Sivuutetut käyttäjät",
-            "import_megolm_keys": "Tuo olemassaolevat osapuolten välisen salauksen huoneavaimet",
-            "key_backup_active_version_none": "Ei mitään",
             "key_backup_algorithm": "Algoritmi:",
-            "key_backup_complete": "Kaikki avaimet on varmuuskopioitu",
             "key_backup_connect": "Yhdistä tämä istunto avainten varmuuskopiointiin",
-            "key_backup_connect_prompt": "Yhdistä tämä istunto avainten varmuuskopiointiin ennen uloskirjautumista, jotta et menetä avaimia, jotka ovat vain tässä istunnossa.",
-            "key_backup_inactive": "Tämä istunto <b>ei varmuuskopioi avaimiasi</b>, mutta sillä on olemassaoleva varmuuskopio, jonka voit palauttaa ja lisätä jatkaaksesi.",
-            "key_backup_inactive_warning": "Avaimiasi <b>ei varmuuskopioida tästä istunnosta</b>.",
             "message_search_disable_warning": "Jos ei ole käytössä, salattujen huoneiden viestejä ei näytetä hakutuloksissa.",
             "message_search_disabled": "Pidä salatut viestit turvallisessa välimuistissa, jotta ne näkyvät hakutuloksissa.",
             "message_search_enabled": {
@@ -2209,19 +2459,14 @@
             "message_search_space_used": "Käytetty tila:",
             "message_search_unsupported": "%(brand)sissa ei ole joitain komponentteja, joita tarvitaan viestien turvalliseen välimuistitallennukseen. Jos haluat kokeilla tätä ominaisuutta, käännä mukautettu %(brand)s Desktop, jossa on mukana <nativeLink>hakukomponentit</nativeLink>.",
             "record_session_details": "Talleta asiakasohjelmiston nimi, versio ja URL-osoite tunnistaaksesi istunnot istuntohallinnassa",
-            "restore_key_backup": "Palauta varmuuskopiosta",
-            "secret_storage_not_ready": "ei valmis",
-            "secret_storage_ready": "valmis",
-            "secret_storage_status": "Salainen tallennus:",
             "send_analytics": "Lähetä analytiikkatietoja",
-            "session_id": "Istunnon tunnus:",
-            "session_key": "Istunnon avain:",
             "strict_encryption": "Älä koskaan lähetä salattuja viestejä vahvistamattomiin istuntoihin tästä istunnosta"
         },
         "send_read_receipts": "Lähetä lukukuittaukset",
         "send_read_receipts_unsupported": "Palvelimesi ei tue lukukuittausten lähettämisen poistamista käytöstä.",
         "send_typing_notifications": "Lähetä kirjoitusilmoituksia",
         "sessions": {
+            "best_security_note": "Parhaan turvallisuuden takaamiseksi vahvista istunnot ja kirjaudu ulos istunnoista, joita et tunnista tai et enää käytä.",
             "browser": "Selain",
             "confirm_sign_out": {
                 "one": "Vahvista uloskirjautuminen tältä laitteelta",
@@ -2245,6 +2490,8 @@
             "device_unverified_description": "Vahvista tämä istunto tai kirjaudu ulos siitä tietoturvan ja luotettavuuden parantamiseksi.",
             "device_verified_description": "Tämä istunto on valmis turvallista viestintää varten.",
             "device_verified_description_current": "Nykyinen istuntosi on valmis turvalliseen viestintään.",
+            "dialog_title": "<strong>Asetukset:</strong> Istunnot",
+            "error_set_name": "Istunnon nimen asettaminen epäonnistui",
             "filter_all": "Kaikki",
             "filter_inactive": "Passiivinen",
             "filter_inactive_description": "Passiivinen %(inactiveAgeDays)s päivää tai pidempään",
@@ -2257,6 +2504,7 @@
             "inactive_sessions_list_description": "Harkitse vanhoista (%(inactiveAgeDays)s tai useamman päivän ikäisistä), käyttämättömistä istunnoista uloskirjautumista.",
             "ip": "IP-osoite",
             "last_activity": "Viimeisin toiminta",
+            "manage": "Hallitse tätä istuntoa",
             "mobile_session": "Mobiili-istunto",
             "n_sessions_selected": {
                 "one": "%(count)s istunto valittu",
@@ -2280,8 +2528,10 @@
             "security_recommendations_description": "Paranna tilisi tietoturvaa seuraamalla näitä suosituksia.",
             "session_id": "Istuntotunniste",
             "show_details": "Näytä yksityiskohdat",
-            "sign_in_with_qr": "Kirjaudu sisään QR-koodilla",
+            "sign_in_with_qr": "Yhdistä uusi laite",
             "sign_in_with_qr_button": "Näytä QR-koodi",
+            "sign_in_with_qr_description": "Kirjaudu QR-koodin avulla toiseen laitteeseen ja määritä suojattu viestinvälitys.",
+            "sign_in_with_qr_unsupported": "Palveluntarjoajasi ei tue tätä",
             "sign_out": "Kirjaudu ulos tästä istunnosta",
             "sign_out_all_other_sessions": "Kirjaudu ulos kaikista muista istunnoista (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2306,6 +2556,7 @@
             "verify_session": "Vahvista istunto",
             "web_session": "Web-istunto"
         },
+        "show_avatar_changes": "Näytä profiilikuvan muutokset",
         "show_breadcrumbs": "Näytä oikotiet viimeksi katsottuihin huoneisiin huoneluettelon yläpuolella",
         "show_chat_effects": "Näytä keskustelutehosteet (animaatiot, kun saat esim. konfettia)",
         "show_displayname_changes": "Näytä näyttönimien muutokset",
@@ -2316,6 +2567,7 @@
         "show_stickers_button": "Näytä tarrapainike",
         "show_typing_notifications": "Näytä kirjoitusilmoitukset",
         "sidebar": {
+            "dialog_title": "<strong>Asetukset:</strong> Sivupalkki",
             "metaspaces_favourites_description": "Ryhmitä kaikki suosimasi huoneet ja henkilöt yhteen paikkaan.",
             "metaspaces_home_all_rooms": "Näytä kaikki huoneet",
             "metaspaces_home_all_rooms_description": "Näytä kaikki huoneesi etusivulla, vaikka ne olisivat jossain muussa avaruudessa.",
@@ -2324,6 +2576,9 @@
             "metaspaces_orphans_description": "Ryhmitä kaikki huoneesi, jotka eivät ole osa avaruutta, yhteen paikkaan.",
             "metaspaces_people_description": "Ryhmitä kaikki ihmiset yhteen paikkaan.",
             "metaspaces_subsection": "Näytettävät avaruudet",
+            "metaspaces_video_rooms": "Videohuoneet ja konferenssit",
+            "metaspaces_video_rooms_description": "Ryhmitä kaikki yksityiset videohuoneet ja konferenssit.",
+            "spaces_explainer": "Avaruudet ovat tapa ryhmitellä huoneita ja ihmisiä. Voit käyttää nykyisten avaruuksiesi lisäksi esivalmisteltuja avaruuksia.",
             "title": "Sivupalkki"
         },
         "start_automatically": "Käynnistä automaattisesti käyttöjärjestelmään kirjautumisen jälkeen",
@@ -2340,6 +2595,7 @@
             "audio_output_empty": "Äänen ulostuloja ei havaittu",
             "auto_gain_control": "Automaattinen vahvistuksen säätö",
             "connection_section": "Yhteys",
+            "dialog_title": "<strong>Asetukset:</strong> Ääni ja video",
             "echo_cancellation": "Kaiunpoisto",
             "mirror_local_feed": "Peilaa paikallinen videosyöte",
             "missing_permissions_prompt": "Mediaoikeuksia puuttuu. Napsauta painikkeesta pyytääksesi oikeuksia.",
@@ -2356,8 +2612,10 @@
         "warning": "<w>VAROITUS:</w> <description/>"
     },
     "share": {
+        "link_copied": "Linkki kopioitu",
         "permalink_message": "Linkitä valittuun viestiin",
         "permalink_most_recent": "Linkitä viimeisimpään viestiin",
+        "title_link": "Jaa linkki",
         "title_message": "Jaa huoneviesti",
         "title_room": "Jaa huone",
         "title_user": "Jaa käyttäjä"
@@ -2383,7 +2641,9 @@
         "devtools": "Avaa kehitystyökalujen dialogin",
         "discardsession": "Pakottaa hylkäämään nykyisen ulospäin suuntautuvan ryhmäistunnon salatussa huoneessa",
         "error_invalid_rendering_type": "Komentovirhe: Renderöintityyppiä (%(renderingType)s) ei löydy",
+        "error_invalid_room": "Komento epäonnistui: Huonetta ei löytynyt (%(roomId)s)",
         "error_invalid_runfn": "Määräys virhe: Ei voitu käsitellä / komentoa",
+        "error_invalid_user_in_room": "Käyttäjää ei löytynyt huoneesta",
         "help": "Näyttää luettelon komennoista käyttötavoin ja kuvauksin",
         "help_dialog_title": "Komento-ohje",
         "holdcall": "Asettaa nykyisen huoneen puhelun pitoon",
@@ -2401,6 +2661,8 @@
         "lenny": "Lisää ( ͡° ͜ʖ ͡°) viestin alkuun",
         "me": "Näyttää toiminnan",
         "msg": "Lähettää viestin annetulle käyttäjälle",
+        "myavatar": "Vaihtaa profiilikuvasi kaikissa huoneissa",
+        "myroomavatar": "Vaihtaa profiilikuvasi vain tässä nykyisessä huoneessa",
         "myroomnick": "Vaihtaa näyttönimesi vain nykyisessä huoneessa",
         "nick": "Vaihtaa näyttönimesi",
         "no_active_call": "Huoneessa ei ole aktiivista puhelua",
@@ -2423,8 +2685,6 @@
         "topic": "Hakee tai asettaa huoneen aiheen",
         "topic_none": "Tässä huoneessa ei ole aihetta.",
         "topic_room_error": "Huoneen aiheen hakeminen epäonnistui: huonetta (%(roomId)s ei löydy.",
-        "tovirtual": "Vaihtaa tämän huoneen virtuaalihuoneeseen, mikäli huoneella sellainen on",
-        "tovirtual_not_found": "Tällä huoneella ei ole virtuaalihuonetta",
         "unban": "Poistaa porttikiellon tunnuksen mukaiselta käyttäjältä",
         "unflip": "Lisää ┬──┬ ノ( ゜-゜ノ) viestin alkuun",
         "unholdcall": "Ottaa nykyisen huoneen puhelun pois pidosta",
@@ -2543,14 +2803,14 @@
         "heading_without_query": "Etsittävät kohteet",
         "join_button_text": "Liity %(roomAddress)s",
         "keyboard_scroll_hint": "Käytä <arrows/> vierittääksesi",
-        "message_search_section_title": "Muut haut",
+        "messages_label": "Viestit",
         "other_rooms_in_space": "Muut huoneet avaruudessa %(spaceName)s",
         "public_rooms_label": "Julkiset huoneet",
+        "public_spaces_label": "Julkiset avaruudet",
         "recent_searches_section_title": "Viimeaikaiset haut",
         "recently_viewed_section_title": "Äskettäin katsottu",
         "result_may_be_hidden_privacy_warning": "Jotkin tulokset saatetaan piilottaa tietosuojan takia",
         "result_may_be_hidden_warning": "Jotkin tulokset saatetaan piilottaa",
-        "search_messages_hint": "Etsi viesteistä huoneen yläosassa olevalla kuvakkeella <icon/>",
         "spaces_title": "Avaruudet, joissa olet",
         "start_group_chat_button": "Aloita ryhmäkeskustelu"
     },
@@ -2576,7 +2836,8 @@
         "tos": "Käyttöehdot"
     },
     "theme": {
-        "light_high_contrast": "Vaalea, suuri kontrasti"
+        "light_high_contrast": "Vaalea, suuri kontrasti",
+        "match_system": "Sama kuin järjestelmän"
     },
     "thread_view_back_action_label": "Takaisin ketjuun",
     "threads": {
@@ -2586,6 +2847,7 @@
             "one": "%(count)s vastaus",
             "other": "%(count)s vastausta"
         },
+        "mark_all_read": "Merkitse kaikki luetuiksi",
         "my_threads": "Omat ketjut",
         "my_threads_description": "Näyttää kaikki ketjut, joissa olet ollut osallinen",
         "open_thread": "Avaa ketju",
@@ -2631,8 +2893,15 @@
         },
         "creation_summary_dm": "%(creator)s loi tämän yksityisviestin.",
         "creation_summary_room": "%(creator)s loi ja määritti huoneen.",
+        "decryption_failure": {
+            "historical_event_user_not_joined": "Sinulla ei ole pääsyä tähän viestiin",
+            "unable_to_decrypt": "Viestin salausta ei voi purkaa"
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Puretaan salausta",
         "download_action_downloading": "Ladataan",
+        "download_failed": "Lataus epäonnistui",
+        "download_failed_description": "Tiedostoa ladattaessa tapahtui virhe",
         "edits": {
             "tooltip_label": "Muokattu %(date)s. Napsauta nähdäksesi muokkaukset.",
             "tooltip_sub": "Napsauta nähdäksesi muokkaukset",
@@ -2682,7 +2951,7 @@
         },
         "m.file": {
             "error_decrypting": "Virhe purettaessa liitteen salausta",
-            "error_invalid": "Virheellinen tiedosto%(extra)s"
+            "error_invalid": "Virheellinen tiedosto"
         },
         "m.image": {
             "error": "Kuvan näyttäminen epäonnistui virheen vuoksi",
@@ -2738,6 +3007,7 @@
         "m.room.encryption": {
             "disable_attempt": "Ohitettu yritys poistaa salaus käytöstä",
             "disabled": "Salaus pois käytöstä",
+            "enabled_dm": "Viestit ovat päästä päähän salattuja. Vahvista käyttäjä %(displayName)s hänen  profiilissaan - napauta hänen profiilikuvaa.",
             "enabled_local": "Tässä keskustelussa olevat viestit salataan päästä päähän.",
             "parameters_changed": "Joitakin salausparametreja on muutettu.",
             "unsupported": "Tämän huoneen käyttämää salausta ei tueta."
@@ -2777,6 +3047,7 @@
             "left_reason": "%(targetName)s poistui huoneesta: %(reason)s",
             "no_change": "%(senderName)s ei tehnyt muutosta",
             "reject_invite": "%(targetName)s hylkäsi kutsun",
+            "reject_invite_reason": "%(targetName)s hylkäsi kutsun: %(reason)s",
             "remove_avatar": "%(senderName)s poisti profiilikuvansa",
             "remove_name": "%(senderName)s poisti näyttönimensä (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s asetti profiilikuvan",
@@ -2812,10 +3083,14 @@
             "sent": "%(senderName)s kutsui käyttäjän %(targetDisplayName)s liittymään huoneeseen."
         },
         "m.room.tombstone": "%(senderDisplayName)s päivitti tämän huoneen.",
-        "m.room.topic": "%(senderDisplayName)s vaihtoi aiheeksi \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s vaihtoi aiheeksi \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s poisti aiheen."
+        },
         "m.sticker": "%(senderDisplayName)s lähetti tarran.",
         "m.video": {
-            "error_decrypting": "Virhe purettaessa videon salausta"
+            "error_decrypting": "Virhe purettaessa videon salausta",
+            "show_video": "Näytä video"
         },
         "m.widget": {
             "added": "%(senderName)s lisäsi sovelman %(widgetName)s",
@@ -2834,6 +3109,8 @@
             "label": "Viestitoiminnot",
             "view_in_room": "Näytä huoneessa"
         },
+        "message_timestamp_received_at": "Vastaanotettu: %(dateTime)s",
+        "message_timestamp_sent_at": "Lähetetty: %(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s muutti estosääntöä muodosta %(oldGlob)s muotoon %(newGlob)s. Syy: %(reason)s",
             "changed_rule_rooms": "%(senderName)s muutti sääntöä, joka esti huoneita säännöllä %(oldGlob)s muotoon %(newGlob)s. Syy: %(reason)s",
@@ -3027,6 +3304,10 @@
     "truncated_list_n_more": {
         "other": "Ja %(count)s muuta..."
     },
+    "unsupported_browser": {
+        "title": "%(brand)s ei tue tätä selainta"
+    },
+    "unsupported_server_title": "Palvelimesi ei ole tuettu",
     "update": {
         "changelog": "Muutosloki",
         "check_action": "Tarkista päivitykset",
@@ -3042,6 +3323,9 @@
         "toast_title": "Päivitä %(brand)s",
         "unavailable": "Ei saatavilla"
     },
+    "update_room_access_modal": {
+        "title": "Salli vieraskäyttäjien liittyä tähän huoneeseen"
+    },
     "upload_failed_generic": "Tiedoston '%(fileName)s' lähettäminen ei onnistunut.",
     "upload_failed_size": "Tiedoston '%(fileName)s' koko ylittää tämän kotipalvelimen lähetettyjen tiedostojen ylärajan",
     "upload_failed_title": "Lähetys epäonnistui",
@@ -3051,6 +3335,7 @@
         "error_files_too_large": "Tiedostot ovat <b>liian isoja</b> lähetettäväksi. Tiedoston kokoraja on %(limit)s.",
         "error_some_files_too_large": "Osa tiedostoista on <b>liian isoja</b> lähetettäväksi. Tiedoston kokoraja on %(limit)s.",
         "error_title": "Lähetysvirhe",
+        "not_image": "Valitsemasi tiedosto ei ole kelvollinen kuvatiedosto.",
         "title": "Lähetä tiedostot",
         "title_progress": "Lähettää tiedostoa (%(current)s / %(total)s)",
         "upload_all_button": "Lähetä kaikki palvelimelle",
@@ -3064,14 +3349,6 @@
         "ban_button_room": "Anna porttikielto huoneeseen",
         "ban_room_confirm_title": "Anna porttikielto huoneeseen %(roomName)s",
         "ban_space_everything": "Anna porttikielto kaikkeen, mihin pystyn",
-        "count_of_sessions": {
-            "other": "%(count)s istuntoa",
-            "one": "%(count)s istunto"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s varmennettua istuntoa",
-            "one": "1 varmennettu istunto"
-        },
         "deactivate_confirm_action": "Poista käyttäjä pysyvästi",
         "deactivate_confirm_description": "Käyttäjän poistaminen kirjaa hänet ulos ja estää häntä kirjautumasta takaisin sisään. Lisäksi hän poistuu kaikista huoneista, joissa hän on. Tätä toimintoa ei voi kumota. Oletko varma, että haluat pysyvästi poistaa tämän käyttäjän?",
         "deactivate_confirm_title": "Poista käyttäjä pysyvästi?",
@@ -3079,15 +3356,12 @@
         "demote_self_confirm_description_space": "Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä avaruudessa, oikeuksia ei voi enää saada takaisin.",
         "demote_self_confirm_room": "Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin.",
         "demote_self_confirm_title": "Alenna itsesi?",
-        "edit_own_devices": "Muokkaa laitteita",
         "error_ban_user": "Porttikiellon antaminen epäonnistui",
         "error_deactivate": "Käyttäjän poistaminen epäonnistui",
         "error_kicking_user": "Käyttäjän poistaminen epäonnistui",
         "error_mute_user": "Käyttäjän mykistäminen epäonnistui",
         "error_revoke_3pid_invite_description": "Kutsun kumoaminen epäonnistui. Kyseessä saattaa olla väliaikainen ongelma tai sinulla ei ole tarvittavia oikeuksia kutsun kumoamiseen.",
         "error_revoke_3pid_invite_title": "Kutsun kumoaminen epäonnistui",
-        "hide_sessions": "Piilota istunnot",
-        "hide_verified_sessions": "Piilota varmennetut istunnot",
         "ignore_confirm_description": "Kaikki tämän käyttäjän lähettämät viestit ja kutsut sivuutetaan. Haluatko varmasti sivuuttaa hänet?",
         "ignore_confirm_title": "Sivuuta %(user)s",
         "invited_by": "Kutsuttu henkilön %(sender)s toimesta",
@@ -3112,13 +3386,14 @@
             "no_recent_messages_description": "Kokeile vierittää aikajanaa ylöspäin nähdäksesi, löytyykö aiempia viestejä.",
             "no_recent_messages_title": "Käyttäjän %(user)s kirjoittamia viimeaikaisia viestejä ei löytynyt"
         },
-        "redact_button": "Poista viimeaikaiset viestit",
+        "redact_button": "Poista viestit",
         "revoke_invite": "Kumoa kutsu",
         "room_encrypted": "Tämän huoneen viestit ovat päästä päähän -salattuja.",
         "room_encrypted_detail": "Viestisi ovat turvattu, ja vain sinulla ja vastaanottajalla on avaimet viestien lukemiseen.",
         "room_unencrypted": "Tämän huoneen viestit eivät ole päästä päähän -salattuja.",
         "room_unencrypted_detail": "Salausta käyttävissä huoneissa viestisi on turvattu, ja vain sinulla ja vastaanottajilla on yksityiset avaimet viestien lukemiseen.",
-        "share_button": "Jaa linkki käyttäjään",
+        "send_message": "Lähetä viesti",
+        "share_button": "Jaa profiili",
         "unban_button_room": "Poista porttikielto huoneeseen",
         "unban_room_confirm_title": "Poista porttikielto huoneeseen %(roomName)s",
         "unban_space_everything": "Poista porttikielto kaikesta, mihin pystyn",
@@ -3126,6 +3401,7 @@
         "verify_explainer": "Lisäturvaksi, varmenna tämä käyttäjä tarkistamalla koodin kummankin laitteella."
     },
     "user_menu": {
+        "link_new_device": "Yhdistä uusi laite",
         "settings": "Kaikki asetukset",
         "switch_theme_dark": "Vaihda tummaan teemaan",
         "switch_theme_light": "Vaihda vaaleaan teemaan"
@@ -3149,6 +3425,7 @@
         "camera_disabled": "Kamerasi on pois päältä",
         "camera_enabled": "Kamerasi on edelleen päällä",
         "cannot_call_yourself_description": "Et voi soittaa itsellesi.",
+        "close_lobby": "Sulje aula",
         "connecting": "Yhdistetään",
         "connection_lost": "Yhteys palvelimeen on katkennut",
         "connection_lost_description": "Et voi soittaa puheluja ilman yhteyttä palvelimeen.",
@@ -3161,16 +3438,23 @@
         "disabled_no_perms_start_video_call": "Sinulla ei ole oikeutta aloittaa videopuheluita",
         "disabled_no_perms_start_voice_call": "Sinulla ei ole oikeutta aloittaa äänipuheluita",
         "disabled_ongoing_call": "Käynnissä oleva puhelu",
+        "element_call": "Element-puhelu",
         "enable_camera": "Laita kamera päälle",
         "enable_microphone": "Poista mikrofonin mykistys",
         "expand": "Palaa puheluun",
         "hangup": "Lopeta",
         "hide_sidebar_button": "Piilota sivupalkki",
         "input_devices": "Sisääntulolaitteet",
+        "jitsi_call": "Jitsi-konferenssi",
         "join_button_tooltip_call_full": "Pahoittelut — tämä puhelu on täynnä",
-        "join_button_tooltip_connecting": "Yhdistetään",
+        "maximise_call": "Suurenna puhelu",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferenssit"
+        },
+        "minimise_call": "Pienennä puhelu",
         "misconfigured_server": "Puhelu epäonnistui palvelimen väärien asetusten takia",
         "misconfigured_server_description": "Pyydä kotipalvelimesi (<code>%(homeserverDomain)s</code>) ylläpitäjää asentamaan TURN-palvelin, jotta puhelut toimisivat luotettavasti.",
+        "misconfigured_server_fallback_accept": "Yritä käyttäen palvelinta %(server)s",
         "more_button": "Lisää",
         "msisdn_lookup_failed": "Puhelinnumeroa ei voi hakea",
         "msisdn_lookup_failed_description": "Puhelinnumeron haussa tapahtui virhe",
@@ -3214,6 +3498,7 @@
         "user_is_presenting": "%(sharerName)s esittää",
         "video_call": "Videopuhelu",
         "video_call_started": "Videopuhelu aloitettu",
+        "video_call_using": "Videopuhelu käyttäen:",
         "voice_call": "Äänipuhelu",
         "you_are_presenting": "Esität parhaillaan"
     },
@@ -3301,7 +3586,10 @@
             "remember_selection": "Muista tämä"
         },
         "popout": "Avaa sovelma omassa ikkunassaan",
-        "set_room_layout": "Aseta minun huoneen asettelu kaikille",
+        "set_room_layout": "Aseta asettelu kaikille",
+        "shared_data_avatar": "Profiilikuvasi verkko-osoite",
+        "shared_data_device_id": "Laitteesi tunniste",
+        "shared_data_lang": "Kielesi",
         "shared_data_mxid": "Käyttäjätunnuksesi",
         "shared_data_name": "Näyttönimesi",
         "shared_data_room_id": "Huoneen tunnus",
@@ -3323,6 +3611,7 @@
             "l33t": "Arvattavat vaihdot, kuten ”@” ”a”:n sijaan ei auta paljoakaan",
             "longerKeyboardPattern": "Käytä pidempiä näppäinyhdistelmiä, joissa on enemmän suunnanmuutoksia",
             "noNeed": "Ei tarvetta symboleille, numeroille tai isoille kirjaimille",
+            "pwned": "Jos käytät tätä salasanaa muualla, vaihda se.",
             "recentYears": "Vältä viime vuosia",
             "repeated": "Vältä toistettuja sanoja ja merkkejä",
             "reverseWords": "Takaperin kirjoitetut sanat eivät ole paljoakaan vaikeampia arvata",
@@ -3336,6 +3625,7 @@
             "extendedRepeat": "Toistot, kuten ”abcabcabe” ovat vain hieman hankalampia arvata kuin ”abc”",
             "keyPattern": "Lyhyet näppäinsarjat ovat helppoja arvata",
             "namesByThemselves": "Nimet ja sukunimet yksinään ovat helppoja arvata",
+            "pwned": "Salasanasi paljastui Internetissä tapahtuneen tietovuodon seurauksena.",
             "recentYears": "Viime vuodet ovat helppoja arvata",
             "sequences": "Sarjat, kuten ”abc” tai ”6543” ovat helppoja arvata",
             "similarToCommon": "Tämä on samankaltainen kuin yleisesti käytetty salasana",
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 1614ec3c562c757df250f7bd9e432820d18afc33..9992d5e51f24b442199ddaeb03580bb9df208ff9 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -12,6 +12,16 @@
             "one": "1 mention non lue."
         },
         "recent_rooms": "Salons récents",
+        "room_messsage_not_sent": "Ouvrir le salon %(roomName)s avec un message non envoyé.",
+        "room_n_unread_invite": "Ouvrir l'invitation du salon %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Ouvrir salon %(roomName)s avec 1 message non lu.",
+            "other": "Ouvrir salon %(roomName)s avec %(count)s messages non lus."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Ouvrir salon %(roomName)s avec 1 mention non lue.",
+            "other": "Ouvrir salon %(roomName)s avec %(count)s messages non lus comprenant des mentions."
+        },
         "room_name": "Salon %(name)s",
         "room_status_bar": "Barre de statut du salon",
         "seek_bar_label": "Barre de recherche audio",
@@ -45,6 +55,8 @@
         "create_a_room": "Créer un salon",
         "create_account": "Créer un compte",
         "decline": "Refuser",
+        "decline_and_block": "Refuser et bloquer",
+        "decline_invite": "Refuser l’invitation",
         "delete": "Supprimer",
         "deny": "Interdire",
         "disable": "Désactiver",
@@ -64,6 +76,7 @@
         "go": "C’est parti",
         "go_back": "Revenir en arrière",
         "got_it": "Compris",
+        "hide": "Masquer",
         "hide_advanced": "Masquer les paramètres avancés",
         "hold": "Mettre en pause",
         "ignore": "Ignorer",
@@ -80,12 +93,14 @@
         "maximise": "Maximiser",
         "mention": "Mentionner",
         "minimise": "Minimiser",
+        "new_message": "Nouveau message",
         "new_room": "Nouveau salon",
         "new_video_room": "Nouveau salon visio",
         "next": "Suivant",
         "no": "Non",
         "ok": "OK",
         "open": "Ouvrir",
+        "open_menu": "Ouvrir le menu",
         "pause": "Pause",
         "pin": "Épingler",
         "play": "Lecture",
@@ -94,13 +109,13 @@
         "react": "Réagir",
         "refresh": "Rafraîchir",
         "register": "S’inscrire",
-        "reject": "Rejeter",
         "reload": "Recharger",
         "remove": "Supprimer",
         "rename": "Renommer",
         "reply": "Répondre",
         "reply_in_thread": "Répondre dans le fil de discussion",
         "report_content": "Signaler le contenu",
+        "report_room": "Signaler le salon",
         "resend": "Renvoyer",
         "reset": "Réinitialiser",
         "resume": "Reprendre",
@@ -142,6 +157,7 @@
         "view_message": "Afficher le message",
         "view_source": "Voir la source",
         "yes": "Oui",
+        "yes_dismiss": "Oui, rejeter",
         "zoom_in": "Zoomer",
         "zoom_out": "Dé-zoomer"
     },
@@ -200,7 +216,7 @@
         "email_help_text": "Ajouter une adresse e-mail pour pouvoir réinitialiser votre mot de passe.",
         "email_phone_discovery_text": "Utiliser une adresse e-mail ou un numéro de téléphone pour pouvoir être découvert par des contacts existants.",
         "enter_email_explainer": "<b>%(homeserver)s</b> va vous envoyer un lien de vérification vous permettant de réinitialiser votre mot de passe.",
-        "enter_email_heading": "Entrez votre adresse e-mail pour réinitialiser le mot de passe",
+        "enter_email_heading": "Saisir votre adresse e-mail pour réinitialiser le mot de passe",
         "failed_connect_identity_server": "Impossible de joindre le serveur d’identité",
         "failed_connect_identity_server_other": "Vous pouvez vous connecter, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.",
         "failed_connect_identity_server_register": "Vous pouvez vous inscrire, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.",
@@ -248,7 +264,7 @@
         "phone_optional_label": "Téléphone (facultatif)",
         "qr_code_login": {
             "check_code_explainer": "Cela vérifiera que la connexion à votre autre appareil est sécurisée.",
-            "check_code_heading": "Entrez le numéro affiché sur votre autre appareil",
+            "check_code_heading": "Saisir le numéro affiché sur votre autre appareil",
             "check_code_input_label": "code à 2 chiffres",
             "check_code_mismatch": "Les chiffres ne correspondent pas",
             "completing_setup": "Fin de la configuration de votre nouvel appareil",
@@ -371,6 +387,7 @@
             "fallback_button": "Commencer l’authentification",
             "mas_cross_signing_reset_cta": "Accédez à votre compte",
             "mas_cross_signing_reset_description": "Réinitialisez votre identité par l’intermédiaire de votre fournisseur de compte, puis revenez et cliquez sur « Réessayer ».",
+            "mas_cross_signing_reset_title": "Accédez à votre compte pour réinitialiser votre identité",
             "msisdn": "Un message a été envoyé à %(msisdn)s",
             "msisdn_token_incorrect": "Jeton incorrect",
             "msisdn_token_prompt": "Merci de saisir le code qu’il contient :",
@@ -405,7 +422,15 @@
         "download_logs": "Télécharger les journaux",
         "downloading_logs": "Téléchargement des journaux",
         "error_empty": "Dites-nous ce qui s’est mal passé ou, encore mieux, créez un rapport d’erreur sur GitHub qui décrit le problème.",
-        "failed_send_logs": "Échec lors de l’envoi des journaux : ",
+        "failed_download_logs": "Impossible de télécharger les journaux de débogage : ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Votre rapport de bogue a été rejeté. Le serveur Rageshake ne prend pas en charge cette application.",
+            "rejected_generic": "Votre rapport de bogue a été rejeté. Le serveur rageshake a rejeté le contenu du rapport en raison de règles internes.",
+            "rejected_recovery_key": "Votre rapport de bogue a été rejeté pour des raisons de sécurité, car il contenait une clé de récupération.",
+            "rejected_version": "Votre rapport de bogue a été rejeté car la version que vous utilisez est trop ancienne.",
+            "server_unknown_error": "Le serveur rageshake a rencontré une erreur inconnue et n'a pas pu gérer le rapport.",
+            "unknown_error": "Impossible d'envoyer les journaux de débogages."
+        },
         "github_issue": "Rapport GitHub",
         "introduction": "Si vous avez soumis une anomalie via GitHub, les journaux de débogage peuvent nous aider à cibler le problème. ",
         "log_request": "Pour nous aider à éviter cela dans le futur, veuillez <a>nous envoyer les journaux</a>.",
@@ -445,7 +470,7 @@
         "access_token": "Jeton d’accès",
         "accessibility": "Accessibilité",
         "advanced": "Avancé",
-        "all_rooms": "Tous les salons",
+        "all_chats": "Toutes les discussions",
         "analytics": "Collecte de données",
         "and_n_others": {
             "other": "et %(count)s autres…",
@@ -464,7 +489,6 @@
         "capabilities": "Capacités",
         "copied": "Copié !",
         "credits": "Crédits",
-        "cross_signing": "Signature croisée",
         "dark": "Sombre",
         "description": "Description",
         "deselect_all": "Tout désélectionner",
@@ -495,7 +519,6 @@
         "legal": "Légal",
         "light": "Clair",
         "loading": "Chargement…",
-        "lobby": "Salle d'attente",
         "location": "Position",
         "low_priority": "Priorité basse",
         "matrix": "Matrix",
@@ -504,6 +527,7 @@
         "message_timestamp_invalid": "Horodatage non valide",
         "microphone": "Micro",
         "model": "Modèle",
+        "moderation_and_safety": "Modération et sécurité",
         "modern": "Moderne",
         "mute": "Mettre en sourdine",
         "n_members": {
@@ -539,6 +563,7 @@
         "qr_code": "QR code",
         "random": "Aléatoire",
         "reactions": "Réactions",
+        "recommended": "Recommandé",
         "report_a_bug": "Signaler un bug",
         "room": "Salon",
         "room_name": "Nom du salon",
@@ -547,7 +572,6 @@
         "saved": "Sauvegardé",
         "saving": "Enregistrement…",
         "secure_backup": "Sauvegarde sécurisée",
-        "security": "Sécurité",
         "select_all": "Tout sélectionner",
         "server": "Serveur",
         "settings": "Paramètres",
@@ -566,7 +590,6 @@
         "thread": "Discussion",
         "threads": "Fils de discussion",
         "timeline": "Fil de discussion",
-        "trusted": "Fiable",
         "unavailable": "indisponible",
         "unencrypted": "Non chiffré",
         "unmute": "Activer le son",
@@ -726,6 +749,13 @@
         "twemoji": "L’art émoji <twemoji>Twemoji</twemoji> est © <author>Twitter, Inc et autres contributeurs</author> utilisé sous licence <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "La police <colr>twemoji-colr</colr> est © <author>Mozilla Foundation</author> utilisée sous licence <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Êtes-vous sûr de vouloir refuser l'invitation à rejoindre \"%(roomName)s\"  ?",
+        "ignore_user_help": "Vous ne verrez aucun message ni aucune invitation à un salon de la part de cet utilisateur.",
+        "reason_description": "Décrivez la raison pour laquelle vous avez signalé le salon.",
+        "report_room_description": "Signalez ce salon à votre fournisseur de compte.",
+        "title": "Refuser l'invitation"
+    },
     "desktop_default_device_name": "%(brand)s pour bureau : %(platformName)s",
     "devtools": {
         "active_widgets": "Widgets actifs",
@@ -733,6 +763,44 @@
         "category_room": "Salon",
         "caution_colon": "Attention :",
         "client_versions": "Versions des clients",
+        "crypto": {
+            "4s_public_key_in_account_data": "dans les données du compte",
+            "4s_public_key_not_in_account_data": "non trouvé",
+            "4s_public_key_status": "Clé publique du coffre secret :",
+            "backup_key_cached": "mise en cache localement",
+            "backup_key_cached_status": "Clé de sauvegarde mise en cache :",
+            "backup_key_not_stored": "non sauvegardé",
+            "backup_key_stored": "dans le coffre secret",
+            "backup_key_stored_status": "Clé de sauvegarde enregistrée :",
+            "backup_key_unexpected_type": "type inattendu",
+            "backup_key_well_formed": "bien formée",
+            "cross_signing": "Signature croisée",
+            "cross_signing_cached": "mise en cache localement",
+            "cross_signing_not_ready": "La signature croisée n’est pas configurée.",
+            "cross_signing_private_keys_in_storage": "dans le coffre secret",
+            "cross_signing_private_keys_in_storage_status": "Clés privées de signature croisée :",
+            "cross_signing_private_keys_not_in_storage": "non trouvé dans le coffre",
+            "cross_signing_public_keys_on_device": "en mémoire",
+            "cross_signing_public_keys_on_device_status": "Clés publiques de signature croisée :",
+            "cross_signing_ready": "La signature croisée est prête à être utilisée.",
+            "cross_signing_status": "État de la signature croisée :",
+            "cross_signing_untrusted": "Votre compte a une identité de signature croisée dans le coffre secret, mais cette session ne lui fait pas encore confiance.",
+            "crypto_not_available": "Le module cryptographique n'est pas disponible",
+            "key_backup_active_version": "Version de sauvegarde active :",
+            "key_backup_active_version_none": "Aucun",
+            "key_backup_inactive_warning": "Vos clés ne sont pas sauvegardées sur cette session.",
+            "key_backup_latest_version": "Dernière version de la sauvegarde sur le serveur :",
+            "key_storage": "Stockage des clés",
+            "master_private_key_cached_status": "Clé privée maîtresse :",
+            "not_found": "non trouvé",
+            "not_found_locally": "non trouvée localement",
+            "secret_storage_not_ready": "pas prêt",
+            "secret_storage_ready": "prêt",
+            "secret_storage_status": "Coffre secret :",
+            "self_signing_private_key_cached_status": "Clé privée d’auto-signature :",
+            "title": "Chiffrement de bout en bout",
+            "user_signing_private_key_cached_status": "Clé privée de signature de l’utilisateur :"
+        },
         "developer_mode": "Mode développeur",
         "developer_tools": "Outils de développement",
         "edit_setting": "Modifier le paramètre",
@@ -788,6 +856,9 @@
         "setting_colon": "Paramètre :",
         "setting_definition": "Définition du paramètre :",
         "setting_id": "Identifiant de paramètre",
+        "settings": {
+            "elementCallUrl": "Lien d'Element Call"
+        },
         "settings_explorer": "Explorateur de paramètres",
         "show_hidden_events": "Afficher les évènements cachés dans le fil de discussion",
         "spaces": {
@@ -840,52 +911,35 @@
     "empty_room_was_name": "Salon vide (précédemment %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Saisissez votre phrase de sécurité ou <button>utilisez votre clé de sécurité</button> pour continuer.",
             "key_validation_text": {
-                "invalid_security_key": "Clé de Sécurité invalide",
-                "recovery_key_is_correct": "Ça a l’air correct !",
-                "wrong_file_type": "Mauvais type de fichier",
-                "wrong_security_key": "Mauvaise Clé de Sécurité"
-            },
-            "reset_title": "Tout réinitialiser",
-            "reset_warning_1": "Poursuivez seulement si vous n’avez aucun autre appareil avec lequel procéder à la vérification.",
-            "reset_warning_2": "Si vous réinitialisez tout, vous allez repartir sans session et utilisateur de confiance. Vous pourriez ne pas voir certains messages passés.",
+                "wrong_security_key": "Clé de récupération incorrecte"
+            },
             "restoring": "Restauration des clés depuis la sauvegarde",
-            "security_key_title": "Clé de sécurité",
-            "security_phrase_incorrect_error": "Impossible d’accéder à l’espace de stockage sécurisé. Merci de vérifier que vous avez saisi la bonne phrase secrète.",
-            "security_phrase_title": "Phrase de sécurité",
-            "separator": "%(securityKey)s ou %(recoveryFile)s",
-            "use_security_key_prompt": "Utilisez votre clé de sécurité pour continuer."
+            "security_key_title": "Clé de récupération"
         },
         "bootstrap_title": "Configuration des clés",
         "cancel_entering_passphrase_description": "Souhaitez-vous vraiment annuler la saisie de la phrase de passe ?",
         "cancel_entering_passphrase_title": "Annuler la saisie du mot de passe ?",
         "confirm_encryption_setup_body": "Cliquez sur le bouton ci-dessous pour confirmer la configuration du chiffrement.",
         "confirm_encryption_setup_title": "Confirmer la configuration du chiffrement",
-        "cross_signing_not_ready": "La signature croisée n’est pas configurée.",
-        "cross_signing_ready": "La signature croisée est prête à être utilisée.",
-        "cross_signing_ready_no_backup": "La signature croisée est prête mais les clés ne sont pas sauvegardées.",
         "cross_signing_room_normal": "Ce salon est chiffré de bout en bout",
         "cross_signing_room_verified": "Tout le monde dans ce salon est vérifié",
         "cross_signing_room_warning": "Quelqu’un utilise une session inconnue",
-        "cross_signing_unsupported": "Votre serveur d’accueil ne prend pas en charge la signature croisée.",
-        "cross_signing_untrusted": "Votre compte a une identité de signature croisée dans le coffre secret, mais cette session ne lui fait pas encore confiance.",
         "cross_signing_user_normal": "Vous n’avez pas vérifié cet utilisateur.",
         "cross_signing_user_verified": "Vous avez vérifié cet utilisateur. Cet utilisateur a vérifié toutes ses sessions.",
         "cross_signing_user_warning": "Cet utilisateur n’a pas vérifié toutes ses sessions.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Vider les clés de signature croisée",
-            "title": "Détruire les clés de signature croisée ?",
-            "warning": "La suppression des clés de signature croisée est permanente. Tous ceux que vous avez vérifié vont voir des alertes de sécurité. Il est peu probable que ce soit ce que vous voulez faire, sauf si vous avez perdu tous les appareils vous permettant d’effectuer une signature croisée."
-        },
+        "enter_recovery_key": "Saisir la clé de récupération",
         "event_shield_reason_authenticity_not_guaranteed": "L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil.",
         "event_shield_reason_mismatched_sender_key": "Chiffré par une session non vérifiée",
         "event_shield_reason_unknown_device": "Chiffré par un appareil inconnu ou supprimé.",
         "event_shield_reason_unsigned_device": "Chiffré par un appareil non vérifié par son propriétaire.",
         "event_shield_reason_unverified_identity": "Chiffré par un utilisateur non vérifié.",
         "export_unsupported": "Votre navigateur ne prend pas en charge les extensions cryptographiques nécessaires",
+        "forgot_recovery_key": "Clé de récupération oubliée ?",
         "import_invalid_keyfile": "Fichier de clé %(brand)s non valide",
         "import_invalid_passphrase": "Erreur d’authentification : mot de passe incorrect ?",
+        "key_storage_out_of_sync": "Le stockage de vos clés n'est pas synchronisé.",
+        "key_storage_out_of_sync_description": "Confirmez votre clé de récupération pour conserver l’accès à votre stockage de clés et à l’historique des messages.",
         "messages_not_secure": {
             "cause_1": "Votre serveur d’accueil",
             "cause_2": "Le serveur d’accueil auquel l’utilisateur que vous vérifiez est connecté",
@@ -895,27 +949,30 @@
             "title": "Vos messages ne sont pas sécurisés"
         },
         "new_recovery_method_detected": {
-            "description_1": "Une nouvelle phrase secrète et clé de sécurité pour les messages sécurisés ont été détectées.",
+            "description_1": "Une nouvelle phrase et clé de sécurité pour les messages sécurisés ont été détectées.",
             "description_2": "Cette session chiffre l’historique en utilisant la nouvelle méthode de récupération.",
             "title": "Nouvelle méthode de récupération",
             "warning": "Si vous n’avez pas activé de nouvelle méthode de récupération, un attaquant essaye peut-être d’accéder à votre compte. Changez immédiatement le mot de passe de votre compte et configurez une nouvelle méthode de récupération dans les paramètres."
         },
-        "not_supported": "<non pris en charge>",
         "pinned_identity_changed": "L'identité de %(displayName)s (<b>%(userId)s</b>) semble avoir changé. <a>En savoir plus</a>",
         "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>semble avoir changé d'identité. <a>En savoir plus</a>",
         "recovery_method_removed": {
-            "description_1": "Cette session a détecté que votre phrase secrète et clé de sécurité pour les messages sécurisés ont été supprimées.",
+            "description_1": "Cette session a détecté que votre phrase de sécurité et clé de sécurité pour les messages sécurisés ont été supprimées.",
             "description_2": "Si vous l’avez fait accidentellement, vous pouvez configurer les messages sécurisés sur cette session ce qui re-chiffrera l’historique des messages de cette session avec une nouvelle méthode de récupération.",
             "title": "Méthode de récupération supprimée",
             "warning": "Si vous n’avez pas supprimé la méthode de récupération, un attaquant peut être en train d’essayer d’accéder à votre compte. Modifiez le mot de passe de votre compte et configurez une nouvelle méthode de récupération dans les réglages."
         },
         "reset_all_button": "Vous avez perdu ou oublié tous vos moyens de récupération ? <a>Tout réinitialiser</a>",
+        "set_up_recovery": "Configurer la récupération",
+        "set_up_recovery_later": "Pas maintenant",
+        "set_up_recovery_toast_description": "Générez une clé de récupération qui peut être utilisée pour restaurer l'historique de vos messages chiffrés au cas où vous perdriez l'accès à vos appareils.",
         "set_up_toast_description": "Sécurité contre la perte d’accès aux messages et données chiffrées",
         "set_up_toast_title": "Configurer la sauvegarde sécurisée",
         "setup_secure_backup": {
-            "explainer": "Sauvegardez vos clés avant de vous déconnecter pour éviter de les perdre.",
-            "title": "Configurer"
+            "explainer": "Sauvegardez vos clés avant de vous déconnecter pour éviter de les perdre."
         },
+        "turn_on_key_storage": "Activer le stockage des clés",
+        "turn_on_key_storage_description": "Stockez votre identité cryptographique et vos clés de message en toute sécurité sur le serveur. Cela vous permettra de consulter l'historique de vos messages sur tous les nouveaux appareils.",
         "udd": {
             "interactive_verification_button": "Vérifier de façon interactive avec des émojis",
             "other_ask_verify_text": "Demandez à cet utilisateur de vérifier sa session, ou vérifiez-la manuellement ci-dessous.",
@@ -925,12 +982,10 @@
             "title": "Non fiable"
         },
         "unable_to_setup_keys_error": "Impossible de configurer les clés",
-        "unsupported": "Ce client ne prend pas en charge le chiffrement de bout en bout.",
         "verification": {
             "accepting": "Acceptation…",
             "after_new_login": {
                 "device_verified": "Appareil vérifié",
-                "reset_confirmation": "Réinitialiser les clés de vérification, c’est certain ?",
                 "skip_verification": "Ignorer la vérification pour l’instant",
                 "unable_to_verify": "Impossible de vérifier cet appareil",
                 "verify_this_device": "Vérifier cet appareil"
@@ -952,7 +1007,7 @@
             "incoming_sas_dialog_waiting": "Attente de la confirmation du partenaire…",
             "incoming_sas_user_dialog_text_1": "Vérifier cet utilisateur pour le marquer comme fiable. Faire confiance aux utilisateurs vous permet d’être tranquille lorsque vous utilisez des messages chiffrés de bout en bout.",
             "incoming_sas_user_dialog_text_2": "Vérifier cet utilisateur marquera sa session comme fiable, et marquera aussi votre session comme fiable pour lui.",
-            "no_key_or_device": "Il semblerait que vous n’avez pas de clé de sécurité ou d’autres appareils pour faire la vérification. Cet appareil ne pourra pas accéder aux anciens messages chiffrés. Afin de vérifier votre identité sur cet appareil, vous devrez réinitialiser vos clés de vérifications.",
+            "no_key_or_device": "Il semblerait que vous ne disposiez pas de clé de récupération ou d’autres appareils pour réalisation la vérification. Cet appareil ne pourra pas accéder aux anciens messages chiffrés. Afin de vérifier votre identité sur cet appareil, vous devrez réinitialiser vos clés de vérifications.",
             "no_support_qr_emoji": "L’appareil que vous essayez de vérifier ne prend pas en charge les QR codes ou la vérification d’émojis, qui sont les méthodes prises en charge par %(brand)s. Essayez avec un autre client.",
             "other_party_cancelled": "L’autre personne a annulé la vérification.",
             "prompt_encrypted": "Vérifiez tous les utilisateurs d’un salon pour vous assurer qu’il est sécurisé.",
@@ -1001,19 +1056,20 @@
             "verify_emoji_prompt": "Vérifier en comparant des émojis uniques.",
             "verify_emoji_prompt_qr": "Si vous ne pouvez pas scanner le code ci-dessus, vérifiez en comparant des émojis uniques.",
             "verify_later": "Je ferai la vérification plus tard",
-            "verify_reset_warning_1": "La réinitialisation de vos clés de vérification ne peut pas être annulé. Après la réinitialisation, vous n’aurez plus accès à vos anciens messages chiffrés, et tous les amis que vous aviez précédemment vérifiés verront des avertissement de sécurité jusqu'à ce vous les vérifiiez à nouveau.",
-            "verify_reset_warning_2": "Veuillez ne continuer que si vous êtes certain d’avoir perdu tous vos autres appareils et votre Clé de Sécurité.",
             "verify_using_device": "Vérifier avec un autre appareil",
-            "verify_using_key": "Vérifier avec une clé de sécurité",
-            "verify_using_key_or_phrase": "Vérifier avec une clé de sécurité ou une phrase",
+            "verify_using_key": "Vérifier avec la clé de récupération",
+            "verify_using_key_or_phrase": "Vérifier avec une clé de récupération ou une phrase",
             "waiting_for_user_accept": "En attente d’acceptation par %(displayName)s…",
             "waiting_other_device": "En attente de votre vérification sur votre autre appareil…",
             "waiting_other_device_details": "En attente de votre vérification sur votre autre appareil, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "En attente de la vérification de %(displayName)s…"
         },
         "verification_requested_toast_title": "Vérification requise",
+        "verified_identity_changed": "L'identité de %(displayName)s (<b>%(userId)s</b>) a été réinitialisée. <a>En savoir plus</a>",
+        "verified_identity_changed_no_displayname": "L'identité de <b>%(userId)s</b> a été réinitialisée. <a>En savoir plus </a>",
         "verify_toast_description": "D’autres utilisateurs pourraient ne pas lui faire confiance",
-        "verify_toast_title": "Vérifier cette session"
+        "verify_toast_title": "Vérifier cette session",
+        "withdraw_verification_action": "Révoquer la vérification"
     },
     "error": {
         "admin_contact": "Veuillez <a>contacter l’administrateur de votre service</a> pour continuer à l’utiliser.",
@@ -1068,11 +1124,7 @@
             "title": "Impossible de copier le lien du salon"
         },
         "error_loading_user_profile": "Impossible de charger le profil de l’utilisateur",
-        "forget_room_failed": "Échec de l’oubli du salon %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Le serveur semble être inaccessible, surchargé ou la recherche a expiré :(",
-            "title": "Échec de la recherche"
-        }
+        "forget_room_failed": "Échec de l’oubli du salon %(errCode)s"
     },
     "error_user_not_logged_in": "L’utilisateur n’est pas identifié",
     "event_preview": {
@@ -1115,7 +1167,7 @@
         "creating_output": "Création du résultat…",
         "creator_summary": "%(creatorName)s a créé ce salon.",
         "current_timeline": "Historique actuel",
-        "enter_number_between_min_max": "Entrez un nombre entre %(min)s et %(max)s",
+        "enter_number_between_min_max": "Saisir un nombre entre %(min)s et %(max)s",
         "error_fetching_file": "Erreur lors de la récupération du fichier",
         "export_info": "C’est le début de l’export de <roomName/>. Exporté par <exporterDetails/> le %(exportDate)s.",
         "export_successful": "Export réussi !",
@@ -1197,6 +1249,7 @@
         "change": "Changer le serveur d’identité",
         "change_prompt": "Se déconnecter du serveur d’identité <current /> et se connecter à <new /> à la place ?",
         "change_server_prompt": "Si vous ne voulez pas utiliser <server /> pour découvrir et être découvrable par les contacts que vous connaissez, saisissez un autre serveur d’identité ci-dessous.",
+        "changed": "Votre serveur d’identité a été changé",
         "checking": "Vérification du serveur",
         "description_connected": "Vous utilisez actuellement <server></server> pour découvrir et être découvert par des contacts existants que vous connaissez. Vous pouvez changer votre serveur d’identité ci-dessous.",
         "description_disconnected": "Vous n’utilisez actuellement aucun serveur d’identité. Pour découvrir et être découvert par les contacts existants que vous connaissez, ajoutez-en un ci-dessous.",
@@ -1240,7 +1293,8 @@
         "use_desktop_heading": "Utilisez plutôt %(brand)s Desktop",
         "use_mobile_heading": "Utilisez plutôt %(brand)s sur votre appareil mobile",
         "use_mobile_heading_after_desktop": "Ou utilisez notre application mobile",
-        "windows": "Windows (%(bits)s-bit)"
+        "windows_64bit": "Windows (64 bits)",
+        "windows_arm_64bit": "Windows (ARM 64 bits)"
     },
     "info_tooltip_title": "Informations",
     "integration_manager": {
@@ -1249,6 +1303,7 @@
         "error_connecting_heading": "Impossible de se connecter au gestionnaire d’intégrations",
         "explainer": "Les gestionnaires d’intégrations reçoivent les données de configuration et peuvent modifier les widgets, envoyer des invitations aux salons et définir les rangs à votre place.",
         "manage_title": "Gérer les intégrations",
+        "toggle_label": "Activer le gestionnaire d'intégration",
         "use_im": "Utilisez un gestionnaire d’intégrations pour gérer les robots, les widgets et les jeux d’autocollants.",
         "use_im_default": "Utilisez un gestionnaire d’intégrations <b>(%(serverName)s)</b> pour gérer les robots, les widgets et les jeux d’autocollants."
     },
@@ -1450,6 +1505,7 @@
         "location_share_live_description": "Implémentation temporaire. Les positions sont persistantes dans l’historique du salon.",
         "mjolnir": "Nouvelles manières d’ignorer des gens",
         "msc3531_hide_messages_pending_moderation": "Permettre aux modérateurs de cacher des messages en attente de modération.",
+        "new_room_list": "Activer la nouvelle liste de salons",
         "notification_settings": "Nouveaux paramètres de notification",
         "notification_settings_beta_caption": "Introduit une manière plus simple de changer vos préférences de notifications. Customisez %(brand)s, comme ça vous convient.",
         "notification_settings_beta_title": "Paramètres de notification",
@@ -1573,8 +1629,14 @@
         "toggle_attribution": "Changer l’attribution"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s Membre",
+            "other": "%(count)s Membres"
+        },
         "filter_placeholder": "Filtrer les membres du salon",
         "invite_button_no_perms_tooltip": "Vous n’avez pas la permission d’inviter des utilisateurs",
+        "invited_label": "Invité",
+        "no_matches": "Aucune correspondance",
         "power_label": "%(userName)s (rang %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Membres du salon",
@@ -1597,6 +1659,7 @@
         "class_global": "Global",
         "class_other": "Autre",
         "default": "Par défaut",
+        "default_settings": "Correspondre aux paramètres par défaut",
         "email_pusher_app_display_name": "Notifications par courriel",
         "enable_prompt_toast_description": "Activer les notifications sur le bureau",
         "enable_prompt_toast_title": "Notifications",
@@ -1615,7 +1678,8 @@
         "mentions_and_keywords_description": "Recevoir des notifications uniquement pour les mentions et mot-clés comme défini dans vos <a>paramètres</a>",
         "mentions_keywords": "Mentions et mots-clés",
         "message_didnt_send": "Le message n’a pas été envoyé. Cliquer pour plus d’info.",
-        "mute_description": "Vous n’aurez aucune notification"
+        "mute_description": "Vous n’aurez aucune notification",
+        "mute_room": "Rendre le salon muet"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s demande une vérification"
@@ -1717,11 +1781,6 @@
         "ongoing": "Suppression…",
         "reason_label": "Raison (optionnelle)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Voulez-vous vraiment rejeter l’invitation ?",
-        "failed": "Échec du rejet de l’invitation",
-        "title": "Rejeter l’invitation"
-    },
     "report_content": {
         "description": "Le signalement de ce message enverra son « event ID » unique à l’administrateur de votre serveur d’accueil. Si les messages dans ce salon sont chiffrés, l’administrateur ne pourra pas lire le texte du message ou voir les fichiers ou les images.",
         "disagree": "Désaccord",
@@ -1744,27 +1803,31 @@
         "spam_or_propaganda": "Publicité ou propagande",
         "toxic_behaviour": "Comportement toxique"
     },
+    "report_room": {
+        "description": "Signalez ce salon à votre fournisseur de compte. Si les messages sont chiffrés, votre administrateur ne pourra pas les lire.",
+        "reason_label": "Décrivez la raison"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Le déchiffrement de %(failedCount)s sessions a échoué !",
         "count_of_successfully_restored_keys": "%(sessionCount)s clés ont été restaurées avec succès",
-        "enter_key_description": "Accédez à votre historique de messages chiffrés et mettez en place la messagerie sécurisée en entrant votre clé de sécurité.",
-        "enter_key_title": "Saisir la clé de sécurité",
-        "enter_phrase_description": "Accédez à votre historique de messages chiffrés et mettez en place la messagerie sécurisée en entrant votre phrase secrète.",
-        "enter_phrase_title": "Saisir la phrase de secrète",
-        "incorrect_security_phrase_dialog": "La sauvegarde n’a pas pu être déchiffrée avec cette phrase secrète : merci de vérifier que vous avez saisi la bonne phrase secrète.",
-        "incorrect_security_phrase_title": "Phrase secrète incorrecte",
+        "enter_key_description": "Accédez à votre historique de messages chiffrés et mettez en place la messagerie sécurisée en saisissant votre clé de récupération.",
+        "enter_key_title": "Saisir la clé de récupération",
+        "enter_phrase_description": "Accédez à votre historique de messages chiffrés et mettez en place la messagerie sécurisée en saisissant votre phrase de sécurité.",
+        "enter_phrase_title": "Saisir la phrase de sécurité",
+        "incorrect_security_phrase_dialog": "La sauvegarde n’a pas pu être déchiffrée avec cette phrase de sécurité : merci de vérifier que vous avez saisi la bonne phrase de sécurité.",
+        "incorrect_security_phrase_title": "Phrase de sécurité incorrecte",
         "key_backup_warning": "<b>Attention</b> : vous ne devriez configurer la sauvegarde des clés que depuis un ordinateur de confiance.",
         "key_fetch_in_progress": "Récupération des clés depuis le serveur…",
-        "key_forgotten_text": "Si vous avez oublié votre clé de sécurité, vous pouvez <button>définir de nouvelles options de récupération</button>",
-        "key_is_invalid": "Clé de sécurité invalide",
-        "key_is_valid": "Ça ressemble à une clé de sécurité !",
+        "key_forgotten_text": "Si vous avez oublié votre clé de récupération, vous pouvez <button>définir de nouvelles options de récupération</button>",
+        "key_is_invalid": "Clé de récupération invalide",
+        "key_is_valid": "La clé de récupération semble valide !",
         "keys_restored_title": "Clés restaurées",
         "load_error_content": "Impossible de récupérer l’état de la sauvegarde",
         "load_keys_progress": "%(completed)s clés sur %(total)s restaurées",
         "no_backup_error": "Aucune sauvegarde n’a été trouvée !",
-        "phrase_forgotten_text": "Si vous avez oublié votre phrase secrète vous pouvez <button1>utiliser votre clé de sécurité</button1> ou <button2>définir de nouvelles options de récupération</button2>",
-        "recovery_key_mismatch_description": "La sauvegarde n’a pas pu être déchiffrée avec cette clé de sécurité : merci de vérifier que vous avez saisi la bonne clé de sécurité.",
-        "recovery_key_mismatch_title": "Pas de correspondance entre les clés de sécurité",
+        "phrase_forgotten_text": "Si vous avez oublié votre phrase de sécurité, vous pouvez <button1>utiliser votre clé de récupération</button1> ou <button2>définir de nouvelles options de récupération</button2>",
+        "recovery_key_mismatch_description": "La sauvegarde n’a pas pu être déchiffrée avec cette clé de récupération : merci de vérifier que vous avez saisi la bonne clé de récupération.",
+        "recovery_key_mismatch_title": "Clé de récupération non concordante",
         "restore_failed_error": "Impossible de restaurer la sauvegarde"
     },
     "right_panel": {
@@ -1926,7 +1989,6 @@
             "you_created": "Vous avez créé ce salon."
         },
         "invite_email_mismatch_suggestion": "Partagez cet e-mail dans les paramètres pour recevoir les invitations directement dans %(brand)s.",
-        "invite_reject_ignore": "Rejeter et ignorer l’utilisateur",
         "invite_sent_to_email": "Cet invitation a été envoyée à %(email)s",
         "invite_sent_to_email_room": "Cette invitation à %(roomName)s a été envoyée à %(email)s",
         "invite_subtitle": "<userName/> vous a invité",
@@ -2022,38 +2084,90 @@
             },
             "uploading_single_file": "Envoi de %(filename)s"
         },
+        "video_room": "Ce salon est un salon vidéo",
         "waiting_for_join_subtitle": "Une fois que les utilisateurs invités seront connectés sur %(brand)s, vous pourrez discuter et le salon sera chiffré de bout en bout",
         "waiting_for_join_title": "En attente de connexion des utilisateurs à %(brand)s"
     },
     "room_list": {
         "add_room_label": "Ajouter un salon",
         "add_space_label": "Ajouter un espace",
+        "appearance": "Apparence",
         "breadcrumbs_empty": "Aucun salon visité récemment",
         "breadcrumbs_label": "Salons visités récemment",
+        "empty": {
+            "no_chats": "Pas encore de discussions",
+            "no_chats_description": "Commencez par envoyer un message à quelqu'un ou en créant un salon",
+            "no_chats_description_no_room_rights": "Commencez par envoyer un message à quelqu'un",
+            "no_favourites": "Vous n'avez pas encore de discussion favorite",
+            "no_favourites_description": "Vous pouvez ajouter une discussion à vos favoris dans les paramètres de discussion",
+            "no_invites": "Vous n'avez aucune invitation non lue",
+            "no_mentions": "Vous n'avez aucune mention non lue",
+            "no_people": "Vous n'avez encore de discussions",
+            "no_people_description": "Veuillez désélectionner des filtres pour voir vos discussions",
+            "no_rooms": "Vous n’êtes membre d’aucun salon",
+            "no_rooms_description": "Veuillez désélectionner des filtres pour voir vos discussions",
+            "no_unread": "Félicitations ! Vous n'avez aucun message non lu",
+            "show_activity": "Voir toutes les activités",
+            "show_chats": "Afficher toutes les discussions"
+        },
         "failed_add_tag": "Échec de l’ajout de l’étiquette %(tagName)s au salon",
         "failed_remove_tag": "Échec de la suppression de l’étiquette %(tagName)s du salon",
         "failed_set_dm_tag": "Échec de l’ajout de l’étiquette de conversation privée",
+        "filters": {
+            "favourite": "Favoris",
+            "invites": "Invitations",
+            "mentions": "Mentions",
+            "people": "Personnes",
+            "rooms": "Salons",
+            "unread": "Non-lus"
+        },
         "home_menu_label": "Options de l’accueil",
         "join_public_room_label": "Rejoindre le salon public",
         "joining_rooms_status": {
             "one": "Vous êtes en train de rejoindre %(count)s salon",
             "other": "Vous êtes en train de rejoindre %(count)s salons"
         },
+        "list_title": "Liste de salons",
+        "more_options": {
+            "copy_link": "Copier le lien du salon",
+            "favourited": "Favorisé",
+            "leave_room": "Quitter le salon",
+            "low_priority": "Priorité basse",
+            "mark_read": "Marquer comme lu",
+            "mark_unread": "Marquer comme non lu"
+        },
         "notification_options": "Paramètres de notifications",
+        "open_space_menu": "Ouvrir le menu de l’espace",
+        "primary_filters": "Filtre de la liste des salons",
         "redacting_messages_status": {
             "one": "Actuellement en train de supprimer les messages dans %(count)s salon",
             "other": "Actuellement en train de supprimer les messages dans %(count)s salons"
         },
+        "room": {
+            "more_options": "Plus d’options",
+            "open_room": "Ouvrir salon %(roomName)s"
+        },
+        "room_options": "Options du salon",
         "show_less": "En voir moins",
+        "show_message_previews": "Afficher les aperçus des messages",
         "show_n_more": {
             "other": "En afficher %(count)s de plus",
             "one": "En afficher %(count)s de plus"
         },
         "show_previews": "Afficher un aperçu des messages",
+        "sort": "Trier",
         "sort_by": "Trier par",
         "sort_by_activity": "Activité",
         "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Activité",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Afficher les salons non lus en premier",
+        "space_menu": {
+            "home": "Accueil de l’espace",
+            "space_settings": "Paramètres de l'espace"
+        },
         "space_menu_label": "Menu %(spaceName)s",
         "sublist_options": "Options de liste",
         "suggested_rooms_heading": "Salons recommandés"
@@ -2282,7 +2396,7 @@
             "public_without_alias_warning": "Pour créer un lien vers ce salon, ajoutez une adresse.",
             "publish_room": "Rendez ce salon visible dans l’annuaire des salons publics.",
             "publish_space": "Rendez cet espace visible dans le répertoires des salons publics.",
-            "strict_encryption": "Ne jamais envoyer des messages chiffrés aux sessions non vérifiées dans ce salon depuis cette session",
+            "strict_encryption": "Envoyez des messages uniquement aux utilisateurs vérifiés.",
             "title": "Sécurité et vie privée"
         },
         "title": "Paramètres du salon – %(roomName)s",
@@ -2336,6 +2450,10 @@
         "recent_changes_heading": "Changements récents qui n’ont pas encore été reçus",
         "title": "Le serveur ne répond pas"
     },
+    "service_worker_error": {
+        "description": "%(brand)s nécessite un service worker pour charger les médias authentifiés à partir des référentiels de contenu Matrix. Ceci n'est pas pris en charge par votre navigateur. Il est donc possible que le contenu multimédia ne se charge pas.",
+        "title": "Échec du chargement du service worker"
+    },
     "seshat": {
         "error_initialising": "Échec de l’initialisation de la recherche de messages, vérifiez <a>vos paramètres</a> pour plus d’information",
         "reset_button": "Réinitialiser le magasin d’évènements",
@@ -2380,7 +2498,7 @@
             "custom_theme_add": "Ajouter un thème personnalisé",
             "custom_theme_downloading": "Téléchargement du thème personnalisé…",
             "custom_theme_error_downloading": "Erreur lors du téléchargement du thème",
-            "custom_theme_help": "Entrez l'URL du thème personnalisé que vous souhaitez appliquer.",
+            "custom_theme_help": "Saisir l'URL du thème personnalisé que vous souhaitez appliquer.",
             "custom_theme_invalid": "Schéma du thème invalide.",
             "dialog_title": "<strong>Paramètres : </strong> Apparence",
             "font_size": "Taille de la police",
@@ -2406,6 +2524,74 @@
         "emoji_autocomplete": "Activer la suggestion d’émojis lors de la saisie",
         "enable_markdown": "Activer Markdown",
         "enable_markdown_description": "Commencez les messages avec <code>/plain</code> pour les envoyer sans markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Les détails de votre compte, vos contacts, vos préférences et votre liste de discussions seront conservés",
+                "breadcrumb_page": "Réinitialiser le chiffrement",
+                "breadcrumb_second_description": "Vous perdrez l’historique de vos messages",
+                "breadcrumb_third_description": "Vous devrez vérifier à nouveau tous vos appareils et tous vos contacts",
+                "breadcrumb_title": "Êtes-vous sûr de vouloir réinitialiser votre identité ?",
+                "breadcrumb_title_forgot": "Vous avez oublié votre clé de récupération ? Vous devez réinitialiser votre identité.",
+                "breadcrumb_title_sync_failed": "Impossible de synchroniser le stockage des clés. Vous devez réinitialiser votre identité.",
+                "breadcrumb_warning": "Ne faites cela que si vous pensez que votre compte a été compromis.",
+                "details_title": "Détails du chiffrement",
+                "do_not_close_warning": "Ne fermez pas cette fenêtre tant que la réinitialisation n'est pas terminée",
+                "export_keys": "Exporter les clés",
+                "import_keys": "Importer les clés",
+                "other_people_device_description": "Attention : les utilisateurs qui ne se sont pas explicitement vérifiés auprès de vous (par exemple, via des émojis) ne recevront pas vos messages chiffrés. De même, les appareils non vérifiés des utilisateurs vérifiés ne recevront pas vos messages chiffrés.",
+                "other_people_device_label": "Dans les salons chiffrés, envoyez des messages uniquement aux utilisateurs vérifiés",
+                "other_people_device_title": "Appareils d'autres personnes",
+                "reset_identity": "Réinitialiser l'identité cryptographique",
+                "reset_in_progress": "Réinitialisation en cours...",
+                "session_id": "Identifiant de session :",
+                "session_key": "Clé de session :",
+                "title": "Avancé"
+            },
+            "confirm_key_storage_off": "Êtes-vous sûr de vouloir désactiver le stockage des clés ?",
+            "confirm_key_storage_off_description": "Si vous vous déconnectez de tous vos appareils, vous perdrez l'historique de vos messages et vous devrez vérifier à nouveau tous vos contacts existants. <a>En savoir plus </a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Désactiver la sauvegarde",
+                "confirm": "Désactiver la sauvegarde",
+                "description": "Désactiver la sauvegarde supprimera votre clé de récupération actuelle et désactivera d’autres mesures de sécurité. Dans ce cas :",
+                "list_first": "Pas d’accès à l’historique des discussions chiffrées sur vos nouveaux appareils",
+                "list_second": "Vous perdrez l'accès à vos messages chiffrés si vous êtes déconnecté de %(brand)s sur tout vos appareils",
+                "title": "Êtes-vous certain de vouloir désactiver la sauvegarde ?"
+            },
+            "device_not_verified_button": "Vérifiez cet appareil",
+            "device_not_verified_description": "Vous devez vérifier cet appareil afin de visualiser vos paramètres de chiffrement.",
+            "device_not_verified_title": "Appareil non vérifié",
+            "dialog_title": "<strong>Paramètres : </strong> Chiffrement",
+            "key_storage": {
+                "allow_key_storage": "Autoriser le stockage des clés",
+                "description": "Stockez votre identité cryptographique et vos clés de message en toute sécurité sur le serveur. Cela vous permettra de consulter l'historique de vos messages sur tous les nouveaux appareils. <a>En savoir plus</a>",
+                "title": "Stockage des clés"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Confirmez la nouvelle clé de récupération",
+                "change_recovery_confirm_description": "Saisissez votre nouvelle clé de récupération ci-dessous pour terminer. Votre ancienne clé ne fonctionnera plus.",
+                "change_recovery_confirm_title": "Saisir votre nouvelle clé de récupération",
+                "change_recovery_key": "Changer la clé de récupération",
+                "change_recovery_key_description": "Notez cette nouvelle clé de récupération dans un endroit sûr. Cliquez ensuite sur Continuer pour confirmer la modification.",
+                "change_recovery_key_title": "Changer la clé de récupération ?",
+                "description": "Récupérez votre identité cryptographique et l'historique de vos messages à l'aide d'une clé de récupération si vous avez perdu tous vos appareils existants.",
+                "enter_key_error": "La clé de récupération que vous avez saisie est incorrecte.",
+                "enter_recovery_key": "Saisir la clé de récupération",
+                "forgot_recovery_key": "Clé de récupération oubliée ?",
+                "key_storage_warning": "Le stockage de vos clés n'est pas synchronisé. Cliquez sur l'un des boutons ci-dessous pour résoudre le problème.",
+                "save_key_description": "Ne partagez cela avec personne !",
+                "save_key_title": "Clé de récupération",
+                "set_up_recovery": "Configurer la sauvegarde",
+                "set_up_recovery_confirm_button": "Terminer la configuration",
+                "set_up_recovery_confirm_description": "Saisissez la clé de récupération affichée sur l’écran précédent pour terminer la configuration de la récupération.",
+                "set_up_recovery_confirm_title": "Saisissez votre clé de récupération pour confirmer",
+                "set_up_recovery_description": "Le stockage de vos clés est protégé par une clé de récupération. Si vous avez besoin d'une nouvelle clé de récupération après la création, vous pouvez la recréer en sélectionnant « %(changeRecoveryKeyButton)s ».",
+                "set_up_recovery_save_key_description": "Recopier cette clé de récupération dans un endroit sûr, comme un gestionnaire de mots de passe, une note chiffrée ou un coffre-fort physique.",
+                "set_up_recovery_save_key_title": "Enregistrez votre clé de récupération dans un endroit sûr",
+                "set_up_recovery_secondary_description": "Après avoir cliqué sur continuer, nous allons générer une clé de récupération pour vous.",
+                "title": "Récupération"
+            },
+            "title": "Chiffrement"
+        },
         "general": {
             "account_management_section": "Gestion du compte",
             "account_section": "Compte",
@@ -2448,6 +2634,7 @@
             "discovery_needs_terms_title": "Laissez les gens vous trouver",
             "display_name": "Nom d'affichage",
             "display_name_error": "Impossible de définir le nom d'affichage",
+            "email_adding_unsupported_by_hs": "Ce serveur d'accueil ne prend pas en charge l'ajout d'adresses e-mail à votre compte.",
             "email_address_in_use": "Cette adresse e-mail est déjà utilisée",
             "email_address_label": "Adresse e-mail",
             "email_not_verified": "Votre adresse e-mail n’a pas encore été vérifiée",
@@ -2472,7 +2659,9 @@
             "error_share_msisdn_discovery": "Impossible de partager le numéro de téléphone",
             "identity_server_no_token": "Aucun jeton d’accès d’identité trouvé",
             "identity_server_not_set": "Serveur d'identité non défini",
+            "invalid_phone_number": "Le numéro de téléphone fourni est invalide.",
             "language_section": "Langue",
+            "msisdn_adding_unsupported_by_hs": "Ce serveur d'accueil ne prend pas en charge l'ajout de numéros de téléphone à votre compte.",
             "msisdn_in_use": "Ce numéro de téléphone est déjà utilisé",
             "msisdn_label": "Numéro de téléphone",
             "msisdn_verification_field_label": "Code de vérification",
@@ -2491,7 +2680,6 @@
             "unable_to_load_msisdns": "Impossible de charger les numéros de téléphone",
             "username": "Nom d’utilisateur"
         },
-        "image_thumbnails": "Afficher les aperçus/vignettes pour les images",
         "inline_url_previews_default": "Activer l’aperçu des URL par défaut",
         "inline_url_previews_room": "Activer l’aperçu des URL par défaut pour les participants de ce salon",
         "inline_url_previews_room_account": "Activer l’aperçu des URL pour ce salon (n’affecte que vous)",
@@ -2507,27 +2695,27 @@
                 "backup_setup_success_description": "Vos clés sont maintenant sauvegardées depuis cet appareil.",
                 "backup_setup_success_title": "Sauvegarde sécurisée réalisée avec succès",
                 "cancel_warning": "Si vous annulez maintenant, vous pourriez perdre vos messages et données chiffrés si vous perdez l’accès à vos identifiants.",
-                "confirm_security_phrase": "Confirmez votre phrase secrète",
+                "confirm_security_phrase": "Confirmez votre phrase de sécurité",
                 "description": "Protection afin d’éviter de perdre l’accès aux messages et données chiffrés en sauvegardant les clés de chiffrement sur votre serveur.",
                 "download_or_copy": "%(downloadButton)s ou %(copyButton)s",
-                "enter_phrase_description": "Saisissez une Phrase de Sécurité connue de vous seul·e car elle est utilisée pour protéger vos données. Pour plus de sécurité, vous ne devriez pas réutiliser le mot de passe de votre compte.",
+                "enter_phrase_description": "Saisissez une phrase de sécurité connue de vous seul·e car elle est utilisée pour protéger vos données. Pour plus de sécurité, vous ne devriez pas réutiliser le mot de passe de votre compte.",
                 "enter_phrase_title": "Saisir une phrase de sécurité",
-                "enter_phrase_to_confirm": "Saisissez à nouveau votre phrase secrète pour la confirmer.",
-                "generate_security_key_description": "Nous génèrerons une clé de sécurité que vous devrez stocker dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre.",
-                "generate_security_key_title": "Générer une clé de sécurité",
+                "enter_phrase_to_confirm": "Saisissez à nouveau votre phrase de sécurité pour la confirmer.",
+                "generate_security_key_description": "Nous allons générer une clé de récupération que vous devrez conserver dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre.",
+                "generate_security_key_title": "Générer une clé de récupération",
                 "pass_phrase_match_failed": "Ça ne correspond pas.",
                 "pass_phrase_match_success": "Ça correspond !",
-                "phrase_strong_enough": "Super ! Cette phrase secrète a l’air assez solide.",
+                "phrase_strong_enough": "Super ! Cette phrase de sécurité a l’air assez robuste.",
                 "secret_storage_query_failure": "Impossible de demander le statut du coffre secret",
-                "security_key_safety_reminder": "Stockez votre clé de sécurité dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre, car elle est utilisée pour protéger vos données chiffrées.",
+                "security_key_safety_reminder": "Conservez votre clé de récupération dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre. Elle est utilisée pour protéger vos données chiffrées.",
                 "set_phrase_again": "Retournez en arrière pour la redéfinir.",
                 "settings_reminder": "Vous pouvez aussi configurer la sauvegarde sécurisée et gérer vos clés depuis les paramètres.",
                 "title_confirm_phrase": "Confirmer la phrase de sécurité",
-                "title_save_key": "Sauvegarder votre clé de sécurité",
+                "title_save_key": "Sauvegarder votre clé de récupération",
                 "title_set_phrase": "Définir une phrase de sécurité",
                 "unable_to_setup": "Impossible de configurer le coffre secret",
                 "use_different_passphrase": "Utiliser une phrase secrète différente ?",
-                "use_phrase_only_you_know": "Utilisez une phrase secrète que vous êtes seul à connaître et enregistrez éventuellement une clé de sécurité à utiliser pour la sauvegarde."
+                "use_phrase_only_you_know": "Utilisez une phrase secrète uniquement connue de vous et enregistrez éventuellement une clé de récupération à utiliser pour vos sauvegardes."
             }
         },
         "key_export_import": {
@@ -2554,6 +2742,14 @@
         "labs_mjolnir": {
             "dialog_title": "<strong>Paramètres : </strong> Utilisateurs ignorés"
         },
+        "media_preview": {
+            "hide_avatars": "Masquer les avatars des salons et des invitations",
+            "hide_media": "Toujours masquer",
+            "media_preview_description": "Un média masqué peut toujours être affiché en cliquant dessus",
+            "media_preview_label": "Afficher les médias dans les discussions",
+            "show_in_private": "Dans les salons privés",
+            "show_media": "Toujours afficher"
+        },
         "notifications": {
             "default_setting_description": "Ce réglage sera appliqué par défaut à tous vos salons.",
             "default_setting_section": "Je veux être notifié pour (réglage par défaut)",
@@ -2577,7 +2773,7 @@
             "error_updating": "Nous avons rencontré une erreur lors de la mise-à-jour de vos préférences de notification. Veuillez essayer de réactiver l’option.",
             "invites": "Invitation dans un salon",
             "keywords": "Affiche un badge <badge/> quand des mots-clés sont utilisés dans un salon.",
-            "keywords_prompt": "Entrer des mots-clés ici, ou pour des orthographes alternatives ou des surnoms",
+            "keywords_prompt": "Saisir des mots-clés ici, ou pour des orthographes alternatives ou des surnoms",
             "labs_notice_prompt": "<strong>Mise-à-jour : </strong>Nous avons simplifié les paramètres de notifications pour rendre les options plus facile à trouver. Certains paramètres que vous aviez choisi par le passé ne sont pas visibles ici, mais ils sont toujours actifs. Si vous continuez, certains de vos paramètres peuvent changer. <a>En savoir plus</a>",
             "mentions_keywords": "Mentions et mots-clés",
             "mentions_keywords_only": "Seulement les mentions et les mots-clés",
@@ -2639,57 +2835,20 @@
         "prompt_invite": "Demander avant d’envoyer des invitations à des identifiants matrix potentiellement non valides",
         "replace_plain_emoji": "Remplacer automatiquement le texte par des émojis",
         "security": {
-            "4s_public_key_in_account_data": "dans les données du compte",
-            "4s_public_key_status": "Clé publique du coffre secret :",
             "analytics_description": "Partagez des données anonymes pour nous aider à identifier les problèmes. Rien de personnel. Aucune tierce partie.",
-            "backup_key_cached_status": "Clé de sauvegarde mise en cache :",
-            "backup_key_stored_status": "Clé de sauvegarde enregistrée :",
-            "backup_key_unexpected_type": "type inattendu",
-            "backup_key_well_formed": "bien formée",
-            "backup_keys_description": "Sauvegardez vos clés de chiffrement et les données de votre compte au cas où vous perdiez l’accès à vos sessions. Vos clés seront sécurisés avec une Clé de Sécurité unique.",
             "bulk_options_accept_all_invites": "Accepter les %(invitedRooms)s invitations",
             "bulk_options_reject_all_invites": "Rejeter la totalité des %(invitedRooms)s invitations",
             "bulk_options_section": "Options de groupe",
-            "cross_signing_cached": "mise en cache localement",
-            "cross_signing_homeserver_support": "Prise en charge de la fonctionnalité par le serveur d’accueil :",
-            "cross_signing_homeserver_support_exists": "existant",
-            "cross_signing_in_4s": "dans le coffre secret",
-            "cross_signing_in_memory": "en mémoire",
-            "cross_signing_master_private_Key": "Clé privée maîtresse :",
-            "cross_signing_not_cached": "non trouvée localement",
-            "cross_signing_not_found": "non trouvé",
-            "cross_signing_not_in_4s": "non trouvé dans le coffre",
-            "cross_signing_not_stored": "non sauvegardé",
-            "cross_signing_private_keys": "Clés privées de signature croisée :",
-            "cross_signing_public_keys": "Clés publiques de signature croisée :",
-            "cross_signing_self_signing_private_key": "Clé privée d’auto-signature :",
-            "cross_signing_user_signing_private_key": "Clé privée de signature de l’utilisateur :",
-            "cryptography_section": "Chiffrement",
             "dehydrated_device_description": "La fonctionnalité d’appareil hors ligne vous permet de recevoir des messages chiffrés même lorsque vous n’êtes connecté à aucun appareil",
             "dehydrated_device_enabled": "Appareil hors ligne activé",
-            "delete_backup": "Supprimer la sauvegarde",
-            "delete_backup_confirm_description": "En êtes-vous sûr ? Vous perdrez vos messages chiffrés si vos clés ne sont pas sauvegardées correctement.",
             "dialog_title": "<strong>Paramètres : </strong> Sécurité et confidentialité",
             "e2ee_default_disabled_warning": "L’administrateur de votre serveur a désactivé le chiffrement de bout en bout par défaut dans les salons privés et les conversations privées.",
             "enable_message_search": "Activer la recherche de messages dans les salons chiffrés",
             "encryption_section": "Chiffrement",
-            "error_loading_key_backup_status": "Impossible de charger l’état de sauvegarde des clés",
-            "export_megolm_keys": "Exporter les clés de chiffrement de salon",
             "ignore_users_empty": "Vous n’avez ignoré personne.",
             "ignore_users_section": "Utilisateurs ignorés",
-            "import_megolm_keys": "Importer les clés de chiffrement de bout en bout",
-            "key_backup_active": "Cette session sauvegarde vos clés.",
-            "key_backup_active_version": "Version de sauvegarde active :",
-            "key_backup_active_version_none": "Aucun",
             "key_backup_algorithm": "Algorithme :",
-            "key_backup_can_be_restored": "Cette sauvegarde peut être restaurée sur cette session",
-            "key_backup_complete": "Toutes les clés ont été sauvegardées",
             "key_backup_connect": "Connecter cette session à la sauvegarde de clés",
-            "key_backup_connect_prompt": "Connectez cette session à la sauvegarde de clés avant de vous déconnecter pour éviter de perdre des clés qui seraient uniquement dans cette session.",
-            "key_backup_in_progress": "Sauvegarde de %(sessionsRemaining)s clés…",
-            "key_backup_inactive": "Cette session <b>ne sauvegarde pas vos clés</b>, mais vous n’avez pas de sauvegarde existante que vous pouvez restaurer ou compléter à l’avenir.",
-            "key_backup_inactive_warning": "Vos clés <b>ne sont pas sauvegardées sur cette session</b>.",
-            "key_backup_latest_version": "Dernière version de la sauvegarde sur le serveur :",
             "message_search_disable_warning": "Si l’option est désactivée, les messages des salons chiffrés n’apparaîtront pas dans les résultats de recherche.",
             "message_search_disabled": "Mettre en cache les messages chiffrés localement et de manière sécurisée pour qu’ils apparaissent dans les résultats de recherche.",
             "message_search_enabled": {
@@ -2709,14 +2868,8 @@
             "message_search_unsupported": "Il manque quelques composants à %(brand)s pour mettre en cache les messages chiffrés localement de manière sécurisée. Si vous voulez essayer cette fonctionnalité, construisez %(brand)s Desktop vous-même en <nativeLink>ajoutant les composants de recherche</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s ne peut actuellement mettre en cache vos messages chiffrés localement de manière sécurisée via le navigateur Web. Utilisez <desktopLink>%(brand)s Desktop</desktopLink> pour que les messages chiffrés apparaissent dans vos résultats de recherche.",
             "record_session_details": "Enregistrez le nom, la version et l'URL du client afin de reconnaitre les sessions plus facilement dans le gestionnaire de sessions",
-            "restore_key_backup": "Restaurer depuis la sauvegarde",
-            "secret_storage_not_ready": "pas prêt",
-            "secret_storage_ready": "prêt",
-            "secret_storage_status": "Coffre secret :",
             "send_analytics": "Envoyer les données de télémétrie",
-            "session_id": "Identifiant de session :",
-            "session_key": "Clé de session :",
-            "strict_encryption": "Ne jamais envoyer de messages chiffrés aux sessions non vérifiées depuis cette session"
+            "strict_encryption": "Envoyer des messages uniquement aux utilisateurs vérifiés"
         },
         "send_read_receipts": "Envoyer les accusés de réception",
         "send_read_receipts_unsupported": "Votre serveur ne supporte pas la désactivation de l’envoi des accusés de réception.",
@@ -2764,6 +2917,7 @@
             "inactive_sessions_list_description": "Pensez à déconnecter les anciennes sessions (%(inactiveAgeDays)s jours ou plus) que vous n’utilisez plus.",
             "ip": "Adresse IP",
             "last_activity": "Dernière activité",
+            "manage": "Gérer cette session",
             "mobile_session": "Session de téléphone portable",
             "n_sessions_selected": {
                 "one": "%(count)s session sélectionnée",
@@ -2957,8 +3111,6 @@
         "topic": "Récupère ou définit le sujet du salon",
         "topic_none": "Ce salon n'a pas de sujet.",
         "topic_room_error": "Impossible de récupérer le sujet du salon : Salon introuvable (%(roomId)s)",
-        "tovirtual": "Bascule dans le salon virtuel de ce salon, s'il en a un",
-        "tovirtual_not_found": "Aucun salon virtuel pour ce salon",
         "unban": "Révoque le bannissement de l’utilisateur ayant l’identifiant fourni",
         "unflip": "Ajoute ┬──┬ ノ( ゜-゜ノ) en préfixe du message",
         "unholdcall": "Reprend l’appel en attente dans ce salon",
@@ -2976,6 +3128,7 @@
         "view": "Affiche le salon avec cette adresse",
         "whois": "Affiche des informations à propos de l’utilisateur"
     },
+    "sliding_sync_legacy_no_longer_supported": "L'ancienne fonctionnalité Sliding Sync n'est plus prise en charge : veuillez vous déconnecter puis vous reconnecter pour activer la nouvelle fonctionnalité Sliding Sync",
     "space": {
         "add_existing_room_space": {
             "create": "Voulez-vous plutôt ajouter un nouveau salon ?",
@@ -3080,7 +3233,7 @@
         "heading_without_query": "Recherche de",
         "join_button_text": "Rejoindre %(roomAddress)s",
         "keyboard_scroll_hint": "Utilisez <arrows/> pour faire défiler",
-        "message_search_section_title": "Autres recherches",
+        "messages_label": "Messages",
         "other_rooms_in_space": "Autres salons dans %(spaceName)s",
         "public_rooms_label": "Salons publics",
         "public_spaces_label": "Espaces publics",
@@ -3090,7 +3243,6 @@
         "result_may_be_hidden_privacy_warning": "Certains résultats pourraient être masqués pour des raisons de confidentialité",
         "result_may_be_hidden_warning": "Certains résultats peuvent être cachés",
         "search_dialog": "Fenêtre de recherche",
-        "search_messages_hint": "Pour chercher des messages, repérez cette icône en haut à droite d'un salon <icon/>",
         "spaces_title": "Espaces où vous êtes",
         "start_group_chat_button": "Démarrer une conversation de groupe"
     },
@@ -3139,9 +3291,7 @@
     "threads_activity_centre": {
         "header": "Activité des fils de discussions",
         "no_rooms_with_threads_notifs": "Vous n’avez pas encore de salons avec des notifications de fil de discussion.",
-        "no_rooms_with_unread_threads": "Vous n'avez pas encore de salons contenant des fils de discussion non lus.",
-        "release_announcement_description": "Les notifications des fils de discussion ont été déplacées. À partir de maintenant, retrouvez-les ici.",
-        "release_announcement_header": "Centre d'activité des fils de discussions"
+        "no_rooms_with_unread_threads": "Vous n'avez pas encore de salons contenant des fils de discussion non lus."
     },
     "time": {
         "about_day_ago": "il y a environ un jour",
@@ -3353,6 +3503,7 @@
             "left_reason": "%(targetName)s a quitté le salon : %(reason)s",
             "no_change": "%(senderName)s n’a fait aucun changement",
             "reject_invite": "%(targetName)s a rejeté l’invitation",
+            "reject_invite_reason": "%(targetName)s a rejeté l'invitation : %(reason)s",
             "remove_avatar": "%(senderName)s a supprimé son image de profil",
             "remove_name": "%(senderName)s a supprimé son nom d’affichage (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s a défini une image de profil",
@@ -3388,10 +3539,14 @@
             "sent": "%(senderName)s a invité %(targetDisplayName)s à rejoindre le salon."
         },
         "m.room.tombstone": "%(senderDisplayName)s a mis à niveau ce salon.",
-        "m.room.topic": "%(senderDisplayName)s a changé le sujet du salon en « %(topic)s ».",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s a changé le sujet du salon en « %(topic)s ».",
+            "removed": "%(senderDisplayName)s a supprimé le sujet."
+        },
         "m.sticker": "%(senderDisplayName)s a envoyé un autocollant.",
         "m.video": {
-            "error_decrypting": "Erreur lors du déchiffrement de la vidéo"
+            "error_decrypting": "Erreur lors du déchiffrement de la vidéo",
+            "show_video": "Voir la vidéo"
         },
         "m.widget": {
             "added": "Widget %(widgetName)s ajouté par %(senderName)s",
@@ -3627,7 +3782,7 @@
     },
     "unsupported_browser": {
         "description": "Si vous continuez, certaines fonctionnalités risquent de cesser de fonctionner et vous risquez de perdre des données à l'avenir. Mettez à jour votre navigateur pour continuer à utiliser%(brand)s .",
-        "title": "%(brand)sne prend pas en charge ce navigateur"
+        "title": "%(brand)s ne prend pas en charge ce navigateur"
     },
     "unsupported_server_description": "Ce serveur utilise une ancienne version de Matrix. Mettez-le à jour vers Matrix %(version)s pour utiliser %(brand)s sans erreurs.",
     "unsupported_server_title": "Votre serveur n’est pas pris en charge",
@@ -3647,10 +3802,11 @@
         "unavailable": "Indisponible"
     },
     "update_room_access_modal": {
-        "description": "Pour créer un lien de partage, vous devez autoriser les invités à rejoindre ce salon. Cela peut rendre le salon moins sûr. Lorsque vous aurez terminé l'appel, vous pourrez redéfinir la confidentialité du salon.",
-        "dont_change_description": "Vous pouvez également prendre l'appel dans un salon séparé.",
+        "description": "Pour créer un lien de partage, rendez ce salon <b>publique</b> ou activer l 'option permettant aux utilisateurs de <b>demander à rejoindre</b>. Cela permet aux invités de participer sans être invités.",
+        "dont_change_description": "Si vous ne souhaitez pas modifier l'accès à ce salon, vous pouvez créer un nouveau salon pour le lien d'appel.",
         "no_change": "Je ne souhaite pas modifier le niveau d'accès.",
-        "title": "Modifier le niveau d'accès du salon"
+        "revert_access_description": "(Ceci peut être rétabli à sa valeur précédente dans les paramètres du salon : <b> Sécurité et confidentialité</b>/<b>Accès</b>)",
+        "title": "Autoriser les utilisateurs invités à rejoindre ce salon"
     },
     "upload_failed_generic": "Le fichier « %(fileName)s » n’a pas pu être envoyé.",
     "upload_failed_size": "Le fichier « %(fileName)s » dépasse la taille limite autorisée par ce serveur pour les envois",
@@ -3677,18 +3833,9 @@
         "ban_room_confirm_title": "Bannir de %(roomName)s",
         "ban_space_everything": "Les bannir de partout où j’ai le droit de le faire",
         "ban_space_specific": "Les bannir de certains endroits où j’ai le droit de le faire",
-        "count_of_sessions": {
-            "one": "%(count)s session",
-            "other": "%(count)s sessions"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sessions vérifiées",
-            "one": "1 session vérifiée"
-        },
         "deactivate_confirm_action": "Désactiver l’utilisateur",
         "deactivate_confirm_description": "Désactiver cet utilisateur le déconnectera et l’empêchera de se reconnecter. De plus, il quittera tous les salons qu’il a rejoints. Cette action ne peut pas être annulée. Voulez-vous vraiment désactiver cet utilisateur ?",
         "deactivate_confirm_title": "Désactiver l’utilisateur ?",
-        "dehydrated_device_enabled": "Appareil hors ligne activé",
         "demote_button": "Rétrograder",
         "demote_self_confirm_description_space": "Vous ne pourrez pas annuler ce changement puisque vous vous rétrogradez. Si vous êtes le dernier utilisateur a privilèges de cet espace, il deviendra impossible d’en reprendre contrôle.",
         "demote_self_confirm_room": "Vous ne pourrez pas annuler cette modification car vous vous rétrogradez. Si vous êtes le dernier utilisateur privilégié de ce salon, il sera impossible de récupérer les privilèges.",
@@ -3696,17 +3843,14 @@
         "disinvite_button_room": "Désinviter du salon",
         "disinvite_button_room_name": "Annuler l’invitation à %(roomName)s",
         "disinvite_button_space": "Désinviter de l’espace",
-        "edit_own_devices": "Modifier les appareils",
         "error_ban_user": "Échec du bannissement de l’utilisateur",
         "error_deactivate": "Échec de la désactivation de l’utilisateur",
         "error_kicking_user": "Échec de l’expulsion de l’utilisateur",
         "error_mute_user": "Échec de la mise en sourdine de l’utilisateur",
         "error_revoke_3pid_invite_description": "Impossible de révoquer l’invitation. Le serveur subit peut-être un problème temporaire ou vous n’avez pas la permission de révoquer l’invitation.",
         "error_revoke_3pid_invite_title": "Échec de la révocation de l’invitation",
-        "hide_sessions": "Masquer les sessions",
-        "hide_verified_sessions": "Masquer les sessions vérifiées",
         "ignore_button": "Ignorer",
-        "ignore_confirm_description": "Tous les messages et invitations de cette utilisateur seront cachés. Êtes-vous sûr de vouloir les ignorer ?",
+        "ignore_confirm_description": "Tous les messages et invitations de cet utilisateur seront masqués. Êtes-vous sûr de vouloir les ignorer ?",
         "ignore_confirm_title": "Ignorer %(user)s",
         "invited_by": "Invité par %(sender)s",
         "jump_to_rr_button": "Aller à l’accusé de lecture",
@@ -3748,6 +3892,7 @@
         "unban_space_specific": "Annuler le bannissement de certains endroits où j’ai le droit de le faire",
         "unban_space_warning": "Ils ne pourront plus accéder aux endroits dans lesquels vous n’êtes pas administrateur.",
         "unignore_button": "Ne plus ignorer",
+        "verification_unavailable": "Vérification de l’utilisateur indisponible",
         "verify_button": "Vérifier l’utilisateur",
         "verify_explainer": "Pour une sécurité supplémentaire, vérifiez cet utilisateur en comparant un code à usage unique sur vos deux appareils."
     },
@@ -3800,7 +3945,6 @@
         "input_devices": "Périphériques d’entrée",
         "jitsi_call": "Conférence Jitsi",
         "join_button_tooltip_call_full": "Désolé — Cet appel est actuellement complet",
-        "join_button_tooltip_connecting": "Connexion",
         "legacy_call": "Appel vidéo",
         "maximise": "Remplir l’écran",
         "maximise_call": "Plein écran",
@@ -3955,7 +4099,7 @@
         "error_need_to_be_logged_in": "Vous devez être identifié.",
         "error_unable_start_audio_stream_description": "Impossible de démarrer la diffusion audio.",
         "error_unable_start_audio_stream_title": "Échec lors du démarrage de la diffusion en direct",
-        "modal_data_warning": "Les données sur cet écran sont partagées avec %(widgetDomain)s",
+        "modal_data_warning": "Les données ci-dessous sont partagées avec %(widgetDomain)s",
         "modal_title_default": "Fenêtre de widget",
         "no_name": "Application inconnue",
         "open_id_permissions_dialog": {
diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json
index 55f5afb2a508429a06c038ec0ba10a3c8c9ede53..be37d40bc6c11c6495fcd39dc0c2d2b08099513d 100644
--- a/src/i18n/strings/gl.json
+++ b/src/i18n/strings/gl.json
@@ -83,7 +83,6 @@
         "react": "Reacciona",
         "refresh": "Actualizar",
         "register": "Rexistrar",
-        "reject": "Rexeitar",
         "remove": "Eliminar",
         "rename": "Cambiar nome",
         "reply": "Resposta",
@@ -318,7 +317,6 @@
         "download_logs": "Descargar rexistro",
         "downloading_logs": "Descargando o rexistro",
         "error_empty": "Cóntanos o que fallou ou, mellor aínda, abre un informe en GitHub que describa o problema.",
-        "failed_send_logs": "Fallo ao enviar os informes: ",
         "github_issue": "Informe en GitHub",
         "introduction": "Se informaches do fallo en GitHub, os rexistros poden ser útiles para arranxar o problema. ",
         "log_request": "Para axudarnos a evitar esto no futuro, envíanos <a>o rexistro</a>.",
@@ -357,7 +355,6 @@
         "access_token": "Token de acceso",
         "accessibility": "Accesibilidade",
         "advanced": "Avanzado",
-        "all_rooms": "Todas as salas",
         "analytics": "Análise",
         "and_n_others": {
             "other": "e %(count)s outras...",
@@ -372,7 +369,6 @@
         "capabilities": "Capacidades",
         "copied": "Copiado!",
         "credits": "Créditos",
-        "cross_signing": "Sinatura cruzada",
         "dark": "Escuro",
         "description": "Descrición",
         "deselect_all": "Retirar selección a todos",
@@ -442,7 +438,6 @@
         "room_name": "Nome da sala",
         "rooms": "Salas",
         "secure_backup": "Copia Segura",
-        "security": "Seguridade",
         "select_all": "Seleccionar todos",
         "server": "Servidor",
         "settings": "Axustes",
@@ -461,7 +456,6 @@
         "thread": "Tema",
         "threads": "Conversas",
         "timeline": "Cronoloxía",
-        "trusted": "Confiable",
         "unencrypted": "Non cifrada",
         "unmute": "Non acalar",
         "unnamed_room": "Sala sen nome",
@@ -675,44 +669,23 @@
     "empty_room_was_name": "Sala baleira (era %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Escribe a túa Frase de Seguridade ou <button>usa a túa Chave de Seguridade</button> para continuar.",
             "key_validation_text": {
-                "invalid_security_key": "Chave de Seguridade non válida",
-                "recovery_key_is_correct": "Pinta ben!",
-                "wrong_file_type": "Tipo de ficheiro erróneo",
                 "wrong_security_key": "Chave de Seguridade incorrecta"
             },
-            "reset_title": "Restablecer todo",
-            "reset_warning_1": "Fai isto únicamente se non tes outro dispositivo co que completar a verificación.",
-            "reset_warning_2": "Se restableces todo, volverás a comezar sen sesións verificadas, usuarias de confianza, e poderías non poder ver as mensaxes anteriores.",
             "restoring": "Restablecendo chaves desde a copia",
-            "security_key_title": "Chave de Seguridade",
-            "security_phrase_incorrect_error": "Non se puido acceder ao almacenaxe segredo. Comproba que escribiches correctamente a Frase de Seguridade.",
-            "security_phrase_title": "Frase de seguridade",
-            "separator": "%(securityKey)s ou %(recoveryFile)s",
-            "use_security_key_prompt": "Usa a túa Chave de Seguridade para continuar."
+            "security_key_title": "Chave de Seguridade"
         },
         "bootstrap_title": "Configurando as chaves",
         "cancel_entering_passphrase_description": "¿Estás seguro de que non queres escribir a frase de paso?",
         "cancel_entering_passphrase_title": "Cancelar a escrita da frase de paso?",
         "confirm_encryption_setup_body": "Preme no botón inferior para confirmar os axustes do cifrado.",
         "confirm_encryption_setup_title": "Confirma os axustes de cifrado",
-        "cross_signing_not_ready": "Non está configurada a Sinatura-Cruzada.",
-        "cross_signing_ready": "A Sinatura-Cruzada está lista para usar.",
-        "cross_signing_ready_no_backup": "A sinatura-cruzada está preparada pero non hai copia das chaves.",
         "cross_signing_room_normal": "Esta sala está cifrada extremo-a-extremo",
         "cross_signing_room_verified": "Todas nesta sala están verificadas",
         "cross_signing_room_warning": "Alguén está a usar unha sesión descoñecida",
-        "cross_signing_unsupported": "O teu servidor non soporta a sinatura cruzada.",
-        "cross_signing_untrusted": "A túa conta ten unha identidade de sinatura cruzada no almacenaxe segredo, pero aínda non confiaches nela nesta sesión.",
         "cross_signing_user_normal": "Non verificaches esta usuaria.",
         "cross_signing_user_verified": "Verificaches esta usuaria. A usuaria verificou todas as súas sesións.",
         "cross_signing_user_warning": "Esta usuaria non verificou ningunha das súas sesións.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Baleirar chaves de sinatura-cruzada",
-            "title": "Destruír chaves de sinatura-cruzada?",
-            "warning": "O eliminación das chaves de sinatura cruzada é permanente. Calquera a quen verificases con elas verá alertas de seguridade. Seguramente non queres facer esto, a menos que perdeses todos os dispositivos nos que podías asinar."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "A autenticidade desta mensaxe cifrada non está garantida neste dispositivo.",
         "event_shield_reason_mismatched_sender_key": "Cifrada por unha sesión non verificada",
         "export_unsupported": "O seu navegador non soporta as extensións de criptografía necesarias",
@@ -732,7 +705,6 @@
             "title": "Novo Método de Recuperación",
             "warning": "Se non configuras o novo método de recuperación, un atacante podería intentar o acceso á túa conta. Cambia inmediatamente o contrasinal da conta e configura un novo método de recuperación nos Axustes."
         },
-        "not_supported": "<non soportado>",
         "recovery_method_removed": {
             "description_1": "Esta sesión detectou que se eliminaron a túa Frase de Seguridade e chave para Mensaxes Seguras.",
             "description_2": "Se fixeches esto sen querer, podes configurar Mensaxes Seguras nesta sesión e volverá a cifrar as mensaxes da sesión cun novo método de recuperación.",
@@ -743,8 +715,7 @@
         "set_up_toast_description": "Protéxete de perder o acceso a mensaxes e datos cifrados",
         "set_up_toast_title": "Configurar Copia de apoio Segura",
         "setup_secure_backup": {
-            "explainer": "Fai unha copia de apoio das chaves antes de saír para evitar perdelas.",
-            "title": "Configurar"
+            "explainer": "Fai unha copia de apoio das chaves antes de saír para evitar perdelas."
         },
         "udd": {
             "interactive_verification_button": "Verificar interactivamente usando emoji",
@@ -755,12 +726,10 @@
             "title": "Non confiable"
         },
         "unable_to_setup_keys_error": "Non se puideron configurar as chaves",
-        "unsupported": "Este cliente non soporta o cifrado extremo-a-extremo.",
         "verification": {
             "accepting": "Aceptando…",
             "after_new_login": {
                 "device_verified": "Dispositivo verificado",
-                "reset_confirmation": "Queres restablecer as chaves de verificación?",
                 "skip_verification": "Omitir a verificación por agora",
                 "unable_to_verify": "Non se puido verificar este dispositivo",
                 "verify_this_device": "Verifica este dispositivo"
@@ -823,7 +792,6 @@
             "verify_emoji_prompt": "Verficación por comparación de emoticonas.",
             "verify_emoji_prompt_qr": "Se non podes escanear o código superior, verifica comparando as emoticonas.",
             "verify_later": "Verificarei máis tarde",
-            "verify_reset_warning_1": "O restablecemento das chaves de seguridade non se pode desfacer. Tras o restablecemento, non terás acceso ás antigas mensaxes cifradas, e calquera amizade que verificaras con anterioridade vai ver un aviso de seguridade ata que volvades a verificarvos mutuamente.",
             "verify_using_device": "Verifica usando outro dispositivo",
             "verify_using_key": "Verificar coa Chave de Seguridade",
             "verify_using_key_or_phrase": "Verificar coa Chave ou Frase de Seguridade",
@@ -878,11 +846,7 @@
             "title": "Non se puido copiar ligazón da sala"
         },
         "error_loading_user_profile": "Non se cargou o perfil da usuaria",
-        "forget_room_failed": "Fallo ao esquecer sala %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "O servidor podería non estar dispoñible, sobrecargado, ou caducou a busca :(",
-            "title": "Fallou a busca"
-        }
+        "forget_room_failed": "Fallo ao esquecer sala %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1412,11 +1376,6 @@
         "ongoing": "Eliminando…",
         "reason_label": "Razón (optativa)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Seguro que desexa rexeitar o convite?",
-        "failed": "Fallo ao rexeitar o convite",
-        "title": "Rexeitar convite"
-    },
     "report_content": {
         "description": "Ao denunciar esta mensaxe vasnos enviar o seu 'event ID' único á administración do servidor. Se as mensaxes da sala están cifradas, a administración do servidor non poderá ler o texto da mensaxe ou ver imaxes ou ficheiros.",
         "disagree": "En desacordo",
@@ -1545,7 +1504,6 @@
             "you_created": "Creaches esta sala."
         },
         "invite_email_mismatch_suggestion": "Comparte este email en Axustes para recibir convites directamente en %(brand)s.",
-        "invite_reject_ignore": "Rexeitar e Ignorar usuaria",
         "invite_sent_to_email": "Este convite enviouse a %(email)s",
         "invite_sent_to_email_room": "Este convite para %(roomName)s foi enviado a %(email)s",
         "invite_subtitle": "<userName/> convidoute",
@@ -2000,7 +1958,6 @@
             "remove_msisdn_prompt": "Eliminar %(phone)s?",
             "spell_check_locale_placeholder": "Elixe o idioma"
         },
-        "image_thumbnails": "Mostrar miniaturas/vista previa das imaxes",
         "inline_url_previews_default": "Activar por defecto as vistas previas en liña de URL",
         "inline_url_previews_room": "Activar a vista previa de URL por defecto para as participantes nesta sala",
         "inline_url_previews_room_account": "Activar vista previa de URL nesta sala (só che afecta a ti)",
@@ -2098,48 +2055,16 @@
         "prompt_invite": "Avisar antes de enviar convites a IDs de Matrix potencialmente incorrectos",
         "replace_plain_emoji": "Substituír automaticamente Emoji en texto plano",
         "security": {
-            "4s_public_key_in_account_data": "nos datos da conta",
-            "4s_public_key_status": "Chave pública da almacenaxe segreda:",
-            "backup_key_cached_status": "Chave da copia na caché:",
-            "backup_key_stored_status": "Chave da copia gardada:",
-            "backup_key_unexpected_type": "tipo non agardado",
-            "backup_key_well_formed": "ben formado",
-            "backup_keys_description": "Fai unha copia de apoio das chaves de cifrado da túa conta en caso de perder o acceso ás túas sesións. As chaves estarán seguras cunha única Chave de Seguridade.",
             "bulk_options_accept_all_invites": "Aceptar os %(invitedRooms)s convites",
             "bulk_options_reject_all_invites": "Rexeitar todos os %(invitedRooms)s convites",
             "bulk_options_section": "Opcións agrupadas",
-            "cross_signing_cached": "na caché local",
-            "cross_signing_homeserver_support": "Soporte de funcións do servidor:",
-            "cross_signing_homeserver_support_exists": "existe",
-            "cross_signing_in_4s": "no almacenaxe segredo",
-            "cross_signing_in_memory": "en memoria",
-            "cross_signing_master_private_Key": "Chave mestra principal:",
-            "cross_signing_not_cached": "non se atopa localmente",
-            "cross_signing_not_found": "non atopado",
-            "cross_signing_not_in_4s": "non atopado no almacenaxe",
-            "cross_signing_not_stored": "non gardado",
-            "cross_signing_private_keys": "Chaves privadas da sinatura cruzada:",
-            "cross_signing_public_keys": "Chaves públicas da sinatura cruzada:",
-            "cross_signing_self_signing_private_key": "Auto asinado da chave privada:",
-            "cross_signing_user_signing_private_key": "Chave privada de sinatura da usuaria:",
-            "cryptography_section": "Criptografía",
-            "delete_backup": "Borrar copia de apoio",
-            "delete_backup_confirm_description": "Estás seguro? Perderás as mensaxes cifradas se non tes unha copia de apoio das chaves de cifrado.",
             "e2ee_default_disabled_warning": "A administración do servidor desactivou por defecto o cifrado extremo-a-extremo en salas privadas e Mensaxes Directas.",
             "enable_message_search": "Activar a busca de mensaxes en salas cifradas",
             "encryption_section": "Cifrado",
-            "error_loading_key_backup_status": "Non se puido cargar o estado das chaves de apoio",
-            "export_megolm_keys": "Exportar chaves E2E da sala",
             "ignore_users_empty": "Non tes usuarias ignoradas.",
             "ignore_users_section": "Usuarias ignoradas",
-            "import_megolm_keys": "Importar chaves E2E da sala",
-            "key_backup_active_version_none": "Nada",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_complete": "Copiaronse todas as chaves",
             "key_backup_connect": "Conecta esta sesión a Copia de Apoio de chaves",
-            "key_backup_connect_prompt": "Conecta esta sesión ao gardado das chaves antes de desconectarte para evitar perder calquera chave que só puidese estar nesta sesión.",
-            "key_backup_inactive": "Esta sesión <b>non está facendo copia das chaves</b>, pero tes unha copia de apoio existente que podes restablecer e engadir para seguir adiante.",
-            "key_backup_inactive_warning": "As túas chaves <b>non están a ser copiadas desde esta sesión</b>.",
             "message_search_disable_warning": "Se está desactivado, as mensaxes das salas cifradas non aparecerán nos resultados das buscas.",
             "message_search_disabled": "Gardar de xeito seguro mensaxes cifradas na caché local para que aparezan nos resultados de buscas.",
             "message_search_enabled": {
@@ -2158,13 +2083,7 @@
             "message_search_space_used": "Espazo utilizado:",
             "message_search_unsupported": "Falta un compoñente de %(brand)s requerido para almacenar localmente mensaxes cifradas na caché. Se queres experimentar con esta función, compila unha versión personalizada de %(brand)s Desktop <nativeLink>cos compoñentes de busca engadidos</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s non pode por na caché local de xeito as mensaxes cifradas cando usa un navegador web. Usa <desktopLink>%(brand)s Desktop</desktopLink> para que as mensaxes cifradas aparezan nos resultados.",
-            "restore_key_backup": "Restaurar desde copia de apoio",
-            "secret_storage_not_ready": "non lista",
-            "secret_storage_ready": "lista",
-            "secret_storage_status": "Almacenaxe segreda:",
             "send_analytics": "Enviar datos de análises",
-            "session_id": "ID da sesión:",
-            "session_key": "Chave da sesión:",
             "strict_encryption": "Non enviar nunca desde esta sesión mensaxes cifradas a sesións non verificadas"
         },
         "send_read_receipts": "Enviar resgardos de lectura",
@@ -2322,8 +2241,6 @@
         "topic": "Obtén ou establece o asunto da sala",
         "topic_none": "Esta sala non ten asunto.",
         "topic_room_error": "Non se obtivo o asunto da sala: Non se atopou a sala (%(roomId)s)",
-        "tovirtual": "Cambia á sala virtual desta sala, se é que existe",
-        "tovirtual_not_found": "No hai sala virtual para esta sala",
         "unban": "Desbloquea usuaria co ID dado",
         "unflip": "Antecede con ┬──┬ ノ( ゜-゜ノ) a unha mensaxe de texto plano",
         "unholdcall": "Acepta a chamada na sala actual",
@@ -2442,7 +2359,6 @@
         "heading_without_query": "Buscar",
         "join_button_text": "Unirse a %(roomAddress)s",
         "keyboard_scroll_hint": "Usa <arrows/> para desprazarte",
-        "message_search_section_title": "Outras buscas",
         "other_rooms_in_space": "Outras salas en %(spaceName)s",
         "public_rooms_label": "Salas públicas",
         "recent_searches_section_title": "Buscas recentes",
@@ -2451,7 +2367,6 @@
         "result_may_be_hidden_privacy_warning": "Algúns resultados poden estar agochados por privacidade",
         "result_may_be_hidden_warning": "Algúns resultados poderían estar agochados",
         "search_dialog": "Diálogo de busca",
-        "search_messages_hint": "Para buscar mensaxes, busca esta icona arriba de todo na sala <icon/>",
         "spaces_title": "Espazos nos que estás",
         "start_group_chat_button": "Inicia un chat en grupo"
     },
@@ -2705,7 +2620,9 @@
             "sent": "%(senderName)s enviou un convite a %(targetDisplayName)s para unirse a sala."
         },
         "m.room.tombstone": "%(senderDisplayName)s actualizou esta sala.",
-        "m.room.topic": "%(senderDisplayName)s cambiou o asunto a \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s cambiou o asunto a \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s enviou un adhesivo.",
         "m.video": {
             "error_decrypting": "Fallo descifrando vídeo"
@@ -2963,14 +2880,6 @@
         "ban_room_confirm_title": "Vetar en %(roomName)s",
         "ban_space_everything": "Vetalos en tódolos sitios nos que eu poida",
         "ban_space_specific": "Vetalos en lugares específicos nos que eu poida",
-        "count_of_sessions": {
-            "other": "%(count)s sesións",
-            "one": "%(count)s sesión"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sesións verificadas",
-            "one": "1 sesión verificada"
-        },
         "deactivate_confirm_action": "Desactivar usuaria",
         "deactivate_confirm_description": "Ao desactivar esta usuaria ficará desconectada e non poderá volver a acceder. Ademáis deixará todas as salas nas que estivese. Esta acción non ten volta, ¿desexas desactivar esta usuaria?",
         "deactivate_confirm_title": "¿Desactivar usuaria?",
@@ -2981,15 +2890,12 @@
         "disinvite_button_room": "Retirar convite á sala",
         "disinvite_button_room_name": "Retirar o convite para %(roomName)s",
         "disinvite_button_space": "Retirar convite ao espazo",
-        "edit_own_devices": "Editar dispositivos",
         "error_ban_user": "Fallo ao bloquear usuaria",
         "error_deactivate": "Fallo ao desactivar a usuaria",
         "error_kicking_user": "Fallou a eliminación da usuaria",
         "error_mute_user": "Fallo ó silenciar usuaria",
         "error_revoke_3pid_invite_description": "Non se revogou o convite. O servidor podería estar experimentando un problema temporal ou non tes permisos suficientes para revogar o convite.",
         "error_revoke_3pid_invite_title": "Fallo ao revogar o convite",
-        "hide_sessions": "Agochar sesións",
-        "hide_verified_sessions": "Agochar sesións verificadas",
         "invited_by": "Convidada por %(sender)s",
         "jump_to_rr_button": "Ir ao resgardo de lectura",
         "kick_button_room": "Eliminar da sala",
@@ -3069,7 +2975,6 @@
         "hangup": "Quedada",
         "hide_sidebar_button": "Agochar barra lateral",
         "input_devices": "Dispositivos de entrada",
-        "join_button_tooltip_connecting": "Conectando",
         "misconfigured_server": "Fallou a chamada porque o servidor está mal configurado",
         "misconfigured_server_description": "Contacta coa administración do teu servidor (<code>%(homeserverDomain)s</code>) para configurar un servidor TURN para que as chamadas funcionen de xeito fiable.",
         "more_button": "Máis",
diff --git a/src/i18n/strings/he.json b/src/i18n/strings/he.json
index 36f05ead73c34004bf88884cc15ee3f20eecc2ff..22a1cecc89b345c596e1a86152f03df3178ef5bf 100644
--- a/src/i18n/strings/he.json
+++ b/src/i18n/strings/he.json
@@ -73,7 +73,6 @@
         "react": "הגב",
         "refresh": "רענן",
         "register": "צור חשבון",
-        "reject": "דחה",
         "reload": "טעינה מחדש",
         "remove": "הסר",
         "rename": "שנה שם",
@@ -289,7 +288,6 @@
         "download_logs": "הורד יומנים",
         "downloading_logs": "מוריד לוגים",
         "error_empty": "אנא ספר לנו מה השתבש או, יותר טוב, צור בעיה של GitHub המתארת את הבעיה.",
-        "failed_send_logs": "כשל במשלוח יומנים: ",
         "github_issue": "סוגיית GitHub",
         "introduction": "אם שלחתם באג דרך GitHub, שליחת לוגים יכולה לעזור לנו לאתר את הבעיה. ",
         "log_request": "כדי לעזור לנו למנוע זאת בעתיד, אנא <a> שלחו לנו יומנים </a>.",
@@ -326,7 +324,6 @@
     "common": {
         "accessibility": "נגישות",
         "advanced": "מתקדם",
-        "all_rooms": "כל החדרים",
         "analytics": "אנליטיקה",
         "and_n_others": {
             "one": "ועוד אחד אחר...",
@@ -340,7 +337,6 @@
         "capabilities": "יכולות",
         "copied": "הועתק!",
         "credits": "נקודות זכות",
-        "cross_signing": "חתימה צולבת",
         "dark": "כהה",
         "description": "תאור",
         "deselect_all": "הסר סימון מהכל",
@@ -404,7 +400,6 @@
         "room_name": "שם חדר",
         "rooms": "חדרים",
         "secure_backup": "גיבוי מאובטח",
-        "security": "אבטחה",
         "select_all": "בחר הכל",
         "server": "שרת",
         "settings": "הגדרות",
@@ -423,7 +418,6 @@
         "thread": "שרשורים",
         "threads": "שרשורים",
         "timeline": "קו זמן",
-        "trusted": "אמין",
         "unencrypted": "לא מוצפן",
         "unmute": "בטל השתקה",
         "unnamed_room": "חדר ללא שם",
@@ -574,37 +568,22 @@
     "encryption": {
         "access_secret_storage_dialog": {
             "key_validation_text": {
-                "invalid_security_key": "מפתח אבטחה לא חוקי",
-                "recovery_key_is_correct": "נראה טוב!",
-                "wrong_file_type": "סוג קובץ שגוי",
                 "wrong_security_key": "מפתח אבטחה שגוי"
             },
             "restoring": "שחזור מפתחות מגיבוי",
-            "security_key_title": "מפתח אבטחה",
-            "security_phrase_incorrect_error": "אין אפשרות לגשת לאחסון הסודי. אנא אשר שהזנת את ביטוי האבטחה הנכון.",
-            "security_phrase_title": "ביטוי אבטחה",
-            "use_security_key_prompt": "השתמש במפתח האבטחה שלך כדי להמשיך."
+            "security_key_title": "מפתח אבטחה"
         },
         "bootstrap_title": "מגדיר מפתחות",
         "cancel_entering_passphrase_description": "האם אתם בטוחים שהינכם רוצים לבטל?",
         "cancel_entering_passphrase_title": "בטל הקלדת סיסמא?",
         "confirm_encryption_setup_body": "לחץ על הלחצן למטה כדי לאשר את הגדרת ההצפנה.",
         "confirm_encryption_setup_title": "אשר את הגדרת ההצפנה",
-        "cross_signing_not_ready": "חתימה צולבת אינה מוגדרת עדיין.",
-        "cross_signing_ready": "חתימה צולבת מוכנה לשימוש.",
         "cross_signing_room_normal": "חדר זה מוצפן מקצה לקצה",
         "cross_signing_room_verified": "כולם מאומתים בחדר זה",
         "cross_signing_room_warning": "מישהו משתמש בהפעלה לא ידועה",
-        "cross_signing_unsupported": "השרת שלכם אינו תומך בחתימות-צולבות.",
-        "cross_signing_untrusted": "לחשבונך זהות חתימה צולבת באחסון סודי, אך הפגישה זו אינה מהימנה עדיין.",
         "cross_signing_user_normal": "לא אימתת משתמש זה.",
         "cross_signing_user_verified": "אימתת משתמש זה. משתמש זה אימת את כל ההפעלות שלו.",
         "cross_signing_user_warning": "משתמש זה לא אימת את כל ההפעלות שלו.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "נקה מפתחות חתימה צולבת",
-            "title": "להרוס מפתחות חתימה צולבת?",
-            "warning": "מחיקת מפתחות חתימה צולבת הינה קבועה. כל מי שאימתת איתו יראה התראות אבטחה. כמעט בוודאות אינך רוצה לעשות זאת, אלא אם איבדת כל מכשיר ממנו תוכל לחתום."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "לא ניתן להבטיח את האותנטיות של הודעה מוצפנת זו במכשיר זה.",
         "event_shield_reason_mismatched_sender_key": "הוצפן על ידי מושב לא מאומת",
         "export_unsupported": "הדפדפן שלכם אינו תומך בהצפנה הדרושה",
@@ -620,7 +599,6 @@
             "title": "שיטת שחזור חדשה",
             "warning": "אם לא הגדרת את שיטת השחזור החדשה, ייתכן שתוקף מנסה לגשת לחשבונך. שנה את סיסמת החשבון שלך והגדר מיד שיטת שחזור חדשה בהגדרות."
         },
-        "not_supported": "<לא נתמך>",
         "recovery_method_removed": {
             "description_2": "אם עשית זאת בטעות, באפשרותך להגדיר הודעות מאובטחות בהפעלה זו אשר תצפין מחדש את היסטוריית ההודעות של הפגישה בשיטת שחזור חדשה.",
             "title": "שיטת השחזור הוסרה",
@@ -629,8 +607,7 @@
         "set_up_toast_description": "שמור מפני איבוד גישה אל הודעות ומידע מוצפן",
         "set_up_toast_title": "צור גיבוי מאובטח",
         "setup_secure_backup": {
-            "explainer": "גבה את המפתחות שלך לפני היציאה כדי להימנע מלאבד אותם.",
-            "title": "הגדר"
+            "explainer": "גבה את המפתחות שלך לפני היציאה כדי להימנע מלאבד אותם."
         },
         "udd": {
             "other_ask_verify_text": "בקש ממשתמש זה לאמת את ההתחברות שלו, או לאמת אותה באופן ידני למטה.",
@@ -640,7 +617,6 @@
             "title": "לא אמין"
         },
         "unable_to_setup_keys_error": "לא ניתן להגדיר מקשים",
-        "unsupported": "לקוח זה אינו תומך בהצפנה מקצה לקצה.",
         "verification": {
             "accepting": "מקבל…",
             "after_new_login": {
@@ -741,11 +717,7 @@
             "title": "לא ניתן להעתיק קישור לחדר"
         },
         "error_loading_user_profile": "לא ניתן לטעון את פרופיל המשתמש",
-        "forget_room_failed": "נכשל בעת בקשה לשכוח חדר %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "יתכן שהשרת לא יהיה זמין, עמוס יתר על המידה או שתם הזמן הקצוב לחיפוש :(",
-            "title": "החיפוש נכשל"
-        }
+        "forget_room_failed": "נכשל בעת בקשה לשכוח חדר %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1181,11 +1153,6 @@
         "ongoing": "מסיר…",
         "reason_label": "סיבה (לא חובה)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "האם אתם בטוחים שברצונכם לדחות את ההזמנה?",
-        "failed": "דחיית ההזמנה נכשלה",
-        "title": "דחה הזמנה"
-    },
     "report_content": {
         "description": "דיווח על הודעה זו ישלח את 'מזהה האירוע' הייחודי למנהל שרת הבית שלך. אם הודעות בחדר זה מוצפנות, מנהל שרת הבית שלך לא יוכל לקרוא את טקסט ההודעה או להציג קבצים או תמונות.",
         "missing_reason": "אנא מלאו מדוע אתם מדווחים.",
@@ -1272,7 +1239,6 @@
             "you_created": "אתם יצרתם את החדר הזה."
         },
         "invite_email_mismatch_suggestion": "שתף דוא\"ל זה בהגדרות כדי לקבל הזמנות ישירות ב-%(brand)s.",
-        "invite_reject_ignore": "דחה והתעלם ממשתמש זה",
         "invite_sent_to_email_room": "הזמנה לחדר %(roomName)s נשלחה לכתובת %(email)s",
         "invite_subtitle": "<userName/> הזמין אתכם",
         "invite_this_room": "הזמן לחדר זה",
@@ -1649,7 +1615,6 @@
             "remove_email_prompt": "הסר כתובות %(email)s ?",
             "remove_msisdn_prompt": "הסר מספרי %(phone)s ?"
         },
-        "image_thumbnails": "הראה תצוגה מקדימה\\ממוזערת של תמונות",
         "inline_url_previews_default": "אפשר צפייה של תצוגת קישורים בצאט כברירת מחדל",
         "inline_url_previews_room": "אפשר לחברים בחדר זה לצפות בתצוגת קישורים",
         "inline_url_previews_room_account": "הראה תצוגה מקדימה של קישורים בחדר זה (משפיע רק עליכם)",
@@ -1740,48 +1705,16 @@
         "prompt_invite": "שאלו אותי לפני שאתם שולחים הזמנה אל קוד זיהוי אפשרי של משתמש מערכת",
         "replace_plain_emoji": "החלף טקסט עם סמל באופן אוטומטי",
         "security": {
-            "4s_public_key_in_account_data": "במידע בחשבון",
-            "4s_public_key_status": "מקום שמירה סודי של המפתח הציבורי:",
-            "backup_key_cached_status": "גבה מפתח במטמון:",
-            "backup_key_stored_status": "גבה מפתח שמור:",
-            "backup_key_unexpected_type": "סוג בלתי צפוי",
-            "backup_key_well_formed": "מעוצב היטב",
-            "backup_keys_description": "גבה את מפתחות ההצפנה שלך עם נתוני חשבונך במקרה שתאבד את הגישה להפעלות שלך. המפתחות שלך מאובטחים באמצעות מפתח אבטחה ייחודי.",
             "bulk_options_accept_all_invites": "קבל את כל ההזמנות של %(invitedRooms)s",
             "bulk_options_reject_all_invites": "דחה את כל ההזמנות של %(invitedRooms)s",
             "bulk_options_section": "אפשרויות בתפזורת",
-            "cross_signing_cached": "אוחסן מקומי",
-            "cross_signing_homeserver_support": "תמיכה שרת:",
-            "cross_signing_homeserver_support_exists": "קיים",
-            "cross_signing_in_4s": "באחסון סודי",
-            "cross_signing_in_memory": "בזכרון",
-            "cross_signing_master_private_Key": "מפתח מאסטר פרטי:",
-            "cross_signing_not_cached": "לא נמצא באחסון מקומי",
-            "cross_signing_not_found": "לא נמצא",
-            "cross_signing_not_in_4s": "לא נמצא באחסון",
-            "cross_signing_not_stored": "לא שמור",
-            "cross_signing_private_keys": "מפתחות פרטיים של חתימה צולבת:",
-            "cross_signing_public_keys": "מפתחות ציבוריים של חתימה צולבת:",
-            "cross_signing_self_signing_private_key": "מפתח פרטי ברישום עצמאי:",
-            "cross_signing_user_signing_private_key": "משתמש חותם על מפתח פרטי:",
-            "cryptography_section": "קריפטוגרפיה",
-            "delete_backup": "מחק גיבוי",
-            "delete_backup_confirm_description": "האם אתה בטוח? תאבד את ההודעות המוצפנות שלך אם המפתחות שלך לא מגובים כראוי.",
             "e2ee_default_disabled_warning": "מנהל השרת שלך השבית הצפנה מקצה לקצה כברירת מחדל בחדרים פרטיים ובהודעות ישירות.",
             "enable_message_search": "אפשר חיפוש הודעות בחדרים מוצפנים",
             "encryption_section": "הצפנה",
-            "error_loading_key_backup_status": "לא ניתן לטעון את מצב גיבוי המפתח",
-            "export_megolm_keys": "ייצא מפתחות חדר E2E",
             "ignore_users_empty": "אין לך משתמשים שהתעלמו מהם.",
             "ignore_users_section": "משתמשים שהתעלמתם מהם",
-            "import_megolm_keys": "ייבא מפתחות לחדר E2E",
-            "key_backup_active_version_none": "ללא",
             "key_backup_algorithm": "אלגוריתם:",
-            "key_backup_complete": "כל המפתחות מגובים",
             "key_backup_connect": "חבר את התחברות הזו לגיבוי מפתח",
-            "key_backup_connect_prompt": "חבר את ההפעלה הזו לגיבוי המפתח לפני היציאה, כדי למנוע אובדן מפתחות שיכולים להיות רק בפגישה זו.",
-            "key_backup_inactive": "הפעלה זו <b> אינה מגבה את המפתחות שלך </b>, אך יש לך גיבוי קיים ממנו תוכל לשחזר ולהוסיף להמשך.",
-            "key_backup_inactive_warning": "המפתחות שלך <b> אינם מגובים מהתחברות זו </b>.",
             "message_search_disable_warning": "אם מושבת, הודעות מחדרים מוצפנים לא יופיעו בתוצאות החיפוש.",
             "message_search_disabled": "שמור באופן מאובטח הודעות מוצפנות באופן מקומי כדי שיופיעו בתוצאות החיפוש.",
             "message_search_enabled": {
@@ -1800,13 +1733,7 @@
             "message_search_space_used": "שטח משומש:",
             "message_search_unsupported": "%(brand)s חסרים כמה רכיבים הנדרשים לצורך אחסון במטמון מאובטח של הודעות מוצפנות באופן מקומי. אם תרצה להתנסות בתכונה זו, בנה %(brand)s מותאם אישית לדסקטום עם <nativeLink>חפש רכיבים להוספה</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s אינם יכולים לשמור במטמון מאובטח הודעות מוצפנות באופן מקומי בזמן שהם פועלים בדפדפן אינטרנט. השתמש ב- <desktopLink>%(brand)s Desktop </desktopLink> כדי שהודעות מוצפנות יופיעו בתוצאות החיפוש.",
-            "restore_key_backup": "שחזר מגיבוי",
-            "secret_storage_not_ready": "לא מוכן",
-            "secret_storage_ready": "מוכן",
-            "secret_storage_status": "אחסון סודי:",
             "send_analytics": "שלח מידע אנליטי",
-            "session_id": "מזהה מושב:",
-            "session_key": "מפתח מושב:",
             "strict_encryption": "לעולם אל תשלח הודעות מוצפנות אל התחברות שאינה מאומתת מהתחברות זו"
         },
         "send_read_receipts": "שילחו אישורי קריאה",
@@ -2202,7 +2129,9 @@
             "sent": "%(senderName)s שלח הזמנה ל%(targetDisplayName)s להצטרף אל החדר."
         },
         "m.room.tombstone": "%(senderDisplayName)s שידרג את החדר הזה.",
-        "m.room.topic": "%(senderDisplayName)s שינה את שם הנושא ל-\"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s שינה את שם הנושא ל-\"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s שלח מדבקה",
         "m.video": {
             "error_decrypting": "שגיאה בפענוח וידאו"
@@ -2411,14 +2340,6 @@
     "user_info": {
         "admin_tools_section": "כלי מנהל",
         "ban_button_space": "חסום ממרחב העבודה",
-        "count_of_sessions": {
-            "one": "%(count)s מושבים",
-            "other": "%(count)s מושבים"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 מושב מאומת",
-            "other": "%(count)s מושבים מאומתים"
-        },
         "deactivate_confirm_action": "השבת משתמש",
         "deactivate_confirm_description": "השבתת משתמש זה תנתק אותו וימנע ממנו להתחבר חזרה. בנוסף, הם יעזבו את כל החדרים בהם הם נמצאים. לא ניתן לבטל פעולה זו. האם אתה בטוח שברצונך להשבית משתמש זה?",
         "deactivate_confirm_title": "השבת משתמש?",
@@ -2427,15 +2348,12 @@
         "demote_self_confirm_room": "לא תוכל לבטל את השינוי הזה מכיוון שאתה מוריד את עצמך בדרגה, אם אתה המשתמש המיועד האחרון בחדר, אי אפשר יהיה להחזיר לו הרשאות.",
         "demote_self_confirm_title": "להוריד את עצמך?",
         "disinvite_button_space": "בטל הזמנה ממרחב העבודה",
-        "edit_own_devices": "הגדרת מכשירים",
         "error_ban_user": "כשלון בחסימת משתמש",
         "error_deactivate": "השבתת משתמש נכשלה",
         "error_kicking_user": "הסרת המשתמש נכשלה",
         "error_mute_user": "כשלון בהשתקת משתמש",
         "error_revoke_3pid_invite_description": "לא ניתן היה לבטל את ההזמנה. ייתכן שהשרת נתקל בבעיה זמנית או שאין לך הרשאות מספיקות לבטל את ההזמנה.",
         "error_revoke_3pid_invite_title": "ביטול ההזמנה נכשל",
-        "hide_sessions": "הסתר מושבים",
-        "hide_verified_sessions": "הסתר מושבים מאומתים",
         "invited_by": "הוזמנו על ידי %(sender)s",
         "jump_to_rr_button": "קפצו לקבלת קריאה",
         "kick_button_space": "הסר ממרחב העבודה",
@@ -2498,7 +2416,6 @@
         "expand": "חזור לשיחה",
         "hangup": "ניתוק",
         "hide_sidebar_button": "הסתר סרגל צד",
-        "join_button_tooltip_connecting": "מקשר",
         "misconfigured_server": "השיחה נכשלה בגלל הגדרות שרת שגויות",
         "misconfigured_server_description": "אנא בקשו ממנהל השרת (<code>%(homeserverDomain)s</code>) לסדר את הגדרות שרת TURN על מנת שהשיחות יפעלו בעקביות.",
         "more_button": "יותר",
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index b3e9db9cf455815cdd67995242b330abd2776680..76f822fee41edd7d083ac77e79b38b4af7fb03f3 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -1,15 +1,30 @@
 {
     "a11y": {
-        "jump_first_invite": "Újrás az első meghívóhoz.",
+        "emoji_picker": "Emodzsiválasztó",
+        "jump_first_invite": "Ugrás az első meghívóhoz.",
+        "message_composer": "Üzenetszerkesztő",
         "n_unread_messages": {
-            "other": "%(count)s olvasatlan üzenet.",
-            "one": "1 olvasatlan üzenet."
+            "%(count)s olvasatlan üzenet.": "other",
+            "1 olvasatlan üzenet.": "one"
         },
         "n_unread_messages_mentions": {
-            "other": "%(count)s olvasatlan üzenet megemlítéssel.",
-            "one": "1 olvasatlan megemlítés."
+            "%(count)s olvasatlan üzenet megemlítéssel.": "other",
+            "1 olvasatlan megemlítés.": "one"
+        },
+        "recent_rooms": "Legutóbbi szobák",
+        "room_messsage_not_sent": "A(z) %(roomName)s szoba megnyitása nem beállított üzenettel.",
+        "room_n_unread_invite": "A(z) %(roomName)s szoba meghívásának megnyitása.",
+        "room_n_unread_messages": {
+            "A(z) %(roomName)s szoba megnyitása 1 olvasatlan üzenettel.": "one",
+            "A(z) %(roomName)s szoba megnyitása %(count)s olvasatlan üzenettel.": "other"
+        },
+        "room_n_unread_messages_mentions": {
+            "A(z) %(roomName)s szoba megnyitása 1 olvasatlan megemlítéssel.": "one",
+            "A(z) %(roomName)s szoba megnyitása %(count)s olvasatlan megemlítéssel.": "other"
         },
         "room_name": "Szoba: %(name)s",
+        "room_status_bar": "Szoba állapotsora",
+        "seek_bar_label": "Hang keresősávja",
         "unread_messages": "Olvasatlan üzenetek.",
         "user_menu": "Felhasználói menü"
     },
@@ -31,7 +46,7 @@
         "click_to_copy": "Másolás kattintással",
         "close": "Bezárás",
         "collapse": "Összecsukás",
-        "complete": "Kiegészít",
+        "complete": "Kiegészítés",
         "confirm": "Megerősítés",
         "continue": "Folytatás",
         "copy": "Másolás",
@@ -40,6 +55,8 @@
         "create_a_room": "Szoba létrehozása",
         "create_account": "Fiók létrehozása",
         "decline": "Elutasítás",
+        "decline_and_block": "Elutasítás és letiltás",
+        "decline_invite": "Meghívás elutasítása",
         "delete": "Törlés",
         "deny": "Megtagadás",
         "disable": "Tiltás",
@@ -59,6 +76,7 @@
         "go": "Meghívás",
         "go_back": "Vissza",
         "got_it": "Értem",
+        "hide": "Elrejtés",
         "hide_advanced": "Speciális beállítások elrejtése",
         "hold": "Várakoztatás",
         "ignore": "Mellőzés",
@@ -75,12 +93,14 @@
         "maximise": "Teljes méret",
         "mention": "Megemlítés",
         "minimise": "Lecsukás",
+        "new_message": "Új üzenet",
         "new_room": "Új szoba",
-        "new_video_room": "Új videó szoba",
+        "new_video_room": "Új videószoba",
         "next": "Következő",
         "no": "Nem",
         "ok": "Rendben",
         "open": "Megnyitás",
+        "open_menu": "Menü megnyitása",
         "pause": "Szünet",
         "pin": "Kitűzés",
         "play": "Lejátszás",
@@ -89,13 +109,13 @@
         "react": "Reakció",
         "refresh": "Frissítés",
         "register": "Regisztráció",
-        "reject": "Elutasítás",
         "reload": "Újratöltés",
         "remove": "Eltávolítás",
         "rename": "Átnevezés",
         "reply": "Válasz",
         "reply_in_thread": "Válasz üzenetszálban",
         "report_content": "Tartalom jelentése",
+        "report_room": "Szoba jelentése",
         "resend": "Újraküldés",
         "reset": "Visszaállítás",
         "resume": "Folytatás",
@@ -105,6 +125,7 @@
         "save": "Mentés",
         "search": "Keresés",
         "send_report": "Jelentés küldése",
+        "set_avatar": "Profilkép beállítása",
         "share": "Megosztás",
         "show": "Megjelenítés",
         "show_advanced": "Speciális beállítások megjelenítése",
@@ -128,6 +149,7 @@
         "update": "Frissítés",
         "upgrade": "Fejlesztés",
         "upload": "Feltöltés",
+        "upload_file": "Fájl feltöltése",
         "verify": "Ellenőrzés",
         "view": "Megtekintés",
         "view_all": "Összes megtekintése",
@@ -135,38 +157,39 @@
         "view_message": "Üzenet megjelenítése",
         "view_source": "Forrás megjelenítése",
         "yes": "Igen",
+        "yes_dismiss": "Igen, elvetés",
         "zoom_in": "Nagyítás",
         "zoom_out": "Kicsinyítés"
     },
     "analytics": {
         "accept_button": "Rendben van",
-        "bullet_1": "<Bold>Nem</Bold> mentünk vagy analizálunk semmilyen felhasználói adatot",
+        "bullet_1": "<Bold>Nem</Bold> mentjük vagy profilozzuk a felhasználói adatokat",
         "bullet_2": "<Bold>Nem</Bold> osztunk meg információt harmadik féllel",
         "consent_migration": "Előzőleg beleegyezett, hogy anonimizált használati adatokat oszt meg velünk. Most frissítjük ennek a működését.",
         "disable_prompt": "Ezt bármikor kikapcsolhatja a beállításokban",
-        "enable_prompt": "Segítsen jobbá tenni: %(analyticsOwner)s",
+        "enable_prompt": "Segítsen jobbá tenni az %(analyticsOwner)set",
         "learn_more": "Anonimizált adatok megosztása a problémák feltárásához. Semmi személyes. Nincs harmadik fél. <LearnMoreLink>További információk</LearnMoreLink>",
         "privacy_policy": "Elolvashatja az összes feltételünket <PrivacyPolicyUrl>itt</PrivacyPolicyUrl>",
-        "pseudonymous_usage_data": "Segítsen észrevennünk a hibákat, és jobbá tenni a(z) %(analyticsOwner)s a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközei között meg lesz osztva.",
+        "pseudonymous_usage_data": "Segítsen észrevennünk a hibákat, és jobbá tenni az %(analyticsOwner)set a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, amely az eszközei között meg lesz osztva.",
         "shared_data_heading": "Az alábbi adatok közül bármelyik megosztásra kerülhet:"
     },
     "auth": {
-        "3pid_in_use": "Ez az e-mail cím vagy telefonszám már használatban van.",
+        "3pid_in_use": "Ez az e-mail-cím vagy telefonszám már használatban van.",
         "account_clash": "Az új (%(newAccountId)s) fiókod elkészült, de jelenleg egy másik fiókba (%(loggedInUserId)s) vagy bejelentkezve.",
         "account_clash_previous_account": "Folytatás az előző fiókkal",
         "account_deactivated": "Ez a fiók zárolva van.",
         "autodiscovery_generic_failure": "Nem sikerült lekérni az automatikus felderítés beállításait a kiszolgálóról",
         "autodiscovery_hs_incompatible": "A Matrix-kiszolgálója túl régi, és nem támogatja a minimálisan szükséges API-verziót. Lépjen kapcsolatba a kiszolgáló tulajdonosával, vagy frissítse.",
         "autodiscovery_invalid": "A Matrix-kiszolgáló felderítésére kapott válasz érvénytelen",
-        "autodiscovery_invalid_hs": "A matrix URL nem tűnik érvényesnek",
-        "autodiscovery_invalid_hs_base_url": "Hibás base_url az m.homeserver -hez",
+        "autodiscovery_invalid_hs": "A Matrix-kiszolgáló webcíme nem tűnik érvényesnek",
+        "autodiscovery_invalid_hs_base_url": "Az m.homeserver beállításhoz megadott base_url érvénytelen",
         "autodiscovery_invalid_is": "Az azonosítási kiszolgáló webcíme nem tűnik érvényesnek",
-        "autodiscovery_invalid_is_base_url": "Érvénytelen base_url az m.identity_server -hez",
+        "autodiscovery_invalid_is_base_url": "Az m.identity_server beállításhoz megadott base_url érvénytelen",
         "autodiscovery_invalid_is_response": "Az azonosítási kiszolgáló felderítésére érkezett válasz érvénytelen",
         "autodiscovery_invalid_json": "Érvénytelen JSON",
         "autodiscovery_no_well_known": "Nem található .well-known JSON-fájl",
         "autodiscovery_unexpected_error_hs": "A Matrix-kiszolgáló konfiguráció betöltésekor váratlan hiba történt",
-        "autodiscovery_unexpected_error_is": "Az azonosítási kiszolgáló beállításainak feldolgozásánál váratlan hiba történt",
+        "autodiscovery_unexpected_error_is": "Az azonosítási kiszolgáló beállításainak feldolgozásakor váratlan hiba történt",
         "captcha_description": "A Matrix-kiszolgáló ellenőrizné, hogy Ön nem egy robot.",
         "change_password_action": "Jelszó módosítása",
         "change_password_confirm_invalid": "A jelszavak nem egyeznek meg",
@@ -176,110 +199,141 @@
         "change_password_error": "Hiba a jelszó módosítása során: %(error)s",
         "change_password_mismatch": "Az új jelszavak nem egyeznek",
         "change_password_new_label": "Új jelszó",
-        "check_email_explainer": "Kövesse az utasításokat amit elküldtünk ide: <b>%(email)s</b>",
+        "check_email_explainer": "Kövesse az ide küldött utasításokat: <b>%(email)s</b>",
         "check_email_resend_prompt": "Nem érkezett meg?",
-        "check_email_resend_tooltip": "E-mail a ellenőrzési hivatkozással újra elküldve!",
-        "check_email_wrong_email_button": "E-mail cím megadása újból",
-        "check_email_wrong_email_prompt": "Hibás e-mail cím?",
+        "check_email_resend_tooltip": "Az ellenőrzési hivatkozást tartalmazó e-mail újraküldve!",
+        "check_email_wrong_email_button": "E-mail-cím újbóli megadása",
+        "check_email_wrong_email_prompt": "Hibás e-mail-cím?",
         "continue_with_idp": "Folytatás ezzel a szolgáltatóval: %(provider)s",
         "continue_with_sso": "Folytatás ezzel: %(ssoButtons)s",
-        "country_dropdown": "Ország lenyíló menü",
-        "create_account_prompt": "Új vagy? <a>Készíts egy fiókot</a>",
+        "country_dropdown": "Ország legördülő menü",
+        "create_account_prompt": "Új itt? <a>Hozzon létre egy fiókot</a>",
         "create_account_title": "Fiók létrehozása",
-        "email_discovery_text": "Az e-mail (nem kötelező) megadása segíthet abban, hogy az ismerőseid megtaláljanak Matrix-on.",
+        "email_discovery_text": "Az e-mail-cím (nem kötelező) megadása segíthet abban, hogy az ismerősei megtalálják a Matrixon.",
         "email_field_label": "E-mail",
-        "email_field_label_invalid": "Az e-mail cím nem tűnik érvényesnek",
-        "email_field_label_required": "E-mail cím megadása",
-        "email_help_text": "Adj meg egy e-mail címet, hogy vissza tudd állítani a jelszavad.",
-        "email_phone_discovery_text": "Az e-mail, vagy telefonszám használatával a jelenlegi ismerőseid is megtalálhatnak.",
-        "enter_email_explainer": "<b>%(homeserver)s</b> e-mailt küld a jelszó beállítási hivatkozással.",
-        "enter_email_heading": "E-mail cím megadása a jelszó beállításhoz",
+        "email_field_label_invalid": "Az e-mail-cím nem tűnik érvényesnek",
+        "email_field_label_required": "E-mail-cím megadása",
+        "email_help_text": "Adjon hozzá egy e-mail-címet, hogy visszaállíthassa jelszavát.",
+        "email_phone_discovery_text": "Ha megadja az e-mail-címét vagy telefonszámát, akkor megtalálhatják a jelenlegi ismerősei.",
+        "enter_email_explainer": "A(z) <b>%(homeserver)s</b> e-mailt küld a jelszó-visszaállítási hivatkozással.",
+        "enter_email_heading": "Adja meg az e-mail-címét a jelszó visszaállításhoz",
         "failed_connect_identity_server": "Az azonosítási kiszolgáló nem érhető el",
-        "failed_connect_identity_server_other": "Beléphet, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló rendszergazdájával.",
-        "failed_connect_identity_server_register": "Regisztrálhat, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló rendszergazdájával.",
-        "failed_connect_identity_server_reset_password": "A jelszavát visszaállíthatja, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló rendszergazdájával.",
+        "failed_connect_identity_server_other": "Beléphet, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló adminisztrátorával.",
+        "failed_connect_identity_server_register": "Regisztrálhat, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló adminisztrátorával.",
+        "failed_connect_identity_server_reset_password": "A jelszavát visszaállíthatja, de néhány funkció nem lesz elérhető, amíg az azonosítási kiszolgáló újra elérhető nem lesz. Ha ezt a figyelmeztetést folyamatosan látja, akkor ellenőrizze a beállításokat, vagy vegye fel a kapcsolatot a kiszolgáló adminisztrátorával.",
         "failed_homeserver_discovery": "A Matrix-kiszolgáló felderítése sikertelen",
         "failed_query_registration_methods": "A támogatott regisztrációs módokat nem lehet lekérdezni.",
-        "failed_soft_logout_auth": "Újra bejelentkezés sikertelen",
+        "failed_soft_logout_auth": "Az újbóli hitelesítés sikertelen",
         "failed_soft_logout_homeserver": "Az újbóli hitelesítés a Matrix-kiszolgáló hibájából sikertelen",
-        "forgot_password_email_invalid": "Az e-mail cím nem tűnik érvényesnek.",
-        "forgot_password_email_required": "A fiókodhoz kötött e-mail címet add meg.",
-        "forgot_password_prompt": "Elfelejtetted a jelszavad?",
-        "forgot_password_send_email": "E-mail küldés",
+        "forgot_password_email_invalid": "Az e-mail-cím nem tűnik érvényesnek.",
+        "forgot_password_email_required": "A fiókjához kötött e-mail-címet kell megadnia.",
+        "forgot_password_prompt": "Elfelejtette a jelszavát?",
+        "forgot_password_send_email": "E-mail küldése",
         "identifier_label": "Bejelentkezés ezzel:",
         "incorrect_credentials": "Helytelen felhasználónév vagy jelszó.",
         "incorrect_credentials_detail": "Vegye figyelembe, hogy a(z) %(hs)s kiszolgálóra jelentkezik be, és nem a matrix.org-ra.",
         "incorrect_password": "Helytelen jelszó",
-        "log_in_new_account": "<a>Belépés</a> az új fiókodba.",
+        "log_in_new_account": "<a>Belépés</a> az új fiókjába.",
         "logout_dialog": {
-            "description": "Biztos, hogy ki akarsz jelentkezni?",
+            "description": "Biztos, hogy kijelentkezik?",
             "megolm_export": "Kulcsok kézi mentése",
-            "setup_key_backup_title": "Elveszted a hozzáférést a titkosított üzeneteidhez",
-            "setup_secure_backup_description_1": "A titkosított üzenetek végponttól végpontig titkosítással védettek. Csak neked és a címzetteknek lehet meg a kulcs az üzenet visszafejtéséhez.",
+            "setup_key_backup_title": "Elveszíti a hozzáférést a titkosított üzeneteihez",
+            "setup_secure_backup_description_1": "A titkosított üzenetek végpontok közti titkosítással védettek. Csak Önnek és a címzetteknek lehet meg a kulcsuk az üzenet visszafejtéséhez.",
             "setup_secure_backup_description_2": "A kijelentkezéssel a kulcsok az eszközről törlődnek, ami azt jelenti, hogy ha nincsenek meg máshol a kulcsok, vagy nincsenek mentve a kiszolgálón, akkor a titkosított üzenetek olvashatatlanná válnak.",
             "skip_key_backup": "Nincs szükségem a titkosított üzeneteimre",
-            "use_key_backup": "Kulcs mentés használatának megkezdése"
+            "use_key_backup": "Kulcsmentés használatának megkezdése"
         },
-        "misconfigured_body": "Kérje meg a(z) %(brand)s rendszergazdáját, hogy ellenőrizze a <a>beállításait</a>, hibás vagy duplikált bejegyzéseket keresve.",
-        "misconfigured_title": "A(z) %(brand)s alkalmazás hibásan van beállítva",
-        "msisdn_field_description": "Mások meghívhatnak a szobákba a kapcsolatoknál megadott adataiddal",
-        "msisdn_field_label": "Telefon",
-        "msisdn_field_number_invalid": "Ez a telefonszám nem tűnik teljesen helyesnek, kérlek ellenőrizd újra",
+        "misconfigured_body": "Kérje meg a(z) %(brand)s adminisztrátorát, hogy ellenőrizze a <a>beállításait</a>, hibás vagy duplikált bejegyzéseket keresve.",
+        "misconfigured_title": "Az %(brand)s hibásan van beállítva",
+        "mobile_create_account_title": "Fiók létrehozására készül a következőn:%(hsName)s",
+        "msisdn_field_description": "Meghívhatják mások szobákba a kapcsolatoknál megadott adatai alapján",
+        "msisdn_field_label": "Telefonszám",
+        "msisdn_field_number_invalid": "Ez a telefonszám nem tűnik teljesen helyesnek, ellenőrizze újra",
         "msisdn_field_required_invalid": "Telefonszám megadása",
         "no_hs_url_provided": "Nincs megadva a Matrix-kiszolgáló webcíme",
         "oidc": {
             "error_title": "Sajnos nem tudtuk bejelentkeztetni",
+            "generic_auth_error": "Hiba történt a hitelesítés során. Lépjen a bejelentkezési oldalra, és próbálja újra.",
             "missing_or_invalid_stored_state": "A böngészőt arra kértük, hogy jegyezze meg, melyik Matrix-kiszolgálót használta a bejelentkezéshez, de sajnos a böngészője elfelejtette. Navigáljon a bejelentkezési oldalra, és próbálja újra."
         },
         "password_field_keep_going_prompt": "Így tovább…",
         "password_field_label": "Adja meg a jelszót",
         "password_field_strong_label": "Szép, erős jelszó!",
         "password_field_weak_label": "A jelszó engedélyezett, de nem biztonságos",
-        "phone_label": "Telefon",
+        "phone_label": "Telefonszám",
         "phone_optional_label": "Telefonszám (nem kötelező)",
         "qr_code_login": {
+            "check_code_explainer": "Ezzel ellenőrizni fogja, hogy a másik eszközzel való kapcsolat biztonságos-e.",
+            "check_code_heading": "Adja meg a másik készüléken megjelenő számot",
+            "check_code_input_label": "2 számjegyű kód",
+            "check_code_mismatch": "A számok nem egyeznek meg",
             "completing_setup": "Új eszköz beállításának elvégzése",
-            "error_unexpected": "Nemvárt hiba történt.",
-            "scan_code_instruction": "A kijelentkezett eszközzel olvasd be a QR kódot alább.",
-            "scan_qr_code": "QR kód beolvasása",
-            "select_qr_code": "Kiválasztás „%(scanQRCode)s”",
+            "error_etag_missing": "Váratlan hiba történt. Ennek oka lehet egy böngészőbővítmény, proxykiszolgáló vagy kiszolgáló hibás konfigurációja.",
+            "error_expired": "A bejelentkezés lejárt. Próbálja újra.",
+            "error_expired_title": "A bejelentkezés nem fejeződött be időben",
+            "error_insecure_channel_detected": "Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. Meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk.",
+            "error_insecure_channel_detected_instructions": "És most mi lesz?",
+            "error_insecure_channel_detected_instructions_1": "Próbáljon meg újra bejelentkezni a másik eszközre QR-kóddal, ha hálózati probléma lépett fel",
+            "error_insecure_channel_detected_instructions_2": "Ha ugyanezzel a problémával találkozik, próbáljon ki egy másik Wi-Fi-hálózatot, vagy Wi-Fi helyett használja mobiladat-kapcsolatát",
+            "error_insecure_channel_detected_instructions_3": "Ha ez nem működik, akkor jelentkezzen be kézileg",
+            "error_insecure_channel_detected_title": "A kapcsolat nem biztonságos",
+            "error_other_device_already_signed_in": "Semmi mást nem kell tennie.",
+            "error_other_device_already_signed_in_title": "A másik eszköz már be van jelentkezve",
+            "error_rate_limited": "Túl sok próbálkozás rövid időn belül. Várjon egy kicsit, mielőtt újra próbálkozna.",
+            "error_unexpected": "Váratlan hiba történt. A másik eszköz csatlakoztatására vonatkozó kérés megszakadt.",
+            "error_unsupported_protocol": "Ez az eszköz nem támogatja a másik eszközre QR-kóddal való bejelentkezést.",
+            "error_unsupported_protocol_title": "A másik eszköz nem kompatibilis",
+            "error_user_cancelled": "A bejelentkezés megszakadt a másik eszközön.",
+            "error_user_cancelled_title": "Bejelentkezési kérés törölve",
+            "error_user_declined": "Elutasította a másik eszközéről érkező bejelentkezési kérést.",
+            "error_user_declined_title": "A bejelentkezés elutasítva",
+            "follow_remaining_instructions": "Kövesse az utasításokat a másik eszköz összekapcsolásához",
+            "open_element_other_device": "Nyissa meg a másik eszközön ezt: %(brand)s",
+            "point_the_camera": "Irányítsa a kamerát az itt látható QR-kódra",
+            "scan_code_instruction": "Olvassa be a QR-kódot egy másik eszközzel",
+            "scan_qr_code": "Bejelentkezés QR-kóddal",
+            "security_code": "Biztonsági kód",
+            "security_code_prompt": "Ha a rendszer kéri, írja be az alábbi kódot a másik eszközön.",
+            "select_qr_code": "Válassza a „%(scanQRCode)s” lehetőséget",
+            "unsupported_explainer": "Fiókszolgáltatója nem támogatja a bejelentkezést egy új eszközre QR-kóddal.",
+            "unsupported_heading": "A QR-kód nem támogatott",
             "waiting_for_device": "Várakozás a másik eszköz bejelentkezésére"
         },
         "register_action": "Fiók létrehozása",
         "registration": {
             "continue_without_email_description": "Csak egy figyelmeztetés, ha nem ad meg e-mail-címet, és elfelejti a jelszavát, akkor <b>véglegesen elveszíti a hozzáférést a fiókjához</b>.",
-            "continue_without_email_field_label": "E-mail (nem kötelező)",
+            "continue_without_email_field_label": "E-mail-cím (nem kötelező)",
             "continue_without_email_title": "Folytatás e-mail-cím nélkül"
         },
         "registration_disabled": "A regisztráció ki van kapcsolva ezen a Matrix-kiszolgálón.",
         "registration_msisdn_field_required_invalid": "Telefonszám megadása (ennél a Matrix-kiszolgálónál kötelező)",
-        "registration_successful": "Regisztráció sikeres",
-        "registration_username_in_use": "Valaki már használja ezt a felhasználói nevet. Próbáljon ki másikat, illetve ha ön az, jelentkezzen be alább.",
-        "registration_username_unable_check": "A felhasználói név foglaltságának ellenőrzése nem sikerült. Kérjük próbálja meg később.",
+        "registration_successful": "Sikeres regisztráció",
+        "registration_username_in_use": "Valaki már használja ezt a felhasználónevet. Próbáljon ki egy másikat, illetve ha Ön az, akkor jelentkezzen be lent.",
+        "registration_username_unable_check": "A felhasználónév foglaltságának ellenőrzése nem sikerült. Próbálja meg később.",
         "registration_username_validation": "Csak kisbetűt, számokat, kötőjeleket és aláhúzásokat használj",
         "reset_password": {
             "confirm_new_password": "Új jelszó megerősítése",
             "devices_logout_success": "Az összes eszközéről kijelentkezett és leküldéses értesítéseket sem fog kapni. Az értesítések újbóli engedélyezéséhez újra be kell jelentkezni az egyes eszközökön.",
             "other_devices_logout_warning_1": "A kijelentkezéssel az üzeneteket titkosító kulcsokat az eszközök törlik magukról ami elérhetetlenné teheti a régi titkosított csevegéseket.",
-            "other_devices_logout_warning_2": "Ha szeretné megtartani a hozzáférést a titkosított szobákban lévő csevegésekhez, állítson be Kulcs mentést vagy exportálja ki a kulcsokat valamelyik eszközéről mielőtt továbblép.",
+            "other_devices_logout_warning_2": "Ha szeretné megtartani a hozzáférést a titkosított szobákban lévő csevegésekhez, állítson be kulcsmentést vagy exportálja a kulcsokat valamelyik eszközéről mielőtt továbblép.",
             "password_not_entered": "Új jelszót kell megadni.",
             "passwords_mismatch": "Az új jelszavaknak meg kell egyezniük egymással.",
             "rate_limit_error": "Rövid idő alatt túl sok próbálkozás. Várjon egy kicsit mielőtt újra próbálkozik.",
             "rate_limit_error_with_time": "Rövid idő alatt túl sok próbálkozás. Próbálkozzon ennyi idő múlva: %(timeout)s.",
-            "reset_successful": "A jelszavad újra beállításra került.",
+            "reset_successful": "A jelszava vissza lett állítva.",
             "return_to_login": "Vissza a bejelentkezési képernyőre",
-            "sign_out_other_devices": "Kijelentkezés minden eszközből"
+            "sign_out_other_devices": "Kijelentkezés az összes eszközről"
         },
         "reset_password_action": "Jelszó visszaállítása",
         "reset_password_button": "Elfelejtette a jelszavát?",
-        "reset_password_email_field_description": "A felhasználói fiók visszaszerzése e-mail címmel",
+        "reset_password_email_field_description": "A felhasználói fiók visszaszerzése e-mail-címmel",
         "reset_password_email_field_required_invalid": "E-mail-cím megadása (ezen a Matrix-kiszolgálón kötelező)",
         "reset_password_email_not_associated": "Úgy tűnik, hogy ez az e-mail-cím nincs összekötve Matrix-azonosítóval ezen a Matrix-kiszolgálón.",
         "reset_password_email_not_found_title": "Az e-mail-cím nem található",
-        "reset_password_title": "Jelszó megváltoztatása",
+        "reset_password_title": "Jelszó visszaállítása",
         "server_picker_custom": "Másik Matrix-kiszolgáló",
-        "server_picker_description": "Használhatja a más szerver opciót, hogy egy másik matrix szerverre jelentkezz be amihez megadod a szerver url címét. Ezzel használhatja a(z) %(brand)s klienst egy már létező Matrix fiókkal egy másik matrix szerveren.",
-        "server_picker_description_matrix.org": "Csatlakozzon több millió felhasználóhoz ingyen a legnagyobb nyilvános szerveren",
+        "server_picker_description": "Használhatja az egyéni kiszolgáló lehetőséget, hogy egy másik Matrix-kiszolgáló címének megadásával jelentkezzen be. Ezzel használhatja az %(brand)s klienst egy már létező Matrix-fiókkal, egy másik Matrix-kiszolgálón.",
+        "server_picker_description_matrix.org": "Csatlakozzon több millió felhasználóhoz ingyen, a legnagyobb nyilvános kiszolgálón",
         "server_picker_dialog_title": "Döntse el, hol szeretne fiókot létrehozni",
         "server_picker_explainer": "Használja a választott Matrix-kiszolgálóját, ha van ilyenje, vagy üzemeltessen egy sajátot.",
         "server_picker_failed_validate_homeserver": "A Matrix-kiszolgálót nem lehet ellenőrizni",
@@ -289,9 +343,9 @@
         "server_picker_matrix.org": "A matrix.org a legnagyobb nyilvános Matrix-kiszolgáló a világon, és sok felhasználónak megfelelő választás.",
         "server_picker_required": "Matrix-kiszolgáló megadása",
         "server_picker_title": "Bejelentkezés a Matrix-kiszolgálójába",
-        "server_picker_title_default": "Szerver lehetőségek",
+        "server_picker_title_default": "Kiszolgálóbeállítások",
         "server_picker_title_registration": "Fiók létrehozása itt:",
-        "session_logged_out_description": "A biztonság érdekében ez a kapcsolat le lesz bontva. Légy szíves jelentkezz be újra.",
+        "session_logged_out_description": "A biztonság érdekében ez a munkamenet ki lesz jelentkeztetve. Jelentkezzen be újra.",
         "session_logged_out_title": "Kijelentkezett",
         "set_email": {
             "description": "Ez lehetővé teszi, hogy vissza tudja állítani a jelszavát, és értesítéseket fogadjon.",
@@ -301,21 +355,21 @@
         "set_email_prompt": "Szeretne beállítani e-mail-címet?",
         "sign_in_description": "Használja a fiókját a továbblépéshez.",
         "sign_in_instead": "Bejelentkezés inkább",
-        "sign_in_instead_prompt": "Bejelentkezés inkább",
+        "sign_in_instead_prompt": "Már van fiókja? <a>Jelentkezzen be itt</a>",
         "sign_in_or_register": "Bejelentkezés vagy fiók létrehozása",
         "sign_in_or_register_description": "A folytatáshoz használja a fiókját, vagy hozzon létre egy újat.",
-        "sign_in_prompt": "Van már fiókod? <a>Jelentkezz be</a>",
+        "sign_in_prompt": "Van már fiókja? <a>Jelentkezzen be</a>",
         "sign_in_with_sso": "Bejelentkezés „egyszeri bejelentkezéssel”",
         "signing_in": "Bejelentkezés…",
         "soft_logout": {
-            "clear_data_button": "Minden adat törlése",
-            "clear_data_description": "Az adatok törlése ebből a munkamenetből végleges. A titkosított üzenetek elvesznek hacsak nincsenek elmentve a kulcsai.",
-            "clear_data_title": "Minden adat törlése ebben a munkamenetben?"
-        },
-        "soft_logout_heading": "Kijelentkeztél",
-        "soft_logout_intro_password": "Add meg a jelszavadat a belépéshez, hogy visszaszerezd a hozzáférésed a fiókodhoz.",
-        "soft_logout_intro_sso": "Jelentkezz be és szerezd vissza a hozzáférésed a fiókodhoz.",
-        "soft_logout_intro_unsupported_auth": "Nem tud bejelentkezni a fiókjába. További információkért vegye fel a kapcsolatot a Matrix-kiszolgáló rendszergazdájával.",
+            "clear_data_button": "Összes adat törlése",
+            "clear_data_description": "Az adatok törlése ebből a munkamenetből végleges. A titkosított üzenetek elvesznek, hacsak nincsenek mentve a kulcsai.",
+            "clear_data_title": "Törli az összes adatot ebben a munkamenetben?"
+        },
+        "soft_logout_heading": "Kijelentkezett",
+        "soft_logout_intro_password": "Adja meg a jelszavát a belépéshez, hogy visszaszerezze a hozzáférését a fiókjához.",
+        "soft_logout_intro_sso": "Jelentkezzen be, és szerezze vissza a hozzáférését a fiókjához.",
+        "soft_logout_intro_unsupported_auth": "Nem tud bejelentkezni a fiókjába. További információkért vegye fel a kapcsolatot a Matrix-kiszolgáló adminisztrátorával.",
         "soft_logout_subheading": "Személyes adatok törlése",
         "soft_logout_warning": "Figyelmeztetés: A személyes adatai (beleértve a titkosító kulcsokat is) továbbra is az eszközön vannak tárolva. Ha az eszközt nem használja tovább vagy másik fiókba szeretne bejelentkezni, törölje őket.",
         "sso": "Egyszeri bejelentkezés",
@@ -326,23 +380,26 @@
         "syncing": "Szinkronizálás…",
         "uia": {
             "code": "Kód",
-            "email": "A fiók elkészítéséhez nyissa meg az e-mailben elküldött hivatkozást amit erre a címre küldtünk: %(emailAddress)s.",
-            "email_auth_header": "Ellenőrizze az e-mail-t a továbblépéshez",
+            "email": "A fiók elkészítéséhez nyissa meg az e-mailben elküldött hivatkozást, melyet erre a címre küldtünk: %(emailAddress)s.",
+            "email_auth_header": "Ellenőrizze a leveleit a továbblépéshez",
             "email_resend_prompt": "Nem kapta meg? <a>Újraküldés</a>",
             "email_resent": "Újraküldve!",
             "fallback_button": "Hitelesítés indítása",
-            "msisdn": "Szöveges üzenetet küldtünk neki: %(msisdn)s",
+            "mas_cross_signing_reset_cta": "Ugrás a fiókjához",
+            "mas_cross_signing_reset_description": "Állítsa alaphelyzetbe személyazonosságát a fiókszolgáltatón keresztül, majd térjen vissza, és kattintson az „Újra” gombra.",
+            "mas_cross_signing_reset_title": "Ugorjon a fiókjához a személyazonossága alaphelyzetbe állításához",
+            "msisdn": "Szöveges üzenet küldve ide: %(msisdn)s",
             "msisdn_token_incorrect": "Helytelen token",
-            "msisdn_token_prompt": "Add meg a benne lévő kódot:",
-            "password_prompt": "A fiók jelszó megadásával erősítsd meg a személyazonosságodat.",
-            "recaptcha_missing_params": "A Matrix-kiszolgáló konfigurációjából hiányzik a captcha nyilvános kulcsa. Értesítse erről a Matrix-kiszolgáló rendszergazdáját.",
+            "msisdn_token_prompt": "Adja meg a benne lévő kódot:",
+            "password_prompt": "Erősítse meg a személyazonosságát a fiókja jelszavának megadásával.",
+            "recaptcha_missing_params": "A Matrix-kiszolgáló konfigurációjából hiányzik a captcha nyilvános kulcsa. Értesítse erről a Matrix-kiszolgáló adminisztrátorát.",
             "registration_token_label": "Regisztrációs token",
-            "registration_token_prompt": "Adja meg a regisztrációs tokent, amelyet a Matrix-kiszolgáló rendszergazdája adott meg.",
+            "registration_token_prompt": "Adja meg a regisztrációs tokent, amelyet a Matrix-kiszolgáló adminisztrátora adott meg.",
             "sso_body": "Erősítse meg az e-mail-cím hozzáadását azáltal, hogy az egyszeri bejelentkezéssel bizonyítja a személyazonosságát.",
             "sso_failed": "A személyazonosság ellenőrzésénél valami hiba történt. Megszakítás és próbálja újra.",
             "sso_postauth_body": "A személyazonossága megerősítéséhez kattintson a lenti gombra.",
-            "sso_postauth_title": "Erősítsd meg a továbblépéshez",
-            "sso_preauth_body": "A folytatáshoz, használja az egyszeri bejelentkezést, hogy megerősítse a személyazonosságát.",
+            "sso_postauth_title": "Erősítse meg a továbblépéshez",
+            "sso_preauth_body": "A folytatáshoz használja az egyszeri bejelentkezést, hogy megerősítse a személyazonosságát.",
             "sso_title": "A folytatáshoz használja az egyszeri bejelentkezést (SSO)",
             "terms": "Nézze át és fogadja el a Matrix-kiszolgáló felhasználási feltételeit:",
             "terms_invalid": "Nézze át és fogadja el a Matrix-kiszolgáló felhasználási feltételeit"
@@ -350,41 +407,49 @@
         "unsupported_auth": "Ez a Matrix-kiszolgáló nem biztosít olyan bejelentkezési folyamatot, amelyet a kliens támogatna.",
         "unsupported_auth_email": "Ez a Matrix-kiszolgáló nem támogatja az e-mail-címmel történő bejelentkezést.",
         "unsupported_auth_msisdn": "Ez a kiszolgáló nem támogatja a telefonszámmal történő hitelesítést.",
-        "username_field_required_invalid": "Felhasználói név megadása",
+        "username_field_required_invalid": "Felhasználónév megadása",
         "username_in_use": "Ez a felhasználónév már foglalt, próbáljon ki másikat.",
-        "verify_email_explainer": "Tudnunk kell, hogy Ön tényleg az akinek mondja magát mielőtt a jelszót beállíthatja. Kattintson a hivatkozásra az e-mailben amit éppen most küldtünk ide: <b>%(email)s</b>",
-        "verify_email_heading": "E-mail ellenőrzés a továbblépéshez"
+        "verify_email_explainer": "Mielőtt visszaállíthatja a jelszót, tudnunk kell, hogy tényleg az, akinek mondja magát. Kattintson a hivatkozásra az e-mailben, melyet épp most küldtünk ide: <b>%(email)s</b>",
+        "verify_email_heading": "Erősítse meg az e-mailt a továbblépéshez"
     },
     "bug_reporting": {
         "additional_context": "Ha a hiba felderítésében további adatok is segíthetnek, mint az, hogy mit csinált épp, mik a szobák vagy felhasználók azonosítói, stb. Ezeket itt adja meg.",
         "before_submitting": "Mielőtt elküldi a naplókat, <a>hozzon létre egy jegyet a GitHubon</a>, amelyben leírja a problémáját.",
         "collecting_information": "Alkalmazás verzióinformációinak összegyűjtése",
         "collecting_logs": "Naplók összegyűjtése",
-        "create_new_issue": "Ahhoz hogy megvizsgálhassuk a hibát, <newIssueLink>hozzon létre egy új hibajegyet</newIssueLink> a GitHubon.",
+        "create_new_issue": "Hogy megvizsgálhassuk a hibát, <newIssueLink>hozzon létre egy új jegyet</newIssueLink> a GitHubon.",
         "description": "A hibakeresési naplók alkalmazáshasználati adatokat tartalmaznak, amelyek tartalmazzák a felkeresett szobák azonosítóit vagy álneveit, a legutóbb használt felületi elemeket, valamint a többi felhasználó neveit. A csevegőüzenetek szövegét nem tartalmazza.",
         "download_logs": "Naplók letöltése",
         "downloading_logs": "Naplók letöltése folyamatban",
-        "error_empty": "Kérlek mond el nekünk mi az ami nem működött, vagy még jobb, ha egy GitHub jegyben leírod a problémát.",
-        "failed_send_logs": "Hiba a napló küldésénél: ",
+        "error_empty": "Mondja el nekünk, hogy mi az, ami nem működött, vagy még jobb, ha egy GitHub-jegyben írja le a problémát.",
+        "failed_download_logs": "Nem sikerült letölteni a hibakeresési naplókat: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "A hibajelentését elutasították. A rageshake kiszolgáló nem támogatja ezt az alkalmazást.",
+            "rejected_generic": "A hibajelentését elutasították. A rageshake kiszolgáló egy házirend miatt elutasította a jelentés tartalmát.",
+            "rejected_recovery_key": "A hibajelentést biztonsági okokból elutasították, mivel helyreállítási kulcsot tartalmazott.",
+            "rejected_version": "Hibajelentését elutasították, mivel az Ön által használt verzió túl régi.",
+            "server_unknown_error": "A rageshake kiszolgáló ismeretlen hibát észlelt, és nem tudta kezelni a jelentést.",
+            "unknown_error": "A naplók elküldése sikertelen."
+        },
         "github_issue": "GitHub-jegy",
         "introduction": "Ha a GitHubon keresztül küldött be hibajegyet, akkor a hibakeresési naplók segítenek nekünk felderíteni a problémát. ",
         "log_request": "Segítsen abban, hogy ez később ne fordulhasson elő, <a>küldje el nekünk a naplókat</a>.",
-        "logs_sent": "Napló elküldve",
+        "logs_sent": "Naplók elküldve",
         "matrix_security_issue": "A Matrixszal kapcsolatos biztonsági hibák jelentésével kapcsolatban olvassa el a Matrix.org <a>biztonsági hibák közzétételi házirendjét</a>.",
-        "preparing_download": "Napló előkészítése feltöltéshez",
-        "preparing_logs": "Előkészülés napló küldéshez",
+        "preparing_download": "Naplók előkészítése a feltöltéshez",
+        "preparing_logs": "Felkészülés a naplók küldéséhez",
         "send_logs": "Naplók elküldése",
         "submit_debug_logs": "Hibakeresési naplók elküldése",
         "textarea_label": "Megjegyzések",
         "thank_you": "Köszönjük!",
         "title": "Hibajelentés",
-        "unsupported_browser": "Emlékeztető: A böngésződ nem támogatott, így az élmény kiszámíthatatlan lehet.",
+        "unsupported_browser": "Emlékeztető: A böngészője nem támogatott, így az élmény kiszámíthatatlan lehet.",
         "uploading_logs": "Naplók feltöltése folyamatban",
         "waiting_for_server": "Várakozás a kiszolgáló válaszára"
     },
     "cannot_invite_without_identity_server": "Matrix-kiszolgáló nélkül nem lehet felhasználókat meghívni e-mailben. Kapcsolódjon egyhez a „Beállítások” alatt.",
     "cannot_reach_homeserver": "A Matrix-kiszolgáló nem érhető el",
-    "cannot_reach_homeserver_detail": "Győződjön meg arról, hogy stabil az internetkapcsolata, vagy vegye fel a kapcsolatot a kiszolgáló rendszergazdájával",
+    "cannot_reach_homeserver_detail": "Győződjön meg arról, hogy stabil az internetkapcsolata, vagy vegye fel a kapcsolatot a kiszolgáló adminisztrátorával",
     "cant_load_page": "Az oldal nem tölthető be",
     "chat_card_back_action_label": "Vissza a csevegéshez",
     "chat_effects": {
@@ -405,28 +470,28 @@
         "access_token": "Hozzáférési kulcs",
         "accessibility": "Akadálymentesség",
         "advanced": "Speciális",
-        "all_rooms": "Összes szoba",
+        "all_chats": "Összes csevegés",
         "analytics": "Analitika",
         "and_n_others": {
-            "other": "és még: %(count)s ...",
-            "one": "és még egy..."
+            "és még: %(count)s ...": "other",
+            "és még egy...": "one"
         },
         "appearance": "Megjelenítés",
         "application": "Alkalmazás",
         "are_you_sure": "Biztos?",
         "attachment": "Melléklet",
-        "authentication": "Azonosítás",
+        "authentication": "Hitelesítés",
         "avatar": "Profilkép",
         "beta": "Béta",
         "camera": "Kamera",
         "cameras": "Kamerák",
+        "cancel": "Mégse",
         "capabilities": "Képességek",
         "copied": "Másolva!",
         "credits": "Közreműködők",
-        "cross_signing": "Eszközök közti hitelesítés",
         "dark": "Sötét",
         "description": "Leírás",
-        "deselect_all": "Semmit nem jelöl ki",
+        "deselect_all": "Kijelölés megszüntetése",
         "device": "Eszköz",
         "edited": "szerkesztve",
         "email_address": "E-mail-cím",
@@ -440,7 +505,7 @@
         "filter_results": "Találatok szűrése",
         "forward_message": "Üzenet továbbítása",
         "general": "Általános",
-        "go_to_settings": "Irány a Beállítások",
+        "go_to_settings": "Ugrás a Beállításokhoz",
         "guest": "Vendég",
         "help": "Súgó",
         "historical": "Archív",
@@ -449,7 +514,7 @@
         "identity_server": "Azonosítási kiszolgáló",
         "image": "Kép",
         "integration_manager": "Integrációkezelő",
-        "joined": "Csatlakozott",
+        "joined": "Csatlakozva",
         "labs": "Labor",
         "legal": "Jogi feltételek",
         "light": "Világos",
@@ -459,17 +524,17 @@
         "matrix": "Matrix",
         "message": "Üzenet",
         "message_layout": "Üzenet elrendezése",
+        "message_timestamp_invalid": "Érvénytelen időbélyeg",
         "microphone": "Mikrofon",
         "model": "Modell",
+        "moderation_and_safety": "Moderálás és biztonság",
         "modern": "Modern",
         "mute": "Némítás",
         "n_members": {
-            "one": "%(count)s tag",
-            "other": "%(count)s tag"
+            "%(count)s tag": "other"
         },
         "n_rooms": {
-            "one": "%(count)s szoba",
-            "other": "%(count)s szoba"
+            "%(count)s szoba": "other"
         },
         "name": "Név",
         "no_results": "Nincs találat",
@@ -493,41 +558,43 @@
         "public": "Nyilvános",
         "public_room": "Nyilvános szoba",
         "public_space": "Nyilvános tér",
-        "qr_code": "QR kód",
+        "qr_code": "QR-kód",
         "random": "Véletlen",
         "reactions": "Reakciók",
+        "recommended": "Ajánlott",
         "report_a_bug": "Hibajegy feladása",
         "room": "Szoba",
         "room_name": "Szoba neve",
         "rooms": "Szobák",
+        "save": "Mentés",
+        "saved": "Mentve",
         "saving": "Mentés…",
         "secure_backup": "Biztonsági mentés",
-        "security": "Biztonság",
-        "select_all": "Mindet kijelöli",
+        "select_all": "Összes kijelölése",
         "server": "Kiszolgáló",
         "settings": "Beállítások",
-        "setup_secure_messages": "Biztonságos Üzenetek beállítása",
+        "setup_secure_messages": "Biztonságos üzenetek beállítása",
         "show_more": "Több megjelenítése",
         "someone": "Valaki",
         "space": "Tér",
         "spaces": "Terek",
         "sticker": "Matrica",
-        "stickerpack": "Matrica csomag",
+        "stickerpack": "Matricacsomag",
         "success": "Sikeres",
         "suggestions": "Javaslatok",
         "support": "Támogatás",
-        "system_alerts": "Rendszer figyelmeztetések",
+        "system_alerts": "Rendszer-figyelmeztetések",
         "theme": "Téma",
         "thread": "Üzenetszál",
         "threads": "Üzenetszálak",
         "timeline": "Idővonal",
-        "trusted": "Megbízható",
         "unavailable": "nem érhető el",
         "unencrypted": "Titkosítatlan",
         "unmute": "Némítás visszavonása",
         "unnamed_room": "Névtelen szoba",
         "unnamed_space": "Névtelen tér",
         "unverified": "Ellenőrizetlen",
+        "updating": "Frissítés…",
         "user": "Felhasználó",
         "user_avatar": "Profilkép",
         "username": "Felhasználói név",
@@ -535,21 +602,21 @@
         "verified": "Ellenőrizve",
         "version": "Verzió",
         "video": "Videó",
-        "video_room": "Videó szoba",
+        "video_room": "Videószoba",
         "view_message": "Üzenet megjelenítése",
         "warning": "Figyelmeztetés"
     },
     "composer": {
         "autocomplete": {
             "@room_description": "Az egész szoba értesítése",
-            "command_a11y": "Parancs automatikus kiegészítés",
+            "command_a11y": "Parancsok automatikus kiegészítése",
             "command_description": "Parancsok",
-            "emoji_a11y": "Emodzsi automatikus kiegészítése",
-            "notification_a11y": "Értesítés automatikus kiegészítése",
-            "notification_description": "Szoba értesítések",
-            "room_a11y": "Szoba automatikus kiegészítése",
-            "space_a11y": "Tér automatikus kiegészítése",
-            "user_a11y": "Felhasználó automatikus kiegészítése",
+            "emoji_a11y": "Emodzsik automatikus kiegészítése",
+            "notification_a11y": "Értesítések automatikus kiegészítése",
+            "notification_description": "Szobaértesítés",
+            "room_a11y": "Szobák automatikus kiegészítése",
+            "space_a11y": "Terek automatikus kiegészítése",
+            "user_a11y": "Felhasználók automatikus kiegészítése",
             "user_description": "Felhasználók"
         },
         "close_sticker_picker": "Matricák elrejtése",
@@ -559,7 +626,7 @@
         "format_decrease_indent": "Behúzás csökkentése",
         "format_increase_indent": "Behúzás növelése",
         "format_inline_code": "Kód",
-        "format_insert_link": "Link beillesztése",
+        "format_insert_link": "Hivatkozás beillesztése",
         "format_italic": "Dőlt",
         "format_italics": "Dőlt",
         "format_link": "Hivatkozás",
@@ -576,7 +643,7 @@
         },
         "mode_plain": "Formázás elrejtése",
         "mode_rich_text": "Formázás megjelenítése",
-        "no_perms_notice": "Nincs jogod üzenetet küldeni ebbe a szobába",
+        "no_perms_notice": "Nincs joga üzenetet küldeni ebbe a szobába",
         "placeholder": "Üzenet küldése…",
         "placeholder_encrypted": "Titkosított üzenet küldése…",
         "placeholder_reply": "Válasz küldése…",
@@ -590,40 +657,40 @@
         "room_upgraded_link": "A beszélgetés itt folytatódik.",
         "room_upgraded_notice": "Ezt a szobát lecseréltük és nem aktív többé.",
         "send_button_title": "Üzenet küldése",
-        "send_button_voice_message": "Hang üzenet küldése",
-        "send_voice_message": "Hang üzenet küldése",
-        "stop_voice_message": "Felvétel megállítása",
-        "voice_message_button": "Hang üzenet"
+        "send_button_voice_message": "Hangüzenet küldése",
+        "send_voice_message": "Hangüzenet küldése",
+        "stop_voice_message": "Felvétel leállítása",
+        "voice_message_button": "Hangüzenet"
     },
-    "console_dev_note": "Ha tudja mit csinál, Element egy nyílt forráskódú szoftver, nézze meg a GitHubon (https://github.com/vector-im/element-web/) és segítsen!",
+    "console_dev_note": "Ha tudja mit csinál, az Element nyílt forráskódú szoftver, nézze meg a GitHubon (https://github.com/vector-im/element-web/) és működjön közre!",
     "console_scam_warning": "Ha valaki azt kéri hogy másoljon/illesszen be itt valamit, nagy esély van rá hogy valaki becsapja!",
     "console_wait": "Várjon!",
     "create_room": {
         "action_create_room": "Szoba létrehozása",
         "action_create_video_room": "Videószoba létrehozása",
-        "encrypted_video_room_warning": "Ezt később nem lehet kikapcsolni. A szoba titkosítva lesz de a hívások nem.",
-        "encrypted_warning": "Ezt később nem lehet kikapcsolni. A hidak és a legtöbb bot nem fog működni egyenlőre.",
-        "encryption_forced": "A szervered megköveteli, hogy a titkosítás be legyen kapcsolva a privát szobákban.",
-        "encryption_label": "Végpontok közötti titkosítás engedélyezése",
-        "error_title": "Szoba létrehozása sikertelen",
+        "encrypted_video_room_warning": "Ezt később nem lehet kikapcsolni. A szoba titkosítva lesz, de a beágyazott hívás nem.",
+        "encrypted_warning": "Ezt később nem lehet kikapcsolni. A hidak és a legtöbb bot egyelőre nem fog működni.",
+        "encryption_forced": "A kiszolgálója megköveteli, hogy a titkosítás be legyen kapcsolva a privát szobákban.",
+        "encryption_label": "Végpontok közti titkosítás engedélyezése",
+        "error_title": "A szoba létrehozása sikertelen",
         "generic_error": "A kiszolgáló elérhetetlen, túlterhelt vagy hibára futott.",
-        "join_rule_change_notice": "A szoba beállításokban ezt bármikor megváltoztathatja.",
+        "join_rule_change_notice": "A szoba beállításaiban ezt bármikor megváltoztathatja.",
         "join_rule_invite": "Privát szoba (csak meghívóval)",
         "join_rule_invite_label": "Csak a meghívott emberek fogják megtalálni és tudnak belépni a szobába.",
-        "join_rule_knock_label": "Bárki kérheti a belépést, de az adminoknak vagy moderátoroknak kell engedélyezniük. Ezt később megváltoztathatja.",
+        "join_rule_knock_label": "Bárki kérheti a belépést, de az adminisztrátoroknak vagy moderátoroknak kell engedélyezniük. Ezt később megváltoztathatja.",
         "join_rule_public_label": "Bárki megtalálhatja és beléphet ebbe a szobába.",
         "join_rule_public_parent_space_label": "Bárki megtalálhatja és beléphet a szobába, nem csak <SpaceName/> tér tagsága.",
-        "join_rule_restricted": "Tér tagság számára látható",
-        "join_rule_restricted_label": "<SpaceName/> téren bárki megtalálhatja és beléphet a szobába.",
-        "name_validation_required": "Kérlek adj meg egy nevet a szobához",
-        "room_visibility_label": "Szoba láthatóság",
+        "join_rule_restricted": "Tér tagsága számára látható",
+        "join_rule_restricted_label": "A(z) <SpaceName/> térben bárki megtalálhatja és beléphet a szobába.",
+        "name_validation_required": "Adjon meg egy nevet a szobához",
+        "room_visibility_label": "Szoba láthatósága",
         "title_private_room": "Privát szoba létrehozása",
         "title_public_room": "Nyilvános szoba létrehozása",
         "title_video_room": "Videószoba létrehozása",
         "topic_label": "Téma (nem kötelező)",
-        "unfederated": "A szobába ne léphessenek be azok, akik nem ezen a szerveren vannak: %(serverName)s.",
-        "unfederated_label_default_off": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.",
-        "unfederated_label_default_on": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.",
+        "unfederated": "A szobába ne léphessenek be azok, akik nem ezen a kiszolgálón vannak: %(serverName)s.",
+        "unfederated_label_default_off": "Beállíthatja, ha a szobát csak a belső csapatok használhassák a Matrix-kiszolgálóján. Ezt később nem lehet megváltoztatni.",
+        "unfederated_label_default_on": "Ne engedélyezze ezt, ha a szobát külső csapat is használja másik Matrix-kiszolgálóról. Ezt később nem lehet megváltoztatni.",
         "unsupported_version": "A kiszolgáló nem támogatja a megadott szobaverziót."
     },
     "create_space": {
@@ -635,8 +702,8 @@
         "address_placeholder": "például sajat-ter",
         "creating": "Létrehozás…",
         "creating_rooms": "Szobák létrehozása…",
-        "done_action": "Irány a teréhez",
-        "done_action_first_room": "Ugrás az első szobámra",
+        "done_action": "Ugrás a saját teréhez",
+        "done_action_first_room": "Ugrás az első szobájához",
         "explainer": "A terek a szobák és emberek csoportosításának új módja. Milyen teret szeretne létrehozni? Később megváltoztathatja.",
         "failed_create_initial_rooms": "Térhez tartozó kezdő szobákat nem sikerült elkészíteni",
         "failed_invite_users": "Az alábbi felhasználókat nem sikerült meghívni a térbe: %(csvUsers)s",
@@ -656,6 +723,7 @@
         "private_space_description": "Privát tér önnek és a csoporttársainak",
         "public_description": "Mindenki számára nyílt tér, a közösségek számára ideális",
         "public_heading": "Saját nyilvános tér",
+        "search_public_button": "Nyilvános terek keresése",
         "setup_rooms_community_description": "Készítsünk szobát mindhez.",
         "setup_rooms_community_heading": "Mik azok amikről beszélni szeretne itt: %(spaceName)s?",
         "setup_rooms_description": "Később is hozzáadhat többet, beleértve meglévőket is.",
@@ -663,22 +731,29 @@
         "setup_rooms_private_heading": "Milyen projekteken dolgozik a csoportja?",
         "share_description": "Egyenlőre csak ön, még jobb lehet másokkal együtt.",
         "share_heading": "Megosztás: %(name)s",
-        "skip_action": "Kihagy egyenlőre",
+        "skip_action": "Kihagyás egyelőre",
         "subspace_adding": "Hozzáadás…",
-        "subspace_beta_notice": "Adjon hozzá az ön által kezelt térhez.",
+        "subspace_beta_notice": "Tér hozzáadása az Ön által kezelt térhez.",
         "subspace_dropdown_title": "Tér létrehozása",
         "subspace_existing_space_prompt": "Inkább meglévő teret adna hozzá?",
         "subspace_join_rule_invite_description": "Csak a meghívott emberek fogják megtalálni és tudnak belépni erre a térre.",
         "subspace_join_rule_invite_only": "Privát tér (csak meghívóval)",
         "subspace_join_rule_label": "Tér láthatósága",
-        "subspace_join_rule_public_description": "Bárki megtalálhatja és beléphet a térbe, nem csak <SpaceName/> tér tagsága.",
-        "subspace_join_rule_restricted_description": "<SpaceName/> téren bárki megtalálhatja és beléphet."
+        "subspace_join_rule_public_description": "Bárki megtalálhatja és beléphet a térbe, nem csak a(z) <SpaceName/> tér tagsága.",
+        "subspace_join_rule_restricted_description": "A(z) <SpaceName/> téren bárki megtalálhatja és beléphet."
     },
     "credits": {
         "default_cover_photo": "Az <photo>alapértelmezett borítóképre</photo> a következő vonatkozik: © <author>Jesús Roncero</author>, a <terms>CC BY-SA 4.0</terms> licenc feltételei szerint használva.",
         "twemoji": "A <twemoji>Twemoji</twemoji> emodzsikra a következő vonatkozik: © <author>Twitter, Inc. és egyéb közreműködők</author>, a <terms>CC-BY 4.0</terms> licenc feltételei szerint használva.",
         "twemoji_colr": "A <colr>twemoji-colr</colr> betűkészletre a következő vonatkozik: © <author>Mozilla Foundation</author>, az <terms>Apache 2.0</terms> licenc feltételei szerint használva."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Biztos, hogy elutasítja a(z) „%(roomName)s” szobába való meghívást?",
+        "ignore_user_help": "Ettől a felhasználótól nem fog többé üzeneteket vagy meghívásokat látni.",
+        "reason_description": "Írja le a szoba jelentésének okát.",
+        "report_room_description": "A szoba jelentése a fiókszolgáltatójának.",
+        "title": "Meghívás elutasítása"
+    },
     "desktop_default_device_name": "Asztali %(brand)s: (%(platformName)s)",
     "devtools": {
         "active_widgets": "Aktív kisalkalmazások",
@@ -686,34 +761,72 @@
         "category_room": "Szoba",
         "caution_colon": "Figyelmeztetés:",
         "client_versions": "Kliensverziók",
+        "crypto": {
+            "4s_public_key_in_account_data": "a fiókadatokban",
+            "4s_public_key_not_in_account_data": "nem található",
+            "4s_public_key_status": "Titkos tároló nyilvános kulcsa:",
+            "backup_key_cached": "helyileg gyorsítótárazva",
+            "backup_key_cached_status": "Biztonsági kulcs gyorsítótárazva:",
+            "backup_key_not_stored": "nincs tárolva",
+            "backup_key_stored": "titkos tárolóban",
+            "backup_key_stored_status": "Biztonsági kulcs tárolva:",
+            "backup_key_unexpected_type": "váratlan típus",
+            "backup_key_well_formed": "helyesen formázott",
+            "cross_signing": "Eszközök közti hitelesítés",
+            "cross_signing_cached": "helyileg gyorsítótárazva",
+            "cross_signing_not_ready": "Az eszközök közti hitelesítés nincs beállítva.",
+            "cross_signing_private_keys_in_storage": "a biztonsági tárolóban",
+            "cross_signing_private_keys_in_storage_status": "Az eszközök közti hitelesítés titkos kulcsai:",
+            "cross_signing_private_keys_not_in_storage": "nem találhatók a tárolóban",
+            "cross_signing_public_keys_on_device": "a memóriában",
+            "cross_signing_public_keys_on_device_status": "Az eszközök közti hitelesítés nyilvános kulcsai:",
+            "cross_signing_ready": "Az eszközök közti hitelesítés használatra kész.",
+            "cross_signing_status": "Eszközök közti hitelesítés állapota:",
+            "cross_signing_untrusted": "A fiókjához tartozik egy eszközök közti hitelesítési személyazonosság, de ez a munkamenet még nem jelölte megbízhatónak.",
+            "crypto_not_available": "A kriptográfiai modul nem érhető el",
+            "key_backup_active_version": "Aktív biztonsági mentés verziója:",
+            "key_backup_active_version_none": "Nincs",
+            "key_backup_inactive_warning": "A kulcsokról nem készül biztonsági mentés ebből a munkamenetből.",
+            "key_backup_latest_version": "A legújabb biztonsági mentés verziója a kiszolgálón:",
+            "key_storage": "Kulcstároló",
+            "master_private_key_cached_status": "Elsődleges titkos kulcs:",
+            "not_found": "nem található",
+            "not_found_locally": "helyben nem található",
+            "secret_storage_not_ready": "nincs kész",
+            "secret_storage_ready": "kész",
+            "secret_storage_status": "Titkos tároló:",
+            "self_signing_private_key_cached_status": "Önaláíró titkos kulcs:",
+            "title": "Végpontok közti titkosítás",
+            "user_signing_private_key_cached_status": "Felhasználó aláírási titkos kulcsa:"
+        },
         "developer_mode": "Fejlesztői mód",
         "developer_tools": "Fejlesztői eszközök",
         "edit_setting": "Beállítások szerkesztése",
         "edit_values": "Értékek szerkesztése",
         "empty_string": "<üres karakterek>",
         "event_content": "Esemény tartalma",
-        "event_id": "Esemény azon.: %(eventId)s",
+        "event_id": "Eseményazonosító: %(eventId)s",
         "event_sent": "Az esemény elküldve!",
         "event_type": "Esemény típusa",
         "explore_account_data": "Fiókadatok felderítése",
-        "explore_room_account_data": "Szoba fiók adatok felderítése",
+        "explore_room_account_data": "Szoba fiókadatainak felderítése",
         "explore_room_state": "Szobaállapot felderítése",
         "failed_to_find_widget": "Hiba történt a kisalkalmazás keresése során.",
-        "failed_to_load": "Betöltés sikertelen.",
-        "failed_to_save": "A beállítások elmentése nem sikerült.",
+        "failed_to_load": "A betöltés sikertelen.",
+        "failed_to_save": "A beállítások elmentése sikertelen.",
         "failed_to_send": "Az eseményt nem sikerült elküldeni!",
-        "id": "Azon.: ",
+        "id": "Azonosító: ",
         "invalid_json": "Nem tűnik érvényes JSON szövegnek.",
         "level": "Szint",
         "low_bandwidth_mode": "Alacsony sávszélességű mód",
         "low_bandwidth_mode_description": "Kompatibilis Matrix-kiszolgálóra van szükség.",
         "main_timeline": "Fő idővonal",
         "no_receipt_found": "Nincs visszajelzés",
-        "notification_state": "Értesítés állapot: <strong>%(notificationState)s</strong>",
+        "notification_state": "Értesítés állapota: <strong>%(notificationState)s</strong>",
         "notifications_debug": "Értesítések hibakeresése",
         "number_of_users": "Felhasználószám",
-        "original_event_source": "Eredeti esemény forráskód",
-        "room_encrypted": "A szoba <strong>titkosított ✅</strong>",
+        "original_event_source": "Eredeti esemény forráskódja",
+        "room_encrypted": "A szoba <strong>titkosítva van ✅</strong>",
         "room_id": "Szoba azon.: %(roomId)s",
         "room_not_encrypted": "A szoba <strong>nincs titkosítva 🚨</strong>",
         "room_notifications_dot": "Pont: ",
@@ -725,13 +838,13 @@
         "room_notifications_type": "Típus: ",
         "room_status": "Szoba állapota",
         "room_unread_status_count": {
-            "other": "Szoba olvasatlan állapota: <strong>%(status)s</strong>, darabszám: <strong>%(count)s</strong>"
+            "Szoba állapota: <strong>%(status)s</strong>, darabszám: <strong>%(count)s</strong>": "other"
         },
         "save_setting_values": "Beállított értékek mentése",
         "see_history": "Előzmények megtekintése",
-        "send_custom_account_data_event": "Egyedi fiókadat esemény küldése",
-        "send_custom_room_account_data_event": "Egyedi szoba fiókadat esemény küldése",
-        "send_custom_state_event": "Egyedi állapotesemény küldése",
+        "send_custom_account_data_event": "Egyéni fiókadat-esemény küldése",
+        "send_custom_room_account_data_event": "Egyéni szobafiókadat-esemény küldése",
+        "send_custom_state_event": "Egyéni állapotesemény küldése",
         "send_custom_timeline_event": "Egyéni idővonal-esemény küldése",
         "server_info": "Kiszolgálóinformációk",
         "server_versions": "Kiszolgálóverziók",
@@ -740,11 +853,14 @@
         "setting_colon": "Beállítás:",
         "setting_definition": "Beállítás leírása:",
         "setting_id": "Beállításazonosító",
-        "settings_explorer": "Beállítás böngésző",
+        "settings": {
+            "elementCallUrl": "Element Call webcím"
+        },
+        "settings_explorer": "Beállításböngésző",
         "show_hidden_events": "Rejtett események megjelenítése az idővonalon",
         "spaces": {
-            "other": "<%(count)s szóköz>",
-            "one": "<szóköz>"
+            "<%(count)s szóköz>": "other",
+            "<szóköz>": "one"
         },
         "state_key": "Állapotkulcs",
         "thread_root_id": "Üzenetszál gyökérazonosítója: %(threadRootId)s",
@@ -761,20 +877,20 @@
         "value_colon": "Érték:",
         "value_in_this_room": "Érték ebben a szobában",
         "value_this_room_colon": "Érték ebben a szobában:",
-        "values_explicit": "Egyedi szinthez tartozó értékek",
-        "values_explicit_colon": "Egyedi szinthez tartozó értékek:",
-        "values_explicit_room": "Egyedi szinthez tartozó értékek ebben a szobában",
-        "values_explicit_this_room_colon": "Egyedi szinthez tartozó értékek ebben a szobában:",
+        "values_explicit": "Egyéni szinthez tartozó értékek",
+        "values_explicit_colon": "Egyéni szinthez tartozó értékek:",
+        "values_explicit_room": "Egyéni szinthez tartozó értékek ebben a szobában",
+        "values_explicit_this_room_colon": "Egyéni szinthez tartozó értékek ebben a szobában:",
         "view_servers_in_room": "Kiszolgálók megjelenítése a szobában",
-        "view_source_decrypted_event_source": "Visszafejtett esemény forráskód",
-        "view_source_decrypted_event_source_unavailable": "A visszafejtett forrás nem érhető el",
+        "view_source_decrypted_event_source": "Visszafejtett esemény forráskódja",
+        "view_source_decrypted_event_source_unavailable": "A visszafejtett forráskód nem érhető el",
         "widget_screenshots": "Kisalkalmazások képernyőképének engedélyezése a támogatott kisalkalmazásoknál"
     },
     "dialog_close_label": "Ablak bezárása",
     "download_completed": "A letöltés befejeződött",
     "emoji": {
         "categories": "Kategóriák",
-        "category_activities": "Mozgás",
+        "category_activities": "Tevékenységek",
         "category_animals_nature": "Állatok és természet",
         "category_flags": "Zászlók",
         "category_food_drink": "Étel és ital",
@@ -792,80 +908,68 @@
     "empty_room_was_name": "Üres szoba (%(oldName)s volt)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Adja meg a biztonsági jelmondatot vagy <button>használja a biztonsági kulcsot</button> a folytatáshoz.",
             "key_validation_text": {
-                "invalid_security_key": "Érvénytelen biztonsági kulcs",
-                "recovery_key_is_correct": "Jónak tűnik!",
-                "wrong_file_type": "A fájltípus hibás",
-                "wrong_security_key": "Hibás biztonsági kulcs"
-            },
-            "reset_title": "Minden alaphelyzetbe állítása",
-            "reset_warning_1": "Csak akkor tegye meg, ha egyetlen másik eszköze sincs az ellenőrzés elvégzéséhez.",
-            "reset_warning_2": "Ha mindent alaphelyzetbe állít, akkor nem lesz megbízható munkamenete, nem lesznek megbízható felhasználók és a régi üzenetekhez sem biztos, hogy hozzáfér majd.",
+                "wrong_security_key": "Hibás helyreállítási kulcs"
+            },
             "restoring": "Kulcsok helyreállítása mentésből",
-            "security_key_title": "Biztonsági kulcs",
-            "security_phrase_incorrect_error": "A biztonsági tárolóhoz nem lehet hozzáférni. Kérjük ellenőrizze, hogy jó Biztonsági jelmondatot adott-e meg.",
-            "security_phrase_title": "Biztonsági jelmondat",
-            "separator": "%(securityKey)s vagy %(recoveryFile)s",
-            "use_security_key_prompt": "Használja a biztonsági kulcsot a folytatáshoz."
+            "security_key_title": "Helyreállítási kulcs"
         },
         "bootstrap_title": "Kulcsok beállítása",
         "cancel_entering_passphrase_description": "Biztos, hogy megszakítja a jelmondat bevitelét?",
         "cancel_entering_passphrase_title": "Megszakítja a jelmondat bevitelét?",
         "confirm_encryption_setup_body": "Az alábbi gomb megnyomásával erősítsd meg, hogy megadod a titkosítási beállításokat.",
-        "confirm_encryption_setup_title": "Erősítsd meg a titkosítási beállításokat",
-        "cross_signing_not_ready": "Az eszközök közti hitelesítés nincs beállítva.",
-        "cross_signing_ready": "Az eszközök közti hitelesítés használatra kész.",
-        "cross_signing_ready_no_backup": "Az eszközök közti hitelesítés készen áll, de a kulcsokról nincs biztonsági mentés.",
-        "cross_signing_room_normal": "Ez a szoba végpontok közötti titkosítást használ",
+        "confirm_encryption_setup_title": "Erősítse meg a titkosítási beállításokat",
+        "cross_signing_room_normal": "Ez a szoba végpontok közti titkosítást használ",
         "cross_signing_room_verified": "A szobában mindenki ellenőrizve van",
         "cross_signing_room_warning": "Valaki ellenőrizetlen munkamenetet használ",
-        "cross_signing_unsupported": "A Matrix-kiszolgálója nem támogatja az eszközök közti hitelesítést.",
-        "cross_signing_untrusted": "A fiókjához tartozik egy eszközök közti hitelesítési identitás, de ez a munkamenet még nem jelölte megbízhatónak.",
-        "cross_signing_user_normal": "Még nem ellenőrizted ezt a felhasználót.",
-        "cross_signing_user_verified": "Ezt a felhasználót ellenőrizted. Ez a felhasználó hitelesítette az összes munkamenetét.",
+        "cross_signing_user_normal": "Még nem ellenőrizte ezt a felhasználót.",
+        "cross_signing_user_verified": "Ellenőrizte ezt a felhasználót. Ez a felhasználó ellenőrizte az összes munkamenetét.",
         "cross_signing_user_warning": "Ez a felhasználó még nem ellenőrizte az összes munkamenetét.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Eszközök közti hitelesítési kulcsok törlése",
-            "title": "Megsemmisíted az eszközök közti hitelesítés kulcsait?",
-            "warning": "Eszközök közti hitelesítési kulcsok törlése végleges. Mindenki akit ezzel hitelesítettél biztonsági figyelmeztetéseket fog látni. Hacsak nem vesztetted el az összes eszközödet amivel eszközök közti hitelesítést tudsz végezni, nem valószínű, hogy ezt szeretnéd tenni."
-        },
+        "enter_recovery_key": "Adja meg a helyreállítási kulcsot",
         "event_shield_reason_authenticity_not_guaranteed": "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni.",
-        "event_shield_reason_mismatched_sender_key": "Ellenőrizetlen munkamenet titkosította",
+        "event_shield_reason_mismatched_sender_key": "Ellenőrizetlen munkamenet által titkosítva",
         "event_shield_reason_unknown_device": "Ismeretlen vagy törölt eszköz által titkosítva.",
         "event_shield_reason_unsigned_device": "A tulajdonos által nem ellenőrzött eszköz által titkosítva.",
         "event_shield_reason_unverified_identity": "Nem ellenőrzött felhasználó által titkosítva.",
         "export_unsupported": "A böngészője nem támogatja a szükséges titkosítási kiterjesztéseket",
+        "forgot_recovery_key": "Elfelejtette a helyreállítási kulcsot?",
         "import_invalid_keyfile": "Nem érvényes %(brand)s kulcsfájl",
         "import_invalid_passphrase": "Hitelesítési ellenőrzés sikertelen: hibás jelszó?",
+        "key_storage_out_of_sync": "A kulcstároló nincs szinkronban.",
+        "key_storage_out_of_sync_description": "Erősítse meg a helyreállítási kulcsot, hogy továbbra is hozzáférjen a kulcstárolóhoz és az üzenetelőzményekhez.",
         "messages_not_secure": {
-            "cause_1": "Matrix szervered",
+            "cause_1": "Saját Matrix-kiszolgáló",
             "cause_2": "Az ellenőrizendő felhasználó ehhez a Matrix-kiszolgálóhoz kapcsolódik:",
-            "cause_3": "Az ön vagy a másik felhasználó Internet kapcsolata",
-            "cause_4": "Az ön vagy a másik felhasználó munkamenete",
-            "heading": "Valamelyik az alábbiak közül kompromittált:",
-            "title": "Az üzeneteid nincsenek biztonságban"
+            "cause_3": "Az Ön vagy más felhasználók internetkapcsolata",
+            "cause_4": "Az Ön vagy más felhasználók munkamenete",
+            "heading": "A következők egyike kompromittálva lehet:",
+            "title": "Az üzenetei nincsenek biztonságban"
         },
         "new_recovery_method_detected": {
             "description_1": "A biztonságos üzenetekhez új biztonsági jelmondat és kulcs lett észlelve.",
             "description_2": "Ez a munkamenet az új helyreállítási móddal titkosítja a régi üzeneteket.",
             "title": "Új helyreállítási mód",
-            "warning": "Ha nem Ön állította be az új helyreállítási módot, akkor lehet, hogy egy támadó próbálja elérni a fiókját. Változtassa meg a fiókja jelszavát, és amint csak lehet, állítsa be az új helyreállítási eljárást a Beállításokban."
+            "warning": "Ha nem Ön állította be az új helyreállítási módot, akkor lehet, hogy egy támadó próbálja elérni a fiókját. Változtassa meg a fiókja jelszavát, és amint csak lehet, állítsa be az új helyreállítási módot a Beállításokban."
         },
-        "not_supported": "<nem támogatott>",
+        "pinned_identity_changed": "Úgy tűnik, hogy %(displayName)s (<b>%(userId)s</b>) személyazonossága megváltozott. <a>További információ</a>",
+        "pinned_identity_changed_no_displayname": "Úgy tűnik, hogy <b>%(userId)s</b> személyazonossága megváltozott. <a>További információ</a>",
         "recovery_method_removed": {
-            "description_1": "A munkamenet észrevette, hogy a biztonságos üzenetek biztonsági jelmondata és kulcsa törölve lett.",
+            "description_1": "Ez a munkamenet azt észlelte, hogy a biztonságos üzenetek biztonsági jelmondata és kulcsa törölve lett.",
             "description_2": "Ha véletlenül tette, akkor beállíthatja a biztonságos üzeneteket ebben a munkamenetben, ami újra titkosítja a régi üzeneteket a helyreállítási móddal.",
-            "title": "Helyreállítási mód törölve",
-            "warning": "Ha nem Ön törölte a helyreállítási módot, akkor lehet, hogy egy támadó hozzá akar férni a fiókjához. Azonnal változtassa meg a jelszavát, és állítson be egy helyreállítási módot a Beállításokban."
+            "title": "Helyreállítási mód eltávolítva",
+            "warning": "Ha nem Ön távolított el a helyreállítási módot, akkor lehet, hogy egy támadó hozzá akar férni a fiókjához. Azonnal változtassa meg a jelszavát, és állítson be egy helyreállítási módot a Beállításokban."
         },
         "reset_all_button": "Elfelejtette vagy elveszett minden helyreállítási lehetőség? <a>Minden alaphelyzetbe állítása</a>",
+        "set_up_recovery": "Helyreállítás beállítása",
+        "set_up_recovery_later": "Most nem",
+        "set_up_recovery_toast_description": "Létrehozhat egy helyreállítási kulcsot, amellyel helyreállíthatja a titkosított üzenetelőzményeit, ha elveszíti a hozzáférést az eszközeihez.",
         "set_up_toast_description": "Biztosíték a titkosított üzenetekhez és adatokhoz való hozzáférés elvesztése ellen",
         "set_up_toast_title": "Biztonsági mentés beállítása",
         "setup_secure_backup": {
-            "explainer": "Mentse a kulcsait a kiszolgálóra kijelentkezés előtt, hogy ne veszítse el azokat.",
-            "title": "Beállítás"
+            "explainer": "Mentse a kulcsait a kiszolgálóra kijelentkezés előtt, hogy ne veszítse el azokat."
         },
+        "turn_on_key_storage": "A kulcstárolás bekapcsolása",
+        "turn_on_key_storage_description": "Tárolja biztonságosan a kriptográfiai azonosító- és üzenetkulcsokat a kiszolgálón. Ez lehetővé teszi az üzenetek előzményeinek megtekintését bármely új eszközön.",
         "udd": {
             "interactive_verification_button": "Interaktív ellenőrzés emodzsikkal",
             "other_ask_verify_text": "Kérje meg a felhasználót, hogy hitelesítse a munkamenetét, vagy ellenőrizze kézzel lentebb.",
@@ -875,17 +979,15 @@
             "title": "Nem megbízható"
         },
         "unable_to_setup_keys_error": "Nem sikerült a kulcsok beállítása",
-        "unsupported": "A kliens nem támogatja a végponttól végpontig való titkosítást.",
         "verification": {
             "accepting": "Elfogadás…",
             "after_new_login": {
                 "device_verified": "Eszköz ellenőrizve",
-                "reset_confirmation": "Biztos, hogy lecseréli az ellenőrzési kulcsokat?",
                 "skip_verification": "Ellenőrzés kihagyása most",
                 "unable_to_verify": "Ennek az eszköznek az ellenőrzése nem lehetséges",
                 "verify_this_device": "Az eszköz ellenőrzése"
             },
-            "cancelled": "Megszakítottad az ellenőrzést.",
+            "cancelled": "Megszakította az ellenőrzést.",
             "cancelled_self": "Az ellenőrzést megszakította a másik eszközön.",
             "cancelled_user": "%(displayName)s megszakította az ellenőrzést.",
             "cancelling": "Megszakítás…",
@@ -893,28 +995,29 @@
             "complete_description": "Sikeresen ellenőrizte ezt a felhasználót.",
             "complete_title": "Ellenőrizve!",
             "error_starting_description": "A beszélgetést a másik felhasználóval nem lehetett elindítani.",
-            "error_starting_title": "Ellenőrzés indításakor hiba lépett fel",
+            "error_starting_title": "Hiba az ellenőrzés indításakor",
             "explainer": "Az ezzel felhasználóval váltott biztonságos üzenetek végpontok közti titkosítással védettek, és azt harmadik fél nem tudja elolvasni.",
             "in_person": "A biztonság érdekében ezt végezze el személyesen, vagy használjon megbízható kommunikációs csatornát.",
-            "incoming_sas_device_dialog_text_1": "Eszköz ellenőrzése és beállítás megbízhatóként. Az eszközben való megbízás megnyugtató lehet, ha végpontok közötti titkosítást használsz.",
+            "incoming_sas_device_dialog_text_1": "Eszköz ellenőrzése és beállítás megbízhatóként. Az eszközben való megbízás megnyugtató lehet, ha végpontok közti titkosítást használ.",
             "incoming_sas_device_dialog_text_2": "Az eszköz ellenőrzése megbízhatónak fogja jelezni az eszközt és azok a felhasználók, akik téged ellenőriztek, megbíznak majd ebben az eszközödben.",
-            "incoming_sas_dialog_title": "Bejövő Hitelesítési Kérés",
+            "incoming_sas_dialog_title": "Bejövő ellenőrzési kérés",
             "incoming_sas_dialog_waiting": "Várakozás a partner megerősítésére…",
-            "incoming_sas_user_dialog_text_1": "Ellenőrizd ezt a felhasználót, hogy megbízhatónak lehessen tekinteni. Megbízható felhasználók további nyugalmat jelenthetnek ha végpontól végpontig titkosítást használsz.",
+            "incoming_sas_user_dialog_text_1": "Ellenőrizze ezt a felhasználót, hogy megbízhatónak jelölje. A felhasználók megbízhatóságának megerősítése további biztonságot nyújt a végpontok közti titkosítással rendelkező üzenetek használatakor.",
             "incoming_sas_user_dialog_text_2": "A felhasználó ellenőrzése által az ő munkamenete megbízhatónak lesz jelölve, és a te munkameneted is megbízhatónak lesz jelölve nála.",
-            "no_key_or_device": "Úgy tűnik, hogy nem rendelkezik biztonsági kulccsal, vagy másik eszközzel, amelyikkel ellenőrizhetné. Ezzel az eszközzel nem fér majd hozzá a régi titkosított üzenetekhez. Ahhoz, hogy a személyazonosságát ezen az eszközön ellenőrizni lehessen, az ellenőrzédi kulcsokat alaphelyzetbe kell állítani.",
-            "no_support_qr_emoji": "Az ellenőrizni kívánt eszköz nem támogatja se a QR kód beolvasást se az emodzsi ellenőrzést, amit a %(brand)s támogat. Próbálja meg egy másik klienssel.",
+            "no_key_or_device": "Úgy tűnik, hogy nem rendelkezik helyreállítási kulccsal, vagy másik eszközzel, amellyel ellenőrizhetné. Ezzel az eszközzel nem fér majd hozzá a régi titkosított üzenetekhez. Ahhoz, hogy a személyazonosságát ezen az eszközön ellenőrizni lehessen, az ellenőrzési kulcsokat alaphelyzetbe kell állítania.",
+            "no_support_qr_emoji": "Az ellenőrizni kívánt eszköz nem támogatja sem a QR-kód leolvasását, sem az emodzsis ellenőrzést, amelyeket az %(brand)s támogat. Próbálja meg egy másik klienssel.",
             "other_party_cancelled": "A másik fél megszakította az ellenőrzést.",
             "prompt_encrypted": "Ellenőrizze a szoba összes tagját, hogy meggyőződjön a biztonságáról.",
             "prompt_self": "Ellenőrzés újrakezdése az értesítésből.",
-            "prompt_unencrypted": "Titkosított szobákban ellenőrizd a szoba összes tagját, hogy meggyőződhess a biztonságról.",
-            "prompt_user": "Ellenőrzés újraindítása a profiljából.",
+            "prompt_unencrypted": "A titkosított szobákban ellenőrizze az összes tagot, hogy meggyőződjön a biztonságosságáról.",
+            "prompt_user": "Indítsa újra az ellenőrzést a profiljából.",
             "qr_or_sas": "%(qrCode)s vagy %(emojiCompare)s",
             "qr_or_sas_header": "Ellenőrizze ezt az eszközt az alábbiak egyikével:",
             "qr_prompt": "Ennek az egyedi kódnak a beolvasása",
             "qr_reciprocate_same_shield_device": "Majdnem kész! A többi eszköze is ugyanazt a pajzsot mutatja?",
             "qr_reciprocate_same_shield_user": "Majdnem kész! %(displayName)s is ugyanazt a pajzsot mutatja?",
             "request_toast_accept": "Munkamenet ellenőrzése",
+            "request_toast_accept_user": "Felhasználó ellenőrzése",
             "request_toast_decline_counter": "Mellőzés (%(counter)s)",
             "request_toast_detail": "%(deviceId)s innen: %(ip)s",
             "reset_proceed_prompt": "Lecserélés folytatása",
@@ -950,23 +1053,24 @@
             "verify_emoji_prompt": "Ellenőrzés egyedi emodzsik összehasonlításával.",
             "verify_emoji_prompt_qr": "Ha nem tudod beolvasni az alábbi kódot, ellenőrizd az egyedi emodzsik összehasonlításával.",
             "verify_later": "Később ellenőrzöm",
-            "verify_reset_warning_1": "Az ellenőrzéshez használt kulcsok alaphelyzetbe állítását nem lehet visszavonni. A visszaállítás után nem fog hozzáférni a régi titkosított üzenetekhez, és minden ismerőse, aki eddig ellenőrizte a személyazonosságát, biztonsági figyelmeztetést fog látni, amíg újra nem ellenőrzi.",
-            "verify_reset_warning_2": "Csak akkor folytassa ha biztos benne, hogy elvesztett minden hozzáférést a többi eszközéhez és biztonsági kulcsához.",
             "verify_using_device": "Ellenőrizze egy másik eszközzel",
-            "verify_using_key": "Ellenőrzés Biztonsági Kulccsal",
-            "verify_using_key_or_phrase": "Ellenőrzés Biztonsági Kulccsal vagy Jelmondattal",
+            "verify_using_key": "Ellenőrzés helyreállítási kulccsal",
+            "verify_using_key_or_phrase": "Ellenőrzés helyreállítási kulccsal vagy jelmondattal",
             "waiting_for_user_accept": "%(displayName)s felhasználóra várakozás az elfogadáshoz…",
             "waiting_other_device": "Várakozás a másik eszköztől való ellenőrzésre…",
             "waiting_other_device_details": "Várakozás a másik eszközről való ellenőrzésre: %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Várakozás %(displayName)s felhasználóra az ellenőrzéshez…"
         },
         "verification_requested_toast_title": "Hitelesítés kérés elküldve",
+        "verified_identity_changed": "%(displayName)s (<b>%(userId)s</b> ) ellenőrzött személyazonossága megváltozott. <a>További információ</a>",
+        "verified_identity_changed_no_displayname": "<b>%(userId)s</b> ellenőrzött személyazonossága megváltozott. <a>További információ</a>",
         "verify_toast_description": "Más felhasználók lehet, hogy nem bíznak benne",
-        "verify_toast_title": "Munkamenet ellenőrzése"
+        "verify_toast_title": "Munkamenet ellenőrzése",
+        "withdraw_verification_action": "Ellenőrzés visszavonása"
     },
     "error": {
-        "admin_contact": "A szolgáltatás további használatához <a>vegye fel a kapcsolatot a szolgáltatás rendszergazdájával</a>.",
-        "admin_contact_short": "Vegye fel a kapcsolatot a <a>kiszolgáló rendszergazdájával</a>.",
+        "admin_contact": "A szolgáltatás további használatához <a>vegye fel a kapcsolatot a szolgáltatás adminisztrátorával</a>.",
+        "admin_contact_short": "Vegye fel a kapcsolatot a <a>kiszolgáló adminisztrátorával</a>.",
         "app_launch_unexpected_error": "Váratlan hiba történt az alkalmazás előkészítésénél. A részletekért lásd a konzolt.",
         "cannot_load_config": "A konfigurációs fájlt nem sikerült betölteni: frissítse az oldalt és próbálja meg újra.",
         "connection": "A kiszolgálóval való kommunikáció során probléma történt, próbálja újra.",
@@ -974,7 +1078,7 @@
         "download_media": "A forrásmédia letöltése sikertelen, nem található forráswebcím",
         "edit_history_unsupported": "Úgy tűnik, hogy a Matrix-kiszolgálója nem támogatja ezt a szolgáltatást.",
         "failed_copy": "Sikertelen másolás",
-        "hs_blocked": "A Matrix-kiszolgálót a rendszergazda zárolta.",
+        "hs_blocked": "A Matrix-kiszolgálót az adminisztrátor zárolta.",
         "invalid_configuration_mixed_server": "Érvénytelen konfiguráció: a default_hs_url nem adható meg a default_server_name vagy a default_server_config kulcsokkal együtt",
         "invalid_configuration_no_server": "Érvénytelen konfiguráció: nincs megadva alapértelmezett kiszolgáló.",
         "invalid_json": "Az Element érvénytelen JSON-t tartalmazó konfigurációval rendelkezik. Javítsa és töltse újra az oldalt.",
@@ -1003,18 +1107,21 @@
         "unknown_error_code": "ismeretlen hibakód",
         "update_power_level": "A hozzáférési szint megváltoztatása sikertelen"
     },
-    "error_database_closed_title": "Az adatbázis váratlanul lezárult",
+    "error_app_open_in_another_tab": "Váltson a másik lapra a csatlakozáshoz ide: %(brand)s. Ez a lap most bezárható.",
+    "error_app_open_in_another_tab_title": "%(brand)s egy másik lapon csatlakoztatva van",
+    "error_app_opened_in_another_window": "%(brand)s egy másik ablakban nyitva van. Kattintson a \"%(label)s\" gombra ennek a használatához: %(brand)s, és zárja be a másik ablakot.",
+    "error_database_closed_description": {
+        "for_desktop": "Lehet, hogy a tárhely megtelt. Kérjük, szabadítson fel egy kis helyet, és töltse be újra.",
+        "for_web": "Ha törölte a böngészési adatokat, akkor ez az üzenet várható. %(brand)s lehet, hogy egy másik lapon is nyitva van, vagy a tárhely betelt. Kérjük, szabadítson fel egy kis helyet és töltse be újra"
+    },
+    "error_database_closed_title": "%(brand)s leállt",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Ennek a szobának a hivatkozását nem sikerül a vágólapra másolni.",
             "title": "A szoba hivatkozása nem másolható"
         },
         "error_loading_user_profile": "A felhasználói profil nem tölthető be",
-        "forget_room_failed": "A szobát nem sikerült elfelejtetni: %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "A kiszolgáló elérhetetlen, túlterhelt vagy a keresés túllépte az időkorlátot :(",
-            "title": "Keresés sikertelen"
-        }
+        "forget_room_failed": "A szobát nem sikerült elfelejtetni: %(errCode)s"
     },
     "error_user_not_logged_in": "A felhasználó nincs bejelentkezve",
     "event_preview": {
@@ -1039,7 +1146,15 @@
             "you": "Ezzel a reagált: %(reaction)s, a következőre: %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Hang",
+            "file": "Fájl",
+            "image": "Kép",
+            "poll": "Szavazás",
+            "video": "Videó"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Exportálás megszakítva",
@@ -1054,21 +1169,17 @@
         "export_info": "Ez a(z) <roomName/> szoba exportálásának kezdete. Exportálta: <exporterDetails/>, időpont: %(exportDate)s.",
         "export_successful": "Sikeres exportálás!",
         "exported_n_events_in_time": {
-            "one": "%(count)s esemény exportálva %(seconds)s másodperc alatt",
-            "other": "%(count)s esemény exportálva %(seconds)s másodperc alatt"
+            "%(count)s esemény exportálva %(seconds)s másodperc alatt": "other"
         },
         "exporting_your_data": "Adatai exportálása",
         "fetched_n_events": {
-            "one": "Eddig %(count)s esemény lett lekérve",
-            "other": "Eddig %(count)s esemény lett lekérve"
+            "Eddig %(count)s esemény lett lekérve": "other"
         },
         "fetched_n_events_in_time": {
-            "one": "%(count)s esemény lekérve %(seconds)s másodperc alatt",
-            "other": "%(count)s esemény lekérve %(seconds)s másodperc alatt"
+            "%(count)s esemény lekérve %(seconds)s másodperc alatt": "other"
         },
         "fetched_n_events_with_total": {
-            "one": "%(count)s / %(total)s esemény lekérve",
-            "other": "%(count)s / %(total)s esemény lekérve"
+            "%(count)s / %(total)s esemény lekérve": "other"
         },
         "fetching_events": "Események lekérése…",
         "file_attached": "Fájl mellékelve",
@@ -1077,21 +1188,22 @@
         "generating_zip": "ZIP előállítása",
         "html": "HTML",
         "html_title": "Exportált adatok",
-        "include_attachments": "Csatolmányokkal együtt",
+        "include_attachments": "Mellékletekkel együtt",
         "json": "JSON",
         "media_omitted": "Média nélkül",
         "media_omitted_file_size": "Média nélkül – fájlméretkorlát túllépve",
         "messages": "Üzenetek",
         "next_page": "Következő üzenetcsoport",
         "num_messages": "Üzenetek száma",
-        "num_messages_min_max": "Az üzenetek száma csak %(min)s és %(max)s közötti szám lehet",
+        "num_messages_min_max": "Az üzenetek száma csak %(min)s és %(max)s közti szám lehet",
         "number_of_messages": "Üzenetek számának megadása",
         "previous_page": "Előző üzenetcsoport",
         "processing": "Feldolgozás…",
         "processing_event_n": "Esemény feldolgozása: %(number)s. / %(total)s",
         "select_option": "Az idővonalon a beszélgetés exportálásához tartozó beállítások kiválasztása",
-        "size_limit": "Méret korlát",
-        "size_limit_min_max": "A méret csak %(min)s MB és %(max)s MB közötti szám lehet",
+        "size_limit": "Méretkorlát",
+        "size_limit_min_max": "A méret csak %(min)s MB és %(max)s MB közti szám lehet",
+        "size_limit_postfix": "MB",
         "starting_export": "Exportálás kezdése…",
         "successful": "Exportálás sikeres",
         "successful_detail": "Az exportálás sikeres volt. Megtalálja a Letöltések könyvtárban.",
@@ -1104,22 +1216,22 @@
     "feedback": {
         "can_contact_label": "Ha további kérdés merülne fel, kapcsolatba léphetnek velem",
         "comment_label": "Megjegyzés",
-        "existing_issue_link": "Először nézd meg, hogy <existingIssuesLink>van-e már jegy róla a Github-on</existingIssuesLink>. Nincs? <newIssueLink>Adj fel egy új jegyet</newIssueLink>.",
-        "may_contact_label": "Keressenek ha további információkra lenne szükségük vagy szeretnék, ha készülő ötleteket tesztelnék",
+        "existing_issue_link": "Először nézze meg, hogy <existingIssuesLink>van-e már róla jegy a GitHubon</existingIssuesLink>. Nincs? <newIssueLink>Adjon fel egy új jegyet</newIssueLink>.",
+        "may_contact_label": "Kapcsolatba léphetnek velem, ha további információkra lenne szükségük, vagy szeretnék, hogy a készülő ötleteket teszteljem",
         "platform_username": "A platformja és a felhasználóneve fel lesz jegyezve, hogy segítsen nekünk a lehető legjobban felhasználni a visszajelzését.",
-        "pro_type": "Tipp: Ha hibajegyet készítesz, légyszíves segíts a probléma feltárásában azzal, hogy elküldöd a <debugLogsLink>részletes naplót</debugLogsLink>.",
+        "pro_type": "Tipp: Ha hibajegyet nyit, akkor küldje el a <debugLogsLink>hibakeresési naplókat</debugLogsLink> a probléma feltárása érdekében.",
         "send_feedback_action": "Visszajelzés küldése",
-        "sent": "Visszajelzés elküldve"
+        "sent": "Visszajelzés elküldve. Köszönjük, nagyra értékeljük!"
     },
     "file_panel": {
-        "empty_description": "Csatolj fájlt a csevegésből vagy húzd és ejtsd bárhova a szobában.",
+        "empty_description": "Csatoljon fájlt a csevegésből, vagy húzza és ejtse bárhova a szobában.",
         "empty_heading": "Ebben a szobában nincsenek fájlok",
-        "guest_note": "<a>Regisztrálnod kell</a> hogy ezt használhasd",
-        "peek_note": "Ahhoz hogy lásd a fájlokat be kell lépned a szobába"
+        "guest_note": "<a>Regisztrálnia kell</a> hogy ezt használhassa",
+        "peek_note": "Ahhoz, hogy lássa a fájlokat, be kell lépnie a szobába"
     },
     "forward": {
         "filter_placeholder": "Szobák vagy emberek keresése",
-        "message_preview_heading": "Üzenet előnézet",
+        "message_preview_heading": "Üzenet-előnézet",
         "no_perms_title": "Nincs jogosultsága ehhez",
         "open_room": "Szoba megnyitása",
         "send_label": "Elküldés",
@@ -1130,6 +1242,7 @@
         "change": "Azonosítási kiszolgáló módosítása",
         "change_prompt": "Bontja a kapcsolatot a(z) <current /> azonosítási kiszolgálóval, és inkább ehhez kapcsolódik: <new />?",
         "change_server_prompt": "Ha nem szeretné a(z) <server /> kiszolgálót használnia kapcsolatok kereséséhez, és hogy megtalálják az ismerősei, akkor adjon meg egy másik azonosítási kiszolgálót.",
+        "changed": "Az azonosítási kiszolgáló megváltozott",
         "checking": "Kiszolgáló ellenőrzése",
         "description_connected": "Jelenleg a(z) <server></server> kiszolgálót használja a kapcsolatok kereséséhez, és hogy megtalálják az ismerősei. A használt azonosítási kiszolgálót alább tudja megváltoztatni.",
         "description_disconnected": "Jelenleg nem használ azonosítási kiszolgálót. A kapcsolatok kereséséhez, és hogy megtalálják az ismerősei, adjon hozzá egy azonosítási kiszolgálót.",
@@ -1148,7 +1261,7 @@
         "no_terms": "A választott azonosítási kiszolgálóhoz nem tartoznak felhasználási feltételek.",
         "suggestions": "Ezt kellene tennie:",
         "suggestions_1": "ellenőrizze a böngészőkiegészítőket, hogy nem blokkolja-e valami az azonosítási kiszolgálót (például a Privacy Badger)",
-        "suggestions_2": "vegye fel a kapcsolatot a(z) <idserver /> azonosítási kiszolgáló rendszergazdáival",
+        "suggestions_2": "vegye fel a kapcsolatot a(z) <idserver /> azonosítási kiszolgáló adminisztrátoraival",
         "suggestions_3": "várjon és próbálja újra",
         "url": "Azonosítási kiszolgáló (%(server)s)",
         "url_field_label": "Új azonosítási kiszolgáló hozzáadása",
@@ -1157,11 +1270,23 @@
     "in_space": "Ebben a térben: %(spaceName)s.",
     "in_space1_and_space2": "Ezekben a terekben: %(space1Name)s és %(space2Name)s.",
     "in_space_and_n_other_spaces": {
-        "one": "Itt: %(spaceName)s és %(count)s másik térben.",
-        "other": "Itt: %(spaceName)s és %(count)s másik térben."
+        "Itt: %(spaceName)s és %(count)s másik térben.": "other"
     },
     "incompatible_browser": {
-        "title": "Nem támogatott böngésző"
+        "continue": "Folytatás mégis",
+        "description": "%(brand)s olyan böngészőfunkciókat használ, amelyek nem érhetők el az aktuális böngészőben. %(detail)s",
+        "detail_can_continue": "Ha folytatja, előfordulhat, hogy egyes funkciók nem működnek, és fennáll annak a kockázata, hogy a jövőben elveszítheti adatait.",
+        "detail_no_continue": "Próbálja meg frissíteni ezt a böngészőt, ha nem a legújabb verziót használja, és próbálja újra.",
+        "learn_more": "Tudjon meg többet",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "A legjobb élmény érdekében használja a <Chrome> Chrome</Chrome>, <Firefox> Firefox </Firefox>, <Edge> Edge</Edge> vagy <Safari> Safari</Safari> alkalmazást.",
+        "title": "Nem támogatott böngésző",
+        "use_desktop_heading": "Használjon inkább %(brand)s asztali alkalmazást",
+        "use_mobile_heading": "%(brand)s mobil alkalmazás használata",
+        "use_mobile_heading_after_desktop": "Vagy használja mobilalkalmazásunkat",
+        "windows_64bit": "Windows (64 bites)",
+        "windows_arm_64bit": "Windows (64 bites ARM)"
     },
     "info_tooltip_title": "Információ",
     "integration_manager": {
@@ -1170,17 +1295,18 @@
         "error_connecting_heading": "Nem lehet kapcsolódni az integrációkezelőhöz",
         "explainer": "Az integrációkezelők megkapják a beállításokat, módosíthatják a kisalkalmazásokat, szobameghívókat küldhetnek és a hozzáférési szintet állíthatnak be az Ön nevében.",
         "manage_title": "Integrációk kezelése",
+        "toggle_label": "Az integrációkezelő engedélyezése",
         "use_im": "Integrációkezelő használata a botok, kisalkalmazások és matricacsomagok kezeléséhez.",
         "use_im_default": "Integrációkezelő használata <b>(%(serverName)s)</b> a botok, kisalkalmazások és matricacsomagok kezeléséhez."
     },
     "integrations": {
-        "disabled_dialog_description": "Ehhez engedélyezd a(z) „%(manageIntegrations)s”-t a Beállításokban.",
+        "disabled_dialog_description": "Ehhez engedélyezze az „%(manageIntegrations)s” lehetőséget a beállításokban.",
         "disabled_dialog_title": "Az integrációk le vannak tiltva",
-        "impossible_dialog_description": "A %(brand)s nem használhat Integrációs Menedzsert. Kérem vegye fel a kapcsolatot az adminisztrátorral.",
+        "impossible_dialog_description": "A %(brand)s nem használhat integrációkezelőt. Vegye fel a kapcsolatot egy adminisztrátorral.",
         "impossible_dialog_title": "Az integrációk nem engedélyezettek"
     },
     "invite": {
-        "ask_anyway_description": "Nem található fiók profil az alábbi Matrix azonosítókhoz - mégis a közvetlen csevegés elindítása mellett dönt?",
+        "ask_anyway_description": "Nem található profil az alábbi Matrix-azonosítókhoz – mégis a közvetlen csevegés elindítása mellett dönt?",
         "ask_anyway_label": "Közvetlen beszélgetés indítása mindenképpen",
         "ask_anyway_never_warn_label": "Közvetlen beszélgetés indítása mindenképpen és később se figyelmeztessen",
         "email_caption": "Meghívás e-maillel",
@@ -1193,7 +1319,7 @@
         "error_already_joined_space": "A felhasználó már a téren van",
         "error_bad_state": "Előbb vissza kell vonni felhasználó kitiltását, mielőtt újra meghívható lesz.",
         "error_dm": "Nem tudjuk elkészíteni a közvetlen üzenetét.",
-        "error_find_room": "Valami nem sikerült a felhasználók meghívásával.",
+        "error_find_room": "Valami nem sikerült a felhasználók meghívása során.",
         "error_find_user_description": "Az alábbi felhasználók nem léteznek vagy hibásan vannak megadva, és nem lehet őket meghívni: %(csvNames)s",
         "error_find_user_title": "Az alábbi felhasználók nem találhatók",
         "error_invite": "Ezeket a felhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra.",
@@ -1201,6 +1327,8 @@
         "error_permissions_space": "Nincs jogosultsága embereket meghívni ebbe a térbe.",
         "error_profile_undisclosed": "A felhasználó lehet, hogy nem létezik",
         "error_transfer_multiple_target": "Csak egy felhasználónak lehet átadni a hívást.",
+        "error_unfederated_room": "Ez a szoba nincs megosztva más szerverekkel. Nem hívhat meg embereket külső szerverekről.",
+        "error_unfederated_space": "Ez a tér nincs megosztva más szerverekkel. Nem hívhat meg embereket külső szerverekről.",
         "error_unknown": "Ismeretlen kiszolgálóhiba",
         "error_user_not_found": "A felhasználó nem létezik",
         "error_version_unsupported_room": "A felhasználó Matrix-kiszolgálója nem támogatja a megadott szobaverziót.",
@@ -1208,38 +1336,38 @@
         "failed_generic": "Sikertelen művelet",
         "failed_title": "Meghívás sikertelen",
         "invalid_address": "Ismeretlen cím",
-        "name_email_mxid_share_room": "Hívj meg valakit a nevét, e-mail címét, vagy felhasználónevét (például <userId/>) megadva, vagy <a>oszd meg ezt a szobát</a>.",
-        "name_email_mxid_share_space": "Hívjon meg valakit a nevét, e-mail címét, vagy felhasználónevét (például <userId/>) megadva, vagy <a>oszd meg ezt a teret</a>.",
-        "name_mxid_share_room": "Hívj meg valakit a nevét, vagy felhasználónevét (például <userId/>) megadva, vagy <a>oszd meg ezt a szobát</a>.",
-        "name_mxid_share_space": "Hívjon meg valakit a nevével, felhasználói nevével (pl. <userId/>) vagy <a>oszd meg ezt a teret</a>.",
-        "recents_section": "Legújabb Beszélgetések",
+        "name_email_mxid_share_room": "Hívjon meg valakit a nevét, e-mail-címét vagy felhasználónevét (például <userId/>) megadva, vagy <a>ossza meg ezt a szobát</a>.",
+        "name_email_mxid_share_space": "Hívjon meg valakit a nevét, e-mail-címét vagy felhasználónevét (például <userId/>) megadva, vagy <a>ossza meg ezt a teret</a>.",
+        "name_mxid_share_room": "Hívjon meg valakit a nevét vagy felhasználónevét (például <userId/>) megadva, vagy <a>ossza meg ezt a szobát</a>.",
+        "name_mxid_share_space": "Hívjon meg valakit a nevét vagy felhasználónevét (például <userId/>) megadva, vagy <a>ossza meg ezt a teret</a>.",
+        "recents_section": "Legújabb beszélgetések",
         "room_failed_partial": "Az alábbi embereket nem sikerül meghívni ide: <RoomName/>, de a többi meghívó elküldve",
         "room_failed_partial_title": "Néhány meghívót nem sikerült elküldeni",
         "room_failed_title": "A felhasználók meghívása sikertelen ide: %(roomName)s",
-        "send_link_prompt": "Vagy meghívó link küldése",
-        "start_conversation_name_email_mxid_prompt": "Indítson beszélgetést valakivel a nevének, e-mail-címének vagy a felhasználónevének használatával (mint <userId/>).",
-        "start_conversation_name_mxid_prompt": "Indíts beszélgetést valakivel és használd hozzá a nevét vagy a felhasználói nevét (mint <userId/>).",
+        "send_link_prompt": "Vagy meghívási hivatkozás küldése",
+        "start_conversation_name_email_mxid_prompt": "Indítson beszélgetést valakivel a nevének, e-mail-címének vagy a felhasználónevének (például <userId/>) használatával.",
+        "start_conversation_name_mxid_prompt": "Indítson beszélgetést valakivel a nevének vagy felhasználónevének (például <userId/>) használatával.",
         "suggestions_disclaimer": "Adatvédelmi okokból néhány javaslat rejtve lehet.",
         "suggestions_disclaimer_prompt": "Ha nem található a keresett személy, küldje el az alábbi hivatkozást neki.",
-        "suggestions_section": "Nemrég küldött Közvetlen Üzenetek",
+        "suggestions_section": "Nemrég küldött közvetlen üzenetek",
         "to_room": "Meghívás ide: %(roomName)s",
         "to_space": "Meghívás ide: %(spaceName)s",
         "transfer_dial_pad_tab": "Tárcsázó számlap",
         "transfer_user_directory_tab": "Felhasználójegyzék",
-        "unable_find_profiles_description_default": "Az alábbi Matrix ID-koz nem sikerül megtalálni a profilokat - így is meghívod őket?",
+        "unable_find_profiles_description_default": "Az alábbi Matrix-azonosítókhoz nem sikerült profilokat találni – így is meghívja őket?",
         "unable_find_profiles_invite_label_default": "Meghívás mindenképp",
-        "unable_find_profiles_invite_never_warn_label_default": "Mindenképpen meghív és ne figyelmeztess többet",
+        "unable_find_profiles_invite_never_warn_label_default": "Meghívás mindenképp, és ne legyen több figyelmeztetés",
         "unable_find_profiles_title": "Az alábbi felhasználók lehet, hogy nem léteznek",
         "unban_first_title": "A felhasználó addig nem hívható meg, amíg fel nem oldják a kitiltását"
     },
     "inviting_user1_and_user2": "%(user1)s és %(user2)s meghívása",
     "inviting_user_and_n_others": {
-        "one": "%(user)s és 1 további meghívása",
-        "other": "%(user)s és %(count)s további meghívása"
+        "%(user)s és 1 további meghívása": "one",
+        "%(user)s és %(count)s további meghívása": "other"
     },
     "items_and_n_others": {
-        "other": "<Items/> és még %(count)s másik",
-        "one": "<Items/> és még egy másik"
+        "<Items/> és még %(count)s másik": "other",
+        "<Items/> és még egy másik": "one"
     },
     "keyboard": {
         "activate_button": "Kiválasztott gomb aktiválása",
@@ -1260,7 +1388,7 @@
         "composer_navigate_next_history": "Következő üzenetre navigálás a szerkesztőben",
         "composer_navigate_prev_history": "Előző üzenetre navigálás a szerkesztőben",
         "composer_new_line": "Új sor",
-        "composer_redo": "Szerkesztés újra érvényesítése",
+        "composer_redo": "Szerkesztés újbóli végrehajtása",
         "composer_toggle_bold": "Félkövér be/ki",
         "composer_toggle_code_block": "Kódblokk be/ki",
         "composer_toggle_italics": "Dőlt be/ki",
@@ -1276,19 +1404,21 @@
         "home": "Kezdőlap",
         "jump_first_message": "Ugrás az első üzenethez",
         "jump_last_message": "Ugrás az utolsó üzenethez",
-        "jump_room_search": "A szobakeresésre ugrás",
+        "jump_room_search": "Ugrás a szobakeresésre",
         "jump_to_read_marker": "A legrégebbi olvasatlan üzenetre ugrás",
-        "keyboard_shortcuts_tab": "Beállítások fül megnyitása",
+        "keyboard_shortcuts_tab": "Beállítások lap megnyitása",
         "navigate_next_history": "Következő, nemrég meglátogatott szoba vagy tér",
         "navigate_next_message_edit": "Következő üzenetre navigálás szerkesztéshez",
         "navigate_prev_history": "Előző, nemrég meglátogatott szoba vagy tér",
         "navigate_prev_message_edit": "Előző üzenetre navigálás szerkesztéshez",
+        "next_landmark": "Ugrás a következő mérföldkőhöz",
         "next_room": "Következő szoba vagy közvetlen üzenet",
         "next_unread_room": "Következő olvasatlan szoba vagy közvetlen üzenet",
         "number": "[szám]",
         "open_user_settings": "Felhasználói beállítások megnyitása",
         "page_down": "Page Down",
         "page_up": "Page Up",
+        "prev_landmark": "Ugrás az előző mérföldkőhöz",
         "prev_room": "Előző szoba vagy közvetlen üzenet",
         "prev_unread_room": "Előző olvasatlan szoba vagy közvetlen üzenet",
         "room_list_collapse_section": "Szobalista rész összecsukása",
@@ -1302,7 +1432,7 @@
         "send_sticker": "Matrica küldése",
         "shift": "Shift",
         "space": "Tér",
-        "switch_to_space": "Tér váltás szám alapján",
+        "switch_to_space": "Tér váltása szám alapján",
         "toggle_hidden_events": "Rejtett esemény láthatósága be/ki",
         "toggle_microphone_mute": "Mikrofon némítása be/ki",
         "toggle_right_panel": "Jobb oldali panel be/ki",
@@ -1333,8 +1463,12 @@
         "dynamic_room_predecessors": "A dinamikus szoba előfutárai",
         "dynamic_room_predecessors_description": "MSC3946 engedélyezése (a későn érkező szobaarchívumok támogatáshoz)",
         "element_call_video_rooms": "Element Call videószobák",
+        "exclude_insecure_devices": "A nem biztonságos eszközök kizárása üzenetek küldésekor vagy fogadásakor",
+        "exclude_insecure_devices_description": "Ha ez a mód engedélyezve van, a titkosított üzenetek nem kerülnek megosztásra a nem ellenőrzött eszközökkel, és a nem ellenőrzött eszközökről érkező üzenetek hibaként jelennek meg. Ne feledje, hogy ha engedélyezi ezt a módot, előfordulhat, hogy nem tud kommunikálni azokkal a felhasználókkal, akik nem ellenőrizték eszközüket.",
         "experimental_description": "Kísérletező kedvében van? Próbálja ki a legújabb fejlesztési ötleteinket. Ezek nincsenek befejezve; lehet, hogy instabilak, megváltozhatnak vagy el is tűnhetnek. <a>Tudjon meg többet</a>.",
-        "experimental_section": "Lehetőségek korai megjelenítése",
+        "experimental_section": "Korai előzetesek",
+        "extended_profiles_msc_support": "A kiszolgálónak támogatnia kell az MSC4133-at",
+        "feature_disable_call_per_sender_encryption": "Küldőnkénti titkosítás letiltása Element Callhoz",
         "feature_wysiwyg_composer_description": "Szövegszerkesztő használata a Markdown formázás helyett az üzenet írásakor.",
         "group_calls": "Új konferenciahívási élmény",
         "group_developer": "Fejlesztői",
@@ -1346,10 +1480,12 @@
         "group_rooms": "Szobák",
         "group_spaces": "Terek",
         "group_themes": "Témák",
+        "group_threads": "Üzenetszálak",
+        "group_ui": "Felhasználói felület",
         "group_voip": "Hang és videó",
         "group_widgets": "Kisalkalmazások",
         "hidebold": "Értesítési pötty elrejtése (csak darabszám megjelenítése)",
-        "html_topic": "A szoba témájának HTML megjelenítése",
+        "html_topic": "A szoba témájának HTML reprezentációjának megjelenítése",
         "join_beta": "Csatlakozás béta lehetőségekhez",
         "join_beta_reload": "A béta funkció bekapcsolása újratölti ezt: %(brand)s.",
         "jump_to_date": "Dátumra ugrás (hozzáadja a /jumptodate parancsot és a dátumra ugrási fejléceket)",
@@ -1361,14 +1497,22 @@
         "location_share_live_description": "Átmeneti megvalósítás. A helyadatok megmaradnak a szoba naplójában.",
         "mjolnir": "Új lehetőség emberek figyelmen kívül hagyására",
         "msc3531_hide_messages_pending_moderation": "A moderátorok kitakarhatják a még nem moderált üzeneteket.",
+        "new_room_list": "Új szobalista engedélyezése",
         "notification_settings": "Új értesítési beállítások",
+        "notification_settings_beta_caption": "Bemutatjuk az értesítési beállítások módosításának egyszerűbb módját. Testreszabhatja a sajátját, ahogy tetszik itt: %(brand)s.",
+        "notification_settings_beta_title": "Értesítési beállítások",
+        "notifications": "Engedélyezze az értesítési panelt a szoba fejlécében",
+        "release_announcement": "Kiadási bejelentés",
+        "render_reaction_images": "Egyéni képek megjelenítése reakciókban",
+        "render_reaction_images_description": "Néha „egyéni emodzsiknak” nevezik.",
         "report_to_moderators": "Jelentés a moderátoroknak",
         "report_to_moderators_description": "A moderálást támogató szobákban a problémás tartalmat a „Jelentés” gombbal lehet a moderátorok felé jelezni.",
-        "sliding_sync": "Csúszó szinkronizációs mód",
+        "sliding_sync": "Csúszóablakos szinkronizálási mód",
         "sliding_sync_description": "Aktív fejlesztés alatt, nem kapcsolható ki.",
         "sliding_sync_disabled_notice": "A kikapcsoláshoz ki-, és bejelentkezés szükséges",
-        "sliding_sync_server_no_support": "A kiszolgálója nem támogatja natívan",
+        "sliding_sync_server_no_support": "A kiszolgálója nem támogatja",
         "under_active_development": "Aktív fejlesztés alatt.",
+        "unrealiable_e2e": "Megbízhatatlan titkosított szobákban",
         "video_rooms": "Videószobák",
         "video_rooms_a_new_way_to_chat": "Új csevegési lehetőség a(z) %(brand)s alkalmazásban, hanggal és videóval.",
         "video_rooms_always_on_voip_channels": "A videószobák szobákba ágyazott, folyamatosan bekapcsolat VoIP-csatornák a(z) %(brand)s alkalmazásban.",
@@ -1377,6 +1521,7 @@
         "video_rooms_faq1_question": "Hogy lehet videószobát készíteni?",
         "video_rooms_faq2_answer": "Igen, a szöveges idővonal a videóval együtt megjelenik.",
         "video_rooms_faq2_question": "Lehet a videóhívás közben szövegesen is csevegni?",
+        "video_rooms_feedbackSubheading": "Köszönjük, hogy kipróbálta a béta verziót. Kérjük, fejtse ki a lehető legrészletesebben, hogy fejleszthessük.",
         "wysiwyg_composer": "Szövegszerkesztő használata"
     },
     "labs_mjolnir": {
@@ -1414,10 +1559,12 @@
     },
     "language_dropdown_label": "Nyelvválasztó lenyíló menü",
     "leave_room_dialog": {
-        "last_person_warning": "Csak ön van itt. Ha kilép, akkor a jövőben senki nem tud majd ide belépni, beleértve önt is.",
-        "leave_room_question": "Biztos, hogy elhagyja a(z) „%(roomName)s” szobát?",
+        "last_person_warning": "Egyedül van itt. Ha kilép, akkor a jövőben senki nem tud majd ide belépni, beleértve Önt is.",
+        "leave_room_question": "Biztos, hogy elhagyja ezt a szobát: „%(roomName)s”?",
         "leave_space_question": "Biztos, hogy elhagyja ezt a teret: %(spaceName)s?",
-        "room_rejoin_warning": "Ez a szoba nem nyilvános. Kilépés után csak újabb meghívóval tudsz újra belépni a szobába.",
+        "room_leave_admin_warning": "Ön az egyetlen rendszergazda ebben a szobában. Ha elhagyja, senki sem tudja módosítani a szoba beállításait, vagy más fontos műveleteket végrehajtani.",
+        "room_leave_mod_warning": "Ön az egyetlen rendszergazda ebben a szobában. Ha elhagyja, senki sem tudja módosítani a szoba beállításait, vagy más fontos műveleteket végrehajtani.",
+        "room_rejoin_warning": "Ez a szoba nem nyilvános. Kilépés után csak újabb meghívóval lehet újra belépni a szobába.",
         "space_rejoin_warning": "Ez a tér nem nyilvános. Kilépés után csak újabb meghívóval lehet újra belépni."
     },
     "left_panel": {
@@ -1440,8 +1587,8 @@
         "error_no_perms_title": "Nincs jogosultsága a helymegosztáshoz",
         "error_send_description": "Az %(brand)s nem tudja elküldeni a földrajzi helyzetét. Próbálja újra később.",
         "error_send_title": "A földrajzi helyzetet nem sikerült elküldeni",
-        "error_sharing_live_location": "Élő pozíció megosztás közben hiba történt",
-        "error_stopping_live_location": "Élő pozíció megosztás megállítása közben hiba történt",
+        "error_sharing_live_location": "Hiba történt az élő pozíciómegosztás során",
+        "error_stopping_live_location": "Hiba történt az élő pozíciómegosztás leállítása során",
         "expand_map": "Térkép szétnyitása",
         "failed_generic": "Nem sikerült a földrajzi helyzetének lekérése. Próbálja újra később.",
         "failed_load_map": "A térkép betöltése sikertelen",
@@ -1453,8 +1600,8 @@
         "live_enable_description": "Figyelem: ez a labor lehetőség egy átmeneti megvalósítás. Ez azt jelenti, hogy a szobába már elküldött helyadatok az élő hely megosztás leállítása után is hozzáférhetők maradnak a szobában.",
         "live_enable_heading": "Élő földrajzi hely megosztása",
         "live_location_active": "Ön folyamatosan megosztja az aktuális földrajzi pozícióját",
-        "live_location_enabled": "Élő pozíció megosztás engedélyezve",
-        "live_location_ended": "Élő pozíció megosztás befejeződött",
+        "live_location_enabled": "Élő pozíciómegosztás engedélyezve",
+        "live_location_ended": "Élő pozíciómegosztás befejezve",
         "live_location_error": "Élő pozíció megosztás hiba",
         "live_locations_empty": "Nincs élő pozíció megosztás",
         "live_share_button": "Megosztás eddig: %(duration)s",
@@ -1474,11 +1621,18 @@
         "toggle_attribution": "Forrásmegjelölés be/ki"
     },
     "member_list": {
+        "count": {
+            "%(count)s tag": "other"
+        },
         "filter_placeholder": "Szoba tagság szűrése",
+        "invite_button_no_perms_tooltip": "Nincs jogosultsága felhasználók meghívására",
+        "invited_label": "Meghívott",
+        "no_matches": "Nincs egyezés",
         "power_label": "%(userName)s (szint: %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Szobatagok",
     "message_edit_dialog_title": "Üzenetszerkesztések",
+    "migrating_crypto": "Kérjük várjon. Frissítjük a(z) %(brand)s alkalmazás titkosítását, hogy gyorsabbá és megbízhatóbbá tegyük azt.",
     "mobile_guide": {
         "toast_accept": "Alkalmazás használata",
         "toast_description": "A(z) %(brand)s kísérleti állapotban van a mobilos webböngészőkben. A jobb élmény és a legújabb funkciók használatához használja az ingyenes natív alkalmazásunkat.",
@@ -1496,18 +1650,27 @@
         "class_global": "Globális",
         "class_other": "Egyéb",
         "default": "Alapértelmezett",
+        "default_settings": "Megegyezik az alapértelmezett beállításokkal",
+        "email_pusher_app_display_name": "E-mail értesítések",
         "enable_prompt_toast_description": "Asztali értesítések engedélyezése",
         "enable_prompt_toast_title": "Értesítések",
         "enable_prompt_toast_title_from_message_send": "Ne szalasszon el egy választ se",
         "error_change_title": "Értesítési beállítások megváltoztatása",
         "keyword": "Kulcsszó",
         "keyword_new": "Új kulcsszó",
+        "level_activity": "Tevékenység",
+        "level_highlight": "Kiemelés",
+        "level_muted": "Némítva",
+        "level_none": "Egyik sem",
+        "level_notification": "Értesítés",
+        "level_unsent": "Elküldetlen",
         "mark_all_read": "Összes megjelölése olvasottként",
         "mentions_and_keywords": "@megemlítések és kulcsszavak",
         "mentions_and_keywords_description": "Értesítések fogadása csak megemlítéseknél és kulcsszavaknál, a <a>beállításokban</a> megadottak szerint",
         "mentions_keywords": "Megemlítések és kulcsszavak",
         "message_didnt_send": "Az üzenet nincs elküldve. Kattintson az információkért.",
-        "mute_description": "Nem kap semmilyen értesítést"
+        "mute_description": "Nem kap semmilyen értesítést",
+        "mute_room": "Szoba némítása"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s ellenőrzést kér"
@@ -1552,12 +1715,10 @@
         "topic_placeholder": "Írjon valamit…",
         "total_decryption_errors": "Visszafejtési hibák miatt néhány szavazat nem kerül beszámításra",
         "total_n_votes": {
-            "one": "%(count)s leadott szavazat. Szavazzon az eredmény megtekintéséhez",
-            "other": "%(count)s leadott szavazat. Szavazzon az eredmény megtekintéséhez"
+            "%(count)s leadott szavazat. Szavazzon az eredmény megtekintéséhez": "other"
         },
         "total_n_votes_voted": {
-            "one": "%(count)s szavazat alapján",
-            "other": "%(count)s szavazat alapján"
+            "%(count)s szavazat alapján": "other"
         },
         "total_no_votes": "Nem adtak le szavazatot",
         "total_not_ended": "Az eredmény a szavazás végeztével válik láthatóvá",
@@ -1568,9 +1729,9 @@
         "unable_edit_title": "A szavazás nem szerkeszthető"
     },
     "power_level": {
-        "admin": "Rendszergazda",
+        "admin": "Adminisztrátor",
         "custom": "Egyéni (%(level)s)",
-        "custom_level": "Egyedi szint",
+        "custom_level": "Egyéni szint",
         "default": "Alapértelmezett",
         "label": "Hozzáférési szint",
         "moderator": "Moderátor",
@@ -1588,7 +1749,8 @@
         "online": "Elérhető",
         "online_for": "%(duration)s óta elérhető",
         "unknown": "Ismeretlen",
-        "unknown_for": "%(duration)s óta az állapota ismeretlen"
+        "unknown_for": "%(duration)s óta az állapota ismeretlen",
+        "unreachable": "A felhasználó szervere elérhetetlen"
     },
     "quick_settings": {
         "all_settings": "Minden beállítás",
@@ -1597,88 +1759,114 @@
         "title": "Gyors beállítások"
     },
     "quit_warning": {
-        "call_in_progress": "Úgy tűnik hívásban vagy, biztosan kilépsz?",
-        "file_upload_in_progress": "Úgy tűnik fájlokat töltesz fel, biztosan kilépsz?"
+        "call_in_progress": "Úgy tűnik hívásban van, biztos, hogy kilép?",
+        "file_upload_in_progress": "Úgy tűnik fájlokat tölt fel, biztos, hogy kilép?"
     },
     "redact": {
         "confirm_button": "Törlés megerősítése",
-        "error": "Nem törölheted ezt az üzenetet. (%(code)s)",
+        "confirm_description": "Biztos benne, hogy el kívánja távolítani (törölni) ezt az eseményt?",
+        "confirm_description_state": "Vegye figyelembe, hogy a szoba ilyen jellegű módosításainak eltávolítása visszavonhatja a változtatást.",
+        "error": "Nem törölheti ezt az üzenetet. (%(code)s)",
         "ongoing": "Eltávolítás…",
-        "reason_label": "Ok (opcionális)"
-    },
-    "reject_invitation_dialog": {
-        "confirmation": "Biztos, hogy elutasítja a meghívást?",
-        "failed": "A meghívót nem sikerült elutasítani",
-        "title": "Meghívó elutasítása"
+        "reason_label": "Ok (nem kötelező)"
     },
     "report_content": {
-        "description": "Az üzenet bejelentése egy egyedi „eseményazonosítót” küld el a Matrix-kiszolgáló rendszergazdájának. Ha az üzenetek titkosítottak a szobában, akkor a Matrix-kiszolgáló rendszergazdája nem tudja elolvasni az üzenetet, vagy nem tudja megnézni a fájlokat vagy képeket.",
+        "description": "Az üzenet bejelentése egy egyedi „eseményazonosítót” küld el a Matrix-kiszolgáló adminisztrátorának. Ha az üzenetek titkosítottak a szobában, akkor a Matrix-kiszolgáló adminisztrátora nem tudja elolvasni az üzenetet, vagy nem tudja megnézni a fájlokat vagy képeket.",
         "disagree": "Nem értek egyet",
-        "hide_messages_from_user": "Válaszd ki ha ennek a felhasználónak a jelenlegi és jövőbeli üzeneteit el szeretnéd rejteni.",
+        "error_create_room_moderation_bot": "Nem lehet szobát létrehozni a moderációs bot segítségével",
+        "hide_messages_from_user": "Jelölje be, ha ennek a felhasználónak a jelenlegi és jövőbeli üzeneteit is el szeretné rejteni.",
         "ignore_user": "Felhasználó mellőzése",
         "illegal_content": "Törvénytelen tartalom",
         "missing_reason": "Adja meg, hogy miért jelenti.",
         "nature": "Válassza ki az üzenet természetét, vagy írja le, hogy miért elítélendő.",
         "nature_disagreement": "Amit ez a felhasználó ír az rossz.\nErről a szoba moderátorának jelentés készül.",
-        "nature_illegal": "A felhasználó illegális viselkedést valósít meg, például kipécézett valakit vagy tettlegességgel fenyeget.\nEz moderátorok felé jelzésre kerül akik akár hivatalos személyek felé továbbíthatják ezt.",
+        "nature_illegal": "A felhasználó illegális viselkedést valósít meg, például kipécézett valakit vagy tettlegességgel fenyeget.\nEz jelezve lesz a moderátorok felé, akik akár hivatalos szervek felé is továbbíthatják ezt.",
+        "nature_nonstandard_admin": "Ezt a szobát illegális vagy mérgező tartalomnak szentelték, vagy a moderátorok nem szűrik az illegális vagy mérgező tartalmakat.\nEzt jelentésre kerül a rendszergazdák felé itt: %(homeserver)s .",
+        "nature_nonstandard_admin_encrypted": "Ezt a szobát illegális vagy mérgező tartalomnak szentelték, vagy a moderátorok nem szűrik az illegális vagy mérgező tartalmakat.\nEzt jelentésre kerül a rendszergazdák felé itt: %(homeserver)s . A rendszergazdák a titkosított tartalmat ebben a szobában nem tudják elolvasni.",
         "nature_other": "Bármi más ok. Írja le a problémát.\nEz lesz elküldve a szoba moderátorainak.",
         "nature_spam": "A felhasználó kéretlen reklámokkal, reklámhivatkozásokkal vagy propagandával bombázza a szobát.\nEz jelezve lesz a szoba moderátorai felé.",
-        "nature_toxic": "A felhasználó mérgező viselkedést jelenít meg, például más felhasználókat inzultál vagy felnőtt tartalmat oszt meg egy családbarát szobában vagy más módon sérti meg a szoba szabályait.\nEz moderátorok felé jelzésre kerül.",
+        "nature_toxic": "A felhasználó mérgező viselkedést tanúsít, például más felhasználókat inzultál vagy felnőtt tartalmat oszt meg egy családbarát szobában, vagy más módon sérti a szoba szabályait.\nEz moderátorok felé jelzésre kerül.",
         "other_label": "Egyéb",
-        "report_content_to_homeserver": "Tartalom bejelentése a Matrix-kiszolgáló rendszergazdájának",
+        "report_content_to_homeserver": "Tartalom bejelentése a Matrix-kiszolgáló adminisztrátorának",
         "report_entire_room": "Az egész szoba jelentése",
         "spam_or_propaganda": "Kéretlen tartalom vagy propaganda",
         "toxic_behaviour": "Mérgező viselkedés"
     },
+    "report_room": {
+        "description": "A szoba jelentése a Matrix-kiszolgáló rendszergazdájának. Ha az üzenetek titkosítva vannak, a rendszergazda nem fogja tudni elolvasni azokat.",
+        "reason_label": "Írja le az okot"
+    },
     "restore_key_backup_dialog": {
-        "count_of_decryption_failures": "%(failedCount)s kapcsolatot nem lehet visszafejteni!",
+        "count_of_decryption_failures": "%(failedCount)s munkamenetet nem lehet visszafejteni!",
         "count_of_successfully_restored_keys": "%(sessionCount)s kulcs sikeresen helyreállítva",
-        "enter_key_description": "A biztonsági kulcs megadásával hozzáférhet a régi biztonságos üzeneteihez és beállíthatja a biztonságos üzenetküldést.",
-        "enter_key_title": "Adja meg a biztonsági kulcsot",
-        "enter_phrase_description": "A Biztonsági Jelmondattal hozzáférhet a régi titkosított üzeneteihez és beállíthatja a biztonságos üzenetküldést.",
-        "enter_phrase_title": "Biztonsági Jelmondat megadása",
-        "incorrect_security_phrase_dialog": "A mentést nem lehet visszafejteni ezzel a Biztonsági Jelmondattal: kérjük ellenőrizze, hogy a megfelelő Biztonsági Jelmondatot adta-e meg.",
-        "incorrect_security_phrase_title": "Helytelen Biztonsági Jelmondat",
+        "enter_key_description": "A helyreállítási kulcs megadásával hozzáférhet a régi biztonságos üzeneteihez és beállíthatja a biztonságos üzenetküldést.",
+        "enter_key_title": "Adja meg a helyreállítási kulcsot",
+        "enter_phrase_description": "A biztonsági jelmondattal hozzáférhet a régi titkosított üzeneteihez, és beállíthatja a biztonságos üzenetküldést.",
+        "enter_phrase_title": "Biztonsági jelmondat megadása",
+        "incorrect_security_phrase_dialog": "A mentést nem lehet visszafejteni ezzel a biztonsági jelmondattal: ellenőrizze, hogy a megfelelő biztonsági jelmondatot adta-e meg.",
+        "incorrect_security_phrase_title": "Helytelen biztonsági jelmondat",
         "key_backup_warning": "<b>Figyelmeztetés</b>: csak biztonságos számítógépről állítson be kulcsmentést.",
         "key_fetch_in_progress": "Kulcsok lekérése a kiszolgálóról…",
-        "key_forgotten_text": "Ha elfelejtette a biztonsági kulcsot, <button>állítson be új helyreállítási lehetőséget</button>",
-        "key_is_invalid": "Érvénytelen biztonsági kulcs",
-        "key_is_valid": "Ez érvényes biztonsági kulcsnak tűnik.",
+        "key_forgotten_text": "Ha elfelejtette a helyreállítási kulcsot, <button>állítson be új helyreállítási lehetőséget</button>",
+        "key_is_invalid": "Érvénytelen helyreállítási kulcs",
+        "key_is_valid": "Ez érvényes helyreállítási kulcsnak tűnik.",
         "keys_restored_title": "Kulcsok helyreállítva",
         "load_error_content": "A mentés állapotát nem lehet lekérdezni",
         "load_keys_progress": "%(completed)s/%(total)s kulcs helyreállítva",
-        "no_backup_error": "Mentés nem található!",
-        "phrase_forgotten_text": "Ha elfelejtette a biztonsági jelmondatot, használhatja a <button1>biztonsági kulcsot</button1> vagy <button2>új helyreállítási lehetőségeket állíthat be</button2>",
-        "recovery_key_mismatch_description": "Ezzel a biztonsági kulccsal a mentést nem lehet visszafejteni: ellenőrizze, hogy a biztonsági kulcsot jól adta-e meg.",
-        "recovery_key_mismatch_title": "A biztonsági kulcsok nem egyeznek",
+        "no_backup_error": "Nem található mentés!",
+        "phrase_forgotten_text": "Ha elfelejtette a biztonsági jelmondatot, használhatja a <button1>helyreállítási kulcsot</button1> vagy <button2>új helyreállítási lehetőségeket állíthat be</button2>",
+        "recovery_key_mismatch_description": "Ezzel a helyreállítási kulccsal t nem lehet visszafejteni a mentést: ellenőrizze, hogy a biztonsági kulcsot jól adta-e meg.",
+        "recovery_key_mismatch_title": "A helyreállítási kulcsok nem egyeznek",
         "restore_failed_error": "A mentést nem lehet helyreállítani"
     },
     "right_panel": {
-        "add_integrations": "Kisalkalmazások, hidak, és botok hozzáadása",
+        "add_integrations": "Bővítmények hozzáadása",
+        "add_topic": "Téma hozzáadása",
+        "extensions_button": "Bővítmények",
+        "extensions_empty_description": "Válassza a „%(addIntegrations)s” lehetőséget a szoba böngészéséhez és bővítmények hozzáadásához",
+        "extensions_empty_title": "Növelje a hatékonyságát több eszközzel, kisalkalmazásokkal és botokkal",
         "files_button": "Fájlok",
         "pinned_messages": {
+            "empty_description": "Válasszon ki egy üzenetet, majd válassza a „%(pinAction)s ”, hogy ide kerüljön.",
+            "empty_title": "Rögzítse a fontos üzeneteket, hogy könnyen felfedezhetők legyenek",
+            "header": {
+                "1 rögzített üzenet": "one",
+                "%(count)s rögzített üzenet": "other"
+            },
             "limits": {
-                "other": "Csak %(count)s kisalkalmazást tud kitűzni"
-            }
+                "Csak %(count)s kisalkalmazást tud kitűzni": "other"
+            },
+            "menu": "Menü megnyitása",
+            "release_announcement": {
+                "close": "OK",
+                "description": "Itt találja az összes kitűzött üzenetet. Húzza az egérmutatót bármely üzenetre, és válassza a „Kitűz” lehetőséget a hozzáadáshoz.",
+                "title": "Minden új kitűzött üzenet"
+            },
+            "reply_thread": "Válasz egy <link>üzenetszálra</link>",
+            "unpin_all": {
+                "button": "Az összes üzenet rögzítésének feloldása",
+                "content": "Győződjön meg róla, hogy valóban el akarja távolítani az összes üzenet rögzítést. Ezt a műveletet nem lehet visszavonni.",
+                "title": "Feloldja az összes üzenet rögzítését?"
+            },
+            "view": "Megtekintés az idővonalon"
         },
-        "pinned_messages_button": "Kitűzött",
+        "pinned_messages_button": "Rögzített üzenetek",
         "poll": {
             "active_heading": "Aktív szavazások",
             "empty_active": "Nincsenek aktív szavazások ebben a szobában",
             "empty_active_load_more": "Nincs aktív szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez",
             "empty_active_load_more_n_days": {
-                "other": "%(count)s napja nincs aktív szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez",
-                "one": "Nincs aktív szavazás az elmúlt napokból. További szavazások betöltése az előző havi szavazások megjelenítéséhez"
+                "%(count)s napja nincs aktív szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez": "other",
+                "Nincs aktív szavazás az elmúlt napokból. További szavazások betöltése az előző havi szavazások megjelenítéséhez": "one"
             },
             "empty_past": "Nincsenek régebbi szavazások ebben a szobában",
             "empty_past_load_more": "Nincs régebbi szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez",
             "empty_past_load_more_n_days": {
-                "one": "Nincs aktív szavazás az elmúlt napokból. További szavazások betöltése az előző havi szavazások megjelenítéséhez",
-                "other": "%(count)s napja nincs aktív szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez"
+                "Nincs aktív szavazás az elmúlt napokból. További szavazások betöltése az előző havi szavazások megjelenítéséhez": "one",
+                "%(count)s napja nincs aktív szavazás. További szavazások betöltése az előző havi szavazások megjelenítéséhez": "other"
             },
             "final_result": {
-                "one": "Végeredmény %(count)s szavazat alapján",
-                "other": "Végeredmény %(count)s szavazat alapján"
+                "Végeredmény %(count)s szavazat alapján": "other"
             },
             "load_more": "Még több szavazás betöltése",
             "loading": "Szavazások betöltése",
@@ -1686,7 +1874,7 @@
             "view_in_timeline": "Szavazás megjelenítése az idővonalon",
             "view_poll": "Szavazás megtekintése"
         },
-        "polls_button": "Szavazás előzményei",
+        "polls_button": "Szavazások",
         "room_summary_card": {
             "title": "Szoba információ"
         },
@@ -1698,15 +1886,15 @@
         }
     },
     "room": {
-        "3pid_invite_email_not_found_account": "Ez a meghívó ide lett küldve: %(email)s ami nincs összekötve a fiókjával",
-        "3pid_invite_email_not_found_account_room": "A meghívó ehhez a szobához: %(roomName)s erre az e-mail címre lett elküldve: %(email)s ami nincs társítva a fiókodhoz",
+        "3pid_invite_email_not_found_account": "Ez a meghívó a(z) %(email)s címre lett küldve, amely nincs összekötve a fiókjával",
+        "3pid_invite_email_not_found_account_room": "A(z) %(roomName)s szobához való meghívó a(z) %(email)s címre lett küldve, amely nincs összekötve a fiókjával",
         "3pid_invite_error_description": "A meghívó ellenőrzésekor az alábbi hibát kaptuk: %(errcode)s. Ezt az információt megpróbálhatja eljuttatni a szoba gazdájának.",
         "3pid_invite_error_invite_action": "Csatlakozás mindenképp",
         "3pid_invite_error_invite_subtitle": "Csak érvényes meghívóval tudsz csatlakozni.",
         "3pid_invite_error_public_subtitle": "Itt továbbra is tud csatlakozni.",
         "3pid_invite_error_title": "Valami hiba történt a meghívójával.",
         "3pid_invite_error_title_room": "A meghívóddal ebbe a szobába: %(roomName)s valami baj történt",
-        "3pid_invite_no_is_subtitle": "Állíts be azonosítási szervert a Beállításokban, hogy közvetlen meghívókat kaphass %(brand)sba.",
+        "3pid_invite_no_is_subtitle": "Használjon egy azonosítási kiszolgálót a Beállításokban, hogy közvetlenül az %(brand)sben kapja meg a meghívókat.",
         "banned_by": "%(memberName)s felhasználó kitiltotta",
         "banned_from_room_by": "Téged kitiltott %(memberName)s ebből a szobából: %(roomName)s",
         "context_menu": {
@@ -1715,9 +1903,10 @@
             "forget": "Szoba elfelejtése",
             "low_priority": "Alacsony prioritás",
             "mark_read": "Megjelölés olvasottként",
+            "mark_unread": "Megjelölés olvasatlanként",
             "notifications_default": "Az alapértelmezett beállítások szerint",
             "notifications_mute": "Szoba némítása",
-            "title": "Szoba beállítások",
+            "title": "Szobabeállítások",
             "unfavourite": "Kedvencnek jelölt"
         },
         "creating_room_text": "Szobát készítünk: %(names)s",
@@ -1726,7 +1915,7 @@
         "dm_invite_title": "%(user)s felhasználóval szeretnél beszélgetni?",
         "drop_file_prompt": "Feltöltéshez húzz ide egy fájlt",
         "edit_topic": "Téma szerkesztése",
-        "error_3pid_invite_email_lookup": "E-mail alapján nem található meg a felhasználó",
+        "error_3pid_invite_email_lookup": "E-mail alapján nem található a felhasználó",
         "error_cancel_knock_title": "Nem sikerült megszakítani",
         "error_join_403": "Meghívóra van szüksége a szoba eléréséhez.",
         "error_join_404_1": "A belépéshez csak a szoba azonosítóját adta meg a kiszolgáló nélkül. A szobaazonosító egy belső azonosító, amellyel további információk nélkül nem lehet belépni szobába.",
@@ -1735,21 +1924,20 @@
         "error_join_404_invite_same_hs": "A személy, aki meghívta, már távozott.",
         "error_join_connection": "A csatlakozás során hiba történt.",
         "error_join_incompatible_version_1": "Sajnáljuk, a Matrix-kiszolgáló túl régi verziójú ahhoz, hogy ebben részt vegyen.",
-        "error_join_incompatible_version_2": "Vegye fel a kapcsolatot a Matrix-kiszolgáló rendszergazdájával.",
+        "error_join_incompatible_version_2": "Vegye fel a kapcsolatot a Matrix-kiszolgáló adminisztrátorával.",
         "error_join_title": "Csatlakozás sikertelen",
         "error_jump_to_date": "A kiszolgáló a következő állapotkóddal és hibakóddal tért vissza: %(statusCode)s – %(errorCode)s",
-        "error_jump_to_date_connection": "Hálózati hiba történt az adott dátum keresése és az ahhoz ugrás során. A Matrix-kiszolgálója lehet, hogy nem érhető el, vagy ideiglenes probléma van az internetkapcsolátával. Próbálja újra később. Ha ez továbbra is fennáll, akkor lépjen kapcsolatba a kiszolgáló rendszergazdájával.",
+        "error_jump_to_date_connection": "Hálózati hiba történt az adott dátum keresése és az ahhoz ugrás során. A Matrix-kiszolgálója lehet, hogy nem érhető el, vagy ideiglenes probléma van az internetkapcsolatával. Próbálja újra később. Ha ez továbbra is fennáll, akkor lépjen kapcsolatba a kiszolgáló adminisztrátorával.",
         "error_jump_to_date_details": "Hiba részletei",
         "error_jump_to_date_not_found": "Nem sikerült megtalálni az eseményt %(dateString)s után keresve. Próbáljon egy korábbi dátumot kiválasztani.",
         "error_jump_to_date_send_logs_prompt": "Küldjön be <debugLogsLink>hibakeresési naplókat</debugLogsLink>, hogy segítsen nekünk a hiba megtalálásában.",
         "error_jump_to_date_title": "Nem található esemény az adott dátumkor",
         "face_pile_summary": {
-            "one": "%(count)s ismerős már csatlakozott",
-            "other": "%(count)s ismerős már csatlakozott"
+            "%(count)s ismerős már csatlakozott": "other"
         },
         "face_pile_tooltip_label": {
-            "one": "1 résztvevő megmutatása",
-            "other": "Az összes %(count)s résztvevő megmutatása"
+            "1 résztvevő megmutatása": "one",
+            "Az összes %(count)s résztvevő megmutatása": "other"
         },
         "face_pile_tooltip_shortcut": "Beleértve: %(commaSeparatedMembers)s",
         "face_pile_tooltip_shortcut_joined": "Önt is beleértve, %(commaSeparatedMembers)s",
@@ -1758,16 +1946,18 @@
         "forget_space": "Ennek a térnek az elfelejtése",
         "header": {
             "n_people_asking_to_join": {
-                "one": "Csatlakozást kér",
-                "other": "%(count)s csatlakozást kérő ember"
+                "Csatlakozást kér": "one",
+                "%(count)s csatlakozást kérő ember": "other"
             },
             "room_is_public": "Ez egy nyilvános szoba"
         },
+        "header_avatar_open_settings_label": "Szobabeállítások megnyitása",
+        "header_face_pile_tooltip": "Taglista váltása",
         "header_untrusted_label": "Nem megbízható",
         "inaccessible": "Ez a szoba vagy tér jelenleg elérhetetlen.",
         "inaccessible_name": "%(roomName)s jelenleg nem érhető el.",
         "inaccessible_subtitle_1": "Próbálkozzon később vagy kérje meg a szoba vagy tér adminisztrátorát, hogy nézze meg van-e hozzáférése.",
-        "inaccessible_subtitle_2": "Amikor a szobát vagy teret próbáltuk elérni ezt a hibaüzenetet kaptuk: %(errcode)s. Ha úgy gondolja, hogy ez egy hiba legyen szíves<issueLink>nyisson egy hibajegyet</issueLink>.",
+        "inaccessible_subtitle_2": "A szoba vagy tér elérésekor ez a hibaüzenetet érkezett: %(errcode)s. Ha úgy gondolja, hogy az üzenetet egy hiba miatt látja, <issueLink>nyisson egy hibajegyet</issueLink>.",
         "intro": {
             "dm_caption": "Csak önök ketten vannak ebben a beszélgetésben, hacsak valamelyikőjük nem hív meg valakit, hogy csatlakozzon.",
             "enable_encryption_prompt": "Titkosítás bekapcsolása a beállításokban.",
@@ -1781,42 +1971,49 @@
             "start_of_room": "Ez a(z) <roomName/> kezdete.",
             "topic": "Téma: %(topic)s ",
             "topic_edit": "Téma: %(topic)s (<a>szerkesztés</a>)",
-            "unencrypted_warning": "Végpontok közötti titkosítás nincs engedélyezve",
+            "unencrypted_warning": "A végpontok közti titkosítás nincs engedélyezve",
             "user_created": "%(displayName)s készítette ezt a szobát.",
             "you_created": "Te készítetted ezt a szobát."
         },
         "invite_email_mismatch_suggestion": "Oszd meg a Beállításokban ezt az e-mail címet, hogy közvetlen meghívókat kaphass %(brand)sba.",
-        "invite_reject_ignore": "Felhasználó elutasítása és figyelmen kívül hagyása",
         "invite_sent_to_email": "Ez a meghívó ide lett küldve: %(email)s",
         "invite_sent_to_email_room": "A meghívó ehhez a szobához: %(roomName)s ide lett elküldve: %(email)s",
-        "invite_subtitle": "<userName/> meghívott",
+        "invite_subtitle": "Meghívta: <userName/>",
         "invite_this_room": "Meghívás a szobába",
         "invite_title": "%(roomName)s szobába szeretnél belépni?",
         "inviter_unknown": "Ismeretlen",
         "invites_you_text": "<inviter/> meghívta",
-        "join_button_account": "Fiók készítés",
+        "join_button_account": "Regisztráció",
         "join_failed_needs_invite": "A %(roomName)s megjelenítéséhez meghívó szükséges",
         "join_the_discussion": "Beszélgetéshez csatlakozás",
         "join_title": "Csatlakozz a szobához, hogy részt vehess",
-        "join_title_account": "Beszélgetéshez való csatlakozás felhasználói fiókkal lehetséges",
+        "join_title_account": "Csatlakozzon a beszélgetéshez egy fiók létrehozásával",
         "joining": "Belépés…",
         "joining_room": "Belépés a szobába…",
         "joining_space": "Belépés a térbe…",
         "jump_read_marker": "Ugrás az első olvasatlan üzenetre.",
-        "jump_to_bottom_button": "A legfrissebb üzenethez görget",
+        "jump_to_bottom_button": "A legfrissebb üzenethez görgetés",
         "jump_to_date": "Ugrás időpontra",
         "jump_to_date_beginning": "A szoba indulása",
         "jump_to_date_prompt": "Idő kiválasztása az ugráshoz",
         "kick_reason": "Ok: %(reason)s",
         "kicked_by": "%(memberName)s felhasználó eltávolította",
         "kicked_from_room_by": "Önt %(memberName)s eltávolította ebből a szobából: %(roomName)s",
-        "knock_denied_subtitle": "Mivel megtagadták a hozzáférést, csak akkor csatlakozhat újra, ha a csoport adminisztrátora vagy moderátora meghívja.",
+        "knock_cancel_action": "Kérés törlése",
+        "knock_denied_subtitle": "Mivel megtagadták a hozzáférést, csak akkor csatlakozhat újra, ha a csoport rendszergazdája vagy moderátora meghívja.",
         "knock_denied_title": "A hozzáférést megtagadták",
+        "knock_message_field_placeholder": "Üzenet (opcionális)",
+        "knock_prompt": "Szeretne csatlakozni?",
+        "knock_prompt_name": "Szeretne csatlakozni ide:%(roomName)s?",
+        "knock_send_action": "Hozzáférés kérése",
+        "knock_sent": "Csatlakozási kérelem elküldve",
+        "knock_sent_subtitle": "Csatlakozási kérelme függőben van.",
+        "knock_subtitle": "A beszélgetés megtekintéséhez vagy abban való részvételhez hozzáférést kell kapnia ebbe a szobába. Az alábbiakban küldhet csatlakozási kérelmet.",
         "leave_error_title": "Hiba a szoba elhagyásakor",
         "leave_server_notices_description": "Ez a szoba a Matrix-kiszolgáló fontos kiszolgálóüzenetei közlésére jött létre, nem tud belőle kilépni.",
         "leave_server_notices_title": "Nem lehet elhagyni a Kiszolgálóüzenetek szobát",
         "leave_unexpected_error": "Váratlan kiszolgálóhiba lépett fel a szobából való kilépés során",
-        "link_email_to_receive_3pid_invite": "Kösd össze a Beállításokban ezt az e-mail címet a fiókoddal, hogy közvetlenül a %(brand)sba kaphassa meghívókat.",
+        "link_email_to_receive_3pid_invite": "Kösse össze a Beállításokban ezt az e-mail-címet a fiókjával, hogy közvetlenül az %(brand)sben kapja meg a meghívókat.",
         "loading_preview": "Előnézet betöltése",
         "no_peek_join_prompt": "%(roomName)s szobának nincs előnézete. Be szeretnél lépni?",
         "no_peek_no_name_join_prompt": "Előnézet nincs, szeretne csatlakozni?",
@@ -1824,29 +2021,41 @@
         "not_found_title": "Ez a szoba vagy tér nem létezik.",
         "not_found_title_name": "%(roomName)s nem létezik.",
         "peek_join_prompt": "%(roomName)s szoba előnézetét látod. Belépsz?",
+        "pinned_message_badge": "Kitűzött üzenet",
+        "pinned_message_banner": {
+            "button_close_list": "A lista bezárása",
+            "button_view_all": "Összes megtekintése",
+            "description": "Ez a szoba rögzített üzeneteket tartalmaz. Kattintson ide a megtekintésükhöz.",
+            "go_to_message": "Tekintse meg a rögzített üzenetet az idővonalon.",
+            "title": "<bold>%(index)s. / %(length)s </bold> rögzített üzenet"
+        },
         "read_topic": "Kattintson a téma elolvasásához",
         "rejecting": "Meghívó elutasítása…",
         "rejoin_button": "Újra-csatlakozás",
         "search": {
             "all_rooms_button": "Keresés az összes szobában",
+            "placeholder": "Üzenetek keresése...",
+            "summary": {
+                "1 találat ehhez: „<query/>”": "one",
+                "%(count)s találat ehhez: „<query/>”": "other"
+            },
             "this_room_button": "Keresés ebben a szobában"
         },
         "status_bar": {
             "delete_all": "Mind törlése",
-            "exceeded_resource_limit": "Az üzenete nem lett elküldve, mert a Matrix-kiszolgáló túllépett egy erőforráskorlátot. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás rendszergazdájával</a>.",
-            "homeserver_blocked": "Az üzenete nem lett elküldve, mert a Matrix-kiszolgáló rendszergazdája letiltotta. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás rendszergazdájával</a>.",
-            "monthly_user_limit_reached": "Az üzenete nem lett elküldve, mert ez a Matrix-kiszolgáló elérte a havi aktív felhasználói korlátot. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás rendszergazdájával</a>.",
+            "exceeded_resource_limit": "Az üzenete nem lett elküldve, mert a Matrix-kiszolgáló túllépett egy erőforráskorlátot. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás adminisztrátorával</a>.",
+            "homeserver_blocked": "Az üzenete nem lett elküldve, mert a Matrix-kiszolgáló adminisztrátora letiltotta. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás adminisztrátorával</a>.",
+            "monthly_user_limit_reached": "Az üzenete nem lett elküldve, mert ez a Matrix-kiszolgáló elérte a havi aktív felhasználói korlátot. A szolgáltatás használatának folytatásához <a>vegye fel a kapcsolatot a szolgáltatás adminisztrátorával</a>.",
             "requires_consent_agreement": "Nem tudsz üzenetet küldeni amíg nem olvasod el és nem fogadod el a <consentLink>felhasználási feltételeket</consentLink>.",
-            "retry_all": "Mind újraküldése",
+            "retry_all": "Összes újraküldése",
             "select_messages_to_retry": "Újraküldéshez vagy törléshez kiválaszthatja az üzeneteket egyenként vagy az összeset együtt",
-            "server_connectivity_lost_description": "Az elküldött üzenetek addig lesznek tárolva amíg a kapcsolatod újra elérhető lesz.",
+            "server_connectivity_lost_description": "Az elküldött üzenetek tárolva lesznek, amíg a kapcsolata újra elérhető nem lesz.",
             "server_connectivity_lost_title": "A kapcsolat megszakadt a kiszolgálóval.",
             "some_messages_not_sent": "Néhány üzenete nem lett elküldve"
         },
         "unknown_status_code_for_timeline_jump": "ismeretlen állapotkód",
         "unread_notifications_predecessor": {
-            "other": "%(count)s olvasatlan értesítésed van a régi verziójú szobában.",
-            "one": "%(count)s olvasatlan értesítésed van a régi verziójú szobában."
+            "%(count)s olvasatlan értesítésed van a régi verziójú szobában.": "one"
         },
         "upgrade_error_description": "Ellenőrizze még egyszer, hogy a kiszolgálója támogatja-e kiválasztott szobaverziót, és próbálja újra.",
         "upgrade_error_title": "Hiba a szoba verziófrissítésekor",
@@ -1856,40 +2065,91 @@
         "upgrade_warning_bar_upgraded": "Ez a szoba már fejlesztve van.",
         "upload": {
             "uploading_multiple_file": {
-                "one": "%(filename)s és még %(count)s db másik feltöltése",
-                "other": "%(filename)s és még %(count)s db másik feltöltése"
+                "%(filename)s és még %(count)s db másik feltöltése": "other"
             },
             "uploading_single_file": "%(filename)s feltöltése"
-        }
+        },
+        "video_room": "Ez a szoba egy videószoba",
+        "waiting_for_join_subtitle": "Miután a meghívott felhasználók csatlakoztak ide: %(brand)s, beszélgethet, és a szoba végponttól végpontig titkosítva lesz",
+        "waiting_for_join_title": "Várakozás a felhasználók csatlakozására ide: %(brand)s"
     },
     "room_list": {
         "add_room_label": "Szoba hozzáadása",
         "add_space_label": "Tér hozzáadása",
+        "appearance": "Megjelenés",
         "breadcrumbs_empty": "Nincsenek nemrégiben meglátogatott szobák",
         "breadcrumbs_label": "Nemrég meglátogatott szobák",
+        "empty": {
+            "no_chats": "Még nincsenek csevegések",
+            "no_chats_description": "Kezdje azzal, hogy üzenetet küld valakinek, vagy létrehoz egy szobát",
+            "no_chats_description_no_room_rights": "Kezdje azzal, hogy üzenetet küld valakinek",
+            "no_favourites": "Még nincs kedvenc csevegése",
+            "no_favourites_description": "A csevegési beállításokban adhat hozzá csevegést a kedvencekhez",
+            "no_invites": "Nincs olvasatlan meghívója",
+            "no_mentions": "Nincs olvasatlan említése",
+            "no_people": "Még nincs közvetlen csevegése senkivel",
+            "no_people_description": "Kikapcsolhatja a szűrőket a többi csevegés megtekintéséhez",
+            "no_rooms": "Még nincs egy szobában sem",
+            "no_rooms_description": "Kikapcsolhatja a szűrőket a többi csevegés megtekintéséhez",
+            "no_unread": "Gratulálunk! Nincsenek olvasatlan üzenetei.",
+            "show_activity": "Összes tevékenység megtekintése",
+            "show_chats": "Összes csevegés megjelenítése"
+        },
         "failed_add_tag": "Nem sikerült hozzáadni a szobához ezt: %(tagName)s",
         "failed_remove_tag": "Nem sikerült a szobáról eltávolítani ezt: %(tagName)s",
         "failed_set_dm_tag": "Nem sikerült a közvetlen beszélgetés címkét beállítani",
+        "filters": {
+            "favourite": "Kedvencek",
+            "invites": "Meghívók",
+            "mentions": "Említések",
+            "people": "Emberek",
+            "rooms": "Szobák",
+            "unread": "Olvasatlan"
+        },
         "home_menu_label": "Kezdőlap beállítások",
         "join_public_room_label": "Belépés nyilvános szobába",
         "joining_rooms_status": {
-            "one": "%(count)s szobába lép be",
-            "other": "%(count)s szobába lép be"
+            "%(count)s szobába lép be": "other"
+        },
+        "list_title": "Szobalista",
+        "more_options": {
+            "copy_link": "Szoba hivatkozásának másolása",
+            "favourited": "Kedvencnek jelölve",
+            "leave_room": "Szoba elhagyása",
+            "low_priority": "Alacsony prioritás",
+            "mark_read": "Megjelölés olvasottként",
+            "mark_unread": "Megjelölés olvasatlanként"
         },
         "notification_options": "Értesítési beállítások",
+        "open_space_menu": "Tér menü megnyitása",
+        "primary_filters": "Szobalistaszűrők",
         "redacting_messages_status": {
-            "one": "Üzenet törlése %(count)s szobából",
-            "other": "Üzenet törlése %(count)s szobából"
+            "Üzenet törlése %(count)s szobából": "other"
+        },
+        "room": {
+            "more_options": "További lehetőségek",
+            "open_room": "A(z) %(roomName)s szoba megnyitása"
         },
+        "room_options": "Szobabeállítások",
         "show_less": "Kevesebb megjelenítése",
+        "show_message_previews": "Üzenetelőnézetek megjelenítése",
         "show_n_more": {
-            "other": "Még %(count)s megjelenítése",
-            "one": "Még %(count)s megjelenítése"
+            "Még %(count)s megjelenítése": "one"
         },
-        "show_previews": "Üzenet előnézet megjelenítése",
+        "show_previews": "Üzenet-előnézet megjelenítése",
+        "sort": "Rendezés",
         "sort_by": "Rendezés",
         "sort_by_activity": "Aktivitás",
-        "sort_unread_first": "Olvasatlan üzeneteket tartalmazó szobák megjelenítése elől",
+        "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Tevékenység",
+            "atoz": "A-Z"
+        },
+        "sort_unread_first": "Olvasatlan üzeneteket tartalmazó szobák megjelenítése elöl",
+        "space_menu": {
+            "home": "Tér kezdőlapja",
+            "space_settings": "Tér beállításai"
+        },
         "space_menu_label": "%(spaceName)s menű",
         "sublist_options": "Lista beállításai",
         "suggested_rooms_heading": "Javasolt szobák"
@@ -1927,6 +2187,7 @@
             "upgrade_warning_dialog_invite_label": "Tagok automatikus meghívása ebből a szobából az újba",
             "upgrade_warning_dialog_report_bug_prompt": "Ez általában a szoba kiszolgálóoldali kezelésében jelent változást. Ha a(z) %(brand)s kliensben tapasztal problémát, akkor küldjön egy hibajelentést.",
             "upgrade_warning_dialog_report_bug_prompt_link": "Ez általában a szoba kiszolgálóoldali kezelésében jelent változást. Ha a(z) %(brand)s kliensben tapasztal problémát, akkor <a>küldjön egy hibajelentést</a>.",
+            "upgrade_warning_dialog_title": "Szoba fejlesztése",
             "upgrade_warning_dialog_title_private": "Privát szoba fejlesztése"
         },
         "alias_not_specified": "nincs meghatározva",
@@ -1943,7 +2204,7 @@
             "alias_field_placeholder_default": "pl.: szobam",
             "alias_field_required_invalid": "Kérem adja meg a címet",
             "alias_field_safe_localpart_invalid": "Néhány karakter nem engedélyezett",
-            "alias_field_taken_invalid": "Ez a cím érvénytelen szervert tartalmaz vagy már használatban van",
+            "alias_field_taken_invalid": "Ez a cím érvénytelen kiszolgálót tartalmaz, vagy már használatban van",
             "alias_field_taken_invalid_domain": "Ez a cím már használatban van",
             "alias_field_taken_valid": "Ez a cím használható",
             "alias_heading": "Szoba címe",
@@ -1952,50 +2213,58 @@
             "aliases_section": "Szobacímek",
             "avatar_field_label": "Szoba profilképe",
             "canonical_alias_field_label": "Fő cím",
-            "default_url_previews_off": "Az URL előnézet alapértelmezetten tiltva van a szobában jelenlévőknek.",
-            "default_url_previews_on": "Az URL előnézetek alapértelmezetten engedélyezve vannak a szobában jelenlévőknek.",
+            "default_url_previews_off": "A webcím-előnézet alapértelmezetten tiltva van a szobában lévőknek.",
+            "default_url_previews_on": "A webcím-előnézet alapértelmezetten engedélyezve van a szobában lévőknek.",
             "description_space": "A tér beállításainak szerkesztése.",
-            "error_creating_alias_description": "A cím beállításánál hiba történt. Vagy nincs engedélyezve a szerveren vagy átmeneti hiba történt.",
+            "error_creating_alias_description": "Hiba történt a cím létrehozása során. Nincs engedélyezve a kiszolgálón, vagy átmeneti hiba történt.",
             "error_creating_alias_title": "Cím beállítási hiba",
             "error_deleting_alias_description": "A cím törlésénél hiba történt. Vagy már nem létezik vagy átmeneti hiba történt.",
             "error_deleting_alias_description_forbidden": "A cím törléséhez nincs jogosultságod.",
             "error_deleting_alias_title": "Cím törlésénél hiba történt",
+            "error_publishing": "Nem lehet közzétenni a szobát",
+            "error_publishing_detail": "Hiba történt a szoba közzétételekor",
             "error_save_space_settings": "A tér beállításának mentése sikertelen.",
-            "error_updating_alias_description": "A szoba címének megváltoztatásakor hiba történt. Lehet, hogy a szerver nem engedélyezi vagy átmeneti hiba történt.",
-            "error_updating_canonical_alias_description": "A szoba elsődleges címének frissítésénél hiba történt. Vagy nincs engedélyezve a szerveren vagy átmeneti hiba történt.",
+            "error_updating_alias_description": "Hiba történt a szoba alternatív címeinek frissítése során. Nincs engedélyezve a kiszolgálón, vagy átmeneti hiba történt.",
+            "error_updating_canonical_alias_description": "Hiba történt a szoba elsődleges címének frissítése során. Nincs engedélyezve a kiszolgálón, vagy átmeneti hiba történt.",
             "error_updating_canonical_alias_title": "Az elsődleges cím frissítése sikertelen",
             "leave_space": "Tér elhagyása",
             "local_alias_field_label": "Helyi cím",
-            "local_aliases_explainer_room": "Állíts be címet ehhez a szobához, hogy a felhasználók a matrix szervereden megtalálhassák (%(localDomain)s)",
-            "local_aliases_explainer_space": "Cím beállítása ehhez a térhez, hogy a felhasználók a matrix szerveren megtalálhassák (%(localDomain)s)",
+            "local_aliases_explainer_room": "Állítson be címeket ehhez a szobához, hogy a felhasználók megtalálhassák a Matrix-kiszolgálón (%(localDomain)s) keresztül",
+            "local_aliases_explainer_space": "Állítson be címeket ehhez a térhez, hogy a felhasználók megtalálhassák a Matrix-kiszolgálón (%(localDomain)s) keresztül",
             "local_aliases_section": "Helyi címek",
             "name_field_label": "Szoba neve",
-            "new_alias_placeholder": "Új nyilvános cím (pl.: #becenév:szerver)",
+            "new_alias_placeholder": "Új nyilvános cím (pl.: #becenév:kiszolgáló)",
             "no_aliases_room": "Ennek a szobának nincs helyi címe",
             "no_aliases_space": "Ennek a térnek nincs helyi címe",
             "other_section": "Egyéb",
             "publish_toggle": "Publikálod a szobát a(z) %(domain)s szoba listájába?",
             "published_aliases_description": "A cím publikálásához először helyi címet kell beállítani.",
-            "published_aliases_explainer_room": "A nyilvánosságra hozott címet bárki bármelyik szerverről használhatja a szobához való belépéshez.",
-            "published_aliases_explainer_space": "A nyilvánosságra hozott címet bárki bármelyik szerverről használhatja a térbe való belépéshez.",
+            "published_aliases_explainer_room": "A közzétett címet bárki használhatja a szobába való belépéshez, bármelyik kiszolgálóról.",
+            "published_aliases_explainer_space": "A közzétett címet bárki használhatja a térbe való belépéshez, bármelyik kiszolgálóról.",
             "published_aliases_section": "Nyilvánosságra hozott cím",
             "save": "Változtatások mentése",
             "topic_field_label": "Szoba témája",
-            "url_preview_encryption_warning": "A titkosított szobákban, mint például ez is, az URL előnézet alapértelmezetten ki van kapcsolva, hogy biztosított legyen, hogy a Matrix szerver (ahol az előnézet készül) ne tudjon információt gyűjteni arról, hogy milyen linkeket látsz ebben a szobában.",
-            "url_preview_explainer": "Ha valaki URL linket helyez az üzenetébe, lehetőség van egy előnézet megjelenítésére amivel további információt kaphatunk a linkről, mint cím, leírás és a weboldal képe.",
-            "url_previews_section": "URL előnézet",
-            "user_url_previews_default_off": "Az URL előnézet alapból <a>tiltva</a> van.",
-            "user_url_previews_default_on": "Az URL előnézet alapból <a>engedélyezve</a> van."
+            "url_preview_encryption_warning": "A titkosított szobákban, mint például ez is, a webcím-előnézet alapértelmezetten ki van kapcsolva, hogy biztosított legyen, hogy a Matrix-kiszolgáló (amelyen az előnézet készül) ne tudjon információt gyűjteni arról, hogy milyen hivatkozásokat lát ebben a szobában.",
+            "url_preview_explainer": "Ha valaki webcímet helyez az üzenetébe, akkor lehetőség van egy előnézet megjelenítésére, amellyel további információt kaphat a hivatkozásról, mint a cím, a leírás és a weboldal képe.",
+            "url_previews_section": "Webcím-előnézet",
+            "user_url_previews_default_off": "A webcím-előnézet alapból <a>tiltva</a> van.",
+            "user_url_previews_default_on": "A webcím-előnézet alapból <a>engedélyezve</a> van."
         },
         "notifications": {
             "browse_button": "Böngészés",
-            "custom_sound_prompt": "Új egyénii hang beállítása",
+            "custom_sound_prompt": "Új egyéni hang beállítása",
             "notification_sound": "Értesítési hang",
             "settings_link": "Értesítések fogadása a <a>beállításokban</a> megadottak szerint",
             "sounds_section": "Hangok",
             "upload_sound_label": "Egyéni hang feltöltése",
             "uploaded_sound": "Feltöltött hang"
         },
+        "people": {
+            "knock_empty": "Nincsenek kérések",
+            "knock_section": "Csatlakozás kérése",
+            "see_less": "Részletek elrejtése",
+            "see_more": "Részletek megjelenítése"
+        },
         "permissions": {
             "add_privileged_user_description": "Több jog adása egy vagy több felhasználónak a szobában",
             "add_privileged_user_filter_placeholder": "Felhasználók keresése a szobában…",
@@ -2054,10 +2323,11 @@
             "encrypted_room_public_confirm_description_1": "<b>Titkosított szobát nem célszerű nyilvánossá tenni.</b> Bárki megtalálhatja és csatlakozhat nyilvános szobákhoz, így bárki elolvashatja az üzeneteket bennük. A titkosítás előnyeit így nem jelentkeznek és később ezt nem lehet kikapcsolni. Nyilvános szobákban a titkosított üzenetek az üzenetküldést és fogadást csak lassítják.",
             "encrypted_room_public_confirm_description_2": "Az ehhez hasonló problémák elkerüléséhez készítsen <a>új nyilvános szobát</a> a tervezett beszélgetésekhez.",
             "encrypted_room_public_confirm_title": "Biztos, hogy nyilvánossá teszi ezt a titkosított szobát?",
+            "encryption_forced": "A kiszolgáló megköveteli a titkosítás letiltását.",
             "encryption_permanent": "Ha egyszer bekapcsolod, már nem lehet kikapcsolni.",
             "error_join_rule_change_title": "A csatlakozási szabályokat nem sikerült frissíteni",
             "error_join_rule_change_unknown": "Ismeretlen hiba",
-            "guest_access_warning": "Emberek támogatott kliensekkel, még regisztrált fiók nélkül is, beléphetnek a szobába.",
+            "guest_access_warning": "A támogatott klienseket használó emberek még regisztrált fiók nélkül is beléphetnek a szobába.",
             "history_visibility_invited": "Csak tagoknak (a meghívásuk idejétől)",
             "history_visibility_joined": "Csak tagoknak (amióta csatlakoztak)",
             "history_visibility_legend": "Ki olvashatja a régi üzeneteket?",
@@ -2067,6 +2337,8 @@
             "join_rule_description": "Döntse el ki léphet be ide: %(roomName)s.",
             "join_rule_invite": "Privát (csak meghívóval)",
             "join_rule_invite_description": "Csak a meghívott emberek léphetnek be.",
+            "join_rule_knock": "Kérjen csatlakozást",
+            "join_rule_knock_description": "Az emberek nem léphetnek be, hacsak nem kapnak engedélyt a belépéshez.",
             "join_rule_public_description": "Bárki megtalálhatja és beléphet.",
             "join_rule_restricted": "Tértagság",
             "join_rule_restricted_description": "A téren bárki megtalálhatja és beléphet. <a>Szerkessze, hogy melyik tér férhet hozzá.</a>",
@@ -2076,34 +2348,36 @@
             "join_rule_restricted_dialog_description": "Döntse el melyik terek férhetnek hozzá ehhez a szobához. Ha a tér ki van választva a tagsága megtalálhatja és beléphet ebbe a szobába: <RoomName/>.",
             "join_rule_restricted_dialog_empty_warning": "Az összes teret törli. A hozzáférés alapállapota „csak meghívóval” lesz.",
             "join_rule_restricted_dialog_filter_placeholder": "Terek keresése",
+            "join_rule_restricted_dialog_heading_known": "Más terek, amelyeket ismerhet",
             "join_rule_restricted_dialog_heading_other": "Más terek vagy szobák melyről lehet, hogy nem tud",
             "join_rule_restricted_dialog_heading_room": "Terek melyről tudja, hogy ezt a szobát tartalmazzák",
             "join_rule_restricted_dialog_heading_space": "Terek melyről tudja, hogy ezt a teret tartalmazzák",
-            "join_rule_restricted_dialog_heading_unknown": "Ezek valószínűleg olyanok, amelyeknek más szoba adminok is tagjai.",
+            "join_rule_restricted_dialog_heading_unknown": "Ezek valószínűleg olyanok, amelyeknek más szobaadminisztrátorok is tagjai.",
             "join_rule_restricted_dialog_title": "Terek kiválasztása",
             "join_rule_restricted_n_more": {
-                "other": "és még %(count)s",
-                "one": "és még %(count)s"
+                "és még %(count)s": "one"
             },
             "join_rule_restricted_summary": {
-                "other": "Jelenleg %(count)s tér rendelkezik hozzáféréssel",
-                "one": "Jelenleg egy tér rendelkezik hozzáféréssel"
+                "Jelenleg %(count)s tér rendelkezik hozzáféréssel": "other",
+                "Jelenleg egy tér rendelkezik hozzáféréssel": "one"
             },
             "join_rule_restricted_upgrade_description": "Ez a fejlesztés lehetővé teszi, hogy a kiválasztott terek tagjai meghívó nélkül is elérjék ezt a szobát.",
             "join_rule_restricted_upgrade_warning": "Ez a szoba olyan terekben is benne van, amelynek nem Ön az adminisztrátora. Ezekben a terekben továbbra is a régi szoba jelenik meg, de az emberek jelzést kapnak, hogy lépjenek be az újba.",
             "join_rule_upgrade_awaiting_room": "Új szoba betöltése",
             "join_rule_upgrade_required": "Fejlesztés szükséges",
             "join_rule_upgrade_sending_invites": {
-                "one": "Meghívók küldése…",
-                "other": "Meghívók küldése… (%(progress)s / %(count)s)"
+                "Meghívók küldése…": "one",
+                "Meghívók küldése… (%(progress)s / %(count)s)": "other"
             },
             "join_rule_upgrade_updating_spaces": {
-                "one": "Terek frissítése…",
-                "other": "Terek frissítése… (%(progress)s / %(count)s)"
+                "Terek frissítése…": "one",
+                "Terek frissítése… (%(progress)s / %(count)s)": "other"
             },
             "join_rule_upgrade_upgrading_room": "Szoba fejlesztése",
-            "public_without_alias_warning": "Hogy linkelhess egy szobához, adj hozzá egy címet.",
-            "strict_encryption": "Ebben a szobában sose küldjön titkosított üzenetet ellenőrizetlen munkamenetekbe ebből a munkamenetből",
+            "public_without_alias_warning": "Hogy hivatkozhasson erre a szobára, adjon hozzá egy címet.",
+            "publish_room": "Szoba láthatóvá tétele a nyilvános szobakatalógusban.",
+            "publish_space": "Tér láthatóvá tétele a nyilvános szobakatalógusban.",
+            "strict_encryption": "Üzenetküldés csak ellenőrzött felhasználóknak",
             "title": "Biztonság és adatvédelem"
         },
         "title": "Szoba beállításai – %(roomName)s",
@@ -2157,22 +2431,28 @@
         "recent_changes_heading": "A legutóbbi változások, amelyek még nem érkeztek meg",
         "title": "A kiszolgáló nem válaszol"
     },
+    "service_worker_error": {
+        "description": "Az %(brand)s ún. service workert igényel a hitelesített médiafájlok Matrix tartalomtárolókból történő betöltéséhez. Ezt a böngészője nem támogatja, ezért előfordulhat, hogy a médiafájlok nem töltődnek be.",
+        "title": "Nem sikerült betölteni a service workert"
+    },
     "seshat": {
-        "error_initialising": "Üzenek keresés kezdő beállítása sikertelen, ellenőrizze a <a>beállításait</a> további információkért",
+        "error_initialising": "Az üzenetkeresés előkészítése sikertelen, további információkért ellenőrizze a <a>beállításait</a>",
         "reset_button": "Az eseménytároló alaphelyzetbe állítása",
         "reset_description": "Az eseményindex-tárolót nagy valószínűséggel nem szeretné alaphelyzetbe állítani",
         "reset_explainer": "Ha ezt teszi, tudnia kell, hogy az üzenetek nem lesznek törölve, de a keresési élmény addig nem lesz tökéletes, amíg az indexek újra el nem készülnek",
         "reset_title": "Alaphelyzetbe állítja az eseménytárolót?",
-        "warning_kind_files": "%(brand)s ezen verziója nem minden titkosított fájl megjelenítését támogatja",
-        "warning_kind_files_app": "Ahhoz, hogy elérd az összes titkosított fájlt, használd az <a>Asztali alkalmazást</a>",
-        "warning_kind_search": "%(brand)s ezen verziója nem támogatja a keresést a titkosított üzenetekben",
-        "warning_kind_search_app": "A titkosított üzenetek kereséséhez használd az <a>Asztali alkalmazást</a>"
+        "warning_kind_files": "Az %(brand)s ezen verziója nem minden titkosított fájl megjelenítését támogatja",
+        "warning_kind_files_app": "Az összes titkosított fájl eléréséhez használja az <a>asztali alkalmazást</a>",
+        "warning_kind_search": "Az %(brand)s ezen verziója nem támogatja a titkosított üzenetekben való keresést",
+        "warning_kind_search_app": "A titkosított üzenetek kereséséhez használja az <a>asztali alkalmazást</a>"
     },
     "setting": {
         "help_about": {
             "access_token_detail": "A hozzáférési kulcs teljes elérést biztosít a fiókhoz. Soha ne ossza meg mással.",
             "brand_version": "%(brand)s verzió:",
             "clear_cache_reload": "Gyorsítótár ürítése és újratöltés",
+            "crypto_version": "Kripto verzió:",
+            "dialog_title": "<strong>Beállítások:</strong> Súgó és névjegy",
             "help_link": "Az %(brand)s használatában való segítséghez kattintson <a>ide</a>.",
             "homeserver": "Matrix-kiszolgáló: <code>%(homeserverUrl)s</code>",
             "identity_server": "Azonosítási kiszolgáló: <code>%(identityServerUrl)s</code>",
@@ -2181,17 +2461,30 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Beállítások: </strong> Fiók",
+            "title": "Fiók"
+        },
         "all_rooms_home": "Minden szoba megjelenítése a Kezdőlapon",
         "all_rooms_home_description": "Minden szoba, amelybe belépett, megjelenik a Kezdőlapon.",
         "always_show_message_timestamps": "Üzenetek időbélyegének megjelenítése mindig",
         "appearance": {
+            "bundled_emoji_font": "Mellékelt emodzsi-betűkészlet használata",
+            "compact_layout": "Kompakt szövegek és üzenetek megjelenítése",
+            "compact_layout_description": "A funkció használatához modern elrendezést kell választani.",
             "custom_font": "Rendszer betűkészletének használata",
             "custom_font_description": "Adja meg a rendszer által használt betűkészlet nevét, és az %(brand)s megpróbálja azt használni.",
             "custom_font_name": "Rendszer betűkészletének neve",
             "custom_font_size": "Egyéni méret használata",
-            "custom_theme_error_downloading": "Hiba a témainformációk letöltése során.",
+            "custom_theme_add": "Egyéni téma hozzáadása",
+            "custom_theme_downloading": "Egyéni téma letöltése…",
+            "custom_theme_error_downloading": "Hiba a téma letöltése során",
+            "custom_theme_help": "Adja meg az alkalmazandó egyéni téma webcímét.",
             "custom_theme_invalid": "Érvénytelen témaséma.",
+            "dialog_title": "<strong>Beállítások:</strong> Megjelenés",
             "font_size": "Betűméret",
+            "font_size_default": "%(fontSize)s (alapértelmezett)",
+            "high_contrast": "Magas kontrasztú",
             "image_size_default": "Alapértelmezett",
             "image_size_large": "Nagy",
             "layout_bubbles": "Üzenetbuborékok",
@@ -2206,9 +2499,80 @@
         "code_block_expand_default": "Kódblokkok kibontása alapértelmezetten",
         "code_block_line_numbers": "Sorszámok megjelenítése a kódblokkokban",
         "disable_historical_profile": "A felhasználók jelenlegi profilképének és nevének megjelenítése az üzenetelőzményekben",
+        "discovery": {
+            "title": "Hogyan találhatják meg"
+        },
         "emoji_autocomplete": "Emodzsik gépelés közbeni felajánlásának bekapcsolása",
         "enable_markdown": "Markdown engedélyezése",
         "enable_markdown_description": "Kezdje az üzenetet a <code>/plain</code> paranccsal, hogy markdown formázás nélkül küldje el.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Fiókadatait, névjegyeit, beállításait és csevegési listáját megőrizzük",
+                "breadcrumb_page": "Titkosítás alaphelyzetbe állítása",
+                "breadcrumb_second_description": "Elveszít minden olyan üzenetelőzményt, amely csak a kiszolgálón van tárolva.",
+                "breadcrumb_third_description": "Újra ellenőriznie kell az összes meglévő eszközét és névjegyét",
+                "breadcrumb_title": "Biztos, hogy alaphelyzetbe állítja a személyazonosságát?",
+                "breadcrumb_title_forgot": "Elfelejtette a helyreállítási kulcsot? Alaphelyzetbe kell állítania a személyazonosságát.",
+                "breadcrumb_title_sync_failed": "A kulcstároló szinkronizálása sikertelen. Alaphelyzetbe kell állítania személyazonosságát.",
+                "breadcrumb_warning": "Csak akkor tegye ezt, ha úgy gondolja, hogy fiókját feltörték.",
+                "details_title": "Titkosítás részletei",
+                "do_not_close_warning": "Ne zárja be ezt az ablakot, amíg az alaphelyzetbe állítás be nem fejeződik",
+                "export_keys": "Kulcsok exportálása",
+                "import_keys": "Kulcsok importálása",
+                "other_people_device_description": "Figyelmeztetés: azok a felhasználók, akikkel nem ellenőrizték kölcsönösen egymást (például emodzsik használatával), nem kapják meg a titkosított üzeneteket. Ezenkívül az ellenőrzött felhasználók nem ellenőrzött eszközei sem kapják meg a titkosított üzeneteket.",
+                "other_people_device_label": "Titkosított szobákban csak az ellenőrzött felhasználók kapják meg az üzeneteket",
+                "other_people_device_title": "Mások eszközei",
+                "reset_identity": "Kriptográfiai személyazonosság alaphelyzetbe állítása",
+                "reset_in_progress": "Alaphelyzetbe állítás folyamatban…",
+                "session_id": "Munkamenet-azonosító:",
+                "session_key": "Munkamenetkulcs:",
+                "title": "Speciális"
+            },
+            "confirm_key_storage_off": "Biztosan kikapcsolva szeretné tartani a kulcstárolást?",
+            "confirm_key_storage_off_description": "Ha kijelentkezik az összes eszközéről, elveszíti üzenetelőzményeit, és újra ellenőriznie kell az összes meglévő kapcsolatát. <a>Tudjon meg többet</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Kulcstároló törlése",
+                "confirm": "Kulcstároló törlése",
+                "description": "A kulcstároló törlése eltávolítja a kriptográfiai személyazonosságát és üzenetkulcsát a kiszolgálóról, és kikapcsolja a következő biztonsági funkciókat:",
+                "list_first": "Nem lesznek meg a titkosított üzenetek előzményei az új eszközein",
+                "list_second": "Elveszíti hozzáférését titkosított üzeneteihez, ha mindenhol kijelentkezett az %(brand)sből.",
+                "title": "Biztos, hogy kikapcsolja a kulcstárolót és törli azt?"
+            },
+            "device_not_verified_button": "Az eszköz ellenőrzése",
+            "device_not_verified_description": "A titkosítási beállítások megtekintéséhez ellenőriznie kell ezt az eszközt.",
+            "device_not_verified_title": "Az eszköz nincs ellenőrizve",
+            "dialog_title": "<strong>Beállítások:</strong> Titkosítás",
+            "key_storage": {
+                "allow_key_storage": "Kulcstárolás engedélyezése",
+                "description": "Tárolja biztonságosan a kriptográfiai személyazonosságát és az üzenetkulcsait a kiszolgálón. Ez lehetővé teszi az üzenetek előzményeinek megtekintését bármely új eszközön. <a>További információ</a>",
+                "title": "Kulcstároló"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Új helyreállítási kulcs megerősítése",
+                "change_recovery_confirm_description": "A befejezéshez adja meg alább az új helyreállítási kulcsot. A régi már nem fog működni.",
+                "change_recovery_confirm_title": "Adja meg az új helyreállítási kulcsot",
+                "change_recovery_key": "Helyreállítási kulcs módosítása",
+                "change_recovery_key_description": "Írja le ezt az új helyreállítási kulcsot egy biztonságos helyre. Ezután kattintson a Folytatás gombra a módosítás megerősítéséhez.",
+                "change_recovery_key_title": "Módosítja a helyreállítási kulcsot?",
+                "description": "Ha az összes meglévő eszközét elvesztette, akkor egy helyreállítási kulccsal visszaszerezheti a kriptográfiai személyazonosságát és az üzenetelőzményeit.",
+                "enter_key_error": "A megadott helyreállítási kulcs helytelen.",
+                "enter_recovery_key": "Adja meg a helyreállítási kulcsot",
+                "forgot_recovery_key": "Elfelejtette a helyreállítási kulcsot?",
+                "key_storage_warning": "A kulcstároló nincs szinkronban. Kattintson az alábbi gombra a probléma megoldásához.",
+                "save_key_description": "Ne ossza meg ezt senkivel!",
+                "save_key_title": "Helyreállítási kulcs",
+                "set_up_recovery": "A helyreállítás beállítása",
+                "set_up_recovery_confirm_button": "Beállítás befejezése",
+                "set_up_recovery_confirm_description": "Adja meg az előző képernyőn látható helyreállítási kulcsot a helyreállítás beállításának befejezéséhez.",
+                "set_up_recovery_confirm_title": "A megerősítéshez adja meg a helyreállítási kulcsot",
+                "set_up_recovery_description": "A kulcstárolót helyreállítási kulcs védi. Ha a beállítás után új helyreállítási kulcsra van szüksége, akkor azt a „%(changeRecoveryKeyButton)s” választásával hozhatja létre újra.",
+                "set_up_recovery_save_key_description": "Jegyezze fel ezt a helyreállítási kulcsot egy biztonságos helyre, például jelszókezelőbe, titkosított jegyzetbe vagy fizikai széfbe.",
+                "set_up_recovery_save_key_title": "Mentse a helyreállítási kulcsot egy biztonságos helyre",
+                "set_up_recovery_secondary_description": "Miután rákattintott a folytatásra, létrehozunk Önnek egy helyreállítási kulcsot.",
+                "title": "Helyreállítás"
+            },
+            "title": "Titkosítás"
+        },
         "general": {
             "account_management_section": "Fiókkezelés",
             "account_section": "Fiók",
@@ -2221,32 +2585,44 @@
             "add_msisdn_dialog_title": "Telefonszám hozzáadása",
             "add_msisdn_instructions": "A szöveges üzenetet elküldtük a +%(msisdn)s számra. Kérlek add meg az ellenőrző kódot amit tartalmazott.",
             "add_msisdn_misconfigured": "Az MSISDN folyamattal történő hozzáadás / kötés hibásan van beállítva",
+            "allow_spellcheck": "Helyesírás-ellenőrzés engedélyezése",
+            "application_language": "Alkalmazás nyelve",
+            "application_language_reload_hint": "Az alkalmazás újratöltődik egy másik nyelv kiválasztása után",
+            "avatar_remove_progress": "Kép eltávolítása...",
+            "avatar_save_progress": "Kép feltöltése...",
+            "avatar_upload_error_text": "A fájlformátum nem támogatott, vagy a kép nagyobb, mint %(size)s.",
+            "avatar_upload_error_text_generic": "Előfordulhat, hogy a fájlformátum nem támogatott.",
+            "avatar_upload_error_title": "Az profilképet nem lehetett feltölteni",
             "confirm_adding_email_body": "Az e-mail-cím hozzáadásának megerősítéséhez kattintson a lenti gombra.",
             "confirm_adding_email_title": "E-mail-cím hozzáadásának megerősítése",
             "deactivate_confirm_body": "Biztos, hogy felfüggeszted a fiókodat? Ezt nem lehet visszavonni.",
             "deactivate_confirm_body_sso": "Erősítsd meg egyszeri bejelentkezéssel, hogy felfüggeszted ezt a fiókot.",
             "deactivate_confirm_content": "Erősítse meg a fiók deaktiválását. Ha folytatja:",
-            "deactivate_confirm_content_1": "A fiók többi nem aktiválható",
+            "deactivate_confirm_content_1": "Nem fogja tudni újraaktiválni a fiókját",
             "deactivate_confirm_content_2": "Nem lehet többé bejelentkezni",
             "deactivate_confirm_content_3": "Senki nem használhatja többet a felhasználónevet (matrix azonosítot), Önt is beleértve: ez a felhasználói név használhatatlan marad",
             "deactivate_confirm_content_4": "Minden szobából és közvetlen beszélgetésből kilép",
             "deactivate_confirm_content_5": "Az azonosítási kiszolgálóról törlésre kerül: a barátai többé nem találják meg az e-mail-címe vagy a telefonszáma alapján",
-            "deactivate_confirm_content_6": "Azok a régi üzenetek amiket az emberek már megkaptak továbbra is láthatóak maradnak, mint az e-mailek amiket régebben küldött. Szeretné elrejteni az üzeneteit azon emberek elől aki ez után lépnek be a szobába?",
-            "deactivate_confirm_continue": "Fiók felfüggesztésének megerősítése",
-            "deactivate_confirm_erase_label": "Üzeneteim elrejtése az újonnan csatlakozók elől",
-            "deactivate_section": "Fiók felfüggesztése",
-            "deactivate_warning": "A fiók felfüggesztése végleges — legyen óvatos!",
-            "discovery_email_empty": "Felkutatási beállítások megjelennek amint hozzáadtál egy e-mail címet alább.",
-            "discovery_email_verification_instructions": "Ellenőrizd a hivatkozást a bejövő leveleid között",
-            "discovery_msisdn_empty": "Felkutatási beállítások megjelennek amint hozzáadtál egy telefonszámot alább.",
+            "deactivate_confirm_content_6": "Azok a régi üzenetek, melyeket az emberek már megkaptak továbbra is láthatóak maradnak, mint a korábban küldött e-mailek. Szeretné elrejteni az üzeneteit azok elől, akik később lépnek be a szobába?",
+            "deactivate_confirm_continue": "Fiók deaktiválásának megerősítése",
+            "deactivate_confirm_erase_label": "Saját üzenetek elrejtése az újonnan csatlakozók elől",
+            "deactivate_section": "Fiók deaktiválása",
+            "deactivate_warning": "A fiók deaktiválása végleges – legyen óvatos!",
+            "discovery_email_empty": "A felderítési lehetőségek e-mail-cím hozzáadása után jelennek meg.",
+            "discovery_email_verification_instructions": "Ellenőrizze a hivatkozást a bejövő levelei közt",
+            "discovery_msisdn_empty": "A felderítési lehetőségek telefonszám hozzáadása után jelennek meg.",
             "discovery_needs_terms": "Azonosítási kiszolgáló (%(serverName)s) felhasználási feltételeinek elfogadása, ezáltal megtalálhatóvá lesz e-mail-cím vagy telefonszám alapján.",
+            "discovery_needs_terms_title": "Hagyja, hogy mások megtalálhassák",
+            "display_name": "Megjelenítendő név",
+            "display_name_error": "Nem sikerült beállítani a megjelenítési nevet",
+            "email_adding_unsupported_by_hs": "Ez a Matrix-kiszolgáló nem támogatja az e-mail-címek hozzáadását a fiókjához.",
             "email_address_in_use": "Ez az e-mail-cím már használatban van",
             "email_address_label": "E-mail cím",
             "email_not_verified": "Az e-mail-címe még nincs ellenőrizve",
-            "email_verification_instructions": "Ellenőrzéshez kattints a linkre az e-mailben amit kaptál és itt kattints a folytatásra újra.",
+            "email_verification_instructions": "Az ellenőrzéshez kattintson a kapott e-mailben lévő hivatkozásra, és kattintson újra a folytatásra.",
             "emails_heading": "E-mail-cím",
             "error_add_email": "Az e-mail címet nem sikerült hozzáadni",
-            "error_deactivate_communication": "A szerverrel való kommunikációval probléma történt. Kérlek próbáld újra.",
+            "error_deactivate_communication": "Hiba történt a kiszolgálóval való kommunikáció során. Próbálja újra.",
             "error_deactivate_invalid_auth": "A kiszolgáló nem küldött vissza érvényes hitelesítési információkat.",
             "error_deactivate_no_auth": "A kiszolgáló nem követelt meg semmilyen hitelesítést",
             "error_email_verification": "Az e-mail cím ellenőrzése sikertelen.",
@@ -2264,7 +2640,9 @@
             "error_share_msisdn_discovery": "A telefonszámot nem sikerült megosztani",
             "identity_server_no_token": "Nem található személyazonosság-hozzáférési kulcs",
             "identity_server_not_set": "Az azonosítási kiszolgáló nincs megadva",
-            "language_section": "Nyelv és régió",
+            "invalid_phone_number": "A megadott telefonszám nem tűnik érvényesnek.",
+            "language_section": "Nyelv",
+            "msisdn_adding_unsupported_by_hs": "Ez a Matrix-kiszolgáló nem támogatja a telefonszámok hozzáadását a fiókjához.",
             "msisdn_in_use": "Ez a telefonszám már használatban van",
             "msisdn_label": "Telefonszám",
             "msisdn_verification_field_label": "Ellenőrző kód",
@@ -2273,11 +2651,16 @@
             "oidc_manage_button": "Fiók kezelése",
             "password_change_section": "Új fiókjelszó beállítása…",
             "password_change_success": "A jelszó sikeresen megváltozott.",
+            "personal_info": "Személyes információk",
+            "profile_subtitle": "Így jelenik meg mások számára az alkalmazásban.",
+            "profile_subtitle_oidc": "Fiókját egy személyazonosság-szolgáltató külön kezeli, így a személyes adatok egy része itt nem változtatható meg.",
             "remove_email_prompt": "%(email)s törlése?",
             "remove_msisdn_prompt": "%(phone)s törlése?",
-            "spell_check_locale_placeholder": "Válasszon nyelvet"
+            "spell_check_locale_placeholder": "Válasszon nyelvet",
+            "unable_to_load_emails": "Nem sikerült betölteni az e-mail-címeket",
+            "unable_to_load_msisdns": "Nem sikerült betölteni a telefonszámokat",
+            "username": "Felhasználónév"
         },
-        "image_thumbnails": "Előnézet/bélyegkép megjelenítése a képekhez",
         "inline_url_previews_default": "Beágyazott webcím-előnézetek alapértelmezett engedélyezése",
         "inline_url_previews_room": "Webcím-előnézetek alapértelmezett engedélyezése a szobatagok számára",
         "inline_url_previews_room_account": "Webcím-előnézetek engedélyezése ebben a szobában (csak Önt érinti)",
@@ -2287,33 +2670,33 @@
             "backup_in_progress": "A kulcsaid mentése folyamatban van (az első mentés több percig is eltarthat).",
             "backup_starting": "Mentés indul…",
             "backup_success": "Sikeres!",
-            "cannot_create_backup": "Kulcs mentés sikertelen",
-            "create_title": "Kulcs mentés készítése",
+            "cannot_create_backup": "Kulcsmentés sikertelen",
+            "create_title": "Kulcsmentés készítése",
             "setup_secure_backup": {
                 "backup_setup_success_description": "A kulcsai nem kerülnek elmentésre erről az eszközről.",
                 "backup_setup_success_title": "Biztonsági mentés sikeres",
-                "cancel_warning": "Ha most megszakítod, akkor a munkameneteidhez való hozzáférés elvesztésével elveszítheted a titkosított üzeneteidet és adataidat.",
+                "cancel_warning": "Ha most megszakítja, akkor a munkameneteihez való hozzáférés elvesztésével elveszítheti a titkosított üzeneteit és adatait.",
                 "confirm_security_phrase": "Biztonsági Jelmondat megerősítése",
                 "description": "Védekezzen a titkosított üzenetekhez és adatokhoz való hozzáférés elvesztése ellen a titkosítási kulcsok kiszolgálóra történő mentésével.",
                 "download_or_copy": "%(downloadButton)s vagy %(copyButton)s",
                 "enter_phrase_description": "Olyan biztonsági jelmondatot adjon meg amit csak Ön ismer, mert ez fogja az adatait őrizni. Hogy biztonságos legyen ne használja a fiók jelszavát.",
                 "enter_phrase_title": "Biztonsági jelmondat megadása",
                 "enter_phrase_to_confirm": "A megerősítéshez adja meg a biztonsági jelmondatot még egyszer.",
-                "generate_security_key_description": "A biztonsági kulcsodat elkészül, ezt tárolja valamilyen biztonságos helyen, például egy jelszókezelőben vagy egy széfben.",
-                "generate_security_key_title": "Biztonsági kulcs előállítása",
+                "generate_security_key_description": "A helyreállítási kulcsa elő lesz állítva, ezt tárolja valamilyen biztonságos helyen, például egy jelszókezelőben vagy egy széfben.",
+                "generate_security_key_title": "Helyreállítási kulcs előállítása",
                 "pass_phrase_match_failed": "Nem egyeznek.",
                 "pass_phrase_match_success": "Egyeznek!",
                 "phrase_strong_enough": "Nagyszerű! Ez a biztonsági jelmondat elég erősnek tűnik.",
                 "secret_storage_query_failure": "A biztonsági tároló állapotát nem lehet lekérdezni",
-                "security_key_safety_reminder": "A biztonsági kulcsot tárolja biztonságos helyen, például egy jelszókezelőben vagy egy széfben, mivel ez tartja biztonságban a titkosított adatait.",
+                "security_key_safety_reminder": "A helyreállítási kulcsot tárolja biztonságos helyen, például egy jelszókezelőben vagy egy széfben, mivel ez tartja biztonságban a titkosított adatait.",
                 "set_phrase_again": "Lépj vissza és állítsd be újra.",
                 "settings_reminder": "A biztonsági mentés beállítását és a kulcsok kezelését a Beállításokban is megadhatja.",
                 "title_confirm_phrase": "Biztonsági jelmondat megerősítése",
-                "title_save_key": "Mentse el a biztonsági kulcsát",
+                "title_save_key": "Mentse el a helyreállítási kulcsát",
                 "title_set_phrase": "Biztonsági Jelmondat beállítása",
                 "unable_to_setup": "A biztonsági tárolót nem sikerült beállítani",
                 "use_different_passphrase": "Másik jelmondat használata?",
-                "use_phrase_only_you_know": "Olyan biztonsági jelmondatot használjon, amelyet csak Ön ismer, és esetleg mentsen el egy biztonsági kulcsot vésztartaléknak."
+                "use_phrase_only_you_know": "Olyan biztonsági jelmondatot használjon, amelyet csak Ön ismer, és esetleg mentsen el egy helyreállítási kulcsot vésztartalékként."
             }
         },
         "key_export_import": {
@@ -2331,12 +2714,28 @@
             "phrase_strong_enough": "Nagyszerű! Ez a jelmondat elég erősnek tűnik."
         },
         "keyboard": {
+            "dialog_title": "<strong>Beállítások:</strong> Billentyűzet",
             "title": "Billentyűzet"
         },
+        "labs": {
+            "dialog_title": "<strong>Beállítások:</strong> Labor"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Beállítások:</strong> Figyelmen kívül hagyott felhasználók"
+        },
+        "media_preview": {
+            "hide_avatars": "A szoba és a meghívó profilképének elrejtése",
+            "hide_media": "Elrejtés mindig",
+            "media_preview_description": "A rejtett médiatartalmak koppintással jeleníthetők meg",
+            "media_preview_label": "Média megjelenítése az idővonalon",
+            "show_in_private": "Privát szobákban",
+            "show_media": "Megjelenítés mindig"
+        },
         "notifications": {
             "default_setting_description": "Ez a beállítás alapértelmezés szerint az összes szobájára érvényes lesz.",
             "default_setting_section": "Szeretnék értesítést kapni az alábbiakról (Alapértelmezett beállítás)",
             "desktop_notification_message_preview": "Üzenet-előnézet megjelenítése az asztali értesítésben",
+            "dialog_title": "<strong>Beállítások:</strong> Értesítések",
             "email_description": "Összefoglaló fogadása e-mailben a nem fogadott értesítésekről",
             "email_section": "E-mail-összefoglaló",
             "email_select": "Válassza ki, hogy mely e-mail-címekre szeretne összefoglalókat küldeni. Itt kezelje az e-mail-címeit: <button>Általános</button>.",
@@ -2354,7 +2753,7 @@
             "error_title": "Az értesítések engedélyezése sikertelen",
             "error_updating": "Hiba történt az értesítési beállítások frissítése során. Próbálja meg be- és kikapcsolni a beállítást.",
             "invites": "Meghívták egy szobába",
-            "keywords": "A <badge/> jelvény megjelenítése, amikor kulcsszavakat használnak egy szobában.",
+            "keywords": "A(z) <badge/> jelvény megjelenítése, amikor kulcsszavakat használnak egy szobában.",
             "keywords_prompt": "Írja be ide a kulcsszavakat, vagy használjon eltérő betűzésű változatokat vagy beceneveket",
             "labs_notice_prompt": "<strong>Frissítés:</strong> Egyszerűsítettük az Értesítési beállításokat, hogy azok könnyebben megtalálhatók legyenek. Néhány a múltban kiválasztott egyéni beállítás itt nem jelenik meg, de továbbra is aktív. Ha folytatja, néhány beállítás megváltozhat. <a>Tudjon meg többet</a>",
             "mentions_keywords": "Említések és kulcsszavak",
@@ -2395,12 +2794,15 @@
             "code_blocks_heading": "Kódblokkok",
             "compact_modern": "Kompaktabb „Modern” elrendezés használata",
             "composer_heading": "Szerkesztő",
+            "default_timezone": "Böngésző alapértelmezése (%(timezone)s)",
+            "dialog_title": "<strong>Beállítások:</strong> Beállítások",
             "enable_hardware_acceleration": "Hardveres gyorsítás engedélyezése",
             "enable_tray_icon": "Tálcaikon megjelenítése és az ablak minimalizálása bezáráskor",
             "keyboard_heading": "Gyorsbillentyűk",
             "keyboard_view_shortcuts_button": "Az összes gyorsbillentyű megtekintéséhez <a>kattintson ide</a>.",
             "media_heading": "Képek, GIF-ek és videók",
             "presence_description": "Ossza meg a tevékenységét és állapotát másokkal.",
+            "publish_timezone": "Időzóna közzététele a nyilvános profilon",
             "rm_lifetime": "Olvasási visszajelzés érvényessége (ms)",
             "rm_lifetime_offscreen": "Olvasási visszajelzés érvényessége a képernyőn kívül (ms)",
             "room_directory_heading": "Szobalista",
@@ -2408,64 +2810,30 @@
             "show_avatars_pills": "Profilképek megjelenítése a felhasználók, szobák és események megemlítésénél",
             "show_polls_button": "Szavazások gomb megjelenítése",
             "surround_text": "Kijelölt szöveg körülvétele speciális karakterek beírásakor",
-            "time_heading": "Idő megjelenítése"
+            "time_heading": "Idő megjelenítése",
+            "user_timezone": "Időzóna beállítása"
         },
         "prompt_invite": "Kérdés a vélhetően hibás Matrix-azonosítóknak küldött meghívók elküldése előtt",
         "replace_plain_emoji": "Egyszerű szöveg automatikus cseréje emodzsira",
         "security": {
-            "4s_public_key_in_account_data": "fiókadatokban",
-            "4s_public_key_status": "Titkos tároló nyilvános kulcsa:",
             "analytics_description": "Anonim adatok megosztása, hogy segítsen nekünk azonosítani a problémákat. Semmi személyes. Nincs harmadik fél.",
-            "backup_key_cached_status": "Gyorsítótárazott mentési kulcs:",
-            "backup_key_stored_status": "Tárolt mentési kulcs:",
-            "backup_key_unexpected_type": "váratlan típus",
-            "backup_key_well_formed": "helyesen formázott",
-            "backup_keys_description": "Mentse el a titkosítási kulcsokat a fiókadatokkal arra az esetre, ha elvesztené a hozzáférést a munkameneteihez. A kulcsok egy egyedi biztonsági kulccsal lesznek védve.",
             "bulk_options_accept_all_invites": "Mind a(z) %(invitedRooms)s meghívó elfogadása",
             "bulk_options_reject_all_invites": "Mind a(z) %(invitedRooms)s meghívó elutasítása",
             "bulk_options_section": "Tömeges beállítások",
-            "cross_signing_cached": "helyben gyorsítótárazott",
-            "cross_signing_homeserver_support": "A Matrix-kiszolgáló funkciótámogatása:",
-            "cross_signing_homeserver_support_exists": "létezik",
-            "cross_signing_in_4s": "a biztonsági tárolóban",
-            "cross_signing_in_memory": "a memóriában",
-            "cross_signing_master_private_Key": "Elsődleges titkos kulcs:",
-            "cross_signing_not_cached": "nem található helyben",
-            "cross_signing_not_found": "nem találhatók",
-            "cross_signing_not_in_4s": "nem találhatók a tárolóban",
-            "cross_signing_not_stored": "nincs tárolva",
-            "cross_signing_private_keys": "Az eszközök közti hitelesítés titkos kulcsai:",
-            "cross_signing_public_keys": "Az eszközök közti hitelesítés nyilvános kulcsai:",
-            "cross_signing_self_signing_private_key": "Önaláíró titkos kulcs:",
-            "cross_signing_user_signing_private_key": "Felhasználó aláírási titkos kulcs:",
-            "cryptography_section": "Titkosítás",
-            "delete_backup": "Mentés törlése",
-            "delete_backup_confirm_description": "Biztos benne? Ha a kulcsai nincsenek megfelelően mentve, akkor elveszíti a titkosított üzeneteit.",
-            "e2ee_default_disabled_warning": "A kiszolgáló rendszergazdája alapértelmezetten kikapcsolta a végpontok közötti titkosítást a privát szobákban és a közvetlen beszélgetésekben.",
+            "dehydrated_device_description": "Az offline eszköz funkció lehetővé teszi titkosított üzenetek fogadását akkor is, ha nincs bejelentkezve egyetlen eszközre sem",
+            "dehydrated_device_enabled": "Offline eszköz engedélyezve",
+            "dialog_title": "<strong>Beállítások:</strong> Biztonság és adatvédelem",
+            "e2ee_default_disabled_warning": "A kiszolgáló rendszergazdája alapértelmezetten kikapcsolta a végpontok közti titkosítást a privát szobákban és a közvetlen beszélgetésekben.",
             "enable_message_search": "Üzenetek keresésének bekapcsolása a titkosított szobákban",
             "encryption_section": "Titkosítás",
-            "error_loading_key_backup_status": "A mentett kulcsok állapotát nem lehet betölteni",
-            "export_megolm_keys": "E2E szobakulcsok exportálása",
             "ignore_users_empty": "Nincsenek mellőzött felhasználók.",
             "ignore_users_section": "Mellőzött felhasználók",
-            "import_megolm_keys": "E2E szobakulcsok importálása",
-            "key_backup_active": "Ez a munkamenet elmenti a kulcsait.",
-            "key_backup_active_version": "Aktív biztonsági mentés verziója:",
-            "key_backup_active_version_none": "Semmi",
             "key_backup_algorithm": "Algoritmus:",
-            "key_backup_can_be_restored": "Ez a biztonsági mentés visszaállítható ebben a munkameneten",
-            "key_backup_complete": "Az összes kulcs elmentve",
             "key_backup_connect": "Munkamenet csatlakoztatása a kulcsmentéshez",
-            "key_backup_connect_prompt": "Csatlakoztassa ezt a munkamenetet a kulcsmentéshez kijelentkezés előtt, hogy ne veszítsen el olyan kulcsot, amely lehet, hogy csak ezen az eszközön van meg.",
-            "key_backup_in_progress": "%(sessionsRemaining)s kulcs biztonsági mentése…",
-            "key_backup_inactive": "Ez az munkamenet <b>nem menti el a kulcsait</b>, de van létező mentése, amelyből helyre tudja állítani, és amelyhez hozzá tudja adni a továbbiakban.",
-            "key_backup_inactive_warning": "A kulcsai <b>nem kerülnek mentésre ebből a munkamenetből</b>.",
-            "key_backup_latest_version": "A legújabb biztonsági mentés verziója a kiszolgálón:",
             "message_search_disable_warning": "Ha nincs engedélyezve akkor a titkosított szobák üzenetei nem jelennek meg a keresések között.",
             "message_search_disabled": "A titkosított üzenetek biztonságos helyi gyorsítótárazása, hogy megjelenhessenek a keresési találatok között.",
             "message_search_enabled": {
-                "one": "A titkosított üzenetek biztonságos helyi gyorsítótárazása, hogy megjelenhessenek a keresési találatok között, ehhez %(size)s helyet használ %(rooms)s szoba üzeneteihez.",
-                "other": "A titkosított üzenetek biztonságos helyi gyorsítótárazása, hogy megjelenhessenek a keresési találatok között, ehhez %(size)s helyet használ %(rooms)s szoba üzeneteihez."
+                "A titkosított üzenetek biztonságos helyi gyorsítótárazása, hogy megjelenhessenek a keresési találatok között, ehhez %(size)s helyet használ %(rooms)s szoba üzeneteihez.": "other"
             },
             "message_search_failed": "Az üzenetkeresés előkészítése sikertelen",
             "message_search_indexed_messages": "Indexált üzenetek:",
@@ -2480,14 +2848,8 @@
             "message_search_unsupported": "A titkosított üzenetek biztonságos helyi tárolásához hiányzik néhány összetevő a(z) %(brand)s alkalmazásból. Ha kísérletezni szeretne ezzel a funkcióval, akkor állítson össze egy egyéni asztali %(brand)s alkalmazást, amely <nativeLink>tartalmazza a keresési összetevőket</nativeLink>.",
             "message_search_unsupported_web": "A(z) %(brand)s nem képes helyileg biztonságosan elmenteni a titkosított üzeneteket, ha webböngészőben fut. Használja az <desktopLink>asztali %(brand)s</desktopLink> alkalmazást, hogy az üzenetekben való kereséskor a titkosított üzenetek is megjelenjenek.",
             "record_session_details": "A kliens nevének, verziójának és webcímének felvétele a munkamenetek könnyebb felismerése érdekében a munkamenet-kezelőben",
-            "restore_key_backup": "Helyreállítás mentésből",
-            "secret_storage_not_ready": "nincs kész",
-            "secret_storage_ready": "kész",
-            "secret_storage_status": "Titkos tároló:",
             "send_analytics": "Analitikai adatok küldése",
-            "session_id": "Munkamenetazonosító:",
-            "session_key": "Munkamenetkulcs:",
-            "strict_encryption": "Sose küldjön titkosított üzenetet ellenőrizetlen munkamenetekbe ebből a munkamenetből"
+            "strict_encryption": "Üzenetküldés csak ellenőrzött felhasználóknak"
         },
         "send_read_receipts": "Olvasási visszajelzés küldése",
         "send_read_receipts_unsupported": "A kiszolgálója nem támogatja az olvasási visszajelzések elküldésének kikapcsolását.",
@@ -2496,20 +2858,20 @@
             "best_security_note": "A legjobb biztonság érdekében ellenőrizze munkameneteit, és jelentkezzen ki minden olyan munkamenetből, amelyet már nem ismer fel vagy használ.",
             "browser": "Böngésző",
             "confirm_sign_out": {
-                "one": "Megerősítés ebből az eszközből való kijelentkezéshez",
-                "other": "Megerősítés ezekből az eszközökből való kijelentkezéshez"
+                "Megerősítés ebből az eszközből való kijelentkezéshez": "one",
+                "Megerősítés ezekből az eszközökből való kijelentkezéshez": "other"
             },
             "confirm_sign_out_body": {
-                "other": "Ezeknek a eszközöknek törlésének a megerősítéséhez kattintson a gombra lent.",
-                "one": "Az eszközből való kilépés megerősítéséhez kattintson a lenti gombra."
+                "Ezeknek a eszközöknek törlésének a megerősítéséhez kattintson a gombra lent.": "other",
+                "Az eszközből való kilépés megerősítéséhez kattintson a lenti gombra.": "one"
             },
             "confirm_sign_out_continue": {
-                "one": "Eszközből való kijelentkezés",
-                "other": "Eszközökből való kijelentkezés"
+                "Eszközből való kijelentkezés": "one",
+                "Eszközökből való kijelentkezés": "other"
             },
             "confirm_sign_out_sso": {
-                "one": "Az eszközből való kijelentkezéshez erősítse meg a személyazonosságát az egyszeri bejelentkezés használatával.",
-                "other": "Az eszközökből való kijelentkezéshez erősítse meg a személyazonosságát az egyszeri bejelentkezés használatával."
+                "Az eszközből való kijelentkezéshez erősítse meg a személyazonosságát az egyszeri bejelentkezés használatával.": "one",
+                "Az eszközökből való kijelentkezéshez erősítse meg a személyazonosságát az egyszeri bejelentkezés használatával.": "other"
             },
             "current_session": "Jelenlegi munkamenet",
             "desktop_session": "Asztali munkamenet",
@@ -2518,6 +2880,7 @@
             "device_unverified_description_current": "Ellenőrizze az aktuális munkamenetet a biztonságos üzenetküldéshez.",
             "device_verified_description": "Ez a munkamenet beállítva a biztonságos üzenetküldéshez.",
             "device_verified_description_current": "Az aktuális munkamenet készen áll a biztonságos üzenetküldésre.",
+            "dialog_title": "<strong>Beállítások:</strong> Munkamenetek",
             "error_pusher_state": "A leküldő állapotának beállítása sikertelen",
             "error_set_name": "Nem sikerült beállítani a munkamenet nevét",
             "filter_all": "Mind",
@@ -2534,10 +2897,10 @@
             "inactive_sessions_list_description": "Fontolja meg a kijelentkezést a régi munkamenetekből (%(inactiveAgeDays)s napnál régebbi) ha már nem használja azokat.",
             "ip": "IP cím",
             "last_activity": "Utolsó tevékenység",
+            "manage": "Munkamenet kezelése",
             "mobile_session": "Mobil munkamenet",
             "n_sessions_selected": {
-                "one": "%(count)s munkamenet kiválasztva",
-                "other": "%(count)s munkamenet kiválasztva"
+                "%(count)s munkamenet kiválasztva": "other"
             },
             "no_inactive_sessions": "Nincs inaktív munkamenet.",
             "no_sessions": "Nincs munkamenet.",
@@ -2557,18 +2920,17 @@
             "security_recommendations_description": "Javítsa a fiókja biztonságát azzal, hogy követi a következő javaslatokat.",
             "session_id": "Kapcsolat azonosító",
             "show_details": "Részletek megmutatása",
-            "sign_in_with_qr": "Belépés QR kóddal",
+            "sign_in_with_qr": "Új eszköz összekapcsolása",
             "sign_in_with_qr_button": "QR kód beolvasása",
-            "sign_in_with_qr_description": "Ennek az eszköznek a felhasználásával és a QR kóddal beléptethet egy másik eszközt. Be kell olvasni a QR kódot azon az eszközön ami még nincs belépve.",
+            "sign_in_with_qr_description": "Használjon QR-kódot egy másik eszközre való bejelentkezéshez és biztonságos üzenetküldés beállításához.",
+            "sign_in_with_qr_unsupported": "A fiókszolgáltató nem támogatja",
             "sign_out": "Kijelentkezés ebből a munkamenetből",
             "sign_out_all_other_sessions": "Kijelentkezés minden munkamenetből (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
-                "one": "Biztos, hogy ki szeretne lépni %(count)s munkamenetből?",
-                "other": "Biztos, hogy ki szeretne lépni %(count)s munkamenetből?"
+                "Biztos, hogy ki szeretne lépni %(count)s munkamenetből?": "other"
             },
             "sign_out_n_sessions": {
-                "one": "Kijelentkezés %(count)s munkamenetből",
-                "other": "Kijelentkezés %(count)s munkamenetből"
+                "Kijelentkezés %(count)s munkamenetből": "other"
             },
             "title": "Munkamenetek",
             "unknown_session": "Ismeretlen munkamenet típus",
@@ -2599,7 +2961,9 @@
         "show_redaction_placeholder": "Helykitöltő megjelenítése a törölt szövegek helyett",
         "show_stickers_button": "Matricák gomb megjelenítése",
         "show_typing_notifications": "Gépelési visszajelzés megjelenítése",
+        "showbold": "Az összes tevékenység megjelenítése a szobalistában (pontok vagy az olvasatlan üzenetek száma)",
         "sidebar": {
+            "dialog_title": "<strong>Beállítások:</strong> Oldalsáv",
             "metaspaces_favourites_description": "Csoportosítsa az összes kedvenc szobáját és ismerősét egy helyre.",
             "metaspaces_home_all_rooms": "Minden szoba megjelenítése",
             "metaspaces_home_all_rooms_description": "Minden szoba megjelenítése a Kezdőlapon, akkor is ha egy tér része.",
@@ -2608,10 +2972,14 @@
             "metaspaces_orphans_description": "Csoportosítsa egy helyre az összes olyan szobát, amely nem egy tér része.",
             "metaspaces_people_description": "Csoportosítsa az összes ismerősét egy helyre.",
             "metaspaces_subsection": "Megjelenítendő terek",
+            "metaspaces_video_rooms": "Videótermek és konferenciák",
+            "metaspaces_video_rooms_description": "Csoportosítsa az összes privát videótermet és konferenciát.",
+            "metaspaces_video_rooms_description_invite_extension": "A konferenciákra a matrixon kívüli embereket is meghívhat.",
             "spaces_explainer": "A terek a szobák és az emberek csoportosításának módjai. A terek mellett, amelyekben tartózkodik, használhat néhány előre beépítettet is.",
             "title": "Oldalsáv"
         },
         "start_automatically": "Automatikus indítás rendszerindítás után",
+        "tac_only_notifications": "Csak az üzenetszálak központban jelenítsen meg értesítéseket",
         "use_12_hour_format": "Az időbélyegek megjelenítése 12 órás formátumban (például du. 2:30)",
         "use_command_enter_send_message": "Command + Enter használata az üzenet küldéséhez",
         "use_command_f_search": "Command + F használata az idővonalon való kereséshez",
@@ -2625,6 +2993,7 @@
             "audio_output_empty": "Nem található hangkimenet",
             "auto_gain_control": "Automatikus hangerőszabályozás",
             "connection_section": "Kapcsolat",
+            "dialog_title": "<strong>Beállítások:</strong> Hang és videó",
             "echo_cancellation": "Visszhangcsillapítás",
             "enable_fallback_ice_server": "Tartalék hívásasszisztens-kiszolgáló engedélyezése (%(server)s)",
             "enable_fallback_ice_server_description": "Csak abban az esetben, ha a Matrix-kiszolgáló nem kínál fel egyet sem. Az IP-címe megosztásra kerülhet a hívás során.",
@@ -2643,8 +3012,12 @@
         "warning": "<w>FIGYELEM:</w> <description/>"
     },
     "share": {
+        "link_copied": "Hivatkozás másolva",
         "permalink_message": "Hivatkozás a kijelölt üzenethez",
         "permalink_most_recent": "Hivatkozás a legfrissebb üzenethez",
+        "share_call": "Konferencia meghívási hivatkozása",
+        "share_call_subtitle": "Hivatkozás a külső felhasználók számára, hogy Matrix-fiók nélkül csatlakozzanak a híváshoz:",
+        "title_link": "Hivatkozás megosztása",
         "title_message": "Szoba üzenetének megosztása",
         "title_room": "Szoba megosztása",
         "title_user": "Felhasználó megosztása"
@@ -2657,7 +3030,7 @@
         "addwidget_no_permissions": "Nem módosíthatja a kisalkalmazásokat ebben a szobában.",
         "ban": "Kitiltja a megadott azonosítójú felhasználót",
         "category_actions": "Műveletek",
-        "category_admin": "Rendszergazda",
+        "category_admin": "Adminisztrátor",
         "category_advanced": "Speciális",
         "category_effects": "Effektek",
         "category_messages": "Üzenetek",
@@ -2715,8 +3088,6 @@
         "topic": "Lekérdezi vagy beállítja a szoba témáját",
         "topic_none": "A szobának nincs témája.",
         "topic_room_error": "A szoba téma nem található: A szoba nem található (%(roomId)s)",
-        "tovirtual": "Átváltás a szoba virtuális szobájába, ha létezik",
-        "tovirtual_not_found": "Ehhez a szobához nincs virtuális szoba",
         "unban": "Visszaengedi a megadott azonosítójú felhasználót",
         "unflip": "Az egyszerű szöveges üzenet elé teszi ezt: ┬──┬ ノ( ゜-゜ノ)",
         "unholdcall": "Visszaveszi tartásból a jelenlegi szoba hívását",
@@ -2734,6 +3105,7 @@
         "view": "Megadott címmel rendelkező szobák megjelenítése",
         "whois": "Információt jelenít meg a felhasználóról"
     },
+    "sliding_sync_legacy_no_longer_supported": "A régi csúszóablakos szinkronizálás már nem támogatott: jelentkezzen ki, és lépjen be újra az új csúszóablakos szinkronizálás engedélyezéséhez.",
     "space": {
         "add_existing_room_space": {
             "create": "Inkább új szobát adna hozzá?",
@@ -2741,8 +3113,8 @@
             "dm_heading": "Közvetlen Beszélgetések",
             "error_heading": "Nem az összes kijelölt lett hozzáadva",
             "progress_text": {
-                "one": "Szobák hozzáadása…",
-                "other": "Szobák hozzáadása… (%(progress)s ennyiből: %(count)s)"
+                "Szobák hozzáadása…": "one",
+                "Szobák hozzáadása… (%(progress)s ennyiből: %(count)s)": "other"
             },
             "space_dropdown_label": "Tér kiválasztása",
             "space_dropdown_title": "Létező szobák hozzáadása",
@@ -2808,44 +3180,45 @@
     },
     "spotlight": {
         "public_rooms": {
-            "network_dropdown_add_dialog_description": "Add meg a felfedezni kívánt új szerver nevét.",
-            "network_dropdown_add_dialog_placeholder": "Szerver neve",
-            "network_dropdown_add_dialog_title": "Új szerver hozzáadása",
-            "network_dropdown_add_server_option": "Új szerver hozzáadása…",
-            "network_dropdown_available_invalid": "A szerver vagy a szoba listája nem található",
-            "network_dropdown_available_invalid_forbidden": "Nincs joga ennek a szervernek a szobalistáját megnézni",
+            "network_dropdown_add_dialog_description": "Adja meg a felfedezendő új kiszolgáló nevét.",
+            "network_dropdown_add_dialog_placeholder": "Kiszolgáló neve",
+            "network_dropdown_add_dialog_title": "Új kiszolgáló hozzáadása",
+            "network_dropdown_add_server_option": "Új kiszolgáló hozzáadása…",
+            "network_dropdown_available_invalid": "A kiszolgáló vagy a szobalistája nem található",
+            "network_dropdown_available_invalid_forbidden": "Nincs joga a kiszolgáló szobalistájának megtekintéséhez",
             "network_dropdown_available_valid": "Jól néz ki",
-            "network_dropdown_remove_server_adornment": "Távoli szerver „%(roomServer)s”",
-            "network_dropdown_required_invalid": "Add meg a szerver nevét",
-            "network_dropdown_selected_label": "Megjelenít: Matrix szobák",
-            "network_dropdown_selected_label_instance": "Megjelenít: %(instance)s szoba (%(server)s)",
-            "network_dropdown_your_server_description": "Matrix szervered"
+            "network_dropdown_remove_server_adornment": "Távoli kiszolgáló: „%(roomServer)s”",
+            "network_dropdown_required_invalid": "Adja meg a kiszolgáló nevét",
+            "network_dropdown_selected_label": "Megjelenítés: Matrix szobák",
+            "network_dropdown_selected_label_instance": "Megjelenítés: %(instance)s szobái (%(server)s)",
+            "network_dropdown_your_server_description": "Saját Matrix-kiszolgáló"
         }
     },
     "spotlight_dialog": {
-        "cant_find_person_helpful_hint": "Ha nem találja, akit kerese, küldje el a meghívó hivatkozást.",
-        "cant_find_room_helpful_hint": "Ha nem található a szoba amit keresett, kérjen egy meghívót vagy készítsen egy új szobát.",
+        "cant_find_person_helpful_hint": "Ha nem találja, akit keres, küldje el neki a meghívási hivatkozást.",
+        "cant_find_room_helpful_hint": "Ha nem található az szoba, amelyet keresett, kérjen egy meghívót, vagy készítsen egy új szobát.",
         "copy_link_text": "Meghívó hivatkozás másolása",
         "count_of_members": {
-            "one": "%(count)s tag",
-            "other": "%(count)s tag"
+            "%(count)s tag": "other"
         },
         "create_new_room_button": "Új szoba létrehozása",
+        "failed_querying_public_rooms": "Nem sikerült lekérdezni nyilvános szobákat",
+        "failed_querying_public_spaces": "Nem sikerült lekérdezni a nyilvános tereket",
         "group_chat_section_title": "További lehetőségek",
         "heading_with_query": "Keresés erre a kifejezésre: „%(query)s”",
         "heading_without_query": "Keresés:",
         "join_button_text": "Belépés ide: %(roomAddress)s",
         "keyboard_scroll_hint": "Görgetés ezekkel: <arrows/>",
-        "message_search_section_title": "Más keresések",
+        "messages_label": "Üzenetek",
         "other_rooms_in_space": "Más szobák itt: %(spaceName)s",
         "public_rooms_label": "Nyilvános szobák",
+        "public_spaces_label": "Nyilvános terek",
         "recent_searches_section_title": "Keresési előzmények",
         "recently_viewed_section_title": "Nemrég megtekintett",
         "remove_filter": "Keresési szűrő eltávolítása innen: %(filter)s",
         "result_may_be_hidden_privacy_warning": "Adatvédelmi okokból néhány találat rejtve lehet",
         "result_may_be_hidden_warning": "Néhány találat rejtve lehet",
         "search_dialog": "Keresési párbeszédablak",
-        "search_messages_hint": "Az üzenetek kereséséhez keresse ezt az ikont a szoba tetején: <icon/>",
         "spaces_title": "Terek, amelynek tagja",
         "start_group_chat_button": "Csoportos csevegés indítása"
     },
@@ -2879,15 +3252,22 @@
         "all_threads": "Minden üzenetszál",
         "all_threads_description": "A szobában lévő összes üzenetszál megjelenítése",
         "count_of_reply": {
-            "one": "%(count)s válasz",
-            "other": "%(count)s válasz"
+            "%(count)s válasz": "other"
         },
+        "empty_description": "Amikor az egérmutatót egy üzenet fölé viszi használja ezt: „%(replyInThread)s ”.",
+        "empty_title": "Az üzenetszálak segítenek a különböző témájú beszélgetések figyelemmel kísérésében.",
         "error_start_thread_existing_relation": "Nem lehet üzenetszálat indítani olyan eseményről ami már rendelkezik kapcsolattal",
+        "mark_all_read": "Az összes megjelölése olvasottként",
         "my_threads": "Saját üzenetszálak",
-        "my_threads_description": "Minden üzenetszál megjelenítése, amelyben részt vesz",
+        "my_threads_description": "Összes olyan üzenetszál megjelenítése, amelyben részt vesz",
         "open_thread": "Üzenetszál megnyitása",
         "show_thread_filter": "Megjelenítés:"
     },
+    "threads_activity_centre": {
+        "header": "Üzenetszál-tevékenységek",
+        "no_rooms_with_threads_notifs": "Még nincsenek olyan szobái, amelyek üzenetszál értesítéseket tartalmaznak.",
+        "no_rooms_with_unread_threads": "Nincsenek még olvasatlan üzenetszálakkal rendelkező szobái."
+    },
     "time": {
         "about_day_ago": "egy napja",
         "about_hour_ago": "egy órája",
@@ -2919,7 +3299,7 @@
     "timeline": {
         "context_menu": {
             "collapse_reply_thread": "Üzenetszál összecsukása",
-            "external_url": "Forrás URL",
+            "external_url": "Forráswebcím",
             "open_in_osm": "Megnyitás az OpenStreetMapen",
             "report": "Jelentés",
             "resent_unsent_reactions": "%(unsentCount)s reakció újraküldése",
@@ -2929,9 +3309,21 @@
         },
         "creation_summary_dm": "%(creator)s hozta létre ezt az üzenetet.",
         "creation_summary_room": "%(creator)s elkészítette és beállította a szobát.",
+        "decryption_failure": {
+            "blocked": "A feladó megakadályozta, hogy megkapja ezt az üzenetet",
+            "historical_event_no_key_backup": "A korábbi üzenetek nem állnak rendelkezésre ezen a készüléken",
+            "historical_event_unverified_device": "Ellenőriznie kell ezt az eszközt a korábbi üzenetekhez való hozzáféréshez",
+            "historical_event_user_not_joined": "Nincs hozzáférése ehhez az üzenethez",
+            "sender_identity_previously_verified": "A feladó ellenőrzött személyazonossága megváltozott",
+            "sender_unsigned_device": "Nem biztonságos eszközről küldve.",
+            "unable_to_decrypt": "Nem sikerült visszafejteni az üzenetet"
+        },
         "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Visszafejtés",
         "download_action_downloading": "Letöltés",
+        "download_failed": "Letöltés sikertelen",
+        "download_failed_description": "Hiba történt a fájl letöltése közben",
+        "e2e_state": "A végpontok közti titkosítás állapota",
         "edits": {
             "tooltip_label": "Szerkesztés ideje: %(date)s. Kattintson a szerkesztések megtekintéséhez.",
             "tooltip_sub": "A szerkesztések megtekintéséhez kattints",
@@ -2942,6 +3334,7 @@
         "historical_messages_unavailable": "Nem tekintheted meg a régebbi üzeneteket",
         "in_room_name": " itt: <strong>%(room)s</strong>",
         "io.element.widgets.layout": "%(senderName)s frissítette a szoba kinézetét",
+        "late_event_separator": "Eredetileg elküldve: %(dateTime)s",
         "load_error": {
             "no_permission": "Megpróbálta betölteni a szoba megadott időpontjának megfelelő adatait, de nincs joga a kérdéses üzenetek megjelenítéséhez.",
             "title": "Az idővonal pozíciót nem sikerült betölteni",
@@ -2984,7 +3377,7 @@
         },
         "m.file": {
             "error_decrypting": "Csatolmány visszafejtése sikertelen",
-            "error_invalid": "Hibás fájl%(extra)s"
+            "error_invalid": "Hibás fájl"
         },
         "m.image": {
             "error": "Kép megjelenítése egy hiba miatt nem lehetséges",
@@ -3004,8 +3397,7 @@
         },
         "m.poll": {
             "count_of_votes": {
-                "one": "%(count)s szavazat",
-                "other": "%(count)s szavazat"
+                "%(count)s szavazat": "other"
             }
         },
         "m.poll.end": {
@@ -3021,12 +3413,12 @@
         },
         "m.room.canonical_alias": {
             "alt_added": {
-                "other": "%(senderName)s hozzáadta a szoba alternatív címeit: %(addresses)s.",
-                "one": "%(senderName)s alternatív címeket adott hozzá a szobához: %(addresses)s."
+                "%(senderName)s hozzáadta a szoba alternatív címeit: %(addresses)s.": "other",
+                "%(senderName)s alternatív címeket adott hozzá a szobához: %(addresses)s.": "one"
             },
             "alt_removed": {
-                "other": "%(senderName)s eltávolította az alternatív címeket a szobáról: %(addresses)s.",
-                "one": "%(senderName)s eltávolította az alternatív címet a szobáról: %(addresses)s."
+                "%(senderName)s eltávolította az alternatív címeket a szobáról: %(addresses)s.": "other",
+                "%(senderName)s eltávolította az alternatív címet a szobáról: %(addresses)s.": "one"
             },
             "changed": "%(senderName)s megváltoztatta a szoba címeit.",
             "changed_alternative": "%(senderName)s megváltoztatta a szoba alternatív címeit.",
@@ -3043,7 +3435,7 @@
         "m.room.encryption": {
             "disable_attempt": "A titkosítás kikapcsolására tett kísérlet figyelmen kívül lett hagyva",
             "disabled": "Titkosítás nincs engedélyezve",
-            "enabled": "Az ebben a szobában lévő üzenetek végpontok közötti titkosítással rendelkeznek. Amikor valaki csatlakozik, akkor ellenőrizheti őket a profiljukban, csak koppintson a profilképükre.",
+            "enabled": "Az ebben a szobában lévő üzenetek végpontok közti titkosítással rendelkeznek. Amikor valaki csatlakozik, akkor ellenőrizheti a profilján, csak koppintson a profilképére.",
             "enabled_dm": "Az itt található üzenetek végpontok közti titkosítással rendelkeznek. Ellenőrizze %(displayName)s felhasználót a profilján – kattintson a a profilképére.",
             "enabled_local": "Az üzenetek ebben a beszélgetésben végponti titkosítással vannak védve.",
             "parameters_changed": "Néhány titkosítási paraméter megváltozott.",
@@ -3085,6 +3477,7 @@
             "left_reason": "%(targetName)s elhagyta a szobát, ok: %(reason)s",
             "no_change": "%(senderName)s nem változtatott semmit",
             "reject_invite": "%(targetName)s elutasította a meghívót",
+            "reject_invite_reason": "%(targetName)s elutasította a meghívást: %(reason)s",
             "remove_avatar": "%(senderName)s törölte a profilképét",
             "remove_name": "%(senderName)s törölte a megjelenítendő nevét (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s profilképet állított be",
@@ -3120,15 +3513,19 @@
             "sent": "%(senderName)s meghívót küldött %(targetDisplayName)s számára, hogy lépjen be a szobába."
         },
         "m.room.tombstone": "%(senderDisplayName)s fejlesztette a szobát.",
-        "m.room.topic": "%(senderDisplayName)s a következőre változtatta a témát: „%(topic)s”.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s a következőre változtatta a témát: „%(topic)s”.",
+            "removed": "%(senderDisplayName)s eltávolította a témát."
+        },
         "m.sticker": "%(senderDisplayName)s matricát küldött.",
         "m.video": {
-            "error_decrypting": "Hiba a videó visszafejtésénél"
+            "error_decrypting": "Hiba a videó visszafejtésénél",
+            "show_video": "Videó megjelenítése"
         },
         "m.widget": {
             "added": "%(senderName)s hozzáadta a %(widgetName)s kisalkalmazást",
             "jitsi_ended": "A videókonferenciát befejezte: %(senderName)s",
-            "jitsi_join_right_prompt": "Csatlakozz a konferenciához a jobb oldali szoba információs panel segítségével",
+            "jitsi_join_right_prompt": "Csatlakozzon a konferenciához a jobb oldali szobainformációs panel segítségével",
             "jitsi_join_top_prompt": "Csatlakozz a konferenciához a szoba tetején",
             "jitsi_started": "A videókonferenciát elindította: %(senderName)s",
             "jitsi_updated": "A videókonferenciát frissítette: %(senderName)s",
@@ -3139,9 +3536,11 @@
             "collapse_reply_chain": "Idézetek összecsukása",
             "copy_link_thread": "Hivatkozás másolása az üzenetszálba",
             "expand_reply_chain": "Idézetek megjelenítése",
-            "label": "Üzenet Műveletek",
+            "label": "Üzenetműveletek",
             "view_in_room": "Megjelenítés szobában"
         },
+        "message_timestamp_received_at": "Érkezett: %(dateTime)s",
+        "message_timestamp_sent_at": "Elküldve:%(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s megváltoztatta a kitiltó szabályt erről: %(oldGlob)s, erre: %(newGlob)s, ok: %(reason)s",
             "changed_rule_rooms": "%(senderName)s megváltoztatta a szobákat kitiltó szabályt erről: %(oldGlob)s, erre: %(newGlob)s, ok: %(reason)s",
@@ -3164,16 +3563,16 @@
         },
         "no_permission_messages_before_invite": "A meghívás előtti üzenetek megtekintéséhez nincs engedélye.",
         "no_permission_messages_before_join": "A belépés előtti üzenetek megtekintése nincs engedélyezve számodra.",
-        "pending_moderation": "Üzenet moderálásra vár",
+        "pending_moderation": "Az üzenet moderálásra vár",
         "pending_moderation_reason": "Az üzenet moderálásra vár, ok: %(reason)s",
         "reactions": {
             "add_reaction_prompt": "Reakció hozzáadása",
             "custom_reaction_fallback_label": "Egyéni reakció",
-            "label": "%(reactors)s reagált: %(content)s"
+            "label": "%(reactors)s reagált: %(content)s",
+            "tooltip_caption": "ezzel reagált: %(shortName)s"
         },
         "read_receipt_title": {
-            "one": "%(count)s ember látta",
-            "other": "%(count)s ember látta"
+            "%(count)s ember látta": "other"
         },
         "read_receipts_label": "Olvasási visszajelzés",
         "redacted": {
@@ -3186,7 +3585,7 @@
             "in_reply_to_for_export": "Válasz erre az <a>üzenetre</a>"
         },
         "scalar_starter_link": {
-            "dialog_description": "Azonosítás céljából egy harmadik félhez leszel irányítva (%(integrationsUrl)s). Folytatod?",
+            "dialog_description": "Hitelesítés céljából egy harmadik félhez lesz irányítva (%(integrationsUrl)s). Folytatja?",
             "dialog_title": "Integráció hozzáadása"
         },
         "self_redaction": "Üzenet törölve",
@@ -3196,148 +3595,148 @@
         "send_state_sent": "Üzenet elküldve",
         "summary": {
             "banned": {
-                "other": "%(count)s alkalommal lett kitiltva",
-                "one": "ki lett tiltva"
+                "%(count)s alkalommal lett kitiltva": "other",
+                "ki lett tiltva": "one"
             },
             "banned_multiple": {
-                "other": "%(count)s alkalommal lett kitiltva",
-                "one": "lett kitiltva"
+                "%(count)s alkalommal lett kitiltva": "other",
+                "lett kitiltva": "one"
             },
             "changed_avatar": {
-                "one": "%(oneUser)s megváltoztatta a profilképét",
-                "other": "%(oneUser)s %(count)s alkalommal megváltoztatta a profilképét"
+                "%(oneUser)s megváltoztatta a profilképét": "one",
+                "%(oneUser)s %(count)s alkalommal megváltoztatta a profilképét": "other"
             },
             "changed_avatar_multiple": {
-                "one": "%(severalUsers)s megváltoztatta a profilképét",
-                "other": "%(severalUsers)s %(count)s alkalommal megváltoztatta a profilképét"
+                "%(severalUsers)s megváltoztatta a profilképét": "one",
+                "%(severalUsers)s %(count)s alkalommal megváltoztatta a profilképét": "other"
             },
             "changed_name": {
-                "other": "%(oneUser)s %(count)s alkalommal megváltoztatta a nevét",
-                "one": "%(oneUser)s megváltoztatta a nevét"
+                "%(oneUser)s %(count)s alkalommal megváltoztatta a nevét": "other",
+                "%(oneUser)s megváltoztatta a nevét": "one"
             },
             "changed_name_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal megváltoztatta a nevét",
-                "one": "%(severalUsers)s megváltoztatta a nevét"
+                "%(severalUsers)s %(count)s alkalommal megváltoztatta a nevét": "other",
+                "%(severalUsers)s megváltoztatta a nevét": "one"
             },
             "format": "%(nameList)s %(transitionList)s",
             "hidden_event": {
-                "one": "%(oneUser)s rejtett üzenetet küldött",
-                "other": "%(oneUser)s %(count)s rejtett üzenetet küldött"
+                "%(oneUser)s rejtett üzenetet küldött": "one",
+                "%(oneUser)s %(count)s rejtett üzenetet küldött": "other"
             },
             "hidden_event_multiple": {
-                "one": "%(severalUsers)s rejtett üzenetet küldött",
-                "other": "%(severalUsers)s %(count)s rejtett üzenetet küldött"
+                "%(severalUsers)s rejtett üzenetet küldött": "one",
+                "%(severalUsers)s %(count)s rejtett üzenetet küldött": "other"
             },
             "invite_withdrawn": {
-                "other": "%(oneUser)s meghívóit %(count)s alkalommal vonták vissza",
-                "one": "%(oneUser)s meghívóit visszavonták"
+                "%(oneUser)s meghívóit %(count)s alkalommal vonták vissza": "other",
+                "%(oneUser)s meghívóit visszavonták": "one"
             },
             "invite_withdrawn_multiple": {
-                "other": "%(severalUsers)s meghívóit %(count)s alkalommal visszavonták",
-                "one": "%(severalUsers)s visszavonták a meghívásukat"
+                "%(severalUsers)s meghívóit %(count)s alkalommal visszavonták": "other",
+                "%(severalUsers)s visszavonták a meghívásukat": "one"
             },
             "invited": {
-                "other": "%(count)s alkalommal lett meghívva",
-                "one": "meg lett hívva"
+                "%(count)s alkalommal lett meghívva": "other",
+                "meg lett hívva": "one"
             },
             "invited_multiple": {
-                "other": "%(count)s alkalommal lett meghívva",
-                "one": "meg lett hívva"
+                "%(count)s alkalommal lett meghívva": "other",
+                "meg lett hívva": "one"
             },
             "joined": {
-                "other": "%(oneUser)s %(count)s alkalommal csatlakozott",
-                "one": "%(oneUser)s csatlakozott"
+                "%(oneUser)s %(count)s alkalommal csatlakozott": "other",
+                "%(oneUser)s csatlakozott": "one"
             },
             "joined_and_left": {
-                "other": "%(oneUser)s %(count)s alkalommal csatlakozott és távozott",
-                "one": "%(oneUser)s csatlakozott és távozott"
+                "%(oneUser)s %(count)s alkalommal csatlakozott és távozott": "other",
+                "%(oneUser)s csatlakozott és távozott": "one"
             },
             "joined_and_left_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal csatlakozott és távozott",
-                "one": "%(severalUsers)s csatlakozott és távozott"
+                "%(severalUsers)s %(count)s alkalommal csatlakozott és távozott": "other",
+                "%(severalUsers)s csatlakozott és távozott": "one"
             },
             "joined_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal csatlakozott",
-                "one": "%(severalUsers)s csatlakozott"
+                "%(severalUsers)s %(count)s alkalommal csatlakozott": "other",
+                "%(severalUsers)s csatlakozott": "one"
             },
             "kicked": {
-                "one": "eltávolítva",
-                "other": "%(count)s alkalommal lett eltávolítva"
+                "eltávolítva": "one",
+                "%(count)s alkalommal lett eltávolítva": "other"
             },
             "kicked_multiple": {
-                "one": "eltávolítva",
-                "other": "%(count)s alkalommal lett eltávolítva"
+                "eltávolítva": "one",
+                "%(count)s alkalommal lett eltávolítva": "other"
             },
             "left": {
-                "other": "%(oneUser)s %(count)s alkalommal távozott",
-                "one": "%(oneUser)s távozott"
+                "%(oneUser)s %(count)s alkalommal távozott": "other",
+                "%(oneUser)s távozott": "one"
             },
             "left_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal távozott",
-                "one": "%(severalUsers)s távozott"
+                "%(severalUsers)s %(count)s alkalommal távozott": "other",
+                "%(severalUsers)s távozott": "one"
             },
             "no_change": {
-                "other": "%(oneUser)s %(count)s alkalommal nem változtatott semmit",
-                "one": "%(oneUser)snem változtatott semmit"
+                "%(oneUser)s %(count)s alkalommal nem változtatott semmit": "other",
+                "%(oneUser)snem változtatott semmit": "one"
             },
             "no_change_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal nem változtattak semmit",
-                "one": "%(severalUsers)s nem változtattak semmit"
+                "%(severalUsers)s %(count)s alkalommal nem változtattak semmit": "other",
+                "%(severalUsers)s nem változtattak semmit": "one"
             },
             "pinned_events": {
-                "one": "%(oneUser)s módosította a szoba <a>kitűzött üzeneteit</a>",
-                "other": "%(oneUser)s %(count)s alkalommal módosította a szoba <a>kitűzött üzeneteit</a>"
+                "%(oneUser)s módosította a szoba <a>kitűzött üzeneteit</a>": "one",
+                "%(oneUser)s %(count)s alkalommal módosította a szoba <a>kitűzött üzeneteit</a>": "other"
             },
             "pinned_events_multiple": {
-                "one": "%(severalUsers)s módosította a szoba <a>kitűzött üzeneteit</a>",
-                "other": "%(severalUsers)s %(count)s alkalommal módosította a szoba <a>kitűzött üzeneteit</a>"
+                "%(severalUsers)s módosította a szoba <a>kitűzött üzeneteit</a>": "one",
+                "%(severalUsers)s %(count)s alkalommal módosította a szoba <a>kitűzött üzeneteit</a>": "other"
             },
             "redacted": {
-                "one": "%(oneUser)s üzenetet törölt",
-                "other": "%(oneUser)s %(count)s üzenetet törölt"
+                "%(oneUser)s üzenetet törölt": "one",
+                "%(oneUser)s %(count)s üzenetet törölt": "other"
             },
             "redacted_multiple": {
-                "one": "%(severalUsers)s üzenetet törölt",
-                "other": "%(severalUsers)s %(count)s üzenetet törölt"
+                "%(severalUsers)s üzenetet törölt": "one",
+                "%(severalUsers)s %(count)s üzenetet törölt": "other"
             },
             "rejected_invite": {
-                "other": "%(oneUser)s %(count)s alkalommal elutasította a meghívóit",
-                "one": "%(oneUser)s elutasította a meghívóit"
+                "%(oneUser)s %(count)s alkalommal elutasította a meghívóit": "other",
+                "%(oneUser)s elutasította a meghívóit": "one"
             },
             "rejected_invite_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal elutasította a meghívóit",
-                "one": "%(severalUsers)s elutasította a meghívóit"
+                "%(severalUsers)s %(count)s alkalommal elutasította a meghívóit": "other",
+                "%(severalUsers)s elutasította a meghívóit": "one"
             },
             "rejoined": {
-                "other": "%(oneUser)s %(count)s alkalommal távozott és újra csatlakozott",
-                "one": "%(oneUser)s távozott és újra csatlakozott"
+                "%(oneUser)s %(count)s alkalommal távozott és újra csatlakozott": "other",
+                "%(oneUser)s távozott és újra csatlakozott": "one"
             },
             "rejoined_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal távozott és újra csatlakozott",
-                "one": "%(severalUsers)s távozott és újra csatlakozott"
+                "%(severalUsers)s %(count)s alkalommal távozott és újra csatlakozott": "other",
+                "%(severalUsers)s távozott és újra csatlakozott": "one"
             },
             "server_acls": {
-                "one": "%(oneUser)smegváltoztatta a szerver ACL-eket",
-                "other": "%(oneUser)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-t"
+                "%(oneUser)s megváltoztatta a kiszolgáló ACL-jeit": "one",
+                "%(oneUser)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-jeit": "other"
             },
             "server_acls_multiple": {
-                "other": "%(severalUsers)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-t",
-                "one": "%(severalUsers)smegváltoztatta a szerver ACL-eket"
+                "%(severalUsers)s megváltoztatta a kiszolgáló ACL-jeit": "one",
+                "%(severalUsers)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-jeit": "other"
             },
             "unbanned": {
-                "other": "%(count)s alkalommal lett visszaengedve",
-                "one": "vissza lett engedve"
+                "%(count)s alkalommal lett visszaengedve": "other",
+                "vissza lett engedve": "one"
             },
             "unbanned_multiple": {
-                "other": "%(count)s alkalommal lett visszaengedve",
-                "one": "vissza lett engedve"
+                "%(count)s alkalommal lett visszaengedve": "other",
+                "vissza lett engedve": "one"
             }
         },
         "thread_info_basic": "Az üzenetszálból",
         "typing_indicator": {
             "more_users": {
-                "other": "%(names)s és még %(count)s felhasználó gépel…",
-                "one": "%(names)s és még valaki gépel…"
+                "%(names)s és még %(count)s felhasználó gépel…": "other",
+                "%(names)s és még valaki gépel…": "one"
             },
             "one_user": "%(displayName)s gépel…",
             "two_users": "%(names)s és %(lastPerson)s gépelnek…"
@@ -3346,13 +3745,16 @@
         "url_preview": {
             "close": "Előnézet bezárása",
             "show_n_more": {
-                "one": "%(count)s további előnézet megjelenítése",
-                "other": "%(count)s további előnézet megjelenítése"
+                "%(count)s további előnézet megjelenítése": "other"
             }
         }
     },
     "truncated_list_n_more": {
-        "other": "És még %(count)s..."
+        "És még %(count)s...": "other"
+    },
+    "unsupported_browser": {
+        "description": "Ha folytatja, előfordulhat, hogy egyes funkciók nem működnek, és fennáll annak a kockázata, hogy a jövőben elveszítheti adatait. Frissítse böngészőjét ennek a használatához: %(brand)s.",
+        "title": "%(brand)s nem támogatja ezt a böngészőt"
     },
     "unsupported_server_description": "Ez a kiszolgáló a Matrix régebbi verzióját használja. Frissítsen a Matrix %(version)s verzióra az %(brand)s hibamentes használatához.",
     "unsupported_server_title": "A kiszolgálója nem támogatott",
@@ -3368,9 +3770,16 @@
         "release_notes_toast_title": "Újdonságok",
         "see_changes_button": "Mik az újdonságok?",
         "toast_description": "Új %(brand)s verzió érhető el",
-        "toast_title": "A(z) %(brand)s frissítése",
+        "toast_title": "Az %(brand)s frissítése",
         "unavailable": "Elérhetetlen"
     },
+    "update_room_access_modal": {
+        "description": "Megosztási hivatkozás létrehozásához tegye <b>nyilvánossá</b> ezt a szobát, vagy engedélyezze azt a lehetőséget, hogy a felhasználók csatlakozást <b>kérhessenek</b>. Ez lehetővé teszi a vendégek számára, hogy meghívás nélkül csatlakozzanak.",
+        "dont_change_description": "Ha nem szeretné módosítani a szoba hozzáférését, létrehozhat egy új szobát a híváshoz.",
+        "no_change": "Nem akarom megváltoztatni a hozzáférési szintet.",
+        "revert_access_description": "(Ez visszaállítható az előző értékre a Szoba beállításaiban: <b>Biztonság és adatvédelem </b> / <b>Hozzáférés</b>)",
+        "title": "Vendégfelhasználók csatlakozásának engedélyezése ehhez a szobához"
+    },
     "upload_failed_generic": "A(z) „%(fileName)s” fájl feltöltése sikertelen.",
     "upload_failed_size": "A(z) „%(fileName)s” mérete túllépi a Matrix-kiszolgáló által megengedett korlátot",
     "upload_failed_title": "Feltöltés sikertelen",
@@ -3380,48 +3789,39 @@
         "error_files_too_large": "A fájl <b>túl nagy</b> a feltöltéshez. A fájlméret korlátja %(limit)s.",
         "error_some_files_too_large": "Néhány fájl <b>túl nagy</b>, hogy fel lehessen tölteni. A fájlméret korlátja %(limit)s.",
         "error_title": "Feltöltési hiba",
+        "not_image": "A kiválasztott fájl nem érvényes képfájl.",
         "title": "Fájlok feltöltése",
         "title_progress": "Fájlok feltöltése (%(current)s / %(total)s)",
         "upload_all_button": "Összes feltöltése",
         "upload_n_others_button": {
-            "other": "%(count)s másik fájlt feltöltése",
-            "one": "%(count)s másik fájl feltöltése"
+            "%(count)s másik fájlt feltöltése": "other",
+            "%(count)s másik fájl feltöltése": "one"
         }
     },
     "user_info": {
-        "admin_tools_section": "Admin. Eszközök",
+        "admin_tools_section": "Adminisztrációs eszközök",
         "ban_button_room": "Kitiltás a szobából",
         "ban_button_space": "Kitiltás a térről",
         "ban_room_confirm_title": "Kitiltás innen: %(roomName)s",
         "ban_space_everything": "Kitiltani őket mindenhonnan ahonnan joga van hozzá",
         "ban_space_specific": "Kitiltani őket bizonyos helyekről ahonnan joga van hozzá",
-        "count_of_sessions": {
-            "other": "%(count)s munkamenet",
-            "one": "%(count)s munkamenet"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s ellenőrzött munkamenet",
-            "one": "1 ellenőrzött munkamenet"
-        },
         "deactivate_confirm_action": "Felhasználó felfüggesztése",
         "deactivate_confirm_description": "A felhasználó deaktiválása a felhasználót kijelentkezteti és megakadályozza, hogy vissza tudjon lépni. Továbbá kilépteti minden szobából, amelynek tagja volt. Ezt nem lehet visszavonni. Biztos, hogy deaktiválja ezt a felhasználót?",
         "deactivate_confirm_title": "Felhasználó felfüggesztése?",
         "demote_button": "Lefokozás",
         "demote_self_confirm_description_space": "Nem fogja tudni visszavonni ezt a változtatást, mert lefokozza magát, ha Ön az utolsó privilegizált felhasználó a térben, akkor lehetetlen lesz a jogosultságok visszanyerése.",
-        "demote_self_confirm_room": "Ahogy lefokozod magad a változás visszafordíthatatlan, ha te vagy az utolsó jogosultságokkal bíró felhasználó a szobában a jogok már nem szerezhetők vissza.",
-        "demote_self_confirm_title": "Lefokozod magad?",
+        "demote_self_confirm_room": "Nem fogja tudni visszavonni ezt a változtatást, mert lefokozza magát, ha Ön az utolsó privilegizált felhasználó a térben, akkor lehetetlen lesz a jogosultságok visszanyerése.",
+        "demote_self_confirm_title": "Lefokozza magát?",
         "disinvite_button_room": "Meghívó visszavonása a szobából",
         "disinvite_button_room_name": "Meghívó visszavonása innen: %(roomName)s",
         "disinvite_button_space": "Meghívó visszavonása a térről",
-        "edit_own_devices": "Eszközök szerkesztése",
-        "error_ban_user": "A felhasználót nem sikerült kizárni",
-        "error_deactivate": "A felhasználó felfüggesztése nem sikerült",
-        "error_kicking_user": "A felhasználó eltávolítása nem sikerült",
-        "error_mute_user": "A felhasználót némítása sikertelen",
-        "error_revoke_3pid_invite_description": "A meghívót nem lehet visszavonni. Vagy a szervernek átmenetileg problémái vannak vagy nincs megfelelő jogosultságod a meghívó visszavonásához.",
+        "error_ban_user": "A felhasználó kizárása sikertelen",
+        "error_deactivate": "A felhasználó felfüggesztése sikertelen",
+        "error_kicking_user": "A felhasználó eltávolítása sikertelen",
+        "error_mute_user": "A felhasználó némítása sikertelen",
+        "error_revoke_3pid_invite_description": "A meghívót nem lehet visszavonni. A kiszolágónak átmeneti problémái vannak, vagy nincs megfelelő jogosultsága a meghívó visszavonásához.",
         "error_revoke_3pid_invite_title": "A meghívó visszavonása sikertelen",
-        "hide_sessions": "Munkamenetek elrejtése",
-        "hide_verified_sessions": "Ellenőrzött munkamenetek eltakarása",
+        "ignore_button": "Mellőzés",
         "ignore_confirm_description": "Minden üzenet és meghívó ettől a felhasználótól rejtve marad. Biztos, hogy figyelmen kívül hagyja?",
         "ignore_confirm_title": "%(user)s figyelmen kívül hagyása",
         "invited_by": "Meghívta: %(sender)s",
@@ -3431,42 +3831,45 @@
         "kick_button_space": "Eltávolítás a térről",
         "kick_button_space_everything": "Eltávolításuk mindenhonnan ahonnan csak lehet",
         "kick_space_specific": "Eltávolításuk bizonyos helyekről ahonnan lehet",
-        "kick_space_warning": "Továbbra is hozzáférhetnek olyan helyekhez ahol ön nem adminisztrátor.",
-        "promote_warning": "Nem leszel képes visszavonni ezt a változtatást mivel a felhasználót ugyanarra a szintre emeled amin te vagy.",
+        "kick_space_warning": "Továbbra is hozzáférhetnek olyan helyekhez, ahol Ön nem adminisztrátor.",
+        "promote_warning": "Nem fogja tudni visszavonni ezt a változtatást, mert a felhasználót a sajátjával azonos szintre emeli.",
         "redact": {
             "confirm_button": {
-                "other": "%(count)s db üzenet törlése",
-                "one": "1 üzenet törlése"
+                "%(count)s db üzenet törlése": "other",
+                "1 üzenet törlése": "one"
             },
             "confirm_description_1": {
-                "one": "%(count)s üzenetet készül törölni az alábbi felhasználótól: %(user)s. A művelet mindenki számára visszavonhatatlanul eltávolítja ezeket a beszélgetésekből. Biztos, hogy folytatja?",
-                "other": "%(count)s üzenetet készül törölni az alábbi felhasználótól: %(user)s. A művelet mindenki számára visszavonhatatlanul eltávolítja ezeket a beszélgetésekből. Biztos, hogy folytatja?"
+                "%(count)s üzenetet készül törölni az alábbi felhasználótól: %(user)s. A művelet mindenki számára visszavonhatatlanul eltávolítja ezeket a beszélgetésekből. Biztos, hogy folytatja?": "other"
             },
-            "confirm_description_2": "Ez időt vehet igénybe ha sok üzenet érintett. Kérlek közben ne frissíts a kliensben.",
-            "confirm_keep_state_explainer": "Törölje a kijelölést ha a rendszer üzeneteket is törölni szeretné ettől a felhasználótól (pl. tagság változás, profil változás…)",
+            "confirm_description_2": "Ez sok üzenet esetén eltarthat egy darabig. Közben ne frissítse a klienst.",
+            "confirm_keep_state_explainer": "Törölje a kijelölést ha a rendszerüzeneteket is törölni szeretné ettől a felhasználótól (például tagságváltozás, profilváltozás…)",
             "confirm_keep_state_label": "Rendszerüzenetek megtartása",
             "confirm_title": "Friss üzenetek törlése a felhasználótól: %(user)s",
-            "no_recent_messages_description": "Az idővonalon próbálj meg felgörgetni, hogy megnézd van-e régebbi üzenet.",
+            "no_recent_messages_description": "Próbáljon meg felfelé görgetni az idővonalon, hogy megnézze, van-e régebbi üzenet.",
             "no_recent_messages_title": "Nincs friss üzenet ettől a felhasználótól: %(user)s"
         },
-        "redact_button": "Friss üzenetek törlése",
+        "redact_button": "Üzenetek eltávolítása",
         "revoke_invite": "Meghívó visszavonása",
         "room_encrypted": "Az üzenetek a szobában végponttól végpontig titkosítottak.",
         "room_encrypted_detail": "Az üzenete biztonságban van, és csak Ön és a címzettek rendelkeznek a visszafejtéshez szükséges egyedi kulcsokkal.",
-        "room_unencrypted": "Az üzenetek a szobában nincsenek végponttól végpontig titkosítva.",
+        "room_unencrypted": "A szobában lévő üzenetek nincsenek végponttól végpontig titkosítva.",
         "room_unencrypted_detail": "A titkosított szobákban az üzenete biztonságban van, és csak Ön és a címzettek rendelkeznek a visszafejtéshez szükséges egyedi kulcsokkal.",
-        "share_button": "A felhasználóra mutató hivatkozás",
+        "send_message": "Üzenet küldése",
+        "share_button": "Profil megosztása",
         "unban_button_room": "Visszaengedés a szobába",
         "unban_button_space": "Visszaengedés a térre",
         "unban_room_confirm_title": "Kitiltás visszavonása innen: %(roomName)s",
         "unban_space_everything": "Kitiltásuk visszavonása mindenhonnan ahol joga van hozzá",
         "unban_space_specific": "Kitiltásuk visszavonása bizonyos helyekről ahol joga van hozzá",
-        "unban_space_warning": "Később nem férhetnek hozzá olyan helyekhez ahol ön nem adminisztrátor.",
+        "unban_space_warning": "Később nem férhetnek hozzá olyan helyekhez, ahol Ön nem adminisztrátor.",
+        "unignore_button": "Mellőzés feloldása",
+        "verification_unavailable": "A felhasználó-ellenőrzés nem érhető el",
         "verify_button": "Felhasználó ellenőrzése",
         "verify_explainer": "A biztonság fokozásáért ellenőrizd ezt a felhasználót egy egyszeri kód egyeztetésével mindkettőtök készülékén."
     },
     "user_menu": {
-        "settings": "Minden beállítás",
+        "link_new_device": "Új eszköz összekapcsolása",
+        "settings": "Összes beállítás",
         "switch_theme_dark": "Sötét módra váltás",
         "switch_theme_light": "Világos módra váltás"
     },
@@ -3489,6 +3892,7 @@
         "camera_disabled": "A kamerája ki van kapcsolva",
         "camera_enabled": "A kamerája még mindig be van kapcsolva",
         "cannot_call_yourself_description": "Nem hívhatja fel saját magát.",
+        "close_lobby": "Várószoba bezárása",
         "connecting": "Kapcsolódás",
         "connection_lost": "Megszakadt a kapcsolat a kiszolgálóval",
         "connection_lost_description": "Nem kezdeményezhet hívást a kiszolgálóval való kapcsolat nélkül.",
@@ -3498,21 +3902,29 @@
         "dialpad": "Tárcsázó",
         "disable_camera": "Kamera kikapcsolása",
         "disable_microphone": "Mikrofon némítása",
-        "disabled_no_one_here": "Itt nincs senki akit fel lehetne hívni",
-        "disabled_no_perms_start_video_call": "Nincs jogosultságod videó hívást indítani",
-        "disabled_no_perms_start_voice_call": "Nincs jogosultságod hang hívást indítani",
+        "disabled_no_one_here": "Itt nincs senki, akit fel lehetne hívni",
+        "disabled_no_perms_start_video_call": "Nincs jogosultsága videóhívást indítani",
+        "disabled_no_perms_start_voice_call": "Nincs jogosultsága hanghívást indítani",
         "disabled_ongoing_call": "Hívás folyamatban",
+        "element_call": "Element Call",
         "enable_camera": "Kamera bekapcsolása",
         "enable_microphone": "Mikrofon némításának feloldása",
         "expand": "Visszatérés a híváshoz",
+        "get_call_link": "Hívási hivatkozás megosztása",
         "hangup": "Bontás",
         "hide_sidebar_button": "Oldalsáv elrejtése",
         "input_devices": "Beviteli eszközök",
+        "jitsi_call": "Jitsi konferencia",
         "join_button_tooltip_call_full": "Bocsánat — ez a hívás betelt",
-        "join_button_tooltip_connecting": "Kapcsolódás",
+        "legacy_call": "Örökölt hívás",
         "maximise": "Képernyő kitöltése",
+        "maximise_call": "Maximalizálja a hívást",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferenciák"
+        },
+        "minimise_call": "Minimalizálja a hívást",
         "misconfigured_server": "A hívás a helytelenül beállított kiszolgáló miatt sikertelen",
-        "misconfigured_server_description": "Kérje meg a Matrix-kiszolgáló (<code>%(homeserverDomain)s</code>) rendszergazdáját, hogy a hívások megfelelő működéséhez állítson be egy TURN-kiszolgálót.",
+        "misconfigured_server_description": "Kérje meg a Matrix-kiszolgáló (<code>%(homeserverDomain)s</code>) adminisztrátorát, hogy a hívások megfelelő működéséhez állítson be egy TURN-kiszolgálót.",
         "misconfigured_server_fallback": "Alternatív megoldásként megpróbálhatja használni a <server/> nyilvános kiszolgálót, de ez nem lesz olyan megbízható, és megosztja az IP-címét azzal a kiszolgálóval. Ezt a Beállításokban is kezelheti.",
         "misconfigured_server_fallback_accept": "A %(server)s használatának kipróbálása",
         "more_button": "Több",
@@ -3520,8 +3932,7 @@
         "msisdn_lookup_failed_description": "Hiba történt a telefonszám megkeresése során",
         "msisdn_transfer_failed": "A hívás átadása nem lehetséges",
         "n_people_joined": {
-            "one": "%(count)s személy belépett",
-            "other": "%(count)s személy belépett"
+            "%(count)s személy belépett": "other"
         },
         "no_audio_input_description": "Nem található mikrofon. Ellenőrizze a beállításokat és próbálja újra.",
         "no_audio_input_title": "Nem található mikrofon",
@@ -3558,6 +3969,7 @@
         "user_is_presenting": "%(sharerName)s tartja a bemutatót",
         "video_call": "Videóhívás",
         "video_call_started": "A videóhívás elindult",
+        "video_call_using": "Videóhívás:",
         "voice_call": "Hanghívás",
         "you_are_presenting": "Ön tartja a bemutatót"
     },
@@ -3645,7 +4057,7 @@
             "remove": "Visszavonás mindenkitől",
             "revoke": "Jogosultságok visszavonása",
             "screenshot": "Fénykép készítése",
-            "start_audio_stream": "Hang folyam indítása"
+            "start_audio_stream": "Hangközvetítés indítása"
         },
         "cookie_warning": "Ez a kisalkalmazás sütiket használhat.",
         "error_hangup_description": "A híváskapcsolat megszakadt (Hiba: %(message)s)",
@@ -3655,9 +4067,9 @@
         "error_need_invite_permission": "Hogy ezt tegye, ahhoz meg kell tudnia hívni felhasználókat.",
         "error_need_kick_permission": "Hogy ezt tegye, ahhoz ki kell tudnia rúgni felhasználókat.",
         "error_need_to_be_logged_in": "Be kell jelentkeznie.",
-        "error_unable_start_audio_stream_description": "A hang folyam indítása sikertelen.",
+        "error_unable_start_audio_stream_description": "A hangközvetítés indítása sikertelen.",
         "error_unable_start_audio_stream_title": "Az élő adás indítása sikertelen",
-        "modal_data_warning": "Az ezen a képernyőn látható adatok megosztásra kerülnek ezzel: %(widgetDomain)s",
+        "modal_data_warning": "Az alábbi adatok a következővel vannak megosztva: %(widgetDomain)s",
         "modal_title_default": "Előugró kisalkalmazás",
         "no_name": "Ismeretlen alkalmazás",
         "open_id_permissions_dialog": {
@@ -3666,7 +4078,7 @@
             "title": "A kisalkalmazás ellenőrizheti a személyazonosságát"
         },
         "popout": "Kiugró kisalkalmazás",
-        "set_room_layout": "A szoba megjelenésének beállítása mindenki számára",
+        "set_room_layout": "Elrendezés beállítása mindenki számára",
         "shared_data_avatar": "A profilképének webcíme",
         "shared_data_device_id": "Saját eszközazonosító",
         "shared_data_lang": "Saját nyelv",
@@ -3692,6 +4104,7 @@
             "l33t": "A kiszámítható helyettesítések, mint az „a” helyett a „@”, nem sokat segítenek",
             "longerKeyboardPattern": "Használj hosszabb billentyűzetmintát, több kanyarral",
             "noNeed": "Nincs szükség szimbólumokra, számokra vagy nagybetűkre",
+            "pwned": "Ha máshol is használja ezt a jelszót, változtassa meg.",
             "recentYears": "Kerülje a közeli éveket",
             "repeated": "Kerülje a szó-, vagy betűismétlést",
             "reverseWords": "A fordított betűrendet sem sokkal nehezebb kitalálni",
@@ -3705,6 +4118,7 @@
             "extendedRepeat": "Az „abcabcabc” sorozatot csak kicsivel nehezebb kitalálni mint az „abc”-t",
             "keyPattern": "A rövid billentyűzetmintákat könnyű kitalálni",
             "namesByThemselves": "Neveket egymagukban könnyű kitalálni",
+            "pwned": "A jelszavát egy internetes adatlopás miatt felfedték.",
             "recentYears": "A közelmúlt évszámait könnyű kitalálni",
             "sequences": "Az olyan sorozatokat mint az abc vagy 6543, könnyű kitalálni",
             "similarToCommon": "Ez nagyon hasonlít egy gyakori jelszóhoz",
@@ -3712,6 +4126,7 @@
             "straightRow": "A billentyűsorokat könnyű kitalálni",
             "topHundred": "Ez benne van a 100 legelterjedtebb jelszó listájában",
             "topTen": "Ez benne van a 10 legelterjedtebb jelszó listájában",
+            "userInputs": "Nem lehetnek személyes vagy oldalakkal kapcsolatos adatok.",
             "wordByItself": "Egy szót magában könnyű kitalálni"
         }
     }
diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json
index baa2e5abb25846ba1d6a0b9280d58749a7aed212..4df0b7707ba1f5f95b1690d38d01978640e5f188 100644
--- a/src/i18n/strings/id.json
+++ b/src/i18n/strings/id.json
@@ -1,6 +1,8 @@
 {
     "a11y": {
+        "emoji_picker": "Pemilih emoji",
         "jump_first_invite": "Pergi ke undangan pertama.",
+        "message_composer": "Komposer pesan",
         "n_unread_messages": {
             "one": "1 pesan yang belum dibaca.",
             "other": "%(count)s pesan yang belum dibaca."
@@ -9,7 +11,18 @@
             "one": "1 sebutan yang belum dibaca.",
             "other": "%(count)s pesan yang belum dibaca termasuk sebutan."
         },
+        "recent_rooms": "Ruangan terkini",
+        "room_messsage_not_sent": "Buka ruangan %(roomName)s dengan pesan yang belum diatur.",
+        "room_n_unread_invite": "Buka undangan ruangan %(roomName)s.",
+        "room_n_unread_messages": {
+            "other": "Buka ruangan %(roomName)s dengan %(count)s pesan yang belum dibaca."
+        },
+        "room_n_unread_messages_mentions": {
+            "other": "Buka ruangan %(roomName)s dengan %(count)s pesan yang belum dibaca termasuk sebutan."
+        },
         "room_name": "Ruangan %(name)s",
+        "room_status_bar": "Bilah status ruangan",
+        "seek_bar_label": "Bilah pencarian audio",
         "unread_messages": "Pesan yang belum dibaca.",
         "user_menu": "Menu pengguna"
     },
@@ -40,6 +53,8 @@
         "create_a_room": "Buat sebuah ruangan",
         "create_account": "Buat Akun",
         "decline": "Tolak",
+        "decline_and_block": "Tolak dan blokir",
+        "decline_invite": "Tolak undangan",
         "delete": "Hapus",
         "deny": "Tolak",
         "disable": "Nonaktifkan",
@@ -59,6 +74,7 @@
         "go": "Mulai",
         "go_back": "Kembali ke sebelumnya",
         "got_it": "Mengerti",
+        "hide": "Sembunyikan",
         "hide_advanced": "Sembunyikan lanjutan",
         "hold": "Jeda",
         "ignore": "Abaikan",
@@ -75,12 +91,14 @@
         "maximise": "Maksimalkan",
         "mention": "Sebutkan",
         "minimise": "Minimalkan",
+        "new_message": "Pesan baru",
         "new_room": "Ruangan baru",
         "new_video_room": "Ruangan video baru",
         "next": "Lanjut",
         "no": "Tidak",
         "ok": "Oke",
         "open": "Buka",
+        "open_menu": "Buka menu",
         "pause": "Jeda",
         "pin": "Sematkan",
         "play": "Mainkan",
@@ -89,13 +107,13 @@
         "react": "Bereaksi",
         "refresh": "Muat Ulang",
         "register": "Daftar",
-        "reject": "Tolak",
         "reload": "Muat ulang",
         "remove": "Hapus",
         "rename": "Ubah Nama",
         "reply": "Balas",
         "reply_in_thread": "Balas di utasan",
         "report_content": "Laporkan Konten",
+        "report_room": "Laporkan ruangan",
         "resend": "Kirim Ulang",
         "reset": "Atur Ulang",
         "resume": "Lanjutkan",
@@ -105,6 +123,7 @@
         "save": "Simpan",
         "search": "Cari",
         "send_report": "Kirimkan laporan",
+        "set_avatar": "Atur foto profil",
         "share": "Bagikan",
         "show": "Tampilkan",
         "show_advanced": "Tampilkan lanjutan",
@@ -128,6 +147,7 @@
         "update": "Perbarui",
         "upgrade": "Tingkatkan",
         "upload": "Unggah",
+        "upload_file": "Unggah berkas",
         "verify": "Lakukan verifikasi",
         "view": "Pratinjau",
         "view_all": "Tampilkan semua",
@@ -135,6 +155,7 @@
         "view_message": "Tampilkan pesan",
         "view_source": "Tampilkan Sumber",
         "yes": "Ya",
+        "yes_dismiss": "Ya, abaikan",
         "zoom_in": "Perbesar",
         "zoom_out": "Perkecil"
     },
@@ -222,6 +243,7 @@
         },
         "misconfigured_body": "Tanyakan admin %(brand)s Anda untuk memeriksa <a>konfigurasi Anda</a> untuk entri yang tidak benar atau entri duplikat.",
         "misconfigured_title": "%(brand)s Anda telah diatur dengan salah",
+        "mobile_create_account_title": "Anda akan membuat akun di %(hsName)s",
         "msisdn_field_description": "Pengguna lain dapat mengundang Anda ke ruangan menggunakan detail kontak Anda",
         "msisdn_field_label": "Ponsel",
         "msisdn_field_number_invalid": "Nomor teleponnya tidak terlihat benar, mohon periksa dan coba lagi",
@@ -229,6 +251,7 @@
         "no_hs_url_provided": "Tidak ada URL homeserver yang disediakan",
         "oidc": {
             "error_title": "Kami tidak dapat memasukkan Anda",
+            "generic_auth_error": "Terjadi kesalahan saat autentikasi. Buka laman masuk dan coba lagi.",
             "missing_or_invalid_stored_state": "Kami menanyakan browser ini untuk mengingat homeserver apa yang Anda gunakan untuk membantu Anda masuk, tetapi sayangnya browser ini melupakannya. Pergi ke halaman masuk dan coba lagi."
         },
         "password_field_keep_going_prompt": "Lanjutkan…",
@@ -238,11 +261,40 @@
         "phone_label": "Ponsel",
         "phone_optional_label": "Nomor telepon (opsional)",
         "qr_code_login": {
+            "check_code_explainer": "Ini akan memverifikasi bahwa koneksi ke perangkat Anda yang lain aman.",
+            "check_code_heading": "Masukkan nomor yang ditampilkan di perangkat Anda yang lain",
+            "check_code_input_label": "Kode 2 digit",
+            "check_code_mismatch": "Angkanya tidak cocok",
             "completing_setup": "Menyelesaikan penyiapan perangkat baru Anda",
-            "error_unexpected": "Sebuah kesalahan terjadi secara tidak terduga.",
-            "scan_code_instruction": "Pindai kode QR di bawah dengan perangkat Anda yang sudah keluar dari akun.",
-            "scan_qr_code": "Pindai kode QR",
-            "select_qr_code": "Pilih '%(scanQRCode)s'",
+            "error_etag_missing": "Terjadi kesalahan yang tidak terduga. Hal ini mungkin disebabkan oleh pengaya peramban, server proksi atau kesalahan konfigurasi server.",
+            "error_expired": "Masa masuk sudah habis. Silakan coba lagi.",
+            "error_expired_title": "Proses masuk tidak selesai tepat waktu",
+            "error_insecure_channel_detected": "Koneksi aman tidak dapat dibuat ke perangkat baru. Perangkat Anda yang ada masih aman dan Anda tidak perlu khawatir tentang mereka.",
+            "error_insecure_channel_detected_instructions": "Sekarang apa?",
+            "error_insecure_channel_detected_instructions_1": "Coba masuk ke perangkat lain lagi dengan kode QR jika ini adalah masalah jaringan",
+            "error_insecure_channel_detected_instructions_2": "Jika Anda mengalami masalah yang sama, coba jaringan Wi-Fi yang berbeda atau gunakan data seluler Anda daripada Wi-Fi",
+            "error_insecure_channel_detected_instructions_3": "Jika tidak berhasil, masuk secara manual",
+            "error_insecure_channel_detected_title": "Koneksi tidak aman",
+            "error_other_device_already_signed_in": "Anda tidak perlu melakukan hal lain.",
+            "error_other_device_already_signed_in_title": "Perangkat Anda yang lain sudah masuk",
+            "error_rate_limited": "Terlalu banyak percobaan dalam waktu yang singkat. Tunggu beberapa waktu sebelum mencoba lagi.",
+            "error_unexpected": "Terjadi kesalahan yang tidak terduga. Permintaan untuk menghubungkan perangkat Anda yang lain telah dibatalkan.",
+            "error_unsupported_protocol": "Perangkat ini tidak mendukung masuk ke perangkat lain dengan kode QR.",
+            "error_unsupported_protocol_title": "Perangkat lain tidak kompatibel",
+            "error_user_cancelled": "Proses masuk dibatalkan di perangkat lain.",
+            "error_user_cancelled_title": "Permintaan masuk dibatalkan",
+            "error_user_declined": "Anda menolak permintaan dari perangkat Anda yang lain untuk masuk.",
+            "error_user_declined_title": "Masuk ditolak",
+            "follow_remaining_instructions": "Ikuti petunjuk selanjutnya untuk memverifikasi perangkat Anda yang lain",
+            "open_element_other_device": "Buka %(brand)s di perangkat Anda yang lain",
+            "point_the_camera": "Arahkan kamera ke kode QR yang ditampilkan di sini",
+            "scan_code_instruction": "Pindai kode QR dengan perangkat lain",
+            "scan_qr_code": "Masuk dengan kode QR",
+            "security_code": "Kode keamanan",
+            "security_code_prompt": "Jika diminta, masukkan kode di bawah ini pada perangkat Anda yang lain.",
+            "select_qr_code": "Pilih \"%(scanQRCode)s\"",
+            "unsupported_explainer": "Penyedia akun Anda tidak mendukung masuk ke perangkat baru dengan kode QR.",
+            "unsupported_heading": "Kode QR tidak didukung",
             "waiting_for_device": "Menunggu perangkat untuk masuk"
         },
         "register_action": "Buat Akun",
@@ -331,6 +383,9 @@
             "email_resend_prompt": "Belum menerima? <a>Kirim ulang</a>",
             "email_resent": "Dikirimkan ulang!",
             "fallback_button": "Mulai autentikasi",
+            "mas_cross_signing_reset_cta": "Buka akun Anda",
+            "mas_cross_signing_reset_description": "Atur ulang identitas Anda melalui penyedia akun Anda, kemudian kembali dan klik “Coba lagi”.",
+            "mas_cross_signing_reset_title": "Buka akun Anda untuk mengatur ulang identitas Anda",
             "msisdn": "Sebuah pesan teks telah dikirim ke %(msisdn)s",
             "msisdn_token_incorrect": "Token salah",
             "msisdn_token_prompt": "Silakan masukkan kode yang berisi:",
@@ -365,7 +420,15 @@
         "download_logs": "Unduh catatan",
         "downloading_logs": "Mengunduh catatan",
         "error_empty": "Mohon beri tahu kami apa saja yang salah atau, lebih baik, buat sebuah issue GitHub yang menjelaskan masalahnya.",
-        "failed_send_logs": "Gagal untuk mengirimkan catatan: ",
+        "failed_download_logs": "Gagal mengunduh log pengawakutuan: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Laporan pengawakutuan Anda ditolak. Server rageshake tidak mendukung aplikasi ini.",
+            "rejected_generic": "Laporan bug Anda ditolak. Server rageshake menolak isi laporan karena suatu kebijakan.",
+            "rejected_recovery_key": "Laporan pengawakutuan Anda ditolak karena alasan keamanan, karena berisi kunci pemulihan.",
+            "rejected_version": "Laporan pengawakutuan Anda ditolak karena versi yang Anda jalankan terlalu lawas.",
+            "server_unknown_error": "Server rageshake mengalami kesalahan yang tidak diketahui dan tidak dapat menangani laporan.",
+            "unknown_error": "Gagal mengirim log."
+        },
         "github_issue": "Masalah GitHub",
         "introduction": "Jika Anda mengirim sebuah kutu via GitHub, catatan pengawakutu dapat membantu kami melacak masalahnya. ",
         "log_request": "Untuk membantu kami mencegahnya di masa mendatang, silakan <a>kirimkan kami catatan</a>.",
@@ -405,7 +468,7 @@
         "access_token": "Token Akses",
         "accessibility": "Aksesibilitas",
         "advanced": "Tingkat Lanjut",
-        "all_rooms": "Semua ruangan",
+        "all_chats": "Semua Obrolan",
         "analytics": "Analitik",
         "and_n_others": {
             "one": "dan satu lainnya...",
@@ -420,10 +483,10 @@
         "beta": "Beta",
         "camera": "Kamera",
         "cameras": "Kamera",
+        "cancel": "Batalkan",
         "capabilities": "Kemampuan",
         "copied": "Disalin!",
         "credits": "Kredit",
-        "cross_signing": "Penandatanganan silang",
         "dark": "Gelap",
         "description": "Deskripsi",
         "deselect_all": "Batalkan semua pilihan",
@@ -459,8 +522,10 @@
         "matrix": "Matrix",
         "message": "Pesan",
         "message_layout": "Tata letak pesan",
+        "message_timestamp_invalid": "Stempel waktu tidak valid",
         "microphone": "Mikrofon",
         "model": "Model",
+        "moderation_and_safety": "Moderasi dan keamanan",
         "modern": "Modern",
         "mute": "Bisukan",
         "n_members": {
@@ -496,13 +561,15 @@
         "qr_code": "Kode QR",
         "random": "Sembarangan",
         "reactions": "Reaksi",
+        "recommended": "Disarankan",
         "report_a_bug": "Laporkan sebuah bug",
         "room": "Ruangan",
         "room_name": "Nama ruangan",
         "rooms": "Ruangan",
+        "save": "Simpan",
+        "saved": "Disimpan",
         "saving": "Menyimpan…",
         "secure_backup": "Cadangan Aman",
-        "security": "Keamanan",
         "select_all": "Pilih semua",
         "server": "Server",
         "settings": "Pengaturan",
@@ -521,13 +588,13 @@
         "thread": "Utasan",
         "threads": "Utasan",
         "timeline": "Lini Masa",
-        "trusted": "Dipercayai",
         "unavailable": "tidak tersedia",
         "unencrypted": "Tidak Dienkripsi",
         "unmute": "Suarakan",
         "unnamed_room": "Ruangan Tanpa Nama",
         "unnamed_space": "Space Tidak Dinamai",
         "unverified": "Belum diverifikasi",
+        "updating": "Memperbarui...",
         "user": "Pengguna",
         "user_avatar": "Gambar profil",
         "username": "Nama Pengguna",
@@ -583,6 +650,7 @@
         "placeholder_reply_encrypted": "Kirim sebuah balasan terenkripsi…",
         "placeholder_thread": "Balas ke utasan…",
         "placeholder_thread_encrypted": "Balas ke utasan yang terenkripsi…",
+        "poll_button": "Pemungutan suara",
         "poll_button_no_perms_description": "Anda tidak memiliki izin untuk memulai sebuah poll di ruangan ini.",
         "poll_button_no_perms_title": "Izin Dibutuhkan",
         "replying_title": "Membalas",
@@ -655,6 +723,7 @@
         "private_space_description": "Sebuah space pribadi untuk Anda dan tim Anda",
         "public_description": "Space terbuka untuk siapa saja, baik untuk komunitas",
         "public_heading": "Space publik Anda",
+        "search_public_button": "Cari ruangan publik",
         "setup_rooms_community_description": "Mari kita buat ruangan untuk masing-masing.",
         "setup_rooms_community_heading": "Apa saja yang Anda ingin bahas di %(spaceName)s?",
         "setup_rooms_description": "Anda juga dapat menambahkan lebih banyak nanti, termasuk yang sudah ada.",
@@ -678,12 +747,58 @@
         "twemoji": "Gambar emoji <twemoji>Twemoji</twemoji> © <author>Twitter, Inc dan kontributor lainnya</author> digunakan di bawah ketentuan <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "Fon <colr>twemoji-colr</colr> © <author>Mozilla Foundation</author> digunakan di bawah ketentuan <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Apakah Anda yakin ingin menolak undangan untuk bergabung dengan \"%(roomName)s\"?",
+        "ignore_user_help": "Anda tidak akan melihat pesan atau undangan ruangan dari pengguna ini.",
+        "reason_description": "Jelaskan alasan untuk melaporkan ruangan.",
+        "report_room_description": "Laporkan ruangan ini ke penyedia akun Anda.",
+        "title": "Tolak undangan"
+    },
+    "desktop_default_device_name": "%(brand)s Desktop: %(platformName)s",
     "devtools": {
         "active_widgets": "Widget Aktif",
         "category_other": "Lainnya",
         "category_room": "Ruangan",
         "caution_colon": "Peringatan:",
         "client_versions": "Versi Klien",
+        "crypto": {
+            "4s_public_key_in_account_data": "dalam data akun",
+            "4s_public_key_not_in_account_data": "tidak ditemukan",
+            "4s_public_key_status": "Kunci publik penyimpanan rahasia:",
+            "backup_key_cached": "dalam tembolok secara lokal",
+            "backup_key_cached_status": "Kunci cadangan dalam tembolok:",
+            "backup_key_not_stored": "tidak disimpan",
+            "backup_key_stored": "dalam penyimpanan rahasia",
+            "backup_key_stored_status": "Kunci cadangan disimpan:",
+            "backup_key_unexpected_type": "jenis tidak terduga",
+            "backup_key_well_formed": "terbentuk dengan baik",
+            "cross_signing": "Penandatanganan silang",
+            "cross_signing_cached": "dalam tembolok secara lokal",
+            "cross_signing_not_ready": "Penandatanganan silang belum disiapkan.",
+            "cross_signing_private_keys_in_storage": "dalam penyimpanan rahasia",
+            "cross_signing_private_keys_in_storage_status": "Kunci pribadi penandatanganan silang:",
+            "cross_signing_private_keys_not_in_storage": "tidak ditemukan dalam penyimpanan",
+            "cross_signing_public_keys_on_device": "dalam memori",
+            "cross_signing_public_keys_on_device_status": "Kunci publik penandatanganan silang:",
+            "cross_signing_ready": "Penandatanganan silang siap digunakan.",
+            "cross_signing_status": "Status penandatanganan silang:",
+            "cross_signing_untrusted": "Akun Anda memiliki identitas penandatanganan silang dalam penyimpanan rahasia, tetapi belum dipercaya oleh sesi ini.",
+            "crypto_not_available": "Modul kriptografi tidak tersedia",
+            "key_backup_active_version": "Versi cadangan aktif:",
+            "key_backup_active_version_none": "Tidak ada",
+            "key_backup_inactive_warning": "Kunci Anda tidak dicadangkan dari sesi ini.",
+            "key_backup_latest_version": "Versi cadangan terbaru di server:",
+            "key_storage": "Penyimpanan Kunci",
+            "master_private_key_cached_status": "Kunci pribadi utama:",
+            "not_found": "tidak ditemukan",
+            "not_found_locally": "tidak ditemukan secara lokal",
+            "secret_storage_not_ready": "belum siap",
+            "secret_storage_ready": "siap",
+            "secret_storage_status": "Penyimpanan rahasia:",
+            "self_signing_private_key_cached_status": "Kunci pribadi penandatanganan sendiri:",
+            "title": "Enkripsi ujung ke ujung",
+            "user_signing_private_key_cached_status": "Kunci pribadi penandatanganan pengguna:"
+        },
         "developer_mode": "Mode pengembang",
         "developer_tools": "Alat Pengembang",
         "edit_setting": "Edit pengaturan",
@@ -738,6 +853,9 @@
         "setting_colon": "Pengaturan:",
         "setting_definition": "Definisi pengaturan:",
         "setting_id": "ID Pengaturan",
+        "settings": {
+            "elementCallUrl": "URL Element Call"
+        },
         "settings_explorer": "Penelusur pengaturan",
         "show_hidden_events": "Tampilkan peristiwa tersembunyi di lini masa",
         "spaces": {
@@ -790,52 +908,37 @@
     "empty_room_was_name": "Ruangan kosong (sebelumnya %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Masukkan Frasa Keamanan Anda atau <button>gunakan Kunci Keamanan Anda</button> untuk melanjutkan.",
+            "alternatives": "Jika Anda memiliki kunci keamanan atau frasa keamanan, ini juga bisa digunakan.",
             "key_validation_text": {
-                "invalid_security_key": "Kunci Keamanan tidak absah",
-                "recovery_key_is_correct": "Kelihatannya bagus!",
-                "wrong_file_type": "Tipe file salah",
-                "wrong_security_key": "Kunci Keamanan salah"
-            },
-            "reset_title": "Atur ulang semuanya",
-            "reset_warning_1": "Hanya lakukan ini jika Anda tidak memiliki perangkat yang lain untuk menyelesaikan verifikasi.",
-            "reset_warning_2": "Jika Anda mengatur ulang semuanya, Anda dengan mulai ulang dengan tidak ada sesi yang dipercayai, tidak ada pengguna yang dipercayai, dan mungkin tidak dapat melihat pesan-pesan lama.",
+                "wrong_security_key": "Kunci pemulihan yang Anda masukkan salah."
+            },
+            "privacy_warning": "Pastikan tidak ada yang bisa melihat layar ini!",
             "restoring": "Memulihkan kunci-kunci dari cadangan",
-            "security_key_title": "Kunci Keamanan",
-            "security_phrase_incorrect_error": "Tidak dapat mengakses penyimpanan rahasia. Periksa jika Anda memasukkan Frasa Keamanan yang benar.",
-            "security_phrase_title": "Frasa Keamanan",
-            "separator": "%(securityKey)s atau %(recoveryFile)s",
-            "use_security_key_prompt": "Gunakan Kunci Keamanan Anda untuk melanjutkan."
+            "security_key_title": "Kunci pemulihan"
         },
         "bootstrap_title": "Menyiapkan kunci",
         "cancel_entering_passphrase_description": "Apakah Anda yakin untuk membatalkan pemasukkan frasa sandi?",
         "cancel_entering_passphrase_title": "Batalkan memasukkan frasa sandi?",
         "confirm_encryption_setup_body": "Klik tombol di bawah untuk mengkonfirmasi menyiapkan enkripsi.",
         "confirm_encryption_setup_title": "Konfirmasi pengaturan enkripsi",
-        "cross_signing_not_ready": "Penandatanganan silang belum disiapkan.",
-        "cross_signing_ready": "Penandatanganan silang siap digunakan.",
-        "cross_signing_ready_no_backup": "Penandatanganan silang telah siap tetapi kunci belum dicadangkan.",
         "cross_signing_room_normal": "Ruangan ini dienkripsi secara ujung ke ujung",
         "cross_signing_room_verified": "Semuanya di ruangan ini telah terverifikasi",
         "cross_signing_room_warning": "Seseorang menggunakan sesi yang tidak dikenal",
-        "cross_signing_unsupported": "Homeserver Anda tidak mendukung penandatanganan silang.",
-        "cross_signing_untrusted": "Akun Anda mempunyai identitas penandatanganan silang di penyimpanan rahasia, tetapi belum dipercayai oleh sesi ini.",
         "cross_signing_user_normal": "Anda belum memverifikasi pengguna ini.",
         "cross_signing_user_verified": "Anda telah memverifikasi pengguna ini. Pengguna ini telah memverifikasi semua sesinya.",
         "cross_signing_user_warning": "Pengguna ini belum memverifikasi semua sesinya.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Hapus kunci-kunci penandatanganan silang",
-            "title": "Hancurkan kunci-kunci penandatanganan silang?",
-            "warning": "Menghapus kunci penandatanganan silang itu permanen. Siapa saja yang Anda verifikasi akan melihat peringatan keamanan. Anda hampir pasti tidak ingin melakukan ini, kecuali jika Anda kehilangan setiap perangkat yang dapat digunakan untuk melakukan penandatanganan silang."
-        },
+        "enter_recovery_key": "Masukkan kunci pemulihan",
         "event_shield_reason_authenticity_not_guaranteed": "Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini.",
         "event_shield_reason_mismatched_sender_key": "Terenkripsi oleh sesi yang belum diverifikasi",
         "event_shield_reason_unknown_device": "Dienkripsi oleh perangkat yang tidak dikenal atau dihapus.",
         "event_shield_reason_unsigned_device": "Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya.",
         "event_shield_reason_unverified_identity": "Dienkripsi oleh pengguna yang tidak diverifikasi.",
         "export_unsupported": "Browser Anda tidak mendukung ekstensi kriptografi yang dibutuhkan",
+        "forgot_recovery_key": "Lupa kunci pemulihan?",
         "import_invalid_keyfile": "Bukan keyfile %(brand)s yang absah",
         "import_invalid_passphrase": "Pemeriksaan autentikasi gagal: kata sandi salah?",
+        "key_storage_out_of_sync": "Penyimpanan kunci Anda tidak tersinkron.",
+        "key_storage_out_of_sync_description": "Konfirmasikan kunci pemulihan Anda untuk mempertahankan akses ke penyimpanan kunci dan riwayat pesan Anda.",
         "messages_not_secure": {
             "cause_1": "Homeserver Anda",
             "cause_2": "Homeserver pengguna yang Anda memverifikasi",
@@ -850,7 +953,8 @@
             "title": "Metode Pemulihan Baru",
             "warning": "Jika Anda tidak menyetel metode pemulihan yang baru, sebuah penyerang mungkin mencoba mengakses akun Anda. Ubah kata sandi akun Anda dan segera tetapkan metode pemulihan yang baru di Pengaturan."
         },
-        "not_supported": "<tidak didukung>",
+        "pinned_identity_changed": "Identitas (<b>%(userId)s</b>) %(displayName)s tampaknya telah berubah. <a>Pelajari lebih lanjut</a>",
+        "pinned_identity_changed_no_displayname": "Identitas <b>%(userId)s</b> tampaknya telah berubah. <a>Pelajari lebih lanjut</a>",
         "recovery_method_removed": {
             "description_1": "Sesi ini telah mendeteksi bahwa Frasa Keamanan dan kunci untuk Pesan Aman Anda telah dihapus.",
             "description_2": "Jika Anda melakukan ini secara tidak sengaja, Anda dapat mengatur Pesan Aman pada sesi ini yang akan mengenkripsi ulang riwayat pesan sesi ini dengan metode pemulihan baru.",
@@ -858,12 +962,16 @@
             "warning": "Jika Anda tidak menghapus metode pemulihan, sebuah penyerang mungkin mencoba mengakses akun Anda. Ubah kata sandi akun Anda dan segera tetapkan metode pemulihan baru di Pengaturan."
         },
         "reset_all_button": "Lupa atau kehilangan semua metode pemulihan? <a>Atur ulang semuanya</a>",
+        "set_up_recovery": "Siapkan pemulihan",
+        "set_up_recovery_later": "Tidak sekarang",
+        "set_up_recovery_toast_description": "Buat kunci pemulihan yang dapat digunakan untuk memulihkan riwayat pesan terenkripsi jika Anda kehilangan akses ke perangkat Anda.",
         "set_up_toast_description": "Lindungi dari kehilangan akses ke pesan & data terenkripsi",
         "set_up_toast_title": "Siapkan Cadangan Aman",
         "setup_secure_backup": {
-            "explainer": "Cadangkan kunci Anda sebelum keluar untuk menghindari kehilangannya.",
-            "title": "Siapkan"
+            "explainer": "Cadangkan kunci Anda sebelum keluar untuk menghindari kehilangannya."
         },
+        "turn_on_key_storage": "Aktifkan penyimpanan kunci",
+        "turn_on_key_storage_description": "Simpan identitas kriptografi dan kunci pesan Anda secara aman di server. Ini akan memungkinkan Anda untuk melihat riwayat pesan Anda di perangkat baru mana pun.",
         "udd": {
             "interactive_verification_button": "Verifikasi secara interaktif sengan emoji",
             "other_ask_verify_text": "Tanyakan pengguna ini untuk memverifikasi sesinya, atau verifikasi secara manual di bawah.",
@@ -873,12 +981,10 @@
             "title": "Tidak Dipercayai"
         },
         "unable_to_setup_keys_error": "Tidak dapat mengatur kunci-kunci",
-        "unsupported": "Klien ini tidak mendukung enkripsi ujung ke ujung.",
         "verification": {
             "accepting": "Menerima…",
             "after_new_login": {
                 "device_verified": "Perangkat telah diverifikasi",
-                "reset_confirmation": "Benar-benar ingin mengatur ulang kunci-kunci verifikasi?",
                 "skip_verification": "Lewatkan verifikasi untuk sementara",
                 "unable_to_verify": "Tidak dapat memverifikasi perangkat ini",
                 "verify_this_device": "Verifikasi perangkat ini"
@@ -900,7 +1006,7 @@
             "incoming_sas_dialog_waiting": "Menunggu pengguna untuk konfirmasi…",
             "incoming_sas_user_dialog_text_1": "Verifikasi pengguna ini untuk menandainya sebagai terpercaya. Mempercayai pengguna memberikan Anda ketenangan saat menggunakan pesan terenkripsi secara ujung ke ujung.",
             "incoming_sas_user_dialog_text_2": "Memverifikasi pengguna ini akan menandai sesinya sebagai terpercaya, dan juga menandai sesi Anda sebagai terpercaya kepadanya.",
-            "no_key_or_device": "Sepertinya Anda tidak memiliki Kunci Keamanan atau perangkat lainnya yang Anda dapat gunakan untuk memverifikasi.  Perangkat ini tidak dapat mengakses ke pesan terenkripsi lama. Untuk membuktikan identitas Anda, kunci verifikasi harus diatur ulang.",
+            "no_key_or_device": "Sepertinya Anda tidak memiliki Kunci Pemulihan atau perangkat lain yang dapat Anda verifikasi. Perangkat ini tidak akan dapat mengakses pesan terenkripsi lama. Untuk memverifikasi identitas Anda di perangkat ini, Anda harus mengatur ulang kunci verifikasi Anda.",
             "no_support_qr_emoji": "Perangkat yang Anda sedang verifikasi tidak mendukung pemindaian kode QR atau verifikasi emoji, yang didukung oleh %(brand)s. Coba menggunakan klien yang lain.",
             "other_party_cancelled": "Pengguna yang lain membatalkan proses verifikasi ini.",
             "prompt_encrypted": "Verifikasi semua pengguna di sebuah ruangan untuk memastikan keamanannya.",
@@ -913,6 +1019,7 @@
             "qr_reciprocate_same_shield_device": "Hampir selesai! Apakah perangkat lain Anda menampilkan perisai yang sama?",
             "qr_reciprocate_same_shield_user": "Hampir selesai! Apakah %(displayName)s menampilkan perisai yang sama?",
             "request_toast_accept": "Verifikasi Sesi",
+            "request_toast_accept_user": "Verifikasi Pengguna",
             "request_toast_decline_counter": "Abaikan (%(counter)s)",
             "request_toast_detail": "%(deviceId)s dari %(ip)s",
             "reset_proceed_prompt": "Lanjutkan dengan mengatur ulang",
@@ -938,7 +1045,7 @@
             "unverified_sessions_toast_description": "Periksa untuk memastikan akun Anda aman",
             "unverified_sessions_toast_reject": "Nanti",
             "unverified_sessions_toast_title": "Anda memiliki sesi yang belum diverifikasi",
-            "verification_description": "Verifikasi identitas Anda untuk mengakses pesan-pesan terenkripsi Anda dan buktikan identitas Anda kepada lainnya.",
+            "verification_description": "Verifikasi identitas Anda untuk mengakses pesan terenkripsi dan membuktikan identitas Anda kepada orang lain. Jika Anda juga menggunakan ponsel, harap buka aplikasi di sana sebelum melanjutkan.",
             "verification_dialog_title_device": "Verifikasi perangkat lain",
             "verification_dialog_title_user": "Permintaan Verifikasi",
             "verification_skip_warning": "Tanpa memverifikasi, Anda tidak akan memiliki akses ke semua pesan Anda dan tampak tidak dipercayai kepada lainnya.",
@@ -948,19 +1055,20 @@
             "verify_emoji_prompt": "Verifikasi dengan membandingkan emoji unik.",
             "verify_emoji_prompt_qr": "Jika Anda tidak dapat memindai kode di atas, verifikasi dengan membandingkan emoji yang unik.",
             "verify_later": "Saya verifikasi nanti",
-            "verify_reset_warning_1": "Mengatur ulang kunci verifikasi Anda tidak dapat dibatalkan. Setelah mengatur ulang, Anda tidak akan memiliki akses ke pesan terenkripsi lama, dan semua orang yang sebelumnya telah memverifikasi Anda akan melihat peringatan keamanan sampai Anda memverifikasi ulang dengan mereka.",
-            "verify_reset_warning_2": "Hanya lanjutkan jika Anda yakin Anda telah kehilangan semua perangkat lainnya dan kunci keamanan Anda.",
             "verify_using_device": "Verifikasi dengan perangkat lain",
             "verify_using_key": "Verifikasi dengan Kunci Keamanan",
-            "verify_using_key_or_phrase": "Verifikasi dengan Kunci Keamanan atau Frasa",
+            "verify_using_key_or_phrase": "Verifikasi dengan Kunci atau Frasa Keamanan",
             "waiting_for_user_accept": "Menunggu untuk %(displayName)s untuk menerima…",
             "waiting_other_device": "Menunggu Anda untuk verifikasi di perangkat Anda yang lain…",
             "waiting_other_device_details": "Menunggu Anda untuk memverifikasi perangkat Anda yang lain, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Menunggu %(displayName)s untuk memverifikasi…"
         },
         "verification_requested_toast_title": "Verifikasi diminta",
+        "verified_identity_changed": "Identitas terverifikasi %(displayName)s (<b>%(userId)s</b>) telah berubah. <a>Pelajari lebih lanjut</a>",
+        "verified_identity_changed_no_displayname": "Identitas terverifikasi <b>%(userId)s</b> telah berubah. <a>Pelajari lebih lanjut</a>",
         "verify_toast_description": "Pengguna yang lain mungkin tidak mempercayainya",
-        "verify_toast_title": "Verifikasi sesi ini"
+        "verify_toast_title": "Verifikasi sesi ini",
+        "withdraw_verification_action": "Tolak verifikasi"
     },
     "error": {
         "admin_contact": "Mohon <a>hubungi administrator layanan Anda</a> untuk melanjutkan menggunakan layanannya.",
@@ -1001,6 +1109,13 @@
         "unknown_error_code": "kode kesalahan tidak diketahui",
         "update_power_level": "Gagal untuk mengubah tingkat daya"
     },
+    "error_app_open_in_another_tab": "%(brand)s telah dibuka di tab lain.",
+    "error_app_open_in_another_tab_title": "%(brand)s sedang terhubung di tab lain",
+    "error_app_opened_in_another_window": "%(brand)s sedang dibuka di jendela lain. Klik \"%(label)s\" untuk menggunakan %(brand)s di sini dan putuskan sambungan di jendela lainnya.",
+    "error_database_closed_description": {
+        "for_desktop": "Diska Anda mungkin sudah penuh. Mohon bersihkan beberapa ruang dan muat ulang.",
+        "for_web": "Jika Anda menghapus data penelusuran, maka pesan ini akan muncul. %(brand)s mungkin juga terbuka di tab lain, atau diska Anda penuh. Tolong bersihkan beberapa ruang dan muat ulang"
+    },
     "error_database_closed_title": "%(brand)s berhenti bekerja",
     "error_dialog": {
         "copy_room_link_failed": {
@@ -1008,11 +1123,7 @@
             "title": "Tidak dapat menyalin tautan ruangan"
         },
         "error_loading_user_profile": "Tidak dapat memuat profil pengguna",
-        "forget_room_failed": "Gagal melupakan ruangan %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Server mungkin tidak tersedia, terlalu penuh, atau waktu pencarian habis :(",
-            "title": "Pencarian gagal"
-        }
+        "forget_room_failed": "Gagal melupakan ruangan %(errCode)s"
     },
     "error_user_not_logged_in": "Pengguna belum masuk",
     "event_preview": {
@@ -1037,7 +1148,15 @@
             "you": "Anda bereaksi %(reaction)s ke %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Audio",
+            "file": "Berkas",
+            "image": "Gambar",
+            "poll": "Pemugutan suara",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Ekspor Dibatalkan",
@@ -1129,6 +1248,7 @@
         "change": "Ubah server identitas",
         "change_prompt": "Putuskan hubungan dari server identitas <current /> dan hubungkan ke <new />?",
         "change_server_prompt": "Jika Anda tidak ingin menggunakan <server /> untuk menemukan dan dapat ditemukan oleh kontak yang Anda tahu, masukkan server identitas yang lain di bawah.",
+        "changed": "Server identitas Anda telah diubah",
         "checking": "Memeriksa server",
         "description_connected": "Anda saat ini menggunakan <server></server> untuk menemukan dan dapat ditemukan oleh kontak yang Anda tahu. Anda dapat mengubah server identitas di bawah.",
         "description_disconnected": "Anda saat ini tidak menggunakan sebuah server identitas. Untuk menemukan dan dapat ditemukan oleh kontak yang Anda tahu, tambahkan satu di bawah.",
@@ -1160,7 +1280,20 @@
         "other": "Dalam %(spaceName)s dan %(count)s space lainnya."
     },
     "incompatible_browser": {
-        "title": "Peramban tidak didukung"
+        "continue": "Tetap lanjutkan",
+        "description": "%(brand)s menggunakan beberapa fitur peramban yang tidak tersedia di peramban Anda saat ini. %(detail)s",
+        "detail_can_continue": "Jika Anda melanjutkan, beberapa fitur mungkin berhenti bekerja dan ada risiko kehilangan data di masa mendatang.",
+        "detail_no_continue": "Coba perbarui peramban ini jika Anda tidak menggunakan versi terbaru dan coba lagi.",
+        "learn_more": "Pelajari lebih lanjut",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Untuk pengalaman terbaik, gunakan <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge>, atau <Safari>Safari</Safari>.",
+        "title": "Peramban tidak didukung",
+        "use_desktop_heading": "Gunakan %(brand)s Desktop sebagai gantinya",
+        "use_mobile_heading": "Gunakan %(brand)s di ponsel sebagai gantinya",
+        "use_mobile_heading_after_desktop": "Atau gunakan aplikasi ponsel kami",
+        "windows_64bit": "Windows (64-bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
     },
     "info_tooltip_title": "Informasi",
     "integration_manager": {
@@ -1169,6 +1302,7 @@
         "error_connecting_heading": "Tidak dapat menghubungkan ke manajer integrasi",
         "explainer": "Manajer integrasi menerima data pengaturan, dan dapat mengubah widget, mengirimkan undangan ruangan, dan mengatur tingkat daya dengan sepengetahuan Anda.",
         "manage_title": "Kelola integrasi",
+        "toggle_label": "Aktifkan pengelola integrasi",
         "use_im": "Gunakan sebuah manajer integrasi untuk mengelola bot, widget, dan paket stiker.",
         "use_im_default": "Gunakan manajer integrasi <b>(%(serverName)s)</b> untuk mengelola bot, widget, dan paket stiker."
     },
@@ -1200,6 +1334,8 @@
         "error_permissions_space": "Anda tidak memiliki izin untuk mengundang seseorang ke space ini.",
         "error_profile_undisclosed": "Pengguna mungkin atau mungkin tidak ada",
         "error_transfer_multiple_target": "Sebuah panggilan dapat dipindah ke sebuah pengguna.",
+        "error_unfederated_room": "Ruangan ini tidak terfederasi. Anda tidak dapat mengundang orang dari server eksternal.",
+        "error_unfederated_space": "Space ini tidak terfederasi. Anda tidak dapat mengundang orang dari server eksternal.",
         "error_unknown": "Kesalahan server yang tidak diketahui",
         "error_user_not_found": "Pengguna tidak ada",
         "error_version_unsupported_room": "Homeserver penggunanya tidak mendukung versi ruangannya.",
@@ -1282,12 +1418,14 @@
         "navigate_next_message_edit": "Pergi ke pesan berikutnya untuk diedit",
         "navigate_prev_history": "Ruangan atau space yang dikunjungi sebelumnya",
         "navigate_prev_message_edit": "Pergi ke pesan sebelumnya untuk diedit",
+        "next_landmark": "Pergi ke tengara berikutnya",
         "next_room": "Ruangan atau pesan langsung berikutnya",
         "next_unread_room": "Ruangan atau pesan langsung berikutnya yang belum dibaca",
         "number": "[nomor]",
         "open_user_settings": "Buka pengaturan pengguna",
         "page_down": "Halaman Bawah",
         "page_up": "Halaman Atas",
+        "prev_landmark": "Pergi ke tengara sebelumnya",
         "prev_room": "Ruangan atau pesan langsung sebelumnya",
         "prev_unread_room": "Ruangan atau pesan langsung sebelumnya yang belum dibaca",
         "room_list_collapse_section": "Tutup bagian daftar ruangan",
@@ -1332,8 +1470,12 @@
         "dynamic_room_predecessors": "Pendahulu ruang dinamis",
         "dynamic_room_predecessors_description": "Aktifkan MSC3946 (untuk mendukung arsip ruangan yang datang terlambat)",
         "element_call_video_rooms": "Ruangan video Element Call",
+        "exclude_insecure_devices": "Kecualikan perangkat tidak aman ketika mengirim/menerima pesan",
+        "exclude_insecure_devices_description": "Jika mode ini diaktifkan, pesan terenkripsi tidak akan dibagikan dengan perangkat yang tidak terverifikasi, dan pesan dari perangkat yang tidak terverifikasi akan ditampilkan sebagai kesalahan. Perlu diperhatikan bahwa jika Anda mengaktifkan mode ini, Anda mungkin tidak dapat berkomunikasi dengan pengguna yang belum memverifikasi perangkat mereka.",
         "experimental_description": "Merasa eksperimental? Coba ide terkini kami dalam pengembangan. Fitur ini belum selesai; mereka mungkin tidak stabil, mungkin berubah, atau dihapus sama sekali. <a>Pelajari lebih lanjut</a>.",
         "experimental_section": "Pratinjau awal",
+        "extended_profiles_msc_support": "Memerlukan server Anda untuk mendukung MSC4133",
+        "feature_disable_call_per_sender_encryption": "Nonaktifkan enkripsi per pengirim untuk Element Call",
         "feature_wysiwyg_composer_description": "Menggunakan teks kaya daripada Markdown dalam komposer pesan.",
         "group_calls": "Pengalaman panggilan grup baru",
         "group_developer": "Pengembang",
@@ -1345,6 +1487,8 @@
         "group_rooms": "Ruangan",
         "group_spaces": "Space",
         "group_themes": "Tema",
+        "group_threads": "Utas",
+        "group_ui": "Antarmuka pengguna",
         "group_voip": "Suara & Video",
         "group_widgets": "Widget",
         "hidebold": "Sembunyikan titik notifikasi (hanya tampilkan lencana penghitung)",
@@ -1360,10 +1504,12 @@
         "location_share_live_description": "Penerapan sementara. Lokasi tetap berada di riwayat ruangan.",
         "mjolnir": "Cara baru mengabaikan orang",
         "msc3531_hide_messages_pending_moderation": "Memperbolehkan moderator untuk menyembunyikan pesan yang akan dimoderasikan.",
+        "new_room_list": "Aktifkan daftar ruangan baru",
         "notification_settings": "Pengaturan Notifikasi Baru",
         "notification_settings_beta_caption": "Perkenalkan cara yang lebih sederhana untuk mengubah pengaturan notifikasi Anda. Sesuaikan %(brand)s Anda, sesuai keinginan Anda.",
         "notification_settings_beta_title": "Pengaturan Notifikasi",
         "notifications": "Aktifkan panel notifikasi di tajuk ruangan",
+        "release_announcement": "Pengumuman rilis",
         "render_reaction_images": "Render gambar khusus dalam reaksi",
         "render_reaction_images_description": "Terkadang disebut sebagai \"emoji khusus\".",
         "report_to_moderators": "Laporkan ke moderator",
@@ -1371,7 +1517,7 @@
         "sliding_sync": "Mode Sinkronisasi Geser",
         "sliding_sync_description": "Dalam pengembangan aktif, tidak dapat dinonaktifkan.",
         "sliding_sync_disabled_notice": "Keluar dan masuk kembali ke akun untuk menonaktifkan",
-        "sliding_sync_server_no_support": "Server Anda belum mendukungnya",
+        "sliding_sync_server_no_support": "Server Anda tidak memiliki dukungan",
         "under_active_development": "Dalam pengembangan aktif.",
         "unrealiable_e2e": "Tidak dapat diandalkan di ruangan terenkripsi",
         "video_rooms": "Ruangan video",
@@ -1423,6 +1569,8 @@
         "last_person_warning": "Anda adalah satu-satunya di sini. Jika Anda keluar, tidak ada siapa saja dapat bergabung di masa mendatang, termasuk Anda.",
         "leave_room_question": "Anda yakin ingin meninggalkan ruangan '%(roomName)s'?",
         "leave_space_question": "Apakah Anda yakin untuk keluar dari space '%(spaceName)s'?",
+        "room_leave_admin_warning": "Anda adalah administrator satu-satunya di ruangan ini. Jika Anda keluar, tidak ada siapa pun yang dapat mengubah pengaturan ruangan atau melakukan tindakan penting lainnya.",
+        "room_leave_mod_warning": "Anda adalah moderator satu-satunya di ruangan ini. Jika Anda keluar, tidak ada siapa pun yang dapat mengubah pengaturan ruangan atau melakukan tindakan penting lainnya.",
         "room_rejoin_warning": "Ruangan ini tidak publik. Anda tidak dapat bergabung lagi tanpa sebuah undangan.",
         "space_rejoin_warning": "Space ini tidak publik. Anda tidak dapat bergabung lagi tanpa sebuah undangan."
     },
@@ -1480,12 +1628,18 @@
         "toggle_attribution": "Alih atribusi"
     },
     "member_list": {
+        "count": {
+            "other": "%(count)s Anggota"
+        },
         "filter_placeholder": "Saring anggota ruangan",
         "invite_button_no_perms_tooltip": "Anda tidak memiliki izin untuk mengundang pengguna",
+        "invited_label": "Diundang",
+        "no_matches": "Tidak ada kecocokan",
         "power_label": "%(userName)s (tingkat daya %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Anggota ruangan",
     "message_edit_dialog_title": "Editan pesan",
+    "migrating_crypto": "Bersabarlah. Kami sedang memperbarui Element untuk membuat enkripsi lebih cepat dan andal.",
     "mobile_guide": {
         "toast_accept": "Gunakan aplikasi",
         "toast_description": "%(brand)s bersifat eksperimental pada peramban web ponsel. Untuk pengalaman yang lebih baik dan fitur-fitur terkini, gunakan aplikasi natif gratis kami.",
@@ -1503,6 +1657,7 @@
         "class_global": "Global",
         "class_other": "Lainnya",
         "default": "Bawaan",
+        "default_settings": "Cocokkan pengaturan bawaan",
         "email_pusher_app_display_name": "Notifikasi Surel",
         "enable_prompt_toast_description": "Aktifkan notifikasi desktop",
         "enable_prompt_toast_title": "Notifikasi",
@@ -1510,12 +1665,19 @@
         "error_change_title": "Ubah pengaturan notifikasi",
         "keyword": "Kata kunci",
         "keyword_new": "Kata kunci baru",
+        "level_activity": "Aktivitas",
+        "level_highlight": "Sorotan",
+        "level_muted": "Dibisukan",
+        "level_none": "Tidak ada",
+        "level_notification": "Notifikasi",
+        "level_unsent": "Pengiriman dibatalkan",
         "mark_all_read": "Tandai semua sebagai dibaca",
         "mentions_and_keywords": "@sebutan & kata kunci",
         "mentions_and_keywords_description": "Dapatkan notifikasi hanya dengan sebutan dan kata kunci yang diatur di <a>pengaturan</a> Anda",
-        "mentions_keywords": "Sebutan & kata kunci",
+        "mentions_keywords": "Sebutan dan kata kunci",
         "message_didnt_send": "Pesan tidak terkirim. Klik untuk informasi.",
-        "mute_description": "Anda tidak akan mendapatkan notifikasi apa pun"
+        "mute_description": "Anda tidak akan mendapatkan notifikasi apa pun",
+        "mute_room": "Bisukan ruangan"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s meminta verifikasi"
@@ -1596,7 +1758,8 @@
         "online": "Daring",
         "online_for": "Daring selama %(duration)s",
         "unknown": "Tidak Dikenal",
-        "unknown_for": "Tidak diketahui untuk %(duration)s"
+        "unknown_for": "Tidak diketahui untuk %(duration)s",
+        "unreachable": "Server pengguna tidak dapat dijangkau"
     },
     "quick_settings": {
         "all_settings": "Semua pengaturan",
@@ -1616,14 +1779,10 @@
         "ongoing": "Menghilangkan…",
         "reason_label": "Alasan (opsional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Anda yakin menolak undangannya?",
-        "failed": "Gagal menolak undangan",
-        "title": "Tolak undangan"
-    },
     "report_content": {
         "description": "Melaporkan pesan ini akan mengirimkan ID peristiwa yang untuk ke administrator homeserver Anda. Jika pesan-pesan di ruangan ini terenkripsi, maka administrator homeserver Anda tidak akan dapat membaca teks pesan atau menampilkan file atau gambar apa saja.",
         "disagree": "Tidak Setuju",
+        "error_create_room_moderation_bot": "Tidak dapat membuat ruangan dengan bot moderasi",
         "hide_messages_from_user": "Periksa jika Anda ingin menyembunyikan semua pesan saat ini dan pesan baru dari pengguna ini.",
         "ignore_user": "Abaikan pengguna",
         "illegal_content": "Konten Ilegal",
@@ -1631,6 +1790,8 @@
         "nature": "Harap pilih sifat dan jelaskan apa yang membuat pesan ini kasar.",
         "nature_disagreement": "Apa yang ditulis pengguna itu salah.\nIni akan dilaporkan ke moderator ruangan.",
         "nature_illegal": "Pengguna ini menampilkan kelakuan yang ilegal, misalnya dengan doxing orang lain atau ancaman kekerasan.\nIni akan dilaporkan ke moderator ruangan yang mungkin melaporkannya juga ke otoritas hukum.",
+        "nature_nonstandard_admin": "Ruang ini didedikasikan untuk konten ilegal atau toksik atau moderator gagal memoderasi konten ilegal atau toksik.\nHal ini akan dilaporkan kepada administrator %(homeserver)s.",
+        "nature_nonstandard_admin_encrypted": "Ruang ini didedikasikan untuk konten ilegal atau toksik atau moderator gagal memoderasi konten ilegal atau toksik.\nHal ini akan dilaporkan kepada administrator %(homeserver)s. Administrator TIDAK akan dapat membaca konten terenkripsi di ruangan ini.",
         "nature_other": "Alasan yang lain. Mohon jelaskan masalahnya.\nIni akan dilaporkan ke moderator ruangan.",
         "nature_spam": "Pengguna ini spam ruangan dengan iklan, tautan ke iklan atau ke propaganda.\nIni akan dilaporkan ke moderator ruangan.",
         "nature_toxic": "Pengguna ini menampilkan kelakuan yang toksik, misalnya dengan menghina pengguna lain atau membagikan konten dewasa di ruangan ramah keluarga atau merusak aturan ruangan.\nIni akan dilaporkan ke moderator ruangan.",
@@ -1640,6 +1801,10 @@
         "spam_or_propaganda": "Spam atau propaganda",
         "toxic_behaviour": "Kelakukan Toxic"
     },
+    "report_room": {
+        "description": "Laporkan ruangan ini ke admin homeserver Anda. Ini akan mengirimkan ID unik ruangan, tetapi jika pesan dienkripsi, administrator tidak akan dapat membacanya atau melihat file terbagi.",
+        "reason_label": "Jelaskan alasannya"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Gagal untuk mendekripsi %(failedCount)s sesi!",
         "count_of_successfully_restored_keys": "Berhasil memulihkan %(sessionCount)s kunci",
@@ -1652,26 +1817,48 @@
         "key_backup_warning": "<b>Peringatan</b>: Anda seharusnya menyiapkan cadangan kunci di komputer yang dipercayai.",
         "key_fetch_in_progress": "Mendapatkan kunci- dari server…",
         "key_forgotten_text": "Jika Anda lupa Kunci Keamanan, Anda dapat <button>menyiapkan opsi pemulihan baru</button>",
-        "key_is_invalid": "Bukan Kunci Keamanan yang absah",
-        "key_is_valid": "Ini sepertinya Kunci Keamanan yang absah!",
+        "key_is_invalid": "Bukan Kunci Pemulihan yang valid",
+        "key_is_valid": "Ini sepertinya Kunci Pemulihan yang valid!",
         "keys_restored_title": "Kunci-kunci terpulihkan",
         "load_error_content": "Tidak dapat memuat status cadangan",
         "load_keys_progress": "%(completed)s dari %(total)s kunci dipulihkan",
         "no_backup_error": "Tidak ada cadangan yang ditemukan!",
-        "phrase_forgotten_text": "Jika Anda lupa Frasa Keamanan, Anda dapat <button1>menggunakan Kunci Keamanan Anda</button1> atau <button2>siapkan opsi pemulihan baru</button2>",
-        "recovery_key_mismatch_description": "Cadangan tidak dapat didekripsikan dengan Kunci Keamanan ini: mohon periksa jika Anda memasukkan Kunci Keamanan yang benar.",
+        "phrase_forgotten_text": "Jika Anda lupa Frasa Keamanan Anda, Anda dapat <button1>menggunakan Kunci Pemulihan Anda</button1> atau <button2>mengatur opsi pemulihan baru</button2>",
+        "recovery_key_mismatch_description": "Cadangan tidak dapat didekripsi dengan Kunci Pemulihan ini: mohon pastikan Anda memasukkan Kunci Pemulihan yang benar.",
         "recovery_key_mismatch_title": "Kunci Keamanan tidak cocok",
         "restore_failed_error": "Tidak dapat memulihkan cadangan"
     },
     "right_panel": {
-        "add_integrations": "Tambahkan widget, jembatan & bot",
+        "add_integrations": "Tambahkan ekstensi",
+        "add_topic": "Tambahkan topik",
+        "extensions_button": "Ekstensi",
+        "extensions_empty_description": "Pilih “%(addIntegrations)s” untuk menelusuri dan menambahkan ekstensi ke ruangan ini",
+        "extensions_empty_title": "Tingkatkan produktivitas dengan lebih banyak alat, widget, dan bot",
         "files_button": "File",
         "pinned_messages": {
+            "empty_description": "Pilih pesan dan pilih “%(pinAction)s” untuk disertakan di sini.",
+            "empty_title": "Sematkan pesan penting agar mudah ditemukan",
+            "header": {
+                "other": "%(count)s Pesan tersemat"
+            },
             "limits": {
                 "other": "Anda hanya dapat memasang pin sampai %(count)s widget"
-            }
+            },
+            "menu": "Buka menu",
+            "release_announcement": {
+                "close": "Oke",
+                "description": "Temukan semua pesan yang disematkan di sini. Arahkan pesan apa pun dan pilih “Sematkan” untuk menambahkannya.",
+                "title": "Semua pesan baru yang disematkan"
+            },
+            "reply_thread": "Balas ke <link>pesan dalam utas</link>",
+            "unpin_all": {
+                "button": "Lepas sematan semua pesan",
+                "content": "Pastikan Anda benar-benar ingin melepaskan sematan semua pesan tersemat. Tindakan ini tidak dapat diurungkan.",
+                "title": "Hapus sematan semua pesan?"
+            },
+            "view": "Lihat di lini masa"
         },
-        "pinned_messages_button": "Disematkan",
+        "pinned_messages_button": "Pesan yang disematkan",
         "poll": {
             "active_heading": "Pemungutan suara yang aktif",
             "empty_active": "Tidak ada pemungutan suara yang aktif di ruangan ini",
@@ -1696,7 +1883,7 @@
             "view_in_timeline": "Tampilkan pemungutan suara di lini masa",
             "view_poll": "Tampilkan pemungutan suara"
         },
-        "polls_button": "Riwayat pemungutan suara",
+        "polls_button": "Pemungutan suara",
         "room_summary_card": {
             "title": "Informasi ruangan"
         },
@@ -1725,6 +1912,7 @@
             "forget": "Lupakan Ruangan",
             "low_priority": "Prioritas Rendah",
             "mark_read": "Tandai sebagai dibaca",
+            "mark_unread": "Tandai sebagai belum dibaca",
             "notifications_default": "Sesuai dengan pengaturan bawaan",
             "notifications_mute": "Bisukan ruangan",
             "title": "Opsi ruangan",
@@ -1772,6 +1960,8 @@
             },
             "room_is_public": "Ruangan ini publik"
         },
+        "header_avatar_open_settings_label": "Buka pengaturan ruangan",
+        "header_face_pile_tooltip": "Alihkan daftar anggota",
         "header_untrusted_label": "Tidak dipercaya",
         "inaccessible": "Ruangan atau space ini tidak dapat diakses pada saat ini.",
         "inaccessible_name": "%(roomName)s tidak dapat diakses sekarang.",
@@ -1795,10 +1985,9 @@
             "you_created": "Anda membuat ruangan ini."
         },
         "invite_email_mismatch_suggestion": "Bagikan email ini di Pengaturan untuk mendapatkan undangan secara langsung di %(brand)s.",
-        "invite_reject_ignore": "Tolak & Abaikan pengguna",
         "invite_sent_to_email": "Undangan ini telah dikirim ke %(email)s",
         "invite_sent_to_email_room": "Undangan ke %(roomName)s ini terkirim ke %(email)s",
-        "invite_subtitle": "<userName/> mengundang Anda",
+        "invite_subtitle": "Diundang oleh <userName/>",
         "invite_this_room": "Undang ke ruangan ini",
         "invite_title": "Apakah Anda ingin bergabung %(roomName)s?",
         "inviter_unknown": "Tidak Dikenal",
@@ -1841,11 +2030,23 @@
         "not_found_title": "Ruangan atau space ini tidak ada.",
         "not_found_title_name": "%(roomName)s tidak ada.",
         "peek_join_prompt": "Anda melihat tampilan %(roomName)s. Ingin bergabung?",
+        "pinned_message_badge": "Pesan yang disematkan",
+        "pinned_message_banner": {
+            "button_close_list": "Tutup daftar",
+            "button_view_all": "Lihat semua",
+            "description": "Ruangan ini memiliki pesan yang disematkan. Klik untuk melihatnya.",
+            "go_to_message": "Lihat pesan yang disematkan di lini masa.",
+            "title": "<bold>%(index)s dari %(length)s</bold> Pesan yang disematkan"
+        },
         "read_topic": "Klik untuk membaca topik",
         "rejecting": "Menolak undangan…",
         "rejoin_button": "Bergabung Ulang",
         "search": {
             "all_rooms_button": "Cari semua ruangan",
+            "placeholder": "Cari pesan...",
+            "summary": {
+                "other": "%(count)s hasil ditemukan untuk “<query/>”"
+            },
             "this_room_button": "Cari ruangan ini"
         },
         "status_bar": {
@@ -1878,37 +2079,90 @@
             },
             "uploading_single_file": "Mengunggah %(filename)s"
         },
+        "video_room": "Ruangan ini adalah ruangan video",
         "waiting_for_join_subtitle": "Setelah pengguna yang diundang telah bergabung ke %(brand)s, Anda akan dapat bercakapan dan ruangan akan terenkripsi secara ujung ke ujung",
         "waiting_for_join_title": "Menunggu pengguna untuk bergabung ke %(brand)s"
     },
     "room_list": {
         "add_room_label": "Tambahkan ruangan",
         "add_space_label": "Tambahkan space",
+        "appearance": "Penampilan",
         "breadcrumbs_empty": "Tidak ada ruangan yang baru saja dilihat",
         "breadcrumbs_label": "Ruangan yang baru saja dilihat",
+        "empty": {
+            "no_chats": "Belum ada obrolan",
+            "no_chats_description": "Mulailah dengan mengirim pesan kepada seseorang atau dengan membuat ruangan",
+            "no_chats_description_no_room_rights": "Mulailah dengan mengirim pesan kepada seseorang",
+            "no_favourites": "Anda belum memiliki obrolan favorit",
+            "no_favourites_description": "Anda dapat menambahkan obrolan ke favorit Anda di pengaturan obrolan",
+            "no_invites": "Anda tidak memiliki undangan yang belum dibaca",
+            "no_mentions": "Anda tidak memiliki sebutan yang belum dibaca",
+            "no_people": "Anda belum memiliki obrolan langsung dengan siapa pun",
+            "no_people_description": "Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain",
+            "no_rooms": "Anda belum berada di ruangan mana pun",
+            "no_rooms_description": "Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain",
+            "no_unread": "Selamat! Anda tidak memiliki pesan yang belum dibaca",
+            "show_activity": "Lihat semua aktivitas",
+            "show_chats": "Tampilkan semua obrolan"
+        },
         "failed_add_tag": "Gagal menambahkan tag %(tagName)s ke ruangan",
         "failed_remove_tag": "Gagal menghapus tanda %(tagName)s dari ruangan",
         "failed_set_dm_tag": "Gagal menetapkan tanda pesan langsung",
+        "filters": {
+            "favourite": "Favorit",
+            "invites": "Undangan",
+            "mentions": "Sebutan",
+            "people": "Orang",
+            "rooms": "Ruangan",
+            "unread": "Belum dibaca"
+        },
         "home_menu_label": "Opsi Beranda",
         "join_public_room_label": "Bergabung dengan ruangan publik",
         "joining_rooms_status": {
             "one": "Saat ini bergabung dengan %(count)s ruangan",
             "other": "Saat ini bergabung dengan %(count)s ruangan"
         },
+        "list_title": "Daftar ruangan",
+        "more_options": {
+            "copy_link": "Salin tautan ruangan",
+            "favourited": "Difavorit",
+            "leave_room": "Tinggalkan ruangan",
+            "low_priority": "Prioritas rendah",
+            "mark_read": "Tandai sebagai dibaca",
+            "mark_unread": "Tandai sebagai belum dibaca"
+        },
         "notification_options": "Opsi notifikasi",
+        "open_space_menu": "Buka menu space",
+        "primary_filters": "Filter daftar ruangan",
         "redacting_messages_status": {
             "one": "Saat ini menghapus pesan-pesan di %(count)s ruangan",
             "other": "Saat ini menghapus pesan-pesan di %(count)s ruangan"
         },
+        "room": {
+            "more_options": "Opsi Lainnya",
+            "open_room": "Buka ruangan %(roomName)s"
+        },
+        "room_options": "Opsi Ruangan",
         "show_less": "Tampilkan lebih sedikit",
+        "show_message_previews": "Tampilkan pratinjau pesan",
         "show_n_more": {
             "one": "Tampilkan %(count)s lagi",
             "other": "Tampilkan %(count)s lagi"
         },
         "show_previews": "Tampilkan tampilan pesan",
+        "sort": "Urutkan",
         "sort_by": "Sortir berdasarkan",
         "sort_by_activity": "Aktivitas",
+        "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Aktivitas",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Tampilkan ruangan dengan pesan yang belum dibaca dahulu",
+        "space_menu": {
+            "home": "Beranda space",
+            "space_settings": "Pengaturan Space"
+        },
         "space_menu_label": "Menu %(spaceName)s",
         "sublist_options": "Tampilkan daftar opsi",
         "suggested_rooms_heading": "Ruangan yang Disarankan"
@@ -1980,6 +2234,8 @@
             "error_deleting_alias_description": "Terjadi sebuah kesalahan menghapus alamat. Itu mungkin sudah tidak ada atau ada kesalahan sementara.",
             "error_deleting_alias_description_forbidden": "Anda tidak memiliki izin untuk menghapus alamatnya.",
             "error_deleting_alias_title": "Terjadi kesalahan menghapus alamat",
+            "error_publishing": "Tidal dapat menerbitkan ruangan",
+            "error_publishing_detail": "Terjadi kesalahan saat menerbitkan ruangan ini",
             "error_save_space_settings": "Gagal untuk menyimpan pengaturan space.",
             "error_updating_alias_description": "Terjadi sebuah kesalahan memperbarui alamat alternatif ruangan. Ini mungkin tidak diperbolehkan oleh servernya atau ada kegagalan sementara.",
             "error_updating_canonical_alias_description": "Terjadi sebuah kesalahan memperbarui alamat utama ruangan. Ini mungkin tidak diperbolehkan oleh servernya atau ada kegagalan sementara.",
@@ -2133,8 +2389,9 @@
             },
             "join_rule_upgrade_upgrading_room": "Meningkatkan ruangan",
             "public_without_alias_warning": "Untuk menautkan ruangan ini, mohon tambahkan sebuah alamat.",
+            "publish_room": "Buat ruangan ini terlihat di direktori ruangan publik.",
             "publish_space": "Buat ruang ini terlihat di direktori ruangan publik.",
-            "strict_encryption": "Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi di ruangan ini dari sesi ini",
+            "strict_encryption": "Hanya kirim pesan ke pengguna terverifikasi.",
             "title": "Keamanan & Privasi"
         },
         "title": "Pengaturan Ruangan — %(roomName)s",
@@ -2188,6 +2445,10 @@
         "recent_changes_heading": "Perubahan terbaru yang belum diterima",
         "title": "Server tidak merespon"
     },
+    "service_worker_error": {
+        "description": "%(brand)s memerlukan pekerja layanan untuk memuat media yang diautentikasi dari repositori konten Matrix. Ini tidak didukung oleh peramban Anda sehingga Anda mungkin mengalami kegagalan pemuatan media.",
+        "title": "Gagal memuat pekerja layanan"
+    },
     "seshat": {
         "error_initialising": "Initialisasi pencarian pesan gagal, periksa <a>pengaturan Anda</a> untuk informasi lanjut",
         "reset_button": "Atur ulang penyimpanan peristiwa",
@@ -2204,6 +2465,8 @@
             "access_token_detail": "Token akses Anda memberikan akses penuh ke akun Anda. Jangan bagikan dengan siapa pun.",
             "brand_version": "Versi %(brand)s:",
             "clear_cache_reload": "Hapus cache dan muat ulang",
+            "crypto_version": "Versi kripto:",
+            "dialog_title": "<strong>Pengaturan:</strong> Bantuan & Tentang",
             "help_link": "Untuk bantuan dengan menggunakan %(brand)s, klik <a>di sini</a>.",
             "homeserver": "Homeserver adalah <code>%(homeserverUrl)s</code>",
             "identity_server": "Server identitas adalah <code>%(identityServerUrl)s</code>",
@@ -2212,21 +2475,34 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Pengaturan:</strong> Akun",
+            "title": "Akun"
+        },
         "all_rooms_home": "Tampilkan semua ruangan di Beranda",
         "all_rooms_home_description": "Semua ruangan yang Anda bergabung akan ditampilkan di Beranda.",
         "always_show_message_timestamps": "Selalu tampilkan stempel waktu pesan",
         "appearance": {
+            "bundled_emoji_font": "Gunakan fon emoji yang dibundel",
+            "compact_layout": "Tampilkan teks dan pesan ringkas",
+            "compact_layout_description": "Tata letak modern harus dipilih untuk menggunakan fitur ini.",
             "custom_font": "Gunakan sebuah font sistem",
             "custom_font_description": "Atur sebuah nama font yang terinstal di sistem Anda & %(brand)s akan mencoba menggunakannya.",
             "custom_font_name": "Nama font sistem",
             "custom_font_size": "Gunakan ukuran kustom",
-            "custom_theme_error_downloading": "Terjadi kesalahan saat mengunduh informasi tema.",
+            "custom_theme_add": "Tambahkan tema kustom",
+            "custom_theme_downloading": "Mengunduh tema kustom...",
+            "custom_theme_error_downloading": "Terjadi kesalahan mengunduh tema",
+            "custom_theme_help": "Masukkan URL tema kustom yang ingin Anda terapkan.",
             "custom_theme_invalid": "Skema tema tidak absah.",
+            "dialog_title": "<strong>Pengaturan:</strong> Penampilan",
             "font_size": "Ukuran font",
+            "font_size_default": "%(fontSize)s (bawaan)",
+            "high_contrast": "Kontras tinggi",
             "image_size_default": "Bawaan",
             "image_size_large": "Besar",
             "layout_bubbles": "Gelembung pesan",
-            "layout_irc": "IRC (Eksperimental)",
+            "layout_irc": "IRC (eksperimental)",
             "match_system_theme": "Sesuaikan dengan tema sistem",
             "timeline_image_size": "Ukuran gambar di lini masa"
         },
@@ -2237,9 +2513,80 @@
         "code_block_expand_default": "Buka blok kode secara bawaan",
         "code_block_line_numbers": "Tampilkan nomor barisan di blok kode",
         "disable_historical_profile": "Tampilkan foto profil dan nama saat ini untuk pengguna dalam riwayat pesan",
+        "discovery": {
+            "title": "Cara menemukan Anda"
+        },
         "emoji_autocomplete": "Aktifkan saran emoji saat mengetik",
         "enable_markdown": "Aktifkan Markdown",
         "enable_markdown_description": "Mulai pesan dengan <code>/plain</code> untuk mengirim tanpa Markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Detail akun, kontak, preferensi, dan daftar obrolan Anda akan disimpan",
+                "breadcrumb_page": "Atur ulang enkripsi",
+                "breadcrumb_second_description": "Anda akan kehilangan semua riwayat pesan yang hanya disimpan di server",
+                "breadcrumb_third_description": "Anda perlu memverifikasi ulang semua perangkat dan kontak yang ada",
+                "breadcrumb_title": "Apakah Anda yakin ingin mengatur ulang identitas Anda?",
+                "breadcrumb_title_forgot": "Lupa kunci pemulihan Anda? Anda harus mengatur ulang identitas Anda.",
+                "breadcrumb_title_sync_failed": "Gagal menyinkronkan penyimpanan kunci. Anda perlu mengatur ulang identitas Anda.",
+                "breadcrumb_warning": "Lakukan ini hanya jika Anda yakin akun Anda telah terkompromi.",
+                "details_title": "Detail enkripsi",
+                "do_not_close_warning": "Jangan tutup jendela ini sampai pengaturan ulang selesai",
+                "export_keys": "Ekspor kunci",
+                "import_keys": "Impor kunci",
+                "other_people_device_description": "Secara bawaan dalam ruangan terenkripsi, jangan kirim pesan terenkripsi kepada siapa pun sampai Anda memverifikasinya",
+                "other_people_device_label": "Jangan pernah mengirim pesan terenkripsi ke perangkat yang tidak terverifikasi",
+                "other_people_device_title": "Perangkat orang lain",
+                "reset_identity": "Atur ulang identitas kriptografi",
+                "reset_in_progress": "Pengaturan ulang sedang berlangsung...",
+                "session_id": "ID sesi:",
+                "session_key": "Kunci sesi:",
+                "title": "Lanjutan"
+            },
+            "confirm_key_storage_off": "Apakah Anda yakin ingin tetap menonaktifkan penyimpanan kunci?",
+            "confirm_key_storage_off_description": "Jika Anda keluar dari semua perangkat, Anda akan kehilangan riwayat pesan dan perlu memverifikasi semua kontak yang ada lagi. <a> Pelajari lebih lanjut</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Hapus penyimpanan kunci",
+                "confirm": "Hapus penyimpanan kunci",
+                "description": "Menghapus penyimpanan kunci akan menghapus identitas kriptografi dan kunci pesan Anda dari server dan menonaktifkan fitur keamanan berikut:",
+                "list_first": "Anda tidak akan memiliki riwayat pesan terenkripsi di perangkat baru",
+                "list_second": "Anda akan kehilangan akses ke pesan terenkripsi jika Anda keluar dari %(brand)s di mana pun",
+                "title": "Apakah Anda yakin ingin mematikan penyimpanan kunci dan menghapusnya?"
+            },
+            "device_not_verified_button": "Verifikasi perangkat ini",
+            "device_not_verified_description": "Anda perlu memverifikasi perangkat ini untuk melihat pengaturan enkripsi Anda.",
+            "device_not_verified_title": "Perangkat tidak diverifikasi",
+            "dialog_title": "<strong>Pengaturan:</strong> Enkripsi",
+            "key_storage": {
+                "allow_key_storage": "Izinkan penyimpanan kunci",
+                "description": "Simpan identitas kriptografi dan kunci pesan Anda secara aman di server. Hal ini akan memungkinkan Anda untuk melihat riwayat pesan Anda di perangkat baru. <a>Pelajari lebih lanjut</a>",
+                "title": "Penyimpanan kunci"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Konfirmasikan kunci pemulihan baru",
+                "change_recovery_confirm_description": "Masukkan kunci pemulihan baru Anda di bawah ini untuk menyelesaikannya. Kunci lama Anda tidak akan berfungsi lagi.",
+                "change_recovery_confirm_title": "Masukkan kunci pemulihan baru Anda",
+                "change_recovery_key": "Ubah kunci pemulihan",
+                "change_recovery_key_description": "Tuliskan kunci pemulihan baru ini di tempat yang aman. Kemudian klik Lanjutkan untuk mengonfirmasi perubahan.",
+                "change_recovery_key_title": "Ubah kunci pemulihan?",
+                "description": "Pulihkan identitas kriptografi dan riwayat pesan Anda dengan kunci pemulihan jika Anda kehilangan semua perangkat yang ada.",
+                "enter_key_error": "Kunci pemulihan yang Anda masukkan salah.",
+                "enter_recovery_key": "Masukkan kunci pemulihan",
+                "forgot_recovery_key": "Lupa kunci pemulihan?",
+                "key_storage_warning": "Penyimpanan kunci Anda tidak tersinkron. Klik tombol di bawah ini untuk memperbaiki masalah.",
+                "save_key_description": "Jangan bagikan ini kepada siapa pun!",
+                "save_key_title": "Kunci pemulihan",
+                "set_up_recovery": "Siapkan pemulihan",
+                "set_up_recovery_confirm_button": "Selesaikan penyiapan",
+                "set_up_recovery_confirm_description": "Masukkan kunci pemulihan yang ditunjukkan pada layar sebelumnya untuk menyelesaikan penyiapan pemulihan.",
+                "set_up_recovery_confirm_title": "Masukkan kunci pemulihan Anda untuk mengonfirmasi",
+                "set_up_recovery_description": "Penyimpanan kunci Anda dilindungi oleh kunci pemulihan. Jika Anda memerlukan kunci pemulihan baru setelah penyiapan, Anda dapat membuatnya kembali dengan memilih '%(changeRecoveryKeyButton)s'.",
+                "set_up_recovery_save_key_description": "Catatlah kunci pemulihan ini di tempat yang aman, seperti pengelola kata sandi, catatan terenkripsi, atau brankas fisik.",
+                "set_up_recovery_save_key_title": "Simpan kunci pemulihan Anda di tempat yang aman",
+                "set_up_recovery_secondary_description": "Setelah mengeklik lanjutkan, kami akan membuat kunci pemulihan untuk Anda.",
+                "title": "Pemulihan"
+            },
+            "title": "Enkripsi"
+        },
         "general": {
             "account_management_section": "Manajemen akun",
             "account_section": "Akun",
@@ -2252,6 +2599,14 @@
             "add_msisdn_dialog_title": "Tambahkan Nomor Telepon",
             "add_msisdn_instructions": "Sebuah teks pesan telah dikirim ke +%(msisdn)s. Silakan masukkan kode verifikasinya.",
             "add_msisdn_misconfigured": "Aliran penambahan/pengaitan MSISDN tidak diatur dengan benar",
+            "allow_spellcheck": "Izinkan pemeriksaan ejaan",
+            "application_language": "Bahasa aplikasi",
+            "application_language_reload_hint": "Aplikasi akan dimuat ulang setelah memilih bahasa lain",
+            "avatar_remove_progress": "Menghapus gambar...",
+            "avatar_save_progress": "Mengunggah gambar...",
+            "avatar_upload_error_text": "Format berkas tidak didukung atau gambar lebih besar dari %(size)s.",
+            "avatar_upload_error_text_generic": "Format berkas mungkin tidak didukung.",
+            "avatar_upload_error_title": "Gambar avatar tidak dapat diunggah",
             "confirm_adding_email_body": "Klik tombol di bawah untuk mengkonfirmasi penambahan alamat email ini.",
             "confirm_adding_email_title": "Konfirmasi penambahan email",
             "deactivate_confirm_body": "Apakah Anda yakin ingin menonaktifkan akun Anda? Ini tidak dapat dibatalkan.",
@@ -2267,10 +2622,14 @@
             "deactivate_confirm_erase_label": "Sembunyikan pesan saya dari orang baru bergabung",
             "deactivate_section": "Nonaktifkan Akun",
             "deactivate_warning": "Menonaktifkan akun Anda adalah aksi yang permanen — hati-hati!",
-            "discovery_email_empty": "Opsi penemuan akan tersedia setelah Anda telah menambahkan sebuah email di atas.",
+            "discovery_email_empty": "Opsi penemuan akan muncul setelah Anda menambahkan surel.",
             "discovery_email_verification_instructions": "Verifikasi tautannya di kotak masuk Anda",
-            "discovery_msisdn_empty": "Opsi penemuan akan tersedia setelah Anda telah menambahkan sebuah nomor telepon di atas.",
+            "discovery_msisdn_empty": "Opsi penemuan akan muncul setelah Anda telah menambahkan nomor telepon.",
             "discovery_needs_terms": "Terima Ketentuan Layanannya server identitas %(serverName)s untuk mengizinkan Anda untuk dapat ditemukan dengan alamat email atau nomor telepon.",
+            "discovery_needs_terms_title": "Biarkan orang lain menemukan Anda",
+            "display_name": "Nama Tampilan",
+            "display_name_error": "Tidak dapat mengatur nama tampilan",
+            "email_adding_unsupported_by_hs": "Homeserver ini tidak mendukung penambahan alamat surel ke akun Anda.",
             "email_address_in_use": "Alamat email ini telah dipakai",
             "email_address_label": "Alamat Email",
             "email_not_verified": "Alamat email Anda belum diverifikasi",
@@ -2295,7 +2654,9 @@
             "error_share_msisdn_discovery": "Tidak dapat membagikan nomor telepon",
             "identity_server_no_token": "Tidak ada token akses identitas yang ditemukan",
             "identity_server_not_set": "Server identitas tidak diatur",
-            "language_section": "Bahasa dan wilayah",
+            "invalid_phone_number": "Nomor telepon yang diberikan tampaknya tidak valid.",
+            "language_section": "Bahasa",
+            "msisdn_adding_unsupported_by_hs": "Homeserver ini tidak mendukung penambahan nomor telepon ke akun Anda.",
             "msisdn_in_use": "Nomor telepon ini telah dipakai",
             "msisdn_label": "Nomor Telepon",
             "msisdn_verification_field_label": "Kode verifikasi",
@@ -2304,11 +2665,16 @@
             "oidc_manage_button": "Kelola akun",
             "password_change_section": "Atur kata sandi akun baru…",
             "password_change_success": "Kata sandi Anda berhasil diubah.",
+            "personal_info": "Info pribadi",
+            "profile_subtitle": "Ini adalah bagaimana Anda akan terlihat kepada orang lain dalam aplikasi.",
+            "profile_subtitle_oidc": "Akun Anda dikelola secara terpisah oleh penyedia identitas sehingga beberapa informasi pribadi Anda tidak dapat diubah di sini.",
             "remove_email_prompt": "Hapus %(email)s?",
             "remove_msisdn_prompt": "Hapus %(phone)s?",
-            "spell_check_locale_placeholder": "Pilih locale"
+            "spell_check_locale_placeholder": "Pilih locale",
+            "unable_to_load_emails": "Tidak dapat memuat alamat surel",
+            "unable_to_load_msisdns": "Tidak dapat memuat nomor telepon",
+            "username": "Nama pengguna"
         },
-        "image_thumbnails": "Tampilkan gambar mini untuk gambar",
         "inline_url_previews_default": "Aktifkan tampilan URL secara bawaan",
         "inline_url_previews_room": "Aktifkan tampilan URL secara bawaan untuk anggota di ruangan ini",
         "inline_url_previews_room_account": "Aktifkan tampilan URL secara bawaan (hanya memengaruhi Anda)",
@@ -2330,13 +2696,13 @@
                 "enter_phrase_description": "Masukkan frasa keamanan yang hanya Anda tahu, yang digunakan untuk mengamankan data Anda. Supaya aman, jangan menggunakan ulang kata sandi akun Anda.",
                 "enter_phrase_title": "Masukkan sebuah Frasa Keamanan",
                 "enter_phrase_to_confirm": "Masukkan Frasa Keamanan sekali lagi untuk mengkonfirmasinya.",
-                "generate_security_key_description": "Kami akan membuat sebuah Kunci Keamanan untuk Anda simpan di tempat yang aman, seperti manajer sandi atau brankas.",
-                "generate_security_key_title": "Buat sebuah Kunci Keamanan",
+                "generate_security_key_description": "Kami akan membuat Kunci Pemulihan untuk Anda simpan di tempat yang aman, seperti pengelola kata sandi atau brankas.",
+                "generate_security_key_title": "Buat Kunci Pemulihan",
                 "pass_phrase_match_failed": "Itu tidak cocok.",
                 "pass_phrase_match_success": "Mereka cocok!",
                 "phrase_strong_enough": "Hebat! Frasa Keamanan ini kelihatannya kuat.",
                 "secret_storage_query_failure": "Tidak dapat menanyakan status penyimpanan rahasia",
-                "security_key_safety_reminder": "Simpan Kunci Keamanan Anda di tempat yang aman, seperti manajer sandi atau sebuah brankas, yang digunakan untuk mengamankan data terenkripsi Anda.",
+                "security_key_safety_reminder": "Simpan Kunci Pemulihan Anda di tempat yang aman, seperti pengelola kata sandi atau brankas, karena digunakan untuk melindungi data terenkripsi Anda.",
                 "set_phrase_again": "Pergi kembali untuk menyiapkannya lagi.",
                 "settings_reminder": "Anda juga dapat menyiapkan Cadangan Aman & kelola kunci Anda di Pengaturan.",
                 "title_confirm_phrase": "Konfirmasi Frasa Keamanan",
@@ -2344,7 +2710,7 @@
                 "title_set_phrase": "Atur sebuah Frasa Keamanan",
                 "unable_to_setup": "Tidak dapat menyiapkan penyimpanan rahasia",
                 "use_different_passphrase": "Gunakan frasa sandi yang berbeda?",
-                "use_phrase_only_you_know": "Gunakan frasa rahasia yang hanya Anda tahu, dan simpan sebuah Kunci Keamanan untuk menggunakannya untuk cadangan secara opsional."
+                "use_phrase_only_you_know": "Gunakan frasa rahasia yang hanya Anda ketahui, dan secara opsional simpan Kunci Pemulihan untuk digunakan untuk cadangan."
             }
         },
         "key_export_import": {
@@ -2362,12 +2728,28 @@
             "phrase_strong_enough": "Hebat! Frasa keamanan ini kelihatannya kuat"
         },
         "keyboard": {
+            "dialog_title": "<strong>Pengaturan:</strong> Papan Ketik",
             "title": "Papan tik"
         },
+        "labs": {
+            "dialog_title": "<strong>Pengaturan:</strong> Uji Coba"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Pengaturan:</strong> Pengguna yang Diabaikan"
+        },
+        "media_preview": {
+            "hide_avatars": "Sembunyikan avatar ruangan dan pengundang",
+            "hide_media": "Selalu sembunyikan",
+            "media_preview_description": "Media tersembunyi selalu dapat ditampilkan dengan mengetuknya",
+            "media_preview_label": "Tampilkan media di lini masa",
+            "show_in_private": "Di ruangan privat",
+            "show_media": "Selalu tampilkan"
+        },
         "notifications": {
             "default_setting_description": "Pengaturan ini akan diterapkan secara bawaan ke semua ruangan Anda.",
             "default_setting_section": "Saya ingin diberi tahu (Pengaturan Bawaan)",
             "desktop_notification_message_preview": "Tampilkan tampilan pesan di notifikasi desktop",
+            "dialog_title": "<strong>Pengaturan:</strong> Notifikasi",
             "email_description": "Terima surel ikhtisar notifikasi yang terlewat",
             "email_section": "Kirim surel ikhtisar",
             "email_select": "Pilih surel mana yang ingin dikirimkan ikhtisar. Kelola surel Anda di <button>Umum</button>.",
@@ -2426,12 +2808,15 @@
             "code_blocks_heading": "Blok kode",
             "compact_modern": "Gunakan tata letak 'Modern' yang lebih kecil",
             "composer_heading": "Komposer",
+            "default_timezone": "Bawaan peramban (%(timezone)s)",
+            "dialog_title": "<strong>Pengaturan:</strong> Preferensi",
             "enable_hardware_acceleration": "Aktifkan akselerasi perangkat keras",
             "enable_tray_icon": "Tampilkan ikon baki dan minimalkan window ke ikonnya jika ditutup",
             "keyboard_heading": "Pintasan keyboard",
             "keyboard_view_shortcuts_button": "Untuk melihat semua shortcut keyboard, <a>klik di sini</a>.",
             "media_heading": "Gambar, GIF, dan video",
             "presence_description": "Bagikan aktivitas dan status Anda dengan orang lain.",
+            "publish_timezone": "Terbitkan zona waktu di profil publik",
             "rm_lifetime": "Delay Penanda Bacaan (md)",
             "rm_lifetime_offscreen": "Delay Penanda Bacaan diluar layar (md)",
             "room_directory_heading": "Direktori ruangan",
@@ -2439,59 +2824,26 @@
             "show_avatars_pills": "Tampilkan avatar di sebutan pengguna, ruangan, dan peristiwa",
             "show_polls_button": "Tampilkan tombol pemungutan suara",
             "surround_text": "Kelilingi teks yang dipilih saat mengetik karakter khusus",
-            "time_heading": "Tampilkan waktu"
+            "time_heading": "Tampilkan waktu",
+            "user_timezone": "Atur zona waktu"
         },
         "prompt_invite": "Tanyakan sebelum mengirim undangan ke ID Matrix yang mungkin tidak absah",
         "replace_plain_emoji": "Ganti emoji teks biasa secara otomatis",
         "security": {
-            "4s_public_key_in_account_data": "di data akun",
-            "4s_public_key_status": "Kunci publik penyimpanan rahasia:",
             "analytics_description": "Bagikan data anonim untuk membantu kami mengenal masalah. Tidak ada yang pribadi. Tanpa pihak ketiga.",
-            "backup_key_cached_status": "Cadangan kunci dicache:",
-            "backup_key_stored_status": "Cadangan kunci disimpan:",
-            "backup_key_unexpected_type": "tipe yang tidak terduga",
-            "backup_key_well_formed": "terbentuk dengan baik",
-            "backup_keys_description": "Cadangkan kunci enkripsi Anda dengan data akun Anda jika Anda kehilangan akses ke sesi-sesi Anda. Kunci Anda akan diamankan dengan Kunci Keamanan yang unik.",
             "bulk_options_accept_all_invites": "Terima semua %(invitedRooms)s undangan",
             "bulk_options_reject_all_invites": "Tolak semua %(invitedRooms)s undangan",
             "bulk_options_section": "Opsi massal",
-            "cross_signing_cached": "dicache secara lokal",
-            "cross_signing_homeserver_support": "Dukungan fitur homeserver:",
-            "cross_signing_homeserver_support_exists": "sudah ada",
-            "cross_signing_in_4s": "di penyimpanan rahasia",
-            "cross_signing_in_memory": "di penyimpanan",
-            "cross_signing_master_private_Key": "Kunci privat utama:",
-            "cross_signing_not_cached": "tidak ditemukan secara lokal",
-            "cross_signing_not_found": "tidak ditemukan",
-            "cross_signing_not_in_4s": "tidak ditemukan di penyimpanan",
-            "cross_signing_not_stored": "tidak disimpan",
-            "cross_signing_private_keys": "Kunci privat penandatanganan silang:",
-            "cross_signing_public_keys": "Kunci publik penandatanganan silang:",
-            "cross_signing_self_signing_private_key": "Kunci privat penandatanganan diri:",
-            "cross_signing_user_signing_private_key": "Kunci rahasia penandatanganan pengguna:",
-            "cryptography_section": "Kriptografi",
-            "delete_backup": "Hapus Cadangan",
-            "delete_backup_confirm_description": "Apakah Anda yakin? Anda akan kehilangan pesan terenkripsi jika kunci Anda tidak dicadangkan dengan benar.",
+            "dehydrated_device_description": "Fitur perangkat luring memungkinkan Anda menerima pesan terenkripsi bahkan ketika Anda tidak masuk ke perangkat apa pun",
+            "dehydrated_device_enabled": "Perangkat luring diaktifkan",
+            "dialog_title": "<strong>Pengaturan:</strong> Keamanan & Privasi",
             "e2ee_default_disabled_warning": "Admin server Anda telah menonaktifkan enkripsi ujung ke ujung secara bawaan di ruangan privat & Pesan Langsung.",
             "enable_message_search": "Aktifkan pencarian pesan di ruangan terenkripsi",
             "encryption_section": "Enkripsi",
-            "error_loading_key_backup_status": "Tidak dapat memuat status pencadangan kunci",
-            "export_megolm_keys": "Ekspor kunci ruangan enkripsi ujung ke ujung",
             "ignore_users_empty": "Anda tidak memiliki pengguna yang diabaikan.",
             "ignore_users_section": "Pengguna yang diabaikan",
-            "import_megolm_keys": "Impor kunci enkripsi ujung ke ujung",
-            "key_backup_active": "Sesi ini mencadangkan kunci Anda.",
-            "key_backup_active_version": "Versi cadangan aktif:",
-            "key_backup_active_version_none": "Tidak Ada",
             "key_backup_algorithm": "Algoritma:",
-            "key_backup_can_be_restored": "Cadangan ini dapat dipulihkan di sesi ini",
-            "key_backup_complete": "Semua kunci telah dicadangkan",
             "key_backup_connect": "Hubungkan sesi ini ke Pencadangan Kunci",
-            "key_backup_connect_prompt": "Hubungkan sesi ini ke pencadangan kunci sebelum keluar untuk menghindari kehilangan kunci apa saja yang mungkin hanya ada di sesi ini.",
-            "key_backup_in_progress": "Mencadangkan %(sessionsRemaining)s kunci…",
-            "key_backup_inactive": "Sesi ini <b>tidak mencadangkan kunci Anda</b>, tetapi Anda memiliki cadangan yang ada yang dapat Anda pulihkan dan tambahkan untuk selanjutnya.",
-            "key_backup_inactive_warning": "Kunci Anda <b>tidak dicadangan dari sesi ini</b>.",
-            "key_backup_latest_version": "Versi cadangan terbaru di server:",
             "message_search_disable_warning": "Jika dinonaktifkan, pesan dari ruangan terenkripsi tidak akan muncul di hasil pencarian.",
             "message_search_disabled": "Simpan pesan terenkripsi secara lokal dengan aman agar muncul di hasil pencarian.",
             "message_search_enabled": {
@@ -2511,14 +2863,8 @@
             "message_search_unsupported": "%(brand)s tidak memiliki beberapa komponen yang diperlukan untuk menyimpan pesan terenkripsi secara lokal dengan aman. Jika Anda ingin bereksperimen dengan fitur ini, buat %(brand)s Desktop yang khusus dengan <nativeLink>tambahan komponen penelusuran</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s tidak dapat menyimpan pesan terenkripsi secara lokal dengan aman saat dijalankan di browser. Gunakan <desktopLink>%(brand)s Desktop</desktopLink> supaya pesan terenkripsi dapat muncul di hasil pencarian.",
             "record_session_details": "Rekam nama, versi, dan URL klien untuk dapat mengenal sesi dengan lebih mudah dalam pengelola sesi",
-            "restore_key_backup": "Pulihkan dari Cadangan",
-            "secret_storage_not_ready": "belum siap",
-            "secret_storage_ready": "siap",
-            "secret_storage_status": "Penyimpanan rahasia:",
             "send_analytics": "Kirim data analitik",
-            "session_id": "ID Sesi:",
-            "session_key": "Kunci sesi:",
-            "strict_encryption": "Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi dari sesi ini"
+            "strict_encryption": "Hanya kirim pesan ke pengguna terverifikasi"
         },
         "send_read_receipts": "Kirim laporan dibaca",
         "send_read_receipts_unsupported": "Server Anda tidak mendukung penonaktifkan pengiriman laporan dibaca.",
@@ -2549,6 +2895,7 @@
             "device_unverified_description_current": "Verifikasi sesi Anda saat ini untuk perpesanan aman yang ditingkatkan.",
             "device_verified_description": "Sesi ini siap untuk perpesanan yang aman.",
             "device_verified_description_current": "Sesi Anda saat ini siap untuk perpesanan aman.",
+            "dialog_title": "<strong>Pengaturan:</strong> Sesi",
             "error_pusher_state": "Gagal menetapkan keadaan pendorong",
             "error_set_name": "Gagal mengatur nama sesi",
             "filter_all": "Semua",
@@ -2565,6 +2912,7 @@
             "inactive_sessions_list_description": "Pertimbangkan untuk mengeluarkan sesi lama (%(inactiveAgeDays)s hari atau lebih) yang Anda tidak gunakan lagi.",
             "ip": "Alamat IP",
             "last_activity": "Aktivitas terakhir",
+            "manage": "Kelola sesi ini",
             "mobile_session": "Sesi ponsel",
             "n_sessions_selected": {
                 "one": "%(count)s sesi dipilih",
@@ -2588,9 +2936,10 @@
             "security_recommendations_description": "Tingkatkan keamanan akun Anda dengan mengikuti saran berikut.",
             "session_id": "ID Sesi",
             "show_details": "Tampilkan detail",
-            "sign_in_with_qr": "Masuk dengan kode QR",
+            "sign_in_with_qr": "Tautkan perangkat baru",
             "sign_in_with_qr_button": "Tampilkan kode QR",
-            "sign_in_with_qr_description": "Anda dapat menggunakan perangkat ini untuk masuk ke perangkat yang baru dengan sebuah kode QR. Anda harus memindai kode QR yang ditampilkan di perangkat ini dengan perangkat Anda yang telah keluar dari akun.",
+            "sign_in_with_qr_description": "Gunakan kode QR untuk masuk ke akun di perangkat lain dan menyiapkan perpesanan aman.",
+            "sign_in_with_qr_unsupported": "Tidak didukung oleh penyedia akun Anda",
             "sign_out": "Keluarkan sesi ini",
             "sign_out_all_other_sessions": "Keluar dari semua sesi lain (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2630,7 +2979,9 @@
         "show_redaction_placeholder": "Tampilkan sebuah penampung untuk pesan terhapus",
         "show_stickers_button": "Tampilkan tombol stiker",
         "show_typing_notifications": "Tampilkan notifikasi pengetikan",
+        "showbold": "Tampilkan semua aktivitas dalam daftar ruangan (titik atau jumlah pesan belum dibaca)",
         "sidebar": {
+            "dialog_title": "<strong>Pengaturan:</strong> Bilah Samping",
             "metaspaces_favourites_description": "Kelompokkan semua ruangan dan orang favorit Anda di satu tempat.",
             "metaspaces_home_all_rooms": "Tampilkan semua ruangan",
             "metaspaces_home_all_rooms_description": "Tampilkan semua ruangan di Beranda, walaupun mereka berada di sebuah space.",
@@ -2639,10 +2990,14 @@
             "metaspaces_orphans_description": "Kelompokkan semua ruangan yang tidak ada di sebuah space di satu tempat.",
             "metaspaces_people_description": "Kelompokkan semua orang di satu tempat.",
             "metaspaces_subsection": "Space yang ditampilkan",
+            "metaspaces_video_rooms": "Ruangan dan konferensi video",
+            "metaspaces_video_rooms_description": "Kelompokkan semua ruangan dan konferensi video privat.",
+            "metaspaces_video_rooms_description_invite_extension": "Dalam konferensi, Anda dapat mengundang orang-orang di luar Matrix.",
             "spaces_explainer": "Space adalah cara untuk mengelompokkan ruangan dan orang-orang. Di samping space yang Anda berada, Anda juga dapat menggunakan beberapa yang sudah dibuat sebelumnya.",
             "title": "Bilah Samping"
         },
         "start_automatically": "Mulai setelah login sistem secara otomatis",
+        "tac_only_notifications": "Hanya tampilkan notifikasi dalam pusat aktivitas utas",
         "use_12_hour_format": "Tampilkan stempel waktu dalam format 12 jam (mis. 2:30pm)",
         "use_command_enter_send_message": "Gunakan ⌘ + Enter untuk mengirim pesan",
         "use_command_f_search": "Gunakan ⌘ + F untuk cari di lini masa",
@@ -2656,6 +3011,7 @@
             "audio_output_empty": "Tidak ada output audio yang terdeteksi",
             "auto_gain_control": "Kendali suara otomatis",
             "connection_section": "Koneksi",
+            "dialog_title": "<strong>Pengaturan:</strong> Suara & Video",
             "echo_cancellation": "Pembatalan gema",
             "enable_fallback_ice_server": "Perbolehkan server bantuan panggilan cadangan (%(server)s)",
             "enable_fallback_ice_server_description": "Hanya diterapkan jika homeserver Anda tidak menyediakan satu. Alamat IP Anda akan dibagikan selama panggilan berlangsung.",
@@ -2674,8 +3030,12 @@
         "warning": "<w>PERINGATAN:</w> <description/>"
     },
     "share": {
+        "link_copied": "Tautan disalin",
         "permalink_message": "Tautan ke pesan yang dipilih",
         "permalink_most_recent": "Tautan ke pesan terkini",
+        "share_call": "Tautan undangan konferensi",
+        "share_call_subtitle": "Tautan bagi pengguna eksternal untuk bergabung ke panggilan tanpa akun Matrix:",
+        "title_link": "Bagikan Tautan",
         "title_message": "Bagikan Pesan Ruangan",
         "title_room": "Bagikan Ruangan",
         "title_user": "Bagikan Pengguna"
@@ -2746,8 +3106,6 @@
         "topic": "Mendapatkan atau mengatur topik ruangan",
         "topic_none": "Ruangan ini tidak ada topik.",
         "topic_room_error": "Gagal untuk mendapatkan topik ruangan: Tidak dapat menemukan ruangan (%(roomId)s)",
-        "tovirtual": "Mengganti ke ruangan virtual ruangan ini, jika tersedia",
-        "tovirtual_not_found": "Tidak ada ruangan virtual untuk ruangan ini",
         "unban": "Menhilangkan cekalan pengguna dengan ID yang dicantumkan",
         "unflip": "Menambahkan ┬──┬ ノ( ゜-゜ノ) ke pesan teks biasa",
         "unholdcall": "Melanjutkan panggilan di ruang saat ini",
@@ -2765,6 +3123,7 @@
         "view": "Menampilkan ruangan dengan alamat yang ditentukan",
         "whois": "Menampilkan informasi tentang sebuah pengguna"
     },
+    "sliding_sync_legacy_no_longer_supported": "Sinkronisasi geser lama tidak lagi didukung: silakan keluar dan masuk kembali untuk mengaktifkan bendera sinkronisasi geser yang baru",
     "space": {
         "add_existing_room_space": {
             "create": "Ingin menambahkan sebuah ruangan yang baru saja?",
@@ -2863,21 +3222,22 @@
         },
         "create_new_room_button": "Buat ruangan baru",
         "failed_querying_public_rooms": "Gagal melakukan kueri ruangan publik",
+        "failed_querying_public_spaces": "Gagal melakukan kueri space publik",
         "group_chat_section_title": "Opsi lain",
         "heading_with_query": "Gunakan \"%(query)s\" untuk mencari",
         "heading_without_query": "Cari",
         "join_button_text": "Bergabung dengan %(roomAddress)s",
         "keyboard_scroll_hint": "Gunakan <arrows/> untuk menggulirkan",
-        "message_search_section_title": "Pencarian lainnya",
+        "messages_label": "Pesan",
         "other_rooms_in_space": "Ruangan lainnya di %(spaceName)s",
         "public_rooms_label": "Ruangan publik",
+        "public_spaces_label": "Space publik",
         "recent_searches_section_title": "Pencarian terkini",
         "recently_viewed_section_title": "Baru saja dilihat",
         "remove_filter": "Hapus saringan pencarian untuk %(filter)s",
         "result_may_be_hidden_privacy_warning": "Beberapa hasil mungkin disembunyikan untuk privasi",
         "result_may_be_hidden_warning": "Beberapa hasil mungkin tersembunyi",
         "search_dialog": "Dialog Pencarian",
-        "search_messages_hint": "Untuk mencari pesan-pesan, lihat ikon ini di atas ruangan <icon/>",
         "spaces_title": "Space yang Anda berada",
         "start_group_chat_button": "Mulai sebuah grup obrolan"
     },
@@ -2914,12 +3274,20 @@
             "one": "%(count)s balasan",
             "other": "%(count)s balasan"
         },
+        "empty_description": "Gunakan “%(replyInThread)s” ketika berada di atas pesan.",
+        "empty_title": "Utas membantu percakapan Anda sesuai dengan topik dan mudah untuk dilacak.",
         "error_start_thread_existing_relation": "Tidak dapat membuat utasan dari sebuah peristiwa dengan relasi yang sudah ada",
+        "mark_all_read": "Tandai semua sebagai dibaca",
         "my_threads": "Utasan saya",
         "my_threads_description": "Menampilkan semua utasan yang Anda berpartisipasi",
         "open_thread": "Buka utasan",
         "show_thread_filter": "Tampilkan:"
     },
+    "threads_activity_centre": {
+        "header": "Aktivitas utas",
+        "no_rooms_with_threads_notifs": "Anda belum memiliki ruangan dengan notifikasi utas.",
+        "no_rooms_with_unread_threads": "Anda belum memiliki ruangan dengan utas yang belum dibaca."
+    },
     "time": {
         "about_day_ago": "1 hari yang lalu",
         "about_hour_ago": "1 jam yang lalu",
@@ -2961,9 +3329,21 @@
         },
         "creation_summary_dm": "%(creator)s membuat pesan langsung ini.",
         "creation_summary_room": "%(creator)s membuat dan mengatur ruangan ini.",
+        "decryption_failure": {
+            "blocked": "Pengirim telah mencegah Anda menerima pesan ini",
+            "historical_event_no_key_backup": "Riwayat pesan tidak tersedia di perangkat ini",
+            "historical_event_unverified_device": "Anda harus memverifikasi perangkat ini untuk mengakses riwayat pesan",
+            "historical_event_user_not_joined": "Anda tidak memiliki akses ke pesan ini",
+            "sender_identity_previously_verified": "Identitas terverifikasi telah berubah",
+            "sender_unsigned_device": "Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya.",
+            "unable_to_decrypt": "Tidak dapat mendekripsi pesan"
+        },
         "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Mendekripsi",
         "download_action_downloading": "Mengunduh",
+        "download_failed": "Pengunduhan gagal",
+        "download_failed_description": "Terjadi kesalahan saat mengunduh berkas ini",
+        "e2e_state": "Keadaan enkripsi ujung ke ujung",
         "edits": {
             "tooltip_label": "Diedit di %(date)s. Klik untuk melihat editan.",
             "tooltip_sub": "Klik untuk melihat editan",
@@ -2974,6 +3354,7 @@
         "historical_messages_unavailable": "Anda tidak dapat melihat pesan-pesan awal",
         "in_room_name": " di <strong>%(room)s</strong>",
         "io.element.widgets.layout": "%(senderName)s telah memperbarui tata letak ruangan",
+        "late_event_separator": "Awalnya dikirim %(dateTime)s",
         "load_error": {
             "no_permission": "Mencoba memuat titik spesifik di lini masa ruangan ini, tetapi Anda tidak memiliki izin untuk menampilkan pesannya.",
             "title": "Gagal untuk memuat posisi lini masa",
@@ -3016,7 +3397,7 @@
         },
         "m.file": {
             "error_decrypting": "Terjadi kesalahan mendekripsi lampiran",
-            "error_invalid": "File tidak absah%(extra)s"
+            "error_invalid": "Berkas tidak valid"
         },
         "m.image": {
             "error": "Tidak dapat menampilkan gambar karena kesalahan",
@@ -3117,6 +3498,7 @@
             "left_reason": "%(targetName)s keluar dari ruangan ini: %(reason)s",
             "no_change": "%(senderName)s tidak membuat perubahan",
             "reject_invite": "%(targetName)s menolak undangannya",
+            "reject_invite_reason": "%(targetName)s menolak undangan: %(reason)s",
             "remove_avatar": "%(senderName)s menghilangkan foto profilnya",
             "remove_name": "%(senderName)s menghilangkan nama tampilannya (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s mengatur foto profil",
@@ -3152,10 +3534,14 @@
             "sent": "%(senderName)s mengirim sebuah undangan ke %(targetDisplayName)s untuk bergabung dengan ruangan ini."
         },
         "m.room.tombstone": "%(senderDisplayName)s meningkatkan ruangan ini.",
-        "m.room.topic": "%(senderDisplayName)s telah mengubah topik menjadi \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s telah mengubah topik menjadi \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s menghapus topik."
+        },
         "m.sticker": "%(senderDisplayName)s mengirim sebuah stiker.",
         "m.video": {
-            "error_decrypting": "Terjadi kesalahan mendekripsi video"
+            "error_decrypting": "Terjadi kesalahan mendekripsi video",
+            "show_video": "Tampilkan video"
         },
         "m.widget": {
             "added": "Widget %(widgetName)s ditambahkan oleh %(senderName)s",
@@ -3174,6 +3560,8 @@
             "label": "Aksi Pesan",
             "view_in_room": "Tampilkan di ruangan"
         },
+        "message_timestamp_received_at": "Diterima pada: %(dateTime)s",
+        "message_timestamp_sent_at": "Dikirim pada: %(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s memperbarui sebuah peraturan pencekalan yang sebelumnya berisi %(oldGlob)s ke %(newGlob)s untuk %(reason)s",
             "changed_rule_rooms": "%(senderName)s mengubah sebuah peraturan pencekalan ruangan yang sebelumnya berisi %(oldGlob)s ke %(newGlob)s untuk %(reason)s",
@@ -3201,7 +3589,8 @@
         "reactions": {
             "add_reaction_prompt": "Tambahkan reaksi",
             "custom_reaction_fallback_label": "Reaksi khusus",
-            "label": "%(reactors)s berekasi dengan %(content)s"
+            "label": "%(reactors)s berekasi dengan %(content)s",
+            "tooltip_caption": "bereaksi dengan %(shortName)s"
         },
         "read_receipt_title": {
             "one": "Dilihat oleh %(count)s orang",
@@ -3386,6 +3775,10 @@
     "truncated_list_n_more": {
         "other": "Dan %(count)s lagi..."
     },
+    "unsupported_browser": {
+        "description": "Jika Anda lanjut, beberapa fitur dapat berhenti bekerja dan ada risiko kehilangan data di masa mendatang. Perbarui peramban Anda untuk terus menggunakan %(brand)s.",
+        "title": "%(brand)s tidak mendukung peramban ini"
+    },
     "unsupported_server_description": "Server ini menjalankan sebuah versi Matrix yang lama. Tingkatkan ke Matrix %(version)s untuk menggunakan %(brand)s tanpa eror.",
     "unsupported_server_title": "Server Anda tidak didukung",
     "update": {
@@ -3403,6 +3796,13 @@
         "toast_title": "Perbarui %(brand)s",
         "unavailable": "Tidak Tersedia"
     },
+    "update_room_access_modal": {
+        "description": "Untuk membuat tautan berbagi, jadikan ruangan ini <b>publik</b> atau aktifkan opsi bagi pengguna untuk <b>meminta bergabung</b>. Hal ini memungkinkan tamu untuk bergabung tanpa diundang.",
+        "dont_change_description": "Jika Anda tidak ingin mengubah akses ruangan ini, Anda dapat membuat ruang baru untuk tautan panggilan.",
+        "no_change": "Saya tidak ingin mengubah tingkat akses.",
+        "revert_access_description": "(Ini dapat dikembalikan ke nilai sebelumnya di Pengaturan Ruangan: <b>Keamanan & Privasi</b> / <b>Akses</b>)",
+        "title": "Izinkan pengguna tamu untuk bergabung dengan ruangan ini"
+    },
     "upload_failed_generic": "File '%(fileName)s' gagal untuk diunggah.",
     "upload_failed_size": "File '%(fileName)s' melebihi batas ukuran unggahan file homeserver",
     "upload_failed_title": "Unggahan Gagal",
@@ -3412,6 +3812,7 @@
         "error_files_too_large": "File-file ini <b>terlalu besar</b> untuk diunggah. Batas ukuran unggahan file adalah %(limit)s.",
         "error_some_files_too_large": "Beberapa file <b>terlalu besar</b> untuk diunggah. Batas ukuran unggahan file adalah %(limit)s.",
         "error_title": "Kesalahan saat Mengunggah",
+        "not_image": "Berkas yang Anda pilih bukan berkas gambar yang valid.",
         "title": "Unggah file",
         "title_progress": "Mengunggah file (%(current)s dari %(total)s)",
         "upload_all_button": "Unggah semua",
@@ -3427,14 +3828,6 @@
         "ban_room_confirm_title": "Cekal dari %(roomName)s",
         "ban_space_everything": "Cekal dari semuanya yang saya dapat melakukan",
         "ban_space_specific": "Cekal dari beberapa hal yang saya dapat melakukan",
-        "count_of_sessions": {
-            "one": "%(count)s sesi",
-            "other": "%(count)s sesi"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 sesi terverifikasi",
-            "other": "%(count)s sesi terverifikasi"
-        },
         "deactivate_confirm_action": "Nonaktifkan pengguna",
         "deactivate_confirm_description": "Menonaktifkan pengguna ini akan mengeluarkan dan mencegahnya masuk ke akun lagi. Pengguna itu juga akan meninggalkan semua ruangan yang pengguna itu berada. Aksi ini tidak dapat dibatalkan. Apakah Anda yakin Anda ingin menonaktifkan pengguna ini?",
         "deactivate_confirm_title": "Nonaktifkan pengguna?",
@@ -3445,15 +3838,13 @@
         "disinvite_button_room": "Batalkan undangan dari ruangan",
         "disinvite_button_room_name": "Batalkan pengundangan dari %(roomName)s",
         "disinvite_button_space": "Batalkan undangan dari space",
-        "edit_own_devices": "Edit perangkat",
         "error_ban_user": "Gagal untuk mencekal pengguna",
         "error_deactivate": "Gagal untuk menonaktifkan pengguna",
         "error_kicking_user": "Gagal untuk mengeluarkan pengguna",
         "error_mute_user": "Gagal untuk membisukan pengguna",
         "error_revoke_3pid_invite_description": "Tidak dapat menghapus undangan. Server ini mungkin mengalami masalah sementara atau Anda tidak memiliki izin yang dibutuhkan untuk menghapus undangannya.",
         "error_revoke_3pid_invite_title": "Gagal untuk menghapus undangan",
-        "hide_sessions": "Sembunyikan sesi",
-        "hide_verified_sessions": "Sembunyikan sesi terverifikasi",
+        "ignore_button": "Abaikan",
         "ignore_confirm_description": "Semua pesan dan undangan dari pengguna ini akan disembunyikan. Apakah Anda yakin ingin mengabaikan?",
         "ignore_confirm_title": "Abaikan %(user)s",
         "invited_by": "Diundang oleh %(sender)s",
@@ -3481,23 +3872,27 @@
             "no_recent_messages_description": "Coba gulir ke atas di lini masa untuk melihat apa ada pesan-pesan sebelumnya.",
             "no_recent_messages_title": "Tidak ada pesan terkini dari %(user)s yang ditemukan"
         },
-        "redact_button": "Hapus pesan terkini",
+        "redact_button": "Hapus pesan",
         "revoke_invite": "Hapus undangan",
         "room_encrypted": "Pesan di ruangan ini terenkripsi secara ujung ke ujung.",
         "room_encrypted_detail": "Pesan Anda diamankan dan hanya Anda dan penerimanya mempunyai kunci yang unik untuk mengaksesnya.",
         "room_unencrypted": "Pesan di ruangan ini tidak dienkripsi secara ujung ke ujung.",
         "room_unencrypted_detail": "Di ruangan terenkripsi, pesan Anda diamankan dan hanya Anda dan penerimanya mempunyai kunci yang unik untuk mengaksesnya.",
-        "share_button": "Bagikan Tautan ke Pengguna",
+        "send_message": "Kirim pesan",
+        "share_button": "Bagikan profil",
         "unban_button_room": "Batalkan cekalan dari ruangan",
         "unban_button_space": "Batalkan cekalan dari space",
         "unban_room_confirm_title": "Batalkan cekalan dari %(roomName)s",
         "unban_space_everything": "Batalkan pencekalan dari semuanya yang saya dapat melakukan",
         "unban_space_specific": "Batalkan pencekalan dari beberapa hal yang saya dapat melakukan",
         "unban_space_warning": "Mereka tidak dapat mengakses apa saja yang Anda bukan admin di sana.",
+        "unignore_button": "Batalkan pengabaian",
+        "verification_unavailable": "Verifikasi pengguna tidak tersedia",
         "verify_button": "Verifikasi Pengguna",
         "verify_explainer": "Untuk keamanan lebih, verifikasi pengguna ini dengan memeriksa kode satu kali di kedua perangkat Anda."
     },
     "user_menu": {
+        "link_new_device": "Tautkan perangkat baru",
         "settings": "Semua pengaturan",
         "switch_theme_dark": "Ubah ke mode gelap",
         "switch_theme_light": "Ubah ke mode terang"
@@ -3521,6 +3916,7 @@
         "camera_disabled": "Kamera Anda dimatikan",
         "camera_enabled": "Kamera Anda masih nyala",
         "cannot_call_yourself_description": "Anda tidak dapat melakukan panggilan dengan diri sendiri.",
+        "close_lobby": "Tutup lobi",
         "connecting": "Menghubungkan",
         "connection_lost": "Koneksi ke server telah hilang",
         "connection_lost_description": "Anda tidak dapat membuat panggilan tanpa terhubung ke server.",
@@ -3534,15 +3930,23 @@
         "disabled_no_perms_start_video_call": "Anda tidak memiliki izin untuk memulai panggilan video",
         "disabled_no_perms_start_voice_call": "Anda tidak memiliki izin untuk memulai panggilan suara",
         "disabled_ongoing_call": "Panggilan sedang berlangsung",
+        "element_call": "Element Call",
         "enable_camera": "Nyalakan kamera",
         "enable_microphone": "Suarakan mikrofon",
         "expand": "Kembali ke panggilan",
+        "get_call_link": "Bagikan tautan panggilan",
         "hangup": "Akhiri",
         "hide_sidebar_button": "Sembunyikan sisi bilah",
         "input_devices": "Perangkat masukan",
+        "jitsi_call": "Konferensi Jitsi",
         "join_button_tooltip_call_full": "Maaf — panggilan ini saat ini penuh",
-        "join_button_tooltip_connecting": "Menghubungkan",
+        "legacy_call": "Panggilan Lawas",
         "maximise": "Penuhi layar",
+        "maximise_call": "Maksimalkan panggilan",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferensi"
+        },
+        "minimise_call": "Minimalkan panggilan",
         "misconfigured_server": "Panggilan gagal karena servernya tidak dikonfigurasi dengan benar",
         "misconfigured_server_description": "Mohon tanyakan ke administrator homeserver Anda (<code>%(homeserverDomain)s</code>) untuk mengkonfigurasikan server TURN supaya panggilan dapat bekerja dengan benar.",
         "misconfigured_server_fallback": "Secara alternatif, Anda dapat menggunakan server publik di <server/>, tetapi ini tidak akan selalu tersedia, dan akan membagikan alamat IP Anda dengan server itu. Anda juga dapat mengelola ini di Pengaturan.",
@@ -3590,6 +3994,7 @@
         "user_is_presenting": "%(sharerName)s sedang mempresentasi",
         "video_call": "Panggilan video",
         "video_call_started": "Panggilan video dimulai",
+        "video_call_using": "Panggilan video menggunakan:",
         "voice_call": "Panggilan suara",
         "you_are_presenting": "Anda sedang mempresentasi"
     },
@@ -3689,7 +4094,7 @@
         "error_need_to_be_logged_in": "Anda harus masuk.",
         "error_unable_start_audio_stream_description": "Tidak dapat memulai penyiaran audio.",
         "error_unable_start_audio_stream_title": "Gagal untuk memulai siaran langsung",
-        "modal_data_warning": "Data di layar ini dibagikan dengan %(widgetDomain)s",
+        "modal_data_warning": "Data di bawah ini dibagikan dengan %(widgetDomain)s",
         "modal_title_default": "Widget Modal",
         "no_name": "Aplikasi Tidak Diketahui",
         "open_id_permissions_dialog": {
@@ -3698,7 +4103,7 @@
             "title": "Izinkan widget ini untuk memverifikasi identitas Anda"
         },
         "popout": "Widget popout",
-        "set_room_layout": "Tetapkan tata letak ruangan saya untuk semuanya",
+        "set_room_layout": "Tetapkan tata letak untuk semua orang",
         "shared_data_avatar": "URL foto profil Anda",
         "shared_data_device_id": "ID perangkat Anda",
         "shared_data_lang": "Bahasa Anda",
diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json
index e1695b2cc067a56fbdbe4979248a65fae8c4354e..f57cdf156e2dc31ce7c8a21cdd125c52a9e14c8a 100644
--- a/src/i18n/strings/is.json
+++ b/src/i18n/strings/is.json
@@ -86,7 +86,6 @@
         "react": "Bregðast við",
         "refresh": "Endurlesa",
         "register": "Nýskrá",
-        "reject": "Hafna",
         "remove": "Fjarlægja",
         "rename": "Endurnefna",
         "reply": "Svara",
@@ -316,7 +315,6 @@
         "description": "Atvikaskrár innihalda gögn varðandi virkni hugbúnaðarins en líka notandanafn þitt, auðkenni eða samnefni spjallrása sem þú hefur skoðað, hvaða viðmótshluta þú hefur átt við, auk notendanafna annarra notenda. Atvikaskrár innihalda ekki skilaboð.",
         "download_logs": "Niðurhal atvikaskrá",
         "downloading_logs": "Sæki atvikaskrá",
-        "failed_send_logs": "Mistókst að senda atvikaskrár: ",
         "github_issue": "Villutilkynning á GitHub",
         "introduction": "Ef þú hefur tilkynnt vandamál í gegnum GitHub, þá geta atvikaskrár hjálpað okkur við að finna ástæður vandamálanna. ",
         "logs_sent": "Sendi atvikaskrár",
@@ -353,7 +351,6 @@
         "access_token": "Aðgangsteikn",
         "accessibility": "Auðveldað aðgengi",
         "advanced": "Nánar",
-        "all_rooms": "Allar spjallrásir",
         "analytics": "Greiningar",
         "and_n_others": {
             "one": "og einn í viðbót...",
@@ -371,7 +368,6 @@
         "capabilities": "Geta",
         "copied": "Afritað!",
         "credits": "Framlög",
-        "cross_signing": "Kross-undirritun",
         "dark": "Dökkt",
         "description": "Lýsing",
         "deselect_all": "Afvelja allt",
@@ -447,7 +443,6 @@
         "room_name": "Heiti spjallrásar",
         "rooms": "Spjallrásir",
         "secure_backup": "Varið öryggisafrit",
-        "security": "Öryggi",
         "select_all": "Velja allt",
         "server": "Netþjónn",
         "settings": "Stillingar",
@@ -466,7 +461,6 @@
         "thread": "Spjallþráður",
         "threads": "Spjallþræðir",
         "timeline": "Tímalína",
-        "trusted": "Treyst",
         "unencrypted": "Ódulritað",
         "unmute": "Ekki þagga",
         "unnamed_room": "Nafnlaus spjallrás",
@@ -679,38 +673,22 @@
     "encryption": {
         "access_secret_storage_dialog": {
             "key_validation_text": {
-                "invalid_security_key": "Ógildur öryggislykill",
-                "recovery_key_is_correct": "Lítur vel út!",
-                "wrong_file_type": "Röng skráartegund",
                 "wrong_security_key": "Rangur öryggislykill"
             },
-            "reset_title": "Frumstilla allt",
             "restoring": "Endurheimti lykla úr öryggisafriti",
-            "security_key_title": "Öryggislykill",
-            "security_phrase_title": "Öryggisfrasi",
-            "separator": "%(securityKey)s eða %(recoveryFile)s",
-            "use_security_key_prompt": "Notaðu öryggislykilinn þinn til að halda áfram."
+            "security_key_title": "Öryggislykill"
         },
         "bootstrap_title": "Set upp dulritunarlykla",
         "cancel_entering_passphrase_description": "Viltu örugglega hætta við að setja inn lykilfrasa?",
         "cancel_entering_passphrase_title": "Hætta við að setja inn lykilfrasa?",
         "confirm_encryption_setup_body": "Smelltu á hnappinn hér að neðan til að staðfesta uppsetningu á dulritun.",
         "confirm_encryption_setup_title": "Staðfestu uppsetningu dulritunar",
-        "cross_signing_not_ready": "Kross-undirritun er ekki uppsett.",
-        "cross_signing_ready": "Kross-undirritun er tilbúin til notkunar.",
-        "cross_signing_ready_no_backup": "Kross-undirritun er tilbúin en ekki er búið að öryggisafrita dulritunarlykla.",
         "cross_signing_room_normal": "Þessi spjallrás er enda-í-enda dulrituð",
         "cross_signing_room_verified": "Allir á þessari spjallrás eru staðfestir",
         "cross_signing_room_warning": "Einhver er að nota óþekkta setu",
-        "cross_signing_unsupported": "Heimaþjónninn þinn styður ekki kross-undirritun.",
-        "cross_signing_untrusted": "Aðgangurinn þinn er með auðkenni kross-undirritunar í leynigeymslu, en þessu er ekki ennþá treyst í þessari setu.",
         "cross_signing_user_normal": "Þér hefur ekki sannreynt þennan notanda.",
         "cross_signing_user_verified": "Þú hefur sannreynt þennan notanda. Þessi notandi hefur sannreynt öll tæki þeirra.",
         "cross_signing_user_warning": "Þessi notandi hefur ekki sannreynt öll tæki þeirra.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Hreinsa kross-undirritunarlykla",
-            "title": "Eyða kross-undirritunarlyklum?"
-        },
         "event_shield_reason_mismatched_sender_key": "Dulritað meðf ósannreyndu tæki",
         "export_unsupported": "Vafrinn þinn styður ekki nauðsynlegar dulritunarviðbætur",
         "import_invalid_keyfile": "Er ekki gild %(brand)s lykilskrá",
@@ -723,7 +701,6 @@
         "new_recovery_method_detected": {
             "title": "Ný endurheimtuaðferð"
         },
-        "not_supported": "<ekki stutt>",
         "recovery_method_removed": {
             "title": "Endurheimtuaðferð fjarlægð"
         },
@@ -731,8 +708,7 @@
         "set_up_toast_description": "Tryggðu þig gegn því að missa aðgang að dulrituðum skilaboðum og gögnum",
         "set_up_toast_title": "Setja upp varið öryggisafrit",
         "setup_secure_backup": {
-            "explainer": "Taktu öryggisafrit af dulritunarlyklunum áður en þú skráir þig út svo þeir tapist ekki.",
-            "title": "Setja upp"
+            "explainer": "Taktu öryggisafrit af dulritunarlyklunum áður en þú skráir þig út svo þeir tapist ekki."
         },
         "udd": {
             "interactive_verification_button": "Sannprófa gagnvirkt með táknmyndum",
@@ -742,12 +718,10 @@
             "title": "Ekki treyst"
         },
         "unable_to_setup_keys_error": "Tókst ekki að setja upp lykla",
-        "unsupported": "Þetta forrit styður ekki enda-í-enda dulritun.",
         "verification": {
             "accepting": "Samþykki…",
             "after_new_login": {
                 "device_verified": "Tæki er sannreynt",
-                "reset_confirmation": "Viltu í alvörunni endurstilla sannvottunarlyklana?",
                 "skip_verification": "Sleppa sannvottun í bili",
                 "unable_to_verify": "Tókst ekki að sannreyna þetta tæki",
                 "verify_this_device": "Sannreyna þetta tæki"
@@ -849,10 +823,7 @@
             "title": "Tókst ekki að afrita tengil spjallrásar"
         },
         "error_loading_user_profile": "Gat ekki hlaðið inn notandasniði",
-        "forget_room_failed": "Mistókst að gleyma spjallrásinni %(errCode)s",
-        "search_failed": {
-            "title": "Leit mistókst"
-        }
+        "forget_room_failed": "Mistókst að gleyma spjallrásinni %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1362,11 +1333,6 @@
         "ongoing": "Er að fjarlægja…",
         "reason_label": "Ástæða (valkvætt)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Ertu viss um að þú viljir hafna þessu boði?",
-        "failed": "Mistókst að hafna boði",
-        "title": "Hafna boði"
-    },
     "report_content": {
         "description": "Tilkynning um þessi skilaboð mun senda einstakt 'atviksauðkenni' til stjórnanda heimaþjóns. Ef skilaboð í þessari spjallrás eru dulrituð getur stjórnandi heimaþjóns ekki lesið skilaboðatextann eða skoðað skrár eða myndir.",
         "disagree": "Ósammála",
@@ -1487,7 +1453,6 @@
             "user_created": "%(displayName)s bjó til þessa spjallrás.",
             "you_created": "Þú bjóst til þessa spjallrás."
         },
-        "invite_reject_ignore": "Hafna og hunsa notanda",
         "invite_sent_to_email": "Þetta boð var sent til %(email)s",
         "invite_sent_to_email_room": "Þetta boð í %(roomName)s var sent til %(email)s",
         "invite_subtitle": "<userName/> bauð þér",
@@ -1889,7 +1854,6 @@
             "remove_msisdn_prompt": "Fjarlægja %(phone)s?",
             "spell_check_locale_placeholder": "Veldu staðfærslu"
         },
-        "image_thumbnails": "Birta forskoðun/smámyndir fyrir myndir",
         "inline_url_previews_default": "Sjálfgefið virkja forskoðun innfelldra vefslóða",
         "inline_url_previews_room": "Virkja forskoðun vefslóða sjálfgefið fyrir þátttakendur í þessari spjallrás",
         "inline_url_previews_room_account": "Virkja forskoðun vefslóða fyrir þessa spjallrás (einungis fyrir þig)",
@@ -1987,47 +1951,16 @@
         "prompt_invite": "Spyrja áður en boð eru send á mögulega ógild matrix-auðkenni",
         "replace_plain_emoji": "Skipta sjálfkrafa út Emoji-táknum á hreinum texta",
         "security": {
-            "4s_public_key_in_account_data": "í gögnum notandaaðgangs",
-            "4s_public_key_status": "Dreifilykill leynigeymslu:",
-            "backup_key_cached_status": "Öryggisafritunarlykill í skyndiminni:",
-            "backup_key_stored_status": "Geymdur öryggisafritunarlykill:",
-            "backup_key_unexpected_type": "óvænt tegund",
-            "backup_key_well_formed": "rétt sniðið",
-            "backup_keys_description": "Taktu öryggisafrit af dulritunarlyklunum þínum ásamt gögnum notandaaðgangsins fari svo að þú missir aðgang að setunum þínum. Dulritunarlyklarnir verða varðir með einstökum öryggislykli.",
             "bulk_options_accept_all_invites": "Samþykkja alla boðsgesti %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Hafna öllum boðsgestum %(invitedRooms)s",
             "bulk_options_section": "Valkostir magnvinnslu",
-            "cross_signing_cached": "í staðværu skyndiminni",
-            "cross_signing_homeserver_support": "Heimaþjónninn styður eftirfarandi eiginleika:",
-            "cross_signing_homeserver_support_exists": "er til staðar",
-            "cross_signing_in_4s": "í leynigeymslu",
-            "cross_signing_in_memory": "í minni",
-            "cross_signing_master_private_Key": "Aðal-einkalykill:",
-            "cross_signing_not_cached": "fannst ekki á tækinu",
-            "cross_signing_not_found": "fannst ekki",
-            "cross_signing_not_in_4s": "fannst ekki í geymslu",
-            "cross_signing_not_stored": "ekki geymt",
-            "cross_signing_private_keys": "Kross-undirritun einkalykla:",
-            "cross_signing_public_keys": "Kross-undirritun dreifilykla:",
-            "cross_signing_self_signing_private_key": "Sjálf-undirritaður einkalykill:",
-            "cross_signing_user_signing_private_key": "Notanda-undirritaður einkalykill:",
-            "cryptography_section": "Dulritun",
-            "delete_backup": "Eyða öryggisafriti",
-            "delete_backup_confirm_description": "Ertu viss? Þú munt tapa dulrituðu skilaboðunum þínum ef dulritunarlyklarnir þínir eru ekki rétt öryggisafritaðir.",
             "e2ee_default_disabled_warning": "Kerfisstjóri netþjónsins þíns hefur lokað á sjálfvirka dulritun í einkaspjallrásum og beinum skilaboðum.",
             "enable_message_search": "Virka skilaboðleit í dulrituðum spjallrásum",
             "encryption_section": "Dulritun",
-            "error_loading_key_backup_status": "Tókst ekki að hlaða inn stöðu öryggisafritunar dulritunarlykla",
-            "export_megolm_keys": "Flytja út E2E dulritunarlykla spjallrásar",
             "ignore_users_empty": "Þú ert ekki með neina hunsaða notendur.",
             "ignore_users_section": "Hunsaðir notendur",
-            "import_megolm_keys": "Flytja inn E2E dulritunarlykla spjallrásar",
-            "key_backup_active_version_none": "Ekkert",
             "key_backup_algorithm": "Reiknirit:",
-            "key_backup_complete": "Allir lyklar öryggisafritaðir",
             "key_backup_connect": "Tengja þessa setu við öryggisafrit af lykli",
-            "key_backup_inactive": "Þessi seta er <b>ekki að öryggisafrita dulritunarlyklana þína</b>, en þú ert með fyrirliggjandi öryggisafrit sem þú getur endurheimt úr og notað til að halda áfram.",
-            "key_backup_inactive_warning": "Dulritunarlyklarnir þínir eru <b>ekki öryggisafritaðir úr þessari setu</b>.",
             "message_search_disable_warning": "Ef þetta er óvirkt, munu skilaboð frá dulrituðum spjallrásum ekki birtast í leitarniðurstöðum.",
             "message_search_disabled": "Setja dulrituð skilaboð leynilega í skyndiminni á tækinu svo þau birtist í leitarniðurstöðum.",
             "message_search_enabled": {
@@ -2045,13 +1978,7 @@
             "message_search_sleep_time": "Hve hratt ætti að hlaða niður skilaboðum.",
             "message_search_space_used": "Notað geymslupláss:",
             "message_search_unsupported_web": "%(brand)s nær ekki að setja dulrituð skilaboð leynilega í skyndiminni á tækinu á meðan keyrt er í vafra. Notaðu <desktopLink>%(brand)s Desktop vinnutölvuútgáfuna</desktopLink> svo skilaboðin birtist í leitarniðurstöðum.",
-            "restore_key_backup": "Endurheimta úr öryggisafriti",
-            "secret_storage_not_ready": "ekki tilbúið",
-            "secret_storage_ready": "tilbúið",
-            "secret_storage_status": "Leynigeymsla:",
             "send_analytics": "Senda greiningargögn",
-            "session_id": "Auðkenni setu:",
-            "session_key": "Setulykill:",
             "strict_encryption": "Aldrei senda dulrituð skilaboð af þessu tæki til ósannvottaðra tækja"
         },
         "send_read_receipts": "Senda leskvittanir",
@@ -2244,8 +2171,6 @@
         "topic": "Nær í eða stillir umfjöllunarefni spjallrásar",
         "topic_none": "Þessi spjallrás er ekki með umfjöllunarefni.",
         "topic_room_error": "Mistókst að ná í umfjöllunarefni spjallrásar: Gat ekki fundið spjallrásina (%(roomId)s",
-        "tovirtual": "Skiptir yfir í sýndarspjallrás þessarar spjallrásar, ef hún er til staðar",
-        "tovirtual_not_found": "Engin sýndarspjallrás fyrir þessa spjallrás",
         "unban": "Tekur bann af notanda með uppgefið auðkenni",
         "unflip": "Setur ┬──┬ ノ( ゜-゜ノ) framan við hrein textaskilaboð",
         "unholdcall": "Tekur símtalið í fyrirliggjandi spjallrás úr bið",
@@ -2358,7 +2283,6 @@
         "heading_without_query": "Leita að",
         "join_button_text": "Taka þátt í %(roomAddress)s",
         "keyboard_scroll_hint": "Notaðu <arrows/> til að skruna",
-        "message_search_section_title": "Aðrar leitir",
         "other_rooms_in_space": "Aðrar spjallrásir í %(spaceName)s",
         "public_rooms_label": "Almenningsspjallrásir",
         "recent_searches_section_title": "Nýlegar leitir",
@@ -2625,7 +2549,9 @@
             "sent": "%(senderName)s sendi boð til %(targetDisplayName)s um þátttöku í spjallrásinni."
         },
         "m.room.tombstone": "%(senderDisplayName)s uppfærði þessa spjallrás.",
-        "m.room.topic": "%(senderDisplayName)s breytti umræðuefninu í \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s breytti umræðuefninu í \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s sendi límmerki.",
         "m.video": {
             "error_decrypting": "Villa við afkóðun myndskeiðs"
@@ -2862,14 +2788,6 @@
         "ban_room_confirm_title": "Banna í %(roomName)s",
         "ban_space_everything": "Banna viðkomandi frá því að gera allt það sem ég get gert",
         "ban_space_specific": "Banna viðkomandi frá því að gera tiltekna hluti sem ég get gert",
-        "count_of_sessions": {
-            "one": "%(count)s seta",
-            "other": "%(count)s setur"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 sannreynd seta",
-            "other": "%(count)s sannreyndar setur"
-        },
         "deactivate_confirm_action": "Gera notanda óvirkan",
         "deactivate_confirm_title": "Gera notanda óvirkan?",
         "demote_button": "Leggja til baka",
@@ -2879,14 +2797,11 @@
         "disinvite_button_room": "Afbjóða úr herbergi",
         "disinvite_button_room_name": "Afturkalla boð á %(roomName)s",
         "disinvite_button_space": "Afbjóða frá stað",
-        "edit_own_devices": "Breyta tækjum",
         "error_ban_user": "Mistókst að banna notanda",
         "error_deactivate": "Mistókst að gera þennan notanda óvirkan",
         "error_kicking_user": "Mistókst að fjarlægja notanda",
         "error_mute_user": "Mistókst að þagga niður í notanda",
         "error_revoke_3pid_invite_title": "Mistókst að afturkalla boð",
-        "hide_sessions": "Fela setur",
-        "hide_verified_sessions": "Fela sannreyndar setur",
         "invited_by": "Boðið af %(sender)s",
         "jump_to_rr_button": "Fara í fyrstu leskvittun",
         "kick_button_room": "Fjarlægja úr spjallrás",
@@ -2965,7 +2880,6 @@
         "hide_sidebar_button": "Fela hliðarspjald",
         "input_devices": "Inntakstæki",
         "join_button_tooltip_call_full": "Því miður - þetta símtal er fullt í augnablikinu",
-        "join_button_tooltip_connecting": "Tengist",
         "maximise": "Fylla skjá",
         "misconfigured_server": "Símtal mistókst vegna vanstillingar netþjóns",
         "misconfigured_server_description": "Spurðu kerfisstjóra (<code>%(homeserverDomain)s</code>) heimaþjónsins þíns um að setja upp TURN-þjón til að tryggja að símtöl virki eðlilega.",
diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json
index a67adfda7a62ff8a5c67ffdb6723f0496c2bdb55..66ea31ad9c99fad3b91055a1790049629320e313 100644
--- a/src/i18n/strings/it.json
+++ b/src/i18n/strings/it.json
@@ -94,7 +94,6 @@
         "react": "Reagisci",
         "refresh": "Aggiorna",
         "register": "Registrati",
-        "reject": "Rifiuta",
         "reload": "Ricarica",
         "remove": "Rimuovi",
         "rename": "Rinomina",
@@ -246,10 +245,10 @@
         "qr_code_login": {
             "completing_setup": "Completamento configurazione nuovo dispositivo",
             "error_rate_limited": "Troppi tentativi in poco tempo. Attendi un po' prima di riprovare.",
-            "error_unexpected": "Si è verificato un errore imprevisto.",
-            "scan_code_instruction": "Scansiona il codice QR sottostante con il dispositivo che è disconnesso.",
-            "scan_qr_code": "Scansiona codice QR",
-            "select_qr_code": "Seleziona '%(scanQRCode)s'",
+            "error_unexpected": "Si è verificato un errore imprevisto. La richiesta di connessione dell'altro dispositivo è stata annullata.",
+            "scan_code_instruction": "Scansiona il codice QR con un altro dispositivo",
+            "scan_qr_code": "Accedi con codice QR",
+            "select_qr_code": "Seleziona \"%(scanQRCode)s\"",
             "waiting_for_device": "In attesa che il dispositivo acceda"
         },
         "register_action": "Crea account",
@@ -372,7 +371,6 @@
         "download_logs": "Scarica i log",
         "downloading_logs": "Scaricamento dei log",
         "error_empty": "Per favore dicci cos'è andato storto, o meglio, crea una segnalazione su GitHub che descriva il problema.",
-        "failed_send_logs": "Invio dei log fallito: ",
         "github_issue": "Segnalazione GitHub",
         "introduction": "Se hai inviato un errore via GitHub, i log di debug possono aiutarci ad individuare il problema. ",
         "log_request": "Per aiutarci a prevenire questa cosa in futuro, <a>inviaci i log</a>.",
@@ -412,7 +410,6 @@
         "access_token": "Token di accesso",
         "accessibility": "Accessibilità",
         "advanced": "Avanzato",
-        "all_rooms": "Tutte le stanze",
         "analytics": "Statistiche",
         "and_n_others": {
             "other": "e altri %(count)s ...",
@@ -430,7 +427,6 @@
         "capabilities": "Capacità",
         "copied": "Copiato!",
         "credits": "Crediti",
-        "cross_signing": "Firma incrociata",
         "dark": "Scuro",
         "description": "Descrizione",
         "deselect_all": "Deseleziona tutti",
@@ -461,7 +457,6 @@
         "legal": "Informazioni legali",
         "light": "Chiaro",
         "loading": "Caricamento…",
-        "lobby": "Sala d’attesa",
         "location": "Posizione",
         "low_priority": "Bassa priorità",
         "matrix": "Matrix",
@@ -510,7 +505,6 @@
         "rooms": "Stanze",
         "saving": "Salvataggio…",
         "secure_backup": "Backup Sicuro",
-        "security": "Sicurezza",
         "select_all": "Seleziona tutti",
         "server": "Server",
         "settings": "Impostazioni",
@@ -529,7 +523,6 @@
         "thread": "Conversazione",
         "threads": "Conversazioni",
         "timeline": "Linea temporale",
-        "trusted": "Fidato",
         "unavailable": "non disponibile",
         "unencrypted": "Non crittografato",
         "unmute": "Togli silenzio",
@@ -801,44 +794,23 @@
     "empty_room_was_name": "Stanza vuota (era %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Inserisci la tua frase di sicurezza o <button>usa la tua chiave di sicurezza</button> per continuare.",
             "key_validation_text": {
-                "invalid_security_key": "Chiave di sicurezza non valida",
-                "recovery_key_is_correct": "Sembra giusta!",
-                "wrong_file_type": "Tipo di file errato",
                 "wrong_security_key": "Chiave di sicurezza sbagliata"
             },
-            "reset_title": "Reimposta tutto",
-            "reset_warning_1": "Fallo solo se non hai altri dispositivi con cui completare la verifica.",
-            "reset_warning_2": "Se reimposti tutto, ricomincerai senza sessioni fidate, senza utenti fidati e potresti non riuscire a vedere i messaggi passati.",
             "restoring": "Ripristino delle chiavi dal backup",
-            "security_key_title": "Chiave di sicurezza",
-            "security_phrase_incorrect_error": "Impossibile accedere all'archivio segreto. Verifica di avere inserito la password di sicurezza giusta.",
-            "security_phrase_title": "Frase di sicurezza",
-            "separator": "%(securityKey)s o %(recoveryFile)s",
-            "use_security_key_prompt": "Usa la tua chiave di sicurezza per continuare."
+            "security_key_title": "Chiave di sicurezza"
         },
         "bootstrap_title": "Configurazione chiavi",
         "cancel_entering_passphrase_description": "Sei sicuro di volere annullare l'inserimento della frase?",
         "cancel_entering_passphrase_title": "Annullare l'inserimento della password?",
         "confirm_encryption_setup_body": "Clicca il pulsante sotto per confermare l'impostazione della crittografia.",
         "confirm_encryption_setup_title": "Conferma impostazione crittografia",
-        "cross_signing_not_ready": "La firma incrociata non è impostata.",
-        "cross_signing_ready": "La firma incrociata è pronta all'uso.",
-        "cross_signing_ready_no_backup": "La firma incrociata è pronta ma c'è un backup delle chiavi.",
         "cross_signing_room_normal": "Questa stanza è cifrata end-to-end",
         "cross_signing_room_verified": "Tutti in questa stanza sono verificati",
         "cross_signing_room_warning": "Qualcuno sta usando una sessione sconosciuta",
-        "cross_signing_unsupported": "Il tuo homeserver non supporta la firma incrociata.",
-        "cross_signing_untrusted": "Il tuo account ha un'identità a firma incrociata nell'archivio segreto, ma non è ancora fidata da questa sessione.",
         "cross_signing_user_normal": "Non hai verificato questo utente.",
         "cross_signing_user_verified": "Hai verificato questo utente. Questo utente ha verificato tutte le sue sessioni.",
         "cross_signing_user_warning": "Questo utente non ha verificato tutte le sue sessioni.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Elimina chiavi di firma incrociata",
-            "title": "Distruggere le chiavi di firma incrociata?",
-            "warning": "L'eliminazione delle chiavi di firma incrociata è permanente. Chiunque si sia verificato con te vedrà avvisi di sicurezza. Quasi sicuramente non vuoi fare questa cosa, a meno che tu non abbia perso tutti i dispositivi da cui puoi fare l'accesso."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "L'autenticità di questo messaggio crittografato non può essere garantita su questo dispositivo.",
         "event_shield_reason_mismatched_sender_key": "Crittografato da una sessione non verificata",
         "event_shield_reason_unknown_device": "Crittografato da un dispositivo sconosciuto o eliminato.",
@@ -861,7 +833,6 @@
             "title": "Nuovo metodo di recupero",
             "warning": "Se non hai impostato il nuovo metodo di recupero, un aggressore potrebbe tentare di accedere al tuo account. Cambia la password del tuo account e imposta immediatamente un nuovo metodo di recupero nelle impostazioni."
         },
-        "not_supported": "<non supportato>",
         "recovery_method_removed": {
             "description_1": "Questa sessione ha rilevato che la tua password di sicurezza e la chiave per i messaggi sicuri sono state rimosse.",
             "description_2": "Se l'hai fatto accidentalmente, puoi configurare Messaggi Sicuri su questa sessione che cripterà nuovamente la cronologia dei messaggi con un nuovo metodo di recupero.",
@@ -872,8 +843,7 @@
         "set_up_toast_description": "Proteggiti dalla perdita dei messaggi e dati crittografati",
         "set_up_toast_title": "Imposta il Backup Sicuro",
         "setup_secure_backup": {
-            "explainer": "Fai una copia delle tue chiavi prima di disconnetterti per evitare di perderle.",
-            "title": "Imposta"
+            "explainer": "Fai una copia delle tue chiavi prima di disconnetterti per evitare di perderle."
         },
         "udd": {
             "interactive_verification_button": "Verifica interattivamente con emoji",
@@ -884,12 +854,10 @@
             "title": "Non fidato"
         },
         "unable_to_setup_keys_error": "Impossibile impostare le chiavi",
-        "unsupported": "Questo client non supporta la crittografia end-to-end.",
         "verification": {
             "accepting": "Accettazione…",
             "after_new_login": {
                 "device_verified": "Dispositivo verificato",
-                "reset_confirmation": "Reimpostare le chiavi di verifica?",
                 "skip_verification": "Salta la verifica per adesso",
                 "unable_to_verify": "Impossibile verificare questo dispositivo",
                 "verify_this_device": "Verifica questo dispositivo"
@@ -959,8 +927,6 @@
             "verify_emoji_prompt": "Verifica confrontando emoji specifici.",
             "verify_emoji_prompt_qr": "Se non riesci a scansionare il codice sopra, verifica confrontando emoji specifiche.",
             "verify_later": "Verificherò dopo",
-            "verify_reset_warning_1": "La reimpostazione delle chiavi di verifica non può essere annullata. Dopo averlo fatto, non avrai accesso ai vecchi messaggi cifrati, e gli amici che ti avevano verificato in precedenza vedranno avvisi di sicurezza fino a quando non ti ri-verifichi con loro.",
-            "verify_reset_warning_2": "Procedi solo se sei sicuro di avere perso tutti gli altri tuoi dispositivi e la chiave di sicurezza.",
             "verify_using_device": "Verifica con un altro dispositivo",
             "verify_using_key": "Verifica con chiave di sicurezza",
             "verify_using_key_or_phrase": "Verifica con chiave di sicurezza o frase",
@@ -1026,11 +992,7 @@
             "title": "Impossibile copiare il link della stanza"
         },
         "error_loading_user_profile": "Impossibile caricare il profilo utente",
-        "forget_room_failed": "Impossibile dimenticare la stanza %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Il server potrebbe essere non disponibile, sovraccarico o la ricerca è scaduta :(",
-            "title": "Ricerca fallita"
-        }
+        "forget_room_failed": "Impossibile dimenticare la stanza %(errCode)s"
     },
     "error_user_not_logged_in": "Utente non connesso",
     "event_preview": {
@@ -1646,11 +1608,6 @@
         "ongoing": "Rimozione…",
         "reason_label": "Motivo (facoltativo)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Sei sicuro di volere rifiutare l'invito?",
-        "failed": "Rifiuto dell'invito fallito",
-        "title": "Rifiuta l'invito"
-    },
     "report_content": {
         "description": "La segnalazione di questo messaggio invierà il suo 'ID evento' univoco all'amministratore del tuo homeserver. Se i messaggi della stanza sono cifrati, l'amministratore non potrà leggere il messaggio o vedere file e immagini.",
         "disagree": "Rifiuta",
@@ -1830,7 +1787,6 @@
             "you_created": "Hai creato questa stanza."
         },
         "invite_email_mismatch_suggestion": "Condividi questa email nelle impostazioni per ricevere inviti direttamente in %(brand)s.",
-        "invite_reject_ignore": "Rifiuta e ignora l'utente",
         "invite_sent_to_email": "Questo invito è stato inviato a %(email)s",
         "invite_sent_to_email_room": "Questo invito per %(roomName)s è stato inviato a %(email)s",
         "invite_subtitle": "<userName/> ti ha invitato/a",
@@ -2259,14 +2215,14 @@
             "custom_font_description": "Imposta il nome di un font installato nel tuo sistema e %(brand)s proverà ad usarlo.",
             "custom_font_name": "Nome carattere di sistema",
             "custom_font_size": "Usa dimensione personalizzata",
-            "custom_theme_error_downloading": "Errore scaricando informazioni sul tema.",
+            "custom_theme_error_downloading": "Errore di scaricamento del tema",
             "custom_theme_invalid": "Schema del tema non valido.",
             "font_size": "Dimensione carattere",
             "font_size_default": "%(fontSize)s (predefinito)",
             "image_size_default": "Predefinito",
             "image_size_large": "Grande",
             "layout_bubbles": "Messaggi",
-            "layout_irc": "IRC (Sperimentale)",
+            "layout_irc": "IRC (sperimentale)",
             "match_system_theme": "Usa il tema di sistema",
             "timeline_image_size": "Dimensione immagine nella linea temporale"
         },
@@ -2307,9 +2263,9 @@
             "deactivate_confirm_erase_label": "Nascondi i miei messaggi ai nuovi membri",
             "deactivate_section": "Disattiva l'account",
             "deactivate_warning": "La disattivazione dell'account è permanente - attenzione!",
-            "discovery_email_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un'email sopra.",
+            "discovery_email_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un'email.",
             "discovery_email_verification_instructions": "Verifica il link nella tua posta in arrivo",
-            "discovery_msisdn_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un numero di telefono sopra.",
+            "discovery_msisdn_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un numero di telefono.",
             "discovery_needs_terms": "Accetta le condizioni di servizio del server di identità (%(serverName)s) per poter essere trovabile tramite indirizzo email o numero di telefono.",
             "email_address_in_use": "Questo indirizzo e-mail è già in uso",
             "email_address_label": "Indirizzo email",
@@ -2335,7 +2291,7 @@
             "error_share_msisdn_discovery": "Impossibile condividere il numero di telefono",
             "identity_server_no_token": "Nessun token di accesso d'identità trovato",
             "identity_server_not_set": "Server d'identità non impostato",
-            "language_section": "Lingua e regione",
+            "language_section": "Lingua",
             "msisdn_in_use": "Questo numero di telefono è già in uso",
             "msisdn_label": "Numero di telefono",
             "msisdn_verification_field_label": "Codice di verifica",
@@ -2348,7 +2304,6 @@
             "remove_msisdn_prompt": "Rimuovere %(phone)s?",
             "spell_check_locale_placeholder": "Scegli una lingua"
         },
-        "image_thumbnails": "Mostra anteprime/miniature per le immagini",
         "inline_url_previews_default": "Attiva le anteprime URL in modo predefinito",
         "inline_url_previews_room": "Attiva le anteprime URL in modo predefinito per i partecipanti in questa stanza",
         "inline_url_previews_room_account": "Attiva le anteprime URL in questa stanza (riguarda solo te)",
@@ -2370,8 +2325,8 @@
                 "enter_phrase_description": "Inserisci una frase di sicurezza che conosci solo tu, dato che è usata per proteggere i tuoi dati. Per sicurezza, non dovresti riutilizzare la password dell'account.",
                 "enter_phrase_title": "Inserisci una frase di sicurezza",
                 "enter_phrase_to_confirm": "Inserisci di nuovo la password di sicurezza per confermarla.",
-                "generate_security_key_description": "Genereremo per te una chiave di sicurezza da conservare in un posto sicuro, come un in gestore di password o in una cassaforte.",
-                "generate_security_key_title": "Genera una chiave di sicurezza",
+                "generate_security_key_description": "Genereremo per te una Chiave di Recupero da conservare in un posto sicuro, come un gestore di password o una cassaforte.",
+                "generate_security_key_title": "Genera una Chiave di Recupero",
                 "pass_phrase_match_failed": "Non corrisponde.",
                 "pass_phrase_match_success": "Corrisponde!",
                 "phrase_strong_enough": "Ottimo! Questa password di sicurezza sembra abbastanza robusta.",
@@ -2484,54 +2439,17 @@
         "prompt_invite": "Chiedi prima di inviare inviti a possibili ID matrix non validi",
         "replace_plain_emoji": "Sostituisci automaticamente le emoji testuali",
         "security": {
-            "4s_public_key_in_account_data": "nei dati dell'account",
-            "4s_public_key_status": "Chiave pubblica dell'archivio segreto:",
             "analytics_description": "Condividi dati anonimi per aiutarci ad identificare i problemi. Niente di personale. Niente terze parti.",
-            "backup_key_cached_status": "Chiave di backup in cache:",
-            "backup_key_stored_status": "Chiave di backup salvata:",
-            "backup_key_unexpected_type": "tipo inatteso",
-            "backup_key_well_formed": "formattata bene",
-            "backup_keys_description": "Fai il backup delle tue chiavi di crittografia con i dati del tuo account in caso perdessi l'accesso alle sessioni. Le tue chiavi saranno protette con una chiave di recupero univoca.",
             "bulk_options_accept_all_invites": "Accetta tutti i %(invitedRooms)s inviti",
             "bulk_options_reject_all_invites": "Rifiuta tutti gli inviti da %(invitedRooms)s",
             "bulk_options_section": "Opzioni generali",
-            "cross_signing_cached": "in cache locale",
-            "cross_signing_homeserver_support": "Funzioni supportate dall'homeserver:",
-            "cross_signing_homeserver_support_exists": "esiste",
-            "cross_signing_in_4s": "in un archivio segreto",
-            "cross_signing_in_memory": "in memoria",
-            "cross_signing_master_private_Key": "Chiave privata principale:",
-            "cross_signing_not_cached": "non trovato in locale",
-            "cross_signing_not_found": "non trovato",
-            "cross_signing_not_in_4s": "non trovato nell'archivio",
-            "cross_signing_not_stored": "non salvato",
-            "cross_signing_private_keys": "Chiavi private di firma incrociata:",
-            "cross_signing_public_keys": "Chiavi pubbliche di firma incrociata:",
-            "cross_signing_self_signing_private_key": "Chiave privata di auto-firma:",
-            "cross_signing_user_signing_private_key": "Chiave privata di firma utente:",
-            "cryptography_section": "Crittografia",
-            "delete_backup": "Elimina backup",
-            "delete_backup_confirm_description": "Sei sicuro? Perderai i tuoi messaggi cifrati se non hai salvato adeguatamente le tue chiavi.",
             "e2ee_default_disabled_warning": "L'amministratore del server ha disattivato la crittografia end-to-end in modo predefinito nelle stanze private e nei messaggi diretti.",
             "enable_message_search": "Attiva la ricerca messaggi nelle stanze cifrate",
             "encryption_section": "Crittografia",
-            "error_loading_key_backup_status": "Impossibile caricare lo stato del backup delle chiavi",
-            "export_megolm_keys": "Esporta chiavi E2E della stanza",
             "ignore_users_empty": "Non hai utenti ignorati.",
             "ignore_users_section": "Utenti ignorati",
-            "import_megolm_keys": "Importa chiavi E2E stanza",
-            "key_backup_active": "Questa sessione sta facendo il backup delle tue chiavi.",
-            "key_backup_active_version": "Versione di backup attiva:",
-            "key_backup_active_version_none": "Nessuno",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_can_be_restored": "Questo backup può essere ripristinato in questa sessione",
-            "key_backup_complete": "Tutte le chiavi sono state copiate",
             "key_backup_connect": "Connetti questa sessione al backup chiavi",
-            "key_backup_connect_prompt": "Connetti questa sessione al backup chiavi prima di disconnetterti per non perdere eventuali chiavi che possono essere solo in questa sessione.",
-            "key_backup_in_progress": "Backup di %(sessionsRemaining)s chiavi…",
-            "key_backup_inactive": "Questa sessione <b>non sta facendo il backup delle tue chiavi</b>, ma hai un backup esistente dal quale puoi ripristinare e che puoi usare da ora in poi.",
-            "key_backup_inactive_warning": "Il backup chiavi <b>non viene fatto per questa sessione</b>.",
-            "key_backup_latest_version": "Ultima versione del backup sul server:",
             "message_search_disable_warning": "Se disattivato, i messaggi delle stanze cifrate non appariranno nei risultati di ricerca.",
             "message_search_disabled": "Tieni in cache localmente i messaggi cifrati in modo sicuro affinché appaiano nei risultati di ricerca.",
             "message_search_enabled": {
@@ -2551,13 +2469,7 @@
             "message_search_unsupported": "A %(brand)s mancano alcuni componenti richiesti per tenere in cache i messaggi cifrati in modo sicuro. Se vuoi sperimentare questa funzionalità, compila un %(brand)s Desktop personale con <nativeLink>i componenti di ricerca aggiunti</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s non può tenere in cache i messaggi cifrati quando usato in un browser web. Usa <desktopLink>%(brand)s Desktop</desktopLink> affinché i messaggi cifrati appaiano nei risultati di ricerca.",
             "record_session_details": "Registra il nome, la versione e l'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni",
-            "restore_key_backup": "Ripristina da un backup",
-            "secret_storage_not_ready": "non pronto",
-            "secret_storage_ready": "pronto",
-            "secret_storage_status": "Archivio segreto:",
             "send_analytics": "Invia dati statistici",
-            "session_id": "ID sessione:",
-            "session_key": "Chiave sessione:",
             "strict_encryption": "Non inviare mai messaggi cifrati a sessioni non verificate da questa sessione"
         },
         "send_read_receipts": "Invia le conferme di lettura",
@@ -2628,9 +2540,9 @@
             "security_recommendations_description": "Migliora la sicurezza del tuo account seguendo questi consigli.",
             "session_id": "ID sessione",
             "show_details": "Mostra dettagli",
-            "sign_in_with_qr": "Accedi con codice QR",
+            "sign_in_with_qr": "Collega un nuovo dispositivo",
             "sign_in_with_qr_button": "Mostra codice QR",
-            "sign_in_with_qr_description": "Puoi usare questo dispositivo per accedere in un altro con un codice QR. Dovrai scansionare il codice QR mostrato in questo dispositivo con l'altro.",
+            "sign_in_with_qr_description": "Usa un codice QR per accedere a un altro dispositivo e impostare la messaggistica sicura.",
             "sign_out": "Disconnetti da questa sessione",
             "sign_out_all_other_sessions": "Disconnetti tutte le altre sessioni (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2786,8 +2698,6 @@
         "topic": "Ottiene o imposta l'argomento della stanza",
         "topic_none": "Questa stanza non ha un argomento.",
         "topic_room_error": "Lettura argomento stanza fallita: impossibile trovare la stanza (%(roomId)s",
-        "tovirtual": "Passa alla stanza virtuale di questa stanza, se ne ha una",
-        "tovirtual_not_found": "Nessuna stanza virtuale per questa stanza",
         "unban": "Riammette l'utente con l'ID dato",
         "unflip": "Antepone ┬──┬ ノ( ゜-゜ノ) ad un messaggio di testo",
         "unholdcall": "Riprende la chiamata nella stanza attuale",
@@ -2909,7 +2819,6 @@
         "heading_without_query": "Cerca",
         "join_button_text": "Entra in %(roomAddress)s",
         "keyboard_scroll_hint": "Usa <arrows/> per scorrere",
-        "message_search_section_title": "Altre ricerche",
         "other_rooms_in_space": "Altre stanze in %(spaceName)s",
         "public_rooms_label": "Stanze pubbliche",
         "public_spaces_label": "Spazi pubblici",
@@ -2919,7 +2828,6 @@
         "result_may_be_hidden_privacy_warning": "Alcuni risultati potrebbero essere nascosti per privacy",
         "result_may_be_hidden_warning": "Alcuni risultati potrebbero essere nascosti",
         "search_dialog": "Finestra di ricerca",
-        "search_messages_hint": "Per cercare messaggi, trova questa icona in cima ad una stanza <icon/>",
         "spaces_title": "Spazi in cui sei",
         "start_group_chat_button": "Inizia una conversazione di gruppo"
     },
@@ -3198,7 +3106,9 @@
             "sent": "%(senderName)s ha mandato un invito a %(targetDisplayName)s per unirsi alla stanza."
         },
         "m.room.tombstone": "%(senderDisplayName)s ha aggiornato questa stanza.",
-        "m.room.topic": "%(senderDisplayName)s ha modificato l'argomento in \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s ha modificato l'argomento in \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s ha inviato uno sticker.",
         "m.video": {
             "error_decrypting": "Errore decifratura video"
@@ -3475,14 +3385,6 @@
         "ban_room_confirm_title": "Bandisci da %(roomName)s",
         "ban_space_everything": "Bandiscilo ovunque io possa farlo",
         "ban_space_specific": "Bandiscilo da cose specifiche dove posso farlo",
-        "count_of_sessions": {
-            "other": "%(count)s sessioni",
-            "one": "%(count)s sessione"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sessioni verificate",
-            "one": "1 sessione verificata"
-        },
         "deactivate_confirm_action": "Disattiva utente",
         "deactivate_confirm_description": "Disattivare questo utente lo disconnetterà e ne impedirà nuovi accessi. In aggiunta, abbandonerà tutte le stanze in cui è presente. Questa azione non può essere annullata. Sei sicuro di volere disattivare questo utente?",
         "deactivate_confirm_title": "Disattivare l'utente?",
@@ -3493,15 +3395,12 @@
         "disinvite_button_room": "Disinvita dalla stanza",
         "disinvite_button_room_name": "Annulla l'invito da %(roomName)s",
         "disinvite_button_space": "Disinvita dallo spazio",
-        "edit_own_devices": "Modifica dispositivi",
         "error_ban_user": "Ban utente fallito",
         "error_deactivate": "Disattivazione utente fallita",
         "error_kicking_user": "Rimozione utente fallita",
         "error_mute_user": "Impossibile silenziare l'utente",
         "error_revoke_3pid_invite_description": "Impossibile revocare l'invito. Il server potrebbe avere un problema temporaneo o non si dispone di autorizzazioni sufficienti per revocare l'invito.",
         "error_revoke_3pid_invite_title": "Revoca dell'invito fallita",
-        "hide_sessions": "Nascondi sessione",
-        "hide_verified_sessions": "Nascondi sessioni verificate",
         "ignore_confirm_description": "Tutti i messaggi e gli inviti da questo utente verranno nascosti. Vuoi davvero ignorarli?",
         "ignore_confirm_title": "Ignora %(user)s",
         "invited_by": "Invitato/a da %(sender)s",
@@ -3592,7 +3491,6 @@
         "input_devices": "Dispositivi di input",
         "jitsi_call": "Conferenza Jitsi",
         "join_button_tooltip_call_full": "Spiacenti — questa chiamata è piena",
-        "join_button_tooltip_connecting": "In connessione",
         "legacy_call": "Chiamata legacy",
         "maximise": "Riempi schermo",
         "maximise_call": "Massimizza la chiamata",
@@ -3753,7 +3651,7 @@
             "title": "Permetti a questo widget di verificare la tua identità"
         },
         "popout": "Oggetto a comparsa",
-        "set_room_layout": "Imposta la disposizione della stanza per tutti",
+        "set_room_layout": "Imposta la disposizione per tutti",
         "shared_data_avatar": "L'URL della tua immagine del profilo",
         "shared_data_device_id": "L'ID del tuo dispositivo",
         "shared_data_lang": "La tua lingua",
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index b4e7e0a931b1f77be4a7d55927616d01b1e81842..e4e61e8907fba16c55204c08b943d868317b1134 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -85,7 +85,6 @@
         "react": "リアクション",
         "refresh": "再読み込み",
         "register": "登録",
-        "reject": "拒否",
         "remove": "削除",
         "rename": "表示名を変更",
         "reply": "返信",
@@ -355,7 +354,6 @@
         "download_logs": "ログのダウンロード",
         "downloading_logs": "ログをダウンロードしています",
         "error_empty": "発生した問題を教えてください。または、問題を説明するGitHub issueを作成してください。",
-        "failed_send_logs": "ログの送信に失敗しました: ",
         "introduction": "もしGitHubで不具合を報告した場合は、デバッグログが問題の解決に役立ちます。 ",
         "log_request": "今後これが起こらないようにするために、<a>ログを送信</a>してください。",
         "logs_sent": "ログが送信されました",
@@ -393,7 +391,6 @@
         "access_token": "アクセストークン",
         "accessibility": "アクセシビリティー",
         "advanced": "詳細",
-        "all_rooms": "全てのルーム",
         "analytics": "分析",
         "and_n_others": {
             "other": "他%(count)s人…",
@@ -411,7 +408,6 @@
         "capabilities": "機能",
         "copied": "コピーしました!",
         "credits": "クレジット",
-        "cross_signing": "クロス署名",
         "dark": "ダーク",
         "description": "詳細",
         "deselect_all": "全ての選択を解除",
@@ -489,7 +485,6 @@
         "rooms": "ルーム",
         "saving": "保存しています…",
         "secure_backup": "セキュアバックアップ",
-        "security": "セキュリティー",
         "select_all": "全て選択",
         "server": "サーバー",
         "settings": "設定",
@@ -508,7 +503,6 @@
         "thread": "スレッド",
         "threads": "スレッド",
         "timeline": "タイムライン",
-        "trusted": "信頼済",
         "unencrypted": "暗号化されていません",
         "unmute": "ミュート解除",
         "unnamed_room": "名前のないルーム",
@@ -759,44 +753,23 @@
     "empty_room_was_name": "空のルーム(以前の名前は%(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "続行するにはセキュリティーフレーズを入力するか、<button>セキュリティーキーを使用</button>してください。",
             "key_validation_text": {
-                "invalid_security_key": "セキュリティーキーが正しくありません",
-                "recovery_key_is_correct": "問題ありません!",
-                "wrong_file_type": "正しくないファイルの種類",
                 "wrong_security_key": "正しくないセキュリティーキー"
             },
-            "reset_title": "全てリセット",
-            "reset_warning_1": "認証を行える端末がない場合のみ行ってください。",
-            "reset_warning_2": "全てをリセットすると、履歴とメッセージが消去され、信頼済の端末、信頼済のユーザーが取り消されます。また、過去のメッセージを表示できなくなる可能性があります。",
             "restoring": "バックアップから鍵を復元",
-            "security_key_title": "セキュリティーキー",
-            "security_phrase_incorrect_error": "機密ストレージにアクセスできません。正しいセキュリティーフレーズを入力したことを確認してください。",
-            "security_phrase_title": "セキュリティーフレーズ",
-            "separator": "%(securityKey)sまたは%(recoveryFile)s",
-            "use_security_key_prompt": "続行するにはセキュリティーキーを使用してください。"
+            "security_key_title": "セキュリティーキー"
         },
         "bootstrap_title": "鍵のセットアップ",
         "cancel_entering_passphrase_description": "パスフレーズの入力をキャンセルしてよろしいですか?",
         "cancel_entering_passphrase_title": "パスフレーズの入力をキャンセルしますか?",
         "confirm_encryption_setup_body": "以下のボタンをクリックして、暗号化の設定を承認してください。",
         "confirm_encryption_setup_title": "暗号化の設定を承認してください",
-        "cross_signing_not_ready": "クロス署名が設定されていません。",
-        "cross_signing_ready": "クロス署名の使用準備が完了しました。",
-        "cross_signing_ready_no_backup": "クロス署名は準備できましたが、鍵はバックアップされていません。",
         "cross_signing_room_normal": "このルームはエンドツーエンドで暗号化されています",
         "cross_signing_room_verified": "このルーム内の全員を認証済",
         "cross_signing_room_warning": "誰かが不明なセッションを使用しています",
-        "cross_signing_unsupported": "あなたのホームサーバーはクロス署名に対応していません。",
-        "cross_signing_untrusted": "あなたのアカウントではクロス署名の認証情報が機密ストレージに保存されていますが、このセッションでは信頼されていません。",
         "cross_signing_user_normal": "あなたはこのユーザーを認証していません。",
         "cross_signing_user_verified": "このユーザーを認証しました。このユーザーは全てのセッションを認証しました。",
         "cross_signing_user_warning": "このユーザーは全てのセッションを確認していません。",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "クロス署名鍵を削除",
-            "title": "クロス署名鍵を破棄してよろしいですか?",
-            "warning": "クロス署名鍵の削除は取り消せません。認証した相手には、セキュリティーに関する警告が表示されます。クロス署名を行える全ての端末を失ったのでない限り、続行すべきではありません。"
-        },
         "event_shield_reason_authenticity_not_guaranteed": "この暗号化されたメッセージの真正性はこの端末では保証できません。",
         "event_shield_reason_mismatched_sender_key": "未認証のセッションによる暗号化",
         "export_unsupported": "お使いのブラウザーは、必要な暗号化拡張機能をサポートしていません",
@@ -816,7 +789,6 @@
             "title": "新しい復元方法",
             "warning": "新しい復元方法を設定しなかった場合、攻撃者がアカウントへアクセスしようとしている可能性があります。設定画面ですぐにアカウントのパスワードを変更し、新しい復元方法を設定してください。"
         },
-        "not_supported": "<サポート対象外>",
         "recovery_method_removed": {
             "description_1": "セキュリティーフレーズと、セキュアメッセージの鍵が削除されました。",
             "description_2": "偶然削除してしまった場合は、このセッションで、メッセージの暗号化を設定することができます。設定すると、新しい復元方法でセッションのデータを改めて暗号化します。",
@@ -827,8 +799,7 @@
         "set_up_toast_description": "暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう",
         "set_up_toast_title": "セキュアバックアップを設定",
         "setup_secure_backup": {
-            "explainer": "鍵を失くさないよう、サインアウトする前にバックアップしてください。",
-            "title": "設定"
+            "explainer": "鍵を失くさないよう、サインアウトする前にバックアップしてください。"
         },
         "udd": {
             "interactive_verification_button": "絵文字で認証",
@@ -839,12 +810,10 @@
             "title": "信頼されていません"
         },
         "unable_to_setup_keys_error": "鍵を設定できません",
-        "unsupported": "このクライアントはエンドツーエンド暗号化に対応していません。",
         "verification": {
             "accepting": "承認しています…",
             "after_new_login": {
                 "device_verified": "端末が認証されました",
-                "reset_confirmation": "本当に認証鍵をリセットしますか?",
                 "skip_verification": "認証をスキップ",
                 "unable_to_verify": "この端末を認証できません",
                 "verify_this_device": "この端末を認証"
@@ -911,8 +880,6 @@
             "verify_emoji_prompt": "絵文字の並びを比較して認証。",
             "verify_emoji_prompt_qr": "上記のコードをスキャンできない場合は、絵文字による確認を行ってください。",
             "verify_later": "後で認証",
-            "verify_reset_warning_1": "認証鍵のリセットは取り消せません。リセットすると、以前の暗号化されたメッセージにはアクセスできなくなります。また、あなたのアカウントを認証した連絡先には、再認証するまで、セキュリティーに関する警告が表示されます。",
-            "verify_reset_warning_2": "全ての端末とセキュリティーキーを紛失してしまったことが確かである場合にのみ、続行してください。",
             "verify_using_device": "別の端末で認証",
             "verify_using_key": "セキュリティーキーで認証",
             "verify_using_key_or_phrase": "セキュリティーキーあるいはセキュリティーフレーズで認証",
@@ -968,11 +935,7 @@
             "title": "ルームのリンクをコピーできません"
         },
         "error_loading_user_profile": "ユーザーのプロフィールを読み込めませんでした",
-        "forget_room_failed": "ルームの履歴を消去するのに失敗しました %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "サーバーが使用できないか、オーバーロードしているか、または検索がタイムアウトした可能性があります :(",
-            "title": "検索に失敗しました"
-        }
+        "forget_room_failed": "ルームの履歴を消去するのに失敗しました %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1530,11 +1493,6 @@
         "ongoing": "削除しています…",
         "reason_label": "理由(任意)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "招待を辞退してよろしいですか?",
-        "failed": "招待を辞退できませんでした",
-        "title": "招待を辞退"
-    },
     "report_content": {
         "description": "このメッセージを報告すると、このメッセージの一意の「イベントID」があなたのホームサーバーの管理者に送信されます。このルーム内のメッセージが暗号化されている場合、ホームサーバーの管理者はメッセージのテキストを読んだり、ファイルや画像を表示したりすることはできません。",
         "disagree": "同意しない",
@@ -1672,7 +1630,6 @@
             "you_created": "このルームを作成しました。"
         },
         "invite_email_mismatch_suggestion": "このメールアドレスを設定から共有すると、%(brand)sから招待を受け取れます。",
-        "invite_reject_ignore": "拒否した上で、このユーザーを無視",
         "invite_sent_to_email": "招待が%(email)sに送信されました",
         "invite_sent_to_email_room": "ルーム %(roomName)sへの招待がメールアドレス %(email)s へ送信されました",
         "invite_subtitle": "<userName/>があなたを招待しています",
@@ -2142,7 +2099,6 @@
             "remove_msisdn_prompt": "%(phone)sを削除しますか?",
             "spell_check_locale_placeholder": "ロケールを選択"
         },
-        "image_thumbnails": "画像のプレビューまたはサムネイルを表示",
         "inline_url_previews_default": "既定でインラインURLプレビューを有効にする",
         "inline_url_previews_room": "このルームの参加者のために既定でURLプレビューを有効にする",
         "inline_url_previews_room_account": "このルームのURLプレビューを有効にする(あなたにのみ適用)",
@@ -2248,50 +2204,16 @@
         "prompt_invite": "不正の可能性があるMatrix IDに招待を送信する前に確認",
         "replace_plain_emoji": "自動的にプレーンテキストの絵文字を置き換える",
         "security": {
-            "4s_public_key_in_account_data": "アカウントデータ内",
-            "4s_public_key_status": "機密ストレージの公開鍵:",
-            "backup_key_cached_status": "バックアップキーのキャッシュ:",
-            "backup_key_stored_status": "バックアップキーの保存:",
-            "backup_key_unexpected_type": "予期しない種類",
-            "backup_key_well_formed": "正常な形式です",
-            "backup_keys_description": "セッションにアクセスできなくなる場合に備えて、アカウントデータと暗号鍵をバックアップしましょう。鍵は一意のセキュリティーキーで保護されます。",
             "bulk_options_accept_all_invites": "%(invitedRooms)sの全ての招待を承認",
             "bulk_options_reject_all_invites": "%(invitedRooms)sの全ての招待を拒否",
             "bulk_options_section": "一括オプション",
-            "cross_signing_cached": "ローカルでキャッシュ",
-            "cross_signing_homeserver_support": "ホームサーバーの対応状況:",
-            "cross_signing_homeserver_support_exists": "対応",
-            "cross_signing_in_4s": "機密ストレージ内",
-            "cross_signing_in_memory": "メモリー内",
-            "cross_signing_master_private_Key": "マスター秘密鍵:",
-            "cross_signing_not_cached": "ローカルにありません",
-            "cross_signing_not_found": "ありません",
-            "cross_signing_not_in_4s": "ストレージ内にありません",
-            "cross_signing_not_stored": "保存されていません",
-            "cross_signing_private_keys": "クロス署名の秘密鍵:",
-            "cross_signing_public_keys": "クロス署名の公開鍵:",
-            "cross_signing_self_signing_private_key": "自己署名の秘密鍵:",
-            "cross_signing_user_signing_private_key": "ユーザー署名の秘密鍵:",
-            "cryptography_section": "暗号",
-            "delete_backup": "バックアップを削除",
-            "delete_backup_confirm_description": "本当によろしいですか? もし鍵が正常にバックアップされていない場合、暗号化されたメッセージにアクセスできなくなります。",
             "e2ee_default_disabled_warning": "サーバー管理者は、非公開のルームとダイレクトメッセージで既定でエンドツーエンド暗号化を無効にしています。",
             "enable_message_search": "暗号化されたルームでメッセージの検索を有効にする",
             "encryption_section": "暗号化",
-            "error_loading_key_backup_status": "鍵のバックアップの状態を読み込めません",
-            "export_megolm_keys": "ルームのエンドツーエンド暗号鍵をエクスポート",
             "ignore_users_empty": "無視しているユーザーはいません。",
             "ignore_users_section": "無視しているユーザー",
-            "import_megolm_keys": "ルームのエンドツーエンド暗号鍵をインポート",
-            "key_backup_active": "このセッションは鍵をバックアップしています。",
-            "key_backup_active_version_none": "なし",
             "key_backup_algorithm": "アルゴリズム:",
-            "key_backup_complete": "全ての鍵がバックアップされています",
             "key_backup_connect": "このセッションを鍵のバックアップに接続",
-            "key_backup_connect_prompt": "サインアウトする前に、このセッションにだけある鍵を失わないよう、セッションを鍵のバックアップに接続しましょう。",
-            "key_backup_in_progress": "%(sessionsRemaining)s個の鍵をバックアップしています…",
-            "key_backup_inactive": "このセッションでは<b>鍵をバックアップしていません</b>が、復元に使用したり、今後鍵を追加したりできるバックアップがあります。",
-            "key_backup_inactive_warning": "鍵は<b>このセッションからバックアップされていません</b>。",
             "message_search_disable_warning": "無効にすると、暗号化されたルームのメッセージは検索結果に表示されません。",
             "message_search_disabled": "検索結果の表示用に、暗号化されたメッセージをローカルに安全にキャッシュしています。",
             "message_search_enabled": {
@@ -2311,13 +2233,7 @@
             "message_search_unsupported": "暗号化されたメッセージの安全なキャッシュをローカルに保存するためのコンポーネントが%(brand)sにありません。この機能を試してみたい場合は、<nativeLink>検索コンポーネントが追加された</nativeLink>%(brand)sデスクトップのカスタム版をビルドしてください。",
             "message_search_unsupported_web": "Webブラウザー上で動作する%(brand)sは、暗号化メッセージの安全なキャッシュをローカルに保存できません。<desktopLink>%(brand)s デスクトップ</desktopLink>を使用すると、暗号化メッセージを検索結果に表示することができます。",
             "record_session_details": "クライアントの名称、バージョン、URLを記録し、セッションマネージャーでより容易にセッションを認識できるよう設定",
-            "restore_key_backup": "バックアップから復元",
-            "secret_storage_not_ready": "準備ができていません",
-            "secret_storage_ready": "準備ができました",
-            "secret_storage_status": "機密ストレージ:",
             "send_analytics": "分析データを送信",
-            "session_id": "セッションID:",
-            "session_key": "セッションキー:",
             "strict_encryption": "このセッションでは、未認証のセッションに対して暗号化されたメッセージを送信しない"
         },
         "send_read_receipts": "開封確認メッセージを送信",
@@ -2532,8 +2448,6 @@
         "topic": "ルームのトピックを取得または設定",
         "topic_none": "このルームにはトピックがありません。",
         "topic_room_error": "ルームのトピックの取得に失敗しました:ルームを発見できません(%(roomId)s)",
-        "tovirtual": "このルームのバーチャルルームに移動(あれば)",
-        "tovirtual_not_found": "このルームのバーチャルルームはありません",
         "unban": "指定したIDのユーザーのブロックを解除",
         "unflip": "プレーンテキストメッセージの前に ┬──┬ ノ( ゜-゜ノ) を付ける",
         "unholdcall": "現在のルームの通話を保留から外す",
@@ -2652,7 +2566,6 @@
         "heading_without_query": "検索",
         "join_button_text": "%(roomAddress)sに参加",
         "keyboard_scroll_hint": "<arrows/>でスクロール",
-        "message_search_section_title": "その他の検索",
         "other_rooms_in_space": "%(spaceName)sの他のルーム",
         "public_rooms_label": "公開ルーム",
         "recent_searches_section_title": "最近の検索",
@@ -2661,7 +2574,6 @@
         "result_may_be_hidden_privacy_warning": "プライバシーの観点から表示していない結果があります",
         "result_may_be_hidden_warning": "いくつかの結果が表示されていない可能性があります",
         "search_dialog": "検索ダイアログ",
-        "search_messages_hint": "メッセージを検索する場合は、ルームの上に表示されるアイコン<icon/>をクリックしてください。",
         "spaces_title": "参加しているスペース",
         "start_group_chat_button": "グループチャットを開始"
     },
@@ -2928,7 +2840,9 @@
             "sent": "%(senderName)sが%(targetDisplayName)sをこのルームに招待しました。"
         },
         "m.room.tombstone": "%(senderDisplayName)sがこのルームをアップグレードしました。",
-        "m.room.topic": "%(senderDisplayName)sがトピックを\"%(topic)s\"に変更しました。",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)sがトピックを\"%(topic)s\"に変更しました。"
+        },
         "m.sticker": "%(senderDisplayName)sがステッカーを送信しました。",
         "m.video": {
             "error_decrypting": "動画を復号化する際にエラーが発生しました"
@@ -3192,14 +3106,6 @@
         "ban_room_confirm_title": "%(roomName)sからブロック",
         "ban_space_everything": "自分に可能な範囲で、全てのものからブロック",
         "ban_space_specific": "自分に可能な範囲で、特定のものからブロック",
-        "count_of_sessions": {
-            "other": "%(count)s個のセッション",
-            "one": "%(count)s個のセッション"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s件の認証済のセッション",
-            "one": "1件の認証済のセッション"
-        },
         "deactivate_confirm_action": "ユーザーを無効化",
         "deactivate_confirm_description": "このユーザーを無効化すると、このユーザーはログアウトし、再度ログインすることはできなくなります。また、現在参加している全てのルームから退出します。このアクションを元に戻すことはできません。このユーザーを無効化してもよろしいですか?",
         "deactivate_confirm_title": "ユーザーを無効化しますか?",
@@ -3210,15 +3116,12 @@
         "disinvite_button_room": "ルームへの招待を取り消す",
         "disinvite_button_room_name": "%(roomName)sへの招待を取り消す",
         "disinvite_button_space": "スペースへの招待を取り消す",
-        "edit_own_devices": "端末を編集",
         "error_ban_user": "ユーザーをブロックできませんでした",
         "error_deactivate": "ユーザーの無効化に失敗しました",
         "error_kicking_user": "ユーザーの追放に失敗しました",
         "error_mute_user": "ユーザーのミュートに失敗しました",
         "error_revoke_3pid_invite_description": "招待を取り消すことができませんでした。サーバーで一時的な問題が発生しているか、招待を取り消すための十分な権限がありません。",
         "error_revoke_3pid_invite_title": "招待を取り消せませんでした",
-        "hide_sessions": "セッションを隠す",
-        "hide_verified_sessions": "認証済のセッションを隠す",
         "ignore_confirm_description": "このユーザーのメッセージと招待を非表示にします。無視してよろしいですか?",
         "ignore_confirm_title": "%(user)sを無視",
         "invited_by": "%(sender)sからの招待",
@@ -3306,7 +3209,6 @@
         "hide_sidebar_button": "サイドバーを表示しない",
         "input_devices": "入力装置",
         "join_button_tooltip_call_full": "すみません ― この通話は現在満員です",
-        "join_button_tooltip_connecting": "接続しています",
         "maximise": "全画面",
         "misconfigured_server": "サーバーの不正な設定のため通話に失敗しました",
         "misconfigured_server_description": "安定した通話のために、ホームサーバー(<code>%(homeserverDomain)s</code>)の管理者にTURNサーバーの設定を依頼してください。",
diff --git a/src/i18n/strings/ka.json b/src/i18n/strings/ka.json
new file mode 100644
index 0000000000000000000000000000000000000000..514e069bfa519e35438cff68ca2f79a3b956dbbf
--- /dev/null
+++ b/src/i18n/strings/ka.json
@@ -0,0 +1,2956 @@
+{
+    "a11y": {
+        "user_menu": ""
+    },
+    "action": {
+        "accept": "მიღება",
+        "add": "რჩეულებში",
+        "add_existing_room": "დაამატეთ არსებული ოთახი",
+        "add_people": "ხალხის დამატება",
+        "apply": "მიმართვა",
+        "approve": "დადასტურება",
+        "ask_to_join": "მოითხოვეთ შეერთება",
+        "back": "უკან",
+        "call": "ზარი",
+        "cancel": "გაუქმება",
+        "change": "ცვლილება",
+        "clear": "გასუფთავება",
+        "click": "დააწკაპუნეთ",
+        "click_to_copy": "დააწკაპუნეთ კოპირებისთვის",
+        "close": "დახურვა",
+        "collapse": "აკეცვა",
+        "complete": "დასრულება",
+        "confirm": "დადასტურება",
+        "continue": "განაგრძეთ",
+        "copy": "კოპირება",
+        "copy_link": "ბმულის კოპირება",
+        "create": "შექმნა",
+        "create_a_room": "ოთახის შექმნა",
+        "decline": "უარყოფა",
+        "delete": "წაშლა",
+        "deny": "აკრძალვა",
+        "disable": "გამორთვა",
+        "disconnect": "კავშირის გაწყვეტა",
+        "dismiss": "დახურვა",
+        "done": "მზადაა",
+        "download": "ჩამოტვირთვა",
+        "edit": "რედაქტირება",
+        "enable": "ჩართვა",
+        "enter_fullscreen": "შედით სრულ ეკრანზე",
+        "exit_fullscreeen": "სრული ეკრანიდან გამოსვლა",
+        "expand": "გაფართოება",
+        "explore_public_rooms": "გამოიკვლიეთ საჯარო ოთახები",
+        "explore_rooms": "ოთახების დათავლიერება",
+        "export": "ექსპორტი",
+        "forward": "გადაგზავნა",
+        "go": "წადი",
+        "go_back": "Დაბრუნდი",
+        "got_it": "გასაგებია",
+        "hide_advanced": "გაფართოების დამალვა",
+        "hold": "გამართავს",
+        "ignore": "უგულებელყოფა",
+        "import": "",
+        "invite": "მოწვევა",
+        "invite_to_space": "მოიწვიე სივრცეში",
+        "invites_list": "მოწვევები",
+        "join": "შეუერთდი",
+        "learn_more": "Გაიგე მეტი",
+        "leave": "დატოვება",
+        "leave_room": "დატოვე ოთახი",
+        "logout": "გამოსვლა",
+        "manage": "Მართვა",
+        "maximise": "მაქსიმალური ზომა",
+        "mention": "ახსენეთ",
+        "minimise": "ჩაკემინიმიზაციაცვა",
+        "new_room": "ახალი ოთახი",
+        "new_video_room": "ახალი ვიდეო ოთახი",
+        "next": "შემდეგი",
+        "no": "არა",
+        "ok": "OK",
+        "pause": "პაუზა",
+        "pin": "$",
+        "play": "დაკვრა",
+        "proceed": "განაგრძეთ",
+        "quote": "ციტირება",
+        "react": "რეაგირება",
+        "refresh": "განახლება",
+        "register": "რეგისტრაცია",
+        "reload": "განაახლეთ",
+        "remove": "წაშლა",
+        "rename": "გადარქმევა",
+        "reply": "პასუხი",
+        "reply_in_thread": "პასუხი თემაში",
+        "report_content": "კონტენტის რეპორტი",
+        "resend": "გადაგზავნა",
+        "reset": "აღდგენა",
+        "resume": "Გაგრძელება",
+        "retry": "ხელახლა ცდა",
+        "review": "Მიმოხილვა",
+        "revoke": "გაუქმება",
+        "save": "შენახვა",
+        "search": "ძიება",
+        "send_report": "ანგარიშის გაგზავნა",
+        "share": "გაზიარება",
+        "show": "ჩვენება",
+        "show_advanced": "გაფართოების ჩვენება",
+        "show_all": "ყველას ჩვენება",
+        "sign_in": "შესვლა",
+        "sign_out": "გამოსვლა",
+        "skip": "გამოტოვება",
+        "start": "დაწყება",
+        "start_chat": "ჩატის დაწყება",
+        "start_new_chat": "დაიწყეთ ახალი ჩატი",
+        "stop": "შეწყვეტა",
+        "submit": "წარადგინეთ",
+        "subscribe": "გამოწერა",
+        "transfer": "გადაცემა",
+        "trust": "ნდობა",
+        "try_again": "სცადეთ კიდევ ერთხელ",
+        "unban": "გაუქმება",
+        "unignore": "უგულებელყოფა",
+        "unpin": "ჩამაგრების მოხსნა",
+        "unsubscribe": "გაუქმება",
+        "update": "განახლებულია",
+        "upgrade": "გაუმჯობესება",
+        "upload": "ატვირთეთ",
+        "verify": "$",
+        "view": "ნახვა",
+        "view_all": "იხილეთ ყველა",
+        "view_list": "სიის ნახვა",
+        "view_message": "შეტყობინების ნახვა",
+        "view_source": "Წყაროს ნახვა",
+        "yes": "დიახ",
+        "zoom_in": "მასშტაბირება",
+        "zoom_out": "მასშტაბირება"
+    },
+    "analytics": {
+        "accept_button": "ეს კარგია",
+        "bullet_1": "ჩვენ<Bold> ნუ</Bold> ნებისმიერი ანგარიშის მონაცემების ჩაწერა ან პროფილირება",
+        "bullet_2": "ჩვენ<Bold> ნუ</Bold> ინფორმაციის გაზიარება მესამე პირებთან",
+        "consent_migration": "თქვენ ადრე დათანხმდით მოგვაზიაროთ ანონიმური გამოყენების მონაცემები. ჩვენ ვაახლებთ, თუ როგორ მუშაობს ეს.",
+        "disable_prompt": "ამის გამორთვა ნებისმიერ დროს შეგიძლიათ პარამეტრებში",
+        "enable_prompt": "დაეხმარეთ გაუმჯობესებას%(analyticsOwner)s",
+        "learn_more": "გააზიარეთ ანონიმური მონაცემები, რათა დაგვეხმაროთ პრობლემების იდენტიფიცირებაში. არაფერი პირადული. არ არის მესამე მხარე.<LearnMoreLink> გაიგე მეტი</LearnMoreLink>",
+        "privacy_policy": "თქვენ შეგიძლიათ წაიკითხოთ ჩვენი ყველა პირობა<PrivacyPolicyUrl> აქ</PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "დაგვეხმარეთ პრობლემების ამოცნობაში და გაუმჯობესებაში%(analyticsOwner)s ანონიმური გამოყენების მონაცემების გაზიარებით. იმის გასაგებად, თუ როგორ იყენებენ ადამიანები მრავალ მოწყობილობას, ჩვენ გამოვქმნით შემთხვევით იდენტიფიკატორს, რომელიც გაზიარებულია თქვენი მოწყობილობების მიერ.",
+        "shared_data_heading": "ნებისმიერი შემდეგი მონაცემი შეიძლება იყოს გაზიარებული:"
+    },
+    "auth": {
+        "3pid_in_use": "ეს ელექტრონული ფოსტის მისამართი ან ტელეფონის ნომერი უკვე გამოიყენება.",
+        "account_clash": "თქვენი ახალი ანგარიში (%(newAccountId)s ) რეგისტრირებულია, მაგრამ თქვენ უკვე შესული ხართ სხვა ანგარიშზე (%(loggedInUserId)s ).",
+        "account_clash_previous_account": "გააგრძელეთ წინა ანგარიშით",
+        "account_deactivated": "ეს ანგარიში დეაქტივირებულია.",
+        "autodiscovery_generic_failure": "სერვერიდან ავტომატური აღმოჩენის კონფიგურაციის მიღება ვერ მოხერხდა",
+        "autodiscovery_hs_incompatible": "თქვენი სახლის სერვერი ძალიან ძველია და არ აქვს საჭირო მინიმალური API ვერსიის მხარდაჭერა. გთხოვთ, დაუკავშირდეთ თქვენი სერვერის მფლობელს, ან განაახლეთ თქვენი სერვერი.",
+        "autodiscovery_invalid": "სახლის სერვერის აღმოჩენის არასწორი პასუხი",
+        "autodiscovery_invalid_hs": "ჰომოსერვერის URL არ არის მართებული Matrix ჰომესერვერი",
+        "autodiscovery_invalid_hs_base_url": "არასწორი base_url m.homeserver-ისთვის",
+        "autodiscovery_invalid_is": "პირადობის სერვერის URL, როგორც ჩანს, არ არის სწორი საიდენტიფიკაციო სერვერი",
+        "autodiscovery_invalid_is_base_url": "არასწორი base_url m.identity_server-ისთვის",
+        "autodiscovery_invalid_is_response": "საიდენტიფიკაციო სერვერის აღმოჩენის არასწორი პასუხი",
+        "autodiscovery_invalid_json": "არასწორი JSON",
+        "autodiscovery_no_well_known": "კარგად ცნობილი JSON ფაილი ვერ მოიძებნა",
+        "autodiscovery_unexpected_error_hs": "მოულოდნელი შეცდომა სახლის სერვერის კონფიგურაციის გადაჭრისას",
+        "autodiscovery_unexpected_error_is": "იდენტიფიკაციის სერვერის კონფიგურაციის გადაჭრისას მოულოდნელი შეცდომა",
+        "captcha_description": "ამ სახლის სერვერს სურს დარწმუნდეს, რომ თქვენ არ ხართ რობოტი.",
+        "change_password_action": "Პაროლის შეცვლა",
+        "change_password_confirm_invalid": "პაროლები არ ემთხვევა",
+        "change_password_confirm_label": "Პაროლის დადასტურება",
+        "change_password_current_label": "Მიმდინარე პაროლი",
+        "change_password_empty": "პაროლები არ შეიძლება იყოს ცარიელი",
+        "change_password_error": "შეცდომა პაროლის შეცვლისას:%(error)s",
+        "change_password_mismatch": "ახალი პაროლები არ ემთხვევა",
+        "change_password_new_label": "Ახალი პაროლი",
+        "check_email_explainer": "მიჰყევით გაგზავნილ ინსტრუქციას<b>%(email)s</b>",
+        "check_email_resend_prompt": "არ მიგიღიათ?",
+        "check_email_resend_tooltip": "დამადასტურებელი ბმული ელფოსტა ხელახლა გაიგზავნა!",
+        "check_email_wrong_email_button": "ხელახლა შეიყვანეთ ელფოსტის მისამართი",
+        "check_email_wrong_email_prompt": "არასწორი ელფოსტის მისამართი?",
+        "continue_with_idp": "გააგრძელე%(provider)s",
+        "continue_with_sso": "გააგრძელე%(ssoButtons)s",
+        "country_dropdown": "ქვეყნის ჩამოსაშლელი სია",
+        "create_account_prompt": "ახალი აქ?<a> შექმენით ანგარიში</a>",
+        "create_account_title": "Შექმენი ანგარიში",
+        "email_discovery_text": "გამოიყენეთ ელფოსტა, რათა სურვილისამებრ იყოს აღმოჩენილი არსებული კონტაქტების მიერ.",
+        "email_field_label": "ელფოსტა",
+        "email_field_label_invalid": "არ ჰგავს მოქმედ ელფოსტის მისამართს",
+        "email_field_label_required": "შეიყვანეთ ელ.ფოსტის მისამართი",
+        "email_help_text": "დაამატეთ ელფოსტა, რათა შეძლოთ პაროლის გადაყენება.",
+        "email_phone_discovery_text": "გამოიყენეთ ელფოსტა ან ტელეფონი, რათა სურვილისამებრ აღმოჩნდეთ არსებული კონტაქტების მიერ.",
+        "enter_email_explainer": "<b>%(homeserver)s</b>გამოგიგზავნით დამადასტურებელ ბმულს, რომელიც საშუალებას მოგცემთ აღადგინოთ თქვენი პაროლი.",
+        "enter_email_heading": "შეიყვანეთ თქვენი ელფოსტა პაროლის აღსადგენად",
+        "failed_connect_identity_server": "პირადობის სერვერთან დაკავშირება შეუძლებელია",
+        "failed_connect_identity_server_other": "შეგიძლიათ შეხვიდეთ, მაგრამ ზოგიერთი ფუნქცია მიუწვდომელი იქნება მანამ, სანამ საიდენტიფიკაციო სერვერი არ დაბრუნდება ონლაინ რეჟიმში. თუ კვლავ ხედავთ ამ გაფრთხილებას, შეამოწმეთ თქვენი კონფიგურაცია ან დაუკავშირდით სერვერის ადმინისტრატორს.",
+        "failed_connect_identity_server_register": "შეგიძლიათ დარეგისტრირდეთ, მაგრამ ზოგიერთი ფუნქცია მიუწვდომელი იქნება მანამ, სანამ საიდენტიფიკაციო სერვერი არ დაბრუნდება ონლაინ რეჟიმში. თუ კვლავ ხედავთ ამ გაფრთხილებას, შეამოწმეთ თქვენი კონფიგურაცია ან დაუკავშირდით სერვერის ადმინისტრატორს.",
+        "failed_connect_identity_server_reset_password": "შეგიძლიათ პაროლის გადატვირთვა, მაგრამ ზოგიერთი ფუნქცია მიუწვდომელი იქნება მანამ, სანამ საიდენტიფიკაციო სერვერი არ დაბრუნდება ონლაინ რეჟიმში. თუ კვლავ ხედავთ ამ გაფრთხილებას, შეამოწმეთ თქვენი კონფიგურაცია ან დაუკავშირდით სერვერის ადმინისტრატორს.",
+        "failed_homeserver_discovery": "სახლის სერვერის აღმოჩენა ვერ მოხერხდა",
+        "failed_query_registration_methods": "მხარდაჭერილი რეგისტრაციის მეთოდების მოთხოვნა შეუძლებელია.",
+        "failed_soft_logout_auth": "ხელახალი ავტორიზაცია ვერ მოხერხდა",
+        "failed_soft_logout_homeserver": "ხელახალი ავტორიზაცია ვერ მოხერხდა სახლის სერვერის პრობლემის გამო",
+        "forgot_password_email_invalid": "ელფოსტის მისამართი, როგორც ჩანს, არასწორია.",
+        "forgot_password_email_required": "თქვენს ანგარიშთან დაკავშირებული ელ.ფოსტის მისამართი უნდა შეიყვანოთ.",
+        "forgot_password_prompt": "დაგავიწყდათ პაროლი?",
+        "forgot_password_send_email": "ელ.ფოსტის გაგზავნა",
+        "identifier_label": "შედით სისტემაში",
+        "incorrect_credentials": "არასწორი მომხმარებლის სახელი და/ან პაროლი.",
+        "incorrect_credentials_detail": "გთხოვთ გაითვალისწინოთ, რომ შედიხართ%(hs)s სერვერი და არა matrix.org.",
+        "incorrect_password": "არასწორი პაროლი",
+        "log_in_new_account": "<a>შესვლა</a> თქვენს ახალ ანგარიშზე.",
+        "logout_dialog": {
+            "description": "დარწმუნებული ხართ, რომ გსურთ გამოსვლა?",
+            "megolm_export": "გასაღებების ხელით ექსპორტი",
+            "setup_key_backup_title": "თქვენ დაკარგავთ წვდომას თქვენს დაშიფრულ შეტყობინებებზე",
+            "setup_secure_backup_description_1": "დაშიფრული შეტყობინებები დაცულია ბოლოდან ბოლომდე დაშიფვრით. მხოლოდ თქვენ და მიმღებ(ებ)ს გაქვთ ამ შეტყობინებების წაკითხვის გასაღები.",
+            "setup_secure_backup_description_2": "როდესაც გამოხვალთ, ეს გასაღებები წაიშლება ამ მოწყობილობიდან, რაც ნიშნავს, რომ თქვენ ვერ შეძლებთ დაშიფრული შეტყობინებების წაკითხვას, თუ არ გაქვთ მათი გასაღებები თქვენს სხვა მოწყობილობებზე, ან არ შექმნით მათ სარეზერვო ასლს სერვერზე.",
+            "skip_key_backup": "არ მინდა ჩემი დაშიფრული შეტყობინებები",
+            "use_key_backup": "დაიწყეთ Key Backup-ის გამოყენება"
+        },
+        "misconfigured_body": "ჰკითხეთ თქვენს%(brand)s ადმინი შესამოწმებლად<a> თქვენი კონფიგურაცია</a> არასწორი ან დუბლიკატი ჩანაწერებისთვის.",
+        "misconfigured_title": "შენი%(brand)s არასწორად არის კონფიგურირებული",
+        "msisdn_field_description": "სხვა მომხმარებლებს შეუძლიათ მოგიწვიონ ოთახებში თქვენი საკონტაქტო მონაცემების გამოყენებით",
+        "msisdn_field_label": "ტელეფონი",
+        "msisdn_field_number_invalid": "ეს ტელეფონის ნომერი არ გამოიყურება საკმაოდ სწორად, გთხოვთ, შეამოწმოთ და ხელახლა სცადოთ",
+        "msisdn_field_required_invalid": "შეიყვანეთ ტელეფონის ნომერი",
+        "no_hs_url_provided": "სახლის სერვერის URL არ არის მოწოდებული",
+        "oidc": {
+            "error_title": "ჩვენ ვერ შევედით თქვენ"
+        },
+        "password_field_keep_going_prompt": "გააგრძელე…",
+        "password_field_label": "შეიყვანეთ პაროლი",
+        "password_field_strong_label": "კარგი, ძლიერი პაროლი!",
+        "password_field_weak_label": "პაროლი დაშვებულია, მაგრამ არაუსაფრთხო",
+        "phone_label": "",
+        "phone_optional_label": "ტელეფონი (სურვილისამებრ)",
+        "qr_code_login": {
+            "completing_setup": "თქვენი ახალი მოწყობილობის დაყენების დასრულება",
+            "error_unexpected": "მოხდა მოულოდნელი შეცდომა. თქვენი სხვა მოწყობილობის დაკავშირების მოთხოვნა გაუქმდა.",
+            "scan_code_instruction": "QR კოდის სკანირება სხვა მოწყობილობით",
+            "scan_qr_code": "შედით QR კოდით",
+            "select_qr_code": "აირჩიეთ &quot;%(scanQRCode)s &quot;",
+            "waiting_for_device": "ელოდება მოწყობილობის შესვლას"
+        },
+        "register_action": "ანგარიშის შექმნა",
+        "registration": {
+            "continue_without_email_description": "უბრალოდ, თუ არ დაამატებთ ელფოსტას და არ დაგავიწყდებათ პაროლი, შეგიძლიათ<b> სამუდამოდ დაკარგეთ წვდომა თქვენს ანგარიშზე</b> .",
+            "continue_without_email_field_label": "ელფოსტა (სურვილისამებრ)",
+            "continue_without_email_title": "გაგრძელება ელფოსტის გარეშე"
+        },
+        "registration_disabled": "რეგისტრაცია გათიშულია ამ სახლის სერვერზე.",
+        "registration_msisdn_field_required_invalid": "შეიყვანეთ ტელეფონის ნომერი (აუცილებელია ამ სახლის სერვერზე)",
+        "registration_successful": "რეგისტრაცია წარმატებით დასრულდა",
+        "registration_username_in_use": "ვიღაცას უკვე აქვს ეს მომხმარებლის სახელი. სცადეთ სხვა, ან თუ ეს თქვენ ხართ, შედით ქვემოთ.",
+        "registration_username_unable_check": "შეუძლებელია იმის შემოწმება, არის თუ არა მომხმარებლის სახელი აღებული. სცადეთ მოგვიანებით.",
+        "registration_username_validation": "გამოიყენეთ მხოლოდ მცირე ასოები, რიცხვები, ტირეები და ხაზგასმული",
+        "reset_password": {
+            "confirm_new_password": "Დაადასტურეთ ახალი პაროლი",
+            "devices_logout_success": "",
+            "other_devices_logout_warning_1": "თქვენი მოწყობილობებიდან გასვლა წაშლის მათზე შენახულ შეტყობინებების დაშიფვრის გასაღებებს, რაც დაშიფრული ჩეთის ისტორიას წაუკითხავად გახდის.",
+            "other_devices_logout_warning_2": "თუ გსურთ შეინარჩუნოთ წვდომა თქვენს ჩეთის ისტორიაზე დაშიფრულ ოთახებში, დააყენეთ გასაღების სარეზერვო ასლი ან თქვენი შეტყობინებების კლავიშების ექსპორტი სხვა მოწყობილობიდან გაგრძელებამდე.",
+            "password_not_entered": "ახალი პაროლი უნდა შეიყვანოთ.",
+            "passwords_mismatch": "ახალი პაროლები უნდა ემთხვეოდეს ერთმანეთს.",
+            "rate_limit_error": "ძალიან ბევრი მცდელობა მოკლე დროში. დაელოდეთ ცოტა ხანს ხელახლა ცდამდე.",
+            "rate_limit_error_with_time": "ძალიან ბევრი მცდელობა მოკლე დროში. ხელახლა სცადეთ შემდეგ%(timeout)s .",
+            "reset_successful": "თქვენი პაროლი აღდგენილია.",
+            "return_to_login": "დაბრუნება შესვლის ეკრანზე",
+            "sign_out_other_devices": "გადით ყველა მოწყობილობიდან"
+        },
+        "reset_password_action": "პაროლის აღდგენა",
+        "reset_password_button": "დაგავიწყდა შენი პაროლი?",
+        "reset_password_email_field_description": "გამოიყენეთ ელფოსტის მისამართი თქვენი ანგარიშის აღსადგენად",
+        "reset_password_email_field_required_invalid": "შეიყვანეთ ელფოსტის მისამართი (აუცილებელია ამ სახლის სერვერზე)",
+        "reset_password_email_not_associated": "როგორც ჩანს, თქვენი ელფოსტის მისამართი არ ასოცირდება Matrix ID-თან ამ სახლის სერვერზე.",
+        "reset_password_email_not_found_title": "ეს ელფოსტის მისამართი ვერ მოიძებნა",
+        "reset_password_title": "გადააყენეთ თქვენი პაროლი",
+        "server_picker_custom": "სხვა სახლის სერვერი",
+        "server_picker_description": "თქვენ შეგიძლიათ გამოიყენოთ პერსონალური სერვერის ვარიანტები სხვა Matrix სერვერებზე შესასვლელად, სახლის სერვერის სხვა URL-ის მითითებით. ეს საშუალებას გაძლევთ გამოიყენოთ%(brand)s არსებული Matrix ანგარიშით სხვა ჰომოსერვერზე.",
+        "server_picker_description_matrix.org": "შეუერთდით მილიონებს უფასოდ უდიდეს საჯარო სერვერზე",
+        "server_picker_dialog_title": "გადაწყვიტეთ სად განთავსდება თქვენი ანგარიში",
+        "server_picker_explainer": "გამოიყენეთ თქვენი სასურველი Matrix ჰომესერვერი, თუ გაქვთ, ან მასპინძლობს საკუთარ თავს.",
+        "server_picker_failed_validate_homeserver": "სახლის სერვერის დადასტურება შეუძლებელია",
+        "server_picker_intro": "ჩვენ ვეძახით იმ ადგილებს, სადაც შეგიძლიათ თქვენი ანგარიშის ჰოსტინგი &quot;homeservers&quot;.",
+        "server_picker_invalid_url": "არასწორი ლინკი",
+        "server_picker_learn_more": "სახლის სერვერების შესახებ",
+        "server_picker_matrix.org": "Matrix.org არის ყველაზე დიდი საჯარო ჰომოსერვერი მსოფლიოში, ამიტომ ის ბევრისთვის კარგი ადგილია.",
+        "server_picker_required": "მიუთითეთ სახლის სერვერი",
+        "server_picker_title": "შედით თქვენს სახლის სერვერზე",
+        "server_picker_title_default": "სერვერის პარამეტრები",
+        "server_picker_title_registration": "მასპინძლის ანგარიში ჩართულია",
+        "session_logged_out_description": "უსაფრთხოების მიზნით, ეს სესია გამორთულია. გთხოვთ შეხვიდეთ ხელახლა.",
+        "session_logged_out_title": "გამოსული",
+        "set_email": {
+            "description": "ეს საშუალებას მოგცემთ აღადგინოთ პაროლი და მიიღოთ შეტყობინებები.",
+            "verification_pending_description": "გთხოვთ, შეამოწმოთ თქვენი ელფოსტა და დააწკაპუნოთ მასში არსებულ ბმულზე. როდესაც ეს გაკეთდება, დააჭირეთ გაგრძელება.",
+            "verification_pending_title": "დადასტურება მოლოდინშია"
+        },
+        "set_email_prompt": "გსურთ ელ.ფოსტის მისამართის დაყენება?",
+        "sign_in_description": "გამოიყენეთ თქვენი ანგარიში გასაგრძელებლად.",
+        "sign_in_instead": "ამის ნაცვლად შედით სისტემაში",
+        "sign_in_instead_prompt": "უკვე გაქვთ ანგარიში?<a> შედით აქ</a>",
+        "sign_in_or_register": "შედით ან შექმენით ანგარიში",
+        "sign_in_or_register_description": "გამოიყენეთ თქვენი ანგარიში ან შექმენით ახალი გასაგრძელებლად.",
+        "sign_in_prompt": "გაქვთ ანგარიში?<a> შესვლა</a>",
+        "sign_in_with_sso": "შედით ერთი შესვლით",
+        "signing_in": "შესვლა…",
+        "soft_logout": {
+            "clear_data_button": "ყველა მონაცემის გასუფთავება",
+            "clear_data_description": "ამ სესიიდან ყველა მონაცემის გასუფთავება მუდმივია. დაშიფრული შეტყობინებები დაიკარგება, თუ მათი გასაღებები არ იქნება სარეზერვო ასლი.",
+            "clear_data_title": "გსურთ ამ სესიის ყველა მონაცემის გასუფთავება?"
+        },
+        "soft_logout_heading": "თქვენ გასული ხართ",
+        "soft_logout_intro_password": "შეიყვანეთ პაროლი სისტემაში შესასვლელად და თქვენს ანგარიშზე წვდომის აღსადგენად.",
+        "soft_logout_intro_sso": "შედით სისტემაში და აღადგინეთ წვდომა თქვენს ანგარიშზე.",
+        "soft_logout_intro_unsupported_auth": "თქვენ არ შეგიძლიათ შეხვიდეთ თქვენს ანგარიშში. გთხოვთ, დაუკავშირდეთ თქვენი სახლის სერვერის ადმინისტრატორს დამატებითი ინფორმაციისთვის.",
+        "soft_logout_subheading": "პირადი მონაცემების გასუფთავება",
+        "soft_logout_warning": "გაფრთხილება: თქვენი პერსონალური მონაცემები (მათ შორის დაშიფვრის გასაღებები) კვლავ ინახება ამ სესიაზე. გაასუფთავეთ, თუ დაასრულეთ ამ სესიის გამოყენება ან გსურთ სხვა ანგარიშში შესვლა.",
+        "sso": "ერთჯერადი ავტორიზაცია",
+        "sso_failed_missing_storage": "ჩვენ ვთხოვეთ ბრაუზერს დაემახსოვრებინა, რომელ ჰომესერვერს იყენებდით, რომ შეხვიდეთ სისტემაში, მაგრამ სამწუხაროდ თქვენს ბრაუზერს დაავიწყდა ეს. გადადით შესვლის გვერდზე და სცადეთ ხელახლა.",
+        "sso_or_username_password": "%(ssoButtons)sან%(usernamePassword)s",
+        "sync_footer_subtitle": "თუ ბევრ ოთახს შეუერთდით, ამას შეიძლება გარკვეული დრო დასჭირდეს",
+        "syncing": "სინქრონიზაცია…",
+        "uia": {
+            "code": "კოდი",
+            "email": "თქვენი ანგარიშის შესაქმნელად, გახსენით ბმული იმ ელფოსტაში, რომელსაც ახლახან გავგზავნეთ%(emailAddress)s .",
+            "email_auth_header": "შეამოწმეთ თქვენი ელფოსტა გასაგრძელებლად",
+            "email_resend_prompt": "არ მიგიღიათ?<a> ხელახლა გაგზავნეთ</a>",
+            "email_resent": "გაბრაზება!",
+            "fallback_button": "დაიწყეთ ავტორიზაცია",
+            "msisdn": "ტექსტური შეტყობინება გაიგზავნა%(msisdn)s",
+            "msisdn_token_incorrect": "ჟეტონი არასწორია",
+            "msisdn_token_prompt": "გთხოვთ შეიყვანოთ კოდი, რომელიც შეიცავს:",
+            "password_prompt": "დაადასტურეთ თქვენი ვინაობა ქვემოთ თქვენი ანგარიშის პაროლის შეყვანით.",
+            "recaptcha_missing_params": "გამოტოვებულია captcha-ს საჯარო გასაღები სახლის სერვერის კონფიგურაციაში. გთხოვთ, შეატყობინოთ ამის შესახებ თქვენი სახლის სერვერის ადმინისტრატორს.",
+            "registration_token_label": "რეგისტრაციის ნიშანი",
+            "registration_token_prompt": "შეიყვანეთ სარეგისტრაციო ჟეტონი, რომელიც მოწოდებულია სახლის სერვერის ადმინისტრატორის მიერ.",
+            "sso_body": "ელ. ფოსტის მისამართის დასადასტურებლად გამოიყენე ერთჯერადი ავტორიზაცია, საკუთარი იდენტობის დასადასტურებლად.",
+            "sso_failed": "თქვენი ვინაობის დადასტურებისას მოხდა რაღაც შეცდომა. გააუქმეთ და სცადეთ ხელახლა.",
+            "sso_postauth_body": "დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე თქვენი ვინაობის დასადასტურებლად.",
+            "sso_postauth_title": "დაადასტურეთ გასაგრძელებლად",
+            "sso_preauth_body": "გასაგრძელებლად გამოიყენეთ Single Sign On თქვენი ვინაობის დასადასტურებლად.",
+            "sso_title": "გასაგრძელებლად გამოიყენე ერთჯერადი ავტორიზაცია",
+            "terms": "გთხოვთ, გადახედოთ და დაეთანხმოთ ამ სახლის სერვერის პოლიტიკას:",
+            "terms_invalid": "გთხოვთ, გადახედოთ და დაეთანხმოთ სახლის სერვერის ყველა პოლიტიკას"
+        },
+        "unsupported_auth": "ეს სახლის სერვერი არ გვთავაზობს შესვლის ნაკადებს, რომლებიც მხარდაჭერილია ამ კლიენტის მიერ.",
+        "unsupported_auth_email": "ამ სახლის სერვერს არ აქვს ელ.ფოსტის მისამართის გამოყენებით შესვლის მხარდაჭერა.",
+        "unsupported_auth_msisdn": "ამ სერვერს არ აქვს ტელეფონის ნომრით ავტორიზაციის მხარდაჭერა.",
+        "username_field_required_invalid": "შეიყვანეთ მომხმარებლის სახელი",
+        "username_in_use": "ვიღაცას უკვე აქვს ეს მომხმარებლის სახელი, გთხოვთ სცადოთ სხვა.",
+        "verify_email_explainer": "პაროლის გადაყენებამდე უნდა ვიცოდეთ, რომ ეს თქვენ ხართ. დააწკაპუნეთ ბმულზე იმ ელფოსტაში, რომელსაც ახლახან გავგზავნეთ<b>%(email)s</b>",
+        "verify_email_heading": "გადაამოწმეთ თქვენი ელფოსტა გასაგრძელებლად"
+    },
+    "bug_reporting": {
+        "additional_context": "თუ არსებობს დამატებითი კონტექსტი, რომელიც დაგვეხმარება საკითხის ანალიზში, მაგალითად, რას აკეთებდით იმ დროს, ოთახის ID, მომხმარებლის ID და ა.შ., გთხოვთ, მიუთითოთ ეს საკითხები აქ.",
+        "before_submitting": "ჟურნალების გაგზავნამდე, თქვენ უნდა<a> შექმენით GitHub საკითხი</a> თქვენი პრობლემის აღსაწერად.",
+        "collecting_information": "აპლიკაციის ვერსიის ინფორმაციის შეგროვება",
+        "collecting_logs": "მორების შეგროვება",
+        "create_new_issue": "გთხოვთ<newIssueLink> ახალი საკითხის შექმნა</newIssueLink> GitHub-ზე, რათა გამოვიკვლიოთ ეს შეცდომა.",
+        "download_logs": "ჩამოტვირთეთ ჟურნალები",
+        "downloading_logs": "ლოგების ჩამოტვირთვა",
+        "error_empty": "გთხოვთ, გვითხრათ, რა მოხდა არასწორედ, ან, უკეთესი, შექმენით GitHub პრობლემა, რომელიც აღწერს პრობლემას.",
+        "github_issue": "GitHub-ის პრობლემა",
+        "introduction": "თუ თქვენ წარადგინეთ ხარვეზი GitHub-ის მეშვეობით, გამართვის ჟურნალები დაგვეხმარება პრობლემის გარკვევაში. ",
+        "log_request": "მომავალში ამის თავიდან ასაცილებლად, გთხოვთ<a> გამოგვიგზავნეთ ჟურნალები</a> .",
+        "logs_sent": "ჟურნალები გაიგზავნა",
+        "matrix_security_issue": "Matrix-თან დაკავშირებული უსაფრთხოების საკითხის შესახებ შეტყობინებისთვის გთხოვთ, წაიკითხოთ Matrix.org<a> უსაფრთხოების გამჟღავნების პოლიტიკა</a> .",
+        "preparing_download": "ემზადება ჟურნალების ჩამოსატვირთად",
+        "preparing_logs": "ემზადება ჟურნალების გასაგზავნად",
+        "send_logs": "მორების გაგზავნა",
+        "submit_debug_logs": "გამართვის ჟურნალების გაგზავნა",
+        "textarea_label": "შენიშვნები",
+        "thank_you": "გმადლობთ!",
+        "title": "შეცდომის შესახებ შეტყობინება",
+        "unsupported_browser": "შეხსენება: თქვენი ბრაუზერი არ არის მხარდაჭერილი, ამიტომ თქვენი გამოცდილება შეიძლება არაპროგნოზირებადი იყოს.",
+        "uploading_logs": "ლოგების ატვირთვა",
+        "waiting_for_server": "ველოდებით პასუხს სერვერისგან"
+    },
+    "cannot_invite_without_identity_server": "შეუძლებელია მომხმარებლის ელ.ფოსტით დამატება პირადობის სერვერის გარეშე. თქვენ შეგიძლიათ დაუკავშირდეთ ერთ-ერთს \"პარამეტრებში\".",
+    "cannot_reach_homeserver": "სახლის სერვერთან მისვლა შეუძლებელია",
+    "cannot_reach_homeserver_detail": "დარწმუნდით, რომ გაქვთ სტაბილური ინტერნეტ კავშირი, ან დაუკავშირდით სერვერის ადმინისტრატორს",
+    "chat_card_back_action_label": "დაბრუნება ჩატში",
+    "common": {
+        "access_token": "წვდომის ჟეტონი",
+        "accessibility": "მარტივი წვდომა",
+        "advanced": "გაფართოებული",
+        "analytics": "ანალიტიკა",
+        "and_n_others": {
+            "one": "და ერთი სხვა...",
+            "other": "და%(count)s სხვები..."
+        },
+        "appearance": "გარეგნობა",
+        "application": "განაცხადი",
+        "are_you_sure": "Დარწმუნებული ხარ?",
+        "attachment": "Მიმაგრებული ფაილი",
+        "authentication": "ავთენტიფიკაცია",
+        "avatar": "ავატარი",
+        "beta": "ბეტა",
+        "camera": "კამერა",
+        "cameras": "კამერები",
+        "capabilities": "შესაძლებლობები",
+        "copied": "დაკოპირებულია!",
+        "credits": "კრედიტები",
+        "dark": "მუქი",
+        "description": "აღწერა",
+        "deselect_all": "Გააუქმეთ ყველა მონიშვნა",
+        "device": "მოწყობილობა",
+        "edited": "რედაქტირებულია",
+        "email_address": "Ელექტრონული მისამართი",
+        "emoji": "სიცილაკები",
+        "encrypted": "დაშიფრულია",
+        "encryption_enabled": "დაშიფვრა ჩართულია",
+        "error": "შეცდომა",
+        "faq": "ხშირად დასმული კითხვები",
+        "favourites": "რჩეულები",
+        "feedback": "კავშირი",
+        "filter_results": "შედეგების გაფილტვრა",
+        "forward_message": "შეტყობინების გადაგზავნა",
+        "general": "გენერალი",
+        "go_to_settings": "გადადით პარამეტრებში",
+        "guest": "სტუმარი",
+        "help": "დახმარება",
+        "historical": "ისტორიული",
+        "home": "მთავარი",
+        "homeserver": "სახლის სერვერი",
+        "identity_server": "პირადობის სერვერი",
+        "image": "სურათი",
+        "integration_manager": "ინტეგრაციის მენეჯერი",
+        "joined": "შეუერთდა",
+        "labs": "ლაბორატორიები",
+        "legal": "კანონიერი",
+        "light": "ღია",
+        "loading": "იტვირთება…",
+        "location": "მდებარეობა",
+        "low_priority": "დაბალი პრიორიტეტი",
+        "matrix": "Matrix",
+        "message": "შეტყობინება",
+        "message_layout": "შეტყობინებების ფორმა",
+        "microphone": "მიკროფონი",
+        "model": "მოდელი",
+        "modern": "თანამედროვე",
+        "mute": "დადუმება",
+        "n_members": {
+            "one": "%(count)sწევრი",
+            "other": "%(count)sწევრები"
+        },
+        "n_rooms": {
+            "one": "%(count)sოთახი",
+            "other": "%(count)sოთახები"
+        },
+        "name": "სახელი",
+        "no_results": "შედეგი არ არის",
+        "no_results_found": "Შედეგები არ მოიძებნა",
+        "not_trusted": "არ არის სანდო",
+        "off": "გამორთულია",
+        "offline": "ხაზგარეშე",
+        "on": "ჩართულია",
+        "options": "პარამეტრები",
+        "orphan_rooms": "სხვა ოთახები",
+        "password": "პაროლი",
+        "people": "ხალხი",
+        "preferences": "პარამეტრები",
+        "presence": "ყოფნა",
+        "preview_message": "ჰეი შენ. შენ საუკეთესო ხარ!",
+        "privacy": "კონფიდენციალურობა",
+        "private": "პირადი",
+        "private_room": "პირადი ოთახი",
+        "private_space": "კერძო ფართი",
+        "profile": "პროფილი",
+        "public": "საჯარო",
+        "public_room": "საჯარო ოთახი",
+        "public_space": "საჯარო სივრცე",
+        "qr_code": "QR კოდი",
+        "random": "შემთხვევითი",
+        "reactions": "რეაქციები",
+        "report_a_bug": "ხარვეზის შეტყობინება",
+        "room": "ოთახი",
+        "room_name": "ოთახის სახელი",
+        "rooms": "ოთახები",
+        "saving": "მიმდინარეობს შენახვა…",
+        "secure_backup": "უსაფრთხო სარეზერვო",
+        "select_all": "Მონიშნე ყველა",
+        "server": "სერვერი",
+        "settings": "პარამეტრები",
+        "setup_secure_messages": "დააყენეთ უსაფრთხო შეტყობინებები",
+        "show_more": "მეტის ჩვენება",
+        "someone": "ვინმე",
+        "space": "შორისი",
+        "spaces": "ფართები",
+        "sticker": "სტიკერი",
+        "stickerpack": "სტიკერპაკეტი",
+        "success": "წარმატება",
+        "suggestions": "შეთავაზებები",
+        "support": "დახმარება",
+        "system_alerts": "სისტემის გაფრთხილებები",
+        "theme": "თემა",
+        "thread": "თემა",
+        "threads": "ძაფები",
+        "timeline": "ვადები",
+        "unavailable": "მიუწვდომელია",
+        "unencrypted": "არ არის დაშიფრული",
+        "unmute": "დადუმების გაუქმება",
+        "unnamed_room": "უსახელო ოთახი",
+        "unnamed_space": "უსახელო სივრცე",
+        "unverified": "გადაუმოწმებელი",
+        "user": "მომხმარებელი:",
+        "user_avatar": "პროფილის სურათი",
+        "username": "მომხმარებლის სახელი",
+        "verification_cancelled": "დადასტურება გაუქმდა",
+        "verified": "დამოწმებულია",
+        "version": "ვერსია",
+        "video": "ვიდეო",
+        "video_room": "ვიდეო ოთახი",
+        "view_message": "შეტყობინების ნახვა",
+        "warning": "გაფრთხილება"
+    },
+    "composer": {
+        "format_bold": "თამამი",
+        "format_code_block": "კოდის ბლოკი",
+        "format_decrease_indent": "შეწევის შემცირება",
+        "format_increase_indent": "შეწევის გაზრდა",
+        "format_inline_code": "კოდი",
+        "format_italic": "დახრილი",
+        "format_link": "Ბმული",
+        "format_ordered_list": "დანომრილი სია",
+        "format_strikethrough": "დარტყმა",
+        "format_underline": "ხაზი გაუსვით",
+        "format_unordered_list": "ბურთულებიანი სია",
+        "placeholder": "შეტყობინების გაგზავნა…",
+        "placeholder_encrypted": "დაშიფრული შეტყობინების გაგზავნა…",
+        "placeholder_reply": "პასუხის გაგზავნა…",
+        "placeholder_reply_encrypted": "დაშიფრული პასუხის გაგზავნა…",
+        "placeholder_thread": "თემის პასუხი…",
+        "placeholder_thread_encrypted": "პასუხი დაშიფრულ თემას…",
+        "send_button_title": "შეტყობინების გაგზავნა"
+    },
+    "create_room": {
+        "action_create_room": "",
+        "action_create_video_room": "შექმენით ვიდეო ოთახი",
+        "encrypted_video_room_warning": "თქვენ არ შეგიძლიათ გამორთოთ ეს მოგვიანებით. ოთახი დაშიფრული იქნება, მაგრამ ჩაშენებული ზარი არა.",
+        "encrypted_warning": "თქვენ არ შეგიძლიათ გამორთოთ ეს მოგვიანებით. ხიდები და ბოტების უმეტესობა ჯერ არ იმუშავებს.",
+        "encryption_forced": "თქვენი სერვერი ითხოვს დაშიფვრას პირად ოთახებში გასააქტიურებლად.",
+        "encryption_label": "ჩართეთ ბოლოდან ბოლომდე დაშიფვრა",
+        "error_title": "ოთახის შექმნის შეცდომა",
+        "generic_error": "სერვერი შეიძლება იყოს მიუწვდომელი, გადატვირთული, ან შეიძლება ეს ბაგია.",
+        "join_rule_change_notice": "ამის შეცვლა ნებისმიერ დროს შეგიძლიათ ოთახის პარამეტრებიდან.",
+        "join_rule_invite": "პირადი ოთახი (მხოლოდ მოსაწვევი)",
+        "join_rule_invite_label": "მხოლოდ მოწვეული ადამიანები შეძლებენ ამ ოთახის პოვნას და შეერთებას.",
+        "join_rule_knock_label": "ნებისმიერს შეუძლია შეერთების მოთხოვნა, მაგრამ ადმინისტრატორებმა ან მოდერატორებმა უნდა მიანიჭონ წვდომა. თქვენ შეგიძლიათ შეცვალოთ ეს მოგვიანებით.",
+        "join_rule_public_label": "ნებისმიერს შეეძლება ამ ოთახის პოვნა და გაწევრიანება.",
+        "join_rule_public_parent_space_label": "ნებისმიერს შეეძლება ამ ოთახის პოვნა და გაწევრიანება და არა მხოლოდ წევრები<SpaceName/> .",
+        "join_rule_restricted": "ხილული კოსმოსური წევრებისთვის",
+        "join_rule_restricted_label": "ყველა შიგნით<SpaceName/> შეძლებს ამ ოთახის პოვნას და შეერთებას.",
+        "name_validation_required": "გთხოვთ, შეიყვანოთ ოთახის სახელი",
+        "room_visibility_label": "ოთახის ხილვადობა",
+        "title_private_room": "შექმენით კერძო ოთახი",
+        "title_public_room": "შექმენით საჯარო ოთახი",
+        "title_video_room": "შექმენით ვიდეო ოთახი",
+        "topic_label": "თემა (სურვილისამებრ)",
+        "unfederated": "დაბლოკეთ ვინმე, რომლის წევრი არ არის%(serverName)s ოდესმე ამ ოთახში შესვლიდან.",
+        "unfederated_label_default_off": "თქვენ შეიძლება ჩართოთ ეს, თუ ოთახი გამოყენებული იქნება მხოლოდ თქვენი სახლის სერვერზე შიდა გუნდებთან თანამშრომლობისთვის. ამის მოგვიანებით შეცვლა შეუძლებელია.",
+        "unfederated_label_default_on": "შეგიძლიათ გამორთოთ ეს, თუ ოთახი გამოყენებული იქნება გარე გუნდებთან თანამშრომლობისთვის, რომლებსაც აქვთ საკუთარი სახლის სერვერი. ამის მოგვიანებით შეცვლა შეუძლებელია.",
+        "unsupported_version": "სერვერი არ მუშაობს ოთახის მითითებულ ვერსიაზე."
+    },
+    "credits": {
+        "default_cover_photo": "The<photo> ნაგულისხმევი ყდის ფოტო</photo> არის ©<author> ჯესუს რონსერო</author> გამოიყენება პირობების მიხედვით<terms> CC-BY-SA 4.0</terms> .",
+        "twemoji": "The<twemoji> Twemoji</twemoji> emoji ხელოვნება არის ©<author> Twitter, Inc და სხვა კონტრიბუტორები</author> გამოიყენება პირობების მიხედვით<terms> CC-BY 4.0</terms> .",
+        "twemoji_colr": "The<colr> twemoji-colr</colr> შრიფტი არის ©<author> Mozilla Foundation</author> გამოიყენება პირობების მიხედვით<terms> Apache 2.0</terms> ."
+    },
+    "devtools": {
+        "active_widgets": "აქტიური ვიჯეტები",
+        "category_other": "სხვა",
+        "category_room": "ოთახი",
+        "caution_colon": "სიფრთხილე:",
+        "client_versions": "კლიენტის ვერსიები",
+        "developer_mode": "დეველოპერის რეჟიმი",
+        "developer_tools": "დეველოპერის ინსტრუმენტები",
+        "edit_setting": "პარამეტრის რედაქტირება",
+        "edit_values": "მნიშვნელობების რედაქტირება",
+        "empty_string": "<empty string>",
+        "event_content": "ღონისძიების შინაარსი",
+        "event_id": "ღონისძიების ID:%(eventId)s",
+        "event_sent": "ღონისძიება გაგზავნილია!",
+        "event_type": "ღონისძიების ტიპი",
+        "explore_account_data": "შეისწავლეთ ანგარიშის მონაცემები",
+        "explore_room_account_data": "შეისწავლეთ ოთახის ანგარიშის მონაცემები",
+        "explore_room_state": "შეისწავლეთ ოთახის მდგომარეობა",
+        "failed_to_find_widget": "ამ ვიჯეტის პოვნისას მოხდა შეცდომა.",
+        "failed_to_load": "ჩატვირთვა ვერ მოხერხდა.",
+        "failed_to_save": "პარამეტრების შენახვა ვერ მოხერხდა.",
+        "failed_to_send": "ღონისძიების გაგზავნა ვერ მოხერხდა!",
+        "id": "ID: ",
+        "invalid_json": "არ ჰგავს მოქმედ JSON-ს.",
+        "level": "დონე",
+        "low_bandwidth_mode": "დაბალი გამტარობის რეჟიმი",
+        "low_bandwidth_mode_description": "მოითხოვს თავსებადი სახლის სერვერს.",
+        "main_timeline": "მთავარი ვადები",
+        "no_receipt_found": "ქვითარი ვერ მოიძებნა",
+        "notification_state": "შეტყობინების მდგომარეობა არის<strong>%(notificationState)s</strong>",
+        "notifications_debug": "შეტყობინებების გამართვა",
+        "number_of_users": "მომხმარებელთა რაოდენობა",
+        "original_event_source": "ღონისძიების ორიგინალური წყარო",
+        "room_encrypted": "ოთახი არის<strong> დაშიფრული ✅</strong>",
+        "room_id": "ოთახის ID:%(roomId)s",
+        "room_not_encrypted": "ოთახი არის<strong> არ არის დაშიფრული 🚨</strong>",
+        "room_notifications_dot": "წერტილი: ",
+        "room_notifications_highlight": "მონიშნეთ: ",
+        "room_notifications_last_event": "ბოლო ღონისძიება:",
+        "room_notifications_sender": "გამგზავნი: ",
+        "room_notifications_thread_id": "თემის ID: ",
+        "room_notifications_total": "სულ:",
+        "room_notifications_type": "ტიპი: ",
+        "room_status": "ოთახის სტატუსი",
+        "room_unread_status_count": {
+            "one": "ოთახის წაუკითხავი სტატუსი:<strong>%(status)s</strong> , დათვალეთ:<strong>%(count)s</strong>",
+            "other": "ოთახის წაუკითხავი სტატუსი:<strong>%(status)s</strong> , დათვალეთ:<strong>%(count)s</strong>"
+        },
+        "save_setting_values": "პარამეტრების მნიშვნელობების შენახვა",
+        "see_history": "იხილეთ ისტორია",
+        "send_custom_account_data_event": "პირადი ანგარიშის მონაცემების ღონისძიების გაგზავნა",
+        "send_custom_room_account_data_event": "პირადი ოთახის ანგარიშის მონაცემების ღონისძიების გაგზავნა",
+        "send_custom_state_event": "პირადი სახელმწიფო ღონისძიების გაგზავნა",
+        "send_custom_timeline_event": "პირადი ქრონოლოგიის ღონისძიების გაგზავნა",
+        "server_info": "სერვერის ინფორმაცია",
+        "server_versions": "სერვერის ვერსიები",
+        "settable_global": "დაყენება შესაძლებელია გლობალურ დონეზე",
+        "settable_room": "დაყენება ოთახში",
+        "setting_colon": "პარამეტრი:",
+        "setting_definition": "პარამეტრის განმარტება:",
+        "setting_id": "ID-ის დაყენება",
+        "settings_explorer": "პარამეტრების მკვლევარი",
+        "show_hidden_events": "დამალული მოვლენების ჩვენება ქრონიკაში",
+        "state_key": "სახელმწიფო გასაღები",
+        "thread_root_id": "თემის ფესვის ID:%(threadRootId)s",
+        "threads_timeline": "თემების ვადები",
+        "title": "დეველოპერის ინსტრუმენტები",
+        "toggle_event": "მოვლენის გადართვა",
+        "toolbox": "ხელსაწყოს ყუთი",
+        "use_at_own_risk": "ეს UI არ ამოწმებს მნიშვნელობების ტიპებს. გამოიყენეთ საკუთარი რისკის ქვეშ.",
+        "user_read_up_to": "მომხმარებელმა წაიკითხა: ",
+        "user_read_up_to_ignore_synthetic": "მომხმარებელმა წაიკითხა მდე (იგნორირება Synthetic): ",
+        "user_read_up_to_private": "მომხმარებელი წაიკითხა მდე (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "მომხმარებელმა წაიკითხა მდე (m.read.private;ignoreSynthetic):",
+        "value": "ღირებულება",
+        "value_colon": "ღირებულება:",
+        "value_in_this_room": "ღირებულება ამ ოთახში",
+        "value_this_room_colon": "ღირებულება ამ ოთახში:",
+        "values_explicit": "ღირებულებები აშკარა დონეზე",
+        "values_explicit_colon": "ღირებულებები აშკარა დონეზე:",
+        "values_explicit_room": "ღირებულებები აშკარა დონეზე ამ ოთახში",
+        "values_explicit_this_room_colon": "მნიშვნელობები აშკარა დონეზე ამ ოთახში:",
+        "view_servers_in_room": "იხილეთ სერვერები ოთახში",
+        "view_source_decrypted_event_source": "გაშიფრული მოვლენის წყარო",
+        "view_source_decrypted_event_source_unavailable": "გაშიფრული წყარო მიუწვდომელია",
+        "widget_screenshots": "ჩართეთ ვიჯეტის ეკრანის ანაბეჭდები მხარდაჭერილ ვიჯეტებზე"
+    },
+    "emoji": {
+        "categories": "კატეგორიები",
+        "category_activities": "Საქმიანობის",
+        "category_animals_nature": "ცხოველები და ბუნება",
+        "category_flags": "დროშები",
+        "category_food_drink": "Საჭმელ - სასმელი",
+        "category_frequently_used": "ხშირად გამოყენებული",
+        "category_objects": "ობიექტები",
+        "category_smileys_people": "სმაილი და ხალხი",
+        "category_symbols": "სიმბოლოები",
+        "category_travel_places": "მოგზაურობა და ადგილები",
+        "quick_reactions": "სწრაფი რეაქციები"
+    },
+    "empty_room": "ცარიელი ოთახი",
+    "empty_room_was_name": "ცარიელი ოთახი (იყო%(oldName)s )",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "key_validation_text": {
+                "wrong_security_key": "არასწორი უსაფრთხოების გასაღები"
+            },
+            "restoring": "გასაღებების აღდგენა სარეზერვო ასლიდან",
+            "security_key_title": "უსაფრთხოების გასაღები"
+        },
+        "bootstrap_title": "გასაღებების დაყენება",
+        "cancel_entering_passphrase_description": "დარწმუნებული ხართ, რომ გსურთ გააუქმოთ პაროლის შეყვანა?",
+        "cancel_entering_passphrase_title": "გააუქმოს პაროლის შეყვანა?",
+        "confirm_encryption_setup_body": "დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე დაშიფვრის დაყენების დასადასტურებლად.",
+        "confirm_encryption_setup_title": "დაადასტურეთ დაშიფვრის დაყენება",
+        "cross_signing_room_normal": "",
+        "cross_signing_room_verified": "ამ ოთახში ყველა დამოწმებულია",
+        "cross_signing_room_warning": "ვიღაც უცნობ სესიას იყენებს",
+        "cross_signing_user_normal": "თქვენ არ დაადასტურეთ ეს მომხმარებელი.",
+        "cross_signing_user_verified": "თქვენ დაადასტურეთ ეს მომხმარებელი. ამ მომხმარებელმა დაადასტურა ყველა მათი სესია.",
+        "cross_signing_user_warning": "ამ მომხმარებელს არ დაუდასტურებია ყველა მათი სესია.",
+        "event_shield_reason_authenticity_not_guaranteed": "ამ დაშიფრული შეტყობინების ავთენტურობის გარანტია შეუძლებელია ამ მოწყობილობაზე.",
+        "event_shield_reason_mismatched_sender_key": "დაშიფრულია დაუდასტურებელი სესიით",
+        "event_shield_reason_unknown_device": "დაშიფრულია უცნობი ან წაშლილი მოწყობილობით.",
+        "event_shield_reason_unsigned_device": "დაშიფრულია მოწყობილობის მიერ, რომელიც არ არის დადასტურებული მისი მფლობელის მიერ.",
+        "event_shield_reason_unverified_identity": "დაშიფრულია დაუდასტურებელი მომხმარებლის მიერ.",
+        "export_unsupported": "თქვენი ბრაუზერი არ უჭერს მხარს კრიპტოგრაფიის საჭირო გაფართოებებს",
+        "import_invalid_keyfile": "არ არის მოქმედი%(brand)s გასაღები ფაილი",
+        "import_invalid_passphrase": "ავტორიზაციის შემოწმება ვერ მოხერხდა: პაროლი არასწორია?",
+        "messages_not_secure": {
+            "cause_1": "თქვენი სახლის სერვერი",
+            "cause_2": "ჰომოსერვერი, რომელსაც ადასტურებთ მომხმარებელი, დაკავშირებულია",
+            "cause_3": "თქვენი ან სხვა მომხმარებლების ინტერნეტ კავშირი",
+            "cause_4": "თქვენი ან სხვა მომხმარებლების სესია",
+            "heading": "ერთ-ერთი შემდეგი შეიძლება იყოს კომპრომეტირებული:",
+            "title": "თქვენი შეტყობინებები არ არის დაცული"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "აღმოჩენილია უსაფრთხოების ახალი ფრაზა და გასაღები Secure Messages-ისთვის.",
+            "description_2": "ეს სესია შიფრავს ისტორიას ახალი აღდგენის მეთოდის გამოყენებით.",
+            "title": "აღდგენის ახალი მეთოდი",
+            "warning": "თუ თქვენ არ დააყენეთ აღდგენის ახალი მეთოდი, შესაძლოა თავდამსხმელი ცდილობდეს თქვენს ანგარიშზე წვდომას. შეცვალეთ თქვენი ანგარიშის პაროლი და დააყენეთ აღდგენის ახალი მეთოდი დაუყოვნებლივ პარამეტრებში."
+        },
+        "recovery_method_removed": {
+            "description_1": "ამ სესიამ დაადგინა, რომ თქვენი უსაფრთხოების ფრაზა და Secure Messages გასაღები წაიშალა.",
+            "description_2": "თუ ეს შემთხვევით გააკეთეთ, შეგიძლიათ დააყენოთ Secure Messages ამ სესიაზე, რომელიც ხელახლა დაშიფვრავს ამ სესიის შეტყობინებების ისტორიას აღდგენის ახალი მეთოდით.",
+            "title": "აღდგენის მეთოდი ამოღებულია",
+            "warning": "თუ თქვენ არ წაშალეთ აღდგენის მეთოდი, შესაძლოა თავდამსხმელი ცდილობდეს თქვენს ანგარიშზე წვდომას. შეცვალეთ თქვენი ანგარიშის პაროლი და დააყენეთ აღდგენის ახალი მეთოდი დაუყოვნებლივ პარამეტრებში."
+        },
+        "reset_all_button": "დაგავიწყდათ თუ დაკარგეთ აღდგენის ყველა მეთოდი?<a> გადატვირთეთ ყველა</a>",
+        "set_up_toast_description": "დაიცავით დაშიფრულ შეტყობინებებსა და მონაცემებზე წვდომის დაკარგვისგან",
+        "set_up_toast_title": "დააყენეთ უსაფრთხო სარეზერვო ასლი",
+        "setup_secure_backup": {
+            "explainer": "შექმენით თქვენი გასაღებების სარეზერვო ასლები გასვლამდე, რათა არ დაკარგოთ ისინი."
+        },
+        "udd": {
+            "interactive_verification_button": "ინტერაქტიულად გადაამოწმეთ emoji-ით",
+            "other_ask_verify_text": "სთხოვეთ ამ მომხმარებელს დაადასტუროს თავისი სესია, ან ხელით დაადასტუროს იგი ქვემოთ.",
+            "other_new_session_text": "%(name)s(%(userId)s ) შესული ხართ ახალ სესიაში დადასტურების გარეშე:",
+            "own_ask_verify_text": "დაადასტურეთ თქვენი სხვა სესია ქვემოთ მოცემული ერთ-ერთი ვარიანტის გამოყენებით.",
+            "own_new_session_text": "თქვენ შეხვედით ახალ სესიაში მისი დადასტურების გარეშე:",
+            "title": "არ არის სანდო"
+        },
+        "unable_to_setup_keys_error": "გასაღებების დაყენება შეუძლებელია",
+        "verification": {
+            "accepting": "მიღება…",
+            "after_new_login": {
+                "device_verified": "მოწყობილობა დადასტურებულია",
+                "skip_verification": "ამ დროისთვის გამოტოვეთ დადასტურება",
+                "unable_to_verify": "ამ მოწყობილობის დადასტურება შეუძლებელია",
+                "verify_this_device": "დაადასტურეთ ეს მოწყობილობა"
+            },
+            "cancelled": "თქვენ გააუქმეთ დადასტურება.",
+            "cancelled_self": "თქვენ გააუქმეთ დადასტურება თქვენს სხვა მოწყობილობაზე.",
+            "cancelled_user": "%(displayName)sგაუქმებული გადამოწმება.",
+            "cancelling": "მიმდინარეობს გაუქმება…",
+            "complete_action": "Გავიგე",
+            "complete_description": "თქვენ წარმატებით დაადასტურეთ ეს მომხმარებელი.",
+            "complete_title": "დამოწმებულია!",
+            "error_starting_description": "ჩვენ ვერ შევძელით ჩეთის დაწყება სხვა მომხმარებელთან.",
+            "error_starting_title": "შეცდომა დადასტურების დაწყებისას",
+            "explainer": "ამ მომხმარებლის უსაფრთხო შეტყობინებები არის ბოლომდე დაშიფრული და ვერ წაიკითხება მესამე მხარის მიერ.",
+            "in_person": "უსაფრთხოების მიზნით, გააკეთეთ ეს პირადად ან გამოიყენეთ სანდო გზა კომუნიკაციისთვის.",
+            "incoming_sas_device_dialog_text_1": "დაადასტურეთ ეს მოწყობილობა, რომ მონიშნოთ ის სანდოდ. ამ მოწყობილობის ნდობა მოგანიჭებთ თქვენ და სხვა მომხმარებლებს დამატებით სიმშვიდეს, როდესაც იყენებთ ბოლომდე დაშიფრულ შეტყობინებებს.",
+            "incoming_sas_device_dialog_text_2": "ამ მოწყობილობის დადასტურება მონიშნავს მას სანდოდ და მომხმარებლები, რომლებმაც დაადასტურეს თქვენთან, ენდობიან ამ მოწყობილობას.",
+            "incoming_sas_dialog_title": "შემომავალი დადასტურების მოთხოვნა",
+            "incoming_sas_dialog_waiting": "ელოდება პარტნიორის დადასტურებას…",
+            "incoming_sas_user_dialog_text_1": "დაადასტურეთ ეს მომხმარებელი, რათა მონიშნოთ ისინი სანდოდ. მომხმარებელთა ნდობა გაძლევთ დამატებით სიმშვიდეს, როდესაც იყენებთ ბოლომდე დაშიფრული შეტყობინებების გამოყენებას.",
+            "incoming_sas_user_dialog_text_2": "ამ მომხმარებლის დადასტურება მონიშნავს მის სესიას სანდოდ და ასევე მონიშნავს თქვენს სესიას, როგორც მისთვის სანდო.",
+            "no_key_or_device": "როგორც ჩანს, არ გაქვთ უსაფრთხოების გასაღები ან სხვა მოწყობილობები, რომლებზეც შეგიძლიათ დაადასტუროთ. ეს მოწყობილობა ვერ შეძლებს ძველ დაშიფრულ შეტყობინებებზე წვდომას. იმისათვის, რომ დაადასტუროთ თქვენი ვინაობა ამ მოწყობილობაზე, დაგჭირდებათ თქვენი დამადასტურებელი გასაღებების გადატვირთვა.",
+            "no_support_qr_emoji": "მოწყობილობა, რომლის გადამოწმებას ცდილობთ, არ უჭერს მხარს QR კოდის ან emoji-ის დადასტურებას, რაც არის%(brand)s მხარს უჭერს. სცადეთ სხვა კლიენტთან.",
+            "other_party_cancelled": "მეორე მხარემ გააუქმა გადამოწმება.",
+            "prompt_encrypted": "დაადასტურეთ ოთახის ყველა მომხმარებელი, რომ დარწმუნდეთ, რომ ის უსაფრთხოა.",
+            "prompt_self": "ხელახლა დაიწყეთ გადამოწმება შეტყობინებებიდან.",
+            "prompt_unencrypted": "დაშიფრულ ოთახებში გადაამოწმეთ ყველა მომხმარებელი, რომ დარწმუნდეთ, რომ ის უსაფრთხოა.",
+            "prompt_user": "ხელახლა დაიწყეთ დადასტურება მათი პროფილიდან.",
+            "qr_or_sas": "%(qrCode)sან%(emojiCompare)s",
+            "qr_or_sas_header": "გადაამოწმეთ ეს მოწყობილობა შემდეგიდან ერთ-ერთის შესრულებით:",
+            "qr_prompt": "დაასკანირეთ ეს უნიკალური კოდი",
+            "qr_reciprocate_same_shield_device": "თითქმის იქ! თქვენი სხვა მოწყობილობა აჩვენებს იმავე ფარს?",
+            "qr_reciprocate_same_shield_user": "თითქმის იქ! არის%(displayName)s იგივე ფარს აჩვენებს?",
+            "request_toast_accept": "სესიის გადამოწმება",
+            "request_toast_decline_counter": "იგნორირება (%(counter)s )",
+            "request_toast_detail": "%(deviceId)sსაწყისი%(ip)s",
+            "reset_proceed_prompt": "გააგრძელეთ გადატვირთვა",
+            "sas_caption_self": "დაადასტურეთ ეს მოწყობილობა და დაადასტურეთ, რომ შემდეგი ნომერი გამოჩნდება მის ეკრანზე.",
+            "sas_caption_user": "დაადასტურეთ ეს მომხმარებელი და დაადასტურეთ, რომ შემდეგი ნომერი გამოჩნდება მის ეკრანზე.",
+            "sas_description": "შეადარეთ emoji-ების უნიკალური ნაკრები, თუ არცერთ მოწყობილობაზე არ გაქვთ კამერა",
+            "sas_emoji_caption_self": "დაადასტურეთ, რომ ქვემოთ მოცემული emoji ნაჩვენებია ორივე მოწყობილობაზე, იმავე თანმიმდევრობით:",
+            "sas_emoji_caption_user": "დაადასტურეთ ეს მომხმარებელი და დაადასტურეთ, რომ შემდეგი emoji გამოჩნდება მის ეკრანზე.",
+            "sas_match": "ისინი ემთხვევა",
+            "sas_no_match": "ისინი არ ემთხვევა",
+            "sas_prompt": "შეადარეთ უნიკალური emoji",
+            "scan_qr": "გადაამოწმეთ სკანირებით",
+            "scan_qr_explainer": "იკითხე%(displayName)s თქვენი კოდის სკანირებისთვის:",
+            "self_verification_hint": "გასაგრძელებლად გთხოვთ დაეთანხმოთ დადასტურების მოთხოვნას თქვენს სხვა მოწყობილობაზე.",
+            "start_button": "დაიწყეთ გადამოწმება",
+            "successful_device": "თქვენ წარმატებით დაადასტურეთ%(deviceName)s (%(deviceId)s )!",
+            "successful_own_device": "თქვენ წარმატებით დაადასტურეთ თქვენი მოწყობილობა!",
+            "successful_user": "თქვენ წარმატებით დაადასტურეთ%(displayName)s !",
+            "timed_out": "დადასტურების დრო ამოიწურა.",
+            "unsupported_method": "დადასტურების მხარდაჭერილი მეთოდის პოვნა შეუძლებელია.",
+            "unverified_session_toast_accept": "დიახ, მე ვიყავი",
+            "unverified_session_toast_title": "ახალი შესვლა. ეს შენ იყავი?",
+            "unverified_sessions_toast_description": "გადახედეთ, რათა დარწმუნდეთ, რომ თქვენი ანგარიში უსაფრთხოა",
+            "unverified_sessions_toast_reject": "მოგვიანებით",
+            "unverified_sessions_toast_title": "თქვენ გაქვთ დაუდასტურებელი სესიები",
+            "verification_description": "დაადასტურეთ თქვენი ვინაობა დაშიფრულ შეტყობინებებზე წვდომისთვის და სხვებისთვის თქვენი ვინაობის დასამტკიცებლად. თუ თქვენ ასევე იყენებთ მობილურ მოწყობილობას, გთხოვთ, გახსენით აპი იქ, სანამ გააგრძელებთ.",
+            "verification_dialog_title_device": "გადაამოწმეთ სხვა მოწყობილობა",
+            "verification_dialog_title_user": "გადამოწმების მოთხოვნა",
+            "verification_skip_warning": "დადასტურების გარეშე, თქვენ არ გექნებათ წვდომა თქვენს ყველა შეტყობინებაზე და შეიძლება სხვებისთვის არასანდო გამოჩნდეთ.",
+            "verification_success_with_backup": "თქვენი ახალი მოწყობილობა ახლა დადასტურებულია. მას აქვს წვდომა თქვენს დაშიფრულ შეტყობინებებზე და სხვა მომხმარებლები დაინახავენ მას, როგორც სანდო.",
+            "verification_success_without_backup": "თქვენი ახალი მოწყობილობა ახლა დადასტურებულია. სხვა მომხმარებლები დაინახავენ მას, როგორც სანდო.",
+            "verify_emoji": "გადაამოწმეთ emoji-ით",
+            "verify_emoji_prompt": "გადაამოწმეთ უნიკალური emoji-ების შედარებით.",
+            "verify_emoji_prompt_qr": "თუ ზემოთ მოცემულ კოდს ვერ სკანირებთ, გადაამოწმეთ უნიკალური emoji-ების შედარებით.",
+            "verify_later": "მოგვიანებით გადავამოწმებ",
+            "verify_using_device": "გადაამოწმეთ სხვა მოწყობილობით",
+            "verify_using_key": "გადაამოწმეთ უსაფრთხოების გასაღებით",
+            "verify_using_key_or_phrase": "გადაამოწმეთ უსაფრთხოების გასაღებით ან ფრაზით",
+            "waiting_for_user_accept": "ელოდება%(displayName)s მიღება…",
+            "waiting_other_device": "ელოდება თქვენ გადამოწმებას თქვენს სხვა მოწყობილობაზე…",
+            "waiting_other_device_details": "გელოდებათ გადამოწმებას თქვენს სხვა მოწყობილობაზე,%(deviceName)s (%(deviceId)s )…",
+            "waiting_other_user": "ელოდება%(displayName)s გადამოწმება…"
+        },
+        "verification_requested_toast_title": "მოთხოვნილია დადასტურება",
+        "verify_toast_description": "სხვა მომხმარებლებს შეიძლება არ ენდონ მას",
+        "verify_toast_title": "გადაამოწმეთ ეს სესია"
+    },
+    "error": {
+        "admin_contact": "გთხოვთ<a> დაუკავშირდით თქვენი სერვისის ადმინისტრატორს</a> ამ სერვისის გამოყენების გასაგრძელებლად.",
+        "admin_contact_short": "დაუკავშირდით თქვენს<a> სერვერის ადმინი</a> .",
+        "connection": "სახლის სერვერთან კომუნიკაციისას წარმოიშვა პრობლემა, გთხოვთ, სცადოთ მოგვიანებით.",
+        "dialog_description_default": "",
+        "download_media": "წყაროს მედია ვერ ჩამოიტვირთა, წყაროს url ვერ მოიძებნა",
+        "edit_history_unsupported": "როგორც ჩანს, თქვენი სახლის სერვერი არ უჭერს მხარს ამ ფუნქციას.",
+        "failed_copy": "კოპირება ვერ მოხერხდა",
+        "hs_blocked": "ეს სახლის სერვერი დაბლოკილია მისი ადმინისტრატორის მიერ.",
+        "mau": "",
+        "mixed_content": "HTTP-ის მეშვეობით ჰომოსერვერთან დაკავშირება შეუძლებელია, როდესაც თქვენი ბრაუზერის ზოლში არის HTTPS URL. გამოიყენეთ ან HTTPS ან<a> არაუსაფრთხო სკრიპტების ჩართვა</a> .",
+        "non_urgent_echo_failure_toast": "თქვენი სერვერი ზოგიერთს არ პასუხობს<a> ითხოვს</a> .",
+        "resource_limits": "ამ სახლის სერვერმა გადააჭარბა რესურსის ერთ-ერთ ლიმიტს.",
+        "session_restore": {
+            "clear_storage_button": "გაასუფთავეთ მეხსიერება და გამოდით",
+            "clear_storage_description": "გამოხვალთ სისტემიდან და ამოიღეთ დაშიფვრის გასაღებები?",
+            "description_1": "",
+            "description_2": "თუ ადრე იყენებდით უფრო უახლეს ვერსიას%(brand)s , თქვენი სესია შეიძლება შეუთავსებელი იყოს ამ ვერსიასთან. დახურეთ ეს ფანჯარა და დაუბრუნდით უახლეს ვერსიას.",
+            "description_3": "თქვენი ბრაუზერის მეხსიერების გასუფთავებამ შეიძლება მოაგვაროს პრობლემა, მაგრამ გამოხვალთ სისტემიდან და გამოიწვევს დაშიფრული ჩეთის ისტორიის წაკითხვას.",
+            "title": "სესიის აღდგენა შეუძლებელია"
+        },
+        "something_went_wrong": "რაღაც შეფერხდა!",
+        "storage_evicted_description_1": "სესიის ზოგიერთი მონაცემი, მათ შორის დაშიფრული შეტყობინების გასაღებები, აკლია. გამოდით და შედით სისტემაში ამის გამოსასწორებლად, გასაღებების აღდგენით სარეზერვო ასლიდან.",
+        "storage_evicted_description_2": "თქვენმა ბრაუზერმა, სავარაუდოდ, წაშალა ეს მონაცემები, როდესაც დისკზე ნაკლები სივრცეა.",
+        "storage_evicted_title": "სესიის მონაცემები აკლია",
+        "sync": "Homeserver-თან დაკავშირება შეუძლებელია. ხელახლა მცდელობა…",
+        "tls": "სახლის სერვერთან დაკავშირება შეუძლებელია - გთხოვთ, შეამოწმოთ თქვენი დაკავშირება, დარწმუნდით<a> ჰომოსერვერის SSL სერთიფიკატი</a> არის სანდო და რომ ბრაუზერის გაფართოება არ ბლოკავს მოთხოვნებს.",
+        "unknown": "უცნობი შეცდომა",
+        "unknown_error_code": "უცნობი შეცდომის კოდი",
+        "update_power_level": "დენის დონის შეცვლა ვერ მოხერხდა"
+    },
+    "error_database_closed_title": "%(brand)sშეწყვიტა მუშაობა",
+    "error_user_not_logged_in": "მომხმარებელი არ არის შესული",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "ზარი მიმდინარეობს",
+            "user": "%(senderName)sშეუერთდა ზარს",
+            "you": "თქვენ შეუერთდით ზარს"
+        },
+        "m.call.hangup": {
+            "user": "%(senderName)sდაასრულა ზარი",
+            "you": "თქვენ დაასრულეთ ზარი"
+        },
+        "m.call.invite": {
+            "dm_receive": "%(senderName)sრეკავს",
+            "dm_send": "პასუხს ელოდება",
+            "user": "%(senderName)sდაიწყო ზარი",
+            "you": "თქვენ დაიწყეთ ზარი"
+        },
+        "m.emote": "*%(senderName)s%(emote)s",
+        "m.reaction": {
+            "user": "%(sender)sგამოეხმაურა%(reaction)s რომ%(message)s",
+            "you": "შენ რეაგირებდი%(reaction)s რომ%(message)s"
+        },
+        "m.sticker": "%(senderName)s:%(stickerName)s",
+        "m.text": "%(senderName)s:%(message)s"
+    },
+    "export_chat": {
+        "cancelled": "ექსპორტი გაუქმდა",
+        "cancelled_detail": "ექსპორტი წარმატებით გაუქმდა",
+        "confirm_stop": "დარწმუნებული ხართ, რომ გსურთ თქვენი მონაცემების ექსპორტის შეწყვეტა? თუ ამას გააკეთებთ, თავიდან უნდა დაიწყოთ.",
+        "creating_html": "HTML- ის შექმნა...",
+        "creating_output": "გამომავალი შექმნა...",
+        "creator_summary": "%(creatorName)sშექმნა ეს ოთახი.",
+        "current_timeline": "მიმდინარე ვადები",
+        "enter_number_between_min_max": "შეიყვანეთ რიცხვი შორის%(min)s და%(max)s",
+        "error_fetching_file": "შეცდომა ფაილის აღებაში",
+        "export_info": "ეს არის ექსპორტის დასაწყისი<roomName/>. ექსპორტირ <exporterDetails/> ებულია at%(exportDate)s.",
+        "export_successful": "წარმატებული ექსპორტი!",
+        "exported_n_events_in_time": {
+            "one": "ექსპორტი %(count)s მოვლენა წ %(seconds)s ამებში",
+            "other": "ექსპორტი %(count)s მოვლენები წ %(seconds)s ამებში"
+        },
+        "exporting_your_data": "მიმდინარეობს თქვენი მონაცემების ექსპორტი",
+        "fetched_n_events": {
+            "one": "ჯერჯერობით მიღ %(count)s ებული მოვლენა",
+            "other": "ჯერჯერობით მიღ %(count)s ებული მოვლენები"
+        },
+        "fetched_n_events_in_time": {
+            "one": "მიღებული ღონის %(count)s ძიება s %(seconds)s",
+            "other": "მიღებული მო %(count)s ვლენები s- ში %(seconds)s"
+        },
+        "fetched_n_events_with_total": {
+            "one": "ამოღებული ღონის %(count)s ძიებიდან %(total)s",
+            "other": "ამოღებული მო %(count)s ვლენებიდან %(total)s"
+        },
+        "fetching_events": "მოვლენების მოპოვება...",
+        "file_attached": "ფაილი თანდართულია",
+        "format": "ფორმატი",
+        "from_the_beginning": "თავიდანვე",
+        "generating_zip": "ZIP გენერირება",
+        "html": "ჰტმლ",
+        "html_title": "ექსპორტი მონაცემ",
+        "include_attachments": "ჩართეთ დანართები",
+        "json": "ჯსონი",
+        "media_omitted": "მედია გამოტოვებულია",
+        "media_omitted_file_size": "მედია გამოტოვებულია - ფაილის ზომის ლიმიტი გადაემატ",
+        "messages": "შეტყობინებები",
+        "next_page": "მესიჯების შემდეგი ჯგუფი",
+        "num_messages": "შეტყობინებების რაოდენობა",
+        "num_messages_min_max": "შეტყობინებების რაოდენობა შეიძლება იყოს მხოლოდ მათ შორის%(min)s და%(max)s",
+        "number_of_messages": "მიუთითეთ რამდენიმე შეტყობინება",
+        "previous_page": "შეტყობინებების წინა ჯგ",
+        "processing": "მუშავდება…",
+        "processing_event_n": "დამუშავების ღონ %(number)s ისძიება %(total)s",
+        "select_option": "აირჩიეთ ქვემოთ მოცემული ვარიანტებიდან ჩეთის ექსპორტისთვის თქვენი დროის ხაზიდან",
+        "size_limit": "ზომის ლიმიტი",
+        "size_limit_min_max": "ზომა შეიძლება იყოს მხოლოდ რიცხვი შორის%(min)s MB და%(max)s მბ",
+        "size_limit_postfix": "მბ",
+        "starting_export": "ექსპორტის დაწყება...",
+        "successful": "ექსპორტი წარმატებულია",
+        "successful_detail": "თქვენი ექსპორტი წარმატებით დასრულდა. იპოვეთ ის თქვენს ჩამოტვირთვების საქაღალდეში.",
+        "text": "უბრალო ტექსტი",
+        "title": "ჩეთის ექსპორტი",
+        "topic": "თემა: %(topic)s",
+        "unload_confirm": "დარწმუნებული ხართ, რომ გსურთ გასვლა ამ ექსპორტის დროს?"
+    },
+    "failed_load_async_component": "ვერ იტვირთება! შეამოწმეთ თქვენი ინტერნეტ-კავშირი და სცადეთ ისევ.",
+    "in_space": "In%(spaceName)s .",
+    "in_space1_and_space2": "სივრცეებში%(space1Name)s და%(space2Name)s .",
+    "in_space_and_n_other_spaces": {
+        "one": "In%(spaceName)s და კიდევ ერთი სივრცე.",
+        "other": "In%(spaceName)s და%(count)s სხვა სივრცეები."
+    },
+    "invite": {
+        "ask_anyway_description": "ქვემოთ ჩამოთვლილი Matrix ID-ების პროფილების პოვნა ვერ ხერხდება - მაინც გსურთ DM-ის დაწყება?",
+        "ask_anyway_label": "მაინც დაიწყე DM",
+        "ask_anyway_never_warn_label": "მაინც დაიწყე DM და აღარასოდეს გამაფრთხილო",
+        "email_caption": "მოიწვიე ელექტრონული ფოსტით",
+        "email_limit_one": "მოწვევები ელექტრონული ფოსტით შეიძლება გაიგზავნოს მხოლოდ ერთ ჯერზე",
+        "email_use_default_is": "გამოიყენეთ საიდენტიფიკაციო სერვერი ელექტრონული ფოსტით მოსაწვევად.<default> გამოიყენეთ ნაგულისხმევი (%(defaultIdentityServerName)s )</default> ან მართეთ<settings> პარამეტრები</settings> .",
+        "email_use_is": "გამოიყენეთ საიდენტიფიკაციო სერვერი ელექტრონული ფოსტით მოსაწვევად. მართვა შიგნით<settings> პარამეტრები</settings> .",
+        "error_already_invited_room": "მომხმარებელი უკვე მოწვეულია ოთახში",
+        "error_already_invited_space": "მომხმარებელი უკვე მოწვეულია სივრცეში",
+        "error_already_joined_room": "მომხმარებელი უკვე ოთახშია",
+        "error_already_joined_space": "მომხმარებელი უკვე სივრცეშია",
+        "error_bad_state": "მომხმარებლის მოწვევამდე მომხმარებლის აკრძალვა უნდა მოხდეს.",
+        "error_dm": "ჩვენ ვერ შევქმენით თქვენი DM.",
+        "error_find_room": "მომხმარებლების მოწვევის მცდელობამ წარმოიქმნა შეფერხება.",
+        "error_find_user_description": "შემდეგი მომხმარებლები შეიძლება არ არსებობდეს ან არასწორია და მათი მოწვევა შეუძლებელია:%(csvNames)s",
+        "error_find_user_title": "შემდეგი მომხმარებლების პოვნა ვერ მოხერხდა",
+        "error_invite": "ჩვენ ვერ მოვიწვიეთ ეს მომხმარებლები. გთხოვთ, შეამოწმოთ მომხმარებლები, რომელთა მოწვევაც გსურთ და ხელახლა სცადოთ.",
+        "error_permissions_room": "თქვენ არ გაქვთ ამ ოთახში ხალხის მოწვევის უფლება.",
+        "error_permissions_space": "",
+        "error_profile_undisclosed": "მომხმარებელი შეიძლება არსებობდეს ან არ არსებობდეს",
+        "error_transfer_multiple_target": "ზარის გადაცემა შესაძლებელია მხოლოდ ერთ მომხმარებელზე.",
+        "error_unknown": "სერვერის უცნობი შეცდომა",
+        "error_user_not_found": "მომხმარებელი არ არსებობს",
+        "error_version_unsupported_room": "მომხმარებლის სახლის სერვერი არ უჭერს მხარს ოთახის ვერსიას.",
+        "error_version_unsupported_space": "მომხმარებლის სახლის სერვერი არ უჭერს მხარს სივრცის ვერსიას.",
+        "failed_generic": "ოპერაცია ჩაიშალა",
+        "failed_title": "მოწვევა ვერ მოხერხდა",
+        "invalid_address": "არაღიარებული მისამართი",
+        "name_email_mxid_share_room": "მოიწვიე ვინმე თავისი სახელის, ელფოსტის მისამართის, მომხმარებლის სახელის გამოყენებით (როგორც<userId/> ) ან<a> გააზიარე ეს ოთახი</a> .",
+        "name_email_mxid_share_space": "მოიწვიე ვინმე თავისი სახელის, ელფოსტის მისამართის, მომხმარებლის სახელის გამოყენებით (მაგ<userId/> ) ან<a> გააზიარე ეს სივრცე</a> .",
+        "name_mxid_share_room": "მოიწვიე ვინმე თავისი სახელის, მომხმარებლის სახელის (მაგ<userId/> ) ან<a> გააზიარე ეს ოთახი</a> .",
+        "name_mxid_share_space": "მოიწვიე ვინმე თავისი სახელის, მომხმარებლის სახელის (მაგ<userId/> ) ან<a> გააზიარე ეს სივრცე</a> .",
+        "recents_section": "ბოლო საუბრები",
+        "room_failed_partial": "ჩვენ გავაგზავნეთ სხვები, მაგრამ ქვემოთ მოყვანილი ადამიანების მოწვევა ვერ მოხერხდა<RoomName/>",
+        "room_failed_partial_title": "ზოგიერთი მოწვევის გაგზავნა ვერ მოხერხდა",
+        "room_failed_title": "მომხმარებლების მოწვევა ვერ მოხერხდა%(roomName)s",
+        "send_link_prompt": "ან გაგზავნეთ მოწვევის ბმული",
+        "start_conversation_name_email_mxid_prompt": "დაიწყეთ საუბარი ვინმესთან მისი სახელის, ელფოსტის მისამართის ან მომხმარებლის სახელის გამოყენებით (მაგ<userId/> ).",
+        "start_conversation_name_mxid_prompt": "დაიწყეთ საუბარი ვინმესთან მისი სახელის ან მომხმარებლის სახელის გამოყენებით (მაგ<userId/> ).",
+        "suggestions_disclaimer": "ზოგიერთი შემოთავაზება შესაძლოა დამალული იყოს კონფიდენციალურობისთვის.",
+        "suggestions_disclaimer_prompt": "თუ ვერ ხედავთ ვისაც ეძებთ, გაუგზავნეთ თქვენი მოწვევის ბმული ქვემოთ.",
+        "suggestions_section": "ახლახანს პირდაპირი შეტყობინება",
+        "to_room": "მოიწვიე%(roomName)s",
+        "to_space": "მოიწვიე%(spaceName)s",
+        "transfer_dial_pad_tab": "აკრიფეთ pad",
+        "transfer_user_directory_tab": "მომხმარებლის დირექტორია",
+        "unable_find_profiles_description_default": "ქვემოთ ჩამოთვლილი Matrix ID-ების პროფილების პოვნა ვერ ხერხდება - მაინც გსურთ მათი მოწვევა?",
+        "unable_find_profiles_invite_label_default": "მოიწვიე მაინც",
+        "unable_find_profiles_invite_never_warn_label_default": "მოიწვიე მაინც და აღარასოდეს გამაფრთხილო",
+        "unable_find_profiles_title": "შემდეგი მომხმარებლები შეიძლება არ არსებობდეს",
+        "unban_first_title": "მომხმარებლის მოწვევა შეუძლებელია მანამ, სანამ ის არ იქნება აკრძალული"
+    },
+    "inviting_user1_and_user2": "მოწვევა%(user1)s და%(user2)s",
+    "inviting_user_and_n_others": {
+        "one": "მოწვევა%(user)s და ერთი სხვა",
+        "other": "მოწვევა%(user)s და%(count)s სხვები"
+    },
+    "items_and_n_others": {
+        "one": "<Items/>და ერთი სხვა",
+        "other": "<Items/>და%(count)s სხვები"
+    },
+    "keyboard": {
+        "alt": "ყველა",
+        "backspace": "Backspace",
+        "control": "Ctrl",
+        "dismiss_read_marker_and_jump_bottom": "გააუქმეთ წაკითხული მარკერი და გადადით ქვემოთ",
+        "end": "დასასრული",
+        "enter": "შედი",
+        "escape": "ესკ",
+        "home": "სახლში",
+        "number": "[ნომერი]",
+        "page_down": "გვერდი ქვემოთ",
+        "page_up": "გვერდი ზემოთ",
+        "shift": "ცვლა",
+        "space": "სივრცე"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "ეკრანის გაზიარების მხოლოდ რეჟიმში",
+        "ask_to_join": "ჩართეთ შეერთების მოთხოვნა",
+        "automatic_debug_logs": "ავტომატურად გაგზავნეთ გამართვის ჟურნალი ნებისმიერი შეცდომის შესახებ",
+        "automatic_debug_logs_decryption": "გაშიფვრის შეცდომებზე გამართვის ჟურნალების ავტომატურად გაგზავნა",
+        "automatic_debug_logs_key_backup": "გამართვის ჟურნალების ავტომატურად გაგზავნა, როდესაც გასაღების სარეზერვო ასლი არ მუშაობს",
+        "beta_description": "რა არის შემდეგი%(brand)s ? ლაბორატორიები საუკეთესო საშუალებაა ნივთების ადრეულ ეტაპზე მისაღებად, ახალი ფუნქციების შესამოწმებლად და მათ ჩამოყალიბებაში, სანამ ისინი რეალურად დაიწყება.",
+        "beta_feature": "ეს არის ბეტა ფუნქცია",
+        "beta_feedback_leave_button": "ბეტადან გასასვლელად ეწვიეთ თქვენს პარამეტრებს.",
+        "beta_feedback_title": "%(featureName)sბეტა გამოხმაურება",
+        "beta_section": "მომავალი ფუნქციები",
+        "bridge_state": "აჩვენეთ ინფორმაცია ხიდების შესახებ ოთახის პარამეტრ",
+        "bridge_state_channel": "არხი:<channelLink/>",
+        "bridge_state_creator": "ეს ხიდი უზრუნველყოფილი იყო<user /> .",
+        "bridge_state_manager": "ამ ხიდს მართავს<user /> .",
+        "bridge_state_workspace": "სამუშაო ადგილი:<networkLink/>",
+        "click_for_info": "დააწკაპუნეთ მეტი ინფორმაციისთვის",
+        "currently_experimental": "ახლა ექსპერიმენტული.",
+        "custom_themes": "დახმარება მორგებული თემების",
+        "dynamic_room_predecessors": "დინამიური ოთახის წინამორბედები",
+        "dynamic_room_predecessors_description": "",
+        "element_call_video_rooms": "ელემენტის ზარის ვიდეოოო",
+        "experimental_description": "ექსპერიმენტულად გრძნობ თავს? სცადეთ ჩვენი უახლესი იდეები განვითარების პროცესში. ეს მახასიათებლები არ არის დასრულებული; ისინი შეიძლება იყოს არასტაბილური, შეიძლება შეიცვალოს ან საერთოდ ჩამოაგდეს.<a> გაიგე მეტი</a> .",
+        "experimental_section": "ადრეული გადახედვები",
+        "feature_wysiwyg_composer_description": "გამოიყენეთ მდიდარი ტექსტი Markdown ნაცვლად შეტყობინების კომპოზიტორში.",
+        "group_calls": "ჯგუფური ზარის ახალი გამოცდილება",
+        "group_developer": "დეველოპერი",
+        "group_encryption": "დაშიფვრა",
+        "group_experimental": "ექსპერიმენტული",
+        "group_messaging": "შეტყობინებები",
+        "group_moderation": "ზომიერება",
+        "group_profile": "პროფილი",
+        "group_rooms": "ოთახები",
+        "group_spaces": "ფართები",
+        "group_themes": "თემები",
+        "group_voip": "ხმა და ვიდეო",
+        "group_widgets": "ვიჯეტები",
+        "hidebold": "შეტყობინების წერტილის დამალვა (მხოლოდ მრიცხველების სამკერდე ნიშნების ჩვენება)",
+        "html_topic": "ოთახის თემების HTML წარმოდგენის ჩვენება",
+        "join_beta": "შეუერთდი ბეტას",
+        "join_beta_reload": "ბეტაში შეერთება ხელახლა ჩაიტვირთება%(brand)s .",
+        "jump_to_date": "",
+        "jump_to_date_msc_support": "მოითხოვს თქვენს სერვერს MSC3030 მხარდაჭერას",
+        "latex_maths": "შეტყობინებებში LaTeX მათემატიკის",
+        "leave_beta": "დატოვე ბეტა",
+        "leave_beta_reload": "ბეტადან გასვლა ხელახლა ჩაიტვირთება%(brand)s .",
+        "location_share_live": "ადგილმდებარეობის ცოცხალი",
+        "location_share_live_description": "დროებითი განხორციელება ადგილები შენარჩუნებულია ოთახის ისტორიაში.",
+        "mjolnir": "ხალხის უგულებელყოფის ახალი გზები",
+        "msc3531_hide_messages_pending_moderation": "დაე მოდერატორებს დაიმალონ შეტყობინებები",
+        "notification_settings": "ახალი შეტყობინების პარა",
+        "notification_settings_beta_caption": "წარმოგიდგენთ შეტყობინებების პარამეტრების შესაცვლელად უფრო მარტივ გზას. მოარგეთ თქვენი%(brand)s , ისე როგორც შენ მოგწონს.",
+        "notification_settings_beta_title": "შეტყობინებების პარამეტრები",
+        "notifications": "ჩართეთ შეტყობინებების პანელი ოთახის სათაურში",
+        "render_reaction_images": "რეაქციებში მორგებული სურათების გადაცემა",
+        "render_reaction_images_description": "ზოგჯერ მოიხსენიება როგორც &quot;მორგებული emojis&quot;.",
+        "report_to_moderators": "მოდერატორებს ანგარიში",
+        "report_to_moderators_description": "ოთახებში, რომლებსაც აქვთ მოდერაციის მხარდაჭერა, ღილაკი „მოხსენება“ საშუალებას მოგცემთ შეატყობინოთ ოთახის მოდერატორებს არასათანადო მოპყრობის შესახებ.",
+        "sliding_sync": "მოსლაიდული სინქრო",
+        "sliding_sync_description": "აქტიური განვითარების პირობებში არ შეიძლება გამორთული იყოს.",
+        "sliding_sync_disabled_notice": "გამოდით და ისევ გამორთეთ",
+        "sliding_sync_server_no_support": "თქვენს სერვერს არ აქვს მხარდაჭერა",
+        "under_active_development": "აქტიური განვითარების ქვეშ.",
+        "unrealiable_e2e": "არასანდო დაშიფრულ ოთახებში",
+        "video_rooms": "ვიდეო ოთახები",
+        "video_rooms_a_new_way_to_chat": "ხმოვან და ვიდეოზე საუბრის ახალი გზა%(brand)s.",
+        "video_rooms_always_on_voip_channels": "ვიდეო ოთახები ყოველთვის ჩართული VoIP არხებია, რომლებიც ჩართულია ოთახში. %(brand)s",
+        "video_rooms_beta": "ვიდეო ოთახები ბეტა ფუნქციაა",
+        "video_rooms_faq1_answer": "გამოიყენეთ ღილაკი „+“ მარცხენა პანელის ოთახის განყოფილებაში.",
+        "video_rooms_faq1_question": "როგორ შემიძლია შევქმნათ ვიდეო ოთახი?",
+        "video_rooms_faq2_answer": "დიახ, ჩატის ქრონოლოგია ნაჩვენებია ვიდეოს გვერდით.",
+        "video_rooms_faq2_question": "შემიძლია ვიდეო ზარის გვერდით ტექსტური ჩატის გამოყენება?",
+        "video_rooms_feedbackSubheading": "გმადლობთ, რომ სცადეთ ბეტა. გთხოვთ, შეძლებისდაგვარად შეხვიდეთ დეტალურად, რათა გავაუმჯობესოთ იგი.",
+        "wysiwyg_composer": "მდიდარი ტექსტის რედაქ"
+    },
+    "labs_mjolnir": {
+        "advanced_warning": "⚠ ეს პარამეტრები განკუთვნილია მოწინავე მომხმარებლებისთვის.",
+        "ban_reason": "იგნორირებული/დაბლოკილი",
+        "error_adding_ignore": "შეცდომა იგნორირებული მომხმარებლის/სერვერის დამატებისას",
+        "error_adding_list_description": "გთხოვთ, დაადასტუროთ ოთახის ID ან მისამართი და სცადოთ ხელახლა.",
+        "error_adding_list_title": "შეცდომა სიაში გამოწერისას",
+        "error_removing_ignore": "შეცდომა იგნორირებული მომხმარებლის/სერვერის წაშლისას",
+        "error_removing_list_description": "გთხოვთ, სცადოთ ხელახლა ან ნახეთ თქვენი კონსოლი მინიშნებებისთვის.",
+        "error_removing_list_title": "შეცდომა სიიდან გამოწერის გაუქმებისას",
+        "explainer_1": "აქ დაამატეთ მომხმარებლები და სერვერები, რომელთა იგნორირება გსურთ. გამოიყენეთ ვარსკვლავები რომ გქონდეთ%(brand)s ემთხვევა ნებისმიერ პერსონაჟს. მაგალითად,<code> @bot:*</code> უგულებელყოფს ყველა მომხმარებელს, რომელსაც აქვს სახელი &quot;bot&quot; ნებისმიერ სერვერზე.",
+        "explainer_2": "ადამიანების იგნორირება ხდება აკრძალვის სიების საშუალებით, რომლებიც შეიცავს წესებს, თუ ვინ უნდა აიკრძალოს. აკრძალვის სიის გამოწერა ნიშნავს, რომ ამ სიით დაბლოკილი მომხმარებლები/სერვერები დაიმალება თქვენგან.",
+        "lists": "თქვენ ამჟამად გამოწერილი ხართ:",
+        "lists_description_1": "აკრძალვის სიის გამოწერა გამოიწვევს მასში გაწევრიანებას!",
+        "lists_description_2": "თუ ეს არ არის ის, რაც გსურთ, გთხოვთ, გამოიყენოთ სხვა ინსტრუმენტი მომხმარებლების უგულებელყოფისთვის.",
+        "lists_heading": "გამოწერილი სიები",
+        "lists_new_label": "ოთახის ID ან აკრძალვის სიის მისამართი",
+        "no_lists": "თქვენ არ ხართ გამოწერილი არცერთი სიით",
+        "personal_description": "თქვენი პერსონალური აკრძალვის სია შეიცავს ყველა მომხმარებელს/სერვერს, საიდანაც პირადად არ გსურთ შეტყობინებების ნახვა. თქვენი პირველი მომხმარებლის/სერვერის უგულებელყოფის შემდეგ, ახალი ოთახი გამოჩნდება თქვენს ოთახების სიაში სახელად &#39;%(myBanList)s - დარჩით ამ ოთახში აკრძალვის სიის მოქმედების შესანარჩუნებლად.",
+        "personal_empty": "თქვენ არავის უგულებელყოფთ.",
+        "personal_heading": "პირადი აკრძალვის სია",
+        "personal_new_label": "სერვერის ან მომხმარებლის ID იგნორირება",
+        "personal_new_placeholder": "მაგ.: @bot:* ან example.org",
+        "personal_section": "თქვენ ამჟამად უგულებელყოფთ:",
+        "room_name": "ჩემი აკრძალვის სია",
+        "room_topic": "ეს არის თქვენი დაბლოკილი მომხმარებლების/სერვერების სია - არ დატოვოთ ოთახი!",
+        "rules_empty": "არცერთი",
+        "rules_server": "სერვერის წესები",
+        "rules_title": "აკრძალვის სიის წესები -%(roomName)s",
+        "rules_user": "მომხმარებლის წესები",
+        "something_went_wrong": "რაღაც შეფერხდა. გთხოვთ, სცადოთ ხელახლა ან ნახეთ თქვენი კონსოლი მინიშნებებისთვის.",
+        "title": "იგნორირებული მომხმარებლები",
+        "view_rules": "წესების ნახვა"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "ეს სახლის სერვერი არ არის კონფიგურირებული რუკების ჩვენებისთვის.",
+        "MapStyleUrlNotReachable": "ეს სახლის სერვერი არ არის სწორად კონფიგურირებული რუკების საჩვენებლად, ან კონფიგურირებული რუქის სერვერი შეიძლება მიუწვდომელი იყოს.",
+        "WebGLNotEnabled": "WebGL საჭიროა რუკების საჩვენებლად, გთხოვთ, ჩართოთ ის თქვენი ბრაუზერის პარამეტრებში.",
+        "click_drop_pin": "დააწკაპუნეთ ქინძისთავის დასაშვებად",
+        "click_move_pin": "დააწკაპუნეთ პინის გადასატანად",
+        "close_sidebar": "გვერდითი ზოლის დახურვა",
+        "error_fetch_location": "მდებარეობის მოძიება ვერ მოხერხდა",
+        "error_no_perms_description": "ამ ოთახში მდებარეობების გასაზიარებლად უნდა გქონდეთ შესაბამისი ნებართვები.",
+        "error_no_perms_title": "თქვენ არ გაქვთ მდებარეობების გაზიარების ნებართვა",
+        "error_send_description": "%(brand)sთქვენი მდებარეობის გაგზავნა ვერ მოხერხდა. გთხოვთ, სცადოთ მოგვიანებით.",
+        "error_send_title": "თქვენი მდებარეობის გაგზავნა ვერ მოხერხდა",
+        "error_sharing_live_location": "თქვენი პირდაპირი მდებარეობის გაზიარებისას მოხდა შეცდომა",
+        "error_stopping_live_location": "თქვენი პირდაპირი მდებარეობის შეჩერებისას მოხდა შეცდომა",
+        "expand_map": "გააფართოვეთ რუკა",
+        "failed_generic": "თქვენი მდებარეობის მიღება ვერ მოხერხდა. გთხოვთ, სცადოთ მოგვიანებით.",
+        "failed_load_map": "რუკის ჩატვირთვა შეუძლებელია",
+        "failed_permission": "%(brand)sუარი ეთქვა თქვენი მდებარეობის მოპოვების ნებართვაზე. გთხოვთ, დაუშვათ მდებარეობაზე წვდომა თქვენი ბრაუზერის პარამეტრებში.",
+        "failed_timeout": "თქვენი მდებარეობის მოპოვების მცდელობის დრო ამოიწურა. გთხოვთ, სცადოთ მოგვიანებით.",
+        "failed_unknown": "უცნობი შეცდომა მდებარეობის მიღებისას. გთხოვთ, სცადოთ მოგვიანებით.",
+        "find_my_location": "იპოვე ჩემი მდებარეობა",
+        "live_description": "%(displayName)sცოცხალი მდებარეობა",
+        "live_enable_description": "გთხოვთ გაითვალისწინოთ: ეს არის ლაბორატორიის ფუნქცია, რომელიც დროებითი გან ეს ნიშნავს, რომ თქვენ ვერ შეძლებთ წაშალოთ თქვენი მდებარეობის ისტორია და მოწინავე მომხმარებლები შეძლებენ თქვენი მდებარეობის ისტორიის ნახვას მაშინაც კი, რაც შეწყვეტთ თქვენი ცოცხალი ადგილმდებარეობის გაზიარებას",
+        "live_enable_heading": "მდებარეობის პირდაპირი გაზიარება",
+        "live_location_active": "თქვენ აზიარებთ თქვენს პირდაპირ მდებარეობას",
+        "live_location_enabled": "პირდაპირი მდებარეობა ჩართულია",
+        "live_location_ended": "პირდაპირი მდებარეობა დასრულდა",
+        "live_location_error": "პირდაპირი მდებარეობის შეცდომა",
+        "live_locations_empty": "ცოცხალი მდებარეობები არ არის",
+        "live_share_button": "გააზიარეთ%(duration)s",
+        "live_toggle_label": "ჩართეთ მდებარეობის პირდაპირი გაზიარება",
+        "live_until": "იცოცხლე სანამ%(expiryTime)s",
+        "live_update_time": "განახლებულია%(humanizedUpdateTime)s",
+        "loading_live_location": "მიმდინარეობს პირდაპირი მდებარეობის ჩატვირთვა…",
+        "location_not_available": "მდებარეობა მიუწვდომელია",
+        "map_feedback": "რუკის გამოხმაურება",
+        "mapbox_logo": "Mapbox ლოგო",
+        "reset_bearing": "ტარების გადატვირთვა ჩრდილოეთით",
+        "share_button": "მდებარეობის გაზიარება",
+        "share_type_live": "ჩემი ცოცხალი ადგილმდებარეობა",
+        "share_type_own": "ჩემი ამჟამინდელი მდებარეობა",
+        "share_type_pin": "დააგდე პინი",
+        "share_type_prompt": "რა ტიპის მდებარეობის გაზიარება გსურთ?",
+        "toggle_attribution": "მიკუთვნების გადართვა"
+    },
+    "member_list_back_action_label": "ოთახის წევრები",
+    "mobile_guide": {
+        "toast_accept": "გამოიყენეთ აპლიკაცია",
+        "toast_description": "%(brand)sექსპერიმენტულია მობილური ვებ ბრაუზერზე. უკეთესი გამოცდილებისა და უახლესი ფუნქციებისთვის გამოიყენეთ ჩვენი უფასო მშობლიური აპლიკაცია.",
+        "toast_title": "გამოიყენეთ აპლიკაცია უკეთესი გამოცდილებისთვის"
+    },
+    "name_and_id": "%(name)s(%(userId)s )",
+    "notifications": {
+        "all_messages": "ყველა შეტყობინება",
+        "all_messages_description": "მიიღეთ შეტყობინება ყოველი შეტყობინებისთვის",
+        "class_global": "გლობალური",
+        "class_other": "სხვა",
+        "default": "ნაგულისხმევი",
+        "email_pusher_app_display_name": "ელ.ფოსტის შეტყობინებები",
+        "enable_prompt_toast_description": "დესკტოპის შეტყობინებების ჩართვა",
+        "enable_prompt_toast_title": "შეტყობინებები",
+        "enable_prompt_toast_title_from_message_send": "არ გამოტოვოთ პასუხი",
+        "error_change_title": "შეტყობინებების პარამეტრების შეცვლა",
+        "keyword": "საკვანძო სიტყვა",
+        "keyword_new": "ახალი საკვანძო სიტყვა",
+        "mark_all_read": "მონიშნეთ ყველა წაკითხულად",
+        "mentions_and_keywords": "@ხსენებები და საკვანძო სიტყვები",
+        "mentions_and_keywords_description": "მიიღეთ შეტყობინებები მხოლოდ ხსენებებითა და საკვანძო სიტყვებით, როგორც დაყენებულია თქვენსში<a> პარამეტრები</a>",
+        "mentions_keywords": "ხსენებები და საკვანძო სიტყვები",
+        "message_didnt_send": "შეტყობინება არ გაიგზავნა. დააწკაპუნეთ ინფორმაციისთვის.",
+        "mute_description": "თქვენ არ მიიღებთ შეტყობინებებს"
+    },
+    "notifier": {
+        "m.key.verification.request": "%(name)sშემოწმებას ითხოვს"
+    },
+    "power_level": {
+        "admin": "ადმინისტრატორი",
+        "custom": "მორგებული (%(level)s )",
+        "custom_level": "მორგებული დონე",
+        "default": "ნაგულისხმევი",
+        "label": "სიმძლავრის დონე",
+        "moderator": "მოდერატორი",
+        "restricted": "შეზღუდული"
+    },
+    "presence": {
+        "away": "მოშორებით",
+        "busy": "დაკავებულია",
+        "idle": "უსაქმური",
+        "idle_for": "უსაქმური ამისთვის%(duration)s",
+        "offline": "ხაზგარეშე",
+        "offline_for": "ხაზგარეშე ამისთვის%(duration)s",
+        "online": "ონლაინ",
+        "online_for": "ონლაინ ამისთვის%(duration)s",
+        "unknown": "უცნობი",
+        "unknown_for": "უცნობია%(duration)s"
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "ეს მოწვევა გაიგზავნა%(email)s რომელიც არ არის დაკავშირებული თქვენს ანგარიშთან",
+        "3pid_invite_email_not_found_account_room": "ეს მოწვევა%(roomName)s გაგზავნეს%(email)s რომელიც არ არის დაკავშირებული თქვენს ანგარიშთან",
+        "3pid_invite_error_description": "შეცდომა (%(errcode)s ) დაბრუნდა თქვენი მოწვევის დადასტურების მცდელობისას. შეგიძლიათ სცადოთ ამ ინფორმაციის გადაცემა იმ პირს, ვინც მოგიწვიათ.",
+        "3pid_invite_error_invite_action": "შეეცადეთ შეუერთდეთ მაინც",
+        "3pid_invite_error_invite_subtitle": "თქვენ შეგიძლიათ შეუერთდეთ მას მხოლოდ სამუშაო მოწვევით.",
+        "3pid_invite_error_public_subtitle": "აქ მაინც შეგიძლიათ შეუერთდეთ.",
+        "3pid_invite_error_title": "თქვენს მოწვევასთან დაკავშირებით მოხდა რაღაც შეცდომა.",
+        "3pid_invite_error_title_room": "შეფერხება მოხდა თქვენს მოწვევასთან დაკავშირებით%(roomName)s",
+        "3pid_invite_no_is_subtitle": "გამოიყენეთ პირადობის სერვერი პარამეტრებში, რათა მიიღოთ მოწვევები პირდაპირ%(brand)s .",
+        "banned_by": "აკრძალული იყავით%(memberName)s",
+        "banned_from_room_by": "აგეკრძალათ%(roomName)s მიერ%(memberName)s",
+        "context_menu": {
+            "copy_link": "დააკოპირეთ ოთახის ბმული",
+            "favourite": "საყვარელი",
+            "forget": "დაივიწყე ოთახი",
+            "low_priority": "დაბალი პრიორიტეტი",
+            "mark_read": "წაკითხულად მონიშვნა",
+            "notifications_default": "ემთხვევა ნაგულისხმევ პარამეტრს",
+            "notifications_mute": "მუნჯი ოთახი",
+            "title": "ოთახის ვარიანტები",
+            "unfavourite": "ფავორიტი"
+        },
+        "creating_room_text": "ჩვენ ვქმნით ოთახს%(names)s",
+        "dm_invite_action": "დაიწყეთ საუბარი",
+        "dm_invite_subtitle": "<userName/>სურს ჩატი",
+        "dm_invite_title": "გსურთ ესაუბროთ%(user)s ?",
+        "drop_file_prompt": "ჩამოაგდეთ ფაილი აქ ასატვირთად",
+        "edit_topic": "თემის რედაქტირება",
+        "error_3pid_invite_email_lookup": "მომხმარებლის ელფოსტით პოვნა შეუძლებელია",
+        "error_cancel_knock_title": "გაუქმება ვერ მოხერხდა",
+        "error_join_403": "ამ ოთახში შესასვლელად მოწვევა გჭირდებათ.",
+        "error_join_404_1": "თქვენ სცადეთ შეერთება ოთახის ID-ის გამოყენებით, სერვერების სიის მოწოდების გარეშე, რომელთა მეშვეობითაც შეგიძლიათ შეუერთდეთ. ოთახის ID არის შიდა იდენტიფიკატორი და არ შეიძლება გამოყენებულ იქნას ოთახთან შესაერთებლად დამატებითი ინფორმაციის გარეშე.",
+        "error_join_404_2": "თუ იცით ოთახის მისამართი, სცადეთ მის ნაცვლად შემოერთება.",
+        "error_join_404_invite": "ადამიანი, ვინც მოგიწვიათ, უკვე წავიდა, ან მისი სერვერი ხაზგარეშეა.",
+        "error_join_404_invite_same_hs": "ვინც მოგიწვია უკვე წავიდა.",
+        "error_join_connection": "შეერთებისას მოხდა შეცდომა.",
+        "error_join_incompatible_version_1": "უკაცრავად, თქვენი სახლის სერვერი ძალიან ძველია აქ მონაწილეობისთვის.",
+        "error_join_incompatible_version_2": "გთხოვთ, დაუკავშირდეთ თქვენი სახლის სერვერის ადმინისტრატორს.",
+        "error_join_title": "შეერთება ვერ მოხერხდა",
+        "error_jump_to_date": "სერვერი დაბრუნდა%(statusCode)s შეცდომის კოდით%(errorCode)s",
+        "error_jump_to_date_connection": "ქსელის შეცდომა მოხდა მოცემული თარიღის პოვნისა და გადასვლის მცდელობისას. თქვენი საშინაო სერვერი შეიძლება ამოქმედებული იყოს ან უბრალოდ დროებითი პრობლემა იყო თქვენს ინტერნეტთან დაკავშირებით. გთხოვთ კვლავ სცადოთ თუ ეს გაგრძელდება, გთხოვთ, დაუკავშირდეთ თქვენს ჰოისსერვერის",
+        "error_jump_to_date_details": "შეცდომის დეტალები",
+        "error_jump_to_date_not_found": "ჩვენ ვერ ვიპოვეთ მოლოდინში მოვლენილი ღონისძიება%(dateString)s . სცადეთ აირჩიოთ უფრო ადრეული თარიღი.",
+        "error_jump_to_date_send_logs_prompt": "გთხოვთ წარადგინოთ<debugLogsLink> გამართვის ჟურნალები</debugLogsLink> დაგვეხმარება პრობლემის გარკვევაში.",
+        "error_jump_to_date_title": "ამ თარიღის მოვლენის პოვნა შეუძლებელია",
+        "face_pile_summary": {
+            "one": "%(count)sადამიანი, რომელსაც იცნობთ, უკვე შემოუერთდა",
+            "other": "%(count)sადამიანები, რომლებსაც იცნობთ, უკვე შემოუერთდნენ"
+        },
+        "face_pile_tooltip_label": {
+            "one": "1 წევრის ნახვა",
+            "other": "ყველას ნახვა%(count)s წევრები"
+        },
+        "face_pile_tooltip_shortcut": "მათ შორის%(commaSeparatedMembers)s",
+        "face_pile_tooltip_shortcut_joined": "მათ შორის შენც,%(commaSeparatedMembers)s",
+        "failed_reject_invite": "მოწვევის უარყოფა ვერ მოხერხდა",
+        "forget_room": "დაივიწყე ეს ოთახი",
+        "forget_space": "დაივიწყეთ ეს სივრცე",
+        "header": {
+            "n_people_asking_to_join": {
+                "one": "ითხოვს შეერთებას",
+                "other": "%(count)sადამიანები, რომლებიც ითხოვენ შეერთებას"
+            },
+            "room_is_public": "ეს ოთახი საჯაროა"
+        },
+        "header_untrusted_label": "არასანდო",
+        "inaccessible": "ეს ოთახი ან სივრცე ამჟამად მიუწვდომელია.",
+        "inaccessible_name": "%(roomName)sამ დროისთვის მიუწვდომელია.",
+        "inaccessible_subtitle_1": "სცადეთ ხელახლა მოგვიანებით, ან სთხოვეთ ოთახის ან სივრცის ადმინისტრატორს, შეამოწმოს, გაქვთ თუ არა წვდომა.",
+        "inaccessible_subtitle_2": "%(errcode)sდააბრუნეს ოთახში ან სივრცეში შესვლის მცდელობისას. თუ ფიქრობთ, რომ ამ შეტყობინებას შეცდომით ხედავთ, გთხოვთ<issueLink> შეცდომის ანგარიშის გაგზავნა</issueLink> .",
+        "intro": {
+            "dm_caption": "მხოლოდ თქვენ ორნი ხართ ამ საუბარში, თუ რომელიმე თქვენგანი არ მოიწვევს ვინმეს.",
+            "enable_encryption_prompt": "ჩართეთ დაშიფვრა პარამეტრებში.",
+            "encrypted_3pid_dm_pending_join": "მას შემდეგ რაც ყველა შეუერთდება, თქვენ შეძლებთ ჩეთს",
+            "no_avatar_label": "დაამატეთ ფოტო, რათა ადამიანებმა ადვილად შეამჩნიონ თქვენი ოთახი.",
+            "no_topic": "<a>დაამატეთ თემა</a> დაეხმარონ ხალხს გაიგონ რაზეა საუბარი.",
+            "private_unencrypted_warning": "თქვენი პირადი შეტყობინებები ჩვეულებრივ დაშიფრულია, მაგრამ ეს ოთახი არ არის. ჩვეულებრივ, ეს გამოწვეულია მხარდაჭერილი მოწყობილობის ან მეთოდის გამოყენების გამო, როგორიცაა ელფოსტის მოწვევები.",
+            "room_invite": "მოიწვიე მხოლოდ ამ ოთახში",
+            "send_message_start_dm": "გაგზავნეთ თქვენი პირველი შეტყობინება მოსაწვევად<displayName/> სასაუბროდ",
+            "start_of_dm_history": "ეს არის თქვენი პირდაპირი შეტყობინებების ისტორიის დასაწყისი<displayName/> .",
+            "start_of_room": "ეს არის დასაწყისი<roomName/> .",
+            "topic": "თემა: %(topic)s",
+            "topic_edit": "თემა:%(topic)s (<a> რედაქტირება</a> )",
+            "unencrypted_warning": "ბოლოდან ბოლომდე დაშიფვრა არ არის ჩართული",
+            "user_created": "%(displayName)sშექმნა ეს ოთახი.",
+            "you_created": "თქვენ შექმენით ეს ოთახი."
+        },
+        "invite_email_mismatch_suggestion": "გააზიარეთ ეს ელფოსტა პარამეტრებში, რათა პირდაპირ მიიღოთ მოწვევები%(brand)s .",
+        "invite_sent_to_email": "ეს მოწვევა გაიგზავნა%(email)s",
+        "invite_sent_to_email_room": "ეს მოწვევა%(roomName)s გაგზავნეს%(email)s",
+        "invite_subtitle": "მოწვეული<userName/>",
+        "invite_this_room": "მოიწვიე ამ ოთახში",
+        "invite_title": "გინდა შემოუერთდე%(roomName)s ?",
+        "inviter_unknown": "უცნობი",
+        "invites_you_text": "<inviter/>გიწვევთ",
+        "join_button_account": "დარეგისტრირდით",
+        "join_failed_needs_invite": "სანახავად%(roomName)s , მოწვევა გჭირდებათ",
+        "join_the_discussion": "შეუერთდით დისკუსიას",
+        "join_title": "ჩაერთეთ ოთახში მონაწილეობის მისაღებად",
+        "join_title_account": "შეუერთდით საუბარს ანგარიშით",
+        "joining": "უერთდება…",
+        "joining_room": "ოთახთან შეერთება…",
+        "joining_space": "სივრცის შეერთება…",
+        "jump_read_marker": "გადადით პირველ წაუკითხავ შეტყობინებაზე.",
+        "jump_to_bottom_button": "გადადით უახლეს შეტყობინებებზე",
+        "jump_to_date": "თარიღზე გადასვლა",
+        "jump_to_date_beginning": "ოთახის დასაწყისი",
+        "jump_to_date_prompt": "აირჩიეთ თარიღი, რომელზეც გადახვალთ",
+        "kick_reason": "მიზეზი:%(reason)s",
+        "kicked_by": "თქვენ წაშალეთ%(memberName)s",
+        "kicked_from_room_by": "თქვენ წაშალეთ%(roomName)s მიერ%(memberName)s",
+        "knock_cancel_action": "Მოთხოვნის გაუქმება",
+        "knock_denied_subtitle": "ვინაიდან წვდომაზე უარი გეთქვათ, თქვენ ვერ შეუერთდებით, თუ ჯგუფის ადმინისტრატორის ან მოდერატორის მიერ არ მოგიწვევთ.",
+        "knock_denied_title": "თქვენ უარი თქვით წვდომაზე",
+        "knock_message_field_placeholder": "შეტყობინება (სურვილისამებრ)",
+        "knock_prompt": "გთხოვ შეერთდეს?",
+        "knock_prompt_name": "მოითხოვეთ შეერთება%(roomName)s ?",
+        "knock_send_action": "მოითხოვეთ წვდომა",
+        "knock_sent": "გაწევრიანების მოთხოვნა გაიგზავნა",
+        "knock_sent_subtitle": "თქვენი შეერთების მოთხოვნა მოლოდინშია.",
+        "knock_subtitle": "თქვენ უნდა გქონდეთ წვდომა ამ ოთახში, რათა ნახოთ ან მონაწილეობა მიიღოთ საუბარში. თქვენ შეგიძლიათ გააგზავნოთ მოთხოვნა გაწევრიანების შესახებ ქვემოთ.",
+        "leave_error_title": "შეცდომა ოთახიდან გასვლისას",
+        "leave_server_notices_description": "ეს ოთახი გამოიყენება Homeserver-ის მნიშვნელოვანი შეტყობინებებისთვის, ასე რომ თქვენ არ შეგიძლიათ მისი დატოვება.",
+        "leave_server_notices_title": "სერვერის შეტყობინებების ოთახიდან გასვლა შეუძლებელია",
+        "leave_unexpected_error": "სერვერის მოულოდნელი შეცდომა ოთახიდან გასვლის მცდელობისას",
+        "link_email_to_receive_3pid_invite": "დააკავშირეთ ეს ელფოსტა თქვენს ანგარიშთან პარამეტრებში, რათა პირდაპირ მიიღოთ მოწვევები%(brand)s .",
+        "loading_preview": "გადახედვის ჩატვირთვა",
+        "no_peek_join_prompt": "%(roomName)sწინასწარ დათვალიერება შეუძლებელია. გსურთ შეუერთდეთ მას?",
+        "no_peek_no_name_join_prompt": "გადახედვა არ არის, გსურთ შეუერთდეთ?",
+        "not_found_subtitle": "დარწმუნებული ხარ, რომ სწორ ადგილას ხარ?",
+        "not_found_title": "ეს ოთახი ან სივრცე არ არსებობს.",
+        "not_found_title_name": "%(roomName)sარ არსებობს.",
+        "peek_join_prompt": "თქვენ ათვალიერებთ%(roomName)s . გსურთ შეუერთდეთ მას?",
+        "read_topic": "დააწკაპუნეთ თემის წასაკითხად",
+        "rejecting": "მოწვევის უარყოფა…",
+        "rejoin_button": "ხელახლა შეერთება",
+        "search": {
+            "all_rooms_button": "მოძებნეთ ყველა ოთახი",
+            "this_room_button": "მოძებნეთ ეს ოთახი"
+        },
+        "status_bar": {
+            "delete_all": "წაშალე ყველა",
+            "exceeded_resource_limit": "თქვენი შეტყობინება არ გაიგზავნა, რადგან ამ სახლის სერვერმა გადააჭარბა რესურსის ლიმიტს. გთხოვთ<a> დაუკავშირდით თქვენი სერვისის ადმინისტრატორს</a> სერვისით სარგებლობის გასაგრძელებლად.",
+            "homeserver_blocked": "თქვენი შეტყობინება არ გაიგზავნა, რადგან ეს სახლის სერვერი დაბლოკილია მისი ადმინისტრატორის მიერ. გთხოვთ<a> დაუკავშირდით თქვენი სერვისის ადმინისტრატორს</a> სერვისით სარგებლობის გასაგრძელებლად.",
+            "monthly_user_limit_reached": "თქვენი შეტყობინება არ გაიგზავნა, რადგან ამ სახლის სერვერმა მიაღწია აქტიური მომხმარებლის ყოველთვიურ ლიმიტს. გთხოვთ<a> დაუკავშირდით თქვენი სერვისის ადმინისტრატორს</a> სერვისით სარგებლობის გასაგრძელებლად.",
+            "requires_consent_agreement": "სანამ არ განიხილავთ და არ დაეთანხმებით შეტყობინებას ვერ გაგზავნით<consentLink> ჩვენი წესები და პირობები</consentLink> .",
+            "retry_all": "ხელახლა სცადე ყველა",
+            "select_messages_to_retry": "თქვენ შეგიძლიათ აირჩიოთ ყველა ან ცალკეული შეტყობინება ხელახლა საცდელად ან წასაშლელად",
+            "server_connectivity_lost_description": "გაგზავნილი შეტყობინებები შეინახება სანამ თქვენი კავშირი არ დაბრუნდება.",
+            "server_connectivity_lost_title": "სერვერთან კავშირი დაიკარგა.",
+            "some_messages_not_sent": "ზოგიერთი თქვენი შეტყობინება არ არის გაგზავნილი"
+        },
+        "unknown_status_code_for_timeline_jump": "უცნობი სტატუსის კოდი",
+        "unread_notifications_predecessor": {
+            "one": "თქვენ გაქვთ%(count)s წაუკითხავი შეტყობინება ამ ოთახის წინა ვერსიაში.",
+            "other": "თქვენ გაქვთ%(count)s წაუკითხავი შეტყობინებები ამ ოთახის წინა ვერსიაში."
+        },
+        "upgrade_error_description": "ორჯერ შეამოწმეთ, რომ თქვენს სერვერს აქვს არჩეული ოთახის ვერსია და სცადეთ ხელახლა.",
+        "upgrade_error_title": "შეცდომა ოთახის განახლებისას",
+        "upgrade_warning_bar": "ამ ოთახის განახლება დახურავს ოთახის ამჟამინდელ ინსტანციას და შექმნის განახლებულ ოთახს ამავე სახელწოდებით.",
+        "upgrade_warning_bar_admins": "მხოლოდ ოთახის ადმინისტრატორები დაინახავენ ამ გაფრთხილებას",
+        "upgrade_warning_bar_unstable": "ეს ოთახი არის გაშვებული ოთახის ვერსია<roomVersion /> , რომელიც ამ სახლის სერვერმა მოინიშნა, როგორც<i> არასტაბილური</i> .",
+        "upgrade_warning_bar_upgraded": "ეს ოთახი უკვე განახლებულია.",
+        "upload": {
+            "uploading_multiple_file": {
+                "one": "ატვირთვა%(filename)s და%(count)s სხვა",
+                "other": "ატვირთვა%(filename)s და%(count)s სხვები"
+            },
+            "uploading_single_file": "ატვირთვა%(filename)s"
+        },
+        "waiting_for_join_subtitle": "მას შემდეგ, რაც მოწვეული მომხმარებლები შეუერთდებიან%(brand)s , თქვენ შეძლებთ ჩეთს და ოთახი იქნება ბოლომდე დაშიფრული",
+        "waiting_for_join_title": "ელოდება მომხმარებლების შეერთებას%(brand)s"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "გადაწყვიტეთ ვის შეუძლია ნახოს და შეუერთდეს%(spaceName)s .",
+            "title": "წვდომა"
+        },
+        "advanced": {
+            "error_upgrade_description": "ოთახის განახლება ვერ დასრულდა",
+            "error_upgrade_title": "ოთახის განახლება ვერ მოხერხდა",
+            "information_section_room": "ოთახის ინფორმაცია",
+            "information_section_space": "კოსმოსური ინფორმაცია",
+            "room_id": "შიდა ოთახის ID",
+            "room_predecessor": "იხილეთ ძველი შეტყობინებები%(roomName)s .",
+            "room_upgrade_button": "განაახლეთ ეს ოთახი ოთახის რეკომენდებულ ვერსიამდე",
+            "room_upgrade_warning": "<b>გაფრთხილება</b> : ოთახის განახლება იქნება<i> არ მოხდეს ოთახის წევრების ავტომატურად მიგრაცია ოთახის ახალ ვერსიაში.</i> ჩვენ გამოვაქვეყნებთ ახალი ოთახის ბმულს ოთახის ძველ ვერსიაში - ოთახის წევრებს მოუწევთ დააწკაპუნოთ ამ ბმულზე, რათა შეუერთდნენ ახალ ოთახს.",
+            "room_version": "ოთახის ვერსია:",
+            "room_version_section": "ოთახის ვერსია",
+            "space_predecessor": "იხილეთ ძველი ვერსია%(spaceName)s .",
+            "space_upgrade_button": "განაახლეთ ეს სივრცე ოთახის რეკომენდებულ ვერსიამდე",
+            "unfederated": "ეს ოთახი მიუწვდომელია დისტანციური Matrix სერვერებით",
+            "upgrade_button": "განაახლეთ ეს ოთახი ვერსიამდე%(version)s",
+            "upgrade_dialog_description": "ამ ოთახის განახლება მოითხოვს ოთახის ამჟამინდელი ინსტანციის დახურვას და მის ადგილას ახალი ოთახის შექმნას. იმისათვის, რომ ოთახის წევრებს მივცეთ საუკეთესო გამოცდილება, ჩვენ:",
+            "upgrade_dialog_description_1": "შექმენით ახალი ოთახი იგივე სახელით, აღწერილობით და ავატარით",
+            "upgrade_dialog_description_2": "განაახლეთ ოთახის ნებისმიერი ლოკალური მეტსახელი, რათა მიუთითოთ ახალი ოთახი",
+            "upgrade_dialog_description_3": "შეაჩერეთ მომხმარებლების საუბარი ოთახის ძველ ვერსიაში და გამოაქვეყნეთ შეტყობინება, რომელიც მომხმარებლებს ახალ ოთახში გადასვლის რეკომენდაციას აძლევდა",
+            "upgrade_dialog_description_4": "დააბრუნეთ ძველი ოთახის ბმული ახალი ოთახის დასაწყისში, რათა ხალხმა ნახოს ძველი შეტყობინებები",
+            "upgrade_dialog_title": "განაახლეთ ოთახის ვერსია",
+            "upgrade_dwarning_ialog_title_public": "განაახლეთ საჯარო ოთახი",
+            "upgrade_warning_dialog_description": "ოთახის განახლება არის მოწინავე ქმედება და ჩვეულებრივ რეკომენდებულია, როდესაც ოთახი არასტაბილურია შეცდომების, დაკარგული ფუნქციების ან უსაფრთხოების დაუცველობის გამო.",
+            "upgrade_warning_dialog_explainer": "<b>გთხოვთ გაითვალისწინოთ, რომ განახლება გახდის ოთახის ახალ ვერსიას</b> . ყველა მიმდინარე შეტყობინება დარჩება ამ დაარქივებულ ოთახში.",
+            "upgrade_warning_dialog_footer": "თქვენ განაახლებთ ამ ოთახს<oldVersion /> რომ<newVersion /> .",
+            "upgrade_warning_dialog_invite_label": "ავტომატურად მოიწვიეთ წევრები ამ ოთახიდან ახალ ოთახში",
+            "upgrade_warning_dialog_report_bug_prompt": "ეს ჩვეულებრივ გავლენას ახდენს მხოლოდ სერვერზე ოთახის დამუშავებაზე. თუ თქვენ გაქვთ პრობლემები თქვენს%(brand)s , გთხოვთ შეატყობინოთ ხარვეზის შესახებ.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "ეს ჩვეულებრივ გავლენას ახდენს მხოლოდ სერვერზე ოთახის დამუშავებაზე. თუ თქვენ გაქვთ პრობლემები თქვენს%(brand)s , გთხოვთ<a> შეატყობინეთ შეცდომის შესახებ</a> .",
+            "upgrade_warning_dialog_title": "ოთახის განახლება",
+            "upgrade_warning_dialog_title_private": "განაახლეთ პირადი ოთახი"
+        },
+        "alias_not_specified": "არ არის მითითებული",
+        "bridges": {
+            "description": "ეს ოთახი აკავშირებს შეტყობინებებს შემდეგ პლატფორმებზე.<a> გაიგე მეტი.</a>",
+            "empty": "ეს ოთახი არ აკავშირებს შეტყობინებებს არცერთ პლატფორმაზე.<a> გაიგე მეტი.</a>",
+            "title": "ხიდები"
+        },
+        "delete_avatar_label": "ავატარის წაშლა",
+        "general": {
+            "alias_field_has_domain_invalid": "აკლია დომენის გამყოფი მაგ.:domain .org)",
+            "alias_field_has_localpart_invalid": "გამოტოვებულია ოთახის სახელი ან გამყოფი მაგ. (my-room:domain.org)",
+            "alias_field_matches_invalid": "ეს მისამართი არ მიუთითებს ამ ოთახში",
+            "alias_field_placeholder_default": "მაგ. ჩემი ოთახი",
+            "alias_field_required_invalid": "გთხოვთ მიუთითოთ მისამართი",
+            "alias_field_safe_localpart_invalid": "ზოგიერთი პერსონაჟი დაუშვებელია",
+            "alias_field_taken_invalid": "ამ მისამართს ჰქონდა არასწორი სერვერი ან უკვე გამოიყენება",
+            "alias_field_taken_invalid_domain": "ეს მისამართი უკვე გამოიყენება",
+            "alias_field_taken_valid": "ეს მისამართი ხელმისაწვდომია გამოსაყენებლად",
+            "alias_heading": "ოთახის მისამართი",
+            "aliases_items_label": "სხვა გამოქვეყნებული მისამართები:",
+            "aliases_no_items_label": "სხვა გამოქვეყნებული მისამართი ჯერ არ არის, დაამატეთ ერთი ქვემოთ",
+            "aliases_section": "ოთახის მისამართები",
+            "avatar_field_label": "ოთახის ავატარი",
+            "canonical_alias_field_label": "მთავარი მისამართი",
+            "default_url_previews_off": "URL-ის გადახედვა ნაგულისხმევად გამორთულია ამ ოთახში მონაწილეებისთვის.",
+            "default_url_previews_on": "URL-ის გადახედვა ნაგულისხმევად ჩართულია ამ ოთახში მონაწილეებისთვის.",
+            "description_space": "შეცვალეთ პარამეტრები, რომლებიც დაკავშირებულია თქვენს სივრცესთან.",
+            "error_creating_alias_description": "ამ მისამართის შექმნისას მოხდა შეცდომა. ეს შეიძლება არ იყოს დაშვებული სერვერის მიერ ან მოხდა დროებითი უკმარისობა.",
+            "error_creating_alias_title": "შეცდომა მისამართის შექმნისას",
+            "error_deleting_alias_description": "ამ მისამართის წაშლისას მოხდა შეცდომა. ის შეიძლება აღარ არსებობდეს ან მოხდა დროებითი შეცდომა.",
+            "error_deleting_alias_description_forbidden": "თქვენ არ გაქვთ მისამართის წაშლის უფლება.",
+            "error_deleting_alias_title": "მისამართის წაშლისას მოხდა შეცდომა",
+            "error_save_space_settings": "სივრცის პარამეტრების შენახვა ვერ მოხერხდა.",
+            "error_updating_alias_description": "ოთახის ალტერნატიული მისამართების განახლებისას მოხდა შეცდომა. ეს შეიძლება არ იყოს დაშვებული სერვერის მიერ ან მოხდა დროებითი უკმარისობა.",
+            "error_updating_canonical_alias_description": "ოთახის მთავარი მისამართის განახლებისას მოხდა შეცდომა. ეს შეიძლება არ იყოს დაშვებული სერვერის მიერ ან მოხდა დროებითი უკმარისობა.",
+            "error_updating_canonical_alias_title": "შეცდომა ძირითადი მისამართის განახლებისას",
+            "leave_space": "",
+            "local_alias_field_label": "ადგილობრივი მისამართი",
+            "local_aliases_explainer_room": "დააყენეთ მისამართები ამ ოთახისთვის, რათა მომხმარებლებმა შეძლონ ამ ოთახის პოვნა თქვენი სახლის სერვერის მეშვეობით (%(localDomain)s )",
+            "local_aliases_explainer_space": "დააყენეთ მისამართები ამ სივრცისთვის, რათა მომხმარებლებმა იპოვონ ეს სივრცე თქვენი სახლის სერვერის საშუალებით (%(localDomain)s )",
+            "local_aliases_section": "ადგილობრივი მისამართები",
+            "name_field_label": "ოთახის სახელი",
+            "new_alias_placeholder": "ახალი გამოქვეყნებული მისამართი (მაგ. #alias:server)",
+            "no_aliases_room": "ამ ოთახს არ აქვს ადგილობრივი მისამართები",
+            "no_aliases_space": "ამ სივრცეს არ აქვს ადგილობრივი მისამართები",
+            "other_section": "სხვა",
+            "publish_toggle": "გამოაქვეყნეთ ეს ოთახი საზოგადოებისთვის%(domain)s ოთახის დირექტორია?",
+            "published_aliases_description": "მისამართის გამოსაქვეყნებლად, ის ჯერ უნდა დაყენდეს ადგილობრივ მისამართად.",
+            "published_aliases_explainer_room": "გამოქვეყნებული მისამართები შეიძლება გამოყენებულ იქნას ნებისმიერ სერვერზე, რათა შეუერთდეს თქვენს ოთახში.",
+            "published_aliases_explainer_space": "გამოქვეყნებული მისამართები შეიძლება გამოყენებულ იქნას ნებისმიერ სერვერზე, რათა შეუერთდეს თქვენს სივრცეს.",
+            "published_aliases_section": "გამოქვეყნებული მისამართები",
+            "save": "ცვლილებების შენახვა",
+            "topic_field_label": "ოთახის თემა",
+            "url_preview_encryption_warning": "დაშიფრულ ოთახებში, როგორიცაა ეს, URL-ის გადახედვა ნაგულისხმევად გამორთულია, რათა უზრუნველყოს, რომ თქვენი სახლის სერვერი (სადაც იქმნება გადახედვები) ვერ შეაგროვებს ინფორმაციას ამ ოთახში ნახულ ბმულებზე.",
+            "url_preview_explainer": "როდესაც ვინმე ათავსებს URL-ს თავის შეტყობინებაში, URL-ის გადახედვა შეიძლება ნაჩვენები იყოს ამ ბმულის შესახებ მეტი ინფორმაციის მისაღებად, როგორიცაა სათაური, აღწერა და სურათი ვებსაიტიდან.",
+            "url_previews_section": "URL გადახედვები",
+            "user_url_previews_default_off": "თქვენ გაქვთ<a> ინვალიდი</a> URL-ის გადახედვა ნაგულისხმევად.",
+            "user_url_previews_default_on": "თქვენ გაქვთ<a> ჩართულია</a> URL-ის გადახედვა ნაგულისხმევად."
+        },
+        "notifications": {
+            "browse_button": "დათვალიერება",
+            "custom_sound_prompt": "დააყენეთ ახალი მორგებული ხმა",
+            "notification_sound": "შეტყობინების ხმა",
+            "settings_link": "მიიღეთ შეტყობინებები, როგორც დაყენებულია თქვენს<a> პარამეტრები</a>",
+            "sounds_section": "ხმები",
+            "upload_sound_label": "ატვირთეთ მორგებული ხმა",
+            "uploaded_sound": "ატვირთული ხმა"
+        },
+        "people": {
+            "knock_empty": "არანაირი მოთხოვნა",
+            "knock_section": "ითხოვს შეერთებას",
+            "see_less": "ნახე ნაკლები",
+            "see_more": "Მეტის ნახვა"
+        },
+        "permissions": {
+            "add_privileged_user_description": "მიეცით ამ ოთახში ერთ ან რამდენიმე მომხმარებელს მეტი პრივილეგიები",
+            "add_privileged_user_filter_placeholder": "მომხმარებლების ძიება ამ ოთახში…",
+            "add_privileged_user_heading": "დაამატეთ პრივილეგირებული მომხმარებლები",
+            "ban": "აკრძალეთ მომხმარებლები",
+            "ban_reason": "მიზეზი",
+            "banned_by": "აკრძალულია%(displayName)s",
+            "banned_users_section": "აკრძალული მომხმარებლები",
+            "error_changing_pl_description": "მოხდა შეცდომა მომხმარებლის ენერგიის დონის შეცვლისას. დარწმუნდით, რომ გაქვთ საკმარისი ნებართვები და სცადეთ ხელახლა.",
+            "error_changing_pl_reqs_description": "ოთახის დენის დონის მოთხოვნების შეცვლა მოხდა შეცდომა. დარწმუნდით, რომ გაქვთ საკმარისი ნებართვები და სცადეთ ხელახლა.",
+            "error_changing_pl_reqs_title": "ელექტროენერგიის დონის მოთხოვნის შეცვლისას შეცდომა",
+            "error_changing_pl_title": "დენის დონის შეცვლის შეცდომა",
+            "error_unbanning": "აკრძალვის გაუქმება ვერ მოხერხდა",
+            "events_default": "გაგზავნეთ შეტყობინებები",
+            "invite": "მოიწვიე მომხმარებლები",
+            "kick": "მომხმარებლების ამოღება",
+            "m.call": "დაწყება%(brand)s ზარები",
+            "m.call.member": "შეუერთდი%(brand)s ზარები",
+            "m.reaction": "გაგზავნეთ რეაქციები",
+            "m.room.avatar": "ოთახის ავატარის შეცვლა",
+            "m.room.avatar_space": "სივრცის ავატარის შეცვლა",
+            "m.room.canonical_alias": "შეცვალეთ ოთახის მთავარი მისამართი",
+            "m.room.canonical_alias_space": "სივრცის მთავარი მისამართის შეცვლა",
+            "m.room.encryption": "ოთახის დაშიფვრის ჩართვა",
+            "m.room.history_visibility": "ისტორიის ხილვადობის შეცვლა",
+            "m.room.name": "ოთახის სახელის შეცვლა",
+            "m.room.name_space": "სივრცის სახელის შეცვლა",
+            "m.room.pinned_events": "ჩამაგრებული მოვლენების მართვა",
+            "m.room.power_levels": "შეცვალეთ ნებართვები",
+            "m.room.redaction": "ჩემს მიერ გამოგზავნილი შეტყობინებების წაშლა",
+            "m.room.server_acl": "სერვერის ACL-ების შეცვლა",
+            "m.room.tombstone": "განაახლეთ ოთახი",
+            "m.room.topic": "თემის შეცვლა",
+            "m.room.topic_space": "შეცვალეთ აღწერა",
+            "m.space.child": "მართეთ ოთახები ამ სივრცეში",
+            "m.widget": "ვიჯეტების შეცვლა",
+            "muted_users_section": "დადუმებული მომხმარებლები",
+            "no_privileged_users": "არცერთ მომხმარებელს არ აქვს კონკრეტული პრივილეგიები ამ ოთახში",
+            "notifications.room": "შეატყობინეთ ყველას",
+            "permissions_section": "ნებართვები",
+            "permissions_section_description_room": "შეარჩიეთ როლები, რომლებიც საჭიროა ოთახის სხვადასხვა ნაწილის შესაცვლელად",
+            "permissions_section_description_space": "შეარჩიეთ როლები, რომლებიც საჭიროა სივრცის სხვადასხვა ნაწილის შესაცვლელად",
+            "privileged_users_section": "პრივილეგირებული მომხმარებლები",
+            "redact": "წაშალეთ სხვების მიერ გაგზავნილი შეტყობინებები",
+            "send_event_type": "გაგზავნა%(eventType)s მოვლენები",
+            "state_default": "პარამეტრების შეცვლა",
+            "title": "როლები და ნებართვები",
+            "users_default": "ნაგულისხმევი როლი"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "ჩართვის შემდეგ, ოთახის დაშიფვრა ვერ იქნება გამორთული. დაშიფრულ ოთახში გაგზავნილ შეტყობინებებს სერვერი ვერ ხედავს, მხოლოდ ოთახის მონაწილეებს. დაშიფვრის ჩართვამ შესაძლოა ხელი შეუშალოს მრავალი ბოტისა და ხიდის სწორად მუშაობას.<a> შეიტყვეთ მეტი დაშიფვრის შესახებ.</a>",
+            "enable_encryption_confirm_title": "ჩართოთ დაშიფვრა?",
+            "enable_encryption_public_room_confirm_description_1": "<b>არ არის რეკომენდებული დაშიფვრის დამატება საჯარო ოთახებში.</b> ნებისმიერს შეუძლია საჯარო ოთახების პოვნა და შეერთება, ასე რომ ნებისმიერს შეუძლია მათში შეტყობინებების წაკითხვა. თქვენ ვერ მიიღებთ დაშიფვრის სარგებელს და ვერ შეძლებთ მის გამორთვას მოგვიანებით. საჯარო ოთახში შეტყობინებების დაშიფვრა შეანელებს შეტყობინებების მიღებასა და გაგზავნას.",
+            "enable_encryption_public_room_confirm_description_2": "ამ პრობლემების თავიდან ასაცილებლად, შექმენით ა<a> ახალი დაშიფრული ოთახი</a> საუბრისთვის, რომლის გამართვას აპირებთ.",
+            "enable_encryption_public_room_confirm_title": "დარწმუნებული ხართ, რომ გსურთ ამ საჯარო ოთახში დაშიფვრის დამატება?",
+            "encrypted_room_public_confirm_description_1": "<b>არ არის რეკომენდებული დაშიფრული ოთახების გასაჯაროება.</b> ეს ნიშნავს, რომ ნებისმიერს შეუძლია იპოვოს და შეუერთდეს ოთახს, ასე რომ, ნებისმიერს შეუძლია წაიკითხოს შეტყობინებები. თქვენ არ მიიღებთ დაშიფვრის არცერთ სარგებელს. საჯარო ოთახში შეტყობინებების დაშიფვრა შეანელებს შეტყობინებების მიღებასა და გაგზავნას.",
+            "encrypted_room_public_confirm_description_2": "ამ პრობლემების თავიდან ასაცილებლად, შექმენით ა<a> ახალი საჯარო ოთახი</a> საუბრისთვის, რომლის გამართვას აპირებთ.",
+            "encrypted_room_public_confirm_title": "დარწმუნებული ხართ, რომ გსურთ გახადოთ ეს დაშიფრული ოთახი საჯარო?",
+            "encryption_forced": "თქვენი სერვერი მოითხოვს დაშიფვრის გამორთვას.",
+            "encryption_permanent": "ჩართვის შემდეგ, დაშიფვრის გამორთვა შეუძლებელია.",
+            "error_join_rule_change_title": "შეერთების წესების განახლება ვერ მოხერხდა",
+            "error_join_rule_change_unknown": "უცნობი მარცხი",
+            "guest_access_warning": "მხარდაჭერილი კლიენტების მქონე პირები შეძლებენ ოთახში გაწევრიანებას რეგისტრირებული ანგარიშის გარეშე.",
+            "history_visibility_invited": "მხოლოდ წევრები (რადგან ისინი მოწვეულნი იყვნენ)",
+            "history_visibility_joined": "მხოლოდ წევრები (მას შემდეგ, რაც ისინი შეუერთდნენ)",
+            "history_visibility_legend": "ვის შეუძლია ისტორიის წაკითხვა?",
+            "history_visibility_shared": "მხოლოდ წევრები (ამ ვარიანტის არჩევის მომენტიდან)",
+            "history_visibility_warning": "ცვლილებები იმაში, ვისაც შეუძლია ისტორიის წაკითხვა, გავრცელდება მხოლოდ ამ ოთახში მომავალ შეტყობინებებზე. არსებული ისტორიის ხილვადობა უცვლელი იქნება.",
+            "history_visibility_world_readable": "ვინმეს",
+            "join_rule_description": "გადაწყვიტეთ ვის შეუძლია შეუერთდეს%(roomName)s .",
+            "join_rule_invite": "პირადი (მხოლოდ მოწვევა)",
+            "join_rule_invite_description": "მხოლოდ მოწვეულ ადამიანებს შეუძლიათ შემოერთება.",
+            "join_rule_knock": "მოითხოვეთ შეერთება",
+            "join_rule_knock_description": "ხალხი ვერ შეუერთდება, სანამ წვდომა არ არის მინიჭებული.",
+            "join_rule_public_description": "ნებისმიერს შეუძლია იპოვოს და შეუერთდეს.",
+            "join_rule_restricted": "კოსმოსის წევრები",
+            "join_rule_restricted_description": "ნებისმიერს სივრცეში შეუძლია იპოვოს და შეუერთდეს.<a> დაარედაქტირეთ რომელ სივრცეებს შეუძლიათ აქ წვდომა.</a>",
+            "join_rule_restricted_description_active_space": "ვინმეს<spaceName/> შეუძლია იპოვოს და შეუერთდეს. თქვენ შეგიძლიათ აირჩიოთ სხვა ადგილებიც.",
+            "join_rule_restricted_description_prompt": "ნებისმიერს სივრცეში შეუძლია იპოვოს და შეუერთდეს. თქვენ შეგიძლიათ აირჩიოთ რამდენიმე სივრცე.",
+            "join_rule_restricted_description_spaces": "სივრცეები წვდომით",
+            "join_rule_restricted_dialog_description": "გადაწყვიტეთ რომელ სივრცეებს შეუძლია ამ ოთახში წვდომა. თუ არჩეულია სივრცე, მის წევრებს შეუძლიათ იპოვონ და შეუერთდნენ<RoomName/> .",
+            "join_rule_restricted_dialog_empty_warning": "თქვენ ხსნით ყველა სივრცეს. წვდომა ნაგულისხმევად იქნება მხოლოდ მოწვევისთვის",
+            "join_rule_restricted_dialog_filter_placeholder": "ადგილების ძებნა",
+            "join_rule_restricted_dialog_heading_known": "სხვა სივრცეები თქვენ იცით",
+            "join_rule_restricted_dialog_heading_other": "სხვა სივრცეები ან ოთახები, რომლებიც შეიძლება არ იცოდეთ",
+            "join_rule_restricted_dialog_heading_room": "თქვენთვის ცნობილი სივრცეები, რომლებიც შეიცავს ამ ოთახს",
+            "join_rule_restricted_dialog_heading_space": "თქვენთვის ცნობილი სივრცეები, რომლებიც შეიცავს ამ სივრცეს",
+            "join_rule_restricted_dialog_heading_unknown": "ეს, სავარაუდოდ, ოთახის სხვა ადმინისტრატორები არიან.",
+            "join_rule_restricted_dialog_title": "აირჩიეთ სივრცეები",
+            "join_rule_restricted_n_more": {
+                "one": "&amp;%(count)s მეტი",
+                "other": "&amp;%(count)s მეტი"
+            },
+            "join_rule_restricted_summary": {
+                "one": "ამჟამად სივრცეს აქვს წვდომა",
+                "other": "ამჟამად,%(count)s სივრცეებს აქვს წვდომა"
+            },
+            "join_rule_restricted_upgrade_description": "ეს განახლება საშუალებას მისცემს შერჩეული სივრცეების წევრებს წვდომას ამ ოთახში მოწვევის გარეშე.",
+            "join_rule_restricted_upgrade_warning": "ეს ოთახი არის ზოგიერთ სივრცეში, რომლის ადმინი არ ხართ. ამ სივრცეებში ძველი ოთახი კვლავ იქნება ნაჩვენები, მაგრამ ხალხს მოუწოდებენ შეუერთდნენ ახალს.",
+            "join_rule_upgrade_awaiting_room": "იტვირთება ახალი ოთახი",
+            "join_rule_upgrade_required": "საჭიროა განახლება",
+            "join_rule_upgrade_sending_invites": {
+                "one": "მოწვევის გაგზავნა...",
+                "other": "მოწვევების გაგზავნა... (%(progress)s გარეთ%(count)s )"
+            },
+            "join_rule_upgrade_updating_spaces": {
+                "one": "სივრცის განახლება...",
+                "other": "სივრცეების განახლება... (%(progress)s გარეთ%(count)s )"
+            },
+            "join_rule_upgrade_upgrading_room": "განახლების ოთახი",
+            "public_without_alias_warning": "ამ ოთახთან დასაკავშირებლად, გთხოვთ, დაამატოთ მისამართი.",
+            "publish_room": "გახადეთ ეს ოთახი ხილული საჯარო ოთახის დირექტორიაში.",
+            "publish_space": "გახადეთ ეს სივრცე ხილული საჯარო ოთახის დირექტორიაში.",
+            "strict_encryption": "არასოდეს გაუგზავნოთ დაშიფრული შეტყობინებები ამ ოთახის დაუდასტურებელ სესიებს ამ სესიიდან",
+            "title": "უსაფრთხოება და კონფიდენციალურობა"
+        },
+        "title": "ოთახის პარამეტრები -%(roomName)s",
+        "upload_avatar_label": "ატვირთეთ ავატარი",
+        "visibility": {
+            "alias_section": "მისამართი",
+            "error_failed_save": "ამ სივრცის ხილვადობის განახლება ვერ მოხერხდა",
+            "error_update_guest_access": "ამ სივრცეში სტუმრის წვდომის განახლება ვერ მოხერხდა",
+            "error_update_history_visibility": "ამ სივრცის ისტორიის ხილვადობის განახლება ვერ მოხერხდა",
+            "guest_access_explainer": "სტუმრებს შეუძლიათ შეუერთდნენ სივრცეს ანგარიშის გარეშე.",
+            "guest_access_explainer_public_space": "ეს შეიძლება სასარგებლო იყოს საჯარო სივრცეებისთვის.",
+            "guest_access_label": "ჩართეთ სტუმრის წვდომა",
+            "history_visibility_anyone_space": "გადახედვის სივრცე",
+            "history_visibility_anyone_space_description": "ნება მიეცით ხალხს, გადახედონ თქვენს სივრცეს, სანამ შეუერთდებიან.",
+            "history_visibility_anyone_space_recommendation": "რეკომენდირებულია საჯარო სივრცეებისთვის.",
+            "title": "ხილვადობა"
+        },
+        "voip": {
+            "call_type_section": "ზარის ტიპი",
+            "enable_element_call_caption": "%(brand)sარის ბოლომდე დაშიფრული, მაგრამ ამჟამად შეზღუდულია მომხმარებელთა მცირე რაოდენობით.",
+            "enable_element_call_label": "ჩართვა%(brand)s როგორც დამატებითი დარეკვის ვარიანტი ამ ოთახში",
+            "enable_element_call_no_permissions_tooltip": "თქვენ არ გაქვთ საკმარისი ნებართვები ამის შესაცვლელად."
+        }
+    },
+    "room_summary_card_back_action_label": "ოთახის ინფორმაცია",
+    "scalar": {
+        "error_create": "ვიჯეტის შექმნა შეუძლებელია.",
+        "error_membership": "თქვენ არ ხართ ამ ოთახში.",
+        "error_missing_room_id": "აკლია ოთახის ID.",
+        "error_missing_room_id_request": "გამოტოვებულია room_id მოთხოვნაში",
+        "error_missing_user_id_request": "მოთხოვნაში user_id აკლია",
+        "error_permission": "თქვენ არ გაქვთ ამის უფლება ამ ოთახში.",
+        "error_power_level_invalid": "სიმძლავრის დონე უნდა იყოს დადებითი მთელი რიცხვი.",
+        "error_room_not_visible": "ოთახი%(roomId)s არ ჩანს",
+        "error_room_unknown": "ეს ოთახი არ არის აღიარებული.",
+        "error_send_request": "მოთხოვნის გაგზავნა ვერ მოხერხდა.",
+        "failed_read_event": "მოვლენების წაკითხვა ვერ მოხერხდა",
+        "failed_send_event": "ღონისძიების გაგზავნა ვერ მოხერხდა"
+    },
+    "settings": {
+        "all_rooms_home": "სახლის ყველა ოთახის ჩვენება",
+        "all_rooms_home_description": "ყველა ოთახი, რომელშიც იმყოფებით, გამოჩნდება მთავარ გვერდზე.",
+        "always_show_message_timestamps": "ყოველთვის აჩვენე შეტყობინების დროის ანაბეჭდები",
+        "appearance": {
+            "custom_font": "გამოიყენეთ სისტემის შრიფტი",
+            "custom_font_description": "დააყენეთ თქვენს სისტემაში დაინსტალირებული შრიფტის სახელი და%(brand)s შეეცდება მის გამოყენებას.",
+            "custom_font_name": "სისტემის შრიფტის სახელი",
+            "custom_font_size": "გამოიყენეთ მორგებული ზომა",
+            "custom_theme_error_downloading": "თემის ჩამოტვირთვის შეცდომა",
+            "custom_theme_invalid": "თემის არასწორი სქემა.",
+            "font_size": "შრიფტის ზომა",
+            "image_size_default": "ნაგულისხმევი",
+            "image_size_large": "დიდი",
+            "layout_bubbles": "შეტყობინების ბუშტები",
+            "layout_irc": "IRC (ექსპერიმენტული)",
+            "match_system_theme": "ემთხვევა სისტემის თემას",
+            "timeline_image_size": "სურათის ზომა ვადებში"
+        },
+        "automatic_language_detection_syntax_highlight": "ჩართეთ ენის ავტომატური ამოცნობა სინტაქსის ხაზგასმისთვის",
+        "autoplay_gifs": "GIF-ების ავტომატური დაკვრა",
+        "autoplay_videos": "ვიდეოების ავტომატური დაკვრა",
+        "big_emoji": "ჩატში ჩართეთ დიდი emoji",
+        "code_block_expand_default": "ნაგულისხმევად გააფართოვეთ კოდის ბლოკები",
+        "code_block_line_numbers": "ხაზების ნომრების ჩვენება კოდის ბლოკებში",
+        "disable_historical_profile": "აჩვენეთ მომხმარებლის მიმდინარე პროფილის სურათი და სახელი შეტყობინებების ისტორიაში",
+        "emoji_autocomplete": "აკრეფისას ჩართეთ Emoji შემოთავაზებები",
+        "enable_markdown": "ჩართეთ Markdown",
+        "enable_markdown_description": "დაიწყეთ შეტყობინებები<code> /სადა</code> მარკირების გარეშე გაგზავნა.",
+        "general": {
+            "account_management_section": "ანგარიშის მართვა",
+            "account_section": "ანგარიში",
+            "add_email_dialog_title": "ელ. ფოსტის მისამართის დამატება",
+            "add_email_failed_verification": "ელ. ფოსტის მისამართის ვერიფიკაცია ვერ მოხერხდა: დარწმუნდი, რომ დააჭირე ბმულს ელ. ფოსტის წერილში",
+            "add_email_instructions": "ჩვენ გამოგიგზავნეთ ელფოსტა თქვენი მისამართის დასადასტურებლად. გთხოვთ, მიჰყევით იქ არსებულ ინსტრუქციას და შემდეგ დააჭირეთ ქვემოთ მოცემულ ღილაკს.",
+            "add_msisdn_confirm_body": "დააჭირეთ ღილაკს მობილურის ნომრის დასადასტურებლად.",
+            "add_msisdn_confirm_button": "დაადასტურეთ მობილურის ნომრის დამატება",
+            "add_msisdn_confirm_sso_button": "ამ ტელეფონის ნომრის დასადასტურებლად გამოიყენე ერთჯერადი ავტორიზაცია, საკუთარი იდენტობის დასადასტურებლად.",
+            "add_msisdn_dialog_title": "მობილურის ნომრის დამატება",
+            "add_msisdn_instructions": "ტექსტური შეტყობინება გაიგზავნა +%(msisdn)s . გთხოვთ, შეიყვანოთ დამადასტურებელი კოდი მასში.",
+            "add_msisdn_misconfigured": "MSISDN ნაკადის დამატება/დაკავშირება არასწორად არის კონფიგურირებული",
+            "confirm_adding_email_body": "ელ. ფოსტის ამ მისამართის დამატება დაადასტურე ღილაკზე დაჭერით.",
+            "confirm_adding_email_title": "დაადასტურე ელ.ფოსტის დამატება",
+            "deactivate_confirm_body": "დარწმუნებული ხართ, რომ გსურთ თქვენი ანგარიშის დეაქტივაცია? ეს შეუქცევადია.",
+            "deactivate_confirm_body_sso": "დაადასტურეთ თქვენი ანგარიშის დეაქტივაცია ერთი შესვლის გამოყენებით თქვენი ვინაობის დასადასტურებლად.",
+            "deactivate_confirm_content": "დაადასტურეთ, რომ გსურთ თქვენი ანგარიშის დეაქტივაცია. თუ გააგრძელებ:",
+            "deactivate_confirm_content_1": "თქვენ ვერ შეძლებთ თქვენი ანგარიშის ხელახლა გააქტიურებას",
+            "deactivate_confirm_content_2": "თქვენ ვეღარ შეძლებთ შესვლას",
+            "deactivate_confirm_content_3": "ვერავინ შეძლებს თქვენი მომხმარებლის სახელის (MXID) ხელახლა გამოყენებას, მათ შორის თქვენც: ეს მომხმარებლის სახელი მიუწვდომელი დარჩება",
+            "deactivate_confirm_content_4": "თქვენ დატოვებთ ყველა ოთახს და DM-ს, რომელშიც იმყოფებით",
+            "deactivate_confirm_content_5": "თქვენ წაიშლებით პირადობის სერვერიდან: თქვენი მეგობრები ვეღარ გიპოვიან თქვენი ელ.ფოსტის ან ტელეფონის ნომრით",
+            "deactivate_confirm_content_6": "თქვენი ძველი შეტყობინებები კვლავ ხილული იქნება იმ ადამიანებისთვის, რომლებმაც მიიღეს ისინი, ისევე როგორც წარსულში გაგზავნილი წერილები. გსურთ თქვენი გაგზავნილი შეტყობინებების დამალვა იმ ადამიანებისგან, რომლებიც მომავალში შეუერთდებიან ოთახებს?",
+            "deactivate_confirm_continue": "დაადასტურეთ ანგარიშის დეაქტივაცია",
+            "deactivate_confirm_erase_label": "ჩემი შეტყობინებების დამალვა ახალი დამფუძნებლებისგან",
+            "deactivate_section": "ანგარიშის დეაქტივაცია",
+            "deactivate_warning": "თქვენი ანგარიშის დეაქტივაცია მუდმივი ქმედებაა — ფრთხილად იყავით!",
+            "discovery_email_empty": "",
+            "discovery_email_verification_instructions": "გადაამოწმეთ ბმული თქვენს შემოსულებში",
+            "discovery_msisdn_empty": "აღმოჩენის ვარიანტები გამოჩნდება მას შემდეგ, რაც დაამატებთ ტელეფონის ნომერს.",
+            "discovery_needs_terms": "ვეთანხმები პირადობის სერვერს (%(serverName)s ) მომსახურების პირობები, რათა საკუთარ თავს საშუალება მისცეთ აღმოაჩინოთ ელექტრონული ფოსტის მისამართით ან ტელეფონის ნომრით.",
+            "email_address_in_use": "ელ. ფოსტის ეს მისამართი დაკავებულია",
+            "email_address_label": "Ელექტრონული მისამართი",
+            "email_not_verified": "თქვენი ელფოსტის მისამართი ჯერ არ არის დადასტურებული",
+            "email_verification_instructions": "",
+            "emails_heading": "ელფოსტის მისამართები",
+            "error_add_email": "ელფოსტის მისამართის დამატება შეუძლებელია",
+            "error_deactivate_communication": "სერვერთან კომუნიკაციისას წარმოიშვა პრობლემა. გთხოვთ, სცადოთ ხელახლა.",
+            "error_deactivate_invalid_auth": "სერვერმა არ დააბრუნა ავთენტიფიკაციის მოქმედი ინფორმაცია.",
+            "error_deactivate_no_auth": "სერვერს არ სჭირდებოდა ავთენტიფიკაცია",
+            "error_email_verification": "",
+            "error_invalid_email": "ელფოსტის მისამართი არასწორია",
+            "error_invalid_email_detail": "როგორც ჩანს, ეს არ არის სწორი ელფოსტის მისამართი",
+            "error_msisdn_verification": "",
+            "error_password_change_403": "პაროლის შეცვლა ვერ მოხერხდა. თქვენი პაროლი სწორია?",
+            "error_password_change_http": "%(errorMessage)s(HTTP სტატუსი%(httpStatus)s )",
+            "error_password_change_title": "პაროლის შეცვლა მოხდა შეცდომა",
+            "error_password_change_unknown": "პაროლის შეცვლის უცნობი შეცდომა (%(stringifiedError)s )",
+            "error_remove_3pid": "",
+            "error_revoke_email_discovery": "ელფოსტის მისამართისთვის გაზიარების გაუქმება შეუძლებელია",
+            "error_revoke_msisdn_discovery": "",
+            "error_share_email_discovery": "",
+            "error_share_msisdn_discovery": "ტელეფონის ნომრის გაზიარება შეუძლებელია",
+            "identity_server_no_token": "იდენთიფიკაციის წვდომის ტოკენი ვერ მოიძებნა",
+            "identity_server_not_set": "იდენთიფიკაციის სერვერი არ არის განსაზღვრული",
+            "language_section": "Ენა",
+            "msisdn_in_use": "ტელეფონის ეს ნომერი დაკავებულია",
+            "msisdn_label": "ტელეფონის ნომერი",
+            "msisdn_verification_field_label": "დამადასტურებელი კოდი",
+            "msisdn_verification_instructions": "გთხოვთ, შეიყვანოთ ტექსტით გაგზავნილი დამადასტურებელი კოდი.",
+            "msisdns_heading": "ტელეფონის ნომრები",
+            "oidc_manage_button": "Ანგარიშის მართვა",
+            "password_change_section": "დააყენეთ ახალი ანგარიშის პაროლი…",
+            "password_change_success": "თქვენი პაროლი წარმატებით შეიცვალა.",
+            "remove_email_prompt": "ამოღება%(email)s ?",
+            "remove_msisdn_prompt": "ამოღება%(phone)s ?",
+            "spell_check_locale_placeholder": "აირჩიეთ ლოკალი"
+        },
+        "inline_url_previews_default": "ნაგულისხმევად ჩართული URL-ის გადახედვის ჩართვა",
+        "inline_url_previews_room": "URL-ის გადახედვის ჩართვა ნაგულისხმევად ამ ოთახში მონაწილეებისთვის",
+        "inline_url_previews_room_account": "ამ ოთახისთვის URL-ის გადახედვის ჩართვა (მხოლოდ თქვენზე მოქმედებს)",
+        "insert_trailing_colon_mentions": "ჩადეთ ბოლო ორწერტილი მას შემდეგ, რაც მომხმარებელი აღნიშნავს შეტყობინების დასაწყისში",
+        "jump_to_bottom_on_send": "შეტყობინების გაგზავნისას გადადით ქრონოლოგიის ბოლოში",
+        "key_backup": {
+            "backup_in_progress": "მიმდინარეობს თქვენი გასაღებების სარეზერვო ასლის შექმნა (პირველ სარეზერვო ასლს შეიძლება რამდენიმე წუთი დასჭირდეს).",
+            "backup_starting": "იწყება სარეზერვო ასლის შექმნა…",
+            "backup_success": "წარმატებები!",
+            "cannot_create_backup": "გასაღების სარეზერვო ასლის შექმნა შეუძლებელია",
+            "create_title": "შექმენით გასაღების სარეზერვო საშუალება",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "ახლა მიმდინარეობს თქვენი გასაღებების სარეზერვო ასლის შექმნა ამ მოწყობილობიდან.",
+                "backup_setup_success_title": "უსაფრთხო სარეზერვო ასლის შექმნა წარმატებით დასრულდა",
+                "cancel_warning": "თუ ახლა გააუქმებთ, შეიძლება დაკარგოთ დაშიფრული შეტყობინებები და მონაცემები, თუ დაკარგავთ წვდომას თქვენს შესვლაზე.",
+                "confirm_security_phrase": "დაადასტურეთ თქვენი უსაფრთხოების ფრაზა",
+                "description": "დაიცავით დაშიფრულ შეტყობინებებსა და მონაცემებზე წვდომის დაკარგვისგან თქვენს სერვერზე დაშიფვრის გასაღებების სარეზერვო ასლის შექმნა.",
+                "download_or_copy": "%(downloadButton)sან%(copyButton)s",
+                "enter_phrase_description": "შეიყვანეთ უსაფრთხოების ფრაზა მხოლოდ თქვენ იცით, რადგან ის გამოიყენება თქვენი მონაცემების დასაცავად. უსაფრთხოების მიზნით, არ უნდა გამოიყენოთ თქვენი ანგარიშის პაროლი.",
+                "enter_phrase_title": "შეიყვანეთ უსაფრთხოების ფრაზა",
+                "enter_phrase_to_confirm": "მეორედ შეიყვანეთ თქვენი უსაფრთხოების ფრაზა მის დასადასტურებლად.",
+                "generate_security_key_description": "ჩვენ გამოგიმუშავებთ უსაფრთხოების გასაღებს, რომ შეინახოთ სადმე უსაფრთხო ადგილას, როგორიცაა პაროლის მენეჯერი ან სეიფი.",
+                "generate_security_key_title": "შექმენით უსაფრთხოების გასაღები",
+                "pass_phrase_match_failed": "ეს არ ემთხვევა.",
+                "pass_phrase_match_success": "რომ ემთხვევა!",
+                "phrase_strong_enough": "დიდი! უსაფრთხოების ეს ფრაზა საკმაოდ ძლიერი ჩანს.",
+                "secret_storage_query_failure": "საიდუმლო შენახვის სტატუსის მოთხოვნა შეუძლებელია",
+                "security_key_safety_reminder": "შეინახეთ თქვენი უსაფრთხოების გასაღები სადმე უსაფრთხო ადგილას, როგორიცაა პაროლის მენეჯერი ან სეიფი, რადგან ის გამოიყენება თქვენი დაშიფრული მონაცემების დასაცავად.",
+                "set_phrase_again": "დაბრუნდით ისევ დასაყენებლად.",
+                "settings_reminder": "თქვენ ასევე შეგიძლიათ დააყენოთ უსაფრთხო სარეზერვო ასლი და მართოთ თქვენი გასაღებები პარამეტრებში.",
+                "title_confirm_phrase": "დაადასტურეთ უსაფრთხოების ფრაზა",
+                "title_save_key": "შეინახეთ უსაფრთხოების გასაღები",
+                "title_set_phrase": "დააყენეთ უსაფრთხოების ფრაზა",
+                "unable_to_setup": "საიდუმლო მეხსიერების დაყენება შეუძლებელია",
+                "use_different_passphrase": "გამოიყენოთ სხვა საიდუმლო ფრაზა?",
+                "use_phrase_only_you_know": "გამოიყენეთ საიდუმლო ფრაზა მხოლოდ თქვენ იცით და სურვილისამებრ შეინახეთ უსაფრთხოების გასაღები სარეზერვო ასლისთვის გამოსაყენებლად."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "დაადასტურეთ პაროლი",
+            "enter_passphrase": "შეიყვანეთ პაროლი",
+            "export_description_1": "ეს პროცესი საშუალებას გაძლევთ დაშიფრულ ოთახებში მიღებული შეტყობინებების გასაღებები ადგილობრივ ფაილში გადაიტანოთ. ამის შემდეგ თქვენ შეძლებთ ფაილის იმპორტს მომავალში Matrix-ის სხვა კლიენტში, ასე რომ კლიენტი ასევე შეძლებს ამ შეტყობინებების გაშიფვრას.",
+            "export_description_2": "ექსპორტირებული ფაილი საშუალებას მისცემს ნებისმიერს, ვისაც მისი წაკითხვა შეუძლია, გაშიფროს დაშიფრული შეტყობინებები, რომელთა ნახვაც შეგიძლიათ, ამიტომ ფრთხილად უნდა იყოთ მისი დაცვით. ამის დასახმარებლად, ქვემოთ უნდა შეიყვანოთ უნიკალური საიდუმლო ფრაზა, რომელიც გამოყენებული იქნება მხოლოდ ექსპორტირებული მონაცემების დაშიფვრისთვის. მონაცემთა იმპორტი მხოლოდ იმავე პაროლის გამოყენებით იქნება შესაძლებელი.",
+            "export_title": "ოთახის გასაღებების ექსპორტი",
+            "file_to_import": "ფაილი იმპორტისთვის",
+            "import_description_1": "ეს პროცესი საშუალებას გაძლევთ შემოიტანოთ დაშიფვრის გასაღებები, რომლებიც ადრე გქონდათ ექსპორტირებული სხვა Matrix კლიენტიდან. ამის შემდეგ თქვენ შეძლებთ ნებისმიერი შეტყობინების გაშიფვრას, რომელიც სხვა კლიენტს შეეძლო.",
+            "import_description_2": "ექსპორტის ფაილი დაცული იქნება პაროლით. აქ უნდა შეიყვანოთ პაროლი ფაილის გაშიფვრისთვის.",
+            "import_title": "ოთახის გასაღებების იმპორტი",
+            "phrase_cannot_be_empty": "პაროლი არ უნდა იყოს ცარიელი",
+            "phrase_must_match": "პაროლის ფრაზები უნდა ემთხვეოდეს",
+            "phrase_strong_enough": "დიდი! ეს საიდუმლო ფრაზა საკმაოდ მტკიცედ გამოიყურება"
+        },
+        "keyboard": {
+            "title": "კლავიატურა"
+        },
+        "notifications": {
+            "default_setting_description": "ეს პარამეტრი ნაგულისხმევად გამოყენებული იქნება თქვენს ყველა ოთახში.",
+            "default_setting_section": "მინდა მივიღო შეტყობინება (ნაგულისხმევი პარამეტრი)",
+            "desktop_notification_message_preview": "შეტყობინების გადახედვის ჩვენება დესკტოპის შეტყობინებაში",
+            "email_description": "მიიღეთ გამოტოვებული შეტყობინებების შეჯამება ელექტრონული ფოსტით",
+            "email_section": "ელფოსტის შეჯამება",
+            "email_select": "აირჩიეთ რომელ ელფოსტაზე გსურთ შეჯამების გაგზავნა. მართეთ თქვენი ელფოსტა<button> გენერალი</button> .",
+            "enable_audible_notifications_session": "ჩართეთ ხმოვანი შეტყობინებები ამ სესიისთვის",
+            "enable_desktop_notifications_session": "ჩართეთ დესკტოპის შეტყობინებები ამ სესიისთვის",
+            "enable_email_notifications": "ელ.ფოსტის შეტყობინებების ჩართვა ამისთვის%(email)s",
+            "enable_notifications_account": "ჩართეთ შეტყობინებები ამ ანგარიშისთვის",
+            "enable_notifications_account_detail": "გამორთეთ შეტყობინებების გამორთვა ყველა თქვენს მოწყობილობაზე და სესიაზე",
+            "enable_notifications_device": "ჩართეთ შეტყობინებები ამ მოწყობილობისთვის",
+            "error_loading": "თქვენი შეტყობინებების პარამეტრების ჩატვირთვისას მოხდა შეცდომა.",
+            "error_permissions_denied": "%(brand)sარ აქვს უფლება გამოგიგზავნოთ შეტყობინებები - გთხოვთ, შეამოწმოთ თქვენი ბრაუზერის პარამეტრები",
+            "error_permissions_missing": "%(brand)sარ მიეცა შეტყობინებების გაგზავნის ნებართვა - გთხოვთ, სცადოთ ხელახლა",
+            "error_saving": "შეტყობინებების პარამეტრების შენახვისას მოხდა შეცდომა",
+            "error_saving_detail": "თქვენი შეტყობინებების პარამეტრების შენახვისას მოხდა შეცდომა.",
+            "error_title": "შეტყობინებების ჩართვა შეუძლებელია",
+            "error_updating": "თქვენი შეტყობინებების პარამეტრების განახლებისას მოხდა შეცდომა. გთხოვთ, კვლავ სცადოთ თქვენი ვარიანტის გადართვა.",
+            "invites": "ოთახში დაპატიჟა",
+            "keywords": "აჩვენე სამკერდე ნიშანი<badge/> როდესაც საკვანძო სიტყვები გამოიყენება ოთახში.",
+            "keywords_prompt": "შეიყვანეთ აქ საკვანძო სიტყვები, ან გამოიყენეთ ორთოგრაფიული ვარიაციები ან მეტსახელები",
+            "labs_notice_prompt": "<strong>განახლება:</strong> ჩვენ გავამარტივეთ შეტყობინებების პარამეტრები, რათა გაადვილდეს ვარიანტების პოვნა. თქვენ მიერ წარსულში არჩეული ზოგიერთი მორგებული პარამეტრი აქ არ არის ნაჩვენები, მაგრამ ისინი კვლავ აქტიურია. თუ გააგრძელებთ, თქვენი ზოგიერთი პარამეტრი შეიძლება შეიცვალოს.<a> გაიგე მეტი</a>",
+            "mentions_keywords": "ხსენებები და საკვანძო სიტყვები",
+            "mentions_keywords_only": "მხოლოდ ხსენებები და საკვანძო სიტყვები",
+            "messages_containing_keywords": "შეტყობინებები, რომლებიც შეიცავს საკვანძო სიტყვებს",
+            "noisy": "ხმაურიანი",
+            "notices": "ბოტების მიერ გაგზავნილი შეტყობინებები",
+            "notify_at_room": "შეატყობინეთ, როდესაც ვინმე ახსენებს @room-ის გამოყენებას",
+            "notify_keyword": "შეატყობინეთ, როდესაც ვინმე იყენებს საკვანძო სიტყვას",
+            "notify_mention": "შეატყობინეთ, როდესაც ვინმე ახსენებს @displayname ან%(mxid)s",
+            "other_section": "სხვა რამ, რაც ვფიქრობთ, დაგაინტერესებთ:",
+            "people_mentions_keywords": "ხალხი, ხსენებები და საკვანძო სიტყვები",
+            "play_sound_for_description": "ნაგულისხმევად გამოიყენება ყველა ოთახში ყველა მოწყობილობაზე.",
+            "play_sound_for_section": "დაუკარით ხმა",
+            "push_targets": "შეტყობინებების მიზნები",
+            "quick_actions_mark_all_read": "მონიშნეთ ყველა შეტყობინება წაკითხულად",
+            "quick_actions_reset": "გადატვირთეთ ნაგულისხმევ პარამეტრებზე",
+            "quick_actions_section": "სწრაფი მოქმედებები",
+            "room_activity": "ხდება ახალი აქტივობა ოთახში, განახლებები და სტატუსის შეტყობინებები",
+            "rule_call": "ზარის მოწვევა",
+            "rule_contains_display_name": "",
+            "rule_contains_user_name": "",
+            "rule_encrypted": "დაშიფრული შეტყობინებები ჯგუფურ ჩეთებში",
+            "rule_encrypted_room_one_to_one": "დაშიფრული შეტყობინებები ერთ-ერთ ჩეთებში",
+            "rule_invite_for_me": "როცა ოთახში დამპატიჟებენ",
+            "rule_message": "შეტყობინებები ჯგუფურ ჩეთებში",
+            "rule_room_one_to_one": "შეტყობინებები ერთ-ერთ ჩატში",
+            "rule_roomnotif": "@room-ის შემცველი შეტყობინებები",
+            "rule_suppress_notices": "ბოტის მიერ გაგზავნილი შეტყობინებები",
+            "rule_tombstone": "როდესაც ოთახები განახლებულია",
+            "show_message_desktop_notification": "შეტყობინების ჩვენება დესკტოპის შეტყობინებაში",
+            "voip": "აუდიო და ვიდეო ზარები"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "ტექნიკის აჩქარების ჩართვა (გადატვირთვა%(appName)s ამოქმედდეს)",
+            "always_show_menu_bar": "ყოველთვის აჩვენე ფანჯრის მენიუს ზოლი",
+            "autocomplete_delay": "ავტომატური დასრულების დაყოვნება (მმ)",
+            "code_blocks_heading": "კოდის ბლოკები",
+            "compact_modern": "გამოიყენეთ უფრო კომპაქტური „თანამედროვე“ განლაგება",
+            "composer_heading": "კომპოზიტორი",
+            "enable_hardware_acceleration": "ტექნიკის აჩქარების ჩართვა",
+            "enable_tray_icon": "აჩვენეთ უჯრის ხატულა და დახურეთ ფანჯრის მინიმიზაცია",
+            "keyboard_heading": "კლავიატურის მალსახმობები",
+            "keyboard_view_shortcuts_button": "კლავიატურის ყველა მალსახმობის სანახავად,<a> დააწკაპუნეთ აქ</a> .",
+            "media_heading": "სურათები, GIF და ვიდეოები",
+            "presence_description": "გაუზიარეთ თქვენი აქტივობა და სტატუსი სხვებს.",
+            "rm_lifetime": "მარკერის წაკითხვის ხანგრძლივობა (მმ)",
+            "rm_lifetime_offscreen": "მარკერის წაკითხვის ხანგრძლივობა ეკრანიდან (მმ)",
+            "room_directory_heading": "ოთახის დირექტორია",
+            "room_list_heading": "ოთახის სია",
+            "show_avatars_pills": "აჩვენეთ ავატარები მომხმარებლის, ოთახისა და მოვლენის ხსენებებში",
+            "show_polls_button": "გამოკითხვების ღილაკის ჩვენება",
+            "surround_text": "შერჩეული ტექსტის გარშემორტყმა სპეციალური სიმბოლოების აკრეფისას",
+            "time_heading": "დროის ჩვენება"
+        },
+        "prompt_invite": "მოითხოვეთ მოწვევის გაგზავნამდე პოტენციურად არასწორი matrix ID-ებზე",
+        "replace_plain_emoji": "ავტომატური ჩანაცვლება უბრალო ტექსტური Emoji",
+        "security": {
+            "analytics_description": "გააზიარეთ ანონიმური მონაცემები, რათა დაგვეხმაროთ პრობლემების იდენტიფიცირებაში. არაფერი პირადული. არ არის მესამე მხარე.",
+            "bulk_options_accept_all_invites": "მიიღე ყველა%(invitedRooms)s იწვევს",
+            "bulk_options_reject_all_invites": "უარყოთ ყველა%(invitedRooms)s იწვევს",
+            "bulk_options_section": "ნაყარი ვარიანტები",
+            "e2ee_default_disabled_warning": "თქვენმა სერვერის ადმინისტრატორმა ნაგულისხმევად გათიშული დაშიფვრა პირად ოთახებში და პირდაპირ შეტყობინებებში.",
+            "enable_message_search": "ჩართეთ შეტყობინებების ძებნა დაშიფრულ ოთახებში",
+            "encryption_section": "დაშიფვრა",
+            "ignore_users_empty": "თქვენ არ გყავთ უგულებელყოფილი მომხმარებლები.",
+            "ignore_users_section": "იგნორირებული მომხმარებლები",
+            "key_backup_algorithm": "ალგორითმი:",
+            "key_backup_connect": "დააკავშირეთ ეს სესია Key Backup-თან",
+            "message_search_disable_warning": "თუ გამორთულია, დაშიფრული ოთახებიდან შეტყობინებები არ გამოჩნდება ძიების შედეგებში.",
+            "message_search_disabled": "დაშიფრული შეტყობინებების ლოკალურად დაშიფვრა, რათა გამოჩნდეს ძიების შედეგებში.",
+            "message_search_enabled": {
+                "one": "დაშიფრული შეტყობინებების უსაფრთხოდ ქეშირება ადგილობრივად, რათა გამოჩნდეს ძიების შედეგებში, გამოყენებით%(size)s შეტყობინებების შესანახად%(rooms)s ოთახი.",
+                "other": "დაშიფრული შეტყობინებების უსაფრთხოდ ქეშირება ადგილობრივად, რათა გამოჩნდეს ძიების შედეგებში, გამოყენებით%(size)s შეტყობინებების შესანახად%(rooms)s ოთახები."
+            },
+            "message_search_failed": "შეტყობინების ძიების ინიციალიზაცია ვერ მოხერხდა",
+            "message_search_indexed_messages": "ინდექსირებული შეტყობინებები:",
+            "message_search_indexed_rooms": "ინდექსირებული ოთახები:",
+            "message_search_indexing": "ამჟამად მიმდინარეობს ინდექსირება:%(currentRoom)s",
+            "message_search_indexing_idle": "ამჟამად არ არის ინდექსირებული შეტყობინებები არცერთი ოთახისთვის.",
+            "message_search_intro": "%(brand)sუსაფრთხოდ ინახავს დაშიფრულ შეტყობინებებს ადგილობრივად, რათა გამოჩნდეს ძიების შედეგებში:",
+            "message_search_room_progress": "%(doneRooms)sგარეთ%(totalRooms)s",
+            "message_search_section": "შეტყობინებების ძებნა",
+            "message_search_sleep_time": "რამდენად სწრაფად უნდა ჩამოიტვირთოს შეტყობინებები.",
+            "message_search_space_used": "გამოყენებული სივრცე:",
+            "message_search_unsupported": "%(brand)sაკლია ზოგიერთი კომპონენტი, რომელიც საჭიროა დაშიფრული შეტყობინებების ადგილობრივად უსაფრთხოდ შესანახად. თუ გსურთ ამ ფუნქციის ექსპერიმენტი, შექმენით საბაჟო%(brand)s დესკტოპი ერთად<nativeLink> დამატებულია საძიებო კომპონენტები</nativeLink> .",
+            "message_search_unsupported_web": "%(brand)sარ შეუძლია დაშიფრული შეტყობინებების ლოკალურად დაშიფვრა ვებ-ბრაუზერში მუშაობისას. გამოყენება<desktopLink>%(brand)s სამუშაო მაგიდა</desktopLink> დაშიფრული შეტყობინებების გამოსაჩენად ძიების შედეგებში.",
+            "record_session_details": "ჩაწერეთ კლიენტის სახელი, ვერსია და url, რათა უფრო ადვილად ამოიცნოთ სესიები სესიების მენეჯერში",
+            "send_analytics": "ანალიტიკის მონაცემების გაგზავნა",
+            "strict_encryption": "არასოდეს გაუგზავნოთ დაშიფრული შეტყობინებები დაუდასტურებელ სესიებზე ამ სესიიდან"
+        },
+        "send_read_receipts": "გაგზავნეთ წაკითხული ქვითრები",
+        "send_read_receipts_unsupported": "თქვენს სერვერს არ აქვს წაკითხული ქვითრების გაგზავნის გათიშვის მხარდაჭერა.",
+        "send_typing_notifications": "აკრეფის შეტყობინებების გაგზავნა",
+        "sessions": {
+            "best_security_note": "საუკეთესო უსაფრთხოებისთვის, გადაამოწმეთ თქვენი სესიები და გამოდით ნებისმიერი სესიიდან, რომელსაც აღარ იცნობთ ან აღარ იყენებთ.",
+            "browser": "ბრაუზერი",
+            "confirm_sign_out": {
+                "one": "დაადასტურეთ ამ მოწყობილობის გამოსვლა",
+                "other": "დაადასტურეთ ამ მოწყობილობებიდან გამოსვლა"
+            },
+            "confirm_sign_out_body": {
+                "one": "დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე, რათა დაადასტუროთ ამ მოწყობილობიდან გასვლა.",
+                "other": "დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე ამ მოწყობილობებიდან გასვლის დასადასტურებლად."
+            },
+            "confirm_sign_out_continue": {
+                "one": "მოწყობილობიდან გასვლა",
+                "other": "მოწყობილობებიდან გასვლა"
+            },
+            "confirm_sign_out_sso": {
+                "one": "დაადასტურეთ ამ მოწყობილობიდან გამოსვლა ერთჯერადი შესვლის გამოყენებით თქვენი პირადობის დასამტ",
+                "other": "დაადასტურეთ ამ მოწყობილობების გამოსვლა ერთჯერადი შესვლის გამოყენებით თქვენი პირადობის დასამტ"
+            },
+            "current_session": "მიმდინარე სესია",
+            "desktop_session": "დესკტოპის სესია",
+            "details_heading": "სესიის დეტალები",
+            "device_unverified_description": "დაადასტურეთ ან გამოდით ამ სესიიდან საუკეთესო უსაფრთხოებისა და საიმედოობისთვის.",
+            "device_unverified_description_current": "დაადასტურეთ თქვენი მიმდინარე სესია გაძლიერებული უსაფრთხო შეტყობინებებისთვის.",
+            "device_verified_description": "ეს სესია მზად არის უსაფრთხო შეტყობინებებისთვის.",
+            "device_verified_description_current": "თქვენი მიმდინარე სესია მზად არის უსაფრთხო შეტყობინებებისთვის.",
+            "error_pusher_state": "დამჭერის მდგომარეობის დაყენება ვერ მოხერხდა",
+            "error_set_name": "სესიის სახელის დაყენება ვერ მოხერხდა",
+            "filter_all": "ყველა",
+            "filter_inactive": "არააქტიური",
+            "filter_inactive_description": "არააქტიურია ამისთვის%(inactiveAgeDays)s დღეები ან მეტი",
+            "filter_label": "ფილტრაციის მოწყობილობები",
+            "filter_unverified_description": "არ არის მზად უსაფრთხო შეტყობინებებისთვის",
+            "filter_verified_description": "მზადაა უსაფრთხო შეტყობინებებისთვის",
+            "hide_details": "დეტალების დამალვა",
+            "inactive_days": "არააქტიურია ამისთვის%(inactiveAgeDays)s + დღეები",
+            "inactive_sessions": "არააქტიური სესიები",
+            "inactive_sessions_explainer_1": "არააქტიური სესიები არის სესიები, რომლებიც დიდი ხნის განმავლობაში არ გამოგიყენებიათ, მაგრამ ისინი აგრძელებენ დაშიფვრის გასაღებების მიღებას.",
+            "inactive_sessions_explainer_2": "არააქტიური სესიების წაშლა აუმჯობესებს უსაფრთხოებას და შესრულებას და გაგიადვილებთ იმის დადგენას, არის თუ არა ახალი სესია საეჭვო.",
+            "inactive_sessions_list_description": "იფიქრეთ ძველი სესიებიდან გასვლის შესახებ (%(inactiveAgeDays)s დღეები ან მეტი) თქვენ აღარ იყენებთ.",
+            "ip": "IP მისამართი",
+            "last_activity": "ბოლო აქტივობა",
+            "mobile_session": "მობილური სესია",
+            "n_sessions_selected": {
+                "one": "%(count)sარჩეული სესია",
+                "other": "%(count)sშერჩეული სესიები"
+            },
+            "no_inactive_sessions": "არააქტიური სესიები ვერ მოიძებნა.",
+            "no_sessions": "სესიები ვერ მოიძებნა.",
+            "no_unverified_sessions": "დაუდასტურებელი სესიები ვერ მოიძებნა.",
+            "no_verified_sessions": "დადასტურებული სესიები ვერ მოიძებნა.",
+            "os": "ოპერაციული სისტემა",
+            "other_sessions_heading": "სხვა სესიები",
+            "push_heading": "Push შეტყობინებები",
+            "push_subheading": "მიიღეთ push-შეტყობინებები ამ სესიაზე.",
+            "push_toggle": "ამ სესიაზე Push-შეტყობინებების გადართვა.",
+            "rename_form_caption": "გთხოვთ გაითვალისწინოთ, რომ სესიების სახელები ასევე ხილულია იმ ადამიანებისთვის, ვისთანაც ურთიერთობთ.",
+            "rename_form_heading": "სესიის გადარქმევა",
+            "rename_form_learn_more": "სესიების გადარქმევა",
+            "rename_form_learn_more_description_1": "სხვა მომხმარებლებს პირდაპირ შეტყობინებებში და ოთახებში, რომლებსაც თქვენ უერთდებით, შეუძლიათ ნახონ თქვენი სესიების სრული სია.",
+            "rename_form_learn_more_description_2": "ეს უზრუნველყოფს მათ დარწმუნებას, რომ ისინი ნამდვილად გესაუბრებიან თქვენთან, მაგრამ ეს ასევე ნიშნავს, რომ მათ შეუძლიათ დაინახონ სესიის სახელი, რომელსაც აქ შეიყვანთ.",
+            "security_recommendations": "უსაფრთხოების რეკომენდაციები",
+            "security_recommendations_description": "გააუმჯობესეთ თქვენი ანგარიშის უსაფრთხოება ამ რეკომენდაციების დაცვით.",
+            "session_id": "სესიის ID",
+            "show_details": "დეტალების ჩვენება",
+            "sign_in_with_qr": "ახალი მოწყობილობის მიბმა",
+            "sign_in_with_qr_button": "QR კოდის ჩვენება",
+            "sign_in_with_qr_description": "გამოიყენეთ QR კოდი სხვა მოწყობილობაში შესასვლელად და უსაფრთხო შეტყობინებების დასაყენებლად.",
+            "sign_out": "გადით ამ სესიიდან",
+            "sign_out_all_other_sessions": "გამოსვლა ყველა სხვა სესიიდან (%(otherSessionsCount)s )",
+            "sign_out_confirm_description": {
+                "one": "დარწმუნებული ხართ, რომ გსურთ სისტემიდან გამოსვლა%(count)s სესია?",
+                "other": "დარწმუნებული ხართ, რომ გსურთ სისტემიდან გამოსვლა%(count)s სესიები?"
+            },
+            "sign_out_n_sessions": {
+                "one": "გასვლა%(count)s სესია",
+                "other": "გასვლა%(count)s სესიები"
+            },
+            "title": "სესიები",
+            "unknown_session": "უცნობი სესიის ტიპი",
+            "unverified_session": "დაუდასტურებელი სესია",
+            "unverified_session_explainer_1": "ამ სესიას არ აქვს დაშიფვრის მხარდაჭერა და, შესაბამისად, ვერ დადასტურდება.",
+            "unverified_session_explainer_2": "თქვენ ვერ შეძლებთ მონაწილეობას ოთახებში, სადაც დაშიფვრა ჩართულია ამ სესიის გამოყენებისას.",
+            "unverified_session_explainer_3": "საუკეთესო უსაფრთხოებისა და კონფიდენციალურობისთვის რეკომენდებულია Matrix კლიენტების გამოყენება, რომლებიც მხარს უჭერენ დაშიფვრას.",
+            "unverified_sessions": "დაუმოწმებელი სესიები",
+            "unverified_sessions_explainer_1": "დაუდასტურებელი სესიები არის სესიები, რომლებიც შესულია თქვენი ავტორიზაციის მონაცემებით, მაგრამ არ არის გადამოწმებული.",
+            "unverified_sessions_explainer_2": "განსაკუთრებით უნდა დარწმუნდეთ, რომ აღიარებთ ამ სესიებს, რადგან ისინი შეიძლება წარმოადგენდეს თქვენი ანგარიშის არაავტორიზებულ გამოყენებას.",
+            "unverified_sessions_list_description": "დაადასტურეთ თქვენი სესიები გაძლიერებული უსაფრთხო შეტყობინებებისთვის ან გამოდით მათგან, რომლებსაც აღარ იცნობთ ან აღარ იყენებთ.",
+            "url": "URL",
+            "verified_session": "დამოწმებული სესია",
+            "verified_sessions": "დამოწმებული სესიები",
+            "verified_sessions_explainer_1": "დადასტურებული სესიები არის ყველგან, სადაც იყენებთ ამ ანგარიშს თქვენი საიდუმლო ფრაზის შეყვანის ან თქვენი ვინაობის დადასტურების შემდეგ სხვა დამოწმებული სესიით.",
+            "verified_sessions_explainer_2": "ეს ნიშნავს, რომ თქვენ გაქვთ ყველა გასაღები, რომელიც საჭიროა თქვენი დაშიფრული შეტყობინებების განბლოკვისთვის და სხვა მომხმარებლებისთვის დასადასტურებლად, რომ ენდობით ამ სესიას.",
+            "verified_sessions_list_description": "საუკეთესო უსაფრთხოებისთვის, გამოდით ნებისმიერი სესიიდან, რომელსაც აღარ იცნობთ ან აღარ იყენებთ.",
+            "verify_session": "სესიის გადამოწმება",
+            "web_session": "ვებ სესია"
+        },
+        "show_avatar_changes": "პროფილის სურათის ცვლილებების ჩვენება",
+        "show_breadcrumbs": "ოთახების სიის ზემოთ ახლახან ნანახი ოთახების მალსახმობების ჩვენება",
+        "show_chat_effects": "ჩეთის ეფექტების ჩვენება (ანიმაციები მაგ. კონფეტის მიღებისას)",
+        "show_displayname_changes": "საჩვენებელი სახელის ცვლილებების ჩვენება",
+        "show_join_leave": "შეერთების/დატოვების შეტყობინებების ჩვენება (მოწვევები/აშლი/აკრძალვები არ შეეხო)",
+        "show_nsfw_content": "NSFW შინაარსის ჩვენება",
+        "show_read_receipts": "სხვა მომხმარებლების მიერ გაგზავნილი წაკითხვის ქვითრების ჩვენება",
+        "show_redaction_placeholder": "წაშლილი შეტყობინებების ჩანაცვლების ველის ჩვენება",
+        "show_stickers_button": "სტიკერების ღილაკის ჩვენება",
+        "show_typing_notifications": "აჩვენეთ აკრეფის შეტყობინებები",
+        "sidebar": {
+            "metaspaces_favourites_description": "დააჯგუფეთ ყველა თქვენი საყვარელი ოთახი და ადამიანი ერთ ადგილას.",
+            "metaspaces_home_all_rooms": "ყველა ოთახის ჩვენება",
+            "metaspaces_home_all_rooms_description": "აჩვენეთ ყველა თქვენი ოთახი Home-ში, თუნდაც ისინი სივრცეში იყოს.",
+            "metaspaces_home_description": "სახლი სასარგებლოა ყველაფრის მიმოხილვისთვის.",
+            "metaspaces_orphans": "ოთახები სივრცის გარეთ",
+            "metaspaces_orphans_description": "დააჯგუფეთ ყველა თქვენი ოთახი, რომელიც არ არის სივრცის ნაწილი ერთ ადგილას.",
+            "metaspaces_people_description": "დააჯგუფეთ ყველა თქვენი ადამიანი ერთ ადგილას.",
+            "metaspaces_subsection": "საჩვენებელი ადგილები",
+            "spaces_explainer": "სივრცეები არის ოთახების და ადამიანების დაჯგუფების გზები. იმ სივრცეებთან ერთად, სადაც იმყოფებით, შეგიძლიათ გამოიყენოთ რამდენიმე წინასწარ აშენებული.",
+            "title": "გვერდითი ზოლი"
+        },
+        "start_automatically": "ავტომატურად დაიწყება სისტემაში შესვლის შემდეგ",
+        "use_12_hour_format": "დროის ანაბეჭდების ჩვენება 12 საათის ფორმატში (მაგ. 14:30)",
+        "use_command_enter_send_message": "გამოიყენეთ Command + Enter შეტყობინების გასაგზავნად",
+        "use_command_f_search": "გამოიყენეთ Command + F დროის ხაზის მოსაძებნად",
+        "use_control_enter_send_message": "გამოიყენეთ Ctrl + Enter შეტყობინების გასაგზავნად",
+        "use_control_f_search": "გამოიყენეთ Ctrl + F დროის ხაზის მოსაძებნად",
+        "voip": {
+            "allow_p2p": "დაუშვით Peer-to-Peer 1:1 ზარებისთვის",
+            "allow_p2p_description": "როდესაც ჩართულია, მეორე მხარემ შესაძლოა დაინახოს თქვენი IP მისამართი",
+            "audio_input_empty": "მიკროფონები არ არის აღმოჩენილი",
+            "audio_output": "აუდიო გამომავალი",
+            "audio_output_empty": "აუდიო გამომავალი არ არის აღმოჩენილი",
+            "auto_gain_control": "მოგების ავტომატური კონტროლი",
+            "connection_section": "კავშირი",
+            "echo_cancellation": "ექოს გაუქმება",
+            "enable_fallback_ice_server": "სარეზერვო ზარის დამხმარე სერვერის დაშვება (%(server)s )",
+            "enable_fallback_ice_server_description": "ვრცელდება მხოლოდ იმ შემთხვევაში, თუ თქვენი სახლის სერვერი არ გთავაზობთ. თქვენი IP მისამართი გაზიარდება ზარის დროს.",
+            "mirror_local_feed": "ასახეთ ადგილობრივი ვიდეო არხი",
+            "missing_permissions_prompt": "აკლია მედიის ნებართვები, დააწკაპუნეთ ღილაკზე ქვემოთ მოთხოვნისთვის.",
+            "noise_suppression": "ხმაურის ჩახშობა",
+            "request_permissions": "მოითხოვეთ მედიის ნებართვები",
+            "title": "ხმა და ვიდეო",
+            "video_input_empty": "ვებკამერები არ არის აღმოჩენილი",
+            "video_section": "ვიდეოს პარამეტრები",
+            "voice_agc": "მიკროფონის ხმის ავტომატური რეგულირება",
+            "voice_processing": "ხმის დამუშავება",
+            "voice_section": "ხმის პარამეტრები"
+        },
+        "warn_quit": "გაფრთხილება შეწყვეტამდე",
+        "warning": "<w>გაფრთხილება:</w><description/>"
+    },
+    "slash_command": {
+        "addwidget": "ამატებს მორგებულ ვიჯეტს URL-ით ოთახში",
+        "addwidget_iframe_missing_src": "iframe-ს არ აქვს src ატრიბუტი",
+        "addwidget_invalid_protocol": "გთხოვთ, მოგვაწოდოთ https:// ან http:// ვიჯეტის URL",
+        "addwidget_missing_url": "გთხოვთ, მოგვაწოდოთ ვიჯეტის URL ან ჩადეთ კოდი",
+        "addwidget_no_permissions": "ამ ოთახში ვიჯეტების შეცვლა შეუძლებელია.",
+        "ban": "აკრძალავს მომხმარებელს მოცემული ID-ით",
+        "category_actions": "მოქმედებები",
+        "category_admin": "ადმინ",
+        "category_advanced": "გაფართოებული",
+        "category_effects": "ეფექტები",
+        "category_messages": "შეტყობინებები",
+        "category_other": "სხვა",
+        "command_error": "ბრძანების შეცდომა",
+        "converttodm": "გარდაქმნის ოთახს DM-ად",
+        "converttoroom": "გარდაქმნის DM-ს ოთახად",
+        "could_not_find_room": "ოთახი ვერ ვიპოვე",
+        "deop": "Deops მომხმარებელი მოცემული ID-ით",
+        "devtools": "ხსნის Developer Tools დიალოგს",
+        "discardsession": "აიძულებს დაშიფრულ ოთახში მიმდინარე გამავალი ჯგუფის სესიას გაუქმდეს",
+        "error_invalid_rendering_type": "ბრძანების შეცდომა: ვერ ვპოულობ რენდერის ტიპის (%(renderingType)s )",
+        "error_invalid_room": "ბრძანება ვერ მოხერხდა: ოთახის პოვნა შეუძლებელია (%(roomId)s )",
+        "error_invalid_runfn": "ბრძანების შეცდომა: slash ბრძანების დამუშავება შეუძლებელია.",
+        "error_invalid_user_in_room": "ოთახში მომხმარებელი ვერ მოიძებნა",
+        "help": "აჩვენებს ბრძანებების ჩამონათვალს გამოყენებისა და აღწერით",
+        "help_dialog_title": "ბრძანება დახმარება",
+        "holdcall": "ათავსებს ზარს მიმდინარე ოთახში",
+        "html": "აგზავნის შეტყობინებას html სახით, მისი მარკდაუნის ინტერპრეტაციის გარეშე",
+        "ignore": "უგულებელყოფს მომხმარებელს, მალავს მის შეტყობინებებს თქვენგან",
+        "ignore_dialog_description": "თქვენ ახლა უგულებელყოფთ%(userId)s",
+        "ignore_dialog_title": "იგნორირებული მომხმარებელი",
+        "invite": "იწვევს მომხმარებელს მოცემული ID-ით მიმდინარე ოთახში",
+        "invite_3pid_needs_is_error": "გამოიყენეთ საიდენტიფიკაციო სერვერი ელექტრონული ფოსტით მოსაწვევად. მართეთ პარამეტრებში.",
+        "invite_3pid_use_default_is_title": "გამოიყენეთ საიდენტიფიკაციო სერვერი",
+        "invite_3pid_use_default_is_title_description": "გამოიყენეთ საიდენტიფიკაციო სერვერი ელექტრონული ფოსტით მოსაწვევად. დააწკაპუნეთ გაგრძელებაზე ნაგულისხმევი საიდენტიფიკაციო სერვერის გამოსაყენებლად (%(defaultIdentityServerName)s ) ან მართეთ პარამეტრებში.",
+        "invite_failed": "მომხმარებელი (%(user)s ) არ დასრულებულა როგორც მოწვეული%(roomId)s მაგრამ არავითარი შეცდომა არ იყო მიცემული მომწვევის პროგრამისგან",
+        "join": "უერთდება ოთახს მოცემული მისამართით",
+        "jumptodate": "გადადით ვადებში მოცემულ თარიღზე",
+        "jumptodate_invalid_input": "ჩვენ ვერ გავიგეთ მოცემული თარიღი (%(inputDate)s ). სცადეთ გამოიყენოთ ფორმატი YYYY-MM-DD.",
+        "lenny": "ამზადებს (͡° ͜ʖ ͡°) უბრალო ტექსტურ შეტყობინებას",
+        "me": "აჩვენებს მოქმედებას",
+        "msg": "უგზავნის შეტყობინებას მოცემულ მომხმარებელს",
+        "myavatar": "ცვლის თქვენი პროფილის სურათს ყველა ოთახში",
+        "myroomavatar": "ცვლის თქვენი პროფილის სურათს მხოლოდ ამ მიმდინარე ოთახში",
+        "myroomnick": "ცვლის თქვენს ეკრანზე მეტსახელს მხოლოდ მიმდინარე ოთახში",
+        "nick": "ცვლის თქვენს ეკრანზე მეტსახელს",
+        "no_active_call": "ამ ოთახში აქტიური ზარი არ არის",
+        "op": "განსაზღვრეთ მომხმარებლის სიმძლავრის დონე",
+        "part_unknown_alias": "ოთახის არაღიარებული მისამართი:%(roomAlias)s",
+        "plain": "აგზავნის შეტყობინებას, როგორც უბრალო ტექსტს, მისი ჩანაწერის ინტერპრეტაციის გარეშე",
+        "query": "ხსნის ჩატს მოცემულ მომხმარებელთან",
+        "query_not_found_phone_number": "ტელეფონის ნომრისთვის Matrix ID-ის პოვნა შეუძლებელია",
+        "rageshake": "გაგზავნეთ შეცდომის ანგარიში ჟურნალებით",
+        "rainbow": "აგზავნის მოცემულ შეტყობინებას ცისარტყელას ფერად",
+        "rainbowme": "აგზავნის მოცემულ ემოციას ცისარტყელას ფერად",
+        "remove": "ამოიღებს მომხმარებელს მოცემული ID-ით ამ ოთახიდან",
+        "roomavatar": "ცვლის მიმდინარე ოთახის ავატარს",
+        "roomname": "აყენებს ოთახის სახელს",
+        "server_error": "სერვერის შეცდომა",
+        "server_error_detail": "სერვერი მიუწვდომელია, გადატვირთულია ან რაღაც სხვა შეფერხდა.",
+        "shrug": "აბრუნებს ¯\\_(ツ)_/¯ უბრალო ტექსტურ შეტყობინებას",
+        "spoiler": "აგზავნის მოცემულ შეტყობინებას სპოილერის სახით",
+        "tableflip": "ამზადებს (╯°□°)╯︵ ┻━┻ უბრალო ტექსტურ შეტყობინებას",
+        "topic": "იღებს ან ადგენს ოთახის თემას",
+        "topic_none": "ამ ოთახს თემა არ აქვს.",
+        "topic_room_error": "ოთახის თემის მიღება ვერ მოხერხდა: ოთახის პოვნა ვერ ხერხდება (%(roomId)s",
+        "unban": "მოხსნის მომხმარებლის აკრძალვას მოცემული ID-ით",
+        "unflip": "ამზადებს ┬──┬ ノ( ゜-゜ノ) უბრალო ტექსტურ შეტყობინებას",
+        "unholdcall": "წყვეტს ზარს მიმდინარე ოთახში",
+        "unignore": "წყვეტს მომხმარებლის იგნორირებას, შემდგომში მათი შეტყობინებების ჩვენებას",
+        "unignore_dialog_description": "თქვენ აღარ იგნორირებას უკეთებთ%(userId)s",
+        "unignore_dialog_title": "უიგნორირებული მომხმარებელი",
+        "unknown_command": "უცნობი ბრძანება",
+        "unknown_command_button": "გაგზავნა როგორც შეტყობინება",
+        "unknown_command_detail": "ამოუცნობი ბრძანება:%(commandText)s",
+        "unknown_command_help": "შეგიძლიათ გამოიყენოთ<code> /დახმარება</code> ხელმისაწვდომი ბრძანებების ჩამოთვლა. ამის მესიჯად გაგზავნა გინდოდა?",
+        "unknown_command_hint": "მინიშნება: დაიწყეთ თქვენი შეტყობინება<code> //</code> ხაზგასმით დასაწყებად.",
+        "upgraderoom": "განაახლებს ოთახს ახალ ვერსიაზე",
+        "upgraderoom_permission_error": "თქვენ არ გაქვთ საჭირო ნებართვები ამ ბრძანების გამოსაყენებლად.",
+        "usage": "გამოყენება",
+        "view": "ხედავს ოთახს მოცემული მისამართით",
+        "whois": "აჩვენებს ინფორმაციას მომხმარებლის შესახებ"
+    },
+    "space": {
+        "add_existing_room_space": {
+            "create": "გსურთ სანაცვლოდ ახალი ოთახის დამატება?",
+            "create_prompt": "შექმენით ახალი ოთახი",
+            "dm_heading": "პირდაპირი შეტყობინებები",
+            "error_heading": "ყველა არჩეული არ არის დამატებული",
+            "progress_text": {
+                "one": "ოთახის დამატება...",
+                "other": "ოთახების დამატება... (%(progress)s გარეთ%(count)s )"
+            },
+            "space_dropdown_label": "სივრცის შერჩევა",
+            "space_dropdown_title": "დაამატეთ არსებული ოთახები",
+            "subspace_moved_note": "სივრცეების დამატება გადავიდა."
+        },
+        "add_existing_subspace": {
+            "create_button": "შექმენით ახალი სივრცე",
+            "create_prompt": "გსურთ სანაცვლოდ ახალი სივრცის დამატება?",
+            "filter_placeholder": "მოძებნეთ სივრცეები",
+            "space_dropdown_title": "დაამატეთ არსებული სივრცე"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "ოთახის ქრონოლოგიის ნახვა (devools)",
+            "explore": "ოთახების დათავლიერება",
+            "home": "ფართი სახლი",
+            "manage_and_explore": "მართეთ და შეისწავლეთ ოთახები",
+            "options": "სივრცის პარამეტრები"
+        },
+        "failed_load_rooms": "ოთახების სიის ჩატვირთვა ვერ მოხერხდა.",
+        "failed_remove_rooms": "ზოგიერთი ოთახის ამოღება ვერ მოხერხდა. სცადეთ მოგვიანებით",
+        "incompatible_server_hierarchy": "თქვენს სერვერს არ აქვს სივრცის იერარქიების ჩვენების მხარდაჭერა.",
+        "invite": "მოიწვიე ხალხი",
+        "invite_description": "მოიწვიე ელექტრონული ფოსტით ან მომხმარებლის სახელით",
+        "invite_link": "გააზიარეთ მოწვევის ბმული",
+        "joining_space": "გაწევრიანება",
+        "landing_welcome": "მოგესალმებით<name/>",
+        "leave_dialog_action": "დატოვე სივრცე",
+        "leave_dialog_description": "თქვენ აპირებთ წასვლას<spaceName/> .",
+        "leave_dialog_only_admin_room_warning": "თქვენ ერთადერთი ადმინი ხართ იმ ზოგიერთი ოთახისა თუ სივრცის, რომლის დატოვებაც გსურთ. მათი დატოვება დატოვებს მათ ყოველგვარი ადმინების გარეშე.",
+        "leave_dialog_only_admin_warning": "თქვენ ხართ ამ სივრცის ერთადერთი ადმინი. მისი დატოვება ნიშნავს, რომ მასზე კონტროლი არავის ექნება.",
+        "leave_dialog_option_all": "დატოვე ყველა ოთახი",
+        "leave_dialog_option_intro": "გსურთ ამ სივრცეში ოთახების დატოვება?",
+        "leave_dialog_option_none": "არ დატოვოთ ოთახი",
+        "leave_dialog_option_specific": "დატოვე რამდენიმე ოთახი",
+        "leave_dialog_public_rejoin_warning": "თქვენ ვერ შეძლებთ ხელახლა შეერთებას, თუ ხელახლა არ იქნებით მოწვეული.",
+        "leave_dialog_title": "დატოვე%(spaceName)s",
+        "mark_suggested": "მონიშნეთ, როგორც შემოთავაზებულია",
+        "no_search_result_hint": "შეგიძლიათ სცადოთ სხვა ძიება ან შეამოწმოთ ბეჭდვითი შეცდომები.",
+        "preferences": {
+            "sections_section": "სექციები საჩვენებლად",
+            "show_people_in_space": "ეს აჯგუფებს თქვენს ჩეთებს ამ სივრცის წევრებთან. ამის გამორთვა დამალავს ამ ჩეთებს თქვენი ხედვიდან%(spaceName)s ."
+        },
+        "room_filter_placeholder": "ოთახების ძებნა",
+        "search_children": "ძიება%(spaceName)s",
+        "search_placeholder": "მოძებნეთ სახელები და აღწერილობები",
+        "select_room_below": "ჯერ აირჩიეთ ქვემოთ მოცემული ოთახი",
+        "share_public": "გააზიარეთ თქვენი საჯარო სივრცე",
+        "suggested": "შესთავაზა",
+        "suggested_tooltip": "ეს ოთახი შემოთავაზებულია, როგორც შესაერთებლად კარგი ოთახი",
+        "title_when_query_available": "შედეგები",
+        "title_when_query_unavailable": "ოთახები და ფართები",
+        "unmark_suggested": "მონიშნეთ, როგორც არ არის შემოთავაზებული",
+        "user_lacks_permission": "ნებართვა არ გაქვს"
+    },
+    "terms": {
+        "column_document": "დოკუმენტი",
+        "column_service": "სერვისი",
+        "column_summary": "რეზიუმე",
+        "identity_server_no_terms_description_1": "ეს მოქმედება საჭიროებს პირადობის სერვერთან კავშირს ელ.ფოსტის ან მობილურის ნომრის დასადასტურებლად, მაგრამ სერვერს არ გააჩნია მომსახურების პირობები.",
+        "identity_server_no_terms_description_2": "განაგრძეთ მხოლოდ იმ შემთხვევაში, თუ ენდობით სერვერის მფლობელს.",
+        "identity_server_no_terms_title": "პირადობის სერვერს არ აქვს მომსახურების პირობები",
+        "inline_intro_text": "მიღება<policyLink /> გასაგრძელებლად:",
+        "integration_manager": "გამოიყენეთ ბოტები, ხიდები, ვიჯეტები და სტიკერების პაკეტები",
+        "intro": "გასაგრძელებლად თქვენ უნდა დაეთანხმოთ ამ სერვისის პირობებს.",
+        "summary_identity_server_1": "იპოვნეთ სხვები ტელეფონით ან ელექტრონული ფოსტით",
+        "summary_identity_server_2": "იპოვეთ ტელეფონით ან ელექტრონული ფოსტით",
+        "tac_button": "გადახედეთ პირობებს",
+        "tac_description": "გამოყენების გასაგრძელებლად%(homeserverDomain)s homeserver თქვენ უნდა გადახედოთ და დაეთანხმოთ ჩვენს წესებსა და პირობებს.",
+        "tac_title": "Ვადები და პირობები",
+        "tos": "Მომსახურების პირობები"
+    },
+    "theme": {
+        "light_high_contrast": "მსუბუქი მაღალი კონტრასტი",
+        "match_system": "მატჩის სისტემა"
+    },
+    "thread_view_back_action_label": "თემაში დაბრუნება",
+    "time": {
+        "about_day_ago": "დაახლოებით ერთი დღის წინ",
+        "about_hour_ago": "დაახლოებით ერთი საათის წინ",
+        "about_minute_ago": "დაახლოებით ერთი წუთის წინ",
+        "date_at_time": "%(date)s ზე %(time)s",
+        "few_seconds_ago": "რამდენიმე წამის წინ",
+        "hours_minutes_seconds_left": "%(hours)sს %(minutes)sწთ %(seconds)sწმ დარჩა",
+        "in_about_day": "დაახლოებით ერთი დღის შემდეგ",
+        "in_about_hour": "დაახლოებით ერთი საათის შემდეგ",
+        "in_about_minute": "დაახლოებით ერთი წუთის შემდეგ",
+        "in_few_seconds": "რამდენიმე წამის შემდეგ",
+        "in_n_days": "%(num)sდღეებიდან",
+        "in_n_hours": "%(num)sსაათის შემდეგ",
+        "in_n_minutes": "%(num)sწუთის შემდეგ",
+        "left": "%(timeRemaining)sდატოვა",
+        "minutes_seconds_left": "%(minutes)sწთ %(seconds)sწმ დარჩა",
+        "n_days_ago": "%(num)sდღის წინ",
+        "n_hours_ago": "%(num)sსაათის წინ",
+        "n_minutes_ago": "%(num)sწუთის წინ",
+        "seconds_left": "%(seconds)sწმ დარჩა",
+        "short_days": "%(value)sდ",
+        "short_days_hours_minutes_seconds": "%(days)sდ%(hours)s თ%(minutes)s მ%(seconds)s ს",
+        "short_hours": "%(value)sთ",
+        "short_hours_minutes_seconds": "%(hours)sთ%(minutes)s მ%(seconds)s ს",
+        "short_minutes": "%(value)sმ",
+        "short_minutes_seconds": "%(minutes)sმ%(seconds)s ს",
+        "short_seconds": "%(value)sს"
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "პასუხის თემის ჩაკეცვა",
+            "external_url": "წყაროს URL",
+            "open_in_osm": "გახსენით OpenStreetMap-ში",
+            "report": "მოხსენება",
+            "resent_unsent_reactions": "ხელახლა გაგზავნა%(unsentCount)s რეაქცია(ები)",
+            "show_url_preview": "გადახედვის ჩვენება",
+            "view_related_event": "იხილეთ დაკავშირებული მოვლენა",
+            "view_source": ""
+        },
+        "creation_summary_dm": "%(creator)sშექმნა ეს DM.",
+        "creation_summary_room": "%(creator)sოთახის შექმნა და კონფიგურაცია.",
+        "disambiguated_profile": "%(displayName)s(%(matrixId)s )",
+        "download_action_decrypting": "გაშიფვრა",
+        "download_action_downloading": "ჩამოტვირთვა",
+        "edits": {
+            "tooltip_label": "რედაქტირებულია%(date)s . დააწკაპუნეთ რედაქტირების სანახავად.",
+            "tooltip_sub": "დააწკაპუნეთ რედაქტირების სანახავად",
+            "tooltip_title": "რედაქტირებულია%(date)s"
+        },
+        "error_no_renderer": "ამ მოვლენის ჩვენება ვერ მოხერხდა",
+        "error_rendering_message": "ამ შეტყობინების ჩატვირთვა შეუძლებელია",
+        "historical_messages_unavailable": "თქვენ ვერ ხედავთ ადრინდელ შეტყობინებებს",
+        "in_room_name": " in<strong>%(room)s</strong>",
+        "io.element.widgets.layout": "%(senderName)sგანაახლა ოთახის განლაგება",
+        "load_error": {
+            "no_permission": "ვცადე კონკრეტული წერტილის ჩატვირთვა ამ ოთახის ქრონოლოგიაში, მაგრამ თქვენ არ გაქვთ სადავო შეტყობინების ნახვის უფლება.",
+            "title": "ვადების პოზიციის ჩატვირთვა ვერ მოხერხდა",
+            "unable_to_find": "ვცადე კონკრეტული წერტილის ჩატვირთვა ამ ოთახის ვადებში, მაგრამ ვერ ვიპოვე."
+        },
+        "m.audio": {
+            "error_downloading_audio": "შეცდომა აუდიოს ჩამოტვირთვისას",
+            "error_processing_audio": "შეცდომა აუდიო შეტყობინების დამუშავებისას",
+            "error_processing_voice_message": "შეცდომა ხმოვანი შეტყობინების დამუშავებისას",
+            "unnamed_audio": "უსახელო აუდიო"
+        },
+        "m.beacon_info": {
+            "view_live_location": ""
+        },
+        "m.call": {
+            "video_call_ended": "ვიდეოზარი დასრულდა",
+            "video_call_started": "ვიდეო ზარი დაიწყო%(roomName)s .",
+            "video_call_started_text": "%(name)sდაიწყო ვიდეოზარი",
+            "video_call_started_unsupported": "ვიდეო ზარი დაიწყო%(roomName)s . (ამ ბრაუზერის მიერ არ არის მხარდაჭერილი)"
+        },
+        "m.call.hangup": {
+            "dm": "ზარი დასრულდა"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "სხვაგან უპასუხა",
+            "call_back_prompt": "დარეკე",
+            "declined": "ზარი უარყოფილია",
+            "failed_connect_media": "მედიის დაკავშირება ვერ მოხერხდა",
+            "failed_connection": "კავშირი ვერ მოხერხდა",
+            "failed_opponent_media": "მათ მოწყობილობას არ შეეძლო კამერის ან მიკროფონის გაშვება",
+            "missed_call": "გამოტოვებული ზარი",
+            "no_answer": "პასუხი არ არის",
+            "unknown_error": "მოხდა უცნობი შეცდომა",
+            "unknown_failure": "უცნობი მარცხი:%(reason)s",
+            "unknown_state": "ზარი გაურკვეველ მდგომარეობაშია!",
+            "video_call": "%(senderName)sმოახდინა ვიდეო ზარი.",
+            "video_call_unsupported": "%(senderName)sმოახდინა ვიდეო ზარი. (ამ ბრაუზერის მიერ არ არის მხარდაჭერილი)",
+            "voice_call": "%(senderName)sმოახდინა ხმოვანი ზარი.",
+            "voice_call_unsupported": "%(senderName)sმოახდინა ხმოვანი ზარი. (ამ ბრაუზერის მიერ არ არის მხარდაჭერილი)"
+        },
+        "m.file": {
+            "error_decrypting": "შეცდომა დანართის გაშიფვრისას",
+            "error_invalid": "არასწორი ფაილი"
+        },
+        "m.image": {
+            "error": "შეცდომის გამო სურათის ჩვენება შეუძლებელია",
+            "error_decrypting": "შეცდომა სურათის გაშიფვრისას",
+            "error_downloading": "შეცდომა სურათის ჩამოტვირთვისას",
+            "sent": "%(senderDisplayName)sგამოაგზავნა სურათი.",
+            "show_image": ""
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "%(name)sგადამოწმება სურს",
+            "you_started": "თქვენ გაგზავნეთ დადასტურების მოთხოვნა"
+        },
+        "m.location": {
+            "full": "%(senderName)sგააზიარა მათი მდებარეობა",
+            "location": "გააზიარა მდებარეობა: ",
+            "self_location": "გააზიარა მათი მდებარეობა: "
+        },
+        "m.poll": {
+            "count_of_votes": {
+                "one": "%(count)sხმის მიცემა",
+                "other": "%(count)sხმები"
+            }
+        },
+        "m.poll.end": {
+            "ended": "დაასრულა გამოკითხვა",
+            "sender_ended": "%(senderName)s დაასრულა გამოკითხვა"
+        },
+        "m.poll.start": "%(senderName)sდაიწყო გამოკითხვა - %(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "%(senderDisplayName)sშეცვალა ოთახის ავატარი.",
+            "changed_img": "%(senderDisplayName)sშეცვალა ოთახის ავატარი<img/>",
+            "lightbox_title": "%(senderDisplayName)sშეცვალე ავატარი%(roomName)s",
+            "removed": "%(senderDisplayName)sამოიღო ოთახის ავატარი."
+        },
+        "m.room.canonical_alias": {
+            "alt_added": {
+                "one": "%(senderName)sდაემატა ალტერნატიული მისამართი%(addresses)s ამ ოთახისთვის.",
+                "other": "%(senderName)sდაამატა ალტერნატიული მისამართები%(addresses)s ამ ოთახისთვის."
+            },
+            "alt_removed": {
+                "one": "%(senderName)sამოღებულია ალტერნატიული მისამართი%(addresses)s ამ ოთახისთვის.",
+                "other": "%(senderName)sამოიღეს ალტერნატიული მისამართები%(addresses)s ამ ოთახისთვის."
+            },
+            "changed": "%(senderName)sშეცვალა მისამართები ამ ოთახისთვის.",
+            "changed_alternative": "%(senderName)sშეცვალა ამ ოთახის ალტერნატიული მისამართები.",
+            "changed_main_and_alternative": "%(senderName)sშეცვალა ამ ოთახის ძირითადი და ალტერნატიული მისამართები.",
+            "removed": "%(senderName)sამოიღეს ამ ოთახის მთავარი მისამართი.",
+            "set": "%(senderName)sდააყენეთ ამ ოთახის მთავარი მისამართი%(address)s."
+        },
+        "m.room.create": {
+            "continuation": "",
+            "see_older_messages": "დააწკაპუნეთ აქ ძველი შეტყობინებების სანახავად.",
+            "unknown_predecessor": "ამ ოთახის ძველი ვერსია ვერ მოიძებნა (ოთახის ID:%(roomId)s ), და ჩვენ არ მოგვაწოდეს &#39;via_servers&#39; მის მოსაძებნად.",
+            "unknown_predecessor_guess_server": "ამ ოთახის ძველი ვერსია ვერ მოიძებნა (ოთახის ID:%(roomId)s ), და ჩვენ არ მოგვაწოდეს &#39;via_servers&#39; მის მოსაძებნად. შესაძლებელია, რომ სერვერის გამოცნობა ოთახის ID-დან იმუშავებს. თუ გსურთ სცადოთ, დააწკაპუნეთ ამ ბმულზე:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "იგნორირებულია დაშიფვრის გამორთვის მცდელობა",
+            "disabled": "დაშიფვრა არ არის ჩართული",
+            "enabled": "ამ ოთახში შეტყობინებები დაშიფრულია ბოლომდე. როდესაც ადამიანები შეუერთდებიან, შეგიძლიათ დაადასტუროთ ისინი მათ პროფილში, უბრალოდ შეეხეთ მათი პროფილის სურათს.",
+            "enabled_dm": "შეტყობინებები აქ არის ბოლომდე დაშიფრული. გადაამოწმეთ%(displayName)s მათ პროფილში - შეეხეთ მათი პროფილის სურათს.",
+            "enabled_local": "შეტყობინებები ამ ჩეთში იქნება ბოლომდე დაშიფრული.",
+            "parameters_changed": "დაშიფვრის ზოგიერთი პარამეტრი შეიცვალა.",
+            "unsupported": "ამ ოთახის მიერ გამოყენებული დაშიფვრა მხარდაჭერილი არ არის."
+        },
+        "m.room.guest_access": {
+            "can_join": "%(senderDisplayName)sსტუმრებს საშუალება მისცა შეუერთდნენ ოთახს.",
+            "forbidden": "%(senderDisplayName)sხელი შეუშალა სტუმრებს ოთახში შეერთებაში.",
+            "unknown": "%(senderDisplayName)sშეცვალა სტუმრის წვდომა %(rule)s"
+        },
+        "m.room.history_visibility": {
+            "invited": "%(senderName)sოთახის მომავალი ისტორია ხილული გახადა ოთახის ყველა წევრისთვის, იმ მომწვეტიდან.",
+            "joined": "%(senderName)sოთახის მომავალი ისტორია ხილული გახადა ოთახის ყველა წევრისთვის, იმ მომენტიდან,",
+            "shared": "%(senderName)sოთახის მომავალი ისტორია ოთახის ყველა წევრისთვის ხილული",
+            "unknown": "%(senderName)sმომავალი ოთახის ისტორია უცნობისთვის ხილული გა %(visibility)s ხდა ()",
+            "world_readable": "%(senderName)sმომავალი ოთახის ისტორია ყველისთვის ხილული გახდა"
+        },
+        "m.room.join_rules": {
+            "invite": "%(senderDisplayName)sოთახში მხოლოდ დაპატიჟება გააკეთა.",
+            "knock": "%(senderDisplayName)sშეცვალა გაწევრიანების წესი გაწევრიანების მოთხოვნ",
+            "public": "%(senderDisplayName)sგახადა ოთახი საჯარო, ვინც იცის ბმული.",
+            "restricted": "%(senderDisplayName)sშეიცვალა ვის შეუძლია შეუერთდეს ამ ოთახს.",
+            "restricted_settings": "%(senderDisplayName)sშეიცვალა ვის შეუძლია შეუერთდეს ამ ოთახს. <a>პარამეტრების ნახვ </a> ა.",
+            "unknown": "%(senderDisplayName)sშეცვალა გაწევრიანების წესი %(rule)s"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "%(targetName)sმიიღო მოწვევა%(displayName)s",
+            "accepted_invite": "%(targetName)sმიიღო მოწვევა",
+            "ban": "%(senderName)sაკრძალული%(targetName)s",
+            "ban_reason": "%(senderName)sაკრძალული%(targetName)s :%(reason)s",
+            "change_avatar": "%(senderName)sშეიცვალა პროფილის სურათი",
+            "change_name": "%(oldDisplayName)sშეიცვალა მათი საჩვენებელი სახელი%(displayName)s",
+            "change_name_avatar": "%(oldDisplayName)sშეიცვალა მათი საჩვენებელი სახელი და პროფილის სურათი",
+            "invite": "%(senderName)sმოწვეული%(targetName)s",
+            "join": "%(targetName)sოთახს შეუერთდა",
+            "kick": "%(senderName)sამოღებულია%(targetName)s",
+            "kick_reason": "%(senderName)sამოღებულია%(targetName)s :%(reason)s",
+            "left": "%(targetName)sდატოვა ოთახი",
+            "left_reason": "%(targetName)sდატოვა ოთახი:%(reason)s",
+            "no_change": "%(senderName)sარ შეცვლილა",
+            "reject_invite": "%(targetName)sუარყო მოწვევა",
+            "remove_avatar": "%(senderName)sწაშალა მათი პროფილის სურათი",
+            "remove_name": "%(senderName)sამოიღეს მათი საჩვენებელი სახელი (%(oldDisplayName)s )",
+            "set_avatar": "%(senderName)sპროფილის სურათის დაყენება",
+            "set_name": "%(senderName)sდააყენეთ მათი საჩვენებელი სახელი%(displayName)s",
+            "unban": "%(senderName)sაკრძალული%(targetName)s",
+            "withdrew_invite": "%(senderName)sგაიყვანა%(targetName)s -ის მოწვევა",
+            "withdrew_invite_reason": "%(senderName)sგაიყვანა%(targetName)s -ის მოწვევა:%(reason)s"
+        },
+        "m.room.name": {
+            "change": "%(senderDisplayName)sშეიცვალა ოთახის სახელი%(oldRoomName)s რომ%(newRoomName)s .",
+            "remove": "%(senderDisplayName)sამოიღო ოთახის სახელი.",
+            "set": "%(senderDisplayName)sშეცვალა ოთახის სახელი%(roomName)s."
+        },
+        "m.room.pinned_events": {
+            "changed": "%(senderName)sშეცვალა ოთახის დამაგრებული შეტყობინებები.",
+            "changed_link": "%(senderName)sშეცვალა ო <a> თახის დამაგრებული შე </a> ტყობინებები.",
+            "pinned": "%(senderName)sშეტყობინება მიაკეთა ამ ოთახში. იხილეთ ყველა დამაგრებული შეტყობინება",
+            "pinned_link": "%(senderName)sშეტყობინ <a> ება მიაკეთა </a> ამ ოთახში. იხილეთ ყველა დამა <b> გრებული შეტყობინება </b>",
+            "unpinned": "%(senderName)sგამოვიდა შეტყობინება ამ ოთახიდან. იხილეთ ყველა დამაგრებული შეტყობინება",
+            "unpinned_link": "%(senderName)sგამოვიდა შეტყობ <a> ინება ამ ო </a> თახიდან. იხილეთ ყველა დამა <b> გრებული შეტყობინება </b>"
+        },
+        "m.room.power_levels": {
+            "changed": "%(senderName)sშეიცვალა სიმძლავრის დონე%(powerLevelDiffText)s .",
+            "user_from_to": "%(userId)sსაწყისი%(fromPowerLevel)s რომ%(toPowerLevel)s"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 ყველა სერვერს აკრძალულია მონაწილეობა! ეს ოთახი აღარ შეიძლება გამოყენებულ იქნას.",
+            "changed": "%(senderDisplayName)sშეცვალა სერვერის ACL-ები ამ ოთახისთვის.",
+            "set": "%(senderDisplayName)sდააყენეთ სერვერის ACL-ები ამ ოთახისთვის."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "%(senderName)sგააუქმა ოთახში გაწევ %(targetDisplayName)s რიანების მოწვევა.",
+            "sent": "%(senderName)sგაუგზავნა მოწვევა ო %(targetDisplayName)s თახში გაწევრიანების შესახებ."
+        },
+        "m.room.tombstone": "%(senderDisplayName)sგანაახლეს ეს ოთახი.",
+        "m.sticker": "%(senderDisplayName)sგაგზავნა სტიკერი.",
+        "m.video": {
+            "error_decrypting": ""
+        },
+        "m.widget": {
+            "added": "%(widgetName)sვიჯეტი დაამატა %(senderName)s",
+            "jitsi_ended": "ვიდეოკონფერენცია დასრულდა%(senderName)s",
+            "jitsi_join_right_prompt": "შეუერთდით კონფერენციას ოთახის საინფორმაციო ბარათიდან მარჯვნივ",
+            "jitsi_join_top_prompt": "შეუერთდით კონფერენციას ამ ოთახის ზედა ნაწილში",
+            "jitsi_started": "ვიდეოკონფერენცია დაიწყო%(senderName)s",
+            "jitsi_updated": "ვიდეოკონფერენცია განახლებულია%(senderName)s",
+            "modified": "%(widgetName)sვიჯეტი შეცვლილია %(senderName)s",
+            "removed": "%(widgetName)sვიჯეტი ამოღებულია %(senderName)s"
+        },
+        "mab": {
+            "collapse_reply_chain": "ციტატების ჩაკეცვა",
+            "copy_link_thread": "დააკოპირეთ ბმული თემაში",
+            "expand_reply_chain": "ციტატების გაფართოება",
+            "label": "შეტყობინების მოქმედებები",
+            "view_in_room": "ხედი ოთახში"
+        },
+        "mjolnir": {
+            "changed_rule_glob": "%(senderName)sგანახლდა აკრძალვის წესი, რომელიც ემთხვეოდა%(oldGlob)s შესატყვისებამდე%(newGlob)s ამისთვის%(reason)s",
+            "changed_rule_rooms": "%(senderName)sშეცვალა წესი, რომელიც კრძალავდა ოთახების შესატყვისს%(oldGlob)s შესატყვისებამდე%(newGlob)s ამისთვის%(reason)s",
+            "changed_rule_servers": "%(senderName)sშეცვალა წესი, რომელიც კრძალავდა სერვერების შესაბამისობას%(oldGlob)s შესატყვისებამდე%(newGlob)s ამისთვის%(reason)s",
+            "changed_rule_users": "%(senderName)sშეცვალა წესი, რომელიც კრძალავდა მომხმარებლების შესაბამისობას%(oldGlob)s შესატყვისებამდე%(newGlob)s ამისთვის%(reason)s",
+            "created_rule": "%(senderName)sშექმნა აკრძალვის წესის შესატყვისი%(glob)s ამისთვის%(reason)s",
+            "created_rule_rooms": "%(senderName)sშექმნეს წესი, რომელიც კრძალავს ოთახების შესაბამისობას%(glob)s ამისთვის%(reason)s",
+            "created_rule_servers": "%(senderName)sშექმნა წესი, რომელიც კრძალავს სერვერების შესაბამისობას%(glob)s ამისთვის%(reason)s",
+            "created_rule_users": "%(senderName)sშექმნეს წესი, რომელიც კრძალავს მომხმარებელთა შესაბამისობას%(glob)s ამისთვის%(reason)s",
+            "message_hidden": "თქვენ უგულებელყავით ეს მომხმარებელი, ამიტომ მისი შეტყობინება დამალულია.<a> აჩვენე მაინც.</a>",
+            "removed_rule": "%(senderName)sამოღებულია აკრძალვის წესის შესატყვისი%(glob)s",
+            "removed_rule_rooms": "%(senderName)sამოიღეს ოთახების შესატყვისი აკრძალვის წესი%(glob)s",
+            "removed_rule_servers": "%(senderName)sწაშალეს სერვერების დამთხვევის აკრძალვის წესი%(glob)s",
+            "removed_rule_users": "%(senderName)sამოიღეს წესი, რომელიც კრძალავს მომხმარებელთა დამთხვევას%(glob)s",
+            "updated_invalid_rule": "%(senderName)sგანაახლეს აკრძალვის არასწორი წესი",
+            "updated_rule": "%(senderName)sგანახლდა აკრძალვის წესის შესატყვისი%(glob)s ამისთვის%(reason)s",
+            "updated_rule_rooms": "%(senderName)sგანაახლეს ოთახების შესატყვისი აკრძალვის წესი%(glob)s ამისთვის%(reason)s",
+            "updated_rule_servers": "%(senderName)sგანაახლეს წესი, რომელიც კრძალავს სერვერების შესაბამისობას%(glob)s ამისთვის%(reason)s",
+            "updated_rule_users": "%(senderName)sგანაახლეს მომხმარებელთა დამთხვევის აკრძალვის წესი%(glob)s ამისთვის%(reason)s"
+        },
+        "no_permission_messages_before_invite": "თქვენ არ გაქვთ ნებართვა, იხილოთ შეტყობინებები, სანამ მოწვეული იქნებოდით.",
+        "no_permission_messages_before_join": "",
+        "pending_moderation": "შეტყობინება მოლოდინის რეჟიმშია",
+        "pending_moderation_reason": "შეტყობინება მოლოდინის რეჟიმშია: %(reason)s",
+        "reactions": {
+            "add_reaction_prompt": "დაამატეთ რეაქცია",
+            "custom_reaction_fallback_label": "მორგებული რეაქცია",
+            "label": "%(reactors)sგამოეხმაურა%(content)s"
+        },
+        "read_receipt_title": {
+            "one": "პირის მიერ ნა %(count)s ხული",
+            "other": "ხალხის მიერ ნა %(count)s ხული"
+        },
+        "read_receipts_label": "წაიკითხეთ ქვითრები",
+        "redacted": {
+            "tooltip": "შეტყობინება წაიშალა%(date)s"
+        },
+        "redaction": "შეტყობინება წაშალ %(name)s",
+        "reply": {
+            "error_loading": "ღონისძიების ჩატვირთვა, რომელზეც პასუხი იყო, ვერ ხერხდება, ის ან არ არსებობს, ან არ გაქვთ მისი ნახვის უფლება.",
+            "in_reply_to": "<a>პასუხად </a> <pill>",
+            "in_reply_to_for_export": "საპასუხოდ<a> ეს შეტყობინება</a>"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "თქვენ გადაგიყვანთ მესამე მხარის საიტზე, რათა შეძლოთ თქვენი ანგარიშის ავთენტიფიკაცია გამოსაყენებლად%(integrationsUrl)s. გსურთ გაგრძელება?",
+            "dialog_title": "დაამატეთ ინტეგრაცია"
+        },
+        "self_redaction": "მესიჯი წაშლილია",
+        "send_state_encrypting": "მიმდინარეობს თქვენი შეტყობინების დაშიფვრა…",
+        "send_state_failed": "გაგზავნა ვერ მოხერხდა",
+        "send_state_sending": "",
+        "send_state_sent": "თქვენი შეტყობინება გაიგზავნა",
+        "summary": {
+            "banned": {
+                "one": "აკრძალული იყო",
+                "other": "აკრძალული იყო%(count)s ჯერ"
+            },
+            "banned_multiple": {
+                "one": "აკრძალეს",
+                "other": "აკრძალეს%(count)s ჯერ"
+            },
+            "changed_avatar": {
+                "one": "%(oneUser)sშეიცვალა პროფილის სურათი",
+                "other": "%(oneUser)sშეიცვალა პროფილის სურათი%(count)s ჯერ"
+            },
+            "changed_avatar_multiple": {
+                "one": "%(severalUsers)sშეცვალა პროფილის სურათი",
+                "other": "%(severalUsers)sშეცვალა მათი პროფილის სურათ %(count)s ის დრო"
+            },
+            "changed_name": {
+                "one": "%(oneUser)sსახელი შეუცვალეს",
+                "other": "%(oneUser)sსახელი შეუცვალეს%(count)s ჯერ"
+            },
+            "changed_name_multiple": {
+                "one": "%(severalUsers)sსახელი შეუცვალეს",
+                "other": "%(severalUsers)sსახელი შეუცვალეს%(count)s ჯერ"
+            },
+            "format": "%(nameList)s %(transitionList)s",
+            "hidden_event": {
+                "one": "%(oneUser)sგაუგზავნა ფარული შეტყობინება",
+                "other": "%(oneUser)sგაგზავნილი%(count)s ფარული შეტყობინებები"
+            },
+            "hidden_event_multiple": {
+                "one": "%(severalUsers)sგაგზავნა ფარული შეტყობინ",
+                "other": "%(severalUsers)sგაგზავნ %(count)s ილი ფარული შეტ"
+            },
+            "invite_withdrawn": {
+                "one": "%(oneUser)sმოწვევა გაუქმდა",
+                "other": "%(oneUser)sმოწვევა გაუქმდა%(count)s ჯერ"
+            },
+            "invite_withdrawn_multiple": {
+                "one": "%(severalUsers)sმოწვევები გაუქმნეს",
+                "other": "%(severalUsers)sმოწვევების გაუქმების დ %(count)s რო ჰქონდა"
+            },
+            "invited": {
+                "one": "იყო მიწვეული",
+                "other": "იყო მიწვეული%(count)s ჯერ"
+            },
+            "invited_multiple": {
+                "one": "მიიწვიეს",
+                "other": "მიიწვიეს%(count)s ჯერ"
+            },
+            "joined": {
+                "one": "%(oneUser)sშეუერთდა",
+                "other": "%(oneUser)sშეუერთდა%(count)s ჯერ"
+            },
+            "joined_and_left": {
+                "one": "%(oneUser)sშეუერთდა და წავიდა",
+                "other": "%(oneUser)sშეუერთდა და წავიდა%(count)s ჯერ"
+            },
+            "joined_and_left_multiple": {
+                "one": "%(severalUsers)sშეუერთდა და წავიდა",
+                "other": "%(severalUsers)sდრო შეუერთდა და დატ %(count)s ოვა"
+            },
+            "joined_multiple": {
+                "one": "%(severalUsers)sშეუერთდა",
+                "other": "%(severalUsers)sშეუერთდა%(count)s ჯერ"
+            },
+            "kicked": {
+                "one": "მოიხსნა",
+                "other": "მოიხსნა%(count)s ჯერ"
+            },
+            "kicked_multiple": {
+                "one": "ამოიღეს",
+                "other": "ამოიღეს%(count)s ჯერ"
+            },
+            "left": {
+                "one": "%(oneUser)sდატოვა",
+                "other": "%(oneUser)sდატოვა%(count)s ჯერ"
+            },
+            "left_multiple": {
+                "one": "%(severalUsers)sდატოვა",
+                "other": "%(severalUsers)sდატოვა%(count)s ჯერ"
+            },
+            "no_change_multiple": {
+                "one": "%(severalUsers)sცვლილებები არ შეიტანა",
+                "other": "%(severalUsers)sცვლილებები არ შეიტანა%(count)s ჯერ"
+            },
+            "pinned_events": {
+                "one": "%(oneUser)sშეიცვალა<a> ჩამაგრებული შეტყობინებები</a> ოთახისთვის",
+                "other": "%(oneUser)sშეიცვალა<a> ჩამაგრებული შეტყობინებები</a> ოთახისთვის%(count)s ჯერ"
+            },
+            "pinned_events_multiple": {
+                "one": "%(severalUsers)sშეიცვალა<a> ჩამაგრებული შეტყობინებები</a> ოთახისთვის",
+                "other": "%(severalUsers)sშეიცვალა<a> ჩამაგრებული შეტყობინებები</a> ოთახისთვის%(count)s ჯერ"
+            },
+            "redacted": {
+                "one": "%(oneUser)sწაშალა შეტყობინება",
+                "other": "%(oneUser)sამოღებულია%(count)s შეტყობინებები"
+            },
+            "redacted_multiple": {
+                "one": "%(severalUsers)sწაშალა შეტყობინება",
+                "other": "%(severalUsers)sამოღებულია%(count)s შეტყობინებები"
+            },
+            "rejected_invite": {
+                "one": "%(oneUser)sუარყო მათი მოწვევა",
+                "other": "%(oneUser)sუარყო მათი მოწვევა%(count)s ჯერ"
+            },
+            "rejected_invite_multiple": {
+                "one": "%(severalUsers)sუარყო მათი მოწვევა",
+                "other": "%(severalUsers)sუარყო მათი მოწვევა%(count)s ჯერ"
+            },
+            "rejoined": {
+                "one": "%(oneUser)sდატოვა და შეუერთდა",
+                "other": "%(oneUser)sდატოვა და შეუერთდა%(count)s ჯერ"
+            },
+            "rejoined_multiple": {
+                "one": "%(severalUsers)sდატოვა და შეუერთდა",
+                "other": "%(severalUsers)sდატოვა და შეუერთდა%(count)s ჯერ"
+            },
+            "server_acls": {
+                "one": "%(oneUser)sშეცვალა სერვერის ACL-ები",
+                "other": "%(oneUser)sშეცვალა სერვერის ACL-ები%(count)s ჯერ"
+            },
+            "server_acls_multiple": {
+                "one": "%(severalUsers)sშეცვალა სერვერის ACL-ები",
+                "other": "%(severalUsers)sშეცვალა სერვერის ACL დრო %(count)s"
+            },
+            "unbanned": {
+                "one": "იყო აკრძალული",
+                "other": "იყო აკრძალული%(count)s ჯერ"
+            },
+            "unbanned_multiple": {
+                "one": "არ იყვნენ აკრძალული",
+                "other": "იყო დაუკრძალებელი დრო %(count)s"
+            }
+        },
+        "thread_info_basic": "ძაფიდან",
+        "typing_indicator": {
+            "more_users": {
+                "one": "%(names)sდა ერთი მეორე აკრეფებს...",
+                "other": "%(names)sდა %(count)s სხვები აკრეფენ..."
+            },
+            "one_user": "%(displayName)sაკრეფებს...",
+            "two_users": "%(names)sდა აკ %(lastPerson)s რეფენ..."
+        },
+        "undecryptable_tooltip": "ამ შეტყობინების გაშიფვრა ვერ მოხერხდა",
+        "url_preview": {
+            "close": "გადახედვის დახურვა",
+            "show_n_more": {
+                "one": "ჩვენება%(count)s სხვა გადახედვა",
+                "other": "ჩვენება%(count)s სხვა გადახედვები"
+            }
+        }
+    },
+    "unsupported_server_description": "ეს სერვერი იყენებს Matrix-ის ძველ ვერსიას. განაახლეთ Matrix-ზე%(version)s გამოიყენოს%(brand)s შეცდომების გარეშე.",
+    "unsupported_server_title": "თქვენი სერვერი მხარდაჭერილი არ არის",
+    "update": {
+        "changelog": "ცვლილებების ჟურნალი",
+        "check_action": "შეამოწმეთ განახლება",
+        "checking": "მიმდინარეობს განახლების შემოწმება…",
+        "downloading": "მიმდინარეობს განახლების ჩამოტვირთვა…",
+        "error_encountered": "წარმოიშვა შეცდომა (%(errorDetail)s ).",
+        "error_unable_load_commit": "ჩაწერის დეტალების ჩატვირთვა შეუძლებელია:%(msg)s",
+        "new_version_available": "ხელმისაწვდომია ახალი ვერსია.<a> განაახლეთ ახლავე.</a>",
+        "no_update": "განახლება არ არის ხელმისაწვდომი.",
+        "release_notes_toast_title": "Რა არის ახალი",
+        "see_changes_button": "რა არის ახალი?",
+        "toast_description": "ახალი ვერსია%(brand)s ხელმისაწვდომია",
+        "toast_title": "განახლება%(brand)s",
+        "unavailable": "მიუწვდომელია"
+    },
+    "upload_failed_generic": "ფაილი '%(fileName)s' ვერ აიტვირთა.",
+    "upload_failed_size": "ფაილი '%(fileName)s' აჭარბებს ამ ჰომსერვერის ზომის ლიმიტს ატვირთვისთვის",
+    "upload_failed_title": "ატვირთვა ვერ მოხერხდა",
+    "voip": {
+        "already_in_call": "უკვე ზარი",
+        "already_in_call_person": "თქვენ უკვე ხართ ამ ადამიანთან ზარზე.",
+        "answered_elsewhere": "სხვაგან უპასუხა",
+        "answered_elsewhere_description": "ზარს სხვა მოწყობილობაზე უპასუხეს.",
+        "call_failed": "ზარი ვერ მოხერხდა",
+        "call_failed_description": "ზარის დამყარება ვერ მოხერხდა",
+        "call_failed_media": "ზარი ვერ მოხერხდა, რადგან ვებკამერაზე ან მიკროფონზე წვდომა ვერ მოხერხდა. შეამოწმეთ რომ:",
+        "call_failed_media_applications": "არცერთი სხვა აპლიკაცია არ იყენებს ვებკამერას",
+        "call_failed_media_connected": "მიკროფონი და ვებკამერა ჩართულია და სწორად არის დაყენებული",
+        "call_failed_media_permissions": "ვებკამერის გამოყენების ნებართვა გაცემულია",
+        "call_failed_microphone": "ზარი ვერ მოხერხდა, რადგან მიკროფონზე წვდომა ვერ მოხერხდა. შეამოწმეთ, რომ მიკროფონი ჩართულია და სწორად არის დაყენებული.",
+        "call_held": "%(peerName)sზარი გამართა",
+        "call_held_resume": "თქვენ შეასრულეთ ზარი<a> რეზიუმე</a>",
+        "call_held_switch": "თქვენ შეასრულეთ ზარი<a> გადართვა</a>",
+        "call_toast_unknown_room": "უცნობი ოთახი",
+        "camera_disabled": "თქვენი კამერა გამორთულია",
+        "camera_enabled": "თქვენი კამერა კვლავ ჩართულია",
+        "cannot_call_yourself_description": "თქვენ არ შეგიძლიათ დარეკოთ საკუთარ თავთან.",
+        "connecting": "",
+        "connection_lost": "სერვერთან კავშირი დაიკარგა",
+        "connection_lost_description": "თქვენ არ შეგიძლიათ განახორციელოთ ზარები სერვერთან კავშირის გარეშე.",
+        "consulting": "კონსულტაციასთან%(transferTarget)s .<a> გადარიცხვა%(transferee)s</a>",
+        "default_device": "ნაგულისხმევი მოწყობილობა",
+        "dial": "აკრიფეთ",
+        "dialpad": "ციფერბლატი",
+        "disable_camera": "გამორთეთ კამერა",
+        "disable_microphone": "მიკროფონის დადუმება",
+        "disabled_no_one_here": "აქ არავინ არის დასარეკი",
+        "disabled_no_perms_start_video_call": "თქვენ არ გაქვთ ვიდეოზარების დაწყების ნებართვა",
+        "disabled_no_perms_start_voice_call": "თქვენ არ გაქვთ ხმოვანი ზარების დაწყების ნებართვა",
+        "disabled_ongoing_call": "მიმდინარე ზარი",
+        "enable_camera": "ჩართეთ კამერა",
+        "enable_microphone": "მიკროფონის დადუმების მოხსნა",
+        "expand": "ზარზე დაბრუნება",
+        "hangup": "დაიკიდე",
+        "hide_sidebar_button": "გვერდითი ზოლის დამალვა",
+        "input_devices": "შეყვანის მოწყობილობები",
+        "join_button_tooltip_call_full": "უკაცრავად - ეს ზარი ამჟამად სავსეა",
+        "maximise": "ეკრანის შევსება",
+        "misconfigured_server": "ზარი ვერ მოხერხდა სერვერის არასწორი კონფიგურაციის გამო",
+        "misconfigured_server_description": "გთხოვთ, ჰკითხოთ თქვენი სახლის სერვერის ადმინისტრატორს (<code>%(homeserverDomain)s</code> ) TURN სერვერის კონფიგურაცია, რათა ზარებმა საიმედოდ იმუშაოს.",
+        "misconfigured_server_fallback": "ალტერნატიულად, შეგიძლიათ სცადოთ გამოიყენოთ საჯარო სერვერი<server/> , მაგრამ ეს არ იქნება ისეთი სანდო და თქვენს IP მისამართს გაუზიარებს ამ სერვერს. ამის მართვა ასევე შეგიძლიათ პარამეტრებში.",
+        "misconfigured_server_fallback_accept": "სცადეთ გამოყენება%(server)s",
+        "more_button": "მეტი",
+        "msisdn_lookup_failed": "ტელეფონის ნომრის მოძებნა შეუძლებელია",
+        "msisdn_lookup_failed_description": "ტელეფონის ნომრის მოძიებაში შეცდომა დაფიქსირდა",
+        "msisdn_transfer_failed": "ზარის გადატანა შეუძლებელია",
+        "n_people_joined": {
+            "one": "%(count)sადამიანი შეუერთდა",
+            "other": "%(count)sხალხი შეუერთდა"
+        },
+        "no_audio_input_description": "ჩვენ ვერ ვიპოვეთ მიკროფონი თქვენს მოწყობილობაზე. გთხოვთ, შეამოწმოთ თქვენი პარამეტრები და სცადოთ ხელახლა.",
+        "no_audio_input_title": "მიკროფონი ვერ მოიძებნა",
+        "no_media_perms_description": "შეიძლება დაგჭირდეთ ხელით ნებართვა%(brand)s თქვენს მიკროფონზე/ვებკამერაზე წვდომისთვის",
+        "no_media_perms_title": "მედიის ნებართვები არ არის",
+        "no_permission_conference": "საჭიროა ნებართვა",
+        "no_permission_conference_description": "ამ ოთახში საკონფერენციო ზარის დაწყების უფლება არ გაქვთ",
+        "on_hold": "%(name)sმოლოდინში",
+        "output_devices": "გამომავალი მოწყობილობები",
+        "screenshare_monitor": "გააზიარეთ მთელი ეკრანი",
+        "screenshare_title": "გააზიარეთ შინაარსი",
+        "screenshare_window": "განაცხადის ფანჯარა",
+        "show_sidebar_button": "გვერდითი ზოლის ჩვენება",
+        "silence": "ზარის გაჩუმება",
+        "silenced": "შეტყობინებები გაჩუმდა",
+        "start_screenshare": "დაიწყეთ თქვენი ეკრანის გაზიარება",
+        "stop_screenshare": "შეაჩერე ეკრანის გაზიარება",
+        "too_many_calls": "ძალიან ბევრი ზარი",
+        "too_many_calls_description": "თქვენ მიაღწიეთ ერთდროული ზარების მაქსიმალურ რაოდენობას.",
+        "transfer_consult_first_label": "ჯერ გაიარე კონსულტაცია",
+        "transfer_failed": "გადაცემა ვერ მოხერხდა",
+        "transfer_failed_description": "ზარის გადატანა ვერ მოხერხდა",
+        "unable_to_access_audio_input_description": "ჩვენ ვერ შევძელით თქვენს მიკროფონზე წვდომა. გთხოვთ, შეამოწმოთ თქვენი ბრაუზერის პარამეტრები და სცადოთ ხელახლა.",
+        "unable_to_access_audio_input_title": "თქვენს მიკროფონზე წვდომა შეუძლებელია",
+        "unable_to_access_media": "ვებკამერაზე / მიკროფონზე წვდომა შეუძლებელია",
+        "unable_to_access_microphone": "მიკროფონზე წვდომა შეუძლებელია",
+        "unknown_caller": "უცნობი აბონენტი",
+        "unknown_person": "უცნობი პირი",
+        "unsilence": "ხმა ჩართულია",
+        "unsupported": "ზარები მხარდაუჭერელია",
+        "unsupported_browser": "თქვენ არ შეგიძლიათ განახორციელოთ ზარები ამ ბრაუზერში.",
+        "user_busy": "მომხმარებელი დაკავებულია",
+        "user_busy_description": "მომხმარებელი, რომელსაც დაურეკე, დაკავებულია.",
+        "user_is_presenting": "%(sharerName)sწარმოადგენს",
+        "video_call": "ვიდეო ზარი",
+        "video_call_started": "ვიდეო ზარი დაიწყო",
+        "voice_call": "ხმოვანი ზარი",
+        "you_are_presenting": "წარმოგიდგენთ"
+    },
+    "widget": {
+        "added_by": "ვიჯეტი დაამატა",
+        "capabilities_dialog": {
+            "content_starting_text": "ამ ვიჯეტს სურს:",
+            "decline_all_permission": "უარყო ყველა",
+            "remember_Selection": "დაიმახსოვრე ჩემი არჩევანი ამ ვიჯეტისთვის",
+            "title": "ვიჯეტის ნებართვების დამტკიცება"
+        },
+        "capability": {
+            "always_on_screen_generic": "დარჩით ეკრანზე მუშაობისას",
+            "always_on_screen_viewing_another_room": "დარჩით ეკრანზე სხვა ოთახის ყურებისას, გაშვებისას",
+            "any_room": "ზემოაღნიშნული, მაგრამ ნებისმიერ ოთახში თქვენც შემოგიერთდებით ან გეპატიჟებით",
+            "byline_empty_state_key": "ცარიელი მდგომარეობის გასაღებით",
+            "byline_state_key": "სახელმწიფო გასაღებით%(stateKey)s",
+            "capability": "The<b>%(capability)s</b> უნარი",
+            "change_avatar_active_room": "შეცვალეთ თქვენი აქტიური ოთახის ავატარი",
+            "change_avatar_this_room": "შეცვალეთ ამ ოთახის ავატარი",
+            "change_name_active_room": "შეცვალეთ თქვენი აქტიური ოთახის სახელი",
+            "change_name_this_room": "შეცვალეთ ამ ოთახის სახელი",
+            "change_topic_active_room": "შეცვალეთ თქვენი აქტიური ოთახის თემა",
+            "change_topic_this_room": "შეცვალეთ ამ ოთახის თემა",
+            "receive_membership_active_room": "ნახეთ, როდის უერთდებიან ადამიანები, ტოვებენ ან მოწვეულნი არიან თქვენს აქტიურ ოთახში",
+            "receive_membership_this_room": "ნახეთ, როდის უერთდებიან ადამიანები, ტოვებენ ან მოწვეულნი არიან ამ ოთახში",
+            "remove_ban_invite_leave_active_room": "წაშალეთ, აკრძალეთ ან მოიწვიეთ ხალხი თქვენს აქტიურ ოთახში და გაიძულებთ დატოვოთ",
+            "remove_ban_invite_leave_this_room": "წაშალეთ, აკრძალეთ ან მოიწვიეთ ხალხი ამ ოთახში და გაიძულებთ წახვიდეთ",
+            "see_avatar_change_active_room": "ნახეთ, როდის იცვლება ავატარი თქვენს აქტიურ ოთახში",
+            "see_avatar_change_this_room": "ნახეთ, როდის იცვლება ავატარი ამ ოთახში",
+            "see_event_type_sent_active_room": "იხ<b>%(eventType)s</b> მოვლენები გამოქვეყნებულია თქვენს აქტიურ ოთახში",
+            "see_event_type_sent_this_room": "იხ<b>%(eventType)s</b> ამ ოთახში გამოქვეყნებული ღონისძიებები",
+            "see_images_sent_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული სურათები",
+            "see_images_sent_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული სურათები",
+            "see_messages_sent_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული შეტყობინებები",
+            "see_messages_sent_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული შეტყობინებები",
+            "see_msgtype_sent_active_room": "იხ<b>%(msgtype)s</b> შეტყობინებები გამოქვეყნებულია თქვენს აქტიურ ოთახში",
+            "see_msgtype_sent_this_room": "იხ<b>%(msgtype)s</b> ამ ოთახში გამოქვეყნებული შეტყობინებები",
+            "see_name_change_active_room": "ნახეთ, როდის იცვლება სახელი თქვენს აქტიურ ოთახში",
+            "see_name_change_this_room": "ნახეთ, როდის შეიცვლება სახელი ამ ოთახში",
+            "see_sent_emotes_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული ემოციები",
+            "see_sent_emotes_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული ემოციები",
+            "see_sent_files_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული ზოგადი ფაილები",
+            "see_sent_files_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული ზოგადი ფაილები",
+            "see_sticker_posted_active_room": "ნახეთ, როცა ვინმე აქვეყნებს სტიკერს თქვენს აქტიურ ოთახში",
+            "see_sticker_posted_this_room": "ნახეთ, როდის დაიდება სტიკერი ამ ოთახში",
+            "see_text_messages_sent_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული ტექსტური შეტყობინებები",
+            "see_text_messages_sent_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული ტექსტური შეტყობინებები",
+            "see_topic_change_active_room": "ნახეთ, როდის იცვლება თემა თქვენს აქტიურ ოთახში",
+            "see_topic_change_this_room": "ნახეთ, როდის შეიცვლება თემა ამ ოთახში",
+            "see_videos_sent_active_room": "იხილეთ თქვენს აქტიურ ოთახში გამოქვეყნებული ვიდეოები",
+            "see_videos_sent_this_room": "იხილეთ ამ ოთახში გამოქვეყნებული ვიდეოები",
+            "send_emotes_active_room": "გაუგზავნეთ ემოციები თქვენს აქტიურ ოთახში",
+            "send_emotes_this_room": "გაგზავნეთ ემოციები, როგორც თქვენ ამ ოთახში",
+            "send_event_type_active_room": "გაგზავნა<b>%(eventType)s</b> მოვლენები, როგორც თქვენ თქვენს აქტიურ ოთახში",
+            "send_event_type_this_room": "გაგზავნა<b>%(eventType)s</b> მოვლენები, როგორც თქვენ ამ ოთახში",
+            "send_files_active_room": "გააგზავნეთ ზოგადი ფაილები თქვენს აქტიურ ოთახში",
+            "send_files_this_room": "გაგზავნეთ ზოგადი ფაილები, როგორც თქვენ ამ ოთახში",
+            "send_images_active_room": "გაგზავნეთ სურათები თქვენს აქტიურ ოთახში",
+            "send_images_this_room": "გაგზავნეთ სურათები, როგორც თქვენ ამ ოთახში",
+            "send_messages_active_room": "გააგზავნეთ შეტყობინებები თქვენს აქტიურ ოთახში",
+            "send_messages_this_room": "გაგზავნეთ შეტყობინებები, როგორც თქვენ ამ ოთახში",
+            "send_msgtype_active_room": "გაგზავნა<b>%(msgtype)s</b> შეტყობინებები თქვენს აქტიურ ოთახში",
+            "send_msgtype_this_room": "გაგზავნა<b>%(msgtype)s</b> შეტყობინებები, როგორც თქვენ ამ ოთახში",
+            "send_stickers_active_room": "გაგზავნეთ სტიკერები თქვენს აქტიურ ოთახში",
+            "send_stickers_active_room_as_you": "გაუგზავნეთ სტიკერები თქვენს აქტიურ ოთახში, როგორც თქვენ",
+            "send_stickers_this_room": "გაგზავნეთ სტიკერები ამ ოთახში",
+            "send_stickers_this_room_as_you": "გაუგზავნეთ სტიკერები ამ ოთახში, როგორც თქვენ",
+            "send_text_messages_active_room": "გააგზავნეთ ტექსტური შეტყობინებები თქვენს აქტიურ ოთახში",
+            "send_text_messages_this_room": "გაგზავნეთ ტექსტური შეტყობინებები, როგორც თქვენ ამ ოთახში",
+            "send_videos_active_room": "გააგზავნეთ ვიდეოები თქვენს აქტიურ ოთახში",
+            "send_videos_this_room": "გაგზავნეთ ვიდეოები, როგორც თქვენ ამ ოთახში",
+            "specific_room": "ზემოთ, მაგრამ ში<Room /> ასევე",
+            "switch_room": "შეცვალეთ რომელ ოთახს უყურებთ",
+            "switch_room_message_user": "შეცვალეთ ოთახი, შეტყობინება ან მომხმარებელი, რომელსაც უყურებთ"
+        },
+        "close_to_view_right_panel": "დახურეთ ეს ვიჯეტი ამ პანელში სანახავად",
+        "context_menu": {
+            "delete": "ვიჯეტის წაშლა",
+            "delete_warning": "ვიჯეტის წაშლა წაშლის მას ამ ოთახში ყველა მომხმარებლისთვის. დარწმუნებული ხართ, რომ გსურთ ამ ვიჯეტის წაშლა?",
+            "move_left": "მარცხნივ გადაადგილება",
+            "move_right": "იმოძრავეთ მარჯვნივ",
+            "remove": "წაშლა ყველასთვის",
+            "revoke": "გააუქმეთ ნებართვები",
+            "screenshot": "სურათის გადაღება",
+            "start_audio_stream": "აუდიო ნაკადის დაწყება"
+        },
+        "cookie_warning": "ამ ვიჯეტმა შეიძლება გამოიყენოს ქუქიები.",
+        "error_hangup_description": "თქვენ გათიშული იყავით ზართან. (შეცდომა:%(message)s )",
+        "error_hangup_title": "კავშირი დაიკარგა",
+        "error_loading": "შეცდომა ვიჯეტის ჩატვირთვისას",
+        "error_mixed_content": "შეცდომა - შერეული შინაარსი",
+        "error_need_invite_permission": "ამისათვის თქვენ უნდა შეძლოთ მომხმარებლების მოწვევა.",
+        "error_need_kick_permission": "ამისათვის თქვენ უნდა შეძლოთ მომხმარებლების დარტყმა.",
+        "error_need_to_be_logged_in": "თქვენ უნდა იყოთ შესული.",
+        "error_unable_start_audio_stream_description": "აუდიო ნაკადის დაწყება შეუძლებელია.",
+        "error_unable_start_audio_stream_title": "პირდაპირი სტრიმინგის დაწყება ვერ მოხერხდა",
+        "modal_data_warning": "ამ ეკრანზე არსებული მონაცემები გაზიარებულია%(widgetDomain)s",
+        "modal_title_default": "ვიჯეტის კაპიტალი",
+        "no_name": "უცნობი აპლიკაცია",
+        "open_id_permissions_dialog": {
+            "remember_selection": "დაიმახსოვრე ეს",
+            "starting_text": "ვიჯეტი დაადასტურებს თქვენს მომხმარებლის ID-ს, მაგრამ ვერ შეძლებს თქვენთვის მოქმედებების შესრულებას:",
+            "title": "მიეცით საშუალება ამ ვიჯეტს დაადასტუროს თქვენი ვინაობა"
+        },
+        "popout": "ამომხტარი ვიჯეტი",
+        "set_room_layout": "განლაგების დაყენება ყველასთვის",
+        "shared_data_avatar": "თქვენი პროფილის სურათის URL",
+        "shared_data_device_id": "თქვენი მოწყობილობის ID",
+        "shared_data_lang": "შენი ენა",
+        "shared_data_mxid": "თქვენი მომხმარებლის ID",
+        "shared_data_name": "თქვენი საჩვენებელი სახელი",
+        "shared_data_room_id": "ოთახის ID",
+        "shared_data_theme": "შენი თემა",
+        "shared_data_url": "%(brand)sURL",
+        "shared_data_warning": "ამ ვიჯეტის გამოყენებამ შესაძლოა მონაცემების გაზიარება<helpIcon /> თან%(widgetDomain)s .",
+        "shared_data_warning_im": "ამ ვიჯეტის გამოყენებამ შესაძლოა მონაცემების გაზიარება<helpIcon /> თან%(widgetDomain)s და თქვენი ინტეგრაციის მენეჯერი.",
+        "shared_data_widget_id": "ვიჯეტის ID",
+        "unencrypted_warning": "ვიჯეტები არ იყენებენ შეტყობინებების დაშიფვრას.",
+        "unmaximise": "მაქსიმიზაციის გაუქმება",
+        "unpin_to_view_right_panel": "მოხსენით ამ ვიჯეტის ჩამაგრება ამ პანელში სანახავად"
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "ყველა დიდი ასოების გამოცნობა თითქმის ისეთივე ადვილია, როგორც პატარა",
+            "anotherWord": "დაამატეთ კიდევ ერთი ან ორი სიტყვა. არაჩვეულებრივი სიტყვები უკეთესია.",
+            "associatedYears": "მოერიდეთ წლებს, რომლებიც დაკავშირებულია თქვენთან",
+            "capitalization": "კაპიტალიზაცია დიდად არ შველის",
+            "dates": "მოერიდეთ თარიღებს და წლებს, რომლებიც დაკავშირებულია თქვენთან",
+            "l33t": "პროგნოზირებადი ჩანაცვლება, როგორიცაა &#39;@&#39;, ნაცვლად &#39;a&#39;, დიდად არ დაგვეხმარება",
+            "longerKeyboardPattern": "გამოიყენეთ უფრო გრძელი კლავიატურის ნიმუში მეტი შემობრუნებით",
+            "noNeed": "არ არის საჭირო სიმბოლოები, ციფრები ან დიდი ასოები",
+            "pwned": "თუ ამ პაროლს სხვაგან იყენებთ, ის უნდა შეცვალოთ.",
+            "recentYears": "მოერიდეთ ბოლო წლებს",
+            "repeated": "მოერიდეთ განმეორებით სიტყვებსა და პერსონაჟებს",
+            "reverseWords": "შებრუნებული სიტყვების გამოცნობა არც ისე რთულია",
+            "sequences": "მოერიდეთ თანმიმდევრობებს",
+            "useWords": "გამოიყენეთ რამდენიმე სიტყვა, მოერიდეთ ჩვეულებრივ ფრაზებს"
+        },
+        "warnings": {
+            "common": "ეს არის ძალიან გავრცელებული პაროლი",
+            "commonNames": "საერთო სახელები და გვარები ადვილი გამოსაცნობია",
+            "dates": "თარიღების გამოცნობა ხშირად ადვილია",
+            "extendedRepeat": "გამეორებები, როგორიცაა &quot;abcabcabc&quot; ოდნავ უფრო რთული გამოსაცნობია, ვიდრე &quot;abc&quot;",
+            "keyPattern": "კლავიატურის მოკლე ნიმუშების გამოცნობა ადვილია",
+            "namesByThemselves": "სახელები და გვარები თავისთავად ადვილი გამოსაცნობია",
+            "pwned": "თქვენი პაროლი გამოვლინდა ინტერნეტში მონაცემთა გარღვევით.",
+            "recentYears": "ბოლო წლების გამოცნობა ადვილია",
+            "sequences": "თანმიმდევრობები, როგორიცაა abc ან 6543, ადვილი გამოსაცნობია",
+            "similarToCommon": "ეს ხშირად გამოყენებული პაროლის მსგავსია",
+            "simpleRepeat": "გამეორებები, როგორიცაა &quot;aaa&quot; ადვილი მისახვედრია",
+            "straightRow": "გასაღებების სწორი რიგები ადვილად გამოსაცნობია",
+            "topHundred": "ეს არის ყველაზე გავრცელებული 100 პაროლი",
+            "topTen": "ეს არის ტოპ-10 გავრცელებული პაროლი",
+            "userInputs": "არ უნდა იყოს პერსონალური ან გვერდთან დაკავშირებული მონაცემები.",
+            "wordByItself": "სიტყვა თავისთავად ადვილი გამოსაცნობია"
+        }
+    }
+}
diff --git a/src/i18n/strings/lo.json b/src/i18n/strings/lo.json
index b9d5104172aa7cb9f999dc6ce6385647928d9984..782980078d31a3c871ec9811c4b69c1a0aaa5bb5 100644
--- a/src/i18n/strings/lo.json
+++ b/src/i18n/strings/lo.json
@@ -82,7 +82,6 @@
         "react": "ປະຕິກິລິຍາ",
         "refresh": "ໂຫຼດຫນ້າຈໍຄືນ",
         "register": "ລົງທະບຽນ",
-        "reject": "ປະຕິເສດ",
         "remove": "ເອົາອອກ",
         "rename": "ປ່ຽນຊື່",
         "reply": "ຕອບ",
@@ -315,7 +314,6 @@
         "download_logs": "ບັນທຶກການດາວໂຫຼດ",
         "downloading_logs": "ບັນທຶກການດາວໂຫຼດ",
         "error_empty": "ກະລຸນາບອກພວກເຮົາວ່າມີຫຍັງຜິດພາດ ຫຼື, ດີກວ່າ, ສ້າງບັນຫາ GitHub ເພື່ອອະທິບາຍບັນຫາ.",
-        "failed_send_logs": "ສົ່ງບັນທຶກບໍ່ສຳເລັດ: ",
         "github_issue": "ບັນຫາ GitHub",
         "introduction": "ຖ້າທ່ານໄດ້ສົ່ງຂໍ້ບົກພ່ອງຜ່ານ GitHub, ບັນທຶກການຂໍ້ຜິດພາດສາມາດຊ່ວຍພວກເຮົາຕິດຕາມບັນຫາໄດ້. ",
         "log_request": "ເພື່ອຊ່ວຍພວກເຮົາປ້ອງກັນສິ່ງນີ້ໃນອະນາຄົດ, ກະລຸນາ <a>ສົ່ງບັນທຶກໃຫ້ພວກເຮົາ</a>.",
@@ -354,7 +352,6 @@
         "access_token": "ເຂົ້າເຖິງToken",
         "accessibility": "ການເຂົ້າເຖິງ",
         "advanced": "ຂັ້ນສູງ",
-        "all_rooms": "ຫ້ອງທັງໝົດ",
         "analytics": "ວິເຄາະ",
         "and_n_others": {
             "one": "ແລະ ອີກອັນນຶ່ງ...",
@@ -371,7 +368,6 @@
         "capabilities": "ຄວາມສາມາດ",
         "copied": "ສຳເນົາແລ້ວ!",
         "credits": "ສິນເຊື່ອ",
-        "cross_signing": "ການເຂົ້າລະຫັດແບບໄຂ້ວ",
         "dark": "ມືດ",
         "description": "ລາຍລະອຽດ",
         "deselect_all": "ຍົກເລີກການເລືອກທັງໝົດ",
@@ -443,7 +439,6 @@
         "room_name": "ຊື່ຫ້ອງ",
         "rooms": "ຫ້ອງ",
         "secure_backup": "ການສໍາຮອງທີ່ປອດໄພ",
-        "security": "ຄວາມປອດໄພ",
         "select_all": "ເລືອກທັງຫມົດ",
         "server": "ເຊີບເວີ",
         "settings": "ການຕັ້ງຄ່າ",
@@ -462,7 +457,6 @@
         "thread": "ກະທູ້",
         "threads": "ກະທູ້",
         "timeline": "ທາມລາຍ",
-        "trusted": "ເຊື່ອຖືໄດ້",
         "unencrypted": "ບໍ່ໄດ້ເຂົ້າລະຫັດ",
         "unmute": "ຍົກເລີກປິດສຽງ",
         "unnamed_room": "ບໍ່ມີຊື່ຫ້ອງ",
@@ -667,43 +661,23 @@
     "empty_room": "ຫ້ອງຫວ່າງ",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "ກະລຸນາໃສ່ປະໂຫຍກຄວາມປອດໄພຂອງທ່ານ ຫຼື <button>ໃຊ້ກະແຈຄວາມປອດໄພຂອງທ່ານ</button> ເພື່ອສືບຕໍ່.",
             "key_validation_text": {
-                "invalid_security_key": "ກະແຈຄວາມປອດໄພບໍ່ຖືກຕ້ອງ",
-                "recovery_key_is_correct": "ດີ!",
-                "wrong_file_type": "ປະເພດໄຟລ໌ບໍ່ຖຶກຕ້ອງ",
                 "wrong_security_key": "ກະແຈຄວາມປອດໄພບໍ່ຖຶກຕ້ອງ"
             },
-            "reset_title": "ຕັ້ງຄ່າໃໝ່ທຸກຢ່າງ",
-            "reset_warning_1": "ເຮັດແນວນີ້ກໍ່ຕໍ່ເມື່ອທ່ານບໍ່ມີອຸປະກອນອື່ນເພື່ອການຢັ້ງຢືນດ້ວຍ.",
-            "reset_warning_2": "ຖ້າທ່ານຕັ້ງຄ່າຄືນໃໝ່ທຸກຢ່າງ, ທ່ານຈະຣີສະຕາດໂດຍບໍ່ມີລະບົບທີ່ເຊື່ອຖືໄດ້, ບໍ່ມີຜູ້ໃຊ້ທີ່ເຊື່ອຖືໄດ້ ແລະ ອາດຈະບໍ່ເຫັນຂໍ້ຄວາມທີ່ຜ່ານມາ.",
             "restoring": "ການຟື້ນຟູລະຫັດຈາກການສໍາຮອງຂໍ້ມູນ",
-            "security_key_title": "ກະແຈຄວາມປອດໄພ",
-            "security_phrase_incorrect_error": "ບໍ່ສາມາດເຂົ້າເຖິງບ່ອນເກັບຂໍ້ມູນລັບໄດ້. ກະລຸນາກວດສອບວ່າທ່ານໃສ່ປະໂຫຍກຄວາມປອດໄພທີ່ຖືກຕ້ອງ.",
-            "security_phrase_title": "ປະໂຫຍກລະຫັດຄວາມປອດໄພ",
-            "use_security_key_prompt": "ໃຊ້ກະແຈຄວາມປອດໄພຂອງທ່ານເພື່ອສືບຕໍ່."
+            "security_key_title": "ກະແຈຄວາມປອດໄພ"
         },
         "bootstrap_title": "ການຕັ້ງຄ່າກະແຈ",
         "cancel_entering_passphrase_description": "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການຍົກເລີກການໃສ່ປະໂຫຍກລະຫັດຜ່ານ?",
         "cancel_entering_passphrase_title": "ຍົກເລີກການໃສ່ປະໂຫຍກລະຫັດຜ່ານບໍ?",
         "confirm_encryption_setup_body": "ກົດທີ່ປຸ່ມຂ້າງລຸ່ມນີ້ເພື່ອຢືນຢັນການຕັ້ງຄ່າການເຂົ້າລະຫັດ.",
         "confirm_encryption_setup_title": "ຢືນຢັນການຕັ້ງຄ່າການເຂົ້າລະຫັດ",
-        "cross_signing_not_ready": "ບໍ່ໄດ້ຕັ້ງຄ່າ Cross-signing.",
-        "cross_signing_ready": "ການລົງຊື່ຂ້າມແມ່ນກຽມພ້ອມສໍາລັບການໃຊ້ງານ.",
-        "cross_signing_ready_no_backup": "ການລົງຊື່ຂ້າມແມ່ນພ້ອມແລ້ວແຕ່ກະແຈບໍ່ໄດ້ສຳຮອງໄວ້.",
         "cross_signing_room_normal": "ຫ້ອງນີ້ຖືກເຂົ້າລະຫັດແບບຕົ້ນທາງ-ເຖິງປາຍທາງ",
         "cross_signing_room_verified": "ທຸກຄົນຢູ່ໃນຫ້ອງນີ້ໄດ້ຮັບການຢັ້ງຢືນແລ້ວ",
         "cross_signing_room_warning": "ບາງຄົນກໍາລັງໃຊ້ເງື່ອນໄຂທີ່ບໍ່ຮູ້ຈັກ",
-        "cross_signing_unsupported": "homeserverຂອງທ່ານບໍ່ຮອງຮັບການລົງຊື່ຂ້າມ.",
-        "cross_signing_untrusted": "ບັນຊີຂອງທ່ານມີຂໍ້ມູນແບບ cross-signing ໃນການເກັບຮັກສາຄວາມລັບ, ແຕ່ວ່າຍັງບໍ່ມີຄວາມເຊື່ອຖືໃນລະບົບນີ້.",
         "cross_signing_user_normal": "ທ່ານຍັງບໍ່ໄດ້ຢືນຢັນຜູ້ໃຊ້ນີ້.",
         "cross_signing_user_verified": "ທ່ານໄດ້ຢັ້ງຢືນຜູ້ໃຊ້ນີ້ແລ້ວ. ຜູ້ໃຊ້ນີ້ໄດ້ຢັ້ງຢືນທຸກເງິຶອນໄຂຂອງເຂົາເຈົ້າແລ້ວ.",
         "cross_signing_user_warning": "ຜູ້ໃຊ້ນີ້ຍັງບໍ່ໄດ້ຢຶນຢັນການເຂົ້າຮ່ວມຂອງເຂົາເຈົ້າທັງຫມົດຂອງ.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "ລຶບກະເເຈ cross-signing",
-            "title": "ທໍາລາຍກະແຈການເຊັນຮ່ວມ cross-signing ບໍ?",
-            "warning": "ການລຶບລະຫັດ cross-signing ແມ່ນຖາວອນ. ໃຜກໍຕາມທີ່ທ່ານໄດ້ຢັ້ງຢືນດ້ວຍຈະເຫັນການແຈ້ງເຕືອນຄວາມປອດໄພ. ທ່ານບໍ່ຕ້ອງເຮັດສິ່ງນີ້ເລີຍ, ເວັ້ນເສຍແຕ່ວ່າທ່ານເຮັດທຸກອຸກອນເສຍ ທີ່ທ່ານສາມາດຂ້າມເຂົ້າສູ່ລະບົບໄດ້."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "ອຸປະກອນນີ້ບໍ່ສາມາດຮັບປະກັນຄວາມຖືກຕ້ອງຂອງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດນີ້ໄດ້.",
         "event_shield_reason_mismatched_sender_key": "ເຂົ້າລະຫັດໂດຍພາກສ່ວນທີ່ບໍ່ໄດ້ຮັບການຢືນຢັນ",
         "export_unsupported": "ບຣາວເຊີຂອງທ່ານບໍ່ຮອງຮັບການເພິ່ມເຂົ້າລະຫັດລັບທີ່ຕ້ອງການ",
@@ -723,7 +697,6 @@
             "title": "ວິທີການກູ້ຄືນໃຫມ່",
             "warning": "ຖ້າທ່ານບໍ່ໄດ້ກຳນົດວິທີການກູ້ຄືນໃໝ່, ຜູ້ໂຈມຕີອາດຈະພະຍາຍາມເຂົ້າເຖິງບັນຊີຂອງທ່ານ. ປ່ຽນລະຫັດຜ່ານບັນຊີຂອງທ່ານ ແລະກຳນົດ ວິທີການກູ້ຄືນໃໝ່ທັນທີໃນການຕັ້ງຄ່າ."
         },
-        "not_supported": "<ບໍ່ຮອງຮັບ>",
         "recovery_method_removed": {
             "description_1": "ລະບົບນີ້ໄດ້ກວດພົບປະໂຫຍກຄວາມປອດໄພ ແລະ ກະແຈຂໍ້ຄວາມທີ່ປອດໄພຂອງທ່ານໄດ້ຖືກເອົາອອກແລ້ວ.",
             "description_2": "ຖ້າທ່ານດຳເນີນການສິ່ງນີ້ໂດຍບໍ່ໄດ້ຕັ້ງໃຈ, ທ່ານສາມາດຕັ້ງຄ່າຄວາມປອດໄພຂອງຂໍ້ຄວາມ ໃນລະບົບນີ້ ເຊິ່ງຈະມີການເຂົ້າລະຫັດປະຫວັດຂໍ້ຄວາມຂອງລະບົບນີ້ຄືນໃໝ່ດ້ວຍຂະບວນການກູ້ຂໍ້ມູນໃໝ່.",
@@ -734,8 +707,7 @@
         "set_up_toast_description": "ປ້ອງກັນການສູນເສຍການເຂົ້າເຖິງຂໍ້ຄວາມ ແລະຂໍ້ມູນທີ່ເຂົ້າລະຫັດ",
         "set_up_toast_title": "ຕັ້ງຄ່າການສໍາຮອງຂໍ້ມູນທີ່ປອດໄພ",
         "setup_secure_backup": {
-            "explainer": "ສຳຮອງຂໍ້ມູນກະແຈຂອງທ່ານກ່ອນທີ່ຈະອອກຈາກລະບົບເພື່ອຫຼີກເວັ້ນການສູນເສຍຂໍ້ມູນ.",
-            "title": "ຕັ້ງຄ່າ"
+            "explainer": "ສຳຮອງຂໍ້ມູນກະແຈຂອງທ່ານກ່ອນທີ່ຈະອອກຈາກລະບົບເພື່ອຫຼີກເວັ້ນການສູນເສຍຂໍ້ມູນ."
         },
         "udd": {
             "other_ask_verify_text": "ຂໍໃຫ້ຜູ້ໃຊ້ນີ້ກວດສອບລະບົບຂອງເຂົາເຈົ້າ, ຫຼື ຢືນຢັນດ້ວຍຕົນເອງຂ້າງລຸ່ມນີ້.",
@@ -745,12 +717,10 @@
             "title": "ເຊື່ອຖືບໍ່ໄດ້"
         },
         "unable_to_setup_keys_error": "ບໍ່ສາມາດຕັ້ງຄ່າກະແຈໄດ້",
-        "unsupported": "ລູກຄ້ານີ້ບໍ່ຮອງຮັບການເຂົ້າລະຫັດແບບຕົ້ນທາງເຖິງປາຍທາງ.",
         "verification": {
             "accepting": "ກຳລັງຍອມຮັບ…",
             "after_new_login": {
                 "device_verified": "ຢັ້ງຢືນອຸປະກອນແລ້ວ",
-                "reset_confirmation": "ຕັ້ງຄ່າຢືນຢັນກະແຈຄືນໃໝ່ບໍ?",
                 "skip_verification": "ຂ້າມການຢັ້ງຢືນດຽວນີ້",
                 "unable_to_verify": "ບໍ່ສາມາດຢັ້ງຢືນອຸປະກອນນີ້ໄດ້",
                 "verify_this_device": "ຢັ້ງຢືນອຸປະກອນນີ້"
@@ -812,7 +782,6 @@
             "verify_emoji_prompt": "ຢັ້ງຢືນໂດຍການປຽບທຽບ emoji ທີ່ເປັນເອກະລັກ.",
             "verify_emoji_prompt_qr": "ຖ້າທ່ານບໍ່ສາມາດສະແກນລະຫັດຂ້າງເທິງໄດ້, ໃຫ້ກວດສອບໂດຍການປຽບທຽບອີໂມຈິທີ່ເປັນເອກະລັກ.",
             "verify_later": "ຂ້ອຍຈະກວດສອບພາຍຫຼັງ",
-            "verify_reset_warning_1": "ການຕັ້ງຄ່າລະຫັດຢືນຢັນຂອງທ່ານບໍ່ສາມາດຍົກເລີກໄດ້. ຫຼັງຈາກການຕັ້ງຄ່າແລ້ວ, ທ່ານຈະບໍ່ສາມາດເຂົ້າເຖິງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດເກົ່າໄດ້, ແລະ ໝູ່ເພື່ອນທີ່ຢືນຢັນໄປກ່ອນໜ້ານີ້ ທ່ານຈະເຫັນຄຳເຕືອນຄວາມປອດໄພຈົນກວ່າທ່ານຈະຢືນຢັນກັບພວກມັນຄືນໃໝ່.",
             "verify_using_device": "ຢັ້ງຢືນດ້ວຍອຸປະກອນອື່ນ",
             "verify_using_key": "ຢືນຢັນດ້ວຍກະແຈຄວາມປອດໄພ",
             "verify_using_key_or_phrase": "ຢືນຢັນດ້ວຍກະແຈຄວາມປອດໄພ ຫຼືປະໂຫຍກ",
@@ -867,11 +836,7 @@
             "title": "ບໍ່ສາມາດສຳເນົາລິ້ງຫ້ອງໄດ້"
         },
         "error_loading_user_profile": "ບໍ່ສາມາດໂຫຼດໂປຣໄຟລ໌ຂອງຜູ້ໃຊ້ໄດ້",
-        "forget_room_failed": "ບໍ່ສາມາດລືມຫ້ອງ ໄດ້%(errCode)s",
-        "search_failed": {
-            "server_unavailable": "ເຊີບເວີອາດຈະບໍ່ມີຢູ່, ໂຫຼດເກີນ, ຫຼື ໝົດເວລາການຊອກຫາ :(",
-            "title": "ຄົ້ນຫາບໍ່ສຳເລັດ"
-        }
+        "forget_room_failed": "ບໍ່ສາມາດລືມຫ້ອງ ໄດ້%(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1377,11 +1342,6 @@
         "ongoing": "ກຳລັງລຶບ…",
         "reason_label": "ເຫດຜົນ (ທາງເລືອກ)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການປະຕິເສດຄຳເຊີນ?",
-        "failed": "ປະຕິເສດຄຳເຊີນບໍ່ສຳເລັດ",
-        "title": "ປະຕິເສດຄຳເຊີນ"
-    },
     "report_content": {
         "description": "ການລາຍງານຂໍ້ຄວາມນີ້ຈະສົ່ງ 'ID ເຫດການ' ທີ່ບໍ່ຊໍ້າກັນໄປຫາຜູ້ຄຸ້ມຄອງລະບົບເຊີບເວີຂອງທ່ານ. ຖ້າຂໍ້ຄວາມຢູ່ໃນຫ້ອງນີ້ຖືກເຂົ້າລະຫັດໄວ້, ຜູ້ຄຸ້ມຄອງລະບົບເຊີບເວີຂອງທ່ານຈະບໍ່ສາມາດອ່ານຂໍ້ຄວາມ ຫຼືເບິ່ງໄຟລ໌ ຫຼືຮູບພາບຕ່າງໆໄດ້.",
         "disagree": "ບໍ່ເຫັນດີ",
@@ -1511,7 +1471,6 @@
             "you_created": "ທ່ານສ້າງຫ້ອງນີ້."
         },
         "invite_email_mismatch_suggestion": "ແບ່ງປັນອີເມວນີ້ໃນການຕັ້ງຄ່າເພື່ອຮັບການເຊີນໂດຍກົງໃນ %(brand)s.",
-        "invite_reject_ignore": "ປະຕິເສດ ແລະ ບໍ່ສົນໃຈຜູ້ໃຊ້",
         "invite_sent_to_email": "ການເຊີນນີ້ຖືກສົ່ງໄປຫາ %(email)s",
         "invite_sent_to_email_room": "ການເຊີນນີ້ໄປຫາ %(roomName)s ໄດ້ຖືກສົ່ງໄປຫາ %(email)s",
         "invite_subtitle": "<userName/> ເຊີນທ່ານ",
@@ -1960,7 +1919,6 @@
             "remove_email_prompt": "ລຶບ %(email)s ອອກບໍ?",
             "remove_msisdn_prompt": "ລຶບ %(phone)sອອກບໍ?"
         },
-        "image_thumbnails": "ສະແດງຕົວຢ່າງ/ຮູບຕົວຢ່າງສຳລັບຮູບພາບ",
         "inline_url_previews_default": "ເປີດໃຊ້ການສະແດງຕົວຢ່າງ URL ໃນແຖວຕາມຄ່າເລີ່ມຕົ້ນ",
         "inline_url_previews_room": "ເປີດໃຊ້ການສະແດງຕົວຢ່າງ URL ໂດຍຄ່າເລີ່ມຕົ້ນສໍາລັບຜູ້ເຂົ້າຮ່ວມໃນຫ້ອງນີ້",
         "inline_url_previews_room_account": "ເປີດໃຊ້ຕົວຢ່າງ URL ສໍາລັບຫ້ອງນີ້ (ມີຜົນຕໍ່ທ່ານເທົ່ານັ້ນ)",
@@ -2055,48 +2013,16 @@
         "prompt_invite": "ເຕືອນກ່ອນທີ່ຈະສົ່ງຄໍາເຊີນໄປຫາ ID matrix ທີ່ອາດຈະບໍ່ຖືກຕ້ອງ",
         "replace_plain_emoji": "ປ່ຽນແທນ Emoji ຂໍ້ຄວາມທຳມະດາໂດຍອັດຕະໂນມັດ",
         "security": {
-            "4s_public_key_in_account_data": "ໃນຂໍ້ມູນບັນຊີ",
-            "4s_public_key_status": "ກະເເຈສາທາລະນະການເກັບຮັກສາຄວາມລັບ:",
-            "backup_key_cached_status": "ລະຫັດສໍາຮອງຂໍ້ມູນທີ່ເກັບໄວ້:",
-            "backup_key_stored_status": "ກະແຈສຳຮອງທີ່ເກັບໄວ້:",
-            "backup_key_unexpected_type": "ປະເພດທີ່ບໍ່ຄາດຄິດ",
-            "backup_key_well_formed": "ສ້າງຕັ້ງຂຶ້ນ",
-            "backup_keys_description": "ສຳຮອງຂໍ້ມູນລະຫັດການເຂົ້າລະຫັດຂອງທ່ານດ້ວຍຂໍ້ມູນບັນຊີຂອງທ່ານໃນກໍລະນີທີ່ທ່ານສູນເສຍການເຂົ້າເຖິງລະບົບຂອງທ່ານ. ກະແຈຂອງທ່ານຈະຖືກຮັກສາໄວ້ດ້ວຍກະແຈຄວາມປອດໄພທີ່ເປັນເອກະລັກ.",
             "bulk_options_accept_all_invites": "ຍອມຮັບການເຊີນທັງໝົດ %(invitedRooms)s",
             "bulk_options_reject_all_invites": "ປະຕິເສດການເຊີນທັງໝົດ %(invitedRooms)s",
             "bulk_options_section": "ຕົວເລືອກຈຳນວນຫຼາຍ",
-            "cross_signing_cached": "ເກັບໄວ້ໃນ cached ເຄື່ອງ",
-            "cross_signing_homeserver_support": "ສະຫນັບສະຫນູນຄຸນນະສົມບັດ Homeserver:",
-            "cross_signing_homeserver_support_exists": "ມີຢູ່",
-            "cross_signing_in_4s": "ໃນການເກັບຮັກສາຄວາມລັບ",
-            "cross_signing_in_memory": "ໃນຄວາມຊົງຈໍາ",
-            "cross_signing_master_private_Key": "ລະຫັດສ່ວນຕົວຫຼັກ:",
-            "cross_signing_not_cached": "ບໍ່ພົບຢູ່ໃນເຄື່ອງ",
-            "cross_signing_not_found": "ບໍ່ພົບເຫັນ",
-            "cross_signing_not_in_4s": "ບໍ່ພົບຢູ່ໃນບ່ອນເກັບມ້ຽນ",
-            "cross_signing_not_stored": "ບໍ່ໄດ້ເກັບຮັກສາໄວ້",
-            "cross_signing_private_keys": "ກະແຈສ່ວນຕົວCross-signing :",
-            "cross_signing_public_keys": "ກະແຈສາທາລະນະທີ່ມີ Cross-signing:",
-            "cross_signing_self_signing_private_key": "ລະຫັດສ່ວນຕົວທີ່ເຊັນດ້ວຍຕົນເອງ:",
-            "cross_signing_user_signing_private_key": "ຜູ້ໃຊ້ເຂົ້າສູ່ລະບົບລະຫັດສ່ວນຕົວ:",
-            "cryptography_section": "ການເຂົ້າລະຫັດລັບ",
-            "delete_backup": "ລຶບການສຳຮອງຂໍ້ມູນ",
-            "delete_backup_confirm_description": "ທ່ານແນ່ໃຈບໍ່? ທ່ານຈະສູນເສຍຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດໄວ້ຫາກກະແຈຂອງທ່ານບໍ່ຖືກສຳຮອງຂໍ້ມູນຢ່າງຖືກຕ້ອງ.",
             "e2ee_default_disabled_warning": "ຜູ້ຄຸມເຊີບເວີຂອງທ່ານໄດ້ປິດການນຳໃຊ້ການເຂົ້າລະຫັດແບບຕົ້ນທາງຮອດປາຍທາງໂດຍຄ່າເລີ່ມຕົ້ນໃນຫ້ອງສ່ວນຕົວ ແລະ ຂໍ້ຄວາມໂດຍກົງ.",
             "enable_message_search": "ເປີດໃຊ້ການຊອກຫາຂໍ້ຄວາມຢູ່ໃນຫ້ອງທີ່ຖືກເຂົ້າລະຫັດ",
             "encryption_section": "ການເຂົ້າລະຫັດ",
-            "error_loading_key_backup_status": "ບໍ່ສາມາດໂຫຼດສະຖານະສຳຮອງລະຫັດໄດ້",
-            "export_megolm_keys": "ສົ່ງກະແຈຫ້ອງ E2E ອອກ",
             "ignore_users_empty": "ທ່ານບໍ່ມີຜູ້ໃຊ້ທີ່ຖືກລະເລີຍ.",
             "ignore_users_section": "ຜູ້ໃຊ້ຖືກຍົກເວັ້ນ",
-            "import_megolm_keys": "ນຳເຂົ້າກະແຈຫ້ອງ E2E",
-            "key_backup_active_version_none": "ບໍ່ມີ",
             "key_backup_algorithm": "ສູດການຄິດໄລ່:",
-            "key_backup_complete": "ກະແຈທັງໝົດຖືກສຳຮອງໄວ້",
             "key_backup_connect": "ເຊື່ອມຕໍ່ລະບົບນີ້ກັບ ກະເເຈສຳຮອງ",
-            "key_backup_connect_prompt": "ເຊື່ອມຕໍ່ລະບົບນີ້ກັບການສໍາຮອງກະແຈກ່ອນທີ່ຈະອອກຈາກລະບົບເພື່ອຫຼີກເວັ້ນການສູນເສຍກະແຈທີ່ອາດຢູ່ໃນລະບົບນີ້ເທົ່ານັ້ນ.",
-            "key_backup_inactive": "ລະບົບນີ້ແມ່ນ <b>ບໍ່ໄດ້ສໍາຮອງລະຫັດຂອງທ່ານ</b>, ແຕ່ທ່ານມີການສໍາຮອງຂໍ້ມູນທີ່ມີຢູ່ແລ້ວທີ່ທ່ານສາມາດກູ້ຄືນຈາກ ແລະເພີ່ມຕໍ່ໄປ.",
-            "key_backup_inactive_warning": "ກະແຈຂອງທ່ານ <b>ບໍ່ຖືກສຳຮອງຂໍ້ມູນຈາກລະບົບນີ້</b>.",
             "message_search_disable_warning": "ຖ້າປິດໃຊ້ງານ, ຂໍ້ຄວາມຈາກຫ້ອງທີ່ເຂົ້າລະຫັດຈະບໍ່ປາກົດຢູ່ໃນຜົນການຄົ້ນຫາ.",
             "message_search_disabled": "ເກັບຮັກສາຂໍ້ຄວາມທີ່ຖືກເຂົ້າລະຫັດໄວ້ຢ່າງປອດໄພຢູ່ໃນເຄື່ອງເພື່ອໃຫ້ປາກົດໃນຜົນການຄົ້ນຫາ.",
             "message_search_enabled": {
@@ -2115,13 +2041,7 @@
             "message_search_space_used": "ພື້ນທີ່ໃຊ້ແລ້ວ:",
             "message_search_unsupported": "%(brand)s ຂາດບາງອົງປະກອບທີ່ຕ້ອງການສໍາລັບການເກັບຂໍ້ຄວາມເຂົ້າລະຫັດໄວ້ຢ່າງປອດໄພຢູ່ໃນເຄື່ອງ. ຖ້າທ່ານຕ້ອງການທົດລອງໃຊ້ຄຸນສົມບັດນີ້, ສ້າງ %(brand)s Desktop ແບບກຳນົດເອງດ້ວຍການເພີ່ມ <nativeLink>ອົງປະກອບການຄົ້ນຫາ</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s ບໍ່ສາມາດເກັບຂໍ້ຄວາມທີ່ຖືກເຂົ້າລະຫັດໄວ້ຢ່າງປອດໄພຢູ່ໃນເຄື່ອງ ໃນຂະນະທີ່ກຳລັງດຳເນີນການໃນເວັບບຣາວເຊີ. ໃຊ້ <desktopLink>%(brand)s Desktop</desktopLink> ເພື່ອໃຫ້ຂໍ້ຄວາມເຂົ້າລະຫັດຈະປາກົດໃນຜົນການຊອກຫາ.",
-            "restore_key_backup": "ກູ້ຄືນຈາກການສໍາຮອງຂໍ້ມູນ",
-            "secret_storage_not_ready": "ບໍ່ພ້ອມ",
-            "secret_storage_ready": "ພ້ອມ",
-            "secret_storage_status": "ການເກັບຮັກສາຄວາມລັບ:",
             "send_analytics": "ສົ່ງຂໍ້ມູນການວິເຄາະ",
-            "session_id": "ID ລະບົບ:",
-            "session_key": "ກະແຈລະບົບ:",
             "strict_encryption": "ບໍ່ສົ່ງຂໍ້ຄວາມເຂົ້າລະຫັດໄປຫາລະບົບທີ່ບໍ່ໄດ້ຢືນຢັນຈາກລະບົບນີ້"
         },
         "send_typing_notifications": "ສົ່ງການແຈ້ງເຕືອນການພິມ",
@@ -2248,8 +2168,6 @@
         "topic": "ໄດ້ຮັບ ຫຼື ກໍານົດຫົວຂໍ້ຫ້ອງ",
         "topic_none": "ຫ້ອງນີ້ບໍ່ມີຫົວຂໍ້.",
         "topic_room_error": "ໂຫຼດຫົວຂໍ້ຫ້ອງບໍ່ສຳເລັດ: ບໍ່ສາມາດຊອກຫາຫ້ອງ (%(roomId)s",
-        "tovirtual": "ສະຫຼັບໄປໃຊ້ຫ້ອງສະເໝືອນຈິງ, ຖ້າມີອີກຫ້ອງໜຶ່ງ",
-        "tovirtual_not_found": "ບໍ່ມີຫ້ອງສະເໝືອນຈິງສຳລັບຫ້ອງນີ້",
         "unban": "ຍົກເລີກການຫ້າມຜູ້ໃຊ້ທີ່ມີ ID",
         "unflip": "ນຳໜ້າ ┬──┬ ノ( ゜-゜ノ)ເປັນຂໍ້ຄວາມທຳມະດາ",
         "unholdcall": "ການຮັບສາຍໃນຫ້ອງປະຈຸບັນຖຶກປິດໄວ້",
@@ -2355,13 +2273,11 @@
         "heading_with_query": "ໃຊ້ \"%(query)s\" ເພື່ອຊອກຫາ",
         "join_button_text": "ເຂົ້າຮ່ວມ %(roomAddress)s",
         "keyboard_scroll_hint": "ໃຊ້ <arrows/> ເພື່ອເລື່ອນ",
-        "message_search_section_title": "ການຄົ້ນຫາອື່ນໆ",
         "other_rooms_in_space": "ຫ້ອງອື່ນໆ%(spaceName)s",
         "public_rooms_label": "ຫ້ອງສາທາລະນະ",
         "recent_searches_section_title": "ການຄົ້ນຫາທີ່ຜ່ານມາ",
         "recently_viewed_section_title": "ເບິ່ງເມື່ອບໍ່ດົນມານີ້",
         "search_dialog": "ຊອກຫາ ກ່ອງໂຕ້ຕອບ",
-        "search_messages_hint": "ເພື່ອຊອກຫາຂໍ້ຄວາມ, ຊອກຫາໄອຄອນນີ້ຢູ່ເທິງສຸດຂອງຫ້ອງ <icon/>",
         "spaces_title": "ຊ່ອງທີ່ທ່ານຢູ່"
     },
     "stickers": {
@@ -2606,7 +2522,9 @@
             "sent": "%(senderName)s ສົ່ງຄຳເຊີນໄປຫາ %(targetDisplayName)s ເພື່ອເຂົ້າຮ່ວມຫ້ອງ."
         },
         "m.room.tombstone": "%(senderDisplayName)s ຍົກລະດັບຫ້ອງນີ້ແລ້ວ.",
-        "m.room.topic": "%(senderDisplayName)s ໄດ້ປ່ຽນຫົວຂໍ້ເປັນ \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s ໄດ້ປ່ຽນຫົວຂໍ້ເປັນ \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s ສົ່ງສະຕິກເກີ.",
         "m.video": {
             "error_decrypting": "ການຖອດລະຫັດວິດີໂອຜິດພາດ"
@@ -2865,14 +2783,6 @@
         "ban_room_confirm_title": "ຫ້າມອອຈາກ %(roomName)s",
         "ban_space_everything": "ຫ້າມພວກເຂົາຈາກທຸກສິ່ງທີ່ຂ້ອຍສາມາດເຮັດໄດ້",
         "ban_space_specific": "ຫ້າມພວກເຂົາອອຈາກສິ່ງທີ່ສະເພາະທີ່ຂ້ອຍສາມາດເຮັດໄດ້",
-        "count_of_sessions": {
-            "one": "%(count)s ລະບົບ",
-            "other": "%(count)ssessions"
-        },
-        "count_of_verified_sessions": {
-            "one": "ຢືນຢັນ 1 session ແລ້ວ",
-            "other": "%(count)sລະບົບຢືນຢັນແລ້ວ"
-        },
         "deactivate_confirm_action": "ປິດໃຊ້ງານຜູ້ໃຊ້",
         "deactivate_confirm_description": "ການປິດໃຊ້ງານຜູ້ໃຊ້ນີ້ຈະອອກຈາກລະບົບ ແລະປ້ອງກັນບໍ່ໃຫ້ເຂົາເຈົ້າເຂົ້າສູ່ລະບົບຄືນອີກ. ນອກຈາກນັ້ນ, ເຂົາເຈົ້າຈະອອກຈາກຫ້ອງທັງໝົດທີ່ເຂົາເຈົ້າຢູ່ໃນ. ຄຳສັ່ງນີ້ບໍ່ສາມາດຍົກເລີກໄດ້. ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການປິດກາໃຊ້ງານຜູ້ໃຊ້ນີ້?",
         "deactivate_confirm_title": "ປິດໃຊ້ງານຜູ້ໃຊ້ບໍ?",
@@ -2883,15 +2793,12 @@
         "disinvite_button_room": "ຕັດຂາດອອກຈາກຫ້ອງ",
         "disinvite_button_room_name": "ຍົກເລີກເຊີນຈາກ %(roomName)s",
         "disinvite_button_space": "ຕັດຂາດຈາກ ພື້ນທີ່ຈັດເກັບ",
-        "edit_own_devices": "ແກ້ໄຂອຸປະກອນ",
         "error_ban_user": "ຫ້າມຜູ້ໃຊ້ບໍ່ສຳເລັດ",
         "error_deactivate": "ປິດໃຊ້ງານຜູ້ໃຊ້ບໍ່ສຳເລັດ",
         "error_kicking_user": "ລຶບຜູ້ໃຊ້ອອກບໍ່ສຳເລັດ",
         "error_mute_user": "ປິດສຽງຜູ້ໃຊ້ບໍ່ສຳເລັດ",
         "error_revoke_3pid_invite_description": "ບໍ່ສາມາດຖອນຄຳເຊີນໄດ້. ເຊີບເວີອາດຈະປະສົບບັນຫາຊົ່ວຄາວ ຫຼື ທ່ານບໍ່ມີສິດພຽງພໍໃນການຖອນຄຳເຊີນ.",
         "error_revoke_3pid_invite_title": "ຍົກເລີກການເຊີນບໍ່ສຳເລັດ",
-        "hide_sessions": "ເຊື່ອງsessions",
-        "hide_verified_sessions": "ເຊື່ອງ sessionsທີ່ຢືນຢັນແລ້ວ",
         "invited_by": "ເຊີນໂດຍ%(sender)s",
         "jump_to_rr_button": "ຂ້າມເພື່ອອ່ານໃບຮັບເງິນ",
         "kick_button_room": "ຍ້າຍອອກຈາກຫ້ອງ",
@@ -2971,7 +2878,6 @@
         "hangup": "ວາງສາຍ",
         "hide_sidebar_button": "ເຊື່ອງແຖບດ້ານຂ້າງ",
         "input_devices": "ອຸປະກອນຂາເຂົ້າ",
-        "join_button_tooltip_connecting": "ກຳລັງເຊື່ອມຕໍ່",
         "misconfigured_server": "ການໂທບໍ່ສຳເລັດເນື່ອງຈາກເຊີບເວີຕັ້ງຄ່າຜິດພາດ",
         "misconfigured_server_description": "ກະລຸນາຕິດຕໍ່ຜູ້ຄຸ້ມຄອງສະຖານີຂອງທ່ານ (<code>%(homeserverDomain)s</code>) ເພື່ອກໍານົດຄ່າຂອງ TURN Server ເພື່ອໃຫ້ການໂທເຮັດວຽກໄດ້ຢ່າງສະຖຽນ.",
         "more_button": "ເພີ່ມເຕີມ",
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index 7e568f3360cf0050b1ff9e5f964daee9317a9cf0..101e8fbc0c8f8be4b3d212f038ff09645e8fe876 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -58,7 +58,7 @@
         "invites_list": "Pakvietimai",
         "join": "Prisijungti",
         "learn_more": "Sužinokite daugiau",
-        "leave": "Išeiti",
+        "leave": "Palikti",
         "leave_room": "Išeiti iš kambario",
         "logout": "Atsijungti",
         "manage": "Tvarkyti",
@@ -75,7 +75,6 @@
         "react": "Reaguoti",
         "refresh": "Įkelti iš naujo",
         "register": "Registruotis",
-        "reject": "Atmesti",
         "remove": "Pašalinti",
         "rename": "Pervadinti",
         "reply": "Atsakyti",
@@ -253,7 +252,6 @@
         "download_logs": "Parsisiųsti žurnalus",
         "downloading_logs": "Parsiunčiami žurnalai",
         "error_empty": "Pasakyite mums kas nutiko, arba, dar geriau, sukurkite GitHub problemą su jos apibūdinimu.",
-        "failed_send_logs": "Nepavyko išsiųsti žurnalų: ",
         "github_issue": "GitHub problema",
         "introduction": "Jei per GitHub pateikėte klaidą, derinimo žurnalai gali padėti mums nustatyti problemą. ",
         "log_request": "Norėdami padėti mums išvengti to ateityje, <a>atsiųskite mums žurnalus</a>.",
@@ -290,7 +288,6 @@
     "common": {
         "access_token": "Prieigos žetonas",
         "advanced": "Išplėstiniai",
-        "all_rooms": "Visi kambariai",
         "analytics": "Analitika",
         "and_n_others": {
             "other": "ir %(count)s kitų...",
@@ -303,7 +300,6 @@
         "camera": "Kamera",
         "copied": "Nukopijuota!",
         "credits": "Padėka",
-        "cross_signing": "Kryžminis pasirašymas",
         "dark": "Tamsi",
         "description": "Aprašas",
         "deselect_all": "Nuimti pasirinkimą nuo visko",
@@ -368,7 +364,6 @@
         "room": "Kambarys",
         "rooms": "Kambariai",
         "secure_backup": "Saugi Atsarginė Kopija",
-        "security": "Saugumas",
         "select_all": "Pasirinkti viską",
         "settings": "Nustatymai",
         "show_more": "Rodyti daugiau",
@@ -382,7 +377,6 @@
         "theme": "Tema",
         "threads": "Temos",
         "timeline": "Laiko juosta",
-        "trusted": "Patikimas",
         "unencrypted": "Neužšifruota",
         "unmute": "Atšaukti nutildymą",
         "unnamed_room": "Bevardis Kambarys",
@@ -505,41 +499,22 @@
     "encryption": {
         "access_secret_storage_dialog": {
             "key_validation_text": {
-                "invalid_security_key": "Klaidingas Saugumo Raktas",
-                "recovery_key_is_correct": "Atrodo gerai!",
-                "wrong_file_type": "Netinkamas failo tipas",
                 "wrong_security_key": "Netinkamas Saugumo Raktas"
             },
-            "reset_title": "Iš naujo nustatyti viską",
-            "reset_warning_1": "Taip darykite tik tuo atveju, jei neturite kito prietaiso, kuriuo galėtumėte užbaigti patikrinimą.",
-            "reset_warning_2": "Jei viską nustatysite iš naujo, paleisite iš naujo be patikimų seansų, be patikimų vartotojų ir galbūt negalėsite matyti ankstesnių žinučių.",
             "restoring": "Raktų atkūrimas iš atsarginės kopijos",
-            "security_key_title": "Saugumo Raktas",
-            "security_phrase_incorrect_error": "Nepavyksta pasiekti slaptosios saugyklos. Prašome patvirtinti kad teisingai įvedėte Saugumo Frazę.",
-            "security_phrase_title": "Slaptafrazė",
-            "use_security_key_prompt": "Naudokite Saugumo Raktą kad tęsti."
+            "security_key_title": "Saugumo Raktas"
         },
         "bootstrap_title": "Raktų nustatymas",
         "cancel_entering_passphrase_description": "Ar tikrai norite atšaukti slaptafrazės įvedimą?",
         "cancel_entering_passphrase_title": "Atšaukti slaptafrazės įvedimą?",
         "confirm_encryption_setup_body": "Paspauskite mygtuką žemiau, kad patvirtintumėte šifravimo nustatymą.",
         "confirm_encryption_setup_title": "Patvirtinti šifravimo sąranką",
-        "cross_signing_not_ready": "Kryžminis pasirašymas nenustatytas.",
-        "cross_signing_ready": "Kryžminis pasirašymas yra paruoštas naudoti.",
-        "cross_signing_ready_no_backup": "Kryžminis pasirašymas paruoštas, tačiau raktai neturi atsarginės kopijos.",
         "cross_signing_room_normal": "Šis kambarys visapusiškai užšifruotas",
         "cross_signing_room_verified": "Visi šiame kambaryje yra patvirtinti",
         "cross_signing_room_warning": "Kažkas naudoja nežinomą seansą",
-        "cross_signing_unsupported": "Jūsų serveris nepalaiko kryžminio pasirašymo.",
-        "cross_signing_untrusted": "Jūsų paskyra slaptoje saugykloje turi kryžminio pasirašymo tapatybę, bet šis seansas dar ja nepasitiki.",
         "cross_signing_user_normal": "Jūs nepatvirtinote šio vartotojo.",
         "cross_signing_user_verified": "Jūs patvirtinote šį vartotoją. Šis vartotojas patvirtino visus savo seansus.",
         "cross_signing_user_warning": "Šis vartotojas nepatvirtino visų savo seansų.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Valyti kryžminio pasirašymo raktus",
-            "title": "Sunaikinti kryžminio pasirašymo raktus?",
-            "warning": "Kryžminio pasirašymo raktų ištrinimas yra neatšaukiamas. Visi, kurie buvo jais patvirtinti, matys saugumo įspėjimus. Jūs greičiausiai nenorite to daryti, nebent praradote visus įrenginius, iš kurių galite patvirtinti kryžminiu pasirašymu."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "Šiame įrenginyje negalima užtikrinti šios užšifruotos žinutės autentiškumo.",
         "event_shield_reason_mismatched_sender_key": "Užšifruota nepatvirtinto seanso",
         "export_unsupported": "Jūsų naršyklė nepalaiko reikalingų kriptografijos plėtinių",
@@ -558,7 +533,6 @@
             "title": "Naujas atgavimo metodas",
             "warning": "Jei jūs nenustatėte naujo paskyros atgavimo metodo, gali būti, kad užpuolikas bando patekti į jūsų paskyrą. Nedelsiant nustatymuose pakeiskite savo paskyros slaptažodį ir nustatykite naują atgavimo metodą."
         },
-        "not_supported": "<nepalaikoma>",
         "recovery_method_removed": {
             "description_2": "Jei tai padarėte netyčia, šiame seanse galite nustatyti saugias žinutes, kurios pakartotinai užšifruos šio seanso žinučių istoriją nauju atgavimo metodu.",
             "title": "Atgavimo Metodas Pašalintas",
@@ -568,8 +542,7 @@
         "set_up_toast_description": "Apsisaugokite nuo prieigos prie šifruotų žinučių ir duomenų praradimo",
         "set_up_toast_title": "Nustatyti Saugią Atsarginę Kopiją",
         "setup_secure_backup": {
-            "explainer": "Prieš atsijungdami sukurkite atsarginę savo raktų kopiją, kad išvengtumėte jų praradimo.",
-            "title": "Nustatyti"
+            "explainer": "Prieš atsijungdami sukurkite atsarginę savo raktų kopiją, kad išvengtumėte jų praradimo."
         },
         "udd": {
             "other_ask_verify_text": "Paprašykite šio vartotojo patvirtinti savo seansą, arba patvirtinkite jį rankiniu būdu žemiau.",
@@ -579,7 +552,6 @@
             "title": "Nepatikimas"
         },
         "unable_to_setup_keys_error": "Nepavyksta nustatyti raktų",
-        "unsupported": "Šis klientas nepalaiko visapusio šifravimo.",
         "verification": {
             "accepting": "Priimama…",
             "cancelled": "Jūs atšaukėte patvirtinimą.",
@@ -679,11 +651,7 @@
             "description": "Nepavyko nukopijuoti nuorodos į kambarį į iškarpinę.",
             "title": "Nepavyko nukopijuoti kambario nurodos"
         },
-        "forget_room_failed": "Nepavyko pamiršti kambario %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Gali būti, kad serveris neprieinamas, perkrautas arba pasibaigė paieškai skirtas laikas :(",
-            "title": "Paieška nepavyko"
-        }
+        "forget_room_failed": "Nepavyko pamiršti kambario %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1000,11 +968,6 @@
         "ongoing": "Pašalinama…",
         "reason_label": "Priežastis (nebūtina)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Ar tikrai norite atmesti pakvietimą?",
-        "failed": "Nepavyko atmesti pakvietimo",
-        "title": "Atmesti pakvietimą"
-    },
     "report_content": {
         "description": "Pranešant apie šią netinkamą žinutę, serverio administratoriui bus nusiųstas unikalus 'įvykio ID'. Jei žinutės šiame kambaryje yra šifruotos, serverio administratorius negalės perskaityti žinutės teksto ar peržiūrėti failų arba paveikslėlių.",
         "missing_reason": "Įrašykite kodėl pranešate.",
@@ -1100,7 +1063,6 @@
             "you_created": "Jūs sukūrėte šį kambarį."
         },
         "invite_email_mismatch_suggestion": "Bendrinkite šį el. pašto adresą nustatymuose, kad gautumėte kvietimus tiesiai į %(brand)s.",
-        "invite_reject_ignore": "Atmesti ir ignoruoti vartotoją",
         "invite_sent_to_email": "Šis kvietimas buvo išsiųstas į %(email)s",
         "invite_sent_to_email_room": "Šis kvietimas į %(roomName)s buvo išsiųstas į %(email)s",
         "invite_subtitle": "<userName/> jus pakvietė",
@@ -1513,7 +1475,6 @@
             "remove_email_prompt": "Pašalinti %(email)s?",
             "remove_msisdn_prompt": "Pašalinti %(phone)s?"
         },
-        "image_thumbnails": "Rodyti vaizdų peržiūras/miniatiūras",
         "inline_url_previews_default": "Įjungti URL nuorodų peržiūras kaip numatytasias",
         "inline_url_previews_room": "Įjungti URL nuorodų peržiūras kaip numatytasias šiame kambaryje esantiems dalyviams",
         "inline_url_previews_room_account": "Įjungti URL nuorodų peržiūras šiame kambaryje (įtakoja tik jus)",
@@ -1603,48 +1564,16 @@
         "prompt_invite": "Klausti prieš siunčiant pakvietimus galimai netinkamiems matrix ID",
         "replace_plain_emoji": "Automatiškai pakeisti paprasto teksto Jaustukus",
         "security": {
-            "4s_public_key_in_account_data": "paskyros duomenyse",
-            "4s_public_key_status": "Slaptos saugyklos viešas raktas:",
-            "backup_key_cached_status": "Atsarginis raktas išsaugotas talpykloje:",
-            "backup_key_stored_status": "Atsarginis raktas saugomas:",
-            "backup_key_unexpected_type": "netikėto tipo",
-            "backup_key_well_formed": "gerai suformuotas",
-            "backup_keys_description": "Pasidarykite šifravimo raktų ir paskyros duomenų atsarginę kopiją, jei prarastumėte prieigą prie sesijų. Jūsų raktai bus apsaugoti unikaliu saugumo raktu.",
             "bulk_options_accept_all_invites": "Priimti visus %(invitedRooms)s pakvietimus",
             "bulk_options_reject_all_invites": "Atmesti visus %(invitedRooms)s pakvietimus",
             "bulk_options_section": "Grupinės parinktys",
-            "cross_signing_cached": "lokaliame podėlyje",
-            "cross_signing_homeserver_support": "Serverio funkcijų palaikymas:",
-            "cross_signing_homeserver_support_exists": "yra",
-            "cross_signing_in_4s": "slaptoje saugykloje",
-            "cross_signing_in_memory": "atmintyje",
-            "cross_signing_master_private_Key": "Pagrindinis privatus raktas:",
-            "cross_signing_not_cached": "lokaliai nerasta",
-            "cross_signing_not_found": "nerasta",
-            "cross_signing_not_in_4s": "saugykloje nerasta",
-            "cross_signing_not_stored": "nesaugomas",
-            "cross_signing_private_keys": "Kryžminio pasirašymo privatūs raktai:",
-            "cross_signing_public_keys": "Kryžminio pasirašymo vieši raktai:",
-            "cross_signing_self_signing_private_key": "Savarankiško pasirašymo privatus raktas:",
-            "cross_signing_user_signing_private_key": "Vartotojo pasirašymo privatus raktas:",
-            "cryptography_section": "Kriptografija",
-            "delete_backup": "Ištrinti Atsarginę Kopiją",
-            "delete_backup_confirm_description": "Ar tikrai? Jūs prarasite savo šifruotas žinutes, jei jūsų raktams nebus tinkamai sukurtos atsarginės kopijos.",
             "e2ee_default_disabled_warning": "Serverio administratorius išjungė visapusį šifravimą, kaip numatytą, privačiuose kambariuose ir Tiesioginėse Žinutėse.",
             "enable_message_search": "Įjungti žinučių paiešką užšifruotuose kambariuose",
             "encryption_section": "Šifravimas",
-            "error_loading_key_backup_status": "Nepavyko įkelti atsarginės raktų kopijos būklės",
-            "export_megolm_keys": "Eksportuoti E2E (visapusio šifravimo) kambarių raktus",
             "ignore_users_empty": "Nėra ignoruojamų naudotojų.",
             "ignore_users_section": "Ignoruojami vartotojai",
-            "import_megolm_keys": "Importuoti E2E (visapusio šifravimo) kambarių raktus",
-            "key_backup_active_version_none": "Nė vienas",
             "key_backup_algorithm": "Algoritmas:",
-            "key_backup_complete": "Atsarginės kopijos sukurtos visiems raktams",
             "key_backup_connect": "Prijungti šį seansą prie Atsarginės Raktų Kopijos",
-            "key_backup_connect_prompt": "Prieš atsijungdami prijunkite šį seansą prie atsarginės raktų kopijos, kad neprarastumėte raktų, kurie gali būti tik šiame seanse.",
-            "key_backup_inactive": "Šis seansas <b>nekuria atsarginių raktų kopijų</b>, bet jūs jau turite atsarginę kopiją iš kurios galite atkurti ir pridėti.",
-            "key_backup_inactive_warning": "Jūsų raktams <b>nėra daromos atsarginės kopijos iš šio seanso</b>.",
             "message_search_disabled": "Šifruotas žinutes saugiai talpinkite lokaliai, kad jos būtų rodomos paieškos rezultatuose.",
             "message_search_enabled": {
                 "one": "Saugiai talpinkite užšifruotas žinutes vietoje, kad jos būtų rodomos paieškos rezultatuose, naudojant %(size)s žinutėms iš %(rooms)s kambario saugoti.",
@@ -1655,13 +1584,7 @@
             "message_search_sleep_time": "Kaip greitai žinutės turi būti parsiųstos.",
             "message_search_unsupported": "%(brand)s trūksta kai kurių komponentų, reikalingų saugiai talpinti šifruotas žinutes lokaliai. Jei norite eksperimentuoti su šia funkcija, sukurkite pasirinktinį %(brand)s Desktop (darbastalio versiją), su <nativeLink>pridėtais paieškos komponentais</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s negali saugiai talpinti šifruotų žinučių lokaliai, kai veikia interneto naršyklėje. Naudokite <desktopLink>%(brand)s Desktop (darbastalio versija)</desktopLink>, kad šifruotos žinutės būtų rodomos paieškos rezultatuose.",
-            "restore_key_backup": "Atkurti iš Atsarginės Kopijos",
-            "secret_storage_not_ready": "neparuošta",
-            "secret_storage_ready": "paruošta",
-            "secret_storage_status": "Slapta saugykla:",
             "send_analytics": "Siųsti analitinius duomenis",
-            "session_id": "Seanso ID:",
-            "session_key": "Seanso raktas:",
             "strict_encryption": "Niekada nesiųsti šifruotų žinučių nepatvirtintiems seansams iš šio seanso"
         },
         "send_read_receipts": "Siųsti skaitymo kvitus",
@@ -2062,7 +1985,9 @@
             "sent": "%(senderName)s išsiuntė pakvietimą %(targetDisplayName)s prisijungti prie kambario."
         },
         "m.room.tombstone": "%(senderDisplayName)s atnaujino šį kambarį.",
-        "m.room.topic": "%(senderDisplayName)s pakeitė temą į \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s pakeitė temą į \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s nusiuntė lipduką.",
         "m.video": {
             "error_decrypting": "Klaida iššifruojant vaizdo įrašą"
@@ -2271,14 +2196,6 @@
         "ban_room_confirm_title": "Atblokuoti už %(roomName)s",
         "ban_space_everything": "Užblokuoti juos iš visko, kur galiu",
         "ban_space_specific": "Užblokuoti juos konkrečiuose dalykuose, kuriuose galiu",
-        "count_of_sessions": {
-            "other": "%(count)s seansai(-ų)",
-            "one": "%(count)s seansas"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s patvirtintų seansų",
-            "one": "1 patvirtintas seansas"
-        },
         "deactivate_confirm_action": "Deaktyvuoti vartotoją",
         "deactivate_confirm_description": "Šio vartotojo deaktyvavimas atjungs juos ir neleis jiems vėl prisijungti atgal. Taip pat jie išeis iš visų kambarių, kuriuose jie yra. Šis veiksmas negali būti atšauktas. Ar tikrai norite deaktyvuoti šį vartotoją?",
         "deactivate_confirm_title": "Deaktyvuoti vartotoją?",
@@ -2289,15 +2206,12 @@
         "disinvite_button_room": "Atšaukti kvietimą iš kambario",
         "disinvite_button_room_name": "Atšaukti kvietimą iš %(roomName)s",
         "disinvite_button_space": "Atšaukti kvietimą iš erdvės",
-        "edit_own_devices": "Redaguoti įrenginius",
         "error_ban_user": "Nepavyko užblokuoti vartotojo",
         "error_deactivate": "Nepavyko deaktyvuoti vartotojo",
         "error_kicking_user": "Nepavyko pašalinti naudotojo",
         "error_mute_user": "Nepavyko nutildyti vartotojo",
         "error_revoke_3pid_invite_description": "Kvietimo atšaukti nepavyko. Gali būti, kad serveryje kilo laikina problema arba neturite pakankamų leidimų atšaukti kvietimą.",
         "error_revoke_3pid_invite_title": "Nepavyko atšaukti kvietimo",
-        "hide_sessions": "Slėpti seansus",
-        "hide_verified_sessions": "Slėpti patvirtintus seansus",
         "invited_by": "Pakvietė %(sender)s",
         "jump_to_rr_button": "Nušokti iki perskaitytų žinučių",
         "kick_button_room": "Pašalinti iš kambario",
@@ -2367,7 +2281,6 @@
         "expand": "Grįžti prie skambučio",
         "hangup": "Padėti ragelį",
         "hide_sidebar_button": "Slėpti šoninę juostą",
-        "join_button_tooltip_connecting": "Jungiamasi",
         "misconfigured_server": "Skambutis nepavyko dėl neteisingai sukonfigūruoto serverio",
         "misconfigured_server_description": "Paprašykite savo serverio administratoriaus (<code>%(homeserverDomain)s</code>) sukonfiguruoti TURN serverį, kad skambučiai veiktų patikimai.",
         "more_button": "Daugiau",
diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json
new file mode 100644
index 0000000000000000000000000000000000000000..739e9a81b0ac278666df9edde4e85062fc0a586d
--- /dev/null
+++ b/src/i18n/strings/lv.json
@@ -0,0 +1,3643 @@
+{
+    "a11y": {
+        "emoji_picker": "Emocijzīmju atlasītājs",
+        "jump_first_invite": "Pārlēkt uz pirmo uzaicinājumu.",
+        "message_composer": "Ziņas sastādītājs",
+        "n_unread_messages": {
+            "one": "1 nelasīta ziņa.",
+            "other": "%(count)s nelasītas ziņas."
+        },
+        "n_unread_messages_mentions": {
+            "zero": "%(count)s nelasītu ziņu ar pieminēšanu",
+            "one": "1 neslasīta ziņa ar pieminēšanu.",
+            "other": "%(count)s nelasītas ziņas ar pieminēšanu."
+        },
+        "recent_rooms": "Nesenās istabas",
+        "room_name": "Istaba %(name)s",
+        "room_status_bar": "Istabas statusa josla",
+        "seek_bar_label": "Audio meklēšanas josla",
+        "unread_messages": "Nelasītās ziņas.",
+        "user_menu": "Lietotāja izvēlne"
+    },
+    "a11y_jump_first_unread_room": "Pāriet uz pirmo nelasīto istabu.",
+    "action": {
+        "accept": "Apstiprināt",
+        "add": "Pievienot",
+        "add_existing_room": "Pievienot esošu istabu",
+        "add_people": "Pievienot cilvēkus",
+        "apply": "Pielietot",
+        "approve": "Apstiprināt",
+        "ask_to_join": "Aicināt pievienoties",
+        "back": "Atpakaļ",
+        "call": "Zvanīt",
+        "cancel": "Atcelt",
+        "change": "Mainīt",
+        "clear": "Notīrīt",
+        "click": "Noklikšķiniet",
+        "click_to_copy": "Noklikšķiniet, lai kopētu",
+        "close": "Aizvērt",
+        "collapse": "Sakļaut",
+        "complete": "Pabeigt",
+        "confirm": "Apstiprināt",
+        "continue": "Turpināt",
+        "copy": "Kopēt",
+        "copy_link": "Kopēt saiti",
+        "create": "Izveidot",
+        "create_a_room": "Izveidot istabu",
+        "create_account": "Izveidot kontu",
+        "decline": "Noraidīt",
+        "delete": "Izdzēst",
+        "deny": "Atteikt",
+        "disable": "Atspējot",
+        "disconnect": "Atvienoties",
+        "dismiss": "Atmest",
+        "done": "Gatavs",
+        "download": "Lejupielādēt",
+        "edit": "Labot",
+        "enable": "Iespējot",
+        "enter_fullscreen": "Atvērt pilnekrāna režīmā",
+        "exit_fullscreeen": "Iziet no pilnekrāna režīma",
+        "expand": "Izvērst",
+        "explore_public_rooms": "Pārlūkot publiskas istabas",
+        "explore_rooms": "Pārlūkot istabas",
+        "export": "Eksportēt",
+        "forward": "Pārsūtīt",
+        "go": "Aiziet",
+        "go_back": "Atgriezties",
+        "got_it": "Sapratu",
+        "hide_advanced": "Slēpt papildu iestatījumus",
+        "hold": "Turiet",
+        "ignore": "Neņemt vērā",
+        "import": "Importēt",
+        "invite": "Uzaicināt",
+        "invite_to_space": "Uzaicināt uz telpu",
+        "invites_list": "Uzaicinājumi",
+        "join": "Pievienoties",
+        "learn_more": "Uzzināt vairāk",
+        "leave": "Pamest",
+        "leave_room": "Pamest istabu",
+        "logout": "Izrakstīties",
+        "manage": "Pārvaldīt",
+        "maximise": "Palielināt pa visu ekrānu",
+        "mention": "Pieminēt",
+        "minimise": "Samazināt",
+        "new_room": "Jauna istaba",
+        "new_video_room": "Jauna video istaba",
+        "next": "Nākamais",
+        "no": "Nē",
+        "ok": "Labi",
+        "open": "Atvērt",
+        "pause": "Pauzēt",
+        "pin": "Piespraust",
+        "play": "Atskaņot",
+        "proceed": "Turpināt",
+        "quote": "Citēt",
+        "react": "Reaģēt",
+        "refresh": "Atsvaidzināt",
+        "register": "Reģistrēties",
+        "reload": "Pārlādēt",
+        "remove": "Dzēst",
+        "rename": "Pārdēvēt",
+        "reply": "Atbildēt",
+        "reply_in_thread": "Atbildēt pavedienā",
+        "report_content": "Ziņot par saturu",
+        "resend": "Nosūtīt atkārtoti",
+        "reset": "Atiestatīt",
+        "resume": "Turpināt",
+        "retry": "Mēģināt vēlreiz",
+        "review": "Pārlūkot",
+        "revoke": "Atsaukt",
+        "save": "Saglabāt",
+        "search": "Meklēt",
+        "send_report": "Nosūtīt ziņojumu",
+        "set_avatar": "Iestatīt profila attēlu",
+        "share": "Dalīties",
+        "show": "Rādīt",
+        "show_advanced": "Rādīt papildu iestatījumus",
+        "show_all": "Rādīt visu",
+        "sign_in": "Pieteikties",
+        "sign_out": "Izrakstīties",
+        "skip": "Izlaist",
+        "start": "Sākt",
+        "start_chat": "Uzsākt tērzēšanu",
+        "start_new_chat": "Uzsākt jaunu tērzēšanu",
+        "stop": "Apturēt",
+        "submit": "Iesniegt",
+        "subscribe": "Abonēt",
+        "transfer": "Pārsūtīt",
+        "trust": "Uzticamība",
+        "try_again": "Mēģiniet vēlreiz",
+        "unban": "Atcelt pieejas liegumu",
+        "unignore": "Atcelt neņemšanu vērā",
+        "unpin": "Atspraust",
+        "unsubscribe": "Atcelt abonementu",
+        "update": "Atjaunināt",
+        "upgrade": "Uzlabot",
+        "upload": "Augšupielādēt",
+        "upload_file": "Augšupielādēt failu",
+        "verify": "Apliecināt",
+        "view": "Skats",
+        "view_all": "Skatīt visu",
+        "view_list": "Skatīt sarakstu",
+        "view_message": "Skatīt ziņu",
+        "view_source": "Skatīt pirmkodu",
+        "yes": "Jā",
+        "zoom_in": "Pietuvināt",
+        "zoom_out": "Attālināt"
+    },
+    "analytics": {
+        "accept_button": "Tas ir labi",
+        "bullet_1": "Mēs <Bold>nesaglabājam</Bold> vai neprofilējam nekādus kontu datus",
+        "bullet_2": "Mēs <Bold>nekopīgojam</Bold> informāciju ar trešajām pusēm",
+        "consent_migration": "Tu iepriekš piekriti kopīgot ar mums anonīmus izmantošanas datus. Mēs atjauninām, kas tas darbojas.",
+        "disable_prompt": "Šo jebkurā brīdī var izslēgt iestatījumos",
+        "enable_prompt": "Palīdzi uzlabot %(analyticsOwner)s",
+        "learn_more": "Kopīgo anonīmus datus, lai palīdzētu mums noteikt nepilnības. Nekā personīga. Bez trešajām pusēm. <LearnMoreLink>Uzzināt vairāk</LearnMoreLink>",
+        "privacy_policy": "Visus mūsu nosacījumus var izlasīt <PrivacyPolicyUrl>šeit</PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "Palīdzi mums atklāt nepilnības un uzlabot %(analyticsOwner)s, kopīgojot anonīmus izmantošanas datus. Lai saprastu, kā cilvēki izmanto vairākas ierīces, mēs izveidojam nejaušu identifikatoru, kas ir kopīgs Tavās ierīcēs.",
+        "shared_data_heading": "Jebkas no šiem datiem var tikt kopīgots:"
+    },
+    "auth": {
+        "3pid_in_use": "Šī e-pasta adrese vai tālruņa numurs jau tiek izmantots.",
+        "account_clash": "Tavs jaunais konts (%(newAccountId)s) ir izveidots, bet Tu jau esi pierakstījies citā kontā (%(loggedInUserId)s).",
+        "account_clash_previous_account": "Turpināt ar iepriekšējo kontu",
+        "account_deactivated": "Šis konts ir deaktivizēts.",
+        "autodiscovery_generic_failure": "Neizdevās no servera iegūt automātiskās atklāšanas konfigurāciju",
+        "autodiscovery_hs_incompatible": "Mājasserveris ir pārāk vecs un neatbalsta mazāko nepieciešamo API versiju. Lūgums sazināties ar servera īpašnieku vai atjaunināt savu serveri.",
+        "autodiscovery_invalid": "Nederīga mājasservera atklāšanas atbilde",
+        "autodiscovery_invalid_hs": "Neizskatās, ka mājasservera URL būtu derīgs Matrix mājasserveris",
+        "autodiscovery_invalid_hs_base_url": "Nederīgs m.homeserver base_url",
+        "autodiscovery_invalid_is": "Neizskatās, ka identitāšu servera URL būtu derīgam identitāšu serverim",
+        "autodiscovery_invalid_is_base_url": "Nederīgs base_url m.identity_server",
+        "autodiscovery_invalid_is_response": "Nederīga identitāšu server atklāšanas atbilde",
+        "autodiscovery_invalid_json": "Nederīgs JSON",
+        "autodiscovery_no_well_known": "Nav atrasta .well-known JSON datne",
+        "autodiscovery_unexpected_error_hs": "Negaidīta kļūme mājasservera konfigurācijā",
+        "autodiscovery_unexpected_error_is": "Negaidīta kļūda identitātes servera konfigurācijā",
+        "captcha_description": "Šis mājasserveris vēlas pārliecināties, ka neesi robots.",
+        "change_password_action": "Nomainīt paroli",
+        "change_password_confirm_invalid": "Paroles nesakrīt",
+        "change_password_confirm_label": "Apstiprināt paroli",
+        "change_password_current_label": "Pašreizējā parole",
+        "change_password_empty": "Paroles nevar būt tukšas",
+        "change_password_error": "Kļūda paroles mainīšanas laikā: %(error)s",
+        "change_password_mismatch": "Jaunās paroles nesakrīt",
+        "change_password_new_label": "Jaunā parole",
+        "check_email_explainer": "Jāseko norādēm, kas tika nosūtītas uz <b>%(email)s</b>",
+        "check_email_resend_prompt": "Nesaņēmi?",
+        "check_email_resend_tooltip": "E-pasta ziņojums ar apstiprinājuma saiti tika nosūtīts atkārtoti.",
+        "check_email_wrong_email_button": "Atkārtoti jāievada e-pasta adrese",
+        "check_email_wrong_email_prompt": "Nepareiza e-pasta adrese?",
+        "continue_with_idp": "Turpināt ar %(provider)s",
+        "continue_with_sso": "Turpināt ar %(ssoButtons)s",
+        "country_dropdown": "Valstu nolaižamā izvēlne",
+        "create_account_prompt": "Pirmo reizi šeit? <a>Izveidot kontu</a>",
+        "create_account_title": "Izveidot kontu",
+        "email_discovery_text": "E-pasta adrese ir izmantojama, lai pēc izvēles ļaut esošajām kontaktpersonām sevi atrast.",
+        "email_field_label": "Epasts",
+        "email_field_label_invalid": "Neizskatās pēc derīgas e-pasta adreses",
+        "email_field_label_required": "Ievadiet epasta adresi",
+        "email_help_text": "Pievienojiet epasta adresi, lai varētu atiestatīt paroli.",
+        "email_phone_discovery_text": "E-pasta adrese vai tālruņa numurs ir izmantojams, lai pēc izvēles ļautu esošajām kontaktpersonām sevi atrast.",
+        "enter_email_explainer": "<b>%(homeserver)s</b> nosūtīts apstirpinājuma saiti, lai ļautu atiestatīt paroli.",
+        "enter_email_heading": "Jāievada sava e-pasta adrese, lai atiestatītu paroli",
+        "failed_connect_identity_server": "Neizdodas sasniegt identitāšu serveri",
+        "failed_connect_identity_server_other": "Jūs varat pierakstīties, taču dažas funkcijas nebūs pieejamas, kamēr nebūs pieejams identitāšu serveris. Ja arī turpmāk redzat šo brīdinājumu, lūdzu, pārbaudiet konfigurāciju vai sazinieties ar servera administratoru.",
+        "failed_connect_identity_server_register": "Var reģistrēties, bet dažas iespējas nebūs pieejamas, līdz identitātes serveris atkal būs tiešsaistē. Ja šis paziņojums ir joprojām redzams, jāpārbauda konfigurācija vai jāsazinās ar servera pārvaldnieku.",
+        "failed_connect_identity_server_reset_password": "Var attiestatīt savu paroli, bet dažas iespējas nebūs pieejamas, līdz identitātes serveris būs atpakaļ tiešsaistē. Ja šis brīdinājums ir joprojām ir redzams, jāpārbauda konfigurācija vai jāsazinās ar servera pārvaldnieku.",
+        "failed_homeserver_discovery": "Neizdevās veikt mājasservera atklāšanu",
+        "failed_query_registration_methods": "Neizdevās pieprasīt atbalstītos reģistrācijas veidus.",
+        "failed_soft_logout_auth": "Neizdevās atkārtoti autentificēties",
+        "failed_soft_logout_homeserver": "Neizdevās atkārtoti autentificēties sarežģījuma mājasserverī dēļ",
+        "forgot_password_email_invalid": "E-pasta adrese neizskatās derīga",
+        "forgot_password_email_required": "Ir jāievada savam kontam piesaistītā epasta adrese.",
+        "forgot_password_prompt": "Aizmirsāt paroli?",
+        "forgot_password_send_email": "Nosūtīt e-pasta ziņu",
+        "identifier_label": "Pieteikties ar",
+        "incorrect_credentials": "Nepareizs lietotājvārds un/vai parole.",
+        "incorrect_credentials_detail": "Lūdzu, ņemiet vērā, ka jūs piesakāties %(hs)s serverī, nevis matrix.org.",
+        "incorrect_password": "Nepareiza parole",
+        "log_in_new_account": "<a>Pieteikties</a> jaunajā kontā.",
+        "logout_dialog": {
+            "description": "Vai tiešām izrakstīties?",
+            "megolm_export": "Pašrocīgi izgūt atslēgas",
+            "setup_key_backup_title": "Tu zaudēsi piekļuvi savām šifrētajām ziņām",
+            "setup_secure_backup_description_1": "Šifrētas ziņas tiek nodrošinātas ar pilnīgu šifrēšanu. Tikai Tev un saņēmējam(iem) ir atslēgas šo ziņojumu lasīšanai.",
+            "setup_secure_backup_description_2": "Kad Tu atteiksies, šīs atslēgas tiks izdzēstas no šīs ierīces, kas nozīmē, ka Tu nevarēsi lasīt šifrētas ziņas, līdz Tev būs to atslēgas citās ierīcēs vai serverī izveidosi to rezerves kopiju.",
+            "skip_key_backup": "Es nevēlos savas šifrētās ziņas",
+            "use_key_backup": "Sākt izmantot atslēgu rezerves kopiju veidošanu"
+        },
+        "misconfigured_body": "Jāvaicā savam %(brand)s pārvaldniekam pārbaudīt, vai <a>konfigurācijā</a> ir nepareizu vai atkārtojošos ierakstu",
+        "misconfigured_title": "%(brand)s ir nepareizi konfigurēts",
+        "mobile_create_account_title": "Tu gatavojies izveidot kontu %(hsName)s",
+        "msisdn_field_description": "Citi lietotāji var uzaicināt uz istabām, izmantojot Tavu kontaktinformāciju",
+        "msisdn_field_label": "Telefons",
+        "msisdn_field_number_invalid": "Šis tālruņa numurs neizskatās pareizs. Lūdzu, pārbaudiet un mēģiniet vēlreiz",
+        "msisdn_field_required_invalid": "Ievadiet tālruņa numuru",
+        "no_hs_url_provided": "Nav norādīts mājasservera URL",
+        "oidc": {
+            "error_title": "Neizdevās Tevi pieteikt",
+            "generic_auth_error": "Kaut kas nogāja greizi autentificēšanās laikā. Jādodas uz pieteikšanās lapu un jāmēģina vēlreiz.",
+            "missing_or_invalid_stored_state": "Mēs vaicājām pārlūkam atcerēties, kuru mājasserveri izmantot, lai ļautu pieteikties, bet diemžēl pārlūks ir to aizmirsis. Jādodas uz pieteikšanās lapu un jāmēģina vēlreiz."
+        },
+        "password_field_keep_going_prompt": "Turpini…",
+        "password_field_label": "Ievadiet paroli",
+        "password_field_strong_label": "Lieliski, sarežģīta parole!",
+        "password_field_weak_label": "Parole ir atļauta, tomēr nedroša",
+        "phone_label": "Telefons",
+        "phone_optional_label": "Tālruņa numurs (izvēles)",
+        "qr_code_login": {
+            "check_code_explainer": "Šis apliecinās, ka savienojums ar otru ierīci ir drošs.",
+            "check_code_heading": "Jāievada skaitlis, kas redzams otrā ierīcē",
+            "check_code_input_label": "2 ciparu kods",
+            "check_code_mismatch": "Skaitļi nesakrīt",
+            "completing_setup": "Pabeidz iestatīt Tavu jauno ierīci",
+            "error_etag_missing": "Atgadījās neparedzēta kļūda. Tas var būt pārlūka paplašinājuma, starpniekservera vai nepilnīgas servera konfigurācijas dēļ.",
+            "error_expired": "Iestājies pierakstīšanās noilgums. Lūdzu mēģiniet vēlreiz.",
+            "error_expired_title": "Pierakstīšanās netika pabeigta laikā",
+            "error_insecure_channel_detected": "Nevarēja izveidot drošu savienojumu ar jauno ierīci. Jūsu esošās ierīces joprojām ir drošībā un jums par tām nav jāuztraucas.",
+            "error_insecure_channel_detected_instructions": "Kas tagad?",
+            "error_insecure_channel_detected_instructions_1": "Mēģiniet vēlreiz pierakstīties citā ierīcē, izmantojot QR kodu, ja gadījumā to izraisīja tīkla problēma",
+            "error_insecure_channel_detected_instructions_2": "Ja rodas tāda pati problēma, izmēģiniet citu Wi-Fi tīklu vai izmantojiet mobilos datus Wi-Fi vietā",
+            "error_insecure_channel_detected_instructions_3": "Ja tas nepalīdz, pierakstieties manuāli",
+            "error_insecure_channel_detected_title": "Savienojums nav drošs",
+            "error_other_device_already_signed_in": "Jums nekas cits nav jādara.",
+            "error_other_device_already_signed_in_title": "Citā jūsu ierīcē jau ir veikta pierakstīšanās",
+            "error_rate_limited": "Pārāk daudz mēģinājumu īsā laika posmā. Jāuzgaida kāds brīdis, pirms mēģināt vēlreiz.",
+            "error_unexpected": "Atgadījās neparedzēta kļūda.",
+            "error_unsupported_protocol": "Šī ierīce neatbalsta pierakstīšanos citā ierīcē ar QR kodu.",
+            "error_unsupported_protocol_title": "Cita ierīce nav saderīga",
+            "error_user_cancelled": "Pierakstīšanās tika atcelta citā ierīcē.",
+            "error_user_cancelled_title": "Pierakstīšanās pieprasījums ir atcelts",
+            "error_user_declined": "Jūs noraidījāt no citas jūsu ierīces nākušo pieprasījumu pierakstīties.",
+            "error_user_declined_title": "Pierakstīšanās noraidīta",
+            "follow_remaining_instructions": "Jāseko atlikušajām norādēm, lai apliecinātu savu otru ierīci",
+            "open_element_other_device": "Jāatver %(brand)s savā otrā ierīcē",
+            "point_the_camera": "Jāvērš kamera uz šeit parādīto kvadrātkodu",
+            "scan_code_instruction": "Zemāk esošais kvadrātkods ir jānolasa ar ierīci, kurā ir notikusi atteikšanās.",
+            "scan_qr_code": "Nolasīt kvadrātkodu",
+            "security_code": "Drošības kods",
+            "security_code_prompt": "Ja tiek prasīts, otrā ierīcē jāievada zemāk esošais kods",
+            "select_qr_code": "Jāatlasa '%(scanQRCode)s'",
+            "unsupported_explainer": "Konta nodrošinātājs neatbalsta pieteikšanos jaunā ierīcē ar kvadrātkodu.",
+            "unsupported_heading": "Kvadrātkods netiek atbalstīts",
+            "waiting_for_device": "Gaida, līdz pieteiksies ierīcē"
+        },
+        "register_action": "Izveidot kontu",
+        "registration": {
+            "continue_without_email_description": "Jāņem vērā, ka, ja nepievieno e-pasta adresi un tiek aizmirsta parole, var <b>neatgriezeniski zaudēt piekļuvi savam kontam</b>.",
+            "continue_without_email_field_label": "Epasts (izvēles)",
+            "continue_without_email_title": "Turpina bez e-pasta adreses"
+        },
+        "registration_disabled": "Reģistrācija šajā mājasserverī ir atspējota.",
+        "registration_msisdn_field_required_invalid": "Jāievada tālruņa numurs (nepieciešams šajā mājasserverī)",
+        "registration_successful": "Reģistrācija ir veiksmīga",
+        "registration_username_in_use": "Kādam jau ir šis lietotājvārds. Jāizmēģina cits vai, ja tas esi Tu, jāpiesakās zemāk.",
+        "registration_username_unable_check": "Nevarēja pārbaudīt, vai lietotājvārds ir aizņemts. Vēlāk jāmēģina vēlreiz.",
+        "registration_username_validation": "Izmantojiet tikai mazos burtus, ciparus, domuzīmes un pasvītrojumus",
+        "reset_password": {
+            "confirm_new_password": "Apstiprināt jauno paroli",
+            "devices_logout_success": "Ir veikta atteikšanās visās ierīcēs, un vairs nesaņemsi pašpiegādes paziņojumus. Lai atkārtoti iespējotu paziņojumus, vēlreiz jāpiesakās katrā ierīcē.",
+            "other_devices_logout_warning_1": "Atteikšanās Tavās ierīcēs izdzēsīs tajās glabātās ziņu šifrēšanas atslēgas, padarot šifrētas tērzēšanas vēsturi nelasāmu.",
+            "other_devices_logout_warning_2": "Ja vēlies saglabāt piekļuvu savas tērzēšanas vēsturei šifrētās istabās, pirms turpināšanas jāiestata atslēgu rezerves kopēšana vai jāizgūst savas ziņu atslēgas kādā no citām Tavām ierīcēm.",
+            "password_not_entered": "Nepieciešams ievadīt jauno paroli.",
+            "passwords_mismatch": "Jaunajām parolēm ir jāsakrīt vienai ar otru.",
+            "rate_limit_error": "Pārāk daudz mēģinājumu īsā laika posmā. Jāpagaida kāds brīdis, pirms mēģināt vēlreiz.",
+            "rate_limit_error_with_time": "Pārāk daudz mēģinājumu īsā laika posmā. Jāmēģina atkārtoti pēc %(timeout)s.",
+            "reset_successful": "Parole tika atiestatīta.",
+            "return_to_login": "Atgriezties uz pierakstīšanās lapu",
+            "sign_out_other_devices": "Atteikties visās ierīcēs"
+        },
+        "reset_password_action": "Atiestatīt paroli",
+        "reset_password_button": "Aizmirsta parole?",
+        "reset_password_email_field_description": "Izmantojiet epasta adresi konta atkopšanai",
+        "reset_password_email_field_required_invalid": "Jāievada e-pasta adrese (nepieciešama šajā mājasserverī)",
+        "reset_password_email_not_associated": "Neizskatās, ka Tava e-pasta adrese būtu saistīta ar kādu Matrix Id šajā mājasserverī.",
+        "reset_password_email_not_found_title": "Šāda epasta adrese nav atrasta",
+        "reset_password_title": "Atiestatīt savu paroli",
+        "server_picker_custom": "Cits mājasserveris",
+        "server_picker_description": "Tu vari izmantot pielāgotas servera iespējas, lai pieteiktos citos Matrix serveros ar cita mājasservera URL norādīšanu. Tas ļauj izmantot %(brand)s ar esošu Matrix kontu citā mājasserverī.",
+        "server_picker_description_matrix.org": "Pievienojieties bez maksas miljoniem lietotāju lielākajā publiskajā serverī",
+        "server_picker_dialog_title": "Izlem, kur Tavs konts tiks mitināts",
+        "server_picker_explainer": "Izmanto savu vēlamo Matrix mājasserveri, ja tāds ir, vai mitini savu!",
+        "server_picker_failed_validate_homeserver": "Nebija iespējams apstiprināt mājasserveri",
+        "server_picker_intro": "Mēs saucam vietas, kur vari mitināt savu kontu, par \"mājasserveriem\".",
+        "server_picker_invalid_url": "Nederīgs URL",
+        "server_picker_learn_more": "Par mājasserveriem",
+        "server_picker_matrix.org": "Matrix.org ir lielākais publiskais mājasserveris pasaulē, tādēļ tas ir laba vieta daudziem.",
+        "server_picker_required": "Norādīt mājasserveri",
+        "server_picker_title": "Pieteikties savā mājasserverī",
+        "server_picker_title_default": "Servera parametri",
+        "server_picker_title_registration": "Mitināts konts",
+        "session_logged_out_description": "Drošības nolūkos šī sesija ir pārtraukta. Lūdzu, pieraksties par jaunu.",
+        "session_logged_out_title": "Izrakstījās",
+        "set_email": {
+            "description": "Tas ļaus jums atiestatīt paroli un saņemt paziņojumus.",
+            "verification_pending_description": "Lūdzu, pārbaudi savu epastu un noklikšķini tajā esošo saiti. Tiklīdz tas ir izdarīts, klikšķini \"turpināt\".",
+            "verification_pending_title": "Gaida apliecināšanu"
+        },
+        "set_email_prompt": "Vai vēlies norādīt epasta adresi?",
+        "sign_in_description": "Jāizmanto savs konts, lai turpinātu.",
+        "sign_in_instead": "Tā vietā pieteikties",
+        "sign_in_instead_prompt": "Jau ir konts? <a>Pierakstieties šeit</a>",
+        "sign_in_or_register": "Pieteikties vai izveidot kontu",
+        "sign_in_or_register_description": "Jāizmanto savs konts vai jāizveido jauns, lai turpinātu.",
+        "sign_in_prompt": "Vai ir konts? <a>Pieteikties</a>",
+        "sign_in_with_sso": "Pieteikties ar vienoto pierakstīšanos",
+        "signing_in": "Piesakās…",
+        "soft_logout": {
+            "clear_data_button": "Notīrīt visus datus",
+            "clear_data_description": "Visu šīs sesijas datu dzēšana ir neatgriezeniska. Šifrētās ziņas tiks zaudētas, ja vien to atslēgas nebūs dublētas.",
+            "clear_data_title": "Notīrīt visus šīs sesijas datus?"
+        },
+        "soft_logout_heading": "Tu esi atteicies",
+        "soft_logout_intro_password": "Ievadiet paroli, lai pierakstītos un atgūtu piekļuvi savam kontam.",
+        "soft_logout_intro_sso": "Pierakstieties un atgūstiet piekļuvi savam kontam.",
+        "soft_logout_intro_unsupported_auth": "Nevar pieteikties savā kontā. Lūgums sazināties ar sava mājasserver pārvaldnieku, lai iegūtu vairāk informācijas.",
+        "soft_logout_subheading": "Dzēst personas datus",
+        "soft_logout_warning": "Brīdinājums: Tavi personīgie dati (tajā skaitā šifrēšanas atslēgas) joprojām tiek glabāti šajā sesijā. Tā ir jāiztīra, ja esi beidzis izmantot šo sesiju vai vēlies pieteikties citā kontā.",
+        "sso": "Vienotā pierakstīšanās",
+        "sso_failed_missing_storage": "Mēs vaicājām pārlūkam atcerēties, kuru mājasserveri izmantot, lai ļautu pieteikties, bet diemžēl pārlūks to ir aizmirsis. Jādodas uz pieteikšanās lapu un jāmēģina vēlreiz.",
+        "sso_or_username_password": "%(ssoButtons)s vai %(usernamePassword)s",
+        "sync_footer_subtitle": "Ja esat pievienojies daudzām istabām, tas var aizņemt kādu laiku",
+        "syncing": "Sinhronizē...",
+        "uia": {
+            "code": "Kods",
+            "email": "Lai izveidotu savu kontu, jāatver saite e-pasta ziņojumā, ko tikko nosūtījām uz %(emailAddress)s.",
+            "email_auth_header": "Jāpārbauda savs e-pasts, lai turpinātu",
+            "email_resend_prompt": "Nesaņēmi to? <a>Nosūtīt to atkārtoti</a>",
+            "email_resent": "Nosūtīts atkārtoti.",
+            "fallback_button": "Uzsākt autentifikāciju",
+            "mas_cross_signing_reset_cta": "Doties uz savu kontu",
+            "msisdn": "Teksta ziņa tika nosūtīta uz %(msisdn)s",
+            "msisdn_token_incorrect": "Nepareizs autentifikācijas tokens",
+            "msisdn_token_prompt": "Lūdzu, ievadiet tajā ietverto kodu:",
+            "password_prompt": "Jāapstiprina sava identitāte ar sava konta paroles ievadīšanu zemāk.",
+            "recaptcha_missing_params": "Mājasservera konfigurācijā trūkst CAPTCHA publiskās atslēgas. Lūgums par to ziņot sava mājasservera pārvaldītājam.",
+            "registration_token_label": "Reģistrācijas pilnvara",
+            "registration_token_prompt": "Jāievada mājasservera pārvaldītāja sniegtā reģistrācijas pilnvara.",
+            "sso_body": "Apstiprināt šīs epasta adreses pievienošanu, izmantojot vienoto pierakstīšanos savas identitātes apliecināšanai.",
+            "sso_failed": "Kaut kas nogāja greizi identitātes apstiprināšanā. Jāatceļ un jāmēģina vēlreiz.",
+            "sso_postauth_body": "Jāklikšķina uz zemāk esošās pogas, lai apstiprinātu savu identitāti.",
+            "sso_postauth_title": "Jāapstiprina, lai turpinātu",
+            "sso_preauth_body": "Lai turpinātu, jāizmanto vienotā pieteikšanās, lai apliecinātu savu identitāti.",
+            "sso_title": "Izmantot vienoto pierakstīšanos, lai turpinātu",
+            "terms": "Lūgums pārskatīt un apstiprināt šī mājasservera nosacījumus:",
+            "terms_invalid": "Lūgums pārskatīt un apstiprināt visus mājasservera nosacījumus"
+        },
+        "unsupported_auth": "Šis mājasserveris nenodrošina nevienu pieteikšanās plūsmu, ko atbalsta šis klients.",
+        "unsupported_auth_email": "Šis mājasserveris nenodrošina pieteikšanos ar e-pasta adresi.",
+        "unsupported_auth_msisdn": "Šis serveris neatbalsta autentifikāciju pēc telefona numura.",
+        "username_field_required_invalid": "Ievadiet lietotājvārdu",
+        "username_in_use": "Kādam jau ir šis lietotājvārds, lūgums mēģināt citu.",
+        "verify_email_explainer": "Pirms paroles atiestatīšanas mums jāzina, ka tas esi Tu. Jāatver saite e-pasta ziņojumā, ko mēs tikko nosūtījām uz <b>%(email)s</b>",
+        "verify_email_heading": "Jāapliecina sava e-pasta adrese, lai turpinātu"
+    },
+    "bug_reporting": {
+        "additional_context": "Ja ir papildu konteksts, kas varētu palīdzēt analizēt problēmu, piemēram, tas, ko jūs tobrīd tobrīd darījāt, istabas ID, lietotāja ID u.c., lūdzu, ievadiet šos datus šeit.",
+        "before_submitting": "Pirms žurnālu iesniegšanas jums ir <a>jāizveido GitHub problēma</a>, lai aprakstītu savu problēmu.",
+        "collecting_information": "Tiek iegūta programmas versijas informācija",
+        "collecting_logs": "Tiek iegūti logfaili",
+        "create_new_issue": "Lūdzu, <newIssueLink>reģistrējiet jaunu problēmu</newIssueLink> vietnē GitHub, lai mēs varētu izpētīt šo kļūdu.",
+        "description": "Atkļūdošanas žurnālos ir ietverti lietotnes dati, tostarp jūsu lietotājvārds, apmeklēto istabu ID vai aizstājvārdi, lietotāja saskarnes elementi, ar kuriem jūs pēdējo reizi mijiedarbojāties, un citu lietotāju lietotājvārdi. Tie nesatur ziņas.",
+        "download_logs": "Lejupielādēt žurnālus",
+        "downloading_logs": "Žurnālu lejupielāde",
+        "error_empty": "Lūdzu, pastāstiet mums, kas nogāja greizi, vai reģistrējiet GitHub problēmu ar tās detalizētu aprakstu.",
+        "github_issue": "GitHub problēma",
+        "introduction": "Ja esat iesniedzis kļūdu, izmantojot GitHub, atkļūdošanas žurnāli var mums palīdzēt izsekot problēmai. ",
+        "log_request": "Lai palīdzētu mums to novērst nākotnē, lūdzu, <a>nosūtiet mums žurnālus</a>.",
+        "logs_sent": "Logfaili nosūtīti",
+        "matrix_security_issue": "Lai ziņotu par drošības problēmu, kas saistīta ar Matrix, lūdzu, iepazīstieties ar Matrix.org <a>Drošības informācijas atklāšanas politiku</a> .",
+        "preparing_download": "Gatavošanās žurnālu lejupielādei",
+        "preparing_logs": "Gatavojos nosūtīt atutošanas logfailus",
+        "send_logs": "Nosūtīt logfailus",
+        "submit_debug_logs": "Iesniegt atutošanas logfailus",
+        "textarea_label": "Piezīmes",
+        "thank_you": "Tencinam!",
+        "title": "Ziņošana par kļūdām",
+        "unsupported_browser": "Atgādinājums: Jūsu pārlūkprogramma netiek atbalstīta, tāpēc jūsu lietošanas pieredze var būt neparedzama.",
+        "uploading_logs": "Žurnālu augšupielāde",
+        "waiting_for_server": "Tiek gaidīta atbilde no servera"
+    },
+    "cannot_invite_without_identity_server": "Nevar uzaicināt lietotāju pa e-pastu bez identitātes servera. Jūs varat izveidot savienojumu ar to sadaļā “Iestatījumi”.",
+    "cannot_reach_homeserver": "Neizdodas sasniegt mājasserveri",
+    "cannot_reach_homeserver_detail": "Jāpārliecinās, ka ir noturīgs interneta savienojums, vai jāsazinās ar server pārvaldītāju.",
+    "cant_load_page": "Neizdevās ielādēt lapu",
+    "chat_card_back_action_label": "Atgriezties uz tērzēšanu",
+    "chat_effects": {
+        "confetti_description": "Nosūta doto ziņu ar konfeti",
+        "confetti_message": "sūta konfeti",
+        "fireworks_description": "Nosūta doto ziņu ar salūtu",
+        "fireworks_message": "sūta salūtu",
+        "hearts_description": "Nosūta doto ziņu ar sirsniņām",
+        "hearts_message": "sūta sirsniņas",
+        "rainfall_description": "Nosūta doto ziņu ar lietusgāzēm",
+        "rainfall_message": "sūta lietusgāzes",
+        "snowfall_description": "Nosūta doto ziņu ar sniegputeni",
+        "snowfall_message": "sūta sniegputeni",
+        "spaceinvaders_description": "Nosūta doto ziņu ar kosmosa tematikas efektu",
+        "spaceinvaders_message": "sūta kosmosa iebrucējus"
+    },
+    "common": {
+        "access_token": "Piekļuves pilvnara",
+        "accessibility": "Pieejamība",
+        "advanced": "Papildu",
+        "analytics": "Analītika",
+        "and_n_others": {
+            "other": "un vēl %(count)s citi...",
+            "one": "un vēl viens cits..."
+        },
+        "appearance": "Izskats",
+        "application": "Lietotne",
+        "are_you_sure": "Vai tiešām?",
+        "attachment": "Pielikums",
+        "authentication": "Autentifikācija",
+        "avatar": "Avatārs",
+        "beta": "Beta",
+        "camera": "Kamera",
+        "cameras": "Kameras",
+        "cancel": "Atcelt",
+        "capabilities": "Iespējas",
+        "copied": "Nokopēts!",
+        "credits": "Apliecinājumi",
+        "dark": "Tumša",
+        "description": "Apraksts",
+        "deselect_all": "Neizvēlēt nevienu",
+        "device": "Ierīce",
+        "edited": "labots",
+        "email_address": "Epasta adrese",
+        "emoji": "Emocijzīmes",
+        "encrypted": "Šifrēts",
+        "encryption_enabled": "Šifrēšana iespējota",
+        "error": "Kļūda",
+        "faq": "BUJ",
+        "favourites": "Izlase",
+        "feedback": "Atsauksmes",
+        "filter_results": "Filtrēt rezultātus",
+        "forward_message": "Pārsūtīt ziņu",
+        "general": "Vispārīgi",
+        "go_to_settings": "Pāriet uz iestatījumiem",
+        "guest": "Viesis",
+        "help": "Palīdzība",
+        "historical": "Bijušie",
+        "home": "Mājup",
+        "homeserver": "Mājasserveris",
+        "identity_server": "Identitāšu serveris",
+        "image": "Attēls",
+        "integration_manager": "Integrācija pārvaldnieks",
+        "joined": "Pievienojās",
+        "labs": "Izmēģinājumu lauciņš",
+        "legal": "Juridiskā informācija",
+        "light": "Gaiša",
+        "loading": "Notiek ielāde…",
+        "location": "Atrašanās vieta",
+        "low_priority": "Zems svarīgums",
+        "matrix": "Matrix",
+        "message": "Ziņa",
+        "message_layout": "Ziņas izkārtojums",
+        "message_timestamp_invalid": "Nederīgs laikspiedols",
+        "microphone": "Mikrofons",
+        "model": "Modelis",
+        "modern": "Mūsdienīgs",
+        "mute": "Apklusināt",
+        "n_members": {
+            "one": "%(count)s dalībnieks",
+            "other": "%(count)s dalībnieki"
+        },
+        "n_rooms": {
+            "one": "%(count)s istaba",
+            "other": "%(count)s istabas"
+        },
+        "name": "Nosaukums",
+        "no_results": "Nav rezultātu",
+        "no_results_found": "Nekas nav atrasts",
+        "not_trusted": "Neuzticama",
+        "off": "Izslēgt",
+        "offline": "Bezsaistē",
+        "on": "Ieslēgt",
+        "options": "Iespējas",
+        "orphan_rooms": "Citas istabas",
+        "password": "Parole",
+        "people": "Cilvēki",
+        "preferences": "Iestatījumi",
+        "presence": "Klātbūtne",
+        "preview_message": "Sveiks! Tu esi labākais!",
+        "privacy": "Privātums",
+        "private": "Privāts",
+        "private_room": "Privāta istaba",
+        "private_space": "Privātā telpa",
+        "profile": "Profils",
+        "public": "Publiska",
+        "public_room": "Publiska istaba",
+        "public_space": "Publiskā telpa",
+        "qr_code": "QR kods",
+        "random": "Nejauša",
+        "reactions": "Reaģēšana",
+        "report_a_bug": "Ziņot par kļūdu",
+        "room": "Istaba",
+        "room_name": "Istabas nosaukums",
+        "rooms": "Istabas",
+        "save": "Saglabāt",
+        "saved": "Saglabāts",
+        "saving": "Saglabāšana...",
+        "secure_backup": "Droša rezerves kopija",
+        "select_all": "Atlasīt visu",
+        "server": "Serveris",
+        "settings": "Iestatījumi",
+        "setup_secure_messages": "Drošu ziņu iestatīšana",
+        "show_more": "Rādīt vairāk",
+        "someone": "Kāds",
+        "space": "Telpa",
+        "spaces": "Telpas",
+        "sticker": "Uzlīme",
+        "stickerpack": "Uzlīmju paka",
+        "success": "Izdevās",
+        "suggestions": "Ieteikumi",
+        "support": "Atbalsts",
+        "system_alerts": "Sistēmas brīdinājumi",
+        "theme": "Tēma",
+        "thread": "Pavediens",
+        "threads": "Pavedieni",
+        "timeline": "Laika skala",
+        "unavailable": "pieejama",
+        "unencrypted": "Nav šifrēts",
+        "unmute": "Pārtraukt apklusināšanu",
+        "unnamed_room": "Istaba bez nosaukuma",
+        "unnamed_space": "Nenosaukta telpa",
+        "unverified": "Neapliecināts",
+        "updating": "Atjaunina...",
+        "user": "Lietotājs",
+        "user_avatar": "Profila attēls",
+        "username": "Lietotājvārds",
+        "verification_cancelled": "Apliecināšana atcelta",
+        "verified": "Verificēts",
+        "version": "Versija",
+        "video": "Video",
+        "video_room": "Video istaba",
+        "view_message": "Skatīt ziņu",
+        "warning": "Brīdinājums"
+    },
+    "composer": {
+        "autocomplete": {
+            "@room_description": "Paziņot visai istabai",
+            "command_a11y": "Komandu automātiska pabeigšana",
+            "command_description": "Komandas",
+            "emoji_a11y": "Emocijzīmju automātiska pabeigšana",
+            "notification_a11y": "Paziņojumu automātiska pabeigšana",
+            "notification_description": "Istabas paziņojums",
+            "room_a11y": "Istabu automātiska pabeigšana",
+            "space_a11y": "Telpas automātiskā pabeigšana",
+            "user_a11y": "Lietotāju automātiska pabeigšana",
+            "user_description": "Lietotāji"
+        },
+        "close_sticker_picker": "Paslēpt uzlīmes",
+        "edit_composer_label": "Rediģēt ziņu",
+        "format_bold": "Treknraksts",
+        "format_code_block": "Koda bloks",
+        "format_decrease_indent": "Atkāpes samazinājums",
+        "format_increase_indent": "Atkāpes palielinājums",
+        "format_inline_code": "Kods",
+        "format_insert_link": "Ievietot saiti",
+        "format_italic": "Slīpraksts",
+        "format_italics": "Slīpraksts",
+        "format_link": "Saite",
+        "format_ordered_list": "Numurēts saraksts",
+        "format_strikethrough": "Pārsvītrojums",
+        "format_underline": "Pasvītrojums",
+        "format_unordered_list": "Saraksts ar izceltiem punktiem",
+        "formatting_toolbar_label": "Formatējums",
+        "link_modal": {
+            "link_field_label": "Saite",
+            "text_field_label": "Teksts",
+            "title_create": "Izveidot saiti",
+            "title_edit": "Rediģēt saiti"
+        },
+        "mode_plain": "Slēpt formatējumu",
+        "mode_rich_text": "Rādīt formatējumu",
+        "no_perms_notice": "Nav vajadzīgo atļauju, lai rakstītu ziņas šajā istabā",
+        "placeholder": "Nosūtīt ziņu…",
+        "placeholder_encrypted": "Nosūtīt šifrētu ziņu…",
+        "placeholder_reply": "Nosūtīt atbildi…",
+        "placeholder_reply_encrypted": "Nosūtīt šifrētu atbildi…",
+        "placeholder_thread": "Atbildēt uz pavedienu…",
+        "placeholder_thread_encrypted": "Atbildēt uz šifrētu pavedienu…",
+        "poll_button": "Aptauja",
+        "poll_button_no_perms_description": "Nav atļaujas uzsākt aptaujas šajā istabā.",
+        "poll_button_no_perms_title": "Nepieciešama atļauja",
+        "replying_title": "Atbildot uz",
+        "room_upgraded_link": "Saruna turpinās šeit.",
+        "room_upgraded_notice": "Šī istaba ir aizstāta un vairs nav aktīva.",
+        "send_button_title": "Sūtīt ziņu",
+        "send_button_voice_message": "Sūtīt balss ziņu",
+        "send_voice_message": "Sūtīt balss ziņu",
+        "stop_voice_message": "Pārtraukt ierakstīšanu",
+        "voice_message_button": "Balss ziņa"
+    },
+    "console_dev_note": "Ja jūs zināt, ko jūs darāt, Element ir atvērtā koda, noteikti apskatiet mūsu GitHub (https://github.com/vector-im/element-web/) un piedalieties!",
+    "console_scam_warning": "Ja kāds jums lika šeit kaut ko kopēt/ielīmēt, pastāv liela iespēja, ka jūs tiksiet apkrāpts!",
+    "console_wait": "Pagaidiet!",
+    "create_room": {
+        "action_create_room": "Izveidot istabu",
+        "action_create_video_room": "Izveidot video istabu",
+        "encrypted_video_room_warning": "Vēlāk to nevar atspējot. Istaba tiks šifrēta, bet iegultais zvans netiks.",
+        "encrypted_warning": "Vēlāk to nevar atspējot. Tilti un lielākā daļa botu vēl nedarbosies.",
+        "encryption_forced": "Serveris pieprasa, lai privātās istabās būtu iespējota šifrēšāna.",
+        "encryption_label": "Iespējot pilnīgu šifrēšanu",
+        "error_title": "Neizdevās izveidot istabu",
+        "generic_error": "Serveris ir nesasniedzams, pārslogots, vai arī esat saskārties ar kļūdu programmā.",
+        "join_rule_change_notice": "Šo var mainīt istabas iestatījumos jebkurā laikā.",
+        "join_rule_invite": "Privāta istaba (tikai ar ielūgumiem)",
+        "join_rule_invite_label": "Tikai uzaicinātās cilvēki varēs atrast un pievienoties šai istabai.",
+        "join_rule_knock_label": "Ikviens var aicināt pievienoties, taču administratoriem vai moderatoriem ir jāpiešķir piekļuve. Varat to mainīt vēlāk.",
+        "join_rule_public_label": "Ikviens varēs atrast un pievienoties šai istabai.",
+        "join_rule_public_parent_space_label": "Ikviens varēs atrast un pievienoties šai istabai, ne tikai <SpaceName/> telpas dalībnieki.",
+        "join_rule_restricted": "Redzams telpas dalībniekiem",
+        "join_rule_restricted_label": "Ikviens, kas atrodas <SpaceName/> telpā, varēs atrast un pievienoties šai istabai.",
+        "name_validation_required": "Lūdzu, ievadiet istabas nosaukumu",
+        "room_visibility_label": "Istabas redzamība",
+        "title_private_room": "Izveidot privātu istabu",
+        "title_public_room": "Izveidot publisku istabu",
+        "title_video_room": "Izveidot video istabu",
+        "topic_label": "Temats (izvēles)",
+        "unfederated": "Liegt pievienoties šai istabai ikvienam, kas nav reģistrēts %(serverName)s serverī.",
+        "unfederated_label_default_off": "Šo varēs iespējot, ja istaba tiks izmantota, lai sadarbotos ar iekšējām mājasservera komandām. Šo vēlāk vairs nevarēs mainīt.",
+        "unfederated_label_default_on": "Šo var atspējot, ja istaba tiks izmantota sadarbībai ar ārējām komandām, kurām ir savas mājasserveris. Vēlāk šo nevar mainīt.",
+        "unsupported_version": "Serveris neatbalsta norādīto istabas versiju."
+    },
+    "create_space": {
+        "add_details_prompt": "Pievienojiet aprakstu, lai palīdzētu cilvēkiem to atpazīt.",
+        "add_details_prompt_2": "Jebkurā laikā varat to mainīt.",
+        "add_existing_rooms_description": "Izvēlieties istabas vai sarunas, kuras pievienot. Šī ir telpa tikai jums, neviens netiks informēts. Vēlāk varat pievienot vairāk.",
+        "add_existing_rooms_heading": "Ko jūs vēlaties organizēt?",
+        "address_label": "Adrese",
+        "address_placeholder": "piemēram, mana-telpa",
+        "creating": "Izveido…",
+        "creating_rooms": "Istabu izveide...",
+        "done_action": "Pāriet uz manu telpu",
+        "done_action_first_room": "Pāriet uz manu pirmo istabu",
+        "explainer": "Telpas ir jauns veids, kā grupēt istabas un cilvēkus. Kāda veida telpu vēlaties izveidot? To var mainīt vēlāk.",
+        "failed_create_initial_rooms": "Neizdevās izveidot telpas sākotnējās istabas",
+        "failed_invite_users": "Neizdevās uzaicināt uz jūsu telpu šādus lietotājus: %(csvUsers)s",
+        "invite_teammates_by_username": "Uzaicināt pēc lietotājvārda",
+        "invite_teammates_description": "Pārliecinieties, vai pareizajiem cilvēkiem ir piekļuve. Vēlāk varēsiet uzaicināt citus.",
+        "invite_teammates_heading": "Uzaiciniet savus komandas biedrus",
+        "inviting_users": "Uzaicina...",
+        "label": "Izveidot telpu",
+        "name_required": "Lūdzu, ievadiet telpas nosaukumu",
+        "personal_space": "Tikai es",
+        "personal_space_description": "Privāta telpa, kur organizēt jūsu istabas",
+        "private_description": "Tikai ar uzaicinājumiem, vislabākais sev vai komandām",
+        "private_heading": "Jūsu privātā telpa",
+        "private_personal_description": "Pārliecinieties, vai pareizajiem cilvēkiem ir piekļuve %(name)s",
+        "private_personal_heading": "Ar ko kopā jūs strādājat?",
+        "private_space": "Es un mani komandas biedri",
+        "private_space_description": "Privāta telpa jums un jūsu komandas biedriem",
+        "public_description": "Atvērta telpa ikvienam, vislabākais kopienām",
+        "public_heading": "Jūsu publiskā telpa",
+        "search_public_button": "Meklēt publiskās telpas",
+        "setup_rooms_community_description": "Izveidojam katram no tiem savu istabu!",
+        "setup_rooms_community_heading": "Kas ir dažas tēmas, ko vēlaties apspriest %(spaceName)s?",
+        "setup_rooms_description": "Vēlāk varat pievienot arī vairāk, ieskaitot jau esošās.",
+        "setup_rooms_private_description": "Mēs izveidosim istabas katram no tiem.",
+        "setup_rooms_private_heading": "Pie kādiem projektiem strādā jūsu komanda?",
+        "share_description": "Šobrīd tas esat tikai jūs, ar citiem būs vēl labāk.",
+        "share_heading": "Dalīties ar %(name)s",
+        "skip_action": "Pagaidām izlaist",
+        "subspace_adding": "Pievieno…",
+        "subspace_beta_notice": "Pievienojiet telpu jūsu pārvaldītai telpai.",
+        "subspace_dropdown_title": "Izveidot telpu",
+        "subspace_existing_space_prompt": "Vai vēlaties tā vietā pievienot esošu telpu?",
+        "subspace_join_rule_invite_description": "Tikai uzaicinātie cilvēki varēs atrast un pievienoties šai telpai.",
+        "subspace_join_rule_invite_only": "Privātā telpa (tikai ar uzaicinājumiem)",
+        "subspace_join_rule_label": "Telpas redzamība",
+        "subspace_join_rule_public_description": "Ikviens varēs atrast un pievienoties šai telpai, ne tikai <SpaceName/> dalībnieki.",
+        "subspace_join_rule_restricted_description": "Ikviens <SpaceName/> varēs atrast un pievienoties."
+    },
+    "credits": {
+        "default_cover_photo": "<photo>Noklusējuma vāka attēls</photo> ir © <author>Jesús Roncero</author> izmantots saskaņā ar <terms>CC-BY-SA 4.0</terms> nosacījumiem.",
+        "twemoji": "<twemoji>Twemoji</twemoji> emocijzīmju māksla ir radījis © <author>Twitter un citi līdzdalībnieki</author>, tā tiek izmantota ar <terms>CC-BY 4.0</terms> nosacījumiem.",
+        "twemoji_colr": "<colr>twemoji-colr</colr> fontu ir radījis ©<author>Mozilla nodibinājums</author>, tas tiek izmantots ar <terms>Apache 2.0</terms> nosacījumiem."
+    },
+    "devtools": {
+        "active_widgets": "Aktīvie logrīki",
+        "category_other": "Citi",
+        "category_room": "Istaba",
+        "caution_colon": "Uzmanību:",
+        "client_versions": "Klienta versijas",
+        "developer_mode": "Izstrādātāja režīms",
+        "developer_tools": "Izstrādātāja rīki",
+        "edit_setting": "Rediģēt iestatījumu",
+        "edit_values": "Rediģēt vērtības",
+        "empty_string": "<empty string>",
+        "event_content": "Notikuma saturs",
+        "event_id": "Notikuma ID: %(eventId)s",
+        "event_sent": "Notikums nosūtīts!",
+        "event_type": "Notikuma tips",
+        "explore_account_data": "Izpētīt konta datus",
+        "explore_room_account_data": "Izpētīt istabas konta datus",
+        "explore_room_state": "Izpētīt istabas stāvokli",
+        "failed_to_find_widget": "Meklējot šo logrīku, radās kļūda.",
+        "failed_to_load": "Neizdevās ielādēt.",
+        "failed_to_save": "Neizdevās saglabāt iestatījumus.",
+        "failed_to_send": "Neizdevās nosūtīt notikumu!",
+        "id": "ID: ",
+        "invalid_json": "Neizskatās pēc derīga JSON.",
+        "level": "Līmenis",
+        "low_bandwidth_mode": "Zema joslas platuma režīms",
+        "low_bandwidth_mode_description": "Nepieciešams saderīgs bāzes serveris.",
+        "main_timeline": "Galvenā laika skala",
+        "no_receipt_found": "Apliecinājums nav atrasts",
+        "notification_state": "Paziņojuma stāvoklis ir <strong>%(notificationState)s</strong>",
+        "notifications_debug": "Paziņojumu atkļūdošana",
+        "number_of_users": "Lietotāju skaits",
+        "original_event_source": "Oriģinālais notikuma pirmkods",
+        "room_encrypted": "Istaba ir <strong>šifrēta ✅</strong>",
+        "room_id": "Istabas ID: %(roomId)s",
+        "room_not_encrypted": "Istaba ir <strong>nešifrēta 🚨</strong>",
+        "room_notifications_dot": "Punkts: ",
+        "room_notifications_highlight": "Izcēlums: ",
+        "room_notifications_last_event": "Pēdējais notikums:",
+        "room_notifications_sender": "Sūtītājs: ",
+        "room_notifications_thread_id": "Pavediena ID: ",
+        "room_notifications_total": "Kopā: ",
+        "room_notifications_type": "Veids: ",
+        "room_status": "Istabas statuss",
+        "room_unread_status_count": {
+            "zero": "",
+            "one": "Istabas neizlasītais statuss: <strong>%(status)s</strong>, skaits: <strong>%(count)s</strong>",
+            "other": "Istabas neizlasītais statuss: <strong>%(status)s</strong>, skaits: <strong>%(count)s</strong>"
+        },
+        "save_setting_values": "Saglabāt iestatījumu vērtības",
+        "see_history": "Skatīt vēsturi",
+        "send_custom_account_data_event": "Nosūtīt pielāgotu konta datu notikumu",
+        "send_custom_room_account_data_event": "Nosūtīt pielāgotu istabas konta datu notikumu",
+        "send_custom_state_event": "Nosūtīt pielāgotu stāvokļa notikumu",
+        "send_custom_timeline_event": "Nosūtīt pielāgotu laika skalas notikumu",
+        "server_info": "Servera informācija",
+        "server_versions": "Servera versijas",
+        "settable_global": "Iestatāms globāli",
+        "settable_room": "Iestatāms istabas līmenī",
+        "setting_colon": "Iestatījums:",
+        "setting_definition": "Iestatījuma definīcija:",
+        "setting_id": "Iestatījuma ID",
+        "settings_explorer": "Iestatījumu pārlūks",
+        "show_hidden_events": "Rādīt slēptos notikumus laika skalā",
+        "spaces": {
+            "zero": "",
+            "one": "<space>",
+            "other": "<%(count)s spaces>"
+        },
+        "state_key": "Stāvokļa atslēga",
+        "thread_root_id": "Pavediena saknes ID: %(threadRootId)s",
+        "threads_timeline": "Pavedienu laika skala",
+        "title": "Izstrādātāja rīki",
+        "toggle_event": "pārslēgt notikumu",
+        "toolbox": "Instrumentārijs",
+        "use_at_own_risk": "Šī lietotāja saskarne NEPĀRBAUDA vērtību veidus. Izmantojiet uz savu risku.",
+        "user_read_up_to": "Lietotājs izlasījis līdz: ",
+        "user_read_up_to_ignore_synthetic": "Lietotājs izlasījis līdz (ignoreSynthetic): ",
+        "user_read_up_to_private": "Lietotājs izlasījis līdz (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "Lietotājs izlasījis līdz (m.read.private;ignoreSynthetic): ",
+        "value": "Vērtība",
+        "value_colon": "Vērtība:",
+        "value_in_this_room": "Vērtība šajā istabā",
+        "value_this_room_colon": "Vērtība šajā istabā:",
+        "values_explicit": "Vērtības precīzos līmeņos",
+        "values_explicit_colon": "Vērtības precīzos līmeņos:",
+        "values_explicit_room": "Vērtības precīzā līmenī šajā istabā",
+        "values_explicit_this_room_colon": "Vērtības precīzos līmeņos šajā istabā:",
+        "view_servers_in_room": "Apskatīt serverus istabā",
+        "view_source_decrypted_event_source": "Atšifrēts notikuma pirmkods",
+        "view_source_decrypted_event_source_unavailable": "Atšifrēts pirmkods nav pieejams",
+        "widget_screenshots": "Iespējot logrīku ekrānuzņēmumus atbalstītajos logrīkos"
+    },
+    "dialog_close_label": "Aizvērt dialoglodziņu",
+    "download_completed": "Lejupielāde pabeigta",
+    "emoji": {
+        "categories": "Kategorijas",
+        "category_activities": "Aktivitātes",
+        "category_animals_nature": "Dzīvnieki un daba",
+        "category_flags": "Karogi",
+        "category_food_drink": "Pārtika un dzērieni",
+        "category_frequently_used": "Bieži lietotas",
+        "category_objects": "Objekti",
+        "category_smileys_people": "Smaidiņi & cilvēki",
+        "category_symbols": "Simboli",
+        "category_travel_places": "Ceļojumi un vietas",
+        "quick_reactions": "Ātra reaģēšana"
+    },
+    "emoji_picker": {
+        "cancel_search_label": "Atcelt meklējumu"
+    },
+    "empty_room": "Tukša istaba",
+    "empty_room_was_name": "Tukša istaba (bija %(oldName)s)",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "key_validation_text": {
+                "wrong_security_key": "Nepareiza drošības atslēga"
+            },
+            "restoring": "Atslēgu atjaunošana no rezerves kopijas",
+            "security_key_title": "Drošības atslēga"
+        },
+        "bootstrap_title": "Atslēgu iestatīšana",
+        "cancel_entering_passphrase_description": "Vai tiešām atcelt paroles vārdkopas ievadīšanu?",
+        "cancel_entering_passphrase_title": "Atcelt frāzveida paroles ievadi?",
+        "confirm_encryption_setup_body": "Noklikšķiniet uz tālāk esošās pogas, lai apstiprinātu šifrēšanas iestatīšanu.",
+        "confirm_encryption_setup_title": "Apstiprināt šifrēšanas iestatīšanu",
+        "cross_signing_room_normal": "Šajā istabā tiek veikta pilnīga šifrēšana",
+        "cross_signing_room_verified": "Visi šajā istabā esošie ir verificēti",
+        "cross_signing_room_warning": "Kāds izmanto nezināmu sesiju",
+        "cross_signing_user_normal": "Šis lietotājs nav apstiprināts.",
+        "cross_signing_user_verified": "Tu apliecināji šo lietotāju. Šis lietotājs ir apliecinājis visas savas sesijas.",
+        "cross_signing_user_warning": "Šis lietotājs nav apliecinājis visas savas sesijas.",
+        "event_shield_reason_authenticity_not_guaranteed": "Šīs šifrētās ziņas autentiskums nevar tikt garantēts šajā ierīcē.",
+        "event_shield_reason_mismatched_sender_key": "Šifrēts neapliecinātā sesijā",
+        "event_shield_reason_unknown_device": "Šifrēts ar nezināmu vai dzēstu ierīci.",
+        "event_shield_reason_unsigned_device": "Šifrēts ar ierīci, kuru nav verificējis tās īpašnieks.",
+        "event_shield_reason_unverified_identity": "Šifrēja neapliecināts lietotājs.",
+        "export_unsupported": "Pārlūks nenodrošina nepieciešamos kriptogrāfijas paplašinājumus",
+        "import_invalid_keyfile": "Nederīgs %(brand)s atslēgfails",
+        "import_invalid_passphrase": "Autentifikācijas pārbaude neizdevās. Nepareiza parole?",
+        "messages_not_secure": {
+            "cause_1": "Mājasserveris",
+            "cause_2": "Mājasserveris, kuram ir pievienojies apliecināmais lietotājs,",
+            "cause_3": "Jūsu vai citu lietotāju interneta savienojums",
+            "cause_4": "Jūsu vai citu lietotāju sesija",
+            "heading": "Kāds no sarakstā esošajiem faktoriem var būt kompromitēts:",
+            "title": "Jūsu ziņas nav drošas"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "Ir atklāta jauna drošības frāze un atslēga drošajām ziņām.",
+            "description_2": "Šajā sesijā vēsture tiek šifrēta, izmantojot jauno atkopšanas metodi.",
+            "title": "Jauna atkopšanas metode",
+            "warning": "Ja neiestatīji jaunu atkopšanas veidu, var būt, ka uzbrucējs mēģina piekļūt Tavam kontam. Nekavējoties iestatījumos jānomaina sava konta parole un jāiestata jauns atkopšanas veids."
+        },
+        "recovery_method_removed": {
+            "description_1": "Šajā sesijā tika konstatēts, ka jūsu drošības frāze un drošo ziņojumu atslēga ir noņemta.",
+            "description_2": "Ja to izdarījāt nejauši, šajā sesijā varat iestatīt drošas ziņas, kas atkārtoti šifrēs šīs sesijas ziņu vēsturi, izmantojot jaunu atkopšanas metodi.",
+            "title": "Atkopšanas metode noņemta",
+            "warning": "Ja nenoņēmi atkopšanas veidu, var būt, ka uzbrucējs mēģina piekļūt Tavam kontam. Nekavējoties jānomaina sava konta parole un jāiestata jauns atkopšanas veids."
+        },
+        "reset_all_button": "Aizmirsāt vai pazaudējāt visas atkopšanās iespējas? <a>Atiestatiet visu</a>",
+        "set_up_toast_description": "Pasargājieties pret piekļuves zaudēšanu šifrētām ziņām un datiem",
+        "set_up_toast_title": "Iestatīt drošu rezerves dublēšanu",
+        "setup_secure_backup": {
+            "explainer": "Pirms izrakstīšanās iestatiet atslēgu dublēšanu, lai izvairītos no tās pazaudēšanas."
+        },
+        "udd": {
+            "interactive_verification_button": "Mijiedarbīga apliecināšana ar emocijzīmēm",
+            "other_ask_verify_text": "Vaicāt šim lietotājam apliecināt savu sesiju vai zemāk apliecināt to pašrocīgi.",
+            "other_new_session_text": "%(name)s (%(userId)s) pieteicās jaunā sesijā bez tās apliecināšanas:",
+            "own_ask_verify_text": "Cita sesija ir jāapliecina ar vienu no zemāk esošajām iespējām.",
+            "own_new_session_text": "Tu pierakstījies jaunā sesijā bez tās apliecināšanas:",
+            "title": "Neuzticama"
+        },
+        "unable_to_setup_keys_error": "Nevar iestatīt atslēgas",
+        "verification": {
+            "accepting": "Apstiprina…",
+            "after_new_login": {
+                "device_verified": "Ierīce ir verificēta",
+                "skip_verification": "Pagaidām izlaist verifikāciju",
+                "unable_to_verify": "Neizdevās verificēt šo ierīci",
+                "verify_this_device": "Verificēt šo ierīci"
+            },
+            "cancelled": "Tu atcēli apliecināšanu.",
+            "cancelled_self": "Jūs atcēlāt verifikāciju citā savā ierīcē.",
+            "cancelled_user": "%(displayName)s atcēla apliecināšanu.",
+            "cancelling": "Atcelšana...",
+            "complete_action": "Sapratu",
+            "complete_description": "Tu esi veiksmīgi apliecinājis šo lietotāju.",
+            "complete_title": "Apliecināts.",
+            "error_starting_description": "Mēs nevarējām uzsākt tērzēšanu ar otru lietotāju.",
+            "error_starting_title": "Kļūda apliecināšanas uzsākšanā",
+            "explainer": "Saziņa ar šo lietotāju ir nodrošināta ar pilnīgu šifrēšanu un nav nolasāma trešajām pusēm.",
+            "in_person": "Lai tas būtu droši, dariet to klātienē vai lietojiet kādu uzticamu saziņas veidu.",
+            "incoming_sas_device_dialog_text_1": "Apliecināt šo ierīci, lai atzīmētu to kā uzticamu. Uzticēšanās šai ierīcei sniedz Tev un citiem lietotājiem paplidu mieru, kad tiek izmantotas pilnībā šifrētas ziņas.",
+            "incoming_sas_device_dialog_text_2": "Šīs ierīces apliecināšana atzīmēs to kā uzticamu, un lietotaji, kuri ir apliecinājušies ar Tevi, uzticēsies šai ierīcei.",
+            "incoming_sas_dialog_title": "Ienākošs apliecināšanas pieprasījums",
+            "incoming_sas_dialog_waiting": "Gaida partnera apstiprinājumu…",
+            "incoming_sas_user_dialog_text_1": "Apliecināt šo lietotāju, lai atzīmētu to kā uzticamu. Uzticēšanās lietotājiem sniedz papildu mieru, kad tiek izmantotas pilnīgi šifrētas ziņas.",
+            "incoming_sas_user_dialog_text_2": "Šī lietotāja apliecināšana atzīmēs viņa sesiju kā uzticamu, kā arī atzīmēs šo sesiju kā viņam uzticamu.",
+            "no_key_or_device": "Izskatās, ka Tev nav drošības atslēgas vai citu ierīču, kurās varētu veikt apliecināšanu. Šajā ierīcē nebūs iespējams piekļūt vecām šifrētajām ziņām. Lai šajā ierīcē apliecinātu savu identitāti, būs nepieciešams atiestatīt savas apliecināšanas atslēgas.",
+            "no_support_qr_emoji": "Ierīce, kuru mēģini apliecināt, nenodrošina kvadrātkoda nolasīšanu vai emocijzīmju apliecināšanu, ko nodrošina %(brand)s. Jāmēģina ar citu klientu.",
+            "other_party_cancelled": "Otra puse atcēla apliecināšanu.",
+            "prompt_encrypted": "Apliecināt visus istabā esošos lietotājus, lai nodrošinātu tās drošību.",
+            "prompt_self": "Apliecināšana jāuzsāk vēlreiz no paziņojuma.",
+            "prompt_unencrypted": "Šifrētās apliecināt visus lietotājus, lai nodrošinātu, ka tās ir drošas.",
+            "prompt_user": "Apliecināšana jāuzsāk vēlreiz no viņu profila.",
+            "qr_or_sas": "%(qrCode)s vai %(emojiCompare)s",
+            "qr_or_sas_header": "Verificējiet šo ierīci, veicot vienu no šīm darbībām:",
+            "qr_prompt": "Noskenējiet šo unikālo kodu",
+            "qr_reciprocate_same_shield_device": "Gandrīz esam galā! Vai cita jūsu ierīce rāda tādu pašu vairogu?",
+            "qr_reciprocate_same_shield_user": "Gandrīz galā! Vai %(displayName)s tiek parādīts tas pats vairogs?",
+            "request_toast_accept": "Apliecināt sesiju",
+            "request_toast_accept_user": "Apliecināt lietotāju",
+            "request_toast_decline_counter": "Neņemt vērā (%(counter)s)",
+            "request_toast_detail": "%(deviceId)s no %(ip)s",
+            "reset_proceed_prompt": "Turpināt atiestatīšanu",
+            "sas_caption_self": "Apliecināt šo ierīci, apstiprinot, ka ekrānā parādās šis skaitlis.",
+            "sas_caption_user": "Apliecināt šo lietotāju, apstiprinot, ka zemāk esošais numurs pārādās lietotāja ekrānā.",
+            "sas_description": "Salīdziniet unikālu emocijzīmju kopu, ja nevienai ierīcei nav kameras",
+            "sas_emoji_caption_self": "Apstiprināt, ka zemāk esošās emocijzīmes air attēlotas abās ierīcēs tādā pašā secībā:",
+            "sas_emoji_caption_user": "Apliecināt šo lietotāju, apstiprinot, ka zemāk esošās emocijzīmes pārādās lietotāja ekrānā.",
+            "sas_match": "Tās sakrīt",
+            "sas_no_match": "Tās nesakrīt",
+            "sas_prompt": "Jāsalīdzina vienreizējās emocijzīmes",
+            "scan_qr": "Apliecināt ar nolasīšanu",
+            "scan_qr_explainer": "Jāvaicā %(displayName)s nolasīt šo kodu:",
+            "self_verification_hint": "Lai turpinātu, lūgums apstiprināt apliecinājuma pieprasījumu otrā ierīcē.",
+            "start_button": "Uzsākt apliecināšanu",
+            "successful_device": "Ir veiksmīgi apliecināta %(deviceName)s (%(deviceId)s).",
+            "successful_own_device": "Ierīce ir veiksmīgi apliecināta.",
+            "successful_user": "Ir veiksmīgi apliecināts %(displayName)s!",
+            "timed_out": "Verifikācijai iestājās noilgums.",
+            "unsupported_method": "Neizdevās atrast atbalstītu verifikācijas metodi.",
+            "unverified_session_toast_accept": "Jā, tas biju es",
+            "unverified_session_toast_title": "Jauna pieteikšanās. Vai tas biji Tu?",
+            "unverified_sessions_toast_description": "Pārskatīt, lai nodrošinātu, ka konts ir drosībā.",
+            "unverified_sessions_toast_reject": "Vēlāk",
+            "unverified_sessions_toast_title": "Tev ir neapliecinātas sesijas",
+            "verification_description": "Verificējiet savu identitāti, lai piekļūtu šifrētām ziņām un pierādītu savu identitāti citiem.",
+            "verification_dialog_title_device": "Apliecināt otru ierīci",
+            "verification_dialog_title_user": "Apliecināšanas pieprasījums",
+            "verification_skip_warning": "Neveicot verifikāciju, jums nebūs piekļuves visām savām ziņām, kā arī ierīce var tikt parādīta citiem kā neuzticama.",
+            "verification_success_with_backup": "Jūsu jaunā ierīce tagad ir verificēta. Tai ir piekļuve jūsu šifrētajām ziņām, un citi lietotāji to redzēs kā uzticamu.",
+            "verification_success_without_backup": "Jūsu jaunā ierīce tagad ir verificēta. Citi lietotāji redzēs to kā uzticamu.",
+            "verify_emoji": "Apliecināt ar emocijzīmēm",
+            "verify_emoji_prompt": "Apliecināt ar vienreizēju emocijzīmu salīdzināšanu.",
+            "verify_emoji_prompt_qr": "Ja nevar nolasīt augstāk esošo kodu, var apliecināt ar vienreizēju emocijzīmju salīdzināšanu.",
+            "verify_later": "Es verificēšu vēlāk",
+            "verify_using_device": "Verificēt ar citu ierīci",
+            "verify_using_key": "Verificēt ar drošības atslēgu",
+            "verify_using_key_or_phrase": "Verificēt, izmantojot drošības atslēgu vai frāzi",
+            "waiting_for_user_accept": "Gaida, kamēr %(displayName)s apstiprinās…",
+            "waiting_other_device": "Gaida, kamēr jūs verificēsiet citā savā ierīcē...",
+            "waiting_other_device_details": "Gaida, kamēr jūs verificēsiet citā ierīcē - %(deviceName)s (%(deviceId)s)…",
+            "waiting_other_user": "Gaida, līdz %(displayName)s, apliecinās…"
+        },
+        "verification_requested_toast_title": "Pieprasīta verifikācija",
+        "verify_toast_description": "Citi lietotāji var neuzskatīt to par uzticamu",
+        "verify_toast_title": "Apliecināt šo sesiju"
+    },
+    "error": {
+        "admin_contact": "Lūgums <a>sazināties ar sava pakalpojuma pārvaldītāju</a>, lai turpinātu izmantot šo pakalpojumu.",
+        "admin_contact_short": "Sazinieties ar <a>servera administratoru</a>.",
+        "connection": "Atgadījās sarežģījums saziņā ar mājasserveri. Lūgums vēlāk mēģināt vēlreiz.",
+        "dialog_description_default": "Notikusi kļūda.",
+        "download_media": "Neizdevās lejupielādēt avota multividi, avota URL netika atrasts",
+        "edit_history_unsupported": "Neizskatās, ka Tavs mājasserveris nodrošina šo iespēju.",
+        "failed_copy": "Nokopēt neizdevās",
+        "hs_blocked": "Šo mājasserveri ir bloķējis tā pārvaldītājs.",
+        "mau": "Šis mājasserveris ir sasniedzis ikmēneša aktīvo lietotāju ierobežojumu.",
+        "mixed_content": "Nevar savienoties ar mājasserveri ar HTTP, kad pārlūka adreses joslā ir HTTPS URL. Vai nu jāizmanto HTTPS, vai <a>jāiespējo nedroši skripti</a>.",
+        "non_urgent_echo_failure_toast": "Jūsu serveris neatbild uz dažiem <a>pieprasījumiem</a>.",
+        "resource_limits": "Šis mājasserveris ir pārsniedzis vienu no tā resursu ierobežojumiem.",
+        "session_restore": {
+            "clear_storage_button": "Iztīrīt krātuvi un izrakstīties",
+            "clear_storage_description": "Izrakstīties un noņemt šifrēšanas atslēgas?",
+            "description_1": "Mēģinot atjaunot iepriekšējo sesiju, radās kļūda.",
+            "description_2": "Ja iepriekš tika izmantota nesenāka %(brand)s versija, sesija var būt nesaderīga ar šo versiju. Jāaizver šis logs un jāatgriežas uz jaunāku versiju.",
+            "description_3": "Pārlūkprogrammas krātuves dzēšana var atrisināt problēmu, taču tas nozīmē, ka jūs izrakstīsieties un šifrēto tērzēšanas vēsturi vairs nebūs iespējams nolasīt.",
+            "title": "Neizdevās atjaunot sesiju"
+        },
+        "something_went_wrong": "Kaut kas nogāja greizi!",
+        "storage_evicted_description_1": "Trūkst dažu sesijas datu, tostarp šifrētu ziņu atslēgas. Izrakstieties un pierakstieties, lai to labotu, atjaunojot atslēgas no rezerves kopijas.",
+        "storage_evicted_description_2": "Jūsu pārlūkprogramma, visticamāk, dzēsa šos datus, kad diskā bija maz vietas.",
+        "storage_evicted_title": "Trūkst sesijas datu",
+        "sync": "Nevar savienoties ar mājasserveri. Mēģina atkārtoti…",
+        "tls": "Nevar savienoties ar mājasserveri - lūgums pārbaudīt savienojamību, pārliecināties, ka <a>mājasservera SSL sertifikāts</a> ir uzticams un ka pārlūka paplašinājums neaiztur pieprasījumus.",
+        "unknown": "Nezināma kļūda",
+        "unknown_error_code": "nezināms kļūdas kods",
+        "update_power_level": "Neizdevās nomainīt jaudas līmeni"
+    },
+    "error_database_closed_title": "%(brand)s pārstāja darboties",
+    "error_dialog": {
+        "copy_room_link_failed": {
+            "description": "Neizdodas nokopēt starpliktuvē saiti uz istabu.",
+            "title": "Istabas saiti nevar ievietot starpliktuvē"
+        },
+        "error_loading_user_profile": "Nevarēja ielādēt lietotāja profilu",
+        "forget_room_failed": "Neizdevās \"aizmirst\" istabu %(errCode)s"
+    },
+    "error_user_not_logged_in": "Lietotājs nav pieteicies",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "Notiek zvans",
+            "user": "%(senderName)s pievienojās zvanam",
+            "you": "Tu pievienojies zvanam"
+        },
+        "m.call.hangup": {
+            "user": "%(senderName)s pabeidza zvanu",
+            "you": "Tu pabeidzi zvanu"
+        },
+        "m.call.invite": {
+            "dm_receive": "%(senderName)s zvana",
+            "dm_send": "Tiek gaidīta atbilde",
+            "user": "%(senderName)s uzsāka zvanu",
+            "you": "Tu uzsāki zvanu"
+        },
+        "m.emote": "* %(senderName)s %(emote)s",
+        "m.reaction": {
+            "user": "%(sender)s reaģēja ar %(reaction)s uz %(message)s",
+            "you": "Jūs reaģējāt ar %(reaction)s uz %(message)s"
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s"
+    },
+    "export_chat": {
+        "cancelled": "Eksportēšana atcelta",
+        "cancelled_detail": "Eksportēšana tika veiksmīgi atcelta",
+        "confirm_stop": "Vai tiešām apturēt savu datu izgūšanu? Ja jā, būs jāsāk no sākuma.",
+        "creating_html": "Notiek HTML izveide…",
+        "creating_output": "Veido izvaddatus...",
+        "creator_summary": "%(creatorName)s izveidoja šo istabu.",
+        "current_timeline": "Pašreizējā laika skala",
+        "enter_number_between_min_max": "Ievadiet skaitli starp %(min)s un %(max)s",
+        "error_fetching_file": "Kļūda datnes izgūšanā",
+        "export_info": "Šis ir <roomName/> istabas eksportēšanas sākums. Eksportē <exporterDetails/> pulksten %(exportDate)s.",
+        "export_successful": "Eksportēšana veiksmīgi pabeigta!",
+        "exported_n_events_in_time": {
+            "zero": "",
+            "one": "Eksportēts %(count)s notikums %(seconds)s sekundēs",
+            "other": "Eksportēti %(count)s notikumi %(seconds)s sekundēs"
+        },
+        "exporting_your_data": "Jūsu datu eksportēšana",
+        "fetched_n_events": {
+            "zero": "",
+            "one": "Līdz šim iegūts %(count)s notikums",
+            "other": "Līdz šim iegūti %(count)s notikumi"
+        },
+        "fetched_n_events_in_time": {
+            "zero": "",
+            "one": "Iegūts %(count)s notikums %(seconds)s sekundēs",
+            "other": "Iegūti %(count)s notikumi %(seconds)s sekundēs"
+        },
+        "fetched_n_events_with_total": {
+            "zero": "",
+            "one": "Iegūts %(count)s notikums no %(total)s",
+            "other": "Iegūti %(count)s notikumi no %(total)s"
+        },
+        "fetching_events": "Notikumu iegūšana...",
+        "file_attached": "Datne pievienota",
+        "format": "Formāts",
+        "from_the_beginning": "No sākuma",
+        "generating_zip": "ZIP ģenerēšana",
+        "html": "HTML",
+        "html_title": "Eksportētie dati",
+        "include_attachments": "Iekļaut pielikumus",
+        "json": "JSON",
+        "media_omitted": "Mediji izlaisti",
+        "media_omitted_file_size": "Informācijas nesējs izlaists - pārsniegts datnes izmēra ierobežojums",
+        "messages": "Ziņas",
+        "next_page": "Nākamā ziņu kopa",
+        "num_messages": "Ziņu skaits",
+        "num_messages_min_max": "Ziņu skaits var būt tikai skaitlis starp %(min)s un %(max)s",
+        "number_of_messages": "Norādiet ziņu skaitu",
+        "previous_page": "Iepriekšējā ziņu grupa",
+        "processing": "Apstrāde...",
+        "processing_event_n": "Apstrādā %(number)s. notikumu no %(total)s",
+        "select_option": "Lai eksportētu tērzēšanas no savas laika joslas, izvēlieties kādu no zemāk norādītajām iespējām",
+        "size_limit": "Izmēra ierobežojums",
+        "size_limit_min_max": "Izmērs var būt tikai skaitlis starp %(min)s MB un %(max)s MB",
+        "size_limit_postfix": "MB",
+        "starting_export": "Tiek uzsākta eksportēšana…",
+        "successful": "Eksportēšana ir veiksmīga",
+        "successful_detail": "Jūsu eksportēšana bija veiksmīga. Atrodiet to mapē Lejupielādes.",
+        "text": "Vienkāršs teksts",
+        "title": "Eksportēt tērzēšanu",
+        "topic": "Tēma: %(topic)s",
+        "unload_confirm": "Vai tiešām iziet šīs izgūšanas laikā?"
+    },
+    "failed_load_async_component": "Ielāde neizdevās! Pārbaudiet interneta savienojumu un mēģiniet vēlreiz.",
+    "feedback": {
+        "can_contact_label": "Tu vari sazināties ar mani, ja Tev ir kādi papildjautājumi",
+        "send_feedback_action": "Nosūtīt atsauksmi",
+        "sent": "Atsauksme nosūtīta"
+    },
+    "file_panel": {
+        "empty_description": "Pievienojiet failus no tērzēšanas vai vienkārši velciet un nometiet tos jebkur istabā.",
+        "empty_heading": "Šajā istabā nav redzamu datņu",
+        "guest_note": "Lai izmantotu šo funkcionalitāti, jums ir <a>jāreģistrējas</a>",
+        "peek_note": "Tev ir jāpievienojas istabai, lai redzētu tās datnes"
+    },
+    "forward": {
+        "filter_placeholder": "Meklēt istabas vai cilvēkus",
+        "message_preview_heading": "Ziņas priekšskatījums",
+        "no_perms_title": "Jums nav atļaujas to darīt",
+        "open_room": "Atvērt istabu",
+        "send_label": "Sūtīt",
+        "sending": "Sūta",
+        "sent": "Nosūtīts"
+    },
+    "identity_server": {
+        "description_connected": "Pašlaik tiek izmantots <server></server>, lai atklātu sev zināmās esošās kontaktpersonas un būtu atklājams tām. Identitāšu serveri var mainīt zemāk.",
+        "description_disconnected": "Pašlaik netiek izmantots neviens identitāšu serveris. Lai atklātu sev zināmos esošos kontaktus un būtu atklājams tiem, ir jāpievieno kāds zemāk.",
+        "description_optional": "Identitātes serveris ir izmantojams pēc izvēles. Ja izvēlas neizmantot identitātes serveri, citi cilvēki nevarēs atrast kontu, un nebūs iespējams uzaicināt citus pēc e-pasta adreses vai tālruņa numura.",
+        "error_connection": "Neizdevās pieslēgties identitāšu serverim",
+        "error_invalid_or_terms": "Nav pieņemti pakalpojuma nosacījumi vai identitātes serveris ir nederīgs.",
+        "no_terms": "Izvēlētajam identitāšu serverim nav nekādu pakalpojuma noteikumu.",
+        "url_field_label": "Ievadiet jaunu identitāšu serveri"
+    },
+    "in_space": "%(spaceName)s telpā.",
+    "in_space1_and_space2": "Telpās %(space1Name)s un %(space2Name)s.",
+    "in_space_and_n_other_spaces": {
+        "zero": "",
+        "one": "%(spaceName)s un vēl vienā telpā.",
+        "other": "%(spaceName)s un %(count)s citās telpās."
+    },
+    "info_tooltip_title": "Informācija",
+    "integration_manager": {
+        "error_connecting": "Savienošanas pārvaldnieks ir bezsaistē vai tas nevar sasniegt Tavu mājasserveri.",
+        "explainer": "Integrācijas pārvaldnieki saņem konfigurācijas datus un var modificēt logrīkus, sūtīt uzaicinājumus uz istabu, kā arī iestatīt lietotāju jaudas līmeni jūsu vārdā.",
+        "manage_title": "Pārvaldīt integrācijas"
+    },
+    "integrations": {
+        "disabled_dialog_title": "Integrācijas ir atspējotas"
+    },
+    "invite": {
+        "ask_anyway_description": "Nevar atrast profilus zemāk uzskaitītajiem Matrix ID. Vai jūs tomēr vēlētos sākt DM jebkurā gadījumā?",
+        "ask_anyway_label": "Sākt DM jebkurā gadījumā",
+        "ask_anyway_never_warn_label": "Sākt DM jebkurā gadījumā un nekad vairs nebrīdināt mani",
+        "email_caption": "Uzaicināt pa e-pastu",
+        "email_limit_one": "Uzaicinājumus pa e-pastu var nosūtīt tikai pa vienam",
+        "email_use_default_is": "Izmantot identitātes serveri, lai uzaicinātu pa e-pastu. <default>Izmantojiet noklusējumu (%(defaultIdentityServerName)s)</default> vai pārvaldiet <settings>Iestatījumos</settings>.",
+        "email_use_is": "Izmantojiet identitātes serveri, lai uzaicinātu ar epastu. Pārvaldiet <settings>iestatījumos</settings>.",
+        "error_already_invited_room": "Lietotājs jau ir uzaicināts uz istabu",
+        "error_already_invited_space": "Lietotājs jau ir uzaicināts uz telpu",
+        "error_already_joined_room": "Lietotājs jau atrodas istabā",
+        "error_already_joined_space": "Lietotājs jau atrodas telpā",
+        "error_bad_state": "Lietotājam jābūt atceltam pieejas liegumam pirms uzaicināšanas.",
+        "error_dm": "Mēs nevarējām izveidot jūsu DM.",
+        "error_find_room": "Kaut kas nogāja greizi, mēģinot uzaicināt lietotājus.",
+        "error_find_user_description": "Šādi lietotāji, iespējams, nepastāv vai ir nederīgi, tādējādi tos nevar uzaicināt: %(csvNames)s",
+        "error_find_user_title": "Neizdevās atrast sekojošos lietotājus",
+        "error_invite": "Mēs nevarējām uzaicināt šos lietotājus. Lūdzu, pārbaudiet lietotājus, kurus vēlaties uzaicināt, un mēģiniet vēlreiz.",
+        "error_permissions_room": "Nav atļaujas uzaicināt cilvēkus šajā istabā.",
+        "error_permissions_space": "Jums nav atļaujas aicināt cilvēkus uz šo telpu.",
+        "error_profile_undisclosed": "Lietotājs var eksistēt un var nebūt",
+        "error_transfer_multiple_target": "Zvanu var pāradresēt tikai vienam lietotājam.",
+        "error_unfederated_room": "Šī istaba nav federēta. Jūs nevarat uzaicināt cilvēkus no ārējiem serveriem.",
+        "error_unfederated_space": "Šī telpa nav federēta. Jūs nevarat uzaicināt cilvēkus no ārējiem serveriem.",
+        "error_unknown": "Nezināma servera kļūda",
+        "error_user_not_found": "Lietotājs neeksistē",
+        "error_version_unsupported_room": "Lietotāja mājasserveris neatbalsta istabas versiju.",
+        "error_version_unsupported_space": "Lietotāja mājasserveris neatbalsta vietas versiju.",
+        "failed_generic": "Darbība neizdevās",
+        "failed_title": "Neizdevās uzaicināt",
+        "invalid_address": "Neatpazīta adrese",
+        "name_email_mxid_share_room": "Uzaiciniet kādu personu, izmantojot vārdu, epasta adresi, lietotājvārdu (piemēram, <userId/>) vai <a>dalieties ar šo istabu</a>.",
+        "name_email_mxid_share_space": "Uzaiciniet kādu, izmantojot vārdu, e-pasta adresi, lietotājvārdu (piemēram, <userId/>) vai <a>dalīties ar šo telpu</a> .",
+        "name_mxid_share_room": "Uzaiciniet kādu, izmantojot vārdu, lietotājvārdu (piemēram, <userId/>) vai <a>dalīties ar šo istabu</a> .",
+        "name_mxid_share_space": "Uzaiciniet kādu, izmantojot vārdu, lietotājvārdu (piemēram, <userId/>) vai <a>dalīties ar šo telpu</a> .",
+        "recents_section": "Nesenās sarunas",
+        "room_failed_partial": "Pārējiem uzaicinājumi tika nosūtīti, bet zemāk norādītos cilvēkus uz <RoomName/> nevarēja uzaicināt",
+        "room_failed_partial_title": "Dažus uzaicinājumus nevarēja nosūtīt",
+        "room_failed_title": "Neizdevās uzaicināt lietotājus uz %(roomName)s",
+        "send_link_prompt": "Vai nosūtiet uzaicinājuma saiti",
+        "start_conversation_name_email_mxid_prompt": "Uzsāciet sarunu ar citiem, izmantojot vārdu, epasta adresi vai lietotājvārdu (piemērs - <userId/>).",
+        "start_conversation_name_mxid_prompt": "Uzsāciet sarunu ar citiem, izmantojot vārdu vai lietotājvārdu (piemērs - <userId/>).",
+        "suggestions_disclaimer": "Daži ieteikumi var būt slēpti dēļ privātuma.",
+        "suggestions_disclaimer_prompt": "Ja neredzat meklēto personu, nosūtiet tai uzaicinājuma saiti zemāk.",
+        "suggestions_section": "Nesenās tiešās sarakstes",
+        "to_room": "Uzaicināt uz %(roomName)s",
+        "to_space": "Uzaicināt uz %(spaceName)s",
+        "transfer_dial_pad_tab": "Cipartastatūra",
+        "transfer_user_directory_tab": "Lietotāju katalogs",
+        "unable_find_profiles_description_default": "Nevar atrast zemāk norādīto Matrix ID profilus - vai tomēr vēlaties tos uzaicināt?",
+        "unable_find_profiles_invite_label_default": "Uzaiciniet jebkurā gadījumā",
+        "unable_find_profiles_invite_never_warn_label_default": "Uzaicināt jebkurā gadījumā un nekad vairs nebrīdināt mani",
+        "unable_find_profiles_title": "Tālāk norādītie lietotāji var nepastāvēt",
+        "unban_first_title": "Lietotāju nevar uzaicināt, kamēr tam nav atcelta bloķēšana"
+    },
+    "inviting_user1_and_user2": "Uzaicina %(user1)s un %(user2)s",
+    "inviting_user_and_n_others": {
+        "zero": "",
+        "one": "Uzaicina %(user)s un vēl vienu",
+        "other": "Uzaicina %(user)s un %(count)s citus"
+    },
+    "items_and_n_others": {
+        "one": "<Items/> un viens cits",
+        "other": "<Items/> un %(count)s citus"
+    },
+    "keyboard": {
+        "activate_button": "Aktivizēt izvēlēto pogu",
+        "alt": "Alt",
+        "autocomplete_navigate_next": "Nākamais automātiskās pabeigšanas ieteikums",
+        "backspace": "Atpakaļatkāpe",
+        "category_room_list": "Istabu saraksts",
+        "composer_navigate_next_history": "Pāriet uz nākamo ziņu sastādītāja vēsturē",
+        "control": "Ctrl",
+        "end": "End",
+        "enter": "Enter",
+        "escape": "Esc",
+        "home": "Mājup",
+        "jump_room_search": "Pāriet uz istabu meklēšanu",
+        "navigate_next_history": "Nākamā nesen apmeklētā istaba vai vieta",
+        "navigate_next_message_edit": "Jāpāriet uz nākamo ziņu, lai labotu",
+        "next_room": "Nākamā istaba vai tiešā ziņa",
+        "next_unread_room": "Nākamā nelasītā istaba vai tiešā ziņa",
+        "number": "[skaitlis]",
+        "page_down": "Lapa uz leju",
+        "page_up": "Lapa uz augšu",
+        "prev_room": "Iepriekšējā istaba vai DM",
+        "prev_unread_room": "Iepriekšējā nelasītā istaba vai DM",
+        "room_list_collapse_section": "Sakļaut istabu saraksta sadaļu",
+        "room_list_expand_section": "Izvērst istabu saraksta sadaļu",
+        "room_list_navigate_down": "Pārvietojieties uz leju istabu sarakstā",
+        "room_list_navigate_up": "Pārvietojieties uz augšu istabu sarakstā",
+        "room_list_select_room": "Izvēlieties istabu no istabu saraksta",
+        "search": "Meklēšana (jābūt iespējotai)",
+        "shift": "Shift",
+        "space": "Atstarpe",
+        "toggle_microphone_mute": "Ieslēgt/izslēgt mikrofonu",
+        "upload_file": "Augšupielādēt datni"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "Atļaut tikai ekrāna kopīgošanas režīmu",
+        "ask_to_join": "Iespējot aicinājumus pievienoties",
+        "automatic_debug_logs": "Automātiski nosūtīt atkļūdošanas žurnālus par jebkuru kļūdu",
+        "automatic_debug_logs_decryption": "Automātiski nosūtīt atkļūdošanas žurnālus par atšifrēšanas kļūdām",
+        "automatic_debug_logs_key_backup": "Automātiski nosūtīt atkļūdošanas žurnālus, ja nedarbojas atslēgas dublēšana",
+        "beta_description": "Kas gaidāms %(brand)s? Laboratorijas ir labākais veids, kā agri iegūt un izmēģināt jaunas iespējas un palīdzēt tās pilnveidot, pirms tās tiek izlaistas.",
+        "beta_feature": "Šī ir beta funkcija",
+        "beta_feedback_leave_button": "Lai izietu no beta versijas, atveriet iestatījumus.",
+        "beta_feedback_title": "Atsauksmes par %(featureName)s beta versiju",
+        "beta_section": "Gaidāmās funkcijas",
+        "bridge_state": "Rādīt informāciju par tiltiem istabas iestatījumos",
+        "bridge_state_channel": "Kanāls: <channelLink/>",
+        "bridge_state_creator": "Šo tiltu nodrošināja <user />.",
+        "bridge_state_manager": "Šo tiltu pārvalda <user />.",
+        "bridge_state_workspace": "Darbavieta: <networkLink/>",
+        "click_for_info": "Noklikšķiniet, lai iegūtu vairāk informācijas",
+        "currently_experimental": "Pašlaik eksperimentāls.",
+        "custom_themes": "Atbalstīt pielāgotu motīvu pievienošanu",
+        "dynamic_room_predecessors": "Dinamiski istabas priekšteči",
+        "dynamic_room_predecessors_description": "Iespējot MSC3946 (lai nodrošinātu vēlu ierašanās istabu arhīvu atbalstu)",
+        "element_call_video_rooms": "Element Call video istabas",
+        "experimental_description": "Vai jūtaties gatavs eksperimentiem? Izmēģiniet mūsu jaunākās idejas izstrādes stadijā. Šīs funkcijas nav pabeigtas un var būt nestabilas, var mainīties vai vispār var tikt atmestas. <a> Uzzināt vairāk</a> .",
+        "experimental_section": "Agrīnie priekšskatījumi",
+        "feature_wysiwyg_composer_description": "Ziņu sastādītājā Markdown vietā izmantot bagātinātu tekstu.",
+        "group_calls": "Jauna grupas zvanu pieredze",
+        "group_developer": "Izstrādātājs",
+        "group_encryption": "Šifrēšana",
+        "group_experimental": "Eksperimentāls",
+        "group_messaging": "Ziņapmaiņa",
+        "group_moderation": "Moderēšana",
+        "group_profile": "Profils",
+        "group_rooms": "Istabas",
+        "group_spaces": "Telpas",
+        "group_themes": "Motīvi",
+        "group_voip": "Balss un video",
+        "group_widgets": "Logrīki",
+        "hidebold": "Slēpt paziņojumu punktu (rādīt tikai skaitītāju emblēmas)",
+        "html_topic": "Rādīt istabas tēmu HTML attēlojumu",
+        "join_beta": "Pievienoties beta versijai",
+        "join_beta_reload": "Pievienošanās beta izraisīs %(brand)s pārlādēšanu",
+        "jump_to_date": "Pāriet uz datumu (pievieno /jumptodate un pāriet uz datuma galvenēm)",
+        "jump_to_date_msc_support": "Nepieciešams, lai jūsu serveris atbalstītu MSC3030",
+        "latex_maths": "Atveidot LaTeX matemātiku ziņās",
+        "leave_beta": "Iziet no beta versijas",
+        "leave_beta_reload": "Izejot no beta versijas, tiks pārlādēts %(brand)s.",
+        "location_share_live": "Atrašanās vietas kopīgošana tiešsaistē",
+        "location_share_live_description": "Pagaidu realizācija. Atrašanās vietas saglabājas istabas vēsturē.",
+        "mjolnir": "Jauni veidi, kā neņemt vērā cilvēkus",
+        "msc3531_hide_messages_pending_moderation": "Ļaut moderatoriem paslēpt ziņojumus, gaidot moderēšanu.",
+        "notification_settings": "Jaunu paziņojumu iestatījumi",
+        "notification_settings_beta_caption": "Iepazīstinām ar vienkāršāku veidu, kā mainīt paziņojumu iestatījumus. Pielāgojiet savu %(brand)s tieši tā, kā jums patīk.",
+        "notification_settings_beta_title": "Paziņojumu iestatījumi",
+        "notifications": "Iespējot paziņojumu paneli istabas galvenē",
+        "render_reaction_images": "Atveidot pielāgotus attēlus reakcijās",
+        "render_reaction_images_description": "Dažkārt tās dēvē par \"pielāgotām emocijzīmēm\".",
+        "report_to_moderators": "Ziņot moderatoriem",
+        "report_to_moderators_description": "Istabās, kurās tiek nodrošināta moderēšana, poga \"Ziņot\" ļauj paziņot istabas moderatoriem par pārkāpumiem.",
+        "sliding_sync": "Sliding Sync režīms",
+        "sliding_sync_description": "Aktīvā izstrādes stadijā, to nevar atspējot.",
+        "sliding_sync_disabled_notice": "Lai atspējotu, izrakstieties un pierakstieties atkārtoti",
+        "sliding_sync_server_no_support": "Jūsu serverim trūkst atbalsta",
+        "under_active_development": "Aktīvas izstrādes stadijā.",
+        "unrealiable_e2e": "Neuzticams šifrētās istabās",
+        "video_rooms": "Video istabas",
+        "video_rooms_a_new_way_to_chat": "Jauns veids, kā tērzēt %(brand)s, izmantojot balsi un video.",
+        "video_rooms_always_on_voip_channels": "Video istabas vienmēr ir ieslēgti VoIP kanāli, kas ir iegulti %(brand)s istabā .",
+        "video_rooms_beta": "Video istabas ir beta funkcija",
+        "video_rooms_faq1_answer": "Izmantojiet pogu “+” kreisā paneļa istabu izvēles sadaļā.",
+        "video_rooms_faq1_question": "Kā izveidot video istabu?",
+        "video_rooms_faq2_answer": "Jā, tērzēšanas laika skala tiek parādīta līdzās videoklipam.",
+        "video_rooms_faq2_question": "Vai es varu izmantot teksta tērzēšanu kopā ar videozvanu?",
+        "video_rooms_feedbackSubheading": "Paldies, ka izmēģinājāt beta versiju. Lūdzu, izsakieties pēc iespējas detalizētāk, lai mēs varētu to uzlabot.",
+        "wysiwyg_composer": "Bagātināta teksta redaktors"
+    },
+    "labs_mjolnir": {
+        "ban_reason": "Neņemts vērā/aizturēts",
+        "error_adding_ignore": "Kļūda vērā neņemama lietotāja/servera pievienošanā",
+        "error_removing_ignore": "Kļūda vērā neņemama lietotāja/servera noņemšanā",
+        "explainer_1": "Šeit ir pievienojami lietotāji un serveri, kurus neņemt vērā. Jāizmanto zvaigznītes, lai %(brand)s atbilstu jebkurai rakstzīmei. Piemēram, <code>@bot:*</code> neņems vērā jebkuru lietotāju, kura vārds ir 'bot', jebkurā serverī.",
+        "explainer_2": "Cilvēku neņemšana vērā tiek īstenaota ar aizliegumu sarakstiem, kas satur kārtulas par tiem, kuri ir jāaizliedz. Aizliegumu saraksta abonēšana nozīmē, ka tajā norādītie lietotaji/serveri tiks paslēpti.",
+        "lists_description_2": "Ja tas nav vēlams, lūgums izmantot citu rīku lietotāju neņemšanai vērā.",
+        "personal_description": "Personīgais aizligumu saraksts satur visus lietotājus/serverus, no kuriem nav vēlmes redzēt ziņas. Pēc pirmā lietotāja/servera neņemšanas vērā istabu sarakstā parādīsies istaba ar nosaukumu '%(myBanList)s' - tajā ir jāpaliek, lai paturētu spēkā aizliegumu sarakstu.",
+        "personal_empty": "Pagaidām nav vērā neņemto.",
+        "personal_new_label": "Vērā neņemamā servera vai lietotāja Id",
+        "personal_section": "Pašlaik netiek ņemti vērā:",
+        "room_topic": "Šis ir jūsu bloķēto lietotāju/serveru saraksts - nepametiet istabu!",
+        "rules_empty": "Neviena",
+        "title": "Vērā neņemtie lietotāji"
+    },
+    "leave_room_dialog": {
+        "last_person_warning": "Jūs šeit esat vienīgais. Ja jūs to pametīsiet, nākotnē neviens vairs nevarēs pievienoties, ieskaitot jūs.",
+        "leave_room_question": "Vai tiešām pamest istabu '%(roomName)s'?",
+        "leave_space_question": "Vai tiešām pamest vietu '%(spaceName)s'?",
+        "room_leave_admin_warning": "Jūs esat vienīgais administrators šajā istabā. Ja jūs to pametīsiet, neviens nevarēs mainīt istabas iestatījumus vai veikt citas svarīgas darbības.",
+        "room_leave_mod_warning": "Jūs esat vienīgais moderators šajā telpā. Ja to pametīsiet, neviens nevarēs mainīt istabas iestatījumus vai veikt citas svarīgas darbības.",
+        "room_rejoin_warning": "Šī istaba nav publiska. Tai nebūs iespējams atkārtoti pievienoties bez uzaicinājuma.",
+        "space_rejoin_warning": "Šī telpa nav publiska. Jūs nevarēsit atkal pievienoties bez uzaicinājuma."
+    },
+    "lightbox": {
+        "rotate_left": "Rotēt pa kreisi",
+        "rotate_right": "Rotēt pa labi"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "Šis mājasserveris nav konfigurēts karšu attēlošanai.",
+        "MapStyleUrlNotReachable": "Šis mājasserveris nav pareizi konfigurēts karšu attēlošanai, vai arī konfigurētais karšu serveris nav sasniedzams.",
+        "WebGLNotEnabled": "WebGL ir nepieciešams, lai parādītu kartes. Lūdzu, iespējojiet to pārlūkprogrammas iestatījumos.",
+        "click_drop_pin": "Noklikšķiniet, lai nomestu spraudīti",
+        "click_move_pin": "Noklikšķiniet, lai pārvietotu spraudīti",
+        "close_sidebar": "Aizvērt sānjoslu",
+        "error_fetch_location": "Neizdevās iegūt atrašanās vietas datus",
+        "error_no_perms_description": "Tev ir jābūt atbilstošām atļaujām, lai kopīgotu atrašanās vietas šajā istabā.",
+        "error_no_perms_title": "Nav atļaujas kopīgot atrašanās vietu",
+        "error_send_description": "%(brand)s nevarēja nosūtīt Tavu atrašanās vietu. Lūgums vēlāk mēģināt vēlreiz.",
+        "error_send_title": "Mēs nevarējām nosūtīt Tavu atrašanās vietu",
+        "error_sharing_live_location": "Notikusi kļūda, kopīgojot reāllaika atrašanās vietu",
+        "error_stopping_live_location": "Notikusi kļūda, pārtraucot reāllaika atrašanās vietas kopīgošanu",
+        "expand_map": "Izvērst karti",
+        "failed_generic": "Neizdevās nolasīt atrašanās vietu. Lūgums vēlāk mēģināt vēlreiz.",
+        "failed_load_map": "Nevar ielādēt karti",
+        "failed_permission": "%(brand)s tika atteikta atļauja iegūt atrašanās vietu. Lūgums atļaut piekļuvi atrašanās vietai pārlūka iestatījumos.",
+        "failed_timeout": "Atrašanās vietas noasīšanas mēģinājumā iestājās noildze. Lūgums vēlāk mēģināt vēlreiz.",
+        "failed_unknown": "Nezināma kļūda, iegūstot atrašanās vietu. Lūdzu, mēģiniet vēlreiz vēlāk.",
+        "find_my_location": "Sameklēt manu atrašanās vietu",
+        "live_description": "%(displayName)s reāllaika atrašanās vieta",
+        "live_enable_description": "Lūgums ņemt vērā: šī ir laboratorijas iespēja, kas izmantot pagaidu īstenojumu. Tas nozīmē, ka nebūs iespējams izdzēst savu atrašanās vietu vēsturi, un pieredzējuši lietotāji varēs piekļūt atrašanās vietu vēsturei pat tad, kad šajā istabā tiks apturēta savas atrašanās vietas kopīgošana tiešraidē.",
+        "live_enable_heading": "Reāllaika atrašanās vietas kopīgošana",
+        "live_location_active": "Tu kopīgo savu atrašanās vietu tiešraidē",
+        "live_location_enabled": "Reāllaika atrašanās vietas kopīgošana iespējota",
+        "live_location_ended": "Reāllaika atrašanās vietas kopīgošana pārtraukta",
+        "live_location_error": "Reāllaika atrašanās vietas kopīgošanas kļūda",
+        "live_locations_empty": "Reāllaika atrašanās vietas kopīgošana nenotiek",
+        "live_share_button": "Kopīgot uz %(duration)s",
+        "live_toggle_label": "Iespējot reāllaika atrašanās vietas kopīgošanu",
+        "live_until": "Tiešraidē līdz %(expiryTime)s",
+        "live_update_time": "Atjaunināts %(humanizedUpdateTime)s",
+        "loading_live_location": "Ielādē tiešraides atrašanās vietu...",
+        "location_not_available": "Atrašanās vieta nav pieejama",
+        "map_feedback": "Atsauksmes par kartēm",
+        "mapbox_logo": "Mapbox logotips",
+        "reset_bearing": "Atiestatīt virzienu uz ziemeļiem",
+        "share_button": "Kopīgot atrašanās vietu",
+        "share_type_live": "Mana reāllaika atrašanās vieta",
+        "share_type_own": "Mana atrašanās vieta",
+        "share_type_pin": "Nomest spraudīti",
+        "share_type_prompt": "Kādu atrašanās vietas veidu vēlaties kopīgot?",
+        "toggle_attribution": "Pārslēgt attiecināšanu"
+    },
+    "member_list": {
+        "filter_placeholder": "Atfiltrēt istabas dalībniekus",
+        "invite_button_no_perms_tooltip": "Jums nav atļaujas uzaicināt lietotājus",
+        "power_label": "%(userName)s (tiesību līmenis %(powerLevelNumber)s)"
+    },
+    "member_list_back_action_label": "Istabas dalībnieki",
+    "message_edit_dialog_title": "Ziņas labojumi",
+    "migrating_crypto": "Uzgaidiet! Mēs atjauninām %(brand)s, lai šifrēšana būtu ātrāka un uzticamāka.",
+    "mobile_guide": {
+        "toast_accept": "Izmantot lietotni",
+        "toast_description": "%(brand)s mobilajā tīmekļa pārlūkprogrammā ir eksperimentāls. Lai iegūtu labāku pieredzi un jaunāko funkcionalitāti, izmantojiet mūsu bezmaksas mobilo lietotni.",
+        "toast_title": "Izmantojiet lietotni labākai pieredzei"
+    },
+    "name_and_id": "%(name)s (%(userId)s)",
+    "no_more_results": "Vairāk nekādu rezultātu nav",
+    "notif_panel": {
+        "empty_description": "Jums nav redzamu paziņojumu.",
+        "empty_heading": "Viss paveikts"
+    },
+    "notifications": {
+        "all_messages": "Visas ziņas",
+        "all_messages_description": "Saņemiet paziņojumu par katru ziņu",
+        "class_global": "Globāli",
+        "class_other": "Citi",
+        "default": "Noklusējuma",
+        "email_pusher_app_display_name": "E-pasta paziņojumi",
+        "enable_prompt_toast_description": "Iespējot darbvirsmas paziņojumus",
+        "enable_prompt_toast_title": "Paziņojumi",
+        "enable_prompt_toast_title_from_message_send": "Nepalaidiet garām atbildi",
+        "error_change_title": "Mainīt paziņojumu iestatījumus",
+        "keyword": "Atslēgvārds",
+        "keyword_new": "Jauns atslēgvārds",
+        "level_activity": "Aktivitātes",
+        "level_highlight": "Izcelt",
+        "level_muted": "Apklusināti",
+        "level_none": "Neviena",
+        "level_notification": "Paziņojums",
+        "level_unsent": "Nenosūtīti",
+        "mark_all_read": "Atzīmēt visus kā lasītus",
+        "mentions_and_keywords": "@pieminēšana un atslēgvārdi",
+        "mentions_and_keywords_description": "Saņemt paziņojumus tikai par pieminēšanu un atslēgvārdiem, kā tas ir norādīts <a>iestatījumos</a>.",
+        "mentions_keywords": "Pieminēšana un atslēgvārdi",
+        "message_didnt_send": "Ziņa netika nosūtīta. Noklikšķiniet, lai iegūtu vairāk informācijas.",
+        "mute_description": "Jūs nesaņemsit nekādus paziņojumus"
+    },
+    "notifier": {
+        "m.key.verification.request": "%(name)s pieprasa apliecināšanu"
+    },
+    "onboarding": {
+        "create_room": "Izveidot grupas tērzēšanu",
+        "explore_rooms": "Pārlūkot publiskas istabas",
+        "has_avatar_label": "Lieliski, tas ļaus cilvēkiem jūs atpazīt",
+        "intro_byline": "Pārvaldiet savas sarunas.",
+        "intro_welcome": "Laipni lūdzam, %(appName)s!",
+        "no_avatar_label": "Pievieno attēlu, lai cilvēki zinātu, ka tas esi Tu!",
+        "send_dm": "Nosūtīt tiešu ziņu",
+        "welcome_detail": "Tagad palīdzēsim uzsākt",
+        "welcome_user": "Laipni lūdzam, %(name)s!"
+    },
+    "pill": {
+        "permalink_other_room": "Ziņa %(room)s istabā",
+        "permalink_this_room": "Ziņa no %(user)s"
+    },
+    "poll": {
+        "create_poll_action": "Izveidot aptauju",
+        "create_poll_title": "Izveidot aptauju",
+        "disclosed_notes": "Balsotāji redz rezultātus, tiklīdz ir nobalsojuši",
+        "edit_poll_title": "Labot aptauju",
+        "end_description": "Vai tiešām izbeigt šo aptauju? Tas parādīs aptaujas galīgo iznākumu un liegs cilvēkiem iespēju balsot.",
+        "end_message": "Aptauja ir beigusies. Populārākā atbilde: %(topAnswer)s",
+        "end_message_no_votes": "Aptauja ir beigusies. Balsis netika nodotas.",
+        "end_title": "Pārtraukt aptauju",
+        "error_ending_description": "Atvainojiet, aptauja netika pārtraukta. Lūdzu, mēģiniet vēlreiz.",
+        "error_ending_title": "Neizdevās pārtraukt aptauju",
+        "error_voting_description": "Atvainojiet, jūsu balsojums netika reģistrēts. Lūdzu mēģiniet vēlreiz.",
+        "error_voting_title": "Balsojums nav reģistrēts",
+        "failed_send_poll_description": "Atvainojiet, aptauja, kuru mēģinājāt izveidot, netika publicēta.",
+        "failed_send_poll_title": "Neizdevās publicēt aptauju",
+        "notes": "Rezultāti tiks atklāti tikai pēc aptaujas beigām",
+        "options_add_button": "Pievienot variantu",
+        "options_heading": "Izveidot atbilžu variantus",
+        "options_label": "Variants %(number)s",
+        "options_placeholder": "Uzrakstiet variantu",
+        "topic_heading": "Kāds ir aptaujas jautājums vai tēma?",
+        "topic_label": "Jautājums vai temats",
+        "topic_placeholder": "Uzrakstiet kaut ko...",
+        "total_decryption_errors": "Atšifrēšanas kļūdu dēļ atsevišķas balsis var nebūt ieskaitītas",
+        "total_n_votes": {
+            "zero": "",
+            "one": "Nodota %(count)s balss. Nobalsojiet, lai redzētu rezultātus",
+            "other": "Nodotas %(count)s balsis. Nobalsojiet, lai redzētu rezultātus"
+        },
+        "total_n_votes_voted": {
+            "one": "Pamatojoties uz %(count)s balss",
+            "other": "Pamatojoties uz %(count)s balsīm"
+        },
+        "total_no_votes": "Nav balsojumu",
+        "total_not_ended": "Rezultāti būs redzami, kad aptauja būs pabeigta",
+        "type_closed": "Slēgta aptauja",
+        "type_heading": "Aptaujas veids",
+        "type_open": "Atvērt aptauju",
+        "unable_edit_description": "Atvaino, aptauju pēc balsu nodošanas nevar labot!",
+        "unable_edit_title": "Nevar labot aptauju"
+    },
+    "power_level": {
+        "admin": "Administrators",
+        "custom": "Pielāgots (%(level)s)",
+        "custom_level": "Pielāgots līmenis",
+        "default": "Noklusējuma",
+        "label": "Jaudas līmenis",
+        "moderator": "Moderators",
+        "restricted": "Ierobežots"
+    },
+    "presence": {
+        "away": "Prom",
+        "busy": "Aizņemts",
+        "idle": "Dīkstāvē",
+        "idle_for": "Dīkstāvē (neaktīvs) %(duration)s",
+        "offline": "Bezsaistē",
+        "offline_for": "Bezsaistē %(duration)s",
+        "online": "Tiešsaistē",
+        "online_for": "Tiešsaistē %(duration)s",
+        "unknown": "Nezināms",
+        "unknown_for": "Neskaidrā statusā %(duration)s",
+        "unreachable": "Lietotāja serveris nav sasniedzams"
+    },
+    "quick_settings": {
+        "all_settings": "Visi iestatījumi",
+        "metaspace_section": "Piespraust sānjoslai",
+        "sidebar_settings": "Papildus iespējas",
+        "title": "Ātrie iestatījumi"
+    },
+    "quit_warning": {
+        "call_in_progress": "Izskatās, ka esi zvanā. Vai tiešām iziet?",
+        "file_upload_in_progress": "Izskatās, ka šobrīd notiek datņu augšupielāde. Vai tiešām iziet?"
+    },
+    "redact": {
+        "confirm_button": "Apstiprināt noņemšanu",
+        "confirm_description": "Vai tiešām noņemt (izdzēst) šo notikumu)?",
+        "confirm_description_state": "Ņemiet vērā, ka šādu izmaiņu noņemšana istabā, var izraisīt veikto izmaiņu atcelšanu.",
+        "error": "Tu nevari izdzēst šo ziņu. (%(code)s)",
+        "ongoing": "Dzēš…",
+        "reason_label": "Iemesls (izvēles)"
+    },
+    "report_content": {
+        "description": "Ziņojuma par šo ziņu iesniegšana nosūtīs tās neatkārtojamo notikuma identifikatoru mājasservera pārvaldniekam. Ja ziņas šajā istabā ir šifrētas, mājasservera pārvaldnieks nevarēs lasīt ziņas saturu vai skatīt jebkādas datnes vai attēlus.",
+        "disagree": "Nepiekrist",
+        "error_create_room_moderation_bot": "Nevar izveidot istabu ar moderēšanas botu",
+        "hide_messages_from_user": "Atzīmējiet, vai vēlaties slēpt visas šī lietotāja pašreizējās un turpmākās ziņas.",
+        "ignore_user": "Neņemt vērā lietotāju",
+        "illegal_content": "Nelikumīgs saturs",
+        "missing_reason": "Lūdzu, norādiet ziņošanas iemeslu.",
+        "nature": "Lūdzu, izvēlieties pārkāpuma raksturu un aprakstiet, kāpēc šī ziņa ir uzskatāma par aizskarošu.",
+        "nature_disagreement": "Tas, ko šis lietotājs raksta, ir nepareizi.\nPar to tiks ziņots istabas moderatoriem.",
+        "nature_illegal": "Šis lietotājs veic nelikumīgas darbības, piemēram, publiskojot cilvēku dzīvesvietu vai draudot ar vardarbību.\nPar to tiks ziņots istabas moderatoriem, kuri to var nodot tiesībsargājošajām iestādēm.",
+        "nature_nonstandard_admin": "Šī istaba ir veltīta nelikumīgam vai kaitīgam saturam vai moderatori nespēj pārskatīt nelikumīgu vai kaitīgu saturu.\nPar šo tiks ziņots %(homeserver)s pārvaldītājiem.",
+        "nature_nonstandard_admin_encrypted": "Šī istaba ir veltīta nelikumīgam vai kaitīgam saturam vai moderatori nespēj pārskatīt nelikumīgu vai kaitīgu saturu.\nPar šo tiks ziņots %(homeserver)s pārvaldītājiem. Tie nevarēs lasīt šīs istabas šifrēto saturu.",
+        "nature_other": "Jebkāds cits iemesls. Lūdzu, aprakstiet problēmu.\nPar to tiks ziņots istabas moderatoriem.",
+        "nature_spam": "Šis lietotājs pārpludina istabu ar reklāmām, saitēm uz reklāmām vai propagandu.\nPar to tiks ziņots istabas moderatoriem.",
+        "nature_toxic": "Šis lietotājs demonstrē toksisku uzvedību, piemēram, apvainojot citus lietotājus vai kopīgojot tikai pieaugušajiem paredzēto saturu ģimenei draudzīgā istabā vai citādi pārkāpjot šīs istabas noteikumus.\nPar to tiks ziņots istabas moderatoriem.",
+        "other_label": "Citi",
+        "report_content_to_homeserver": "Ziņot par saturu mājasservera pārvaldniekam",
+        "report_entire_room": "Ziņot par visu istabu",
+        "spam_or_propaganda": "Surogātpasts vai propaganda",
+        "toxic_behaviour": "Toksiska uzvedība"
+    },
+    "restore_key_backup_dialog": {
+        "count_of_decryption_failures": "Neizdevās atšifrēt %(failedCount)s sesijas.",
+        "enter_key_title": "Ievadiet drošības atslēgu",
+        "incorrect_security_phrase_title": "Nepareiza slepenā frāze",
+        "key_forgotten_text": "Ja ir aizmirsta drošības atslēga, tad var <button>iestatīt jaunas atkopšanas iespējas</button>",
+        "phrase_forgotten_text": "Ja ir aizmirsta drošības vārdkopa, var <button1>izmantot savu drošības atslēgu</button1> vai <button2>iestatīt jaunas atkopšanas iespējas</button2>",
+        "recovery_key_mismatch_title": "Drošības atslēgas atšķiras"
+    },
+    "right_panel": {
+        "add_integrations": "Pievienot logrīkus, tiltus un botus",
+        "add_topic": "Pievienot tēmu",
+        "files_button": "Datnes",
+        "pinned_messages": {
+            "limits": {
+                "zero": "",
+                "one": "",
+                "other": "Varat piespraust ne vairāk kā %(count)s logrīkus"
+            }
+        },
+        "pinned_messages_button": "Piesprausts",
+        "poll": {
+            "active_heading": "Aktīvās aptaujas",
+            "empty_active": "Šajā istabā nav aktīvu aptauju",
+            "empty_active_load_more": "Aktīvu aptauju nav. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas",
+            "empty_active_load_more_n_days": {
+                "zero": "",
+                "one": "Šīs dienas laikā nav aktīvu aptauju. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas",
+                "other": "Pēdējo %(count)s dienu laikā nav aktīvu aptauju. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas"
+            },
+            "empty_past": "Šajā istabā nav iepriekšējo aptauju",
+            "empty_past_load_more": "Iepriekšējo aptauju nav. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas",
+            "empty_past_load_more_n_days": {
+                "zero": "",
+                "one": "Šīs dienas laikā nav iepriekšējo aptauju. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas",
+                "other": "Pēdējo %(count)s dienu laikā nav iepriekšējo aptauju. Ielādējiet vairāk aptauju, lai skatītu iepriekšējo mēnešu aptaujas"
+            },
+            "final_result": {
+                "one": "Gala rezultāts pamatojoties uz %(count)s balss",
+                "other": "Gala rezultāts pamatojoties uz %(count)s balsīm"
+            },
+            "load_more": "Ielādēt vairāk aptauju",
+            "loading": "Ielādē aptaujas",
+            "past_heading": "Iepriekšējās aptaujas",
+            "view_in_timeline": "Skatīt aptauju laika skalā",
+            "view_poll": "Skatīt aptauju"
+        },
+        "polls_button": "Aptaujas vēsture",
+        "room_summary_card": {
+            "title": "Istabas informācija"
+        },
+        "thread_list": {
+            "context_menu_label": "Pavedienu iespējas"
+        },
+        "video_room_chat": {
+            "title": "Tērzēšana"
+        }
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "Šis uzaicinājums tika nosūtīts uz %(email)s adresi, kas nav sasaistīta ar jūsu kontu",
+        "3pid_invite_email_not_found_account_room": "Šis uzaicinājums uz %(roomName)s tika nosūtīts uz %(email)s, kas nav saistīts ar Tavu kontu",
+        "3pid_invite_error_description": "Mēģinot pārbaudīt uzaicinājumu, tika atgriezta kļūda (%(errcode)s). Jūs varētu mēģināt nodot šo informāciju personai, kas jūs uzaicināja.",
+        "3pid_invite_error_invite_action": "Mēģināt pievienoties jebkurā gadījumā",
+        "3pid_invite_error_invite_subtitle": "Jūs varat pievienoties tikai ar derīgu uzaicinājumu.",
+        "3pid_invite_error_public_subtitle": "Jūs joprojām varat pievienoties šeit.",
+        "3pid_invite_error_title": "Kaut kas nogāja greizi ar jūsu uzaicinājumu.",
+        "3pid_invite_error_title_room": "Kaut kas nogāja greizi ar jūsu uzaicinājumu uz %(roomName)s",
+        "3pid_invite_no_is_subtitle": "Izmantojiet identitātes serveri sadaļā Iestatījumi, lai saņemtu uzaicinājumus pa tiešo uz %(brand)s .",
+        "banned_by": "%(memberName)s jūs aizliedza",
+        "banned_from_room_by": "%(memberName)s aizliedza Tev piekļuvi %(roomName)s",
+        "context_menu": {
+            "copy_link": "Ievietot istabas saiti starpliktuvē",
+            "favourite": "Izlase",
+            "forget": "Aizmirst istabu",
+            "low_priority": "Zems svarīgums",
+            "mark_read": "Atzīmēt kā izlasītu",
+            "mark_unread": "Atzīmēt kā nelasītu",
+            "notifications_default": "Saskaņā ar noklusējuma iestatījumu",
+            "notifications_mute": "Apklusināt istabu",
+            "title": "Istabas iespējas",
+            "unfavourite": "Izlasē"
+        },
+        "creating_room_text": "Mēs veidojam istabu ar %(names)s",
+        "dm_invite_action": "Uzsākt saraksti",
+        "dm_invite_subtitle": "<userName/> vēlas sarakstīties",
+        "dm_invite_title": "Vai vēlaties sarakstīties ar %(user)s?",
+        "drop_file_prompt": "Ievilkt datni šeit, lai augšupielādētu",
+        "edit_topic": "Rediģēt tematu",
+        "error_3pid_invite_email_lookup": "Nevar atrast lietotāju pēc e-pasta adreses",
+        "error_cancel_knock_title": "Neizdevās atcelt",
+        "error_join_403": "Jums ir nepieciešams uzaicinājums, lai piekļūtu šai istabai.",
+        "error_join_404_1": "Jūs mēģinājāt pievienoties, izmantojot istabas ID, nesniedzot serveru sarakstu, caur kuriem pievienoties. Istabu ID ir iekšējie identifikatori, un tos nevar izmantot, lai pievienotos istabai bez papildu informācijas.",
+        "error_join_404_2": "Ja ir zināma istabas adrese, jāmēģina pievienoties izmantojot to.",
+        "error_join_404_invite": "Persona, kas jūs uzaicināja, jau ir izgājusi, vai arī tās serveris ir bezsaistē.",
+        "error_join_404_invite_same_hs": "Persona, kas jūs uzaicināja, jau ir izgājusi.",
+        "error_join_connection": "Pievienošanās laikā atgadījās kļūda.",
+        "error_join_incompatible_version_1": "Atvainojamies, mājasserveris ir pārāk vecs, lai šeit piedalītos.",
+        "error_join_incompatible_version_2": "Lūgums sazināties ar mājasservera pārvaldītāju.",
+        "error_join_title": "Neizdevās pievienoties",
+        "error_jump_to_date": "Serveris atgrieza %(statusCode)s ar kļūdas kodu %(errorCode)s",
+        "error_jump_to_date_connection": "Atgadījās kļūda mēģinājumā atrast norādīto datumu un pārlēkt uz to. Tavs mājasserveris varētu nedarboties vai tas bija tikai īslaicīgs sarežģījums ar Tavu interneta savienojumu. Lūgums mēģināt vēlreiz. Ja tas turpinās, lūgums sazināties ar mājasservera pārvaldītāju.",
+        "error_jump_to_date_details": "Detalizēta informācija par kļūdu",
+        "error_jump_to_date_not_found": "Mēs nevarējām atrast notikumu pēc %(dateString)s. Mēģiniet izvēlēties agrāku datumu.",
+        "error_jump_to_date_send_logs_prompt": "Lūdzu, iesniedziet <debugLogsLink>atkļūdošanas žurnālus</debugLogsLink>, lai palīdzētu mums izsekot problēmas cēlonim.",
+        "error_jump_to_date_title": "Nevar atrast notikumu šajā datumā",
+        "face_pile_summary": {
+            "zero": "Neviens zināms cilvēks vēl nav pievienojies",
+            "one": "%(count)s zināms cilvēks ir jau pievienojies",
+            "other": "%(count)s zināmi cilvēki jau ir pievienojušies"
+        },
+        "face_pile_tooltip_label": {
+            "zero": "Apskatīt %(count)s dalībniekus",
+            "one": "Apskatīt %(count)s dalībnieku",
+            "other": "Apskatīt visus %(count)s dalībniekus"
+        },
+        "face_pile_tooltip_shortcut": "Ieskaitot %(commaSeparatedMembers)s",
+        "face_pile_tooltip_shortcut_joined": "Ieskaitot jūs, %(commaSeparatedMembers)s",
+        "failed_reject_invite": "Neizdevās noraidīt uzaicinājumu",
+        "forget_room": "Aizmirst šo istabu",
+        "forget_space": "Aizmirst šo telpu",
+        "header": {
+            "n_people_asking_to_join": {
+                "zero": "",
+                "one": "Aicina pievienoties",
+                "other": "%(count)s cilvēki aicina pievienoties"
+            },
+            "room_is_public": "Šī istaba ir publiska"
+        },
+        "header_face_pile_tooltip": "Dalībnieku saraksta pārslēgšana",
+        "header_untrusted_label": "Neuzticams",
+        "inaccessible": "Šī istaba vai telpa šobrīd nav pieejama.",
+        "inaccessible_name": "%(roomName)s šobrīd nav pieejama.",
+        "inaccessible_subtitle_1": "Mēģiniet vēlreiz vēlāk vai lūdziet istabas vai telpas administratoram pārbaudīt, vai jums ir piekļuve.",
+        "inaccessible_subtitle_2": "Mēģinot piekļūt istabai vai telpai, tika atgriezts %(errcode)s. Ja domājat, ka šis paziņojums ir kļūdains, lūdzu, <issueLink> iesniedziet ziņojumu par kļūdu</issueLink>.",
+        "intro": {
+            "dm_caption": "Tikai jūs abi esat šajā sarakstē, ja vien kāds no jums neuzaicina kādu pievienoties.",
+            "enable_encryption_prompt": "Iespējot šifrēšanu iestatījumos.",
+            "encrypted_3pid_dm_pending_join": "Kad visi būs pievienojušies, jūs varēsiet tērzēt",
+            "no_avatar_label": "Pievienojiet foto, lai padarītu istabu vieglāk pamanāmu citiem cilvēkiem.",
+            "no_topic": "<a>Pievienot tematu</a>, lai dotu cilvēkiem priekšstatu.",
+            "private_unencrypted_warning": "Privātās ziņas parasti ir šifrētas, bet šī istaba nav. Parasti tas ir neatbalstītas ierīces vai veida, piemēram, uzaicinājumu e-pastā dēļ.",
+            "room_invite": "Uzaicināt tikai uz šo istabu",
+            "send_message_start_dm": "Nosūtiet savu pirmo ziņu, lai uzaicinātu <displayName/> uz tērzēšanu",
+            "start_of_dm_history": "Šis ir sākums tiešās sarakstes vēsturei ar <displayName/>.",
+            "start_of_room": "Šis ir <roomName/> istabas pats sākums.",
+            "topic": "Temats: %(topic)s ",
+            "topic_edit": "Temats: %(topic)s (<a>redigēt</a>)",
+            "unencrypted_warning": "Pilnīga šifrēšana nav iespējota",
+            "user_created": "%(displayName)s izveidoja šo istabu.",
+            "you_created": "Tu izveidoji šo istabu."
+        },
+        "invite_email_mismatch_suggestion": "Kopīgojiet šo e-pastu sadaļā Iestatījumi, lai saņemtu ielūgumus tieši uz %(brand)s.",
+        "invite_sent_to_email": "Šis uzaicinājums tika nosūtīts uz %(email)s",
+        "invite_sent_to_email_room": "Šis uzaicinājums uz %(roomName)s tika nosūtīts %(email)s",
+        "invite_subtitle": "<userName/> uzaicināja Tevi",
+        "invite_this_room": "Uzaicināt uz šo istabu",
+        "invite_title": "Vai pievienoties %(roomName)s?",
+        "inviter_unknown": "Nezināms",
+        "invites_you_text": "<inviter/> uzaicina Tevi",
+        "join_button_account": "Reģistrēties",
+        "join_failed_needs_invite": "Lai skatītu %(roomName)s, jums ir nepieciešams uzaicinājums",
+        "join_the_discussion": "Pievienoties diskusijai",
+        "join_title": "Pievienoties istabai, lai piedalītos",
+        "join_title_account": "Pievienoties sarunai ar kontu",
+        "joining": "Pievienojas…",
+        "joining_room": "Pievienojas istabai…",
+        "joining_space": "Pievienojas vietai…",
+        "jump_read_marker": "Pāriet uz pirmo neizlasīto ziņu.",
+        "jump_to_bottom_button": "Ritināt uz jaunākajām ziņām",
+        "jump_to_date": "Pāriet uz datumu",
+        "jump_to_date_beginning": "Istabas sākums",
+        "jump_to_date_prompt": "Izvēlieties datumu, uz kuru pāriet",
+        "kick_reason": "Iemesls: %(reason)s",
+        "kicked_by": "%(memberName)s noņēma jūs",
+        "kicked_from_room_by": "%(memberName)s noņēma jūs no %(roomName)s istabas",
+        "knock_cancel_action": "Atcelt pieprasījumu",
+        "knock_denied_subtitle": "Tā kā jums ir liegta piekļuve, jūs nevarat atkārtoti pievienoties, ja vien jūs nav uzaicinājis grupas administrators vai moderators.",
+        "knock_denied_title": "Jums ir liegta piekļuve",
+        "knock_message_field_placeholder": "Ziņojums (pēc izvēles)",
+        "knock_prompt": "Aicināt pievienoties?",
+        "knock_prompt_name": "Aicināt pievienoties %(roomName)s?",
+        "knock_send_action": "Pieprasīt piekļuvi",
+        "knock_sent": "Pieprasījums pievienoties nosūtīts",
+        "knock_sent_subtitle": "Jūsu pievienošanās pieprasījums tiek izskatīts.",
+        "knock_subtitle": "Lai apskatītu sarunu vai piedalītos tajā, jums ir jāiegūst piekļuve šai istabai. Pieprasījumu pievienoties varat nosūtīt zemāk.",
+        "leave_error_title": "Izejot no istabas, radās kļūda",
+        "leave_server_notices_description": "Šī istaba tiek izmantota svarīgiem ziņojumiem no mājasservera, tāpēc to nevar pamest.",
+        "leave_server_notices_title": "Nevar pamest Server Notices istabu",
+        "leave_unexpected_error": "Mēģinot pamest istabu radās negaidīta servera kļūme",
+        "link_email_to_receive_3pid_invite": "Savienojiet iestatījumos šo e-pasta adresi ar savu kontu, lai uzaicinājumus saņemtu tieši %(brand)s.",
+        "loading_preview": "Priekšskatījuma ielāde",
+        "no_peek_join_prompt": "%(roomName)s nevar priekšskatīt. Vai pievienoties tai?",
+        "no_peek_no_name_join_prompt": "Priekšskatījuma nav pieejams, vai vēlaties pievienoties?",
+        "not_found_subtitle": "Vai tiešām esi pareizajā vietā?",
+        "not_found_title": "Šī istaba vai telpa neeksistē.",
+        "not_found_title_name": "%(roomName)s nepastāv.",
+        "peek_join_prompt": "Šis ir %(roomName)s istabas priekšskatījums. Vai vēlaties tai pievienoties?",
+        "read_topic": "Noklikšķiniet, lai lasītu tematu",
+        "rejecting": "Noraida uzaicinājumu...",
+        "rejoin_button": "Atkārtoti pievienoties",
+        "search": {
+            "all_rooms_button": "Meklēt visas istabas",
+            "this_room_button": "Meklēt šajā istabā"
+        },
+        "status_bar": {
+            "delete_all": "Izdzēst visu",
+            "exceeded_resource_limit": "Tava ziņa netika nosūtīta, jo šis mājasserveris ir pārsniedzis resursu ierobežojumu. Lūgums <a>sazināties ar pakalpojuma pārvaldītāju</a>, lai turpinātu izmantot pakalpojumu.",
+            "homeserver_blocked": "Tava ziņa netika nosūtīta, jo pārvaldītājs ir bloķējis šo mājasserveri. Lūgums <a>sazināties ar pakalpojuma pārvaldītāju</a>, lai turpinātu izmantot pakalpojumu.",
+            "monthly_user_limit_reached": "Tava ziņa netika nosūtīta, jo šis mājasserveris ir sasniedzis ikmēneša aktīvo lietotāju ierobežojumu. Lūgums <a>sazināties ar pakalpojuma pārvaldītāju</a>, lai turpinātu izmantot pakalpojumu.",
+            "requires_consent_agreement": "Jūs nevarat nosūtīt ziņojumus, kamēr neesat izskatījis un piekritis <consentLink>mūsu noteikumiem un nosacījumiem</consentLink>.",
+            "retry_all": "Atkārtoti mēģināt visu",
+            "select_messages_to_retry": "Var atlasīt visas vai atsevišķas ziņas, lai mēģinātu atkārtoti vai izdzēstu",
+            "server_connectivity_lost_description": "Sūtītās ziņas tiks saglabātas līdz brīdim, kad savienojums tiks atjaunots.",
+            "server_connectivity_lost_title": "Savienojums ar serveri pārtrūka.",
+            "some_messages_not_sent": "Daži no jūsu ziņojumiem nav nosūtīti"
+        },
+        "unknown_status_code_for_timeline_jump": "nezināms statusa kods",
+        "unread_notifications_predecessor": {
+            "zero": "Tev ir %(count)s nelasītu paziņojumu ieprieksējā šīs istabas versijā",
+            "one": "Tev ir %(count)s nelasīts paziņojums iepriekšējā šīs istabas versijā.",
+            "other": "Tev ir %(count)s nelasīti paziņojumi iepriekšējā šīs istabas versijā."
+        },
+        "upgrade_error_description": "Pārliecinieties, vai jūsu serveris atbalsta izvēlēto istabas versiju, un mēģiniet vēlreiz.",
+        "upgrade_error_title": "Atjauninot istabu, radās kļūda",
+        "upgrade_warning_bar": "Atjauninot šo istabu, tiks slēgta pašreizējā istabas instance un izveidota atjaunināta istaba ar tādu pašu nosaukumu.",
+        "upgrade_warning_bar_admins": "Tikai istabas administratori redzēs šo brīdinājumu",
+        "upgrade_warning_bar_unstable": "Šī istaba darbojas ar istabas versiju <roomVersion />, kuru šis mājasserveris ir atzīmējis kā <i>nenoturīgu</i>.",
+        "upgrade_warning_bar_upgraded": "Šī istaba jau ir atjaunināta.",
+        "upload": {
+            "uploading_multiple_file": {
+                "one": "Tiek augšupielādēts %(filename)s un %(count)s citi",
+                "other": "Tiek augšupielādēts %(filename)s un %(count)s citi"
+            },
+            "uploading_single_file": "Tiek augšupielādēts %(filename)s"
+        },
+        "waiting_for_join_subtitle": "Kad uzaicinātie lietotāji būs pievienojušies %(brand)s, jūs varēsiet tērzēt un istaba būs pilnībā šifrēta",
+        "waiting_for_join_title": "Gaida lietotāju pievienošanos %(brand)s"
+    },
+    "room_list": {
+        "add_room_label": "Pievienot istabu",
+        "add_space_label": "Pievienot telpu",
+        "breadcrumbs_empty": "Nav nesen apmeklētu istabu",
+        "breadcrumbs_label": "Nesen apmeklētās istabas",
+        "failed_add_tag": "Neizdevās istabai pievienot birku %(tagName)s",
+        "failed_remove_tag": "Neizdevās istabai noņemt birku %(tagName)s",
+        "failed_set_dm_tag": "Neizdevās iestatīt tiešās ziņas atzīmi",
+        "home_menu_label": "Sākumlapas opcijas",
+        "join_public_room_label": "Pievienoties publiskai istabai",
+        "joining_rooms_status": {
+            "zero": "Pašlaik pievienojas %(count)s istabām",
+            "one": "Pašlaik pievienojas %(count)s istabai",
+            "other": "Pašlaik pievienojas %(count)s istabām"
+        },
+        "notification_options": "Paziņojumu iespējas",
+        "redacting_messages_status": {
+            "zero": "",
+            "one": "Pašlaik tiek dzēstas ziņas %(count)s istabā",
+            "other": "Pašlaik tiek dzēstas ziņas %(count)s istabās"
+        },
+        "show_less": "Rādīt mazāk",
+        "show_n_more": {
+            "one": "Rādīt vēl %(count)s",
+            "other": "Rādīt vēl %(count)s"
+        },
+        "show_previews": "Rādīt ziņu priekšskatījumus",
+        "sort_by": "Kārtot pēc",
+        "sort_by_activity": "Aktivitātes",
+        "sort_by_alphabet": "A-Ž",
+        "sort_unread_first": "Rādīt istabas ar nelasītām ziņām augšpusē",
+        "space_menu_label": "%(spaceName)s izvēlne",
+        "sublist_options": "Saraksta iespējas",
+        "suggested_rooms_heading": "Ieteicamās istabas"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "Nosakiet, kas var skatīt un pievienoties %(spaceName)s.",
+            "title": "Piekļuve"
+        },
+        "advanced": {
+            "error_upgrade_description": "Istabas atjaunināšanu nevarēja pabeigt",
+            "error_upgrade_title": "Neizdevās atjaunināt istabu",
+            "information_section_room": "Informācija par istabu",
+            "information_section_space": "Informācija par telpu",
+            "room_id": "Iekšējais istabas ID",
+            "room_predecessor": "Skatīt senākas ziņas %(roomName)s istabā.",
+            "room_upgrade_button": "Atjaunināt šo istabu uz ieteicamo istabas versiju",
+            "room_upgrade_warning": "<b>Brīdinājums!</b> Istabas atjaunināšana <i>neietver automātisku istabas dalībnieku migrēšanu uz jauno istabas versiju</i>. Mēs ievietosim saiti uz jauno istabu vecajā istabas versijā un tās dalībniekiem būs jānoklikšķina uz šīs saites, lai pievienotos jaunajai istabai.",
+            "room_version": "Istabas versija:",
+            "room_version_section": "Istabas versija",
+            "space_predecessor": "Skatīt vecāku %(spaceName)s versiju.",
+            "space_upgrade_button": "Atjaunināt šo telpu uz ieteicamo istabas versiju",
+            "unfederated": "Šī istaba nav pieejama no citiem Matrix serveriem",
+            "upgrade_button": "Atjaunināt šo istabu līdz %(version)s versijai",
+            "upgrade_dialog_description": "Šīs istabas atjaunināšana prasa aizvērt pašreizējo istabas instanci un tās vietā izveidot jaunu istabu. Lai sniegtu istabas dalībniekiem vislabāko iespējamo pieredzi, mēs darīsim sekojošo:",
+            "upgrade_dialog_description_1": "Izveidot istabu ar to pašu nosaukumu, aprakstu un avataru",
+            "upgrade_dialog_description_2": "Atjaunināt jebkurus vietējās istabas aizstājvārdus, lai tie norādītu uz jauno istabu",
+            "upgrade_dialog_description_3": "Pārtraukt lietotāju runāšanu vecajā istabas versijā un publicēt ziņojumu, kurā lietotājiem tiek ieteikts pāriet uz jauno istabu",
+            "upgrade_dialog_description_4": "Jaunās istabas sākumā ievietojiet saiti uz veco istabu, lai cilvēki varētu apskatīt vecās ziņas",
+            "upgrade_dialog_title": "Istabas versijas atjaunināšana",
+            "upgrade_dwarning_ialog_title_public": "Atjaunināt publisko istabu",
+            "upgrade_warning_dialog_description": "Istabas atjaunināšana ir netriviāla darbība un to parasti ieteicams veikt, ja istaba ir nestabila kļūdu, trūkstošas funkcionalitātes vai drošības ievainojamību dēļ.",
+            "upgrade_warning_dialog_explainer": "<b>Lūdzu, ņemiet vērā, ka atjaunināšana izveidos jaunu istabas versiju</b>. Visas pašreizējās ziņas paliks šajā arhivētajā istabā.",
+            "upgrade_warning_dialog_footer": "Jūs jaunināsiet šo istabu no <oldVersion /> uz <newVersion /> versiju.",
+            "upgrade_warning_dialog_invite_label": "Automātiski uzaicināt dalībniekus no šīs istabas uz jauno",
+            "upgrade_warning_dialog_report_bug_prompt": "Tas parasti ietekmē tikai to, kā istaba tiek apstrādāta serverī. Ja jums ir problēmas ar savu %(brand)s, lūdzu, ziņojiet par kļūdu.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "Tas parasti ietekmē tikai to, kā istaba tiek apstrādāta serverī. Ja jums ir problēmas ar savu %(brand)s, lūdzu, <a>ziņojiet par kļūdu</a>.",
+            "upgrade_warning_dialog_title": "Istabas atjaunināšana",
+            "upgrade_warning_dialog_title_private": "Atjaunināt privāto istabu"
+        },
+        "alias_not_specified": "nav noteikts",
+        "bridges": {
+            "description": "Šī istaba pārsūta ziņojumus uz tālāk norādītajām platformām. <a>Uzzināt vairāk.</a>",
+            "empty": "Šī istaba nepārsūta ziņas uz nevienu platformu. <a>Uzzināt vairāk.</a>",
+            "title": "Tilti"
+        },
+        "delete_avatar_label": "Izdzēst iemiesojumu",
+        "general": {
+            "alias_field_has_domain_invalid": "Trūkst domēna atdalītāja (piemēram, :domain.org)",
+            "alias_field_has_localpart_invalid": "Trūkst istabas nosaukuma vai atdalītāja (piemēram, my-room:domain.org)",
+            "alias_field_matches_invalid": "Šī adrese nenorāda uz šo istabu",
+            "alias_field_placeholder_default": "piem., mana-istaba",
+            "alias_field_required_invalid": "Lūdzu, norādiet adresi",
+            "alias_field_safe_localpart_invalid": "Dažas rakstzīmes nav atļautas",
+            "alias_field_taken_invalid": "Šai adresei bija nederīgs serveris vai tā jau tiek izmantota",
+            "alias_field_taken_invalid_domain": "Šī adrese jau tiek izmantota",
+            "alias_field_taken_valid": "Šī adrese ir pieejama",
+            "alias_heading": "Istabas adrese",
+            "aliases_items_label": "Citas publiskotās adreses:",
+            "aliases_no_items_label": "Pagaidām nav nevienas publiskotas adreses, pievienojiet zemāk",
+            "aliases_section": "Istabas adreses",
+            "avatar_field_label": "Istabas avatars",
+            "canonical_alias_field_label": "Galvenā adrese",
+            "default_url_previews_off": "ULR priekšskatījumi šīs istabas dalībniekiem pēc noklusējuma ir atspējoti.",
+            "default_url_previews_on": "URL priekšskatījumi šīs istabas dalībniekiem pēc noklusējuma ir iespējoti.",
+            "description_space": "Rediģējiet ar jūsu telpu saistītos iestatījumus.",
+            "error_creating_alias_description": "Veidojot šo adresi, radās kļūda. Iespējams, serveris to neatļauj vai arī ir radusies īslaicīga kļūme.",
+            "error_creating_alias_title": "Veidojot adresi, radās kļūda",
+            "error_deleting_alias_description": "Noņemot šo adresi, radās kļūda. Iespējams, ka tā vairs nepastāv vai radās īslaicīga kļūda.",
+            "error_deleting_alias_description_forbidden": "Nav atļaujas izdzēst adresi.",
+            "error_deleting_alias_title": "Adreses noņemšanas kļūda",
+            "error_save_space_settings": "Neizdevās saglabāt telpas iestatījumus.",
+            "error_updating_alias_description": "Notikusi kļūda, mēģinot atjaunināt istabas alternatīvās adreses. Iespējams, tas ir liegts servera iestatījumos vai arī notikusi kāda pagaidu kļūme.",
+            "error_updating_canonical_alias_description": "Notikusi kļūda, mēģinot atjaunināt istabas galveno adresi. Iespējams, tas ir liegts servera iestatījumos vai arī notikusi kāda pagaidu kļūme.",
+            "error_updating_canonical_alias_title": "Kļūda galvenās adreses atjaunināšanā",
+            "leave_space": "Pamest telpu",
+            "local_alias_field_label": "Lokālā adrese",
+            "local_aliases_explainer_room": "Iestatīt adreses šai istabai, lai lietotāji varētu to atrast Tavā mājasserverī (%(localDomain)s)",
+            "local_aliases_explainer_space": "Iestatīt šīs vietas adreses, lai lietotāji varētu to atrast caur Tavu mājasserveri (%(localDomain)s)",
+            "local_aliases_section": "Lokālās adreses",
+            "name_field_label": "Istabas nosaukums",
+            "new_alias_placeholder": "Jauna publiska adrese (piemēram, #alias:server)",
+            "no_aliases_room": "Šai istabai nav lokālo adrešu",
+            "no_aliases_space": "Šai telpai nav lokālo adrešu",
+            "other_section": "Citi",
+            "publish_toggle": "Publicēt šo istabu publiskajā %(domain)s katalogā?",
+            "published_aliases_description": "Lai publicētu adresi, tā vispirms ir jāiestata kā lokālā adrese.",
+            "published_aliases_explainer_room": "Publicētās adreses var izmantot ikviens jebkurā serverī, lai pievienotos jūsu istabai.",
+            "published_aliases_explainer_space": "Publicētās adreses var izmantot ikviens jebkurā serverī, lai pievienotos jūsu telpai.",
+            "published_aliases_section": "Publiskotās adreses",
+            "save": "Saglabāt izmaiņas",
+            "topic_field_label": "Istabas temats",
+            "url_preview_encryption_warning": "Šifrētās istabās, tādās kā šī, URL priekšskatījumi pēc noklusējumi ir atspējoti, lai nodrošinātu, ka mājasserveris (kurā priekšskatījumi tiek izveidoti) nevar iegūt informāciju par šajā istabā redzamajām saitēm.",
+            "url_preview_explainer": "Kad kāds savā ziņā ievieto URL, var tikt parādīts priekšskatījums, lai sniegtu vairāk informācijas par saiti, kā piemēram, virsraksts, apraksts un attēls no tīmekļvietnes.",
+            "url_previews_section": "URL priekšskatījumi",
+            "user_url_previews_default_off": "Tu pēc noklusējuma esi <a>atspējojis</a> URL priekšskatījumus.",
+            "user_url_previews_default_on": "Tu pēc noklusējuma esi <a>iespējojis</a> URL priekšskatījumus."
+        },
+        "notifications": {
+            "browse_button": "Pārlūkot",
+            "custom_sound_prompt": "Iestatīt jaunu pielāgotu skaņu",
+            "notification_sound": "Paziņojumu skaņas signāli",
+            "settings_link": "Saņemiet paziņojumus, kā iestatīts jūsu <a>iestatījumos</a>",
+            "sounds_section": "Skaņas signāli",
+            "upload_sound_label": "Augšupielādēt pielāgotu skaņu",
+            "uploaded_sound": "Augšupielādētie skaņas signāli"
+        },
+        "people": {
+            "knock_empty": "Nav pieprasījumu",
+            "knock_section": "Aicinājums pievienoties",
+            "see_less": "Skatīt mazāk",
+            "see_more": "Skatīt vairāk"
+        },
+        "permissions": {
+            "add_privileged_user_description": "Piešķiriet vienam vai vairākiem lietotājiem šajā istabā vairāk privilēģiju",
+            "add_privileged_user_filter_placeholder": "Meklēt lietotājus šajā istabā…",
+            "add_privileged_user_heading": "Priviliģētu lietotāju pievienošana",
+            "ban": "Pieejas liegumi lietotājiem",
+            "ban_reason": "Iemesls",
+            "banned_by": "%(displayName)s liedzis pieeju",
+            "banned_users_section": "Lietotāji, kuriem liegta pieeja",
+            "error_changing_pl_description": "Mainot lietotāja jaudas līmeni, radās kļūda. Pārliecinieties, vai jums ir pietiekamas atļaujas, un mēģiniet vēlreiz.",
+            "error_changing_pl_reqs_description": "Mainot istabas jaudas līmeņa prasības, radās kļūda. Pārliecinieties, vai jums ir pietiekamas atļaujas, un mēģiniet vēlreiz.",
+            "error_changing_pl_reqs_title": "Jaudas līmeņa prasību izmaiņu kļūda",
+            "error_changing_pl_title": "Jaudas līmeņa izmaiņu kļūda",
+            "error_unbanning": "Neizdevās atbanot/atbloķēt (atcelt pieejas liegumu)",
+            "events_default": "Sūtīt ziņas",
+            "invite": "Uzaicināt lietotājus",
+            "kick": "Noņemt lietotājus",
+            "m.call": "Sākt %(brand)s zvanus",
+            "m.call.member": "Pievienoties %(brand)s zvaniem",
+            "m.reaction": "Nosūtīt reakcijas",
+            "m.room.avatar": "Mainīt istabas avataru",
+            "m.room.avatar_space": "Mainīt telpas avataru",
+            "m.room.canonical_alias": "Mainīt istabas galveno adresi",
+            "m.room.canonical_alias_space": "Mainīt telpas galveno adresi",
+            "m.room.encryption": "Iespējot istabas šifrēšanu",
+            "m.room.history_visibility": "Mainīt vēstures redzamību",
+            "m.room.name": "Nomainīt istabas nosaukumu",
+            "m.room.name_space": "Mainīt telpas nosaukumu",
+            "m.room.pinned_events": "Pārvaldīt piespraustos notikumus",
+            "m.room.power_levels": "Mainīt atļaujas",
+            "m.room.redaction": "Noņemt manis nosūtītās ziņas",
+            "m.room.server_acl": "Mainīt servera ACL",
+            "m.room.tombstone": "Atjaunināt istabu",
+            "m.room.topic": "Nomainīt tematu",
+            "m.room.topic_space": "Mainīt aprakstu",
+            "m.space.child": "Pārvaldīt istabas šajā telpā",
+            "m.widget": "Modificēt logrīkus",
+            "muted_users_section": "Apklusinātie lietotāji",
+            "no_privileged_users": "Šajā istabā nav lietotāju ar īpašām privilēģijām",
+            "notifications.room": "Apziņot visus",
+            "permissions_section": "Atļaujas",
+            "permissions_section_description_room": "Izvēlieties lomas, kas nepieciešamas, lai mainītu dažādus istabas parametrus",
+            "permissions_section_description_space": "Atlasiet lomas, kas nepieciešamas, lai mainītu dažādas telpas daļas",
+            "privileged_users_section": "Priviliģētie lietotāji",
+            "redact": "Dzēst citu sūtītas ziņas",
+            "send_event_type": "Nosūtīt %(eventType)s notikumus",
+            "state_default": "Mainīt iestatījumus",
+            "title": "Lomas un atļaujas",
+            "users_default": "Noklusējuma loma"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "Pēc iespējošanas istabas šifrēšanu nevar atspējot. Šifrētā istabā nosūtītās ziņas serveris nevar redzēt, to var tikai istabas dalībnieki. Šifrēšanas iespējošana var neļaut daudziem botiem un tiltiem darboties pareizi. <a>Uzziniet vairāk par šifrēšanu.</a>",
+            "enable_encryption_confirm_title": "Iespējot šifrēšanu?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Nav ieteicams pievienot šifrēšanu publiskām istabām.</b> Jebkurš var atrast un pievienoties publiskām istabām, tāpēc ikviens var lasīt tajās esošās ziņas. Jūs neiegūsiet nekādas sniegtās šifrēšanas priekšrocības, un vēlāk to nevarēsiet to izslēgt. Šifrējot ziņas publiskā istabā, ziņu saņemšana un sūtīšana kļūs lēnāka.",
+            "enable_encryption_public_room_confirm_description_2": "Lai izvairītos no šiem sarežģījumiem, jāizveido <a>jauna šifrēta istaba</a> iecerētajai sarunai.",
+            "enable_encryption_public_room_confirm_title": "Vai tiešām pievienot šifrēšanu šai publiskajai istabai?",
+            "encrypted_room_public_confirm_description_1": "<b>Šifrētas istabas nav ieteicams padarīt publiski pieejamas.</b> Tas nozīmē, ka jebkurš var atrast istabu un pievienoties tai, tātad jebkurš var lasīt ziņojumus. Jūs neiegūsiet nevienu no šifrēšanas sniegtajām priekšrocībām. Šifrējot ziņas publiskā istabā, ziņu saņemšana un nosūtīšana kļūs lēnāka.",
+            "encrypted_room_public_confirm_description_2": "Lai izvairītos no šem sarežģījumiem, jāizveido <a>jauna publiska istaba</a> iecerētajai sarunai.",
+            "encrypted_room_public_confirm_title": "Vai tiešām padarīt šo šifrēto istabu publisku?",
+            "encryption_forced": "Jūsu serveris pieprasa, lai šifrēšana ir atspējota.",
+            "encryption_permanent": "Šifrēšana nevar tikt atspējota, ja reiz tikusi iespējota.",
+            "error_join_rule_change_title": "Neizdevās atjaunināt pievienošanās noteikumus",
+            "error_join_rule_change_unknown": "Nezināma kļūme",
+            "guest_access_warning": "Cilvēki ar atbalstītām lietotnēm varēs pievienoties istabai bez reģistrēta konta.",
+            "history_visibility_invited": "Tikai dalībnieki (no to uzaicināšanas brīža)",
+            "history_visibility_joined": "Tikai dalībnieki (kopš pievienošanās)",
+            "history_visibility_legend": "Kas var lasīt vēsturi?",
+            "history_visibility_shared": "Tikai dalībnieki (no šī parametra iestatīšanas brīža)",
+            "history_visibility_warning": "Izmaiņas attiecībā uz to, kas var lasīt vēsturi, attieksies tikai uz nākamajiem ziņām šajā istabā. Esošās vēstures redzamība nemainīsies.",
+            "history_visibility_world_readable": "Ikviens",
+            "join_rule_description": "Nosakiet, kas var pievienoties %(roomName)s.",
+            "join_rule_invite": "Privāta (tikai ar ielūgumiem)",
+            "join_rule_invite_description": "Tikai uzaicināti cilvēki var pievienoties.",
+            "join_rule_knock": "Aicināt pievienoties",
+            "join_rule_knock_description": "Cilvēki nevar pievienoties, kamēr nav piešķirta piekļuve.",
+            "join_rule_public_description": "Ikviens var atrast un pievienoties.",
+            "join_rule_restricted": "Telpas dalībnieki",
+            "join_rule_restricted_description": "Ikviens telpā esošais var atrast un pievienoties. <a>Rediģējiet telpas, kurām šeit var piekļūt.</a>",
+            "join_rule_restricted_description_active_space": "Ikviens <spaceName/> telpā esošais var atrast un pievienoties. Jūs varat izvēlēties arī citas telpas.",
+            "join_rule_restricted_description_prompt": "Ikviens telpā esošais var atrast un pievienoties. Jūs varat izvēlēties vairākas telpas.",
+            "join_rule_restricted_description_spaces": "Telpas ar piekļuvi",
+            "join_rule_restricted_dialog_description": "Izlemiet, kuras telpas var piekļūt šai istabai. Ja telpa ir atlasīta, tās dalībnieki var atrast un pievienoties <RoomName/> .",
+            "join_rule_restricted_dialog_empty_warning": "Jūs noņemat visas telpas. Piekļuve pēc noklusējuma būs tikai ar uzaicinājumiem",
+            "join_rule_restricted_dialog_filter_placeholder": "Meklēt telpas",
+            "join_rule_restricted_dialog_heading_known": "Citas jums zināmas telpas",
+            "join_rule_restricted_dialog_heading_other": "Citas telpas vai istabas, kuras jūs, iespējams, nezināt",
+            "join_rule_restricted_dialog_heading_room": "Jums zināmās telpas, kuras satur šo istabu",
+            "join_rule_restricted_dialog_heading_space": "Jums zināmās telpas, kuras satur šo telpu",
+            "join_rule_restricted_dialog_title": "Atlasiet telpas",
+            "join_rule_restricted_n_more": {
+                "zero": "",
+                "one": "un vēl %(count)s",
+                "other": "un vēl %(count)s"
+            },
+            "join_rule_restricted_summary": {
+                "zero": "Pašlaik %(count)s vietu ir piekļuve",
+                "one": "Pašlaik %(count)s vietai ir piekļuve",
+                "other": "Pašlaik %(count)s vietām ir piekļuve"
+            },
+            "join_rule_restricted_upgrade_description": "Šis atjauninājums ļaus atlasīto telpu dalībniekiem piekļūt šai istabai bez uzaicinājuma.",
+            "join_rule_restricted_upgrade_warning": "Šī istabā atrodas dažās telpās, kurās jūs neesat administrators. Šajās telpās vecā istaba joprojām būs redzama, bet cilvēki tiks aicināti pievienoties jaunajai istabai.",
+            "join_rule_upgrade_awaiting_room": "Ielādē jaunu istabu",
+            "join_rule_upgrade_required": "Nepieciešama atjaunināšana",
+            "join_rule_upgrade_sending_invites": {
+                "zero": "",
+                "one": "Nosūta uzaicinājumu...",
+                "other": "Nosūta uzaicinājumus... (%(progress)s no %(count)s)"
+            },
+            "join_rule_upgrade_updating_spaces": {
+                "zero": "",
+                "one": "Atsvaidzina telpu...",
+                "other": "Atsvaidzina telpas... (%(progress)s no %(count)s)"
+            },
+            "join_rule_upgrade_upgrading_room": "Istabas atjaunināšana",
+            "public_without_alias_warning": "Lai izveidotu saiti uz šo istabu, lūdzu, pievienojiet adresi.",
+            "publish_room": "Padariet šo istabu redzamu publisko istabu katalogā.",
+            "publish_space": "Padariet šo telpu redzamu publisko istabu katalogā.",
+            "strict_encryption": "Nekad šajā sesijā nesūtīt šifrētas ziņas uz neapliecinātām sesijām",
+            "title": "Drošība un konfidencialitāte"
+        },
+        "title": "Istabas iestatījumi - %(roomName)s",
+        "upload_avatar_label": "Augšupielādēt avataru",
+        "visibility": {
+            "alias_section": "Adrese",
+            "error_failed_save": "Neizdevās atjaunināt šīs telpas redzamību",
+            "error_update_guest_access": "Neizdevās atjaunināt viesa piekļuvi šai telpai",
+            "error_update_history_visibility": "Neizdevās atjaunināt šīs telpas vēstures redzamību",
+            "guest_access_explainer": "Viesi var pievienoties telpai bez konta.",
+            "guest_access_explainer_public_space": "Tas var būt noderīgi publiskās telpās.",
+            "guest_access_label": "Iespējot piekļuvi viesiem",
+            "history_visibility_anyone_space": "Priekšskatīt telpu",
+            "history_visibility_anyone_space_description": "Ļaujiet cilvēkiem priekšskatīt jūsu telpu pirms pievienošanās.",
+            "history_visibility_anyone_space_recommendation": "Ieteicams publiskām telpām.",
+            "title": "Redzamība"
+        },
+        "voip": {
+            "call_type_section": "Zvana veids",
+            "enable_element_call_caption": "%(brand)s ir pilnībā šifrēts, bet pašlaik ir pieejams tikai mazākam lietotāju skaitam.",
+            "enable_element_call_label": "Iespējot %(brand)s kā papildu zvanīšanas iespēju šajā istabā",
+            "enable_element_call_no_permissions_tooltip": "Jums nav pietiekamu atļauju izmaiņu veikšanai."
+        }
+    },
+    "room_summary_card_back_action_label": "Istabas informācija",
+    "scalar": {
+        "error_create": "Neizdevās izveidot widžetu.",
+        "error_membership": "Tu neatrodies šajā istabā.",
+        "error_missing_room_id": "Trūkst roomId.",
+        "error_missing_room_id_request": "Iztrūkstošs room_id pieprasījumā",
+        "error_missing_user_id_request": "Iztrūkstošs user_id pieprasījumā",
+        "error_permission": "Nav atļaujas šai darbībai šajā istabā.",
+        "error_power_level_invalid": "Statusa līmenim ir jābūt pozitīvam skaitlim.",
+        "error_room_not_visible": "Istaba %(roomId)s nav redzama",
+        "error_room_unknown": "Šī istaba netika atpazīta.",
+        "error_send_request": "Neizdevās nosūtīt pieprasījumu.",
+        "failed_read_event": "Neizdevās nolasīt notikumus",
+        "failed_send_event": "Neizdevās nosūtīt notikumu"
+    },
+    "server_offline": {
+        "description_4": "Serveris bezsaistē."
+    },
+    "seshat": {
+        "reset_explainer": "Ja to darīsi, lūgums ņemt vērā, ka neviena no Tavām ziņām netiks izdzēsta, bet meklēšanas pieredze kādu brīdi var pasliktināties, kamēr indekss tiek izveidots no jauna",
+        "warning_kind_files": "Šī %(brand)s versija nenodrošina atsevišķu šifrētu datņu skatīšanu",
+        "warning_kind_files_app": "Jāizmanto <a>darbvirsmas lietotne</a>, lai redzētu visas šifrētās datnes",
+        "warning_kind_search_app": "Izmantojiet <a>lietotni</a>, lai veiktu šifrētu ziņu meklēšanu"
+    },
+    "setting": {
+        "help_about": {
+            "access_token_detail": "Piekļuves pilnvara sniedz pilnu piekļuvi kontam. To nevajag nevienam nodot.",
+            "brand_version": "%(brand)s versija:",
+            "clear_cache_reload": "Notīrīt kešatmiņu un pārlādēt",
+            "help_link": "Lai atvērtu %(brand)s izmantošanas palīdzību, jāklikšķina <a>šeit</a>.",
+            "homeserver": "Mājasserveris ir <code>%(homeserverUrl)s</code>",
+            "identity_server": "Identitātes serveris ir <code>%(identityServerUrl)s</code>",
+            "title": "Palīdzība un par lietotni",
+            "versions": "Versijas"
+        }
+    },
+    "settings": {
+        "all_rooms_home": "Rādīt visas istabas sākuma sadaļā",
+        "all_rooms_home_description": "Visas istabas, kurās atrodaties, parādīsies sākuma sadaļā.",
+        "always_show_message_timestamps": "Vienmēr rādīt ziņas laika zīmogu",
+        "appearance": {
+            "custom_font": "Izmantot sistēmas fontu",
+            "custom_font_description": "Jāiestata sistēmā uzstādīta fonta nosaukums un %(brand)s mēģinās to izmantot.",
+            "custom_font_name": "Sistēmas fonta nosaukums",
+            "custom_font_size": "Izmantot pielāgotu izmēru",
+            "custom_theme_error_downloading": "Kļūda izskata lejupielādēšanā",
+            "custom_theme_invalid": "Nederīga tēmas shēma.",
+            "font_size": "Šrifta izmērs",
+            "image_size_default": "Noklusējuma",
+            "image_size_large": "Liels",
+            "layout_bubbles": "Ziņu burbuļi",
+            "layout_irc": "IRC (eksperimentāls)",
+            "match_system_theme": "Pielāgoties sistēmas tēmai",
+            "timeline_image_size": "Attēla lielums laika skalā"
+        },
+        "automatic_language_detection_syntax_highlight": "Iespējot automātisko valodas noteikšanu sintakses iezīmējumiem",
+        "autoplay_gifs": "Automātiski atskaņot GIF",
+        "autoplay_videos": "Automātski atskaņot videoklipus",
+        "big_emoji": "Iespējot lielas emocijzīmes tērzēšanā",
+        "code_block_expand_default": "Izvērst koda blokus pēc noklusējuma",
+        "code_block_line_numbers": "Rādīt rindu numurus koda blokos",
+        "disable_historical_profile": "Ziņu vēsturē rādīt pašreizējo profila attēlu un vārdu",
+        "emoji_autocomplete": "Iespējot emocijzīmju ieteikumus rakstīšanas laikā",
+        "enable_markdown": "Iespējot Markdown",
+        "enable_markdown_description": "Sāciet ziņas ar <code>/plain</code>, lai nosūtītu tās bez markdown formatējuma.",
+        "general": {
+            "account_management_section": "Konta pārvaldība",
+            "account_section": "Konts",
+            "add_email_dialog_title": "Pievienot e-pasta adresi",
+            "add_email_failed_verification": "Neizdevās apstiprināt e-pasta adresi: jāpārliecinās, ka ir atvērta e-pasta ziņojumā esošā saite",
+            "add_email_instructions": "Mēs nosūtījām jums e-pastu, lai apstiprinātu jūsu adresi. Lūdzu, izpildiet tur sniegtos norādījumus un pēc tam noklikšķiniet uz tālāk esošās pogas.",
+            "add_msisdn_confirm_body": "Jānospiež zemāk esošā poga, lai apstiprinātu šī tālruņa numura pievienošanu.",
+            "add_msisdn_confirm_button": "Apstiprināt tālruņa numura pievienošanu",
+            "add_msisdn_confirm_sso_button": "Apstiprināt šī tālruņa numura pievienošanu, izmantojot vienoto pierakstīšanos savas identitātes apliecināšanai.",
+            "add_msisdn_dialog_title": "Pievienot tālruņa numuru",
+            "add_msisdn_instructions": "Teksta ziņa tika nosūtīta uz +%(msisdn)s. Lūgums ievadiet tajā esošo apliecināšanas kodu.",
+            "add_msisdn_misconfigured": "Pievienošana/saistīšana ar MSISDN plūsmu ir nepareizi konfigurēta",
+            "confirm_adding_email_body": "Nospiest zemāk esošo pogu, lai apstiprinātu šīs e-pasta adreses pievienošanu.",
+            "confirm_adding_email_title": "Apstiprināt e-pasta adreses pievienošanu",
+            "deactivate_confirm_body": "Vai tiešām deaktivizēt savu kontu? Tas ir neatgriezeniski.",
+            "deactivate_confirm_body_sso": "Apstipriniet konta deaktivizēšanu, izmantojot vienoto pierakstīšanos, lai pierādītu savu identitāti.",
+            "deactivate_confirm_content": "Apstipriniet, ka vēlaties deaktivizēt savu kontu. Ja turpināsiet:",
+            "deactivate_confirm_content_1": "Jūs nevarēsiet atkārtoti aktivizēt savu kontu",
+            "deactivate_confirm_content_2": "Jūs vairs nevarēsiet pierakstīties",
+            "deactivate_confirm_content_3": "Neviens nevarēs atkārtoti izmantot jūsu lietotājvārdu (MXID), tostarp jūs: šis lietotājvārds paliks nepieejams",
+            "deactivate_confirm_content_4": "Jūs pametīsit visas istabas un DM, kurās atrodaties",
+            "deactivate_confirm_content_5": "Jūs tiksiet noņemts no identitātes servera: jūsu draugi vairs nevarēs jūs atrast, izmantojot jūsu e-pastu vai tālruņa numuru",
+            "deactivate_confirm_content_6": "Jūsu vecās ziņas joprojām būs redzamas personām, kas tos saņēmuši iepriekš, analoģiski kā pagātnē nosūtītie e-pasta ziņojumi. Vai vēlaties slēpt savas nosūtītās ziņas no personām, kas pievienosies istabām nākotnē?",
+            "deactivate_confirm_continue": "Apstiprināt konta deaktivizēšanu",
+            "deactivate_confirm_erase_label": "Paslēpt manas ziņas no jaunpievienotiem lietotājiem",
+            "deactivate_section": "Deaktivizēt kontu",
+            "deactivate_warning": "Konta deaktivizēšana ir neatgriezeniska darbība — esiet piesardzīgs!",
+            "discovery_email_empty": "Atklāšanas opcijas parādīsies, tiklīdz būsiet pievienojis e-pastu.",
+            "discovery_email_verification_instructions": "Verificējiet saiti savā iesūtnē",
+            "discovery_msisdn_empty": "Atklāšanas opcijas parādīsies, kad būsiet pievienojis tālruņa numuru.",
+            "discovery_needs_terms": "Jāpiekrīt identitāšu servera (%(serverName)s) pakalpojuma noteikumiem, lai padarītu sevi atrodamu citiem pēc e-pasta adreses vai tālruņa numura.",
+            "email_address_in_use": "Šī epasta adrese jau tiek izmantota",
+            "email_address_label": "Epasta adrese",
+            "email_not_verified": "Jūsu e-pasta adrese vēl nav verificēta",
+            "email_verification_instructions": "Noklikšķiniet uz saites saņemtajā e-pastā, lai verificētu to, un pēc tam vēlreiz noklikšķiniet uz turpināt.",
+            "emails_heading": "Epasta adreses",
+            "error_add_email": "Neizdevās pievienot epasta adresi",
+            "error_deactivate_communication": "Sazinoties ar serveri, radās problēma. Lūdzu mēģiniet vēlreiz.",
+            "error_deactivate_invalid_auth": "Serveris neatgrieza derīgu autentifikācijas informāciju.",
+            "error_deactivate_no_auth": "Serveris nepieprasīja autentifikāciju",
+            "error_email_verification": "Neizdevās apstiprināt epasta adresi.",
+            "error_invalid_email": "Nepareiza epasta adrese",
+            "error_invalid_email_detail": "Šī neizskatās pēc derīgas epasta adreses",
+            "error_msisdn_verification": "Nevar verificēt tālruņa numuru.",
+            "error_password_change_403": "Neizdevās nomainīt paroli. Vai tā ir pareiza?",
+            "error_password_change_http": "%(errorMessage)s (HTTP statuss %(httpStatus)s)",
+            "error_password_change_title": "Paroles maiņas kļūda",
+            "error_password_change_unknown": "Nezināma paroles maiņas kļūda (%(stringifiedError)s)",
+            "error_remove_3pid": "Neizdevās dzēst kontaktinformāciju",
+            "error_revoke_email_discovery": "Nevar atsaukt e-pasta adreses kopīgošanu",
+            "error_revoke_msisdn_discovery": "Nevar atsaukt tālruņa numura kopīgošanu",
+            "error_share_email_discovery": "Nevar kopīgot e-pasta adresi",
+            "error_share_msisdn_discovery": "Nevar kopīgot tālruņa numuru",
+            "identity_server_no_token": "Netika atrasts neviena identitātes piekļuves pilnvara",
+            "identity_server_not_set": "Identitātes serveris nav iestatīts",
+            "language_section": "Valoda un reģions",
+            "msisdn_in_use": "Šis tālruņa numurs jau tiek izmantots",
+            "msisdn_label": "Tālruņa numurs",
+            "msisdn_verification_field_label": "Verifikācijas kods",
+            "msisdn_verification_instructions": "Lūdzu, ievadiet verifikācijas kodu, kas nosūtīts īsziņā.",
+            "msisdns_heading": "Tālruņa numuri",
+            "oidc_manage_button": "Pārvaldīt kontu",
+            "password_change_section": "Iestatīt jaunu konta paroli…",
+            "password_change_success": "Jūsu parole tika veiksmīgi nomainīta.",
+            "remove_email_prompt": "Dēst %(email)s?",
+            "remove_msisdn_prompt": "Dzēst %(phone)s?",
+            "spell_check_locale_placeholder": "Izvēlieties lokalizāciju"
+        },
+        "inline_url_previews_default": "Iespējot URL priekšskatījumus pēc noklusējuma",
+        "inline_url_previews_room": "Iespējot URL priekšskatījumus pēc noklusējuma visiem šīs istabas dalībniekiem",
+        "inline_url_previews_room_account": "Iespējot URL priekšskatījumus šajā istabā (ietekmē tikai Tevi)",
+        "insert_trailing_colon_mentions": "Jāievieto beigu kols pēc lietotāja pieminēšanas ziņas sākumā",
+        "jump_to_bottom_on_send": "Nosūtot ziņu, pāriet uz laika skalas beigām",
+        "key_backup": {
+            "backup_in_progress": "Jūsu atslēgas tiek dublētas (pirmā dublēšana var ilgt dažas minūtes).",
+            "backup_starting": "Sāk dublējumu...",
+            "backup_success": "Veiksmīgi!",
+            "cannot_create_backup": "Nevar izveidot atslēgu rezerves kopiju",
+            "create_title": "Izveidot atslēgu rezerves kopiju",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "Tagad jūsu atslēgas tiek dublētas no šīs ierīces.",
+                "backup_setup_success_title": "Droša dublējuma izveide ir veiksmīga",
+                "cancel_warning": "Ja tagad atcelsiet, gadījumā, ja zaudēsiet piekļuvi saviem pierakstīšanās datiem, varat zaudēt šifrētās ziņas un datus.",
+                "confirm_security_phrase": "Apstiprināt savu drošības vārdkopu",
+                "description": "Nodrošinieties pret piekļuves zaudēšanu šifrētām ziņām un datiem, dublējot šifrēšanas atslēgas savā serverī.",
+                "download_or_copy": "%(downloadButton)s vai %(copyButton)s",
+                "enter_phrase_description": "Ievadiet tikai jums zināmu drošības frāzi, jo tā tiks izmantota jūsu datu aizsardzībai. Drošības nolūkos neizmantojiet sava konta paroli kā drošības frāzi.",
+                "enter_phrase_title": "Ievadiet slepeno frāzi",
+                "enter_phrase_to_confirm": "Ievadiet savu drošības frāzi otro reizi, lai to apstiprinātu.",
+                "generate_security_key_description": "Mēs izveidosim drošības atslēgu, ko varat uzglabāt drošā vietā, piemēram, paroļu pārvaldniekā vai seifā.",
+                "generate_security_key_title": "Ģenerēt drošības atslēgu",
+                "pass_phrase_match_failed": "Nesakrīt.",
+                "pass_phrase_match_success": "Sakrīt!",
+                "phrase_strong_enough": "Lieliski! Šī slepenā frāze šķiet pietiekami sarežgīta.",
+                "secret_storage_query_failure": "Neizdodas pieprasīt slepenās glabātuves statusu",
+                "security_key_safety_reminder": "Glabājiet drošības atslēgu drošā vietā, piemēram, paroļu pārvaldniekā vai seifā, jo tā tiek izmantota jūsu šifrēto datu aizsardzībai.",
+                "set_phrase_again": "Atgriezties, lai iestatītu atkārtoti.",
+                "settings_reminder": "Varat arī iestatīt drošu dublēšanu un pārvaldīt atslēgas sadaļā Iestatījumi.",
+                "title_confirm_phrase": "Apstiprināt drošības vārdkopu",
+                "title_save_key": "Saglabājiet savu drošības atslēgu",
+                "title_set_phrase": "Iestatiet slepeno frāzi",
+                "unable_to_setup": "Nevar iestatīt slepeno krātuvi",
+                "use_different_passphrase": "Izmantot citu frāzveida paroli?",
+                "use_phrase_only_you_know": "Izmanto tikai sev zināmu slepeno frāzi un pēc izvēles saglabā drošības atslēgu, lai to izmantotu rezerves kopijām."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "Apstiprināt paroles vārdkopu",
+            "enter_passphrase": "Ievadiet frāzveida paroli",
+            "export_description_1": "Šis process ļauj eksportēt šifrētās istabās saņemto ziņu atslēgas uz vietējo failu. Vēlāk šo failu varēsiet importēt citā Matrix klientā, lai arī šis klients varētu atšifrēt šīs ziņas.",
+            "export_description_2": "Izgūtā datne ļaus ikvienam, kurš to var lasīt, atšifrēt jebkuru šifrētu ziņu, ko Tu vari redzēt, tāpēc tā rūpīgi jāglabā drošībā. Lai to nodrošinātu, Tev zemāk vajadzētu ievadīt neatkārtojamu paroles vārdkopu, kas tiks izmantota tikai izgūto datu šifrēšanai. Datus ievietot būs iespējams tikai ar šo pašu paroles vārdkopu.",
+            "export_title": "Eksportēt istabas atslēgas",
+            "file_to_import": "Ievietojamā datne",
+            "import_description_1": "Šis process ļaus jums importēt šifrēšanas atslēgas, kuras iepriekš eksportētas no cita Matrix klienta. Pēc tam varēsiet atšifrēt visas ziņas, kuras varēja atšifrēt uz otra klienta.",
+            "import_description_2": "Izgūšanas datne būs aizsargāta ar paroles vārdkopu. Tā ir jāievada šeit, lai atšifrētu datni.",
+            "import_title": "Importēt istabas atslēgas",
+            "phrase_cannot_be_empty": "Frāzveida parole nevar būt tukša",
+            "phrase_must_match": "Frāzveida parolēm ir jāsakrīt",
+            "phrase_strong_enough": "Lieliski! Šī piekļuves frāze izskatās pietiekami spēcīga"
+        },
+        "keyboard": {
+            "title": "Klaviatūra"
+        },
+        "notifications": {
+            "default_setting_description": "Šis iestatījums pēc noklusējuma tiks piemērots visām jūsu istabām.",
+            "default_setting_section": "Es vēlos saņemt paziņojumu par (noklusējuma iestatījums)",
+            "desktop_notification_message_preview": "Rādīt ziņas priekšskatījumu darbvirsmas paziņojumā",
+            "email_description": "Saņemt e-pasta kopsavilkumu par garām palaistajiem paziņojumiem",
+            "email_section": "E-pasta kopsavilkums",
+            "email_select": "Atlasiet, uz kuriem e-pastiem vēlaties sūtīt kopsavilkumus. Pārvaldiet savus e-pastus sadaļā <button>Vispārīgi</button> .",
+            "enable_audible_notifications_session": "Iespējot dzirdamus paziņojumus šai sesijai",
+            "enable_desktop_notifications_session": "Iespējot darbvirsmas paziņojumus šai sesijai",
+            "enable_email_notifications": "Iespējot e-pasta paziņojumus uz %(email)s",
+            "enable_notifications_account": "Iespējot paziņojumus šim kontam",
+            "enable_notifications_account_detail": "Izslēgt, lai atspējotu paziņojumus visās savās ierīcēs un sesijās",
+            "enable_notifications_device": "Iespējot paziņojumus šajā ierīcē",
+            "error_loading": "Ielādējot paziņojumu iestatījumus, radās kļūda.",
+            "error_permissions_denied": "%(brand)s nav atļauts nosūtīt Tev paziņojumus. Lūgums pārbaudīt sava pārlūka iestatījumus",
+            "error_permissions_missing": "%(brand)s nav piešķirta atļauja nosūtīt paziņojumus. Lūdzu mēģini vēlreiz",
+            "error_saving": "Kļūda saglabājot paziņojumu preferences",
+            "error_saving_detail": "Saglabājot jūsu paziņojumu preferences, radās kļūda.",
+            "error_title": "Neizdevās iespējot paziņojumus",
+            "error_updating": "Atjauninot paziņojumu preferences, radās kļūda. Lūdzu, mēģiniet vēlreiz pārslēgt opciju.",
+            "invites": "Uzaicināts uz istabu",
+            "keywords": "Rādīt <badge/> emblēmu, kad istabā tiek izmantoti atslēgvārdi.",
+            "keywords_prompt": "Ievadiet šeit atslēgvārdus vai izmantojiet izrunas variantus vai segvārdus",
+            "labs_notice_prompt": "<strong>Jaunums:</strong> Mēs esam vienkāršojuši paziņojumu iestatījumus, lai atvieglotu iespēju atrašanu. Daži iepriekš izvēlētie pielāgotie iestatījumi šeit netiek parādīti, taču tie joprojām ir aktīvi. Ja turpināsit, daži no jūsu iestatījumiem var mainīties. <a>Uzziniet vairāk</a>",
+            "mentions_keywords": "Pieminēšana un atslēgvārdi",
+            "mentions_keywords_only": "Tikai pieminēšana un atslēgvārdi",
+            "messages_containing_keywords": "Ziņas, kas satur atslēgvārdus",
+            "noisy": "Ar skaņu",
+            "notices": "Botu sūtītās ziņas",
+            "notify_at_room": "Paziņot, kad kāds piemin ar @room",
+            "notify_keyword": "Paziņot, kad kāds izmanto atslēgvārdu",
+            "notify_mention": "Paziņot, kad kāds piemin ar @displayname vai %(mxid)s",
+            "other_section": "Citas lietas, kas, mūsuprāt, jūs varētu interesēt:",
+            "people_mentions_keywords": "Cilvēki, pieminēšana un atslēgvārdi",
+            "play_sound_for_description": "Pēc noklusējuma pielietoti visām istabām visās ierīcēs.",
+            "play_sound_for_section": "Atskaņot skaņu",
+            "push_targets": "Paziņojumu adresāti",
+            "quick_actions_mark_all_read": "Atzīmēt visas ziņas kā izlasītas",
+            "quick_actions_reset": "Atiestatīt uz noklusējuma iestatījumiem",
+            "quick_actions_section": "Ātrās darbības",
+            "room_activity": "Notiek jaunas aktivitātes istabā, atjauninājumi un statusa ziņojumi",
+            "rule_call": "Uzaicinājuma zvans",
+            "rule_contains_display_name": "Ziņas, kuras satur manu parādāmo vārdu",
+            "rule_contains_user_name": "Ziņas, kuras satur manu lietotājvārdu",
+            "rule_encrypted": "Šifrētas ziņas grupu tērzēšanās",
+            "rule_encrypted_room_one_to_one": "Šifrētas ziņas viens pret vienu tērzēšanās",
+            "rule_invite_for_me": "Kad esmu uzaicināts/a istabā",
+            "rule_message": "Ziņas grupu tērzēšanās",
+            "rule_room_one_to_one": "Ziņas viens pret vienu tērzēšanās",
+            "rule_roomnotif": "Ziņas, kuras satur @room",
+            "rule_suppress_notices": "Botu nosūtītās ziņas",
+            "rule_tombstone": "Kad istabas ir atjauninātas",
+            "show_message_desktop_notification": "Parādīt ziņu darbvirsmas paziņojumos",
+            "voip": "Audio un videozvani"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "Iespējot aparatūras paātrināšanu (nepieciešama %(appName)s restartēšana, lai tas stātos spēkā)",
+            "always_show_menu_bar": "Vienmēr parādīt loga izvēlnes joslu",
+            "autocomplete_delay": "Automātiskās pabeigšanas aizkave (ms)",
+            "code_blocks_heading": "Koda bloki",
+            "compact_modern": "Izmantot kompaktāku 'Modern' izkārtojumu",
+            "composer_heading": "Komponists",
+            "enable_hardware_acceleration": "Iespējot aparatūras paātrinājumu",
+            "enable_tray_icon": "Rādīt paplātes ikonu un samazināt logu uz to aizvēršanas laikā",
+            "keyboard_heading": "Īsinājumtaustiņi",
+            "keyboard_view_shortcuts_button": "<a>Jāklikšķina šeit</a>, lai apskatītu visas tastatūras saīsnes.",
+            "media_heading": "Attēli, GIF un videoklipi",
+            "presence_description": "Kopīgojiet savas darbības un statusu ar citiem.",
+            "rm_lifetime": "Lasīšanas marķiera kalpošanas laiks (ms)",
+            "rm_lifetime_offscreen": "Lasīšanas marķiera darbības laiks ārpus ekrāna (ms)",
+            "room_directory_heading": "Istabu katalogs",
+            "room_list_heading": "Istabu saraksts",
+            "show_avatars_pills": "Rādīt iemiesojumus lietotāju, istabu un notikumu pieminējumos",
+            "show_polls_button": "Rādīt aptauju pogu",
+            "surround_text": "Iekļaut iezīmēto tekstu, rakstot speciālās rakstzīmes",
+            "time_heading": "Laika attēlošana"
+        },
+        "prompt_invite": "Pārjautāt pirms uzaicinājuma nosūtīšanas potenciāli nederīgiem matrix ID",
+        "replace_plain_emoji": "Automātiski aizstāt vienkāršā teksta emocijzīmes",
+        "security": {
+            "analytics_description": "Kopīgojiet anonīmus datus, lai palīdzētu mums noteikt problēmas. Nekā personīga. Trešās personas nav iesaistītas..",
+            "bulk_options_accept_all_invites": "Pieņemt visus %(invitedRooms)s uzaicinājumus",
+            "bulk_options_reject_all_invites": "Noraidīt visus %(invitedRooms)s uzaicinājumus",
+            "bulk_options_section": "Apjoma iespējas",
+            "e2ee_default_disabled_warning": "Jūsu servera administrators pēc noklusējuma ir atspējojis pilnīgu šifrēšanu privātajās istabās un tiešajos ziņās.",
+            "enable_message_search": "Iespējot ziņu meklēšanu šifrētās istabās",
+            "encryption_section": "Šifrēšana",
+            "ignore_users_empty": "Nav vērā neņemtu lietotāju.",
+            "ignore_users_section": "Vērā neņemtie lietotāji",
+            "key_backup_algorithm": "Algoritms:",
+            "key_backup_connect": "Savienojiet šo sesiju ar atslēgu dublēšanu",
+            "message_search_disable_warning": "Ja šī opcija ir atspējota, ziņas no šifrētām istabām neparādīsies meklēšanas rezultātos.",
+            "message_search_disabled": "Drošā veidā saglabājiet lokālajā kešatmiņā šifrētās ziņas, lai tās parādītos meklēšanas rezultātos.",
+            "message_search_enabled": {
+                "zero": "",
+                "one": "Drošā veidā saglabājiet lokālajā kešatmiņā šifrētās ziņas, izmantojot %(size)s %(rooms)s istabas ziņu saglabāšanai, lai tās parādītos meklēšanas rezultātos.",
+                "other": "Drošā veidā saglabājiet lokālajā kešatmiņā šifrētās ziņas, izmantojot %(size)s %(rooms)s istabām ziņu saglabāšanai, lai tās parādītos meklēšanas rezultātos."
+            },
+            "message_search_failed": "Ziņu meklēšanas inicializācija neizdevās",
+            "message_search_indexed_messages": "Indeksētās ziņas:",
+            "message_search_indexed_rooms": "Indeksētās istabas:",
+            "message_search_indexing": "Pašlaik indeksē: %(currentRoom)s",
+            "message_search_indexing_idle": "Pašlaik neindeksē ziņas nevienai istabai.",
+            "message_search_intro": "%(brand)s droši lokāli saglabā šifrētas ziņas kešatmiņā, lai tās tiktu parādītas meklēšanas rezultātos:",
+            "message_search_room_progress": "%(doneRooms)s no %(totalRooms)s",
+            "message_search_section": "Ziņu meklēšana",
+            "message_search_sleep_time": "Cik ātri būtu jālejupielādē ziņas.",
+            "message_search_space_used": "Izmantotā vieta:",
+            "message_search_unsupported": "%(brand)s trūkst dažu komponentu, kas nepieciešami šifrētu ziņu drošai glabāšanai lokālā kešatmiņā. Ja vēlaties eksperimentēt ar šo funkciju, uzbūvejiet pielāgotu %(brand)s Desktop ar <nativeLink>pievienotiem meklēšanas komponentiem</nativeLink> .",
+            "message_search_unsupported_web": "%(brand)s nevar droši glabāt lokālā kešatmiņā šifrētas ziņas, darbojoties tīmekļa pārlūkprogrammā. Izmantojiet <desktopLink>%(brand)s Desktop</desktopLink>, lai šifrēti ziņojumi tiktu parādīti meklēšanas rezultātos.",
+            "record_session_details": "Klienta nosaukuma, versijas un URL ierakstīšana palīdz vieglāk atpazīt sesijas sesiju pārvaldniekā",
+            "send_analytics": "Sūtīt analītikas datus",
+            "strict_encryption": "Nekad šajā sesijā nesūtīt šifrētas ziņas neapliecinātām sesijām"
+        },
+        "send_read_receipts": "Sūtīt lasīšanas apliecinājumus",
+        "send_read_receipts_unsupported": "Jūsu serveris neatbalsta lasīšanas apliecinājumu sūtīšanas atspējošanu.",
+        "send_typing_notifications": "Sūtīt paziņojumus par rakstīšanu",
+        "sessions": {
+            "best_security_note": "Visdrošāk ir apliecināt savas sesijas un atteikties no jebkuras sesijas, kas nav atpazīstama vai netiek vairs izmantota.",
+            "browser": "Pārlūks",
+            "confirm_sign_out": {
+                "zero": "Jāapstiprina atteikšanās no šīm ierīcēm.",
+                "one": "Jāapstiprina atteikšanās no šīs ierīces.",
+                "other": "Jāapstiprina atteikšanās no šīm ierīcēm."
+            },
+            "confirm_sign_out_body": {
+                "zero": "Jāklikšķina uz zemāk esošās pogas, lai apstiprinātu atteikšanos no šīm ierīcēm.",
+                "one": "Jāklikšķina uz zemāk esošās pogas, lai apstiprinātu atteikšanos no šīs ierīces.",
+                "other": "Jāklikšķina uz zemāk esošās pogas, lai apstiprinātu atteikšanos no šīm ierīcēm."
+            },
+            "confirm_sign_out_continue": {
+                "zero": "Atteikties no ierīcēm",
+                "one": "Atteikties no ierīces",
+                "other": "Atteikties no ierīcēm"
+            },
+            "confirm_sign_out_sso": {
+                "zero": "Jāapstiprina atteikšanās no šīm ierīcēm ar vienoto pierakstīšanos, lai pierādītu savu identitāti.",
+                "one": "Jāapstiprina atteikšanās no šīs ierīces ar vienoto pierakstīšanos, lai pierādītu savu identitāti.",
+                "other": "Jāapstiprina atteikšanās no šīm ierīcēm ar vienoto pierakstīšanos, lai pierādītu savu identitāti."
+            },
+            "current_session": "Pašreizējā sesija",
+            "desktop_session": "Darbvirsmas sesija",
+            "details_heading": "Sesijas izklāsts",
+            "device_unverified_description": "Apliecināt vai izrakstīties no šīs sesijas, lai nodrošinātu vislabāko drošību un uzticamību.",
+            "device_unverified_description_current": "Apliecini savu pašreizējo sesiju pastiprinātai drošajai ziņojumapmaiņai!",
+            "device_verified_description": "Šī sesija ir gatava drošai ziņojumapmaiņai.",
+            "device_verified_description_current": "Pašreizējā sesija ir gatava doršai ziņojumuapmaiņai.",
+            "error_pusher_state": "Neizdevās iestatīt bīdītāja stāvokli",
+            "error_set_name": "Neizdevās iestatīt sesijas nosaukumu",
+            "filter_all": "Visas",
+            "filter_inactive": "Neizmantotas",
+            "filter_inactive_description": "Neizmantotas %(inactiveAgeDays)s dienas vai ilgāk",
+            "filter_label": "Atsijāt ierīces",
+            "filter_unverified_description": "Nav gatavs drošai ziņojumapmaiņai",
+            "filter_verified_description": "Gatavs drošai ziņojumapmaiņai",
+            "hide_details": "Paslēpt izklāstu",
+            "inactive_days": "Neizmantots vismaz %(inactiveAgeDays)s dienas",
+            "inactive_sessions": "Neizmantotās sesijas",
+            "inactive_sessions_explainer_1": "Neizmantotās sesijas ir sesijas, kas kādu laiku nav izmantotas, bet tās turpina saņemt šifrēšanas atslēgas.",
+            "inactive_sessions_explainer_2": "Neizmantotu sesiju noņemšana uzlabo drošību un veiktspēju un padara vieglāk nosakāmu, vai jauna sesija ir aizdomīga.",
+            "inactive_sessions_list_description": "Apsver izrakstīšanos no vecām sesijām (%(inactiveAgeDays)s dienas vai vecākas), kas vairs netiek izmantotas.",
+            "ip": "IP adrese",
+            "last_activity": "Pēdējā darbība",
+            "mobile_session": "Viedierīces sesija",
+            "n_sessions_selected": {
+                "zero": "Atlasītas %(count)s sesiju",
+                "one": "Atlasīta %(count)s sesija",
+                "other": "Atlasītas %(count)s sesijas"
+            },
+            "no_inactive_sessions": "Netika atrasta neviena neizmantota sesija.",
+            "no_sessions": "Netika atrasta neviena sesija.",
+            "no_unverified_sessions": "Netika atrasta neviena neapliecināta sesija.",
+            "no_verified_sessions": "Netika atrasta neviena apliecināta sesija.",
+            "os": "Operētājsistēma",
+            "other_sessions_heading": "Citas sesijas",
+            "push_heading": "Pašpiegādes paziņojumi",
+            "push_subheading": "Saņemt pašpiegādes paziņojumus šajā sesijā.",
+            "push_toggle": "Pārslēgt pašpiegādes paziņojumus šajā sesijā.",
+            "rename_form_caption": "Lūgums ņemt vērā, ka sesiju nosaukumi ir redzami arī cilvēkiem, ar kuriem sazinies.",
+            "rename_form_heading": "Pārdēvēt sesiju",
+            "rename_form_learn_more": "Pārdēvē sesijas",
+            "rename_form_learn_more_description_1": "Citi lietotāji tiešajās ziņās un istabās, kurās pievienojies, var redzēt visu Tavu sesiju sarakstu.",
+            "rename_form_learn_more_description_2": "Tas viņiem nodrošina pārliecību, ka viņi patiešām runā ar Tevi, bet tas arī nozīmē, ka viņi var redzēt šeit ievadīto sesijas nosaukumu.",
+            "security_recommendations": "Drošības ieteikumi",
+            "security_recommendations_description": "Uzlabo sava konta drošību, ievērojot šos ieteikumus!",
+            "session_id": "Sesijas Id",
+            "show_details": "Rādīt izvērsumu",
+            "sign_in_with_qr": "Sasaistīt jaunu ierīci",
+            "sign_in_with_qr_button": "Rādīt kvadrātkodu",
+            "sign_in_with_qr_description": "Izmantot kvadrātkodu, lai pieteiktos citā ierīcē un uzstādītu drošu ziņojumapmaiņu.",
+            "sign_out": "Izrakstīties no šīs sesijas",
+            "sign_out_all_other_sessions": "Izrakstīties no visām pārējām sesijām (%(otherSessionsCount)s)",
+            "sign_out_confirm_description": {
+                "zero": "Vai tiešām atteikties no %(count)s sesijām?",
+                "one": "Vai tiešām atteikties no %(count)s sesijas?",
+                "other": "Vai tiešām atteikties no %(count)s sesijām?"
+            },
+            "sign_out_n_sessions": {
+                "zero": "Izrakstīties no %(count)s sesijām",
+                "one": "Izrakstīties no %(count)s sesijas",
+                "other": "Izrakstīties no %(count)s sesijām"
+            },
+            "title": "Sesijas",
+            "unknown_session": "Nezināms sesijas veids",
+            "unverified_session": "Neapliecināta sesija",
+            "unverified_session_explainer_1": "Šī sesija nenodrošina šifrēšanu, un tādēļ to nevar apliecināt.",
+            "unverified_session_explainer_2": "Ar šo sesiju nebūs iespējams piedalīties istabās, kurās ir iespējota šifrēšana.",
+            "unverified_session_explainer_3": "Lai nodrošinātu vislabāko drošību un privātumu, ir ieteicams izmantot Matrix klientus, kas nodrošina šifrēšanu.",
+            "unverified_sessions": "Neapliecinātās sesijas",
+            "unverified_sessions_explainer_1": "Neapliecinātās sesijas ir sesijas, kurās ir notikusi pieteikšanās ar Taviem pieteikšanās datiem, bet nav savstarpēji apliecinātas.",
+            "unverified_sessions_explainer_2": "Būtu īpaši jāpārliecinās, ka šīs sesijas ir atpazīstamas, jo tās varētu liecināt par neatļautu konta izmantošanu.",
+            "unverified_sessions_list_description": "Apliecini savas sesijas pastiprinātai drošajai ziņojumapmaiņai vai izraksties no tām, kuras nav atpazīstamas vai netiek vairs izmantotas!",
+            "url": "URL",
+            "verified_session": "Apliecināta sesija",
+            "verified_sessions": "Apliecinātās sesijas",
+            "verified_sessions_explainer_1": "Apliecinātās sesijas ir jebkur, kur tiek izmantots šis konts pēc savas paroles vārdkopas ievadīšanas vai savas identitātes apstiprināšanas ar citu apliecināto sesiju.",
+            "verified_sessions_explainer_2": "Tas nozīmē, ka Tev ir visas nepieciešamās atslēgas, lai atslēgtu savas šifrētās ziņas un apstiprinātu citiem lietotājiem, ka Tu uzticies šai sesijai.",
+            "verified_sessions_list_description": "Visdrošāk ir izrakstīties no jebkuras sesijas, kas nav atpazīstama vai netiek vairs izmantota.",
+            "verify_session": "Apliecināt sesiju",
+            "web_session": "Tīmekļa sesija"
+        },
+        "show_avatar_changes": "Rādīt profila attēla izmaiņas",
+        "show_breadcrumbs": "Rādīt saīsnes uz nesen skatītajām istabām istabu saraksta augšpusē",
+        "show_chat_effects": "Rādīt tērzēšanas efektus (piemēram, animācijas, kad tiek saņemts konfeti)",
+        "show_displayname_changes": "Rādīt parādāmā vārda izmaiņas",
+        "show_join_leave": "Rādīt pievienošanās/atstāšanas ziņas (netiek ietekmēti uzaicinājumi/noņemšana/aizliegumi)",
+        "show_nsfw_content": "Rādīt NSFW saturu",
+        "show_read_receipts": "Rādīt izlasīšanas apliecinājumus no citiem lietotājiem",
+        "show_redaction_placeholder": "Rādīt dzēstu ziņu vietturus",
+        "show_stickers_button": "Rādīt uzlīmju pogu",
+        "show_typing_notifications": "Rādīt paziņojumus par rakstīšanu",
+        "sidebar": {
+            "metaspaces_favourites_description": "Grupējiet savas iecienītākās istabas un personas vienuviet.",
+            "metaspaces_home_all_rooms": "Rādīt visas istabas",
+            "metaspaces_home_all_rooms_description": "Rādīt visas savas istabas sākumlapā, pat ja tās pievienotas kādai telpai.",
+            "metaspaces_home_description": "Sākums ir noderīgs, lai iegūtu pārskatu par visu.",
+            "metaspaces_orphans": "Istabas ārpus telpām",
+            "metaspaces_orphans_description": "Grupējiet visas telpām nepiesaistītās istabas vienuviet.",
+            "metaspaces_people_description": "Grupējiet visus savus cilvēkus vienuviet.",
+            "metaspaces_subsection": "Telpas, ko rādīt",
+            "metaspaces_video_rooms": "Video istabas un konferences",
+            "metaspaces_video_rooms_description": "Grupējiet visas privātās video istabas un konferences.",
+            "metaspaces_video_rooms_description_invite_extension": "Konferencēs jūs varat uzaicināt cilvēkus ārpus matrix.",
+            "spaces_explainer": "Telpas ir veids, kā grupēt istabas un cilvēkus. Papildus telpām, kurās atrodaties, varat izmantot arī dažas sākotnēji veidotās telpas.",
+            "title": "Sānjoslas"
+        },
+        "start_automatically": "Startēt pie ierīces ielādes",
+        "use_12_hour_format": "Rādīt laiku 12 stundu formātā (piemēram 2:30pm)",
+        "use_command_enter_send_message": "Lietot Command + Enter ziņas nosūtīšanai",
+        "use_command_f_search": "Lietot Command + F meklēšanai laika skalā",
+        "use_control_enter_send_message": "Lietot Ctrl + Enter ziņas nosūtīšanai",
+        "use_control_f_search": "Lietot Ctrl + F meklēšanai laika skalā",
+        "voip": {
+            "allow_p2p": "Atļaut Peer-to-peer 1:1 zvanos",
+            "allow_p2p_description": "Ja tas ir iespējots, otra puse var redzēt jūsu IP adresi",
+            "audio_input_empty": "Nav mikrofonu",
+            "audio_output": "Audio izeja",
+            "audio_output_empty": "Nav konstatētas audio izejas",
+            "auto_gain_control": "Automātiska pastiprinājuma kontrole",
+            "connection_section": "Savienojums",
+            "echo_cancellation": "Atbalss slāpēšana",
+            "enable_fallback_ice_server": "Atļaut rezerves zvanu palīgserveri (%(server)s)",
+            "enable_fallback_ice_server_description": "Attiecināms tikai tad, ja Tavs mājasserveris to nenodrošina. Tava IP adrese zvana laikā tiks kopīgota.",
+            "mirror_local_feed": "Rādīt spoguļskatā kameras video",
+            "missing_permissions_prompt": "Trūkst multivides atļauju. Lai pieprasītu, noklikšķiniet uz tālāk esošās pogas.",
+            "noise_suppression": "Trokšņu slāpēšana",
+            "request_permissions": "Pieprasīt multivides atļaujas",
+            "title": "Balss un video",
+            "video_input_empty": "Nav webkameru",
+            "video_section": "Video iestatījumi",
+            "voice_agc": "Automātiski pielāgot mikrofona skaļumu",
+            "voice_processing": "Balss apstrāde",
+            "voice_section": "Balss iestatījumi"
+        },
+        "warn_quit": "Brīdāt pirms pamešanas",
+        "warning": "<w> BRĪDINĀJUMS:</w> <description/>"
+    },
+    "share": {
+        "permalink_message": "Saite uz izvēlēto ziņu",
+        "permalink_most_recent": "Saite uz jaunāko ziņu",
+        "share_call": "Konferences uzaicinājuma saite",
+        "share_call_subtitle": "Saite ārējiem lietotājiem, lai pievienotos zvanam bez matrix konta:",
+        "title_link": "Saites kopīgošana",
+        "title_message": "Dalīties ar istabas ziņu",
+        "title_room": "Dalīties ar istabu",
+        "title_user": "Dalīties ar lietotāja kontaktdatiem"
+    },
+    "slash_command": {
+        "addwidget": "Pievieno istabai pielāgotu ekrānvadīklu ar URL",
+        "addwidget_iframe_missing_src": "iframe nav src atribūta",
+        "addwidget_invalid_protocol": "Lūgums norādīt ekrānvadīklas URL ar https:// vai http://",
+        "addwidget_missing_url": "Lūgums norādīt ekrānvadīklas URL vai iegulšanas kodu",
+        "addwidget_no_permissions": "Tu šajā istabā nevari mainīt ekrānvadīklas.",
+        "ban": "Liedz pieeju lietotājam ar norādīto id",
+        "category_actions": "Darbības",
+        "category_admin": "Administrators",
+        "category_advanced": "Papildu",
+        "category_effects": "Efekti",
+        "category_messages": "Ziņas",
+        "category_other": "Citi",
+        "command_error": "Komandas kļūda",
+        "converttodm": "Pārveido istabu par DM",
+        "converttoroom": "Pārveido DM par istabu",
+        "could_not_find_room": "Nevarēja atrast istabu",
+        "deop": "Atceļ operatora statusu lietotājam ar norādīto Id",
+        "devtools": "Atver izstrādātāja rīku logu",
+        "discardsession": "Piespiedu kārtā pārtrauc pašreizējo izejošo grupas sesiju šifrētajā istabā",
+        "error_invalid_rendering_type": "Komandas kļūda: Nevar atrast attēlošanas veidu (%(renderingType)s )",
+        "error_invalid_room": "Komanda neizdevās: Nevar atrast istabu (%(roomId)s )",
+        "error_invalid_runfn": "Komandas kļūda: Nevar apstrādāt slīpsvītras komandu.",
+        "error_invalid_user_in_room": "Nevarēja atrast istabas lietotāju",
+        "help": "Parāda komandu sarakstu ar pielietojumiem un aprakstiem",
+        "help_dialog_title": "Komandu palīdzība",
+        "holdcall": "Iepauzē sazvanu šajā istabā",
+        "html": "Nosūta ziņu kā HTML, to neinterpretējot kā Markdown",
+        "ignore": "Ignorē lietotāju, nerādot jums viņa sūtītās ziņas",
+        "ignore_dialog_description": "Jūs pašlaik ignorējat %(userId)s",
+        "ignore_dialog_title": "Vērā neņemts lietotājs",
+        "invite": "Uzaicina lietotāju ar norādīto id uz pašreizējo istabu",
+        "invite_3pid_needs_is_error": "Izmantojiet identitātes serveri, lai uzaicinātu pa e-pastu. Pārvaldība pieejama Iestatījumos.",
+        "invite_3pid_use_default_is_title": "Izmantot identitāšu serveri",
+        "invite_3pid_use_default_is_title_description": "Izmantojiet identitātes serveri, lai uzaicinātu pa e-pastu. Noklikšķiniet uz Turpināt, lai izmantotu noklusējuma identitātes serveri (%(defaultIdentityServerName)s) vai nomainītu to Iestatījumos.",
+        "invite_failed": "Lietotājs (%(user)s ) neparādās kā uzaicināts uz %(roomId)s, tomēr uzaicinātāja utilīta nav ziņojusi par kļūdu",
+        "join": "Pievienojas istabai ar šādu adresi",
+        "jumptodate": "Pāriet uz norādīto datumu laika skalā",
+        "jumptodate_invalid_input": "Mēs nevarējām saprast norādīto datumu (%(inputDate)s). Mēģiniet izmantot formātu GGGG-MM-DD.",
+        "lenny": "Pievieno ( ͡° ͜ʖ ͡°) pirms vienkārša teksta ziņas",
+        "me": "Parāda darbību",
+        "msg": "Nosūtīt ziņu dotajam lietotājam",
+        "myavatar": "Nomaina profila attēlu visās istabās",
+        "myroomavatar": "Nomaina Tavu profila attēlu tikai pašreizējā istabā",
+        "myroomnick": "Maina parādāmo segvārdu tikai pašreizējā istabā",
+        "nick": "Maina Tavu attēlojamo segvārdu",
+        "no_active_call": "Šajā istabā nav aktīva zvana",
+        "op": "Noteikt lietotāja jaudas līmeni",
+        "part_unknown_alias": "Neatpazīta istabas adrese: %(roomAlias)s",
+        "plain": "Nosūta ziņu kā vienkāršu tekstu, to neinterpretējot kā Markdown",
+        "query": "Atver tērzēšanu ar šo lietotāju",
+        "query_not_found_phone_number": "Tālruņa numuram nevar atrast Matrix ID",
+        "rageshake": "Nosūtīt kļūdas ziņojumu ar žurnāliem/logiem",
+        "rainbow": "Nosūta šo ziņu iekrāsotu varavīksnes krāsās",
+        "rainbowme": "Nosūta šo emociju iekrāsotu varavīksnes krāsās",
+        "remove": "Noņem lietotāju ar norādīto ID no šīs istabas",
+        "roomavatar": "Maina šīs istabas avataru",
+        "roomname": "Iestata istabas nosaukumu",
+        "server_error": "Servera kļūda",
+        "server_error_detail": "Serveris ir nesasniedzams, pārslogots, vai arī esi uzdūries kļūdai.",
+        "shrug": "Pievieno ¯\\_(ツ)_/¯ pirms vienkārša teksta ziņas",
+        "spoiler": "Nosūta norādīto ziņu kā spoileri",
+        "tableflip": "Pievieno (╯°□°)╯︵ ┻━┻ pirms vienkārša teksta ziņas",
+        "topic": "Nolasa vai iestata istabas tematu",
+        "topic_none": "Istabai nav temata.",
+        "topic_room_error": "Neizdevās iegūt istabas tēmu: Nevar atrast istabu (%(roomId)s",
+        "unban": "Atceļ pieejas liegumu lietotājam ar norādīto id",
+        "unflip": "Pievieno ┬──┬ ノ( ゜-゜ノ) pirms vienkārša teksta ziņas",
+        "unholdcall": "Šajā istabā iepauzētās sazvana atpauzēšana",
+        "unignore": "Aptur lietotāja neņemšanu vērā, rādot viņa turpmākās ziņas",
+        "unignore_dialog_description": "%(userId)s vairs netiek neņemts vērā",
+        "unignore_dialog_title": "Atcelta lietotāja neņemšana vērā",
+        "unknown_command": "Nezināma komanda",
+        "unknown_command_button": "Nosūtīt kā ziņu",
+        "unknown_command_detail": "Neatpazīta komanda: %(commandText)s",
+        "unknown_command_help": "Varat izmantot <code>/help</code>, lai parādītu pieejamās komandas. Vai jūs domājāt to nosūtīt kā ziņu?",
+        "unknown_command_hint": "Padoms: Sāciet ziņu ar <code>//</code>, lai sāktu to ar slīpsvītru.",
+        "upgraderoom": "Atjaunina istabu uz jaunu versiju",
+        "upgraderoom_permission_error": "Nav šīs komandas izmantošanai nepieciešamo atļauju.",
+        "usage": "Lietojums",
+        "view": "Skata istabu ar norādīto adresi",
+        "whois": "Parāda lietotāja informāciju"
+    },
+    "space": {
+        "add_existing_room_space": {
+            "create": "Vai tā vietā vēlaties pievienot jaunu istabu?",
+            "create_prompt": "Izveidot jaunu istabu",
+            "dm_heading": "Tiešā sarakste",
+            "error_heading": "Ne visi atlasītie tika pievienoti",
+            "progress_text": {
+                "zero": "",
+                "one": "Pievieno istabu...",
+                "other": "Pievieno istabas... (%(progress)s no %(count)s)"
+            },
+            "space_dropdown_label": "Telpas izvēle",
+            "space_dropdown_title": "Pievienot esošas istabas",
+            "subspace_moved_note": "Telpu pievienošana ir pārvietota."
+        },
+        "add_existing_subspace": {
+            "create_button": "Izveidot jaunu telpu",
+            "create_prompt": "Vai vēlaties tā vietā pievienot jaunu telpu?",
+            "filter_placeholder": "Meklēt telpas",
+            "space_dropdown_title": "Pievienot esošo telpu"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "Skatīt istabas laika grafiku (devtools)",
+            "explore": "Pārlūkot istabas",
+            "home": "Telpas sākumlapa",
+            "manage_and_explore": "Pārvaldiet un izpētiet istabas",
+            "options": "Telpas opcijas"
+        },
+        "failed_load_rooms": "Neizdevās ielādēt istabu sarakstu.",
+        "failed_remove_rooms": "Neizdevās noņemt dažas istabas. Vēlāk mēģiniet vēlreiz",
+        "incompatible_server_hierarchy": "Jūsu serveris neatbalsta telpu hierarhijas rādīšanu.",
+        "invite": "Uzaicināt cilvēkus",
+        "invite_description": "Uzaiciniet ar e-pastu vai lietotājvārdu",
+        "invite_link": "Dalīties ar uzaicinājuma saiti",
+        "joining_space": "Pievienojas",
+        "landing_welcome": "Laipni lūdzam <name/>!",
+        "leave_dialog_action": "Pamest telpu",
+        "leave_dialog_description": "Jūs gatavojaties pamest <spaceName/>.",
+        "leave_dialog_only_admin_room_warning": "Jūs esat vienīgais administrators vairākās istabās vai telpās, kuras vēlaties pamest. Tādējādi tās paliks bez administratoriem.",
+        "leave_dialog_only_admin_warning": "Jūs esat vienīgais šīs telpas administrators. Pametot to, nebūs neviena, kas varēs to pārvaldīt.",
+        "leave_dialog_option_all": "Pamest visas istabas",
+        "leave_dialog_option_intro": "Vai vēlaties pamest istabas šajā telpā?",
+        "leave_dialog_option_none": "Nepamest nevienu istabu",
+        "leave_dialog_option_specific": "Pamest dažas istabas",
+        "leave_dialog_public_rejoin_warning": "Jūs nevarēsit atkārtoti pievienoties, ja netiksiet atkārtoti uzaicināts.",
+        "leave_dialog_title": "Pamest %(spaceName)s",
+        "mark_suggested": "Atzīmēt kā ieteiktu",
+        "no_search_result_hint": "Iespējams, vēlēsities izmēģināt citu meklēšanu vai pārbaudīt drukas kļūdas.",
+        "preferences": {
+            "sections_section": "Parādāmās sadaļas",
+            "show_people_in_space": "Ši opcija grupē jūsu tērzēšanu ar šīs telpas dalībniekiem. Izslēdzot to, šīs tērzēšanas tiks paslēptas no jūsu %(spaceName)s izkārtojuma ."
+        },
+        "room_filter_placeholder": "Meklēt istabas",
+        "search_children": "Meklēt %(spaceName)s",
+        "search_placeholder": "Meklējiet nosaukumus un aprakstus",
+        "select_room_below": "Vispirms izvēlieties istabu zemāk",
+        "share_public": "Dalīties ar jūsu publisko telpu",
+        "suggested": "Ieteicamās",
+        "suggested_tooltip": "Šī istaba tiek ieteikta kā laba, kurai pievienoties",
+        "title_when_query_available": "Rezultāti",
+        "title_when_query_unavailable": "Istabas un telpas",
+        "unmark_suggested": "Atzīmēt kā neieteiktu",
+        "user_lacks_permission": "Nav atļaujas"
+    },
+    "space_settings": {
+        "title": "Iestatījumi - %(spaceName)s"
+    },
+    "spaces": {
+        "error_no_permission_add_room": "Jums nav atļauju pievienot istabas šai telpai",
+        "error_no_permission_add_space": "Jums nav atļauju pievienot telpas šai telpai",
+        "error_no_permission_create_room": "Nav atļauju veidot jaunas istabas šajā vietā",
+        "error_no_permission_invite": "Jums nav atļauju uzaicināt cilvēkus uz šo telpu"
+    },
+    "spotlight": {
+        "public_rooms": {
+            "network_dropdown_add_dialog_description": "Ievadiet nosaukumu jaunam serverim, kuru vēlaties pārlūkot.",
+            "network_dropdown_add_dialog_placeholder": "Servera nosaukums",
+            "network_dropdown_add_dialog_title": "Pievienot jaunu serveri",
+            "network_dropdown_add_server_option": "Pievienot jaunu serveri…",
+            "network_dropdown_available_invalid": "Nevar atrast šo serveri vai tā istabu sarakstu",
+            "network_dropdown_available_invalid_forbidden": "Jums nav atļauts skatīt šī servera istabu sarakstu",
+            "network_dropdown_available_valid": "Izskatās labi",
+            "network_dropdown_remove_server_adornment": "Noņemt serveri \"%(roomServer)s\"",
+            "network_dropdown_required_invalid": "Ievadiet servera nosaukumu",
+            "network_dropdown_selected_label": "Rādīt: Matrix istabas",
+            "network_dropdown_selected_label_instance": "Rādīt: %(instance)s istabas (%(server)s)",
+            "network_dropdown_your_server_description": "Tavs serveris"
+        }
+    },
+    "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Ja neredzat meklēto personu, nosūtiet tai uzaicinājuma saiti.",
+        "cant_find_room_helpful_hint": "Ja nevar atrast meklēto istabu, jāpalūdz uzaicinājums vai jāizveido jauna istaba.",
+        "copy_link_text": "Kopēt uzaicinājuma saiti",
+        "count_of_members": {
+            "zero": "",
+            "one": "%(count)s dalībnieks",
+            "other": "%(count)s dalībnieki"
+        },
+        "create_new_room_button": "Izveidot jaunu istabu",
+        "failed_querying_public_rooms": "Neizdevās pieprasīt publiskās istabas",
+        "failed_querying_public_spaces": "Neizdevās pieprasīt publiskās telpas",
+        "group_chat_section_title": "Citas iespējas",
+        "heading_with_query": "Izmantot \"%(query)s\" meklēšanai",
+        "heading_without_query": "Meklēt",
+        "keyboard_scroll_hint": "Lietojiet <arrows/> ritināšanai",
+        "other_rooms_in_space": "Citas istabas %(spaceName)s",
+        "public_rooms_label": "Publiskas istabas",
+        "public_spaces_label": "Publiskās telpas",
+        "recent_searches_section_title": "Nesenie meklējumi",
+        "recently_viewed_section_title": "Nesen skatītie",
+        "remove_filter": "Noņemt meklēšanas filtru %(filter)s",
+        "result_may_be_hidden_privacy_warning": "Daži rezultāti var būt slēpti dēļ privātuma",
+        "result_may_be_hidden_warning": "Atsevišķi rezultāti var būt slēpti",
+        "spaces_title": "Telpas, kurās atrodaties",
+        "start_group_chat_button": "Uzsākt grupas tērzēšanu"
+    },
+    "stickers": {
+        "empty": "Pašlaik nav iespējota neviena uzlīmju paka",
+        "empty_add_prompt": "Pievienot kādu tagad"
+    },
+    "terms": {
+        "column_document": "Dokuments",
+        "column_service": "Pakalpojums",
+        "column_summary": "Kopsavilkums",
+        "identity_server_no_terms_description_1": "Šai darbībai ir nepieciešama piekļuve noklusējuma identitāšu serverim <server />, lai pārbaudītu e-pasta adresi vai tālruņa numuru, taču serverim nav pakalpojuma noteikumu.",
+        "identity_server_no_terms_description_2": "Turpiniet tikai gadījumā, ja uzticaties servera īpašniekam.",
+        "identity_server_no_terms_title": "Identitāšu serverim nav pakalpojuma noteikumu",
+        "inline_intro_text": "Jāpieņem <policyLink />, lai turpinātu:",
+        "integration_manager": "Izmatot robotprogrammatūru, tiltus, ekrānvadīklas un uzlīmju pakas",
+        "intro": "Lai turpinātu, ir nepieciešams pieņemt šī pakalpojuma noteikumus.",
+        "summary_identity_server_1": "Atrast citus pēc tālruņa numura vai e-pasta adreses",
+        "summary_identity_server_2": "Esi atrodams pēc tālruņa numura vai e-pasta adreses",
+        "tac_button": "Pārskatīt noteikumus un nosacījumus",
+        "tac_description": "Lai turpinātu izmantot mājasserveri %(homeserverDomain)s, ir jāpārskata un jāpiekrīt mūsu noteikumiem un nosacījumiem.",
+        "tac_title": "Noteikumi un nosacījumi",
+        "tos": "Pakalpojuma noteikumi"
+    },
+    "theme": {
+        "light_high_contrast": "Gaišs augsts kontrasts",
+        "match_system": "Saskaņā ar sistēmu"
+    },
+    "thread_view_back_action_label": "Atpakaļ uz pavedienu",
+    "threads": {
+        "count_of_reply": {
+            "zero": "",
+            "one": "%(count)s atbilde",
+            "other": "%(count)s atbildes"
+        },
+        "show_thread_filter": "Rādīt:"
+    },
+    "threads_activity_centre": {
+        "no_rooms_with_threads_notifs": "Jums vēl nav istabu ar pavedienu paziņojumiem.",
+        "no_rooms_with_unread_threads": "Jums vēl nav istabu ar nelasītiem pavedieniem."
+    },
+    "time": {
+        "about_day_ago": "aptuveni dienu iepriekš",
+        "about_hour_ago": "aptuveni stundu iepriekš",
+        "about_minute_ago": "aptuveni minūti iepriekš",
+        "date_at_time": "%(date)s pulksten %(time)s",
+        "few_seconds_ago": "pirms dažām sekundēm",
+        "hours_minutes_seconds_left": "%(hours)sh %(minutes)s m %(seconds)s s atlicis",
+        "in_about_day": "aptuveni dienu kopš šī brīža",
+        "in_about_hour": "aptuveni stundu kopš šī brīža",
+        "in_about_minute": "aptuveni minūti kopš šī brīža",
+        "in_few_seconds": "dažas sekundes kopš šī brīža",
+        "in_n_days": "%(num)s dienas kopš šī brīža",
+        "in_n_hours": "%(num)s stundas kopš šī brīža",
+        "in_n_minutes": "%(num)s minūtes kopš šī brīža",
+        "left": "%(timeRemaining)s atlicis",
+        "minutes_seconds_left": "%(minutes)sm %(seconds)s s atlicis",
+        "n_days_ago": "%(num)s dienas iepriekš",
+        "n_hours_ago": "%(num)s stundas iepriekš",
+        "n_minutes_ago": "%(num)s minūtes iepriekš",
+        "seconds_left": "%(seconds)s sekundes atlikušas",
+        "short_days": "%(value)s d.",
+        "short_days_hours_minutes_seconds": "%(days)sd %(hours)sh %(minutes)sm %(seconds)ss",
+        "short_hours": "%(value)s st.",
+        "short_hours_minutes_seconds": "%(hours)sh %(minutes)sm %(seconds)ss",
+        "short_minutes": "%(value)s min.",
+        "short_minutes_seconds": "%(minutes)sm %(seconds)ss",
+        "short_seconds": "%(value)s sek."
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "Sakļaut atbildes pavedienu",
+            "external_url": "Avota URL adrese",
+            "open_in_osm": "Atvērt ar OpenStreetMap",
+            "report": "Ziņot",
+            "resent_unsent_reactions": "Atkārtoti nosūtīt %(unsentCount)s reakcija(s)",
+            "show_url_preview": "Rādīt priekšskatījumu",
+            "view_related_event": "Skatīt saistīto notikumu",
+            "view_source": "Skatīt pirmkodu"
+        },
+        "creation_summary_dm": "%(creator)s uzsāka šo tiešo saraksti.",
+        "creation_summary_room": "%(creator)s izveidoja un nokonfigurēja istabu.",
+        "decryption_failure": {
+            "historical_event_unverified_device": "Ir jāapliecina šī ierīce, lai piekļūtu senākām ziņām."
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
+        "download_action_decrypting": "Atšifrē",
+        "download_action_downloading": "Lejupielādē",
+        "edits": {
+            "tooltip_label": "Labots %(date)s. Klikšķināt, lai skatītu labojumus.",
+            "tooltip_sub": "Noklikšķiniet, lai skatītu labojumus",
+            "tooltip_title": "Labots %(date)s"
+        },
+        "error_no_renderer": "Šo notikumu nevarēja parādīt",
+        "error_rendering_message": "Nevar ielādēt šo ziņu",
+        "historical_messages_unavailable": "Jūs nevarat redzēt senākas ziņas",
+        "in_room_name": " <strong>%(room)s istabā</strong>",
+        "io.element.widgets.layout": "%(senderName)s ir atjauninājis istabas izkārtojumu",
+        "load_error": {
+            "no_permission": "Notika mēģinājums ielādēt noteiktu šīs istabas laikjoslas punktu, bet nav atļaujas skatīt attiecīgo ziņu.",
+            "title": "Neizdevās ielādēt laikpaziņojumu pozīciju",
+            "unable_to_find": "Mēģinājums ielādēt šīs istabas tērzēšanas vēstures izvēlēto posmu neizdevās, jo tas netika atrasts."
+        },
+        "m.audio": {
+            "error_downloading_audio": "Kļūda skaņas lejupielādēšanā",
+            "error_processing_audio": "Kļūda audio ziņas apstrādē",
+            "error_processing_voice_message": "Balss ziņas apstrādes kļūda",
+            "unnamed_audio": "Nenosaukts audio"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Skatīties reāllaika atrašanās vietu"
+        },
+        "m.call": {
+            "video_call_ended": "Videozvans beidzās",
+            "video_call_started": "Videozvans sākās %(roomName)s.",
+            "video_call_started_text": "%(name)s uzsāka videozvanu",
+            "video_call_started_unsupported": "Videozvans sākās %(roomName)s. (šī pārlūkprogramma neatbalsta)"
+        },
+        "m.call.hangup": {
+            "dm": "Zvans beidzās"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "Atbildēts citur",
+            "call_back_prompt": "Atzvanīt",
+            "declined": "Zvans noraidīts",
+            "failed_connect_media": "Nevarēja savienoties ar multividi",
+            "failed_connection": "Savienojums neizdevās",
+            "failed_opponent_media": "Viņu ierīce nevarēja iedarbināt kameru vai mikrofonu",
+            "missed_call": "Neatbildēts zvans",
+            "no_answer": "Nav atbildes",
+            "unknown_error": "Radās nezināma kļūda",
+            "unknown_failure": "Nezināma kļūme: %(reason)s",
+            "unknown_state": "Zvans ir nezināmā stāvoklī!",
+            "video_call": "%(senderName)s uzsāka video zvanu.",
+            "video_call_unsupported": "%(senderName)s uzsāka video zvanu. (Netiek atbalstīts šajā pārlūkā)",
+            "voice_call": "%(senderName)s uzsāka balss zvanu.",
+            "voice_call_unsupported": "%(senderName)s uzsāka balss zvanu. (Netiek atbalstīts šajā pārlūkā)"
+        },
+        "m.file": {
+            "error_decrypting": "Kļūda pielikuma atšifrēšanā",
+            "error_invalid": "Nederīga datne %(extra)s"
+        },
+        "m.image": {
+            "error": "Nevar parādīt attēlu kļūdas dēļ",
+            "error_decrypting": "Kļūda attēla atšifrēšanā",
+            "error_downloading": "Kļūda attēla lejupielādēšanā",
+            "sent": "%(senderDisplayName)s nosūtīja attēlu.",
+            "show_image": "Rādīt attēlu"
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "%(name)s vēlas veikt apliecināšanu",
+            "you_started": "Tu nosūtīji apliecināšanas pieprasījumu"
+        },
+        "m.location": {
+            "full": "%(senderName)s ir dalījies ar savu atrašanās vietu",
+            "location": "Kopīgoja atrašanās vietu: ",
+            "self_location": "Kopīgoja savu atrašanās vietu: "
+        },
+        "m.poll": {
+            "count_of_votes": {
+                "zero": "",
+                "one": "%(count)s balss",
+                "other": "%(count)s balsis"
+            }
+        },
+        "m.poll.end": {
+            "ended": "Beidzās aptauja",
+            "sender_ended": "%(senderName)s pārtrauca aptauju"
+        },
+        "m.poll.start": "%(senderName)s uzsāka aptauju- %(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "%(senderDisplayName)s mainīja istabas avataru.",
+            "changed_img": "%(senderDisplayName)s nomainīja istabas avataru uz <img/>",
+            "lightbox_title": "%(senderDisplayName)s nomainīja %(roomName)s istabas avataru",
+            "removed": "%(senderDisplayName)s dzēsa istabas avataru."
+        },
+        "m.room.canonical_alias": {
+            "alt_added": {
+                "one": "%(senderName)s pievienoja alternatīvo adresi %(addresses)s šai istabai.",
+                "other": "%(senderName)s pievienoja alternatīvās adreses %(addresses)s šai istabai."
+            },
+            "alt_removed": {
+                "one": "%(senderName)s dzēsa šīs istabas alternatīvo adresi %(addresses)s.",
+                "other": "%(senderName)s dzēsa šīs istabas alternatīvās adreses %(addresses)s."
+            },
+            "changed": "%(senderName)s nomainīja istabas adreses.",
+            "changed_alternative": "%(senderName)s nomainīja šīs istabas alternatīvās adreses.",
+            "changed_main_and_alternative": "%(senderName)s nomainīja istabas galveno un alternatīvo adresi.",
+            "removed": "%(senderName)s dzēsa galveno adresi šai istabai.",
+            "set": "%(senderName)s iestatīja istabas galveno adresi kā %(address)s."
+        },
+        "m.room.create": {
+            "continuation": "Šī istaba ir turpinājums citai sarunai.",
+            "see_older_messages": "Noklikšķiniet šeit, lai skatītu vecākas ziņas.",
+            "unknown_predecessor": "Nevar atrast veco šīs istabas versiju (istabas ID:%(roomId)s), un mums nav pieejams “via_servers”, lai to meklētu.",
+            "unknown_predecessor_guess_server": "Nevar atrast šīs telpas veco versiju (istabas ID:%(roomId)s), un mums nav pieejams 'via_servers' , lai to meklētu. Iespējams, ka noderēs servera uzminēšana pēc istabas ID. Ja vēlaties izmēģināt, noklikšķiniet uz šīs saites:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "Vērā neņemts mēģinājums atspējot šifrēšanu",
+            "disabled": "Šifrēšana nav iespējota",
+            "enabled": "Ziņas šajā istabā ir pilnībā šifrētas. Kad cilvēki pievienosies, Tu vari apstiprināt viņus viņu profilā, ir tikai jāpiesit viņu profila attēlam.",
+            "enabled_dm": "Ziņas šeit ir pilnībā šifrētas. Apstiprini %(displayName)s viņa profilā - piesit viņa profila attēlam!",
+            "enabled_local": "Ziņām šajā istabā tiek piemērota pilnīga šifrēšana.",
+            "parameters_changed": "Daži šifrēšanas parametri ir mainīti.",
+            "unsupported": "Šajā telpā izmantotā šifrēšana netiek atbalstīta."
+        },
+        "m.room.guest_access": {
+            "can_join": "%(senderDisplayName)s atļāva viesiem pievienoties istabai.",
+            "forbidden": "%(senderDisplayName)s aizliedza viesiem pievienoties istabai.",
+            "unknown": "%(senderDisplayName)s nomainīja viesu piekļuvi uz %(rule)s"
+        },
+        "m.room.history_visibility": {
+            "invited": "%(senderName)s padarīja istabas ziņu turpmāko vēsturi redzamu visiem istabas dalībniekiem no brīža, kad tie tika uzaicināti.",
+            "joined": "%(senderName)s padarīja istabas ziņu turpmāko vēsturi redzamu visiem istabas dalībniekiem ar brīdi, kad tie pievienojās.",
+            "shared": "%(senderName)s padarīja istabas ziņu turpmāko vēsturi redzamu visiem istabas dalībniekiem.",
+            "unknown": "%(senderName)s padarīja istabas ziņu turpmāko vēsturi redzamu nepazīstamajiem (%(visibility)s).",
+            "world_readable": "%(senderName)s padarīja istabas ziņu turpmāko vēsturi redzamu ikvienam."
+        },
+        "m.room.join_rules": {
+            "invite": "%(senderDisplayName)s padarīja istabu pieejamu tikai ar ielūgumiem.",
+            "knock": "%(senderDisplayName)s mainīja pievienošanās noteikumu, lai aicinātu pievienoties.",
+            "public": "%(senderDisplayName)s padarīja istabu publiski pieejamu visiem, kas zina saiti.",
+            "restricted": "%(senderDisplayName)s nomainīja, kas var pievienoties šai istabai.",
+            "restricted_settings": "%(senderDisplayName)s nomainīja, kas var pievienoties šai istabai. <a>Skatīt iestatījumus</a>.",
+            "unknown": "%(senderDisplayName)s nomainīja pievienošanās noteikumu uz %(rule)s"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "%(targetName)s pieņēma uzaicinājumu uz %(displayName)s",
+            "accepted_invite": "%(targetName)s pieņēma uzaicinājumu",
+            "ban": "%(senderName)s liedza pieeju %(targetName)s",
+            "ban_reason": "%(senderName)s liedza pieeju %(targetName)s: %(reason)s",
+            "change_avatar": "%(senderName)s nomainīja savu profila attēlu",
+            "change_name": "%(oldDisplayName)s nomainīja savu redzamo vārdu uz %(displayName)s",
+            "change_name_avatar": "%(oldDisplayName)s nomainīja savu attēlojamo vārdu un profila attēlu",
+            "invite": "%(senderName)s uzaicināja %(targetName)s",
+            "join": "%(targetName)s pievienojās istabai",
+            "kick": "%(senderName)s noņemts %(targetName)s",
+            "kick_reason": "%(senderName)s noņemts%(targetName)s: %(reason)s",
+            "left": "%(targetName)s pameta istabu",
+            "left_reason": "%(targetName)s pameta istabu: %(reason)s",
+            "no_change": "%(senderName)s neizdarīja izmaiņas",
+            "reject_invite": "%(targetName)s noraidīja uzaicinājumu",
+            "remove_avatar": "%(senderName)s noņēma savu profila attēlu",
+            "remove_name": "%(senderName)s dzēsa savu redzamo vārdu (%(oldDisplayName)s)",
+            "set_avatar": "%(senderName)s iestatīja profila attēlu",
+            "set_name": "%(senderName)s iestatīja %(displayName)s kā savu redzamo vārdu",
+            "unban": "%(senderName)s noņēma liegumu/atbanoja %(targetName)s",
+            "withdrew_invite": "%(senderName)s atsauca %(targetName)s paredzēto uzaicinājumu",
+            "withdrew_invite_reason": "%(senderName)s atsauca %(targetName)s paredzēto uzaicinājumu: %(reason)s"
+        },
+        "m.room.name": {
+            "change": "%(senderDisplayName)s nomainīja istabas nosaukumu no %(oldRoomName)s uz %(newRoomName)s.",
+            "remove": "%(senderDisplayName)s dzēsa istabas nosaukumu.",
+            "set": "%(senderDisplayName)s nomainīja istabas nosaukumu uz %(roomName)s."
+        },
+        "m.room.pinned_events": {
+            "changed": "%(senderName)s nomainīja šajā istabā piespraustās ziņas.",
+            "changed_link": "%(senderName)s nomainīja <a>piespraustās ziņas</a> šai istabai.",
+            "pinned": "%(senderName)s piesprauda ziņu šajā istabā. Skatīt visas piespraustās ziņas.",
+            "pinned_link": "%(senderName)s piesprauda <a>ziņu</a> šajā istabā. Skatīt visas <b>piespraustās ziņas</b>.",
+            "unpinned": "%(senderName)s noņēma piespraustu ziņu šajā istabā. Skatīt visas piespraustās ziņas.",
+            "unpinned_link": "%(senderName)s noņēma piespraustu <a>ziņu</a> šajā istabā. Skatīt visas <b>piespraustās ziņas</b>."
+        },
+        "m.room.power_levels": {
+            "changed": "%(senderName)s nomainīja jaudas līmeni %(powerLevelDiffText)s.",
+            "user_from_to": "%(userId)s no %(fromPowerLevel)s uz %(toPowerLevel)s"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 Visiem serveriem ir liegta pieeja dalībai! Šī istaba vairs nevar tikt izmantota.",
+            "changed": "%(senderDisplayName)s nomainīja servera ACL piekļuves šai istabai.",
+            "set": "%(senderDisplayName)s iestatīja servera ACL piekļuves šai istabai."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "%(senderName)s atsauca uzaicinājumu %(targetDisplayName)s pievienoties istabai.",
+            "sent": "%(senderName)s nosūtīja uzaicinājumu %(targetDisplayName)s pievienoties istabai."
+        },
+        "m.room.tombstone": "%(senderDisplayName)s atjaunināja šo istabu.",
+        "m.sticker": "%(senderDisplayName)s nosūtīja uzlīmi.",
+        "m.video": {
+            "error_decrypting": "Kļūda video atšifrēšanā"
+        },
+        "m.widget": {
+            "added": "Ekrānvadīklu %(widgetName)s pievienoja %(senderName)s",
+            "jitsi_ended": "%(senderName)s pabeidza videokonferenci",
+            "jitsi_join_right_prompt": "Pievienojieties konferencei no istabas informācijas kartes labajā pusē",
+            "jitsi_join_top_prompt": "Pievienojieties konferencei šīs istabas augšpusē",
+            "jitsi_started": "%(senderName)s sāka videokonferenci",
+            "jitsi_updated": "%(senderName)s atjaunināja videokonferenci",
+            "modified": "ekrānvadīklu %(widgetName)s mainīja %(senderName)s",
+            "removed": "Ekrānvadīklu %(widgetName)s noņēma %(senderName)s"
+        },
+        "mab": {
+            "collapse_reply_chain": "Sakļaut citātus",
+            "copy_link_thread": "Kopēt saiti uz pavedienu",
+            "expand_reply_chain": "Izvērst citātus",
+            "label": "Darbības ar ziņu",
+            "view_in_room": "Skats istabā"
+        },
+        "mjolnir": {
+            "changed_rule_glob": "%(senderName)s pārjaunoja lieguma noteikumu šablonu %(oldGlob)s uz šablonu %(newGlob)s dēļ %(reason)s",
+            "changed_rule_rooms": "%(senderName)s izmainīja noteikumu, kurš liedz pieeju istabām, kas atbilst %(oldGlob)s pazīmei pret %(newGlob)s dēļ %(reason)s",
+            "changed_rule_servers": "%(senderName)s aizstāja noteikumu, kas liedza pieeju serveriem, kas atbilst pazīmei %(oldGlob)s, ar atbilstošu pazīmei %(newGlob)s dēļ %(reason)s",
+            "changed_rule_users": "%(senderName)s aizstāja noteikumu, kurš liedza pieeju lietotājiem %(oldGlob)s ar jaunu noteikumu, kurš aizliedz %(newGlob)s dēļ %(reason)s",
+            "created_rule": "%(senderName)s izveidoja noteikumu pieejas liegšanai, kas atbilst %(glob)s dēļ %(reason)s",
+            "created_rule_rooms": "%(senderName)s izveidoja noteikumu pieejas liegšanai istabām, kas atbilst %(glob)s dēļ %(reason)s",
+            "created_rule_servers": "%(senderName)s izveidoja noteikumu pieejas liegšanai serveriem, kas atbilst %(glob)s dēļ %(reason)s",
+            "created_rule_users": "%(senderName)s izveidoja noteikumu pieejas liegšanai lietotājiem, kas atbilst %(glob)s dēļ %(reason)s",
+            "message_hidden": "Šis lietotājs netiek ņemts vērā, tādēļ viņa ziņa ir paslēpta. <a>Vienalga rādīt.</a>",
+            "removed_rule": "%(senderName)s dzēsa noteikumu pieejas liegšanai atbilstoši %(glob)s",
+            "removed_rule_rooms": "%(senderName)s dzēsa noteikumu pieejas liegšanai istabām, kas atbilst %(glob)s",
+            "removed_rule_servers": "%(senderName)s dzēsa noteikumu pieejas liegšanai serveriem, kas atbilst %(glob)s",
+            "removed_rule_users": "%(senderName)s dzēsa noteikumu pieejas liegšanai lietotājiem, kas atbilst %(glob)s",
+            "updated_invalid_rule": "%(senderName)s izmainīja kļūdainu pieejas liegšanas noteikumu",
+            "updated_rule": "%(senderName)s izmainīja noteikumu pieejas liegšanai, kas atbilst %(glob)s dēļ %(reason)s",
+            "updated_rule_rooms": "%(senderName)s izmainīja noteikumu pieejas liegšanai istabām, kas atbilst %(glob)s dēļ %(reason)s",
+            "updated_rule_servers": "%(senderName)s izmainīja noteikumu pieejas liegšanai serveriem, kas atbilst %(glob)s dēļ %(reason)s",
+            "updated_rule_users": "%(senderName)s izmainīja noteikumu pieejas liegšanai lietotājiem, kas atbilst %(glob)s dēļ %(reason)s"
+        },
+        "no_permission_messages_before_invite": "Jums nav atļaujas skatīt ziņas, kas saņemtas pirms uzaicināšanas.",
+        "no_permission_messages_before_join": "Jums nav atļaujas skatīt ziņas, kas saņemtas pirms pievienošanās.",
+        "pending_moderation": "Ziņa gaida moderēšanu",
+        "pending_moderation_reason": "Ziņa gaida moderēšanu: %(reason)s",
+        "reactions": {
+            "add_reaction_prompt": "Pievienot reakciju",
+            "custom_reaction_fallback_label": "Pielāgota reakcija",
+            "label": "%(reactors)s reaģēja ar %(content)s",
+            "tooltip_caption": "reaģēja ar %(shortName)s"
+        },
+        "read_receipt_title": {
+            "zero": "",
+            "one": "Redzējis %(count)s cilvēks",
+            "other": "Redzējuši %(count)s cilvēki"
+        },
+        "read_receipts_label": "Lasīšanas apliecinājumi",
+        "redacted": {
+            "tooltip": "Ziņa izdzēsta %(date)s"
+        },
+        "redaction": "%(name)s izdzēsa ziņu",
+        "reply": {
+            "error_loading": "Nevar ielādēt notikumu, uz kuru tika atbildēts, jo tas vai nu neeksistē, vai arī jums nav atļaujas to skatīt.",
+            "in_reply_to": "<a>Atbildē uz</a> <pill>",
+            "in_reply_to_for_export": "Atbildot uz <a>šo ziņu</a>"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "Jūs tiksiet novirzīts uz trešās puses vietni, lai jūs varētu autentificēt savu kontu %(integrationsUrl)s lietošanai. Vai vēlaties turpināt?",
+            "dialog_title": "Pievienot integrāciju"
+        },
+        "self_redaction": "Ziņa ir izdzēsta",
+        "send_state_encrypting": "Ziņas šifrēšana...",
+        "send_state_failed": "Neizdevās nosūtīt",
+        "send_state_sending": "Ziņas sūtīšana...",
+        "send_state_sent": "Tava ziņa tika nosūtīta",
+        "summary": {
+            "banned": {
+                "zero": "tika liegta piekļuve %(count)s reižu",
+                "one": "tika liegta piekļuve",
+                "other": "tika liegta piekļuve %(count)s reizes"
+            },
+            "banned_multiple": {
+                "zero": "tika liegta piekļuve %(count)s reižu",
+                "one": "tika liegta piekļuve",
+                "other": "tika liegta piekļuve %(count)s reizes"
+            },
+            "changed_avatar": {
+                "zero": "%(oneUser)s nomainīja savu profila attēlu",
+                "one": "%(oneUser)s nomainīja savu profila attēlu",
+                "other": "%(oneUser)s nomainīja savu profila attēlu %(count)s reizes"
+            },
+            "changed_avatar_multiple": {
+                "zero": "%(severalUsers)s nomainīja savu profila attēlu",
+                "one": "%(severalUsers)s nomainīja savu profila attēlu",
+                "other": "%(severalUsers)s nomainīja savu profila attēlu %(count)s reizes"
+            },
+            "changed_name": {
+                "zero": "%(oneUser)s izmainīja savu vārdu %(count)s reižu",
+                "one": "%(oneUser)s izmainīja savu vārdu",
+                "other": "%(oneUser)s izmainīja savu vārdu %(count)s reizes"
+            },
+            "changed_name_multiple": {
+                "zero": "%(severalUsers)s izmainīja savu vārdu %(count)s reižu",
+                "one": "%(severalUsers)s izmainīja savu vārdu",
+                "other": "%(severalUsers)s izmainīja savu vārdu %(count)s reizes"
+            },
+            "format": "%(nameList)s %(transitionList)s",
+            "hidden_event": {
+                "zero": "%(oneUser)s nosūtīja %(count)s slēptu ziņu",
+                "one": "%(oneUser)s nosūtīja %(count)s slēptu ziņu",
+                "other": "%(oneUser)s nosūtīja %(count)s slēptas ziņas"
+            },
+            "hidden_event_multiple": {
+                "zero": "%(severalUsers)s nosūtīja %(count)s slēptu ziņu",
+                "one": "%(severalUsers)s nosūtīja %(count)s slēptu ziņu",
+                "other": "%(severalUsers)s nosūtīja %(count)s slēptas ziņas"
+            },
+            "invite_withdrawn": {
+                "zero": "%(oneUser)s atsauca savus uzaicinājumu %(count)s reižu",
+                "one": "%(oneUser)s atsauca savu uzaicinājumu",
+                "other": "%(oneUser)s atsauca savu uzaicinājumu %(count)s reizes"
+            },
+            "invite_withdrawn_multiple": {
+                "zero": "%(severalUsers)s atsauca uzaicinājumus %(count)s reižu",
+                "one": "%(severalUsers)s atsauca uzaicinājumus",
+                "other": "%(severalUsers)s atsauca uzaicinājumus %(count)s reizes"
+            },
+            "invited": {
+                "zero": "tika uzaicināts(a) %(count)s reižu",
+                "one": "tika uzaicināts(a)",
+                "other": "tika uzaicināts(a) %(count)s reizes"
+            },
+            "invited_multiple": {
+                "zero": "tika uzaicināti %(count)s reižu",
+                "one": "tika uzaicināti",
+                "other": "tika uzaicināti %(count)s reizes"
+            },
+            "joined": {
+                "zero": "%(oneUser)s pievienojās %(count)s reižu",
+                "one": "%(oneUser)s pievienojās",
+                "other": "%(oneUser)s pievienojās %(count)s reizes"
+            },
+            "joined_and_left": {
+                "zero": "%(oneUser)s pievienojās un pameta %(count)s reižu",
+                "one": "%(oneUser)s pievienojās un pameta",
+                "other": "%(oneUser)s pievienojās un pameta %(count)s reizes"
+            },
+            "joined_and_left_multiple": {
+                "zero": "%(severalUsers)s pievienojās un pameta %(count)s reižu",
+                "one": "%(severalUsers)s pievienojās un pameta",
+                "other": "%(severalUsers)s pievienojās un pameta %(count)s reizes"
+            },
+            "joined_multiple": {
+                "zero": "%(severalUsers)s pievienojās %(count)s reižu",
+                "one": "%(severalUsers)s pievienojās",
+                "other": "%(severalUsers)s pievienojās %(count)s reizes"
+            },
+            "kicked": {
+                "zero": "tika noņemts %(count)s reižu",
+                "one": "tika noņemts",
+                "other": "tika noņemts %(count)s reizes"
+            },
+            "kicked_multiple": {
+                "zero": "tika noņemti %(count)s reižu",
+                "one": "tika noņemti",
+                "other": "tika noņemti %(count)s reizes"
+            },
+            "left": {
+                "zero": "%(oneUser)s pameta %(count)s reižu",
+                "one": "%(oneUser)s pameta",
+                "other": "%(oneUser)s pameta %(count)s reizes"
+            },
+            "left_multiple": {
+                "zero": "%(severalUsers)s pameta %(count)s reižu",
+                "one": "%(severalUsers)s pameta",
+                "other": "%(severalUsers)s pameta %(count)s reizes"
+            },
+            "no_change": {
+                "zero": "%(oneUser)s neveica nekādas izmaiņas %(count)s reižu",
+                "one": "%(oneUser)s neveica nekādas izmaiņas",
+                "other": "%(oneUser)s neveica nekādas izmaiņas %(count)s reizes"
+            },
+            "no_change_multiple": {
+                "zero": "%(severalUsers)s neveica nekādas izmaiņas %(count)s reižu",
+                "one": "%(severalUsers)s neveica nekādas izmaiņas",
+                "other": "%(severalUsers)s neveica nekādas izmaiņas %(count)s reizes"
+            },
+            "pinned_events": {
+                "zero": "%(oneUser)s nomainīja istabas <a>piespraustās ziņas</a> %(count)s reižu",
+                "one": "%(oneUser)s nomainīja istabas<a>piespraustās ziņas</a>",
+                "other": "%(oneUser)s nomainīja istabas <a>piespraustās ziņas</a> %(count)s reizes"
+            },
+            "pinned_events_multiple": {
+                "zero": "%(severalUsers)s nomainīja istabas <a>piespraustās ziņas</a> %(count)s reižu",
+                "one": "%(severalUsers)s nomainīja istabas <a>piespraustās ziņas</a>",
+                "other": "%(severalUsers)snomainīja istabas <a>piespraustās ziņas</a> %(count)s reizes"
+            },
+            "redacted": {
+                "zero": "%(oneUser)s noņēma %(count)s ziņu",
+                "one": "%(oneUser)s noņēma ziņu",
+                "other": "%(oneUser)s noņēma %(count)s ziņas"
+            },
+            "redacted_multiple": {
+                "zero": "%(severalUsers)s noņēma %(count)s ziņu",
+                "one": "%(severalUsers)s noņēma ziņu",
+                "other": "%(severalUsers)s noņēma %(count)s ziņas"
+            },
+            "rejected_invite": {
+                "zero": "%(oneUser)s noraidīja uzaicinājumu %(count)s reižu",
+                "one": "%(oneUser)s noraidīja uzaicinājumu",
+                "other": "%(oneUser)s noraidīja uzaicinājumu %(count)s reizes"
+            },
+            "rejected_invite_multiple": {
+                "zero": "%(severalUsers)s noraidīja uzaicinājumus %(count)s reižu",
+                "one": "%(severalUsers)s noraidīja uzaicinājumus",
+                "other": "%(severalUsers)s noraidīja uzaicinājumus %(count)s reizes"
+            },
+            "rejoined": {
+                "zero": "%(oneUser)s pameta un atkal pievienojās %(count)s reižu",
+                "one": "%(oneUser)s pameta un atkal pievienojās",
+                "other": "%(oneUser)s pameta un atkal pievienojās %(count)s reizes"
+            },
+            "rejoined_multiple": {
+                "zero": "%(severalUsers)s pameta un atkal pievienojās %(count)s reižu",
+                "one": "%(severalUsers)s pameta un atkal pievienojās",
+                "other": "%(severalUsers)s pameta un atkal pievienojās %(count)s reizes"
+            },
+            "server_acls": {
+                "zero": "%(oneUser)s nomainīja servera ACL %(count)s reižu",
+                "one": "%(oneUser)s nomainīja servera ACL",
+                "other": "%(oneUser)s nomainīja servera ACL %(count)s reizes"
+            },
+            "server_acls_multiple": {
+                "zero": "%(severalUsers)s nomainīja servera ACL %(count)s reižu",
+                "one": "%(severalUsers)s nomainīja servera ACL",
+                "other": "%(severalUsers)s nomainīja servera ACL %(count)s reizes"
+            },
+            "unbanned": {
+                "zero": "tika atcelts liegums %(count)s reižu",
+                "one": "tika atcelts liegums",
+                "other": "tika atcelts liegums %(count)s reizes"
+            },
+            "unbanned_multiple": {
+                "zero": "tika atcelts liegums %(count)s reižu",
+                "one": "tika atcelts liegums",
+                "other": "tika atcelts liegums %(count)s reizes"
+            }
+        },
+        "thread_info_basic": "No pavediena",
+        "typing_indicator": {
+            "more_users": {
+                "other": "%(names)s un %(count)s citi raksta…",
+                "one": "%(names)s un vēl viens raksta…"
+            },
+            "one_user": "%(displayName)s raksta…",
+            "two_users": "%(names)s un %(lastPerson)s raksta…"
+        },
+        "undecryptable_tooltip": "Šo ziņu nevarēja atšifrēt",
+        "url_preview": {
+            "close": "Aizvērt priekšskatījumu",
+            "show_n_more": {
+                "one": "Rādīt %(count)s citu priekšskatījumu",
+                "other": "Rādīt %(count)s citus priekšskatījumus"
+            }
+        }
+    },
+    "truncated_list_n_more": {
+        "other": "Un par %(count)s vairāk..."
+    },
+    "unsupported_server_description": "Šis serveris izmanto vecāku Matrix versiju. Lai to izmantotu %(brand)s bez kļūdām, atjauniniet uz Matrix līdz versijai %(version)s.",
+    "unsupported_server_title": "Jūsu serveris netiek atbalstīts",
+    "update": {
+        "changelog": "Izmaiņu vēsture",
+        "check_action": "Pārbaudīt atjauninājumus",
+        "checking": "Atjauninājuma pārbaude...",
+        "downloading": "Atjauninājumu lejupielāde...",
+        "error_encountered": "Gadījās kļūda (%(errorDetail)s).",
+        "error_unable_load_commit": "Neizdodas ielādēt izpildes informāciju: %(msg)s",
+        "new_version_available": "Pieejama jauna versija. <a>Atjaunināt.</a>",
+        "no_update": "Nav atjauninājumu.",
+        "release_notes_toast_title": "Kas jauns",
+        "see_changes_button": "Kas jauns?",
+        "toast_description": "Pieejama jauna %(brand)s versija",
+        "toast_title": "Atjaunināt %(brand)s",
+        "unavailable": "Nesasniedzams"
+    },
+    "update_room_access_modal": {
+        "description": "Lai izveidotu kopīgošanas saiti, jums ir jāļauj viesiem pievienoties šai istabai. Tas var padarīt telpu mazāk drošu. Kad esat pabeidzis zvanu, varat atkal padarīt istabu privātu.",
+        "dont_change_description": "Varat arī rīkot zvanu atsevišķā istabā.",
+        "no_change": "Es nevēlos mainīt piekļuves līmeni.",
+        "title": "Istabas piekļuves līmeņa maiņa"
+    },
+    "upload_failed_generic": "Neizdevās augšupielādēt datni '%(fileName)s'.",
+    "upload_failed_size": "Datne '%(fileName)s' pārsniedz šī mājasservera augšupielāžu izmēra ierobežojumu.",
+    "upload_failed_title": "Augšupielāde (nosūtīšana) neizdevās",
+    "upload_file": {
+        "cancel_all_button": "Atcelt visu",
+        "error_file_too_large": "Šī datne ir <b>pārāk liela</b>, lai to augšupielādētu. Datnes izmēra ierobežojums ir %(limit)s, bet šī datne ir %(sizeOfThisFile)s.",
+        "error_files_too_large": "Šīs datnes ir <b>pārāk lielas</b>, lai tās augšupielādētu. Datnes izmēra ierobežojums ir %(limit)s.",
+        "error_some_files_too_large": "Dažas datnes ir <b>pārāk lielas</b>, lai tās augšupielādētu. Datnes izmēra ierobežojums ir %(limit)s.",
+        "title": "Datņu augšupielāde",
+        "title_progress": "Datņu augšupielāde (%(current)s no %(total)s)",
+        "upload_n_others_button": {
+            "zero": "Augšupielādēt %(count)s citu datņu",
+            "one": "Augšupielādēt %(count)s citu datni",
+            "other": "Augšupielādēt %(count)s citas datnes"
+        }
+    },
+    "user_info": {
+        "admin_tools_section": "Administratora rīki",
+        "deactivate_confirm_action": "Deaktivizēt lietotāju",
+        "deactivate_confirm_description": "Šī lietotāja deaktivizēšana veiks tā atteikšanos un liegs tam atkal pieteikties. Papildus tas pametīs visas istabas, kurās tas ir. Šo darbību nevar apvērst. Vai tiešām deaktivizēt šo lietotāju?",
+        "deactivate_confirm_title": "Deaktivizēt lietotāju?",
+        "demote_button": "Pazemināt",
+        "demote_self_confirm_room": "Tu nevarēsi atdarīt šīs izmaiņas pēc sevis pazemināšanas. Gadījumā, ja esi pēdējais priviliģētais lietotājs istabā, būs neiespējami atgūt šīs privilēģijas.",
+        "demote_self_confirm_title": "Pazemināt sevi?",
+        "disinvite_button_room": "Atsaukt uzaicinājumu uz istabu",
+        "disinvite_button_room_name": "Atsaukt uzaicinājumu uz %(roomName)s",
+        "disinvite_button_space": "Atsaukt uzaicinājumu uz telpu",
+        "error_ban_user": "Neizdevās nobanot/bloķēt (liegt pieeju) lietotāju",
+        "error_mute_user": "Neizdevās apklusināt lietotāju",
+        "ignore_confirm_description": "Visas šī lietotāja ziņas un uzaicinājumi tiks paslēpti. Vai tiešām neņemt viņu vērā?",
+        "ignore_confirm_title": "Neņemt vērā %(user)s",
+        "jump_to_rr_button": "Pāriet uz pēdējo skatīto ziņu",
+        "kick_button_room": "Noņemt no istabas",
+        "kick_button_room_name": "Noņemt no %(roomName)s",
+        "kick_button_space": "Noņemt no telpas",
+        "promote_warning": "Jūs nevarēsiet atcelt šīs izmaiņas, jo paaugstināt lietotājam tādu pašu jaudas līmeni kā jums.",
+        "redact": {
+            "confirm_button": {
+                "one": "Dzēst 1 ziņu",
+                "other": "Dzēst %(count)s ziņas"
+            },
+            "confirm_description_1": {
+                "zero": "",
+                "one": "Jūs gatavojaties dzēst %(count)s lietotāja %(user)s ziņu. Tādējādi tā tiks neatgriezeniski dzēsta visiem sarunas dalībniekiem. Vai vēlaties turpināt?",
+                "other": "Jūs gatavojaties dzēst %(count)s lietotāja %(user)s ziņas. Tādējādi tās tiks neatgriezeniski dzēstas visiem sarunas dalībniekiem. Vai vēlaties turpināt?"
+            },
+            "confirm_description_2": "Lielam ziņu apjomam tas var aizņemt kādu laiku. Lūdzu, tikmēr neatsvaidziniet klientu.",
+            "confirm_keep_state_explainer": "Noņem atzīmi, ja vēlies arī noņemt sistēmas ziņas par šo lietotāju (piemēram, dalības vai profila izmaiņas…)",
+            "confirm_title": "Dzēst nesenās ziņas no %(user)s",
+            "no_recent_messages_description": "Mēģiniet ritināt laika joslu uz augšu, lai redzētu, vai ir kādas agrākas ziņas."
+        },
+        "redact_button": "Dzēst nesenās ziņas",
+        "room_encrypted": "Ziņas šajā istabā ir nodrošinātas ar pilnīgu šifrēšanu.",
+        "room_encrypted_detail": "Tavas ziņas ir drošībā, un tikai Tev un saņēmējam ir neatkārtojamas atslēgas, lai atslēgtu ziņas.",
+        "room_unencrypted": "Ziņām šajā istabā netiek piemērota pilnīga šifrēšana.",
+        "room_unencrypted_detail": "Šifrētās istabās Tavas ziņas ir drošībā, un tikai Tev un saņēmējam ir neatkārtojamas atslēgas, lai atslēgtu ziņas.",
+        "share_button": "Dalīties ar saiti uz lietotāju",
+        "verify_button": "Apliecināt lietotāju",
+        "verify_explainer": "Papildu drošībai jāapliecina šis lietotājs, pārbaudot vienreizēju kodu abās ierīcēs."
+    },
+    "user_menu": {
+        "settings": "Visi iestatījumi",
+        "switch_theme_dark": "Pārslēgt tumšo režīmu",
+        "switch_theme_light": "Pārslēgt gaišo režīmu"
+    },
+    "voip": {
+        "already_in_call": "Notiek zvans",
+        "already_in_call_person": "Tu jau sazvanies ar šo cilvēku.",
+        "answered_elsewhere": "Atbildēja citur",
+        "answered_elsewhere_description": "Uz zvanu tika atbildēts no citas ierīces.",
+        "call_failed": "Zvans neizdevās",
+        "call_failed_description": "Savienojums nevarēja tikt izveidots",
+        "call_failed_media": "Zvans neizdevās, jo nevarēja piekļūt kamerai vai mikrofonam. Pārbaudiet, vai:",
+        "call_failed_media_applications": "Neviena cita lietotne neizmanto kameru",
+        "call_failed_media_connected": "Mikrofons un kamera ir pievienoti un pareizi konfigurēti",
+        "call_failed_media_permissions": "Piešķirta atļauja izmantot kameru",
+        "call_failed_microphone": "Zvans neizdevās, jo nebija piekļuves mikrofonam. Pārliecinieties, vai mikrofons ir pievienots un pareizi konfigurēts.",
+        "call_held": "%(peerName)s aizturēja zvanu",
+        "call_held_resume": "Jūs aizturējāt zvanu <a>Turpināt</a>",
+        "call_held_switch": "Jūs aizturējāt zvanu <a>Slēdzis</a>",
+        "call_toast_unknown_room": "Nezināma istaba",
+        "camera_disabled": "Jūsu kamera ir izslēgta",
+        "camera_enabled": "Jūsu kamera joprojām ir iespējota",
+        "cannot_call_yourself_description": "Nav iespējams piezvanīt sev.",
+        "connecting": "Savieno",
+        "connection_lost": "Savienojamība ar serveri ir zaudēta",
+        "connection_lost_description": "Jūs nevarat veikt zvanus bez savienojuma ar serveri.",
+        "consulting": "Konsultācijas ar %(transferTarget)s.<a>Pāradresēt uz%(transferee)s</a>",
+        "default_device": "Noklusējuma ierīce",
+        "dial": "Zvanīt",
+        "dialpad": "Cipartastatūra",
+        "disable_camera": "Izslēgt kameru",
+        "disable_microphone": "Izslēgt mikrofonu",
+        "disabled_no_one_here": "Šeit nav neviena, kam piezvanīt",
+        "disabled_no_perms_start_video_call": "Jums nav atļaujas sākt videozvanus",
+        "disabled_no_perms_start_voice_call": "Jums nav atļaujas sākt balss zvanus",
+        "disabled_ongoing_call": "Notiek zvans",
+        "enable_camera": "Ieslēgt kameru",
+        "enable_microphone": "Ieslēgt mikrofonu",
+        "expand": "Atgriezieties pie zvana",
+        "get_call_link": "Kopīgot zvana saiti",
+        "hangup": "Beigt zvanu",
+        "hide_sidebar_button": "Paslēpt sānjoslu",
+        "input_devices": "Ievades ierīces",
+        "join_button_tooltip_call_full": "Atvainojiet, šis zvans pašlaik ir pilns",
+        "maximise": "Aizpildīt ekrānu",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferences"
+        },
+        "misconfigured_server": "Zvans neizdevās nekorekti nokonfigurēta servera dēļ",
+        "misconfigured_server_description": "Lūgums vaicāt mājasservera (<code>%(homeserverDomain)s</code>) pārvaldītājiem, lai tie konfigurē TURN serveri, lai zvani darbotos uzticami.",
+        "misconfigured_server_fallback": "Varat arī mēģināt izmantot publisko serveri vietnē <server/>, taču tas nebūs tik uzticams, un tas kopīgos jūsu IP adresi ar šo serveri. Varat šo pārvaldīt arī iestatījumos.",
+        "misconfigured_server_fallback_accept": "Mēģini izmantot %(server)s",
+        "more_button": "Vairāk",
+        "msisdn_lookup_failed": "Nevar atrast tālruņa numuru",
+        "msisdn_lookup_failed_description": "Meklējot tālruņa numuru, radās kļūda",
+        "msisdn_transfer_failed": "Neizdevās pārsūtīt zvanu",
+        "n_people_joined": {
+            "zero": "",
+            "one": "%(count)s persona pievienojās",
+            "other": "%(count)s cilvēki pievienojās"
+        },
+        "no_audio_input_description": "Mēs neatradām mikrofonu jūsu ierīcē. Lūdzu, pārbaudiet iestatījumus un mēģiniet vēlreiz.",
+        "no_audio_input_title": "Mikrofons nav atrasts",
+        "no_media_perms_description": "Varētu būt nepieciešams pašrocīgi ļaut %(brand)s piekļūt mikrofonam/tīmekļa kamerai",
+        "no_media_perms_title": "Nav datu nesēju, kuriem atļauta piekļuve",
+        "no_permission_conference": "Nepieciešama atļauja",
+        "no_permission_conference_description": "Šajā istabā nav atļaujas sākt konferences zvanu",
+        "on_hold": "%(name)s aizturēts",
+        "output_devices": "Izvades ierīces",
+        "screenshare_monitor": "Kopīgot visu ekrānu",
+        "screenshare_title": "Dalīties ar saturu",
+        "screenshare_window": "Lietojumprogrammas logs",
+        "show_sidebar_button": "Rādīt sānjoslu",
+        "silence": "Apklusināt zvanu",
+        "silenced": "Paziņojumi apklusināti",
+        "start_screenshare": "Sākt ekrānu kopīgošanu",
+        "stop_screenshare": "Pārtraukt ekrāna kopīgošanu",
+        "too_many_calls": "Pārāk daudz zvanu",
+        "too_many_calls_description": "Ir sasniegts maksimālais vienaicīgu zvanu skaits.",
+        "transfer_consult_first_label": "Vispirms konsultējieties",
+        "transfer_failed": "Pāradresēšana/pārsūtīšana neizdevās",
+        "transfer_failed_description": "Neizdevās pārsūtīt/pāradresēt zvanu",
+        "unable_to_access_audio_input_description": "Mēs nevarējām piekļūt mikrofonam. Lūgums pārbaudīt pārlūka iestatījumus un mēģināt vēlreiz.",
+        "unable_to_access_audio_input_title": "Nevar piekļūt mikrofonam",
+        "unable_to_access_media": "Nevar piekļūt kamerai / mikrofonam",
+        "unable_to_access_microphone": "Nav pieejas mikrofonam",
+        "unknown_caller": "Nezināms zvanītājs",
+        "unknown_person": "nezināma persona",
+        "unsilence": "Skaņa ieslēgta",
+        "unsupported": "Zvani netiek atbalstīti",
+        "unsupported_browser": "Šajā pārlūkprogrammā nevar veikt zvanus.",
+        "user_busy": "Lietotājs aizņemts",
+        "user_busy_description": "Lietotājs, kuram zvanāt, ir aizņemts.",
+        "user_is_presenting": "%(sharerName)s prezentē",
+        "video_call": "Video zvans",
+        "video_call_started": "Videozvans uzsākts",
+        "voice_call": "Balss zvans",
+        "you_are_presenting": "Jūs prezentējat"
+    },
+    "widget": {
+        "added_by": "Logrīku pievienoja",
+        "capabilities_dialog": {
+            "content_starting_text": "Šis logrīks vēlas:",
+            "decline_all_permission": "Noraidīt visu",
+            "remember_Selection": "Atcerēties manu izvēli šim logrīkam",
+            "title": "Apstiprināt logrīku atļaujas"
+        },
+        "capability": {
+            "always_on_screen_generic": "Darbības laikā paliek uz ekrāna",
+            "always_on_screen_viewing_another_room": "Darbības laikā paliek uz ekrāna, kad tiek skatīta cita istaba",
+            "any_room": "Atbilstoši augstāk norādītajam, kā arī jebkurā istabā, kurai jūs esat pievienojies vai uzaicināts",
+            "byline_empty_state_key": "ar tukšu stāvokļa/statusa atslēgu",
+            "byline_state_key": "ar stāvokļa/statusa atslēgu %(stateKey)s",
+            "capability": "<b>%(capability)s</b> iespējas",
+            "change_avatar_active_room": "Nomainīt aktīvās istabas attēlu",
+            "change_avatar_this_room": "Nomainīt šīs istabas avataru",
+            "change_name_active_room": "Nomainīt aktīvās istabas nosaukumu",
+            "change_name_this_room": "Nomainīt šīs istabas nosaukumu",
+            "change_topic_active_room": "Nomainīt aktīvās istabas tematu",
+            "change_topic_this_room": "Nomainīt šīs istabas tematu",
+            "receive_membership_active_room": "Redzēt, kad cilvēki pievienojas, atstāj vai tiek uzaicināti aktīvajā istabā",
+            "receive_membership_this_room": "Redzēt, kad cilvēki pievienojas, atstāj vai tiek uzaicināti uz šo istabu",
+            "see_avatar_change_active_room": "Redzēt, kad nomainās aktīvās istabas attēls",
+            "see_avatar_change_this_room": "Redzēt, kad notiek šīs istabas avatara izmaiņas",
+            "see_event_type_sent_active_room": "Apskatīt aktīvās istabas <b>%(eventType)s</b> notikumus",
+            "see_event_type_sent_this_room": "Apskatīt <b>%(eventType)s</b> notikumus šajā istabā",
+            "see_images_sent_active_room": "Redzēt pašreizējā iestabā iesūtītos attēlus",
+            "see_images_sent_this_room": "Redzēt attēlus, kuri izlikti šajā istabā",
+            "see_messages_sent_active_room": "Redzēt ziņas, kas ir ierakstītas pašreizējā istabā",
+            "see_messages_sent_this_room": "Redzēt ziņas, kas izvietotas šajā istabā",
+            "see_msgtype_sent_active_room": "Redzēt pašreizējā istabā nosūtītās <b>%(msgtype)s</b> ziņas",
+            "see_msgtype_sent_this_room": "Apskatīt <b>%(msgtype)s</b> ziņas, kas publicētas šajā istabā",
+            "see_name_change_active_room": "Redzēt, kad notiek aktīvās istabas nosaukuma izmaiņas",
+            "see_name_change_this_room": "Redzēt, kad mainās šīs istabas nosaukums",
+            "see_sent_emotes_active_room": "Redzēt emocijas, kuras ir ierakstītas pašreizējā istabā",
+            "see_sent_emotes_this_room": "Redzēt emocijas, kuras izvietotas šajā istabā",
+            "see_sent_files_active_room": "Redzēt pašreizējā istabā ievietotās vispārējās datnes",
+            "see_sent_files_this_room": "Redzēt šajā istabā nosūtītās datnes",
+            "see_sticker_posted_active_room": "Redzēt, kad pašreizējā istabā kāds pievieno uzlīmi",
+            "see_sticker_posted_this_room": "Redzēt, kad šajā istabā tiek nosūtītas uzlīmes",
+            "see_text_messages_sent_active_room": "Redzēt teksta ziņas, kas ir ierakstītas pašreizējā istabā",
+            "see_text_messages_sent_this_room": "Redzēt teksta ziņas, kas izvietotas šajā istabā",
+            "see_topic_change_active_room": "Redzēt, kad mainās pašreizējā tērziņa temats",
+            "see_topic_change_this_room": "Redzēt, kad mainās šīs istabas temats",
+            "see_videos_sent_active_room": "Redzēt pašreizējā istabā iesūtītos video",
+            "see_videos_sent_this_room": "Redzēt video, kuri izlikti šajā istabā",
+            "send_emotes_active_room": "Nosūtīt emocijas savā vārdā uz savu aktīvo istabu",
+            "send_emotes_this_room": "Nosūtīt emocijas savā vārdā uz šo istabu",
+            "send_event_type_active_room": "Sūtīt <b>%(eventType)s</b> notikumus savā vārdā savā aktīvajā istabā",
+            "send_event_type_this_room": "Nosūtīt šīs istabas <b>%(eventType)s</b> notikumus Tavā vārdā",
+            "send_files_active_room": "Pašreizējā istabā nosūtīt vispārīgas datnes savā vārdā",
+            "send_files_this_room": "Šajā istabā sūtīt vispārīgas datnes savā vārdā",
+            "send_images_active_room": "Sūtīt attēlus savā vārdā savā aktīvajā istabā",
+            "send_images_this_room": "Sūtīt attēlus savā vārdā šajā istabā",
+            "send_messages_active_room": "Nosūtīt pašreizējās istabas ziņas savā vārdā",
+            "send_messages_this_room": "Sūtīt ziņas savā vārdā šajā istabā",
+            "send_msgtype_active_room": "Sūtīt <b>%(msgtype)s</b> ziņas savā vārdā savā aktīvajā istabā",
+            "send_msgtype_this_room": "Sūtīt <b>%(msgtype)s</b> ziņas savā vārdā šajā istabā",
+            "send_stickers_active_room": "Nosūtīt uzlīmes pašreizējā istabā",
+            "send_stickers_active_room_as_you": "Nosūtīt uzlīmes pašreizējā istabā savā vārdā",
+            "send_stickers_this_room": "Nosūtīt uzlīmes šajā istabā",
+            "send_stickers_this_room_as_you": "Nosūtīt uzlīmes šajā istabā savā vārdā",
+            "send_text_messages_active_room": "Noūtīt pašreizējās istabas teksta ziņas savā vārdā",
+            "send_text_messages_this_room": "Sūtīt teksta ziņas savā vārdā šajā istabā",
+            "send_videos_active_room": "Sūtīt video savā vārdā savā aktīvajā istabā",
+            "send_videos_this_room": "Sūtīt video savā vārdā šajā istabā",
+            "specific_room": "Atbilstoši augstāk norādītajam, kā arī <Room /> istabā",
+            "switch_room": "Mainīt istabu, kura tiek skatīta",
+            "switch_room_message_user": "Mainīt istabu, ziņu vai lietotāju, kas tiek skatīts"
+        },
+        "close_to_view_right_panel": "Aizvērt šo logrīku, lai skatītu to šajā panelī",
+        "context_menu": {
+            "delete": "Izdzēst ekrānvadīklu",
+            "delete_warning": "Ekrānvadīklas izdzēšana noņems to visiem šīs istabas lietotājiem. Vai tiešām izdzēst šo ekrānvadīklu?",
+            "move_left": "Pārvietot pa kreisi",
+            "move_right": "Pārvietot pa labi",
+            "remove": "Dzēst visiem",
+            "revoke": "Atsaukt atļaujas",
+            "screenshot": "Uzņemt attēlu",
+            "start_audio_stream": "Sākt audio straumēšanu"
+        },
+        "cookie_warning": "Šis logrīks var izmantot sīkfailus.",
+        "error_hangup_description": "Jūs tikāt atvienots no zvana. (Kļūda: %(message)s)",
+        "error_hangup_title": "Savienojums zaudēts",
+        "error_loading": "Ielādējot logrīku, radās kļūda",
+        "error_mixed_content": "Kļūda - jaukts saturs",
+        "error_need_invite_permission": "Lai to izdarītu, jums ir jāspēj uzaicināt lietotājus.",
+        "error_need_to_be_logged_in": "Tev ir jāpierakstās.",
+        "error_unable_start_audio_stream_description": "Neizdodas uzsākt audio straumēšanu.",
+        "error_unable_start_audio_stream_title": "Neizdevās sākt tiešraidi",
+        "modal_data_warning": "Dati šajā ekrānā tiek kopīgoti ar %(widgetDomain)s",
+        "modal_title_default": "Modālais logrīks",
+        "no_name": "Nezināma lietotne",
+        "open_id_permissions_dialog": {
+            "remember_selection": "Atcerieties šo",
+            "starting_text": "Logrīks verificēs jūsu lietotāja ID, bet nevarēs veikt darbības jūsu vietā:",
+            "title": "Atļaut šim logrīkam verificēt jūsu identitāti"
+        },
+        "popout": "Uznirstošais logrīks",
+        "set_room_layout": "Iestatīt manas istabas izkārtojumu ikvienam",
+        "shared_data_avatar": "Tava profila attēla URL",
+        "shared_data_device_id": "Jūsu ierīces ID",
+        "shared_data_lang": "Jūsu valoda",
+        "shared_data_mxid": "Tavs lietotāja Id",
+        "shared_data_name": "Tavs attēlojamais vārds",
+        "shared_data_room_id": "Istabas ID",
+        "shared_data_theme": "Izskats",
+        "shared_data_url": "%(brand)s URL",
+        "shared_data_warning": "Izmantojot šo logrīku, var tikt kopīgoti dati <helpIcon /> ar %(widgetDomain)s.",
+        "shared_data_warning_im": "Izmantojot šo logrīku, var tikt kopīgoti dati <helpIcon /> ar %(widgetDomain)s un jūsu integrācijas pārvaldnieku.",
+        "shared_data_widget_id": "Logrīka ID",
+        "unencrypted_warning": "Logrīki neizmanto ziņu šifrēšanu.",
+        "unmaximise": "Maksimizācijas atcelšana",
+        "unpin_to_view_right_panel": ""
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "Visus lielos burtus ir gandrīz tikpat viegli uzminēt kā visus mazos",
+            "anotherWord": "Papildiniet ar vēl kādiem vārdiem. Netipiski vārdi ir labāk.",
+            "associatedYears": "Izvairieties no gadskaitļiem, kas ir saistīti ar jums",
+            "capitalization": "Lielo pirmo burtu lietojums īpaši nepalīdz",
+            "dates": "Izvairieties no datumiem un gadskaitļiem, kas ir saistīti ar jums",
+            "l33t": "Paredzamās aizstāšanas, piemēram, '@' burta 'a' vietā īpaši nepalīdz",
+            "longerKeyboardPattern": "Izmantojiet garāku tastatūras modeli ar vairāk pagriezieniem",
+            "noNeed": "Nav nepieciešami simboli, cipari vai lielie burti",
+            "pwned": "Ja izmantojat šo paroli citur, jums vajadzētu to nomainīt.",
+            "recentYears": "Izvairieties no pēdējiem gadiem",
+            "repeated": "Izvairieties no atkārtotiem vārdiem un rakstzīmēm",
+            "reverseWords": "Apgrieztus vārdus nav īpaši grūti uzminēt",
+            "sequences": "Izvairieties no secībām",
+            "useWords": "Izmantojiet tikai dažus vārdus, izvairieties no izplatītām frāzēm"
+        },
+        "warnings": {
+            "common": "Šī ir ļoti izplatīta parole",
+            "commonNames": "Izplatīti vārdi un uzvārdi arī ir viegli uzminami",
+            "dates": "Datumi bieži vien ir viegli uzminami",
+            "extendedRepeat": "Atkārtojumi, piemēram, \"abcabcabc\", ir uzminami tikai nedaudz grūtāk kā \"abc\"",
+            "keyPattern": "Īsas tastatūras taustiņu secības ir viegli uzminamas",
+            "namesByThemselves": "Vārdi un uzvārdi paši par sevi ir viegli uzminami",
+            "pwned": "Tava parole tika atklāta internetā pieejamā datu noplūdē.",
+            "recentYears": "Nesenie gadi ir viegli uzminami",
+            "sequences": "Secības, piemēram, abc vai 6543, ir viegli uzminamas",
+            "similarToCommon": "Šī ir līdzīga bieži izmantotai parolei",
+            "simpleRepeat": "Atkārtojumi, piemēram, \"aaa\", ir viegli uzminami",
+            "straightRow": "Tiešas atslēgu rindas ir viegli uzminamas",
+            "topHundred": "Šī ir viena no 100 izplatītākajām parolēm",
+            "topTen": "Šī ir viena no 10 izplatītākajām parolēm",
+            "userInputs": "Nevajadzētu būt nekādiem personīgiem vai ar lapu saistītiem datiem.",
+            "wordByItself": "Vārds pats par sevi ir viegli uzminams"
+        }
+    }
+}
diff --git a/src/i18n/strings/mg_MG.json b/src/i18n/strings/mg_MG.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ec5ed76c5a92a967e442c5956eb56e4a5318d3d
--- /dev/null
+++ b/src/i18n/strings/mg_MG.json
@@ -0,0 +1,3686 @@
+{
+    "a11y": {
+        "emoji_picker": "Mpifidy Emoji",
+        "jump_first_invite": "Hanketo amin'ny fanasana voalohany.",
+        "message_composer": "Mpamorona hafatra",
+        "n_unread_messages": {
+            "one": "1 hafatra tsy novakiana.",
+            "other": "%(count)shafatra tsy novakiana."
+        },
+        "n_unread_messages_mentions": {
+            "one": " Voalaza tsy voavaky.",
+            "other": "%(count)shafatra tsy novakiana anisan'izany ny filazana."
+        },
+        "recent_rooms": "Efitrano vao haingana",
+        "room_name": "Efitra%(name)s",
+        "room_status_bar": "Bara momba ny efitrano",
+        "seek_bar_label": "Audio mitady bar",
+        "unread_messages": "Hafatra tsy novakiana.",
+        "user_menu": "Lisitra mpampiasa"
+    },
+    "a11y_jump_first_unread_room": "Mankanesa any amin'ny efitrano voalohany tsy voavaky.",
+    "action": {
+        "accept": "Manaiky",
+        "add": "Ametraka",
+        "add_existing_room": "Anampy ny efitrano misy",
+        "add_people": "Ampiditra olona",
+        "apply": "Manatontonsa",
+        "approve": "Hanaiky",
+        "ask_to_join": "Mangataka ny hiaraka",
+        "back": "Afara",
+        "call": "Antso",
+        "cancel": "Hanafoana",
+        "change": "Fiovana",
+        "clear": "Mazava",
+        "click": "Tsindrio eto",
+        "click_to_copy": "Tsindrio raha handika",
+        "close": "Akatona",
+        "collapse": "Firodanana",
+        "complete": "Feno",
+        "confirm": "Manamarina",
+        "continue": "Tohizo",
+        "copy": "Kopia",
+        "copy_link": "Kopia",
+        "create": "Hamorina",
+        "create_a_room": "Amorina efitrano",
+        "decline": "Fitontonganan'ny",
+        "delete": "Esorina",
+        "deny": "Lavina",
+        "disable": "Atsaharo",
+        "disconnect": "Akatona",
+        "dismiss": "Hanario",
+        "done": "Vita",
+        "download": "Mampiditra",
+        "edit": "Ahitsy",
+        "enable": "Mampandeha",
+        "enter_fullscreen": "Ampidiro ny efijery feno",
+        "exit_fullscreeen": "Hivoaka ny efijery feno",
+        "expand": "Fampivoarana",
+        "explore_public_rooms": "Tsidiho ny efitranom-bahoaka",
+        "explore_rooms": "Tsidiho ny efitrano",
+        "export": "Fanondranana",
+        "forward": "Teo aloa",
+        "go": "Mandeha",
+        "go_back": "Hiverina",
+        "got_it": "Nahavoaray izy",
+        "hide_advanced": "Fanafenana",
+        "hold": "Tazomina",
+        "ignore": "Dingiavina",
+        "import": "Manafatra",
+        "invite": "Asao",
+        "invite_to_space": "Asao hoany amin'ny habakabaka",
+        "invites_list": "Fanasana",
+        "join": "Iombona",
+        "learn_more": "Hamantatra bebekokoa",
+        "leave": "Lasa",
+        "leave_room": "Miala efitrano",
+        "logout": "Hivoaka",
+        "manage": "Iandraikitra",
+        "maximise": "Ampitomboina araka izay",
+        "mention": "Filazana",
+        "minimise": "Mampihena",
+        "new_room": "Efitrano vaovao",
+        "new_video_room": "Efitrano vata fakan-tsary vaovao",
+        "next": "Fanarahana",
+        "no": "Tsya",
+        "ok": "Eny",
+        "pause": "Mihato",
+        "pin": "Hahantona",
+        "play": "Milalao",
+        "proceed": "Izotra",
+        "quote": "Notsongaina",
+        "react": "Mamaly",
+        "refresh": "Mamelombelona",
+        "register": "Rejisitry",
+        "reload": "Avereno",
+        "remove": "Esorina",
+        "rename": "Hanova anarana",
+        "reply": "Valio",
+        "reply_in_thread": "Valio amin'ny kofehy",
+        "report_content": "Mitatitra votoaty",
+        "resend": "Voaroaka",
+        "reset": "Amerina aminy laoniny",
+        "resume": "CV",
+        "retry": "Averina",
+        "review": "Amerina ijery",
+        "revoke": "Manafoana",
+        "save": "Hitahiry",
+        "search": "Karohina",
+        "send_report": "Mandefa tatitra",
+        "share": "Zaraina",
+        "show": "Mampiseho",
+        "show_advanced": "Asehoy ny fanitsiana ankotrizay",
+        "show_all": "Asehoy manontolo",
+        "sign_in": "Famantarao amin'ny",
+        "sign_out": "Ivoaka",
+        "skip": "Itsambikina",
+        "start": "Manomboka",
+        "start_chat": "Manomboka",
+        "start_new_chat": "Manomboka chat vaovao",
+        "stop": "Atsaharo",
+        "submit": "Manolotra",
+        "subscribe": "Anaraka",
+        "transfer": "Famindrana",
+        "trust": "Itokiana",
+        "try_again": "Andramo indray",
+        "unban": "Tsy misy fandraràna",
+        "unignore": "Tsy azo odian-tsy hita",
+        "unpin": "Avela",
+        "unsubscribe": "Hiala tsy anaraka",
+        "update": "Vaovao farany",
+        "upgrade": "Hanatsarana aminy laoniny",
+        "upload": "Hampiditra",
+        "verify": "Hamarinina",
+        "view": "Hijery",
+        "view_all": "Jereo daholo",
+        "view_list": "Aneho ny lisitra",
+        "view_message": "Hijery",
+        "view_source": "Ijery loharano nopoirany",
+        "yes": "Eny",
+        "zoom_in": "Manakaiky amin'ny",
+        "zoom_out": "Manakaiky ivelany"
+    },
+    "analytics": {
+        "accept_button": "Tsara izany",
+        "bullet_1": "Isika<Bold> iantoraka</Bold> raketo na mombamomba ny angon-drakitra rehetra",
+        "bullet_2": "Isika<Bold> iantoraka</Bold> mizara vaovao aminy antoko fahatelo",
+        "consent_migration": "Nanaiky hizara angona momba ny fampiasana tsy mitonona anarana aminay ianao taloha. Havaozinay ny fomba fiasan'izany.",
+        "disable_prompt": "Azonao atao ny mamono an'io aminy fotoana rehetra ao aminy fanitsy",
+        "enable_prompt": "Ampio izahay hanatsara%(analyticsOwner)s",
+        "learn_more": "Mizarà angona tsy mitonona anarana hanampiana anay hamantatra olana. Tsy misy zavatra manokana. Tsy misy antoko fahatelo.<LearnMoreLink> Hamantatra bebe kokoa</LearnMoreLink>",
+        "privacy_policy": "Azonao atao ny mamaky ny fepetra rehetra<PrivacyPolicyUrl> Eto</PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "Ampio izahay hamantatra olana sy hanatsara%(analyticsOwner)s amin'ny fizarana angon-drakitra fampiasana tsy mitonona anarana. Mba hahatakarana ny fomba fampiasan'ny olona fitaovana maro, dia hamorona mpamantatra kisendrasendra izahay, zarain'ny fitaovanao.",
+        "shared_data_heading": "Ny iray amin'ireto angona manaraka ireto dia azo zaraina:"
+    },
+    "auth": {
+        "3pid_in_use": "Efa ampiasaina io adiresy imailaka na laharan-telefaona io.",
+        "account_clash": "Ny kaontinao vaovao (%(newAccountId)s ) dia voasoratra anarana, fa ianao dia efa tafiditra amina kaonty hafa (%(loggedInUserId)s ).",
+        "account_clash_previous_account": "Tohizo amin'ny kaonty teo aloha",
+        "account_deactivated": "Nesorina ity kaonty ity.",
+        "autodiscovery_generic_failure": "Tsy nahavita nahazo tefy autodiscovery avy amin'ny mpizara",
+        "autodiscovery_hs_incompatible": "Efa antitra loatra ny mpizara tranonao ary tsy mahazaka ny dikan-teny API ambany indrindra ilaina. Mifandraisa amin'ny tompony mpizara anao azafady, na manavao ny mpizara anao.",
+        "autodiscovery_invalid": "Valiny momba ny fitadiavana homeserver tsy mety",
+        "autodiscovery_invalid_hs": "Ny URL an'ny Homeserver dia toa tsy mpizara manan-kery",
+        "autodiscovery_invalid_hs_base_url": "Tsy manan-kery base_url ho an'ny m.homeserver",
+        "autodiscovery_invalid_is": "Ny URL mpizara famantarana dia toa tsy mpizara famantarana manan-kery",
+        "autodiscovery_invalid_is_base_url": "Heriny_url tsy mety ho an'ny m.identity_server",
+        "autodiscovery_invalid_is_response": "Valiny momba ny fahitana mpizara famantarana tsy mety",
+        "autodiscovery_invalid_json": "JSON tsy mety",
+        "autodiscovery_no_well_known": "Tsy hita ny rakitra JSON malaza",
+        "autodiscovery_unexpected_error_hs": "Error tsy ampoizina amin'ny famahana ny fikirakirana homeserver",
+        "autodiscovery_unexpected_error_is": "Olana tsy nampoizina amin'ny famahana ny tefiny mpizara famantarana",
+        "captcha_description": "Ity homeserver ity dia te-hahazo antoka fa tsy robot ianao.",
+        "change_password_action": "Hanova tenimiafina",
+        "change_password_confirm_invalid": "Tsy mifanentana ny tenimiafina",
+        "change_password_confirm_label": "Hamafiso ny tenimiafina",
+        "change_password_current_label": "Tenimiafina ankehitriny",
+        "change_password_empty": "Tsy azo foanana ny tenimiafina",
+        "change_password_error": "Hadisoana teo am-panovana tenimiafina:%(error)s",
+        "change_password_mismatch": "Tsy mifanentana ny tenimiafina vaovao",
+        "change_password_new_label": "Tenimiafina vaovao",
+        "check_email_explainer": "Araho ny toromarika nalefa tany<b>%(email)s</b>",
+        "check_email_resend_prompt": "Tsy nahazo ve ianao?",
+        "check_email_resend_tooltip": "Naverina indray ny imailaka rohy fanamarinana!",
+        "check_email_wrong_email_button": "Ampidiro indray ny adiresy imailaka",
+        "check_email_wrong_email_prompt": "Adiresy imailaka diso?",
+        "continue_with_idp": "Tohizo amin'ny%(provider)s",
+        "continue_with_sso": "Tohizo amin'ny%(ssoButtons)s",
+        "country_dropdown": "Lisitra mizotra amin'ny firenena",
+        "create_account_prompt": "Vaovao eto?<a> Hamorona kaonty</a>",
+        "create_account_title": "Hamorona kaonty",
+        "email_discovery_text": "Ampiasao ny imailaka mba ho hitany mpifandray efa misy.",
+        "email_field_label": "Imailaka",
+        "email_field_label_invalid": "Toa tsy adiresy imailaka manan-kery",
+        "email_field_label_required": "Ampidiro ny adiresy mailaka",
+        "email_help_text": "Ampio imailaka ahafahana mamerina ny tenimiafinao.",
+        "email_phone_discovery_text": "Mampiasà mailaka na telefaona mba ho hitany mpifandray efa misy.",
+        "enter_email_explainer": "<b>%(homeserver)s</b>dia handefa rohy fanamarinana ianao hamela anao hamerina ny tenimiafinao.",
+        "enter_email_heading": "Ampidiro ny imailakao hamerenana ny tenimiafina",
+        "failed_connect_identity_server": "Tsy afaka mankany amin'ny mpizara famantarana",
+        "failed_connect_identity_server_other": "Afaka miditra ianao, saingy tsy ho hita ny endri-javatra sasany mandra-piverina an-tserasera ny mpizara famantarana. Raha mahita an'io fampitandremana io foana ianao dia jereo ny fandrindranao na mifandraisa amin'ny admin server.",
+        "failed_connect_identity_server_register": "Afaka misoratra anarana ianao, saingy tsy ho hita ny endri-javatra sasany mandra-piverina an-tserasera ny mpizara famantarana. Raha mahita an'io fampitandremana io foana ianao dia jereo ny fandrindranao na mifandraisa aminy admin server.",
+        "failed_connect_identity_server_reset_password": "Azonao atao ny mamerina ny tenimiafinao, saingy tsy ho hita ny endri-javatra sasany mandra-piverina an-tserasera ny mpizara famantarana. Raha mahita an'io fampitandremana io foana ianao dia jereo ny fandrindranao na mifandraisa aminy admin server.",
+        "failed_homeserver_discovery": "Tsy nahavita ny fitadiavana homeserver",
+        "failed_query_registration_methods": "Tsy afaka manontany ny fomba fisoratana anarana tohana.",
+        "failed_soft_logout_auth": "Tsy nahavita nanamarina indray",
+        "failed_soft_logout_homeserver": "Tsy nahavita nanamarina indray noho ny olana amin'ny homeserver",
+        "forgot_password_email_invalid": "Toa tsy mitombina ny adiresy imailaka.",
+        "forgot_password_email_required": "Tsy maintsy ampidirina ny adiresy imailaka mifandray aminy kaontinao.",
+        "forgot_password_prompt": "Nanadino ny tenimiafinao?",
+        "forgot_password_send_email": "Alefaso imailaka",
+        "identifier_label": "Midira amin'ny",
+        "incorrect_credentials": "Anarana mpampiasa sy/na tenimiafina diso.",
+        "incorrect_credentials_detail": "Mariho fa miditra ao amin'ny%(hs)s mpizara, fa tsy",
+        "incorrect_password": "Diso tenimiafina",
+        "log_in_new_account": "<a>Hiditra</a> amin'ny kaontinao vaovao.",
+        "logout_dialog": {
+            "description": "Tena te hivoaka ve ianao?",
+            "megolm_export": "Manondrana lakile amin'ny tanana",
+            "setup_key_backup_title": "Ho very ny fidirana amin'ny hafatrao voasakana",
+            "setup_secure_backup_description_1": "Ny hafatra voarakitra dia arovana aminy fanafenana farany mankany amin'ny farany. Ianao sy ny mpandray (ireo) ihany no manana ny fanalahidy hamakiana ireo hafatra ireo.",
+            "setup_secure_backup_description_2": "Rehefa mivoaka ianao dia ho voafafa amin'ity fitaovana ity ireo fanalahidy ireo, izay midika fa tsy ho afaka hamaky hafatra voafono ianao raha tsy manana ny fanalahidin'izy ireo amin'ny fitaovanao hafa, na averinao any aminy mpizara.",
+            "skip_key_backup": "Tsy tiako ny hafatra voarakotra",
+            "use_key_backup": "Manomboka mampiasa fanalahidin'ny fitahirizana"
+        },
+        "misconfigured_body": "Anontanio ny anao%(brand)s admin hanamarina<a> ny config</a> hoany fidirana diso na dika mitovy.",
+        "misconfigured_title": "ny%(brand)s dia diso fanitsiana",
+        "msisdn_field_description": "Ny mpampiasa hafa dia afaka manasa anao any amin'ny efitrano mampiasa ny antsipiriany fifandraisanao",
+        "msisdn_field_label": "Finday",
+        "msisdn_field_number_invalid": "Toa tsy mety io laharan-telefaona io, azafady jereo ary andramo indray",
+        "msisdn_field_required_invalid": "Ampidiro ny laharan-telefaona",
+        "no_hs_url_provided": "Tsy misy URL homeserver nomena",
+        "oidc": {
+            "error_title": "Tsy afaka miditra anao izahay",
+            "generic_auth_error": "Nisy zavatra tsy nety nandritra ny fanamarinana. Mandehana any amin'ny pejy fidirana ary andramo indray.",
+            "missing_or_invalid_stored_state": "Nangataka taminy mpitety tranonkala izahay mba hahatsiaro hoe iza no homeserver ampiasainao hamela anao hisoratra anarana, saingy indrisy fa hadinony mpitety tranonkalanao izany. Mandehana any amin'ny pejy fidirana ary andramo indray."
+        },
+        "password_field_keep_going_prompt": "Tohizo hatrany…",
+        "password_field_label": "Ampidiro ny tenimiafina",
+        "password_field_strong_label": "Tenimiafina tsotra sady mafy",
+        "password_field_weak_label": "Avela ny tenimiafina, saingy tsy azo antoka",
+        "phone_label": "Finday",
+        "phone_optional_label": "Finday (tsy voatery)",
+        "qr_code_login": {
+            "completing_setup": "Mamita ny fametrahana ny fitaovanao vaovao",
+            "error_rate_limited": "Andrana be loatra ao anatiny fotoana fohy. Miandrasa fotoana vao manandrana indray.",
+            "error_unexpected": "Nisy lesoka tsy nampoizina.",
+            "scan_code_instruction": "Andramo ny kaody QR etsy ambany miaraka amin'ny fitaovanao izay mivoaka.",
+            "scan_qr_code": "Fitiliana miafina QR",
+            "select_qr_code": "Safidio%(scanQRCode)s",
+            "waiting_for_device": "Miandry ny hidiran'ny fitaovana"
+        },
+        "register_action": "Hamorona kaonty",
+        "registration": {
+            "continue_without_email_description": "Soso-kevitra fotsiny, raha tsy manampy mailaka ianao ary manadino ny tenimiafinao dia azonao atao<b> very tanteraka ny fidirana amin'ny kaontinao</b> .",
+            "continue_without_email_field_label": "Mailaka (tsy voatery)",
+            "continue_without_email_title": "Manohy tsy misy mailaka"
+        },
+        "registration_disabled": "Nofoanana ny fisoratana anarana amin'ity mpizara trano ity.",
+        "registration_msisdn_field_required_invalid": "Ampidiro ny nomeraon-telefaona (takina amin'ity serivisy ity)",
+        "registration_successful": "Nahomby ny fisoratana anarana",
+        "registration_username_in_use": "Efa misy manana izany solonanarana izany. Manandrama hafa na raha ianao io dia midira eto ambany.",
+        "registration_username_unable_check": "Tsy afaka nanamarina raha nalaina ny solonanarana. Andramo indray rehefa afaka kelikely.",
+        "registration_username_validation": "Mampiasà litera kely, isa, tsipika ary tsipitsipika fotsiny",
+        "reset_password": {
+            "confirm_new_password": "Hamafiso ny tenimiafina vaovao",
+            "devices_logout_success": "Efa nivoaka tamin'ny fitaovana rehetra ianao ary tsy hahazo fampandrenesana fanosehana intsony. Raha te hamerina ny fampandrenesana dia midira indray amin'ny fitaovana tsirairay.",
+            "other_devices_logout_warning_1": "Ny fisoratana anarana aminy fitaovanao dia hamafa ny fanalahidiny hafatra voatahiry ao amin'izy ireo, ka tsy ho voavaky ny tantarany chat voahidy.",
+            "other_devices_logout_warning_2": "Raha te-hihazona ny fidirana amin'ny tantarany chat anao ao amin'ny efitrano misy miafina ianao dia amboary ny fanalahidin'ny vakorakitra na manondrana ny fanalahidiny hafatrao avy aminy fitaovanao hafa alohan'ny handehananao.",
+            "password_not_entered": "Tsy maintsy ampidirina ny tenimiafina vaovao.",
+            "passwords_mismatch": "Tsy maintsy mifanandrify ny tenimiafina vaovao.",
+            "rate_limit_error": "Andrana be loatra ao anatiny fotoana fohy. Miandrasa fotoana vao manandrana indray.",
+            "rate_limit_error_with_time": "Andrana be loatra ao anatiny fotoana fohy. Andramo indray rehefa avy eo%(timeout)s .",
+            "reset_successful": "Naverina ny tenimiafinao.",
+            "return_to_login": "Miverena any amin'ny efijery fidirana",
+            "sign_out_other_devices": "Mialà amin'ny fitaovana rehetra"
+        },
+        "reset_password_action": "Avereno ny tenimiafina",
+        "reset_password_button": "Hadino ny tenimiafina?",
+        "reset_password_email_field_description": "Mampiasà adiresy imailaka hamerenana ny kaontinao",
+        "reset_password_email_field_required_invalid": "Ampidiro ny adiresy imailaka (takina amin'ity homeserver ity)",
+        "reset_password_email_not_associated": "Ny adiresy mailakao dia toa tsy misy ifandraisany amin'ny ID amin'ity serivisy ity.",
+        "reset_password_email_not_found_title": "Tsy hita ity adiresy mailaka ity",
+        "reset_password_title": "Averina amin'ny laoniny ny teny miafina",
+        "server_picker_custom": "Hafa",
+        "server_picker_description": "Azonao atao ny mampiasa ny safidiny mpizara mahazatra mba hisoratra anarana aminy mpizara Matrix hafa aminy alàlany famaritana URL an-trano hafa. Izany dia ahafahanao mampiasa%(brand)s miaraka aminy kaonty efa misy amin'ny homeserver hafa.",
+        "server_picker_description_matrix.org": "Miaraha an-tapitrisany maimaim-poana amin'ny mpizara hoany daholobe lehibe indrindra",
+        "server_picker_dialog_title": "Manapaha hevitra hoe aiza no hampiantranoana ny kaontinao",
+        "server_picker_explainer": "Ampiasao ny homeserver tianao raha manana iray ianao, na mampiantrano ny anao manokana.",
+        "server_picker_failed_validate_homeserver": "Tsy afaka manamarina ny homeserver",
+        "server_picker_intro": "Antsoinay hoe homeservers ireo toerana ahafahanao mampiantrano ny kaontinao.",
+        "server_picker_invalid_url": "URL tsy mety",
+        "server_picker_learn_more": "Momba ny homeservers",
+        "server_picker_matrix.org": "No mpizara tranom-bahoaka lehibe indrindra eran-tany, noho izany dia toerana tsara hoany maro.",
+        "server_picker_required": "Manorata homeserver",
+        "server_picker_title": "Midira ao amin'ny homeserver anao",
+        "server_picker_title_default": "Safidy mpizara",
+        "server_picker_title_registration": "Kaonty mpampiantrano mandeha",
+        "session_logged_out_description": "Hoany fiarovana dia nosoniavina ity fivoriana ity. Midira indray azafady.",
+        "session_logged_out_title": "Nivoaka",
+        "set_email": {
+            "description": "Izany dia ahafahanao mamerina ny tenimiafinao sy mandray fampandrenesana.",
+            "verification_pending_description": "Jereo azafady ny mailakao ary tsindrio ny rohy misy azy. Rehefa vita izany dia tsindrio ny continue.",
+            "verification_pending_title": "Fanamarinana miandry"
+        },
+        "set_email_prompt": "Te hametraka adiresy mailaka ve ianao?",
+        "sign_in_description": "Ampiasao ny kaontinao hanohizana.",
+        "sign_in_instead": "Midira ho solon'izany",
+        "sign_in_instead_prompt": "Efa manana kaonty?<a> Midira eto</a>",
+        "sign_in_or_register": "Midira na Mamorona kaonty",
+        "sign_in_or_register_description": "Ampiasao ny kaontinao na mamorona vaovao hanohizana.",
+        "sign_in_prompt": "Manana kaonty?<a> Hiditra</a>",
+        "sign_in_with_sso": "Midira miaraka amin'ny sonia tokana",
+        "signing_in": "Midira…",
+        "soft_logout": {
+            "clear_data_button": "Fafao ny angona rehetra",
+            "clear_data_description": "Ny famafana ny angona rehetra amin'ity fivoriana ity dia maharitra. Ho very ny hafatra nasiana encryption raha tsy voaharo ny fanalahidiny.",
+            "clear_data_title": "Fafao ny angona rehetra amin'ity fivoriana ity?"
+        },
+        "soft_logout_heading": "Tafavoaka ianao",
+        "soft_logout_intro_password": "Ampidiro ny tenimiafinao hidirana sy hiverenana amin'ny kaontinao.",
+        "soft_logout_intro_sso": "Midira ary mahazo miditra amin'ny kaontinao indray.",
+        "soft_logout_intro_unsupported_auth": "Tsy afaka miditra aminy kaontinao ianao. Mifandraisa amin'ny admin homeserver anao raha mila fanazavana fanampiny.",
+        "soft_logout_subheading": "Fafao ny angona manokana",
+        "soft_logout_warning": "Fampitandremana: mbola voatahiry ato amin'ity session ity ny angon-drakitrao manokana (anisan'izany ny fanalahidin'ny encryption). Esory izany raha vitanao ny mampiasa ity session ity, na te-hiditra amin'ny kaonty hafa.",
+        "sso": "Famantarana tokana",
+        "sso_failed_missing_storage": "Nangataka tamin'ny mpitety tranonkala izahay mba hahatsiaro hoe iza no homeserver ampiasainao hamela anao hisoratra anarana, saingy indrisy fa hadinon'ny mpitety tranonkalanao izany. Mandehana any amin'ny pejy fidirana ary andramo indray.",
+        "sso_or_username_password": "%(ssoButtons)sNa%(usernamePassword)s",
+        "sync_footer_subtitle": "Raha niditra tao aminy efitrano maro ianao dia mety haharitra ela izany",
+        "syncing": "Mandrindra…",
+        "uia": {
+            "code": "Fehezan-dalàna",
+            "email": "Mba hamoronana ny kaontinao dia sokafy ny rohy ao amin'ny mailaka nalefanay%(emailAddress)s .",
+            "email_auth_header": "Jereo ny mailakao hanohizana",
+            "email_resend_prompt": "Tsy nahazo?<a> Alefaso indray</a>",
+            "email_resent": "Avereno alefa",
+            "fallback_button": "Atombohy ny fanamarinana",
+            "msisdn": "Nisy hafatra an-tsoratra nalefa tany%(msisdn)s",
+            "msisdn_token_incorrect": "Diso ny mari-pamantarana",
+            "msisdn_token_prompt": "Ampidiro azafady ny kaody misy azy:",
+            "password_prompt": "Hamafiso ny maha-ianao anao amin'ny fampidirana ny tenimiafinao eto ambany.",
+            "recaptcha_missing_params": "Tsy ampy ny fanalahidiny daholobe captcha amin'ny fandrindrana homeserver. Ampahafantaro aminy mpitantana ny homeserver anao izany azafady.",
+            "registration_token_label": "Token fisoratana anarana",
+            "registration_token_prompt": "Ampidiro ny mari-pamantarana fisoratana anarana nomen'ny mpitantana homeserver.",
+            "sso_body": "Hamafiso ny fampidirana ity adiresy mailaka ity amin'ny fampiasana Single Sign On mba hanaporofoana ny maha-izy anao.",
+            "sso_failed": "Nisy tsy nety tamin'ny fanamarinana ny mombamomba anao. Foana ary andramo indray.",
+            "sso_postauth_body": "Kitiho ny bokotra etsy ambany hanamarina ny maha-izy anao.",
+            "sso_postauth_title": "Hamafiso ny hanohy",
+            "sso_preauth_body": "Mba hanohizana, ampiasao ny Single Sign On mba hanaporofoana ny maha-izy anao.",
+            "sso_title": "Ampiasao ny sonia tokana hanohizana",
+            "terms": "Avereno jerena sy ekeo ny politikan'ity homeserver ity azafady:",
+            "terms_invalid": "Avereno jerena ary ekeo ny politikany homeserver rehetra"
+        },
+        "unsupported_auth": "Ity homeserver ity dia tsy manolotra fidirany fidirana izay tohanan'ity mpanjifa ity.",
+        "unsupported_auth_email": "Ity homeserver ity dia tsy manohana ny fidirana amin'ny alàlany adiresy imailaka.",
+        "unsupported_auth_msisdn": "Ity mpizara ity dia tsy manohana ny fanamarinana amin'ny laharan-telefaona.",
+        "username_field_required_invalid": "Ampidiro ny anarana ho ampiasaina",
+        "username_in_use": "Efa misy manana an'io solonanarana io, azafady manandrama hafa.",
+        "verify_email_explainer": "Mila fantarinay fa ianao io alohany hamerenana ny tenimiafinao. Tsindrio ny rohy ao amin'imailaka nalefanay<b>%(email)s</b>",
+        "verify_email_heading": "Hamarino ny mailakao hanohizana"
+    },
+    "bug_reporting": {
+        "additional_context": "Raha misy teny manodidina fanampiny hanampy amin'ny famakafakana ilay olana, toy ny zavatra nataonao tamin'izany fotoana izany, ID efitrano, ID mpampiasa, sns, dia ampidiro eto ireo zavatra ireo.",
+        "before_submitting": "Alohan'ny handefasana logs dia tsy maintsy<a> mamorona olana GitHub</a> mba hamaritana ny olanao.",
+        "collecting_information": "Manangona fampahafantarana momba ny kinova fampiharana",
+        "collecting_logs": "Fanangonana izay vaovao",
+        "create_new_issue": "Mba miangavy re<newIssueLink> mamorona olana vaovao</newIssueLink> ao amin'ny GitHub mba ahafahantsika manadihady ity bibikely ity.",
+        "description": "Ny diariny fangalana bibikely dia misy angona fampiasana fampiharana ao anatin'izany ny solonanaranao, ny ID na solon'anarana aminy efitrano notsidihanareo, izay singa UI nifaneraseranao farany, ary ny anarany mpampiasa hafa. Tsy misy hafatra izy ireo.",
+        "download_logs": "Haka vaovao",
+        "downloading_logs": "Misintona hazo",
+        "error_empty": "Mba lazao anay izay tsy nety na, tsara kokoa, mamorona olana GitHub izay mamaritra ny olana.",
+        "github_issue": "Olana GitHub",
+        "introduction": "Raha nandefa bibikely taminy alàlan'ny GitHub ianao, dia afaka manampy antsika hanara-maso ilay olana ny log debug.",
+        "log_request": "Mba ho fanampiana atsika hisoroka izany amin'ny ho avy, azafady<a> andefaso hazo izahay</a> .",
+        "logs_sent": "Lasa ny diary",
+        "matrix_security_issue": "Raha te hanao tatitra momba ny olana momba ny fiarovana aminy, azafady vakio ny <a> Politika fampahafantarana ny fiarovana</a> .",
+        "preparing_download": "Manomana na fizotrany fakana diary",
+        "preparing_logs": "Miomana handefa diary",
+        "send_logs": "Andefa ireo gazety",
+        "submit_debug_logs": "Andefa ny gazety ny débogage",
+        "textarea_label": "Fanamarihana",
+        "thank_you": "Misaotra",
+        "title": "Tatitra momba ny bibikely",
+        "unsupported_browser": "Fampahatsiahivana: Tsy tohanana ny mpitety tranonkalanao, ka mety tsy ho azo antenaina ny traikefanao.",
+        "uploading_logs": "Mampakatra vatan-kazo",
+        "waiting_for_server": "Miandry valiny avy amin'ny mpizara"
+    },
+    "cannot_invite_without_identity_server": "Tsy afaka manasa mpampiasa amin'ny alàlany mailaka raha tsy misy mpizara famantarana. Afaka mifandray amin'ny iray ianao eo ambaniny fanitsiana.",
+    "cannot_reach_homeserver": "Tsy afaka mankany amin'ny homeserver",
+    "cannot_reach_homeserver_detail": "Ataovy azo antoka fa manana fifandraisana Internet milamina ianao, na mifandray amin'ny admin server",
+    "cant_load_page": "Tsy afaka mampiditra pejy",
+    "chat_card_back_action_label": "Hiverina any aminy chat",
+    "chat_effects": {
+        "confetti_description": "Mandefa hafatra nomena miaraka amin'ny confettis",
+        "confetti_message": "mandefa confetti",
+        "fireworks_description": "Mandefa ny hafatra nomena miaraka amin'ny afomanga",
+        "fireworks_message": "mandefa afomanga",
+        "hearts_description": "Mandefa hafatra nomena amin'ny fo",
+        "hearts_message": "mandefa fo",
+        "rainfall_description": "Mandefa hafatra nomena miaraka amin'ny orana",
+        "rainfall_message": "mandefa orana",
+        "snowfall_description": "Mandefa hafatra nomena miaraka amin'ny ranomandry",
+        "snowfall_message": "mandefa ranomandry",
+        "spaceinvaders_description": "Mandefa hafatra nomena miaraka amin'ny fiantraikan'ny habakabaka",
+        "spaceinvaders_message": "mandefa mpanafika habakabaka"
+    },
+    "common": {
+        "access_token": "Famantarana fidirana",
+        "accessibility": "Fahafahana",
+        "advanced": "Jery lavitra",
+        "analytics": "Mandalina",
+        "and_n_others": {
+            "one": "ary ny iray hafa...",
+            "other": "Sy%(count)s ny hafa..."
+        },
+        "appearance": "Bika Aman' endrika",
+        "application": "Fampiharana",
+        "are_you_sure": "Azonao antoka ve izany?",
+        "attachment": "Fanamariana",
+        "authentication": "Fanamarinana",
+        "avatar": "Avatar",
+        "beta": "Beta",
+        "camera": "Fakan-tsary",
+        "cameras": "Vata fakan-tsary",
+        "capabilities": "Fahaizana",
+        "copied": "Adika!",
+        "credits": "Fahana",
+        "dark": "Maizina",
+        "description": "Famaritana",
+        "deselect_all": "Esory ny zava-drehetra",
+        "device": "Fitaovana",
+        "edited": "Ho kitiana",
+        "email_address": "Adiresy imailaka",
+        "emoji": "Sary fanehoam-pietseam-po",
+        "encrypted": "Nafenina",
+        "encryption_enabled": "Kilalao",
+        "error": "Fahadisoana",
+        "faq": "Soloky",
+        "favourites": "Ankafizina",
+        "feedback": "Iverina",
+        "filter_results": "Sivana ny valiny",
+        "forward_message": "Talohan'ny",
+        "general": "Jeneraly",
+        "go_to_settings": "Mandehana any amin'ny fanitsiana",
+        "guest": "Hivahiny",
+        "help": "Vonjy",
+        "historical": "ara-tantara",
+        "home": "Trano",
+        "homeserver": "Mpizara an-trano",
+        "identity_server": "Mpizara famantarana",
+        "image": "Sary",
+        "integration_manager": "Mpitantana ny fampidirana",
+        "joined": "Nanatevin-daharana",
+        "labs": "Laboratoara",
+        "legal": "Mitovy",
+        "light": "Fahazavana",
+        "loading": "Manatontonsa",
+        "location": "Toerana isihany",
+        "low_priority": "Laharam-pahamehana ambany",
+        "matrix": "Matrix",
+        "message": "Hafatra",
+        "message_layout": "Fandaharana",
+        "microphone": "Fanamafisampeo ara taribia",
+        "model": "Modely",
+        "modern": "Ankehitriny",
+        "mute": "Moana",
+        "n_members": {
+            "one": "%(count)s mpikambana",
+            "other": "%(count)sIreo mpikambana"
+        },
+        "n_rooms": {
+            "one": "%(count)sefitra",
+            "other": "%(count)s efitrano"
+        },
+        "name": "Anarana",
+        "no_results": "Tsy misy vokany",
+        "no_results_found": "Tsy misy valiny hita",
+        "not_trusted": "Tsy azo itokisana",
+        "off": "Levitra",
+        "offline": "Ivelan'ny aterineto",
+        "on": "Ao amin'ny",
+        "options": "Mety",
+        "orphan_rooms": "Efitrano hafa",
+        "password": "Tenimiafina",
+        "people": "Olona",
+        "preferences": "Tian'ny",
+        "presence": "Fanatrehan'i",
+        "preview_message": "E ianao io. Ianao no mahay indrindra!",
+        "privacy": "Ny fitokisana",
+        "private": "Tsy miankina",
+        "private_room": "Efitrano manokana",
+        "private_space": "Toerana voatokana",
+        "profile": "TANTARANY",
+        "public": "Vahoaka",
+        "public_room": "Efitranom-bahoaka",
+        "public_space": "Toeram-bahoaka",
+        "qr_code": "QR teny miafina",
+        "random": "Kianjoanjo",
+        "reactions": "Fanehoan-kevitra",
+        "report_a_bug": "Tatitra fisiany bibikely",
+        "room": "Efi-trano",
+        "room_name": "Anarana",
+        "rooms": "Efitrano",
+        "saving": "Tsinjo",
+        "secure_backup": "Fitahirizana voaharo",
+        "select_all": "Safidio ny rehetra",
+        "server": "Mpizara",
+        "settings": "Fikirana",
+        "setup_secure_messages": "Mametraha hafatra azo antoka",
+        "show_more": "Asehoy bebe kokoa",
+        "someone": "Olona",
+        "space": "Toerana",
+        "spaces": "Toerana malalaka",
+        "sticker": "Taratasy mandraikitena",
+        "stickerpack": "Tolotra an-tsary",
+        "success": "Fahombiazana",
+        "suggestions": "Hevitra",
+        "support": "Manampy",
+        "system_alerts": "Fanairana momba ny rafitra",
+        "theme": "Foto-kevitra",
+        "thread": "Taridresaka",
+        "threads": "Zanaka lahy",
+        "timeline": "Fanisan'ora",
+        "unavailable": "Mbola tsy afaka",
+        "unencrypted": "Tsy voa-afina",
+        "unmute": "Velomina ny feo",
+        "unnamed_room": "Tsy fantatra anarana",
+        "unnamed_space": "Toerana tsy misy anarana",
+        "unverified": "Tsy voamarina",
+        "user": "Mpampiasa",
+        "user_avatar": "Sary mpamantarana ",
+        "username": "Anarany mpampiasa",
+        "verification_cancelled": "Manafoana ny fanamarinana",
+        "verified": "Fanamariana",
+        "version": "Dikan-teny",
+        "video": "Ainamanjery",
+        "video_room": "Efitranon'ny Lahatsary",
+        "view_message": "Jereo ny hafatra",
+        "warning": "Fampitandremana"
+    },
+    "composer": {
+        "autocomplete": {
+            "@room_description": "Ampahafantaro ny efitrano manontolo",
+            "command_a11y": "Baiko adika mizotra oazy",
+            "command_description": "didy",
+            "emoji_a11y": "Emoji Autocomplete",
+            "notification_a11y": "Adika Autocomplete fampahafantarana",
+            "notification_description": "Fampandrenesana efitrano",
+            "room_a11y": "Adika mande oazy",
+            "space_a11y": "Adika mizotra oazy",
+            "user_a11y": "Dika mande oazy ny mpampiasa",
+            "user_description": "Mpampiasa"
+        },
+        "close_sticker_picker": "Afeno ireo taratasy fametaka",
+        "edit_composer_label": "Ahitsio hafatra",
+        "format_bold": "Sahisahy",
+        "format_code_block": "Andian-tsoratra",
+        "format_decrease_indent": "Fanenahana ny vola isontomana",
+        "format_increase_indent": "Fampitomboana ny vola fahanterana",
+        "format_inline_code": "Laharana miafina",
+        "format_insert_link": "Ampidiro rohy",
+        "format_italic": "Italia",
+        "format_italics": "Italika",
+        "format_link": "Rohy",
+        "format_ordered_list": "Lisitra misy laharana",
+        "format_strikethrough": "Manakana",
+        "format_underline": "Manipika",
+        "format_unordered_list": "Lisitry ny bala",
+        "formatting_toolbar_label": "Fandrafetana",
+        "link_modal": {
+            "link_field_label": "Rohy",
+            "text_field_label": "Text",
+            "title_create": "Mamorona rohy",
+            "title_edit": "Ahitsio rohy"
+        },
+        "mode_plain": "Afeno ny endrika",
+        "mode_rich_text": "Asehoy ny fandrafetana",
+        "no_perms_notice": "Tsy manana alalana handefa hafatra amin'ity efitrano ity ianao",
+        "placeholder": "Mandefasa hafatra…",
+        "placeholder_encrypted": "Mandefa hafatra misy miafina…",
+        "placeholder_reply": "Alefaso ny valiny...",
+        "placeholder_reply_encrypted": "Alefaso valiny misy miafina…",
+        "placeholder_thread": "Valiny aminy lohahevitra…",
+        "placeholder_thread_encrypted": "Valiny amin&#39;ny lohahevitra misy miafina…",
+        "poll_button": "fitsapan-kevitra",
+        "poll_button_no_perms_description": "Tsy manana alalana hanombohana fitsapan-kevitra amin'ity efitrano ity ianao.",
+        "poll_button_no_perms_title": "Mila alalana",
+        "replying_title": "Mamaly",
+        "room_upgraded_link": "Mitohy eto ny resaka.",
+        "room_upgraded_notice": "Nosoloina ity efitrano ity ary tsy miasa intsony.",
+        "send_button_title": "Alefaso hafatra",
+        "send_button_voice_message": "Alefaso hafatra feo",
+        "send_voice_message": "Alefaso hafatra feo",
+        "stop_voice_message": "Atsaharo ny firaketana",
+        "voice_message_button": "Hafatra feo"
+    },
+    "console_dev_note": "Raha fantatrao ny ataonao dia loharano misokatra ny Element, aza hadino ny mijery ny GitHub (https://github.com/vector-im/element-web/) ary mandray anjara!",
+    "console_scam_warning": "Raha misy miteny aminao hoe mandika/mametaka zavatra eto dia mety ho voafitaka ianao!",
+    "console_wait": "Andraso!",
+    "create_room": {
+        "action_create_room": "Mamorina",
+        "action_create_video_room": "Mamorina Orinan-tsary",
+        "encrypted_video_room_warning": "Tsy azonao atao ny manafoana izany any aoriana. Ny efitrano dia hatao encryption fa ny antso tafiditra dia tsy hanao.",
+        "encrypted_warning": "Tsy azonao atao ny manafoana izany any aoriana. Mbola tsy mandeha ny tetezana sy  ny ankamaroany bots.",
+        "encryption_forced": "Mitaky encryption ny mpizara anao mba ho alefa any amin'ny efitrano manokana.",
+        "encryption_label": "Alefaso ny bout ao aminy boutmiafina",
+        "error_title": "Tsy nahavita nanamboatra efitrano",
+        "generic_error": "Mety tsy misy ny mpizara, be loatra, na tratrany bibikely ianao.",
+        "join_rule_change_notice": "Azonao atao ny manova izany aminy fotoana rehetra avy amin'ny firafitry ny efitrano.",
+        "join_rule_invite": "Efitrano manokana (manasa ihany)",
+        "join_rule_invite_label": "Ny olona nasaina ihany no afaka mahita sy miditra amin'ity efitrano ity.",
+        "join_rule_knock_label": "Na iza na iza dia afaka mangataka ny hiditra, fa ny admin na ny mpandrindra dia mila manome fidirana. Azonao ovaina izany any aoriana any.",
+        "join_rule_public_label": "Na iza na iza dia afaka mahita sy miditra amin'ity efitrano ity.",
+        "join_rule_public_parent_space_label": "Na iza na iza dia afaka mahita sy miditra amin'ity efitrano ity, fa tsy ny mpikambana ao amin'ny<SpaceName/> .",
+        "join_rule_restricted": "Hita aminy mpikambana ao amin'ny habakabaka",
+        "join_rule_restricted_label": "Ny olona rehetra ao<SpaceName/> dia afaka mahita sy miditra amin'ity efitrano ity.",
+        "name_validation_required": "Ampidiro anarana hoany amin'ny efitrano azafady",
+        "room_visibility_label": "Fahitana efitrano",
+        "title_private_room": "Mamorina toerana tsy miankina",
+        "title_public_room": "Mamorina vahoaka",
+        "title_video_room": "Mamorina Orinan-tsary",
+        "topic_label": "Lohahevitra (tsy voatery)",
+        "unfederated": "Sakanana izay tsy anisany%(serverName)s hatramin'izay niditra tao amin'ity efitrano ity.",
+        "unfederated_label_default_off": "Azonao atao ny mamela izany raha toa ka hampiasaina amin'ny fiaraha-miasa aminy ekipa anatiny ao amin'ny servero ihany ny efitrano. Tsy azo ovaina izany any aoriana any.",
+        "unfederated_label_default_on": "Azonao atao ny manafoana izany raha hampiasaina hiara-hiasa amin'ireo ekipa ivelany izay manana mpizara trano manokana ny efitrano. Tsy azo ovaina izany any aoriana any.",
+        "unsupported_version": "Ny mpizara dia tsy mahazaka ny dikan-trano voalaza."
+    },
+    "create_space": {
+        "add_details_prompt": "Manampy an-tsipiriana sasany hanampiana ny olona hahafantatra azy io.",
+        "add_details_prompt_2": "Afaka manova ireo ianao amin'ny fotoana rehetra.",
+        "add_existing_rooms_description": "Maka efitrano na resaka ampiana. Sehatra ho anao ihany ity, tsy hisy hampahafantarina. Afaka manampy fanampiny ianao any aoriana.",
+        "add_existing_rooms_heading": "Inona no tianao handaminana?",
+        "address_label": "Adiresy",
+        "address_placeholder": "Ohatra amin'ny toerana malalaka",
+        "creating": "Famoronana...",
+        "creating_rooms": "Mamorona efitrano…",
+        "done_action": "Mandehana any amin'ny toerana misy ahy",
+        "done_action_first_room": "Mandehana any amin'ny efitranoko voalohany",
+        "explainer": "Fomba vaovao ahafahana mampivondrona efitrano sy olona ny habaka. Karazana habakabaka inona no tianao hamboarina? Azonao ovaina izany any aoriana any.",
+        "failed_create_initial_rooms": "Tsy nahavita namorona efitrano habakabaka voalohany",
+        "failed_invite_users": "Tsy nahavita nanasa ireto mpampiasa manaraka ireto ho amin'ny toerana malalaka:%(csvUsers)s",
+        "invite_teammates_by_username": "Asao amin'ny solon'anarana",
+        "invite_teammates_description": "Ataovy azo antoka fa mahazo miditra ny olona sahaza. Afaka manasa bebe kokoa ianao any aoriana.",
+        "invite_teammates_heading": "Asao ny mpiara-miasa aminao",
+        "inviting_users": "Manasa…",
+        "label": "Mamorona toerana malalaka ",
+        "name_required": "Ampidiro anarana hoany habakabaka azafady",
+        "personal_space": "Izaho ihany",
+        "personal_space_description": "Toerana manokana handaminana ny efitranonao",
+        "private_description": "Manasa ihany, tsara indrindra ho anao na ekipa",
+        "private_heading": "Ny toeranao manokana",
+        "private_personal_description": "Ataovy azo antoka fa azon'ny olona tsara idirana%(name)s",
+        "private_personal_heading": "Miaraka amin'iza ianao?",
+        "private_space": "Izaho sy ny mpiara-miasa amiko",
+        "private_space_description": "Toerana manokana ho anao sy ny mpiara-miasa aminao",
+        "public_description": "Toerana misokatra hoany rehetra, tsara indrindra hoany vondrom-piarahamonina",
+        "public_heading": "Ny toerana misy anao ampa-bemaso",
+        "search_public_button": "Mitadiava toerana hoany daholobe",
+        "setup_rooms_community_description": "Andeha isika hamorona efitrano hoany tsirairay amin'izy ireo.",
+        "setup_rooms_community_heading": "Inona avy ireo zavatra tianao horesahina%(spaceName)s ?",
+        "setup_rooms_description": "Afaka manampy fanampiny koa ianao any aoriana, anisan'izany ireo efa misy.",
+        "setup_rooms_private_description": "Hamorona efitrano hoany tsirairay amin'izy ireo izahay.",
+        "setup_rooms_private_heading": "Tetikasa inona no iasan'ny ekipanao?",
+        "share_description": "Ianao ihany amin'izao fotoana izao, dia ho tsara kokoa amin'ny hafa.",
+        "share_heading": "Izara%(name)s",
+        "skip_action": "Atsipazo aloha izao",
+        "subspace_adding": "Manampy…",
+        "subspace_beta_notice": "Manampy toerana amin'ny habakabaka tantaninao",
+        "subspace_dropdown_title": "Mamoròna toerana malalaka",
+        "subspace_existing_space_prompt": "Te-hanampy toerana efa misy ve?",
+        "subspace_join_rule_invite_description": "Ny olona nasaina ihany no afaka mahita sy miditra amin'ity habaka ity.",
+        "subspace_join_rule_invite_only": "Toerana manokana (manasa ihany)",
+        "subspace_join_rule_label": "Fahitana habakabaka",
+        "subspace_join_rule_public_description": "Na iza na iza dia afaka mahita sy miditra amin'ity habakabaka ity, fa tsy ny mpikambana ao amin'ny<SpaceName/> .",
+        "subspace_join_rule_restricted_description": "Na iza na iza ao<SpaceName/> dia afaka mahita sy miditra."
+    },
+    "credits": {
+        "default_cover_photo": "Ny<photo> sary fonony ampiandohany</photo> dia ©<author> Jesús Roncero</author> ampiasaina araka ny fepetrany<terms>CC-BY-SA 4.0</terms> .",
+        "twemoji": "Ny<twemoji> Twemoji</twemoji> Ny zavakanto emoji dia ©<author> Twitter, Inc ary mpandray anjara hafa</author> ampiasaina araka ny fepetran&#39;ny<terms> CC-BY 4.0</terms> .",
+        "twemoji_colr": "Ny<colr> twemoji-colr</colr>ny endriny dia © ny endri-tsoratra<author> Mozilla Foundation</author> ampiasaina araka ny fepetran&#39;ny<terms> Apache 2.0</terms> ."
+    },
+    "devtools": {
+        "active_widgets": "Widgets an-tserasera",
+        "category_other": "Hafa",
+        "category_room": "Efitrano",
+        "caution_colon": "Fampitandremana:",
+        "client_versions": "Dikan-teny ho an'ny mpanjifa",
+        "developer_mode": "Fomba mpamorona",
+        "developer_tools": "Fitaovana fampandrosoana",
+        "edit_setting": "Ahitsio fika",
+        "edit_values": "Ovay ireo sanda",
+        "empty_string": "<empty string>",
+        "event_content": "Votoatin'ny hetsika",
+        "event_id": "ID hetsika:%(eventId)s",
+        "event_sent": "Hetsika nalefa",
+        "event_type": "Karazana hetsika",
+        "explore_account_data": "Mpikaroka ny angona kaonty",
+        "explore_room_account_data": "Hijery fanazavana ny kaonty",
+        "explore_room_state": "Hijery fanjakana",
+        "failed_to_find_widget": "Nisy lesoka tamin'ny fitadiavana ity widget ity.",
+        "failed_to_load": "Tsy nahomby.",
+        "failed_to_save": "Tsy nahavita nanavotra ny toe-javatra.",
+        "failed_to_send": "Tsy nahomby ny fandefasana hetsika!",
+        "id": "Fanamarinana: ",
+        "invalid_json": "Tsy mitovy aminy JSON ho ekena io",
+        "level": "Ambaratonga",
+        "low_bandwidth_mode": "Maody ambany karoka mandalo",
+        "low_bandwidth_mode_description": "Mitaky homeserver mifanentana.",
+        "main_timeline": "Fandaharam-potoana lehibe",
+        "no_receipt_found": "Tsy misy rosia hita",
+        "notification_state": "Sata fampahafantarana dia<strong>%(notificationState)s</strong>",
+        "notifications_debug": "Débogage ny fanairana",
+        "number_of_users": "Isan'ny mpampiasa",
+        "original_event_source": "Loharano hetsika tany am-boalohany",
+        "room_encrypted": "Ny efitrano dia<strong> encrypted ✅</strong>",
+        "room_id": "Fanamariana%(numéro de pièce)s",
+        "room_not_encrypted": "Ny efitrano dia<strong> tsy misy encryption 🚨</strong>",
+        "room_notifications_dot": "Teboka: ",
+        "room_notifications_highlight": "Asongadino: ",
+        "room_notifications_last_event": "Hetsika farany:",
+        "room_notifications_sender": "Mpandefa: ",
+        "room_notifications_thread_id": "Fanamarinana: ",
+        "room_notifications_total": "Sokajy: ",
+        "room_notifications_type": "Karazana: ",
+        "room_status": "Satan'ny efitrano",
+        "room_unread_status_count": {
+            "one": "",
+            "other": "Sata tsy voavaky ny efitrano:<strong>%(status)s</strong> , isao:<strong>%(count)s</strong>"
+        },
+        "save_setting_values": "Tehirizo ny soatoavina napetraka",
+        "see_history": "Jereo ny tantara",
+        "send_custom_account_data_event": "Andefa sehatra iray avy aminy angon-kaonty manokana.",
+        "send_custom_room_account_data_event": "Andefa sehatra iray avy aminy angon-kaonty manokana.",
+        "send_custom_state_event": "Mandefa hetsikam-panjakana manokana",
+        "send_custom_timeline_event": "Mandefa hetsika manokana",
+        "server_info": "Mombamomban'i mpizara",
+        "server_versions": "Dikan'ny mpizara",
+        "settable_global": "Fanitsina ao amin'ny ambaratonga rehetra",
+        "settable_room": "Mety ahitsy amin'ny",
+        "setting_colon": "Fametrahana:",
+        "setting_definition": "Famaritana fikirana :",
+        "setting_id": "Id ny fanitsiana",
+        "settings_explorer": "Mpikaroka ny fikirana",
+        "show_hidden_events": "Asehoy ny hetsika miafina ao amin'ny tsipika",
+        "spaces": {
+            "one": "<space>",
+            "other": "<%(count)s spaces>"
+        },
+        "state_key": "Fanalahidin'ny fanjakana",
+        "thread_root_id": "Id fototra :%(threadRootId)s",
+        "threads_timeline": "Laha-tahirin'ny randram-potoana",
+        "title": "Fitaovana ny mampandroso",
+        "toggle_event": "Itsipaka hetsika",
+        "toolbox": "Toeram-pitahirizana ny mety ilaina",
+        "use_at_own_risk": "TSY manamarina ny karazana soatoavina ity UI ity. Ampiasao amin'ny risikao manokana.",
+        "user_read_up_to": "Ny mpampiasa dia namaky hatramin'ny :",
+        "user_read_up_to_ignore_synthetic": "Ny mpampiasa dia namaky hatramin'ny \n(ignorerSynthetic) :",
+        "user_read_up_to_private": "Ny mpampiasa dia namaky hatramin'ny (m.read.private) :",
+        "user_read_up_to_private_ignore_synthetic": "Ny mpampiasa mamaky hatramin'ny (m.read.private;ignoreSynthetic):",
+        "value": "Manandanja",
+        "value_colon": "Sarobidy:",
+        "value_in_this_room": "Sarobidy ao amin'ity efitrano ity",
+        "value_this_room_colon": "Sarobidy ao amin'ity efitrano ity:",
+        "values_explicit": "Sanda amin'ny ambaratonga mazava",
+        "values_explicit_colon": "Sanda amin'ny ambaratonga mazava:",
+        "values_explicit_room": "Sanda amin'ny ambaratonga mazava ao amin'ity efitrano ity",
+        "values_explicit_this_room_colon": "Sanda amin'ny ambaratonga mazava ao amin'ity efitrano ity:",
+        "view_servers_in_room": "Asehoy ireo mpizara ao",
+        "view_source_decrypted_event_source": "Loharano hetsika voafafa",
+        "view_source_decrypted_event_source_unavailable": "Loharanon-kevitra tsy azo ampiasaina",
+        "widget_screenshots": "Alefaso ny pikantsary widget amin'ny widget tohanana"
+    },
+    "dialog_close_label": "Akatona ny fifanakalozan-kevitra",
+    "emoji": {
+        "categories": "Sokajy",
+        "category_activities": "Fiaraha-mientana",
+        "category_animals_nature": "Ny biby sy ny natiora",
+        "category_flags": "Saina",
+        "category_food_drink": "Sakafo sy zava-pisotro",
+        "category_frequently_used": "Ampiasaina matetika",
+        "category_objects": "Zavatra",
+        "category_smileys_people": "Smileys sy olona",
+        "category_symbols": "Sariohatra",
+        "category_travel_places": "Fitsidihana & Toerana",
+        "quick_reactions": "Fanehoan-kevitra haingana"
+    },
+    "emoji_picker": {
+        "cancel_search_label": "Hanafoana ny fikarohana"
+    },
+    "empty_room": "Efitra banga",
+    "empty_room_was_name": "Efitra banga (dia%(oldName)s )",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "key_validation_text": {
+                "wrong_security_key": "Fanalahidin'ny fiarovana diso"
+            },
+            "restoring": "Famerenana ny fanalahidy avy amin'ny vakorakitra",
+            "security_key_title": "Kitendry fiarovana"
+        },
+        "bootstrap_title": "Fametrahana fanalahidy",
+        "cancel_entering_passphrase_description": "Tena te-hanafoana ny fampidirana fehezanteny ve ianao?",
+        "cancel_entering_passphrase_title": "Hanafoana ny fidirana amin'ny teny miafina ?",
+        "confirm_encryption_setup_body": "Kitiho ny bokotra etsy ambany hanamafisana ny fametrahana encryption.",
+        "confirm_encryption_setup_title": "Hamafiso ny fanamboarana fanafenana",
+        "cross_signing_room_normal": "Ity efitrano ity dia misy encryption avy hatrany",
+        "cross_signing_room_verified": "Ny olona rehetra ao amin'ity efitrano ity dia voamarina",
+        "cross_signing_room_warning": "Misy mpampiasa zotra tsy fantatra",
+        "cross_signing_user_normal": "Tsy nanamarina ity mpampiasa ity ianao.",
+        "cross_signing_user_verified": "Efa nanamarina ity mpampiasa ity ianao. Ity mpampiasa ity dia nanamarina ny fotoam-pivorian'izy ireo rehetra.",
+        "cross_signing_user_warning": "Ity mpampiasa ity dia tsy nanamarina ny fotoam-pivoriana rehetra.",
+        "event_shield_reason_authenticity_not_guaranteed": "Tsy azo antoka amin'ity fitaovana ity ny mahazo itokiana an'ity hafatra miafina ity.",
+        "event_shield_reason_mismatched_sender_key": "Encrypted amin&#39;ny zotra tsy voamarina",
+        "event_shield_reason_unknown_device": "Avy aminy olona tsy fantatra na hofafana",
+        "event_shield_reason_unsigned_device": "Avy aminy fitaovana iray tsy voamarin'ny tompony.",
+        "event_shield_reason_unverified_identity": "Encrypted amin'ny mpampiasa tsy voamarina.",
+        "export_unsupported": "Tsy mahazaka ny fanitarana kriptografika ilaina ny toeram-pivohizana",
+        "import_invalid_keyfile": "Tsy mitombina%(brand)s fanalahidiny trano-kala",
+        "import_invalid_passphrase": "Tsy nahomby ny fanamarinana fanamarinana: tenimiafina diso?",
+        "messages_not_secure": {
+            "cause_1": "Ny mpizara tranonao",
+            "cause_2": "Mifandray amin'ny mpizara izay mpampiasa nohamarininao",
+            "cause_3": "Ny anao, na ny fifandraisany mpampiasa hafa",
+            "cause_4": "Ny anao, na ny fivoriany mpampiasa hafa",
+            "heading": "Ny iray amin'ireto manaraka ireto dia mety ho simba:",
+            "title": "Tsy azo antoka ny hafatrao"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "Nisy andian-teny fiarovana vaovao sy fanalahidy hoany fiarovana dia hita.",
+            "description_2": "Ity fotoam-pivoriana ity dia manasivana tantara amin'ny alalany fomba fanarenana vaovao.",
+            "title": "Fomba fanarenana vaovao",
+            "warning": "Raha tsy nametraka ny fomba fanarenana vaovao ianao dia mety manandrana miditra amin'ny kaontinao ny mpanafika. Hanova ny tenimiafiny kaontinao ary mametraha fomba fanarenana vaovao avy hatrany ao aminy fanitsiana."
+        },
+        "recovery_method_removed": {
+            "description_1": "Ity fotoam-pivoriana ity dia nahita fa nesorina ny fehezan-teny fiarovana sy ny fanalahidy hoany fiarovana hafatra.",
+            "description_2": "Raha nanao izany tsy nahy ianao dia azonao atao ny manangana fiarovana zotra amin'ity zotra ity izay hanasivana indray ny tantarany hafatra amin'ity zotra ity miaraka amin'ny fomba fanarenana vaovao.",
+            "title": "Nesorina ny fomba fanarenana",
+            "warning": "Raha tsy nesorinao ny fomba fanarenana, dia mety hanandrana hiditra aminy kaontinao ny mpanafika. Hanova ny tenimiafiny kaontinao ary mametraha fomba fanarenana vaovao avy hatrany ao amin'ny fanitsiana."
+        },
+        "reset_all_button": "Adino na very ny fomba fanarenana rehetra?<a> Avereno daholo</a>",
+        "set_up_toast_description": "Arovy aminy fahaverezany fidirana amin'ny hafatra sy angon-drakitra voatahiry",
+        "set_up_toast_title": "Hanitsy angona voaharo",
+        "setup_secure_backup": {
+            "explainer": "Amboary ny fanalahidinao alohany hivoahana mba tsy ho very."
+        },
+        "udd": {
+            "interactive_verification_button": "Hamarino amin'ny alalany emoji",
+            "other_ask_verify_text": "Angataho ity mpampiasa ity mba hanamarina ny fotoam-pivorian'izy ireo, na hanamarina izany amin'ny tanana eto ambany.",
+            "other_new_session_text": "%(name)s(%(userId)s ) niditra tamina fivoriana vaovao nefa tsy nanamarina izany:",
+            "own_ask_verify_text": "Hamarino ny fotoam-pivorianao hafa aminy fampiasana ny iray amin'ireo safidy etsy ambany.",
+            "own_new_session_text": "Niditra tamina fivoriana vaovao ianao nefa tsy nanamarina izany:",
+            "title": "Tsy atokisana"
+        },
+        "unable_to_setup_keys_error": "Tsy afaka nanangana fanalahidy",
+        "verification": {
+            "accepting": "Manaiky…",
+            "after_new_login": {
+                "device_verified": "Voamarina ny fitaovana",
+                "skip_verification": "Alefaso ny fanamarinana amin'izao fotoana izao",
+                "unable_to_verify": "Tsy afaka manamarina ity fitaovana ity",
+                "verify_this_device": "Hamarino ity fitaovana ity"
+            },
+            "cancelled": "Nofoananao ny fanamarinana.",
+            "cancelled_self": "Nofoananao ny fanamarinana tamin'ny fitaovanao hafa.",
+            "cancelled_user": "%(displayName)snofoanana ny fanamarinana.",
+            "cancelling": "Manafoana…",
+            "complete_action": "Azoko",
+            "complete_description": "Ianao voamariko soa aman-tsara ity mpampiasa ity.",
+            "complete_title": "Voamarina!",
+            "error_starting_description": "Tsy afaka nanomboka niresaka tamin'ilay mpampiasa hafa izahay.",
+            "error_starting_title": "Hadisoana nanomboka ny fanamarinana",
+            "explainer": "Ny hafatra azo antoka miaraka amin'ity mpampiasa ity dia voafehin'ny farany ary tsy azony olon-kafa vakiana.",
+            "in_person": "Mba hahazoana antoka dia ataovy mivantana izany na mampiasà fomba azo itokisana hifandraisana.",
+            "incoming_sas_device_dialog_text_1": "Hamarino ity fitaovana ity mba hanamarihana azy ho azo itokisana. Ny fahatokisana an'ity fitaovana ity dia manome anao sy ny mpampiasa hafa fiadanan-tsaina fanampiny rehefa mampiasa hafatra misy miafina farany mankany amin'ny farany.",
+            "incoming_sas_device_dialog_text_2": "Ny fanamarinana ity fitaovana ity dia hanamarika azy ho atokisana, ary ireo mpampiasa izay nanamarina niaraka taminao dia hatoky ity fitaovana ity.",
+            "incoming_sas_dialog_title": "Fangatahana fanamarinana miditra",
+            "incoming_sas_dialog_waiting": "Miandry ny mpiara-miasa hanamarina…",
+            "incoming_sas_user_dialog_text_1": "Hamarino ity mpampiasa ity mba hanamarihana azy ireo ho azo itokisana. Ny fahatokisana ny mpampiasa dia manome anao fiadanan-tsaina fanampiny rehefa mampiasa hafatra voarakotra amin'ny farany.",
+            "incoming_sas_user_dialog_text_2": "Ny fanamarinana ity mpampiasa ity dia hanamarika ny fotoam-pivorian'izy ireo ho atokisana, ary hanamarika ihany koa ny fotoam-pivorianao ho atokisana azy ireo.",
+            "no_key_or_device": "Toa tsy manana fanalahidiny ianao na fitaovana hafa azonao hamarinina. Ity fitaovana ity dia tsy ho afaka miditra aminy hafatra efa misy miafina. Mba hanamarinana ny mombamomba anao amin'ity fitaovana ity dia mila averinao ny fanalahidiny fanamarinanao.",
+            "no_support_qr_emoji": "Ny fitaovana ezahinao hohamarinina dia tsy mahazaka ny scan kaody QR na fanamarinana emoji, izany hoe%(brand)s ny manohana. Andramo miaraka aminy mpanjifa hafa.",
+            "other_party_cancelled": "Nanafoana ny fanamarinana ny ankilany.",
+            "prompt_encrypted": "Hamarino ny mpampiasa rehetra ao amin'ny efitrano iray mba hahazoana antoka fa azo antoka izany.",
+            "prompt_self": "Atombohy indray ny fanamarinana avy amin'ny fampahafantarana.",
+            "prompt_unencrypted": "Ao amin'ny efitrano misy miafina, hamarino ny mpampiasa rehetra mba hahazoana antoka fa azo antoka.",
+            "prompt_user": "Atombohy indray ny fanamarinana avy amin'ny mombamomba azy.",
+            "qr_or_sas": "%(qrCode)sna%(emojiCompare)s",
+            "qr_or_sas_header": "Hamarino ity fitaovana ity aminy fanatanterahana ny iray amin'ireto manaraka ireto:",
+            "qr_prompt": "Tilio ity kaody tokana ity",
+            "qr_reciprocate_same_shield_device": "Saika any! Mampiseho ampinga mitovy ve ny fitaovanao hafa?",
+            "qr_reciprocate_same_shield_user": "Saika any! dia%(displayName)s mampiseho ampinga mitovy?",
+            "request_toast_accept": "Hamarino ny fizorana",
+            "request_toast_decline_counter": "Tsy miraharaha (%(counter)s )",
+            "request_toast_detail": "<x id =\"0\"/> avy amin'ny <x id =\"18\"/>",
+            "reset_proceed_prompt": "Ankany amin'ny famerenana ny rehetra aminy laoniny",
+            "sas_caption_self": "Hamarino ity fitaovana ity amin'ny fanamafisana ity isa manaraka ity dia miseho eo aminy efijery.",
+            "sas_caption_user": "Hamarino ity mpampiasa ity aminy fanamafisana ity isa manaraka ity dia miseho eo amin'ny efijery.",
+            "sas_description": "Ampitahao ny andiana emoji tokana raha tsy manana fakan-tsary amin'ny fitaovana roa ianao",
+            "sas_emoji_caption_self": "Hamafiso fa aseho amin'ny fitaovana roa ny emoji etsy ambany, amin'ny filaharana mitovy:",
+            "sas_emoji_caption_user": "Hamarino ity mpampiasa ity amin'ny fanamafisana ity emoji manaraka ity dia miseho eo amin'ny efijery.",
+            "sas_match": "Mifanaraka izy ireo",
+            "sas_no_match": "Tsy mifanentana izy ireo",
+            "sas_prompt": "Ampitahao ny emoji tokana",
+            "scan_qr": "Hamarino amin'ny fitiliana",
+            "scan_qr_explainer": "Anontanio%(displayName)s hijerena ny kaodinao:",
+            "self_verification_hint": "Mba hanohizana dia ekeo azafady ny fangatahana fanamarinana amin'ny fitaovanao hafa.",
+            "start_button": "Atombohy ny fanamarinana",
+            "successful_device": "Vitanao ny nanamarina%(deviceName)s (%(deviceId)s )!",
+            "successful_own_device": "Nahomby ianao nanamarina ny fitaovanao!",
+            "successful_user": "Ianao efa voamariko antsakany sy andavany%(displayName)s!",
+            "timed_out": "Tapitra ny fotoana fanamarinana.",
+            "unsupported_method": "Tsy nahita fomba fanamarinana tohana.",
+            "unverified_session_toast_accept": "Eny, izaho io",
+            "unverified_session_toast_title": "Fidirana vaovao. Ianao ve ity?",
+            "unverified_sessions_toast_description": "Avereno jerena mba hahazoana antoka fa azo antoka ny kaontinao",
+            "unverified_sessions_toast_reject": "Taty aoriana",
+            "unverified_sessions_toast_title": "Manana fivoriana tsy voamarina ianao",
+            "verification_description": "Hamarino ny maha-izy anao mba hidirana aminy hafatra miafina ary porofoy amin'ny hafa ny mombamomba anao.",
+            "verification_dialog_title_device": "Hamarino ny fitaovana hafa",
+            "verification_dialog_title_user": "Fangatahana fanamarinana",
+            "verification_skip_warning": "Raha tsy misy fanamarinana dia tsy afaka miditra aminy hafatrao rehetra ianao ary mety hiseho ho tsy atokisana amin'ny hafa.",
+            "verification_success_with_backup": "Voamarina izao ny fitaovanao vaovao. Afaka miditra amin'ny hafatrao voasakana izy io, ary ho hitany mpampiasa hafa ho azo itokisana.",
+            "verification_success_without_backup": "Voamarina izao ny fitaovanao vaovao. Ny mpampiasa hafa dia hahita azy io ho azo itokisana.",
+            "verify_emoji": "Hamarino amin'ny emoji",
+            "verify_emoji_prompt": "Hamarino amin'ny fampitahana emoji tokana.",
+            "verify_emoji_prompt_qr": "Raha tsy azonao atao ny mijery ny kaody etsy ambony dia hamarino amin'ny fampitahana emoji tokana.",
+            "verify_later": "Hamariniko avy eo",
+            "verify_using_device": "Hamarino amin'ny fitaovana hafa",
+            "verify_using_key": "Hamarino aminy fanalahidiny voaharo",
+            "verify_using_key_or_phrase": "Hamarino amin'ny fanalahidy na rakin-tsoratra voaharo",
+            "waiting_for_user_accept": "Miandry%(displayName)s manaiky…",
+            "waiting_other_device": "Miandry anao hanamarina amin'ny fitaovanao hafa…",
+            "waiting_other_device_details": "Miandry anao hanamarina amin'ny fitaovanao hafa,%(deviceName)s (%(deviceId)s )…",
+            "waiting_other_user": "Miandry%(displayName)s hanamarina…"
+        },
+        "verification_requested_toast_title": "Fanamarinana nangatahana",
+        "verify_toast_description": "Mety tsy matoky azy ny mpampiasa hafa",
+        "verify_toast_title": "Hamarino ity fivoriana ity"
+    },
+    "error": {
+        "admin_contact": "Mba miangavy re<a> mifandraisa amin'ny mpitantana ny serivisy</a> hanohizana mampiasa ity serivisy ity.",
+        "admin_contact_short": "Mifandraisa amin'ny<a> mpizara admin</a> .",
+        "connection": "Nisy olana taminy fifandraisana tamin'ny homeserver, andramo indray rehefa afaka kelikely.",
+        "dialog_description_default": "Nisy hadisoana nitranga.",
+        "download_media": "Tsy nahomby ny fampidinana haino aman-jery loharano, tsy nisy url loharano hita",
+        "edit_history_unsupported": "Toa tsy manohana an'io endri-javatra io ny mpizara tranonao.",
+        "failed_copy": "Tsy nahavita kopia",
+        "hs_blocked": "Nosakanan'ny mpitantana azy io mpizara trano io.",
+        "mau": "Nahatratra ny fetran'ny mpampiasa mavitrika isam-bolana ity homeserver ity.",
+        "mixed_content": "Tsy afaka mifandray aminy homeserver amin'ny alàlan'ny HTTP rehefa misy URL HTTPS ao aminy bara navigateur. Na mampiasa HTTPS na<a> avelao ny script tsy azo antoka</a>.",
+        "non_urgent_echo_failure_toast": "Tsy mamaly ny sasany ny mpizara anao<a> fangatahana</a> .",
+        "resource_limits": "Nihoatra ny iray amin'ireo fetran'ny loharanon-karena ity mpizara trano ity.",
+        "session_restore": {
+            "clear_storage_button": "Fadio ny fitahirizana ary mivoaha",
+            "clear_storage_description": "Misoratra anarana ary manala ny fanalahidin'ny fanafenan-tsoratra?",
+            "description_1": "Nisedra lesoka izahay nanandrana namerina ny fotoam-pivorianao teo aloha.",
+            "description_2": "Raha efa nampiasa dikan-teny vao haingana kokoa ianao teo aloha%(brand)s , mety tsy mifanaraka aminity version ity ny session-nao. Akatona ity varavarankely ity ary miverena amin'ny kinova farany.",
+            "description_3": "Ny famafana ny fitahirizan'ny mpitety tranonkalanao dia mety hamaha ilay olana, fa hanasonia anao hivoaka ary hahatonga ny tantaran'ny chat voatahiry ho tsy azo vakina.",
+            "title": "Tsy afaka namerina ny session"
+        },
+        "something_went_wrong": "Nisy zavatra tsy nety!",
+        "storage_evicted_description_1": "Tsy hita ny angon-drakitra session sasany, anisan'izany ny fanalahidin'ny hafatra miafina. Mivoaha ary midira hanamboatra izany, mamerina ny fanalahidy avy amin'ny vakorakitra.",
+        "storage_evicted_description_2": "Azo inoana fa nesorin'ny mpitety tranonkalanao ity angona ity rehefa kely ny habaka kapila.",
+        "storage_evicted_title": "Tsy ampy angona fivoriana",
+        "sync": "Tsy afaka mifandray amin'ny Homeserver. Manandrana indray…",
+        "tls": "Tsy afaka mifandray amin'ny homeserver -azafady, jereo ny fifandraisanao, ho azo antoka fa ny anao<a> certificat SSL homeserver</a> azo itokisana, ary tsy manakana ny fangatahana ny fanitarana navigateur.",
+        "unknown": "Hadisoana tsy fantatra",
+        "unknown_error_code": "kaody fahadisoana tsy fantatra",
+        "update_power_level": "Tsy nahavita nanova ny ahavon'ny herinaratra"
+    },
+    "error_app_open_in_another_tab": "Midira amin'ny tabilao hafa hifandraisana%(brand)s . Afaka mikatona ity tabilao ity.",
+    "error_app_open_in_another_tab_title": "%(brand)sdia mifandray amin'ny tabilao hafa",
+    "error_app_opened_in_another_window": "%(brand)sdia misokatra aminy varavarankely hafa. Tsindrio%(label)s mampiasa%(brand)s eto ary tapaho ny varavarankely hafa.",
+    "error_database_closed_description": {
+        "for_desktop": "Mety feno ny kapilanao. Hamafa toerana kely azafady ary avereno avereno.",
+        "for_web": "Raha nesorinao ny angona fitetezana dia andrasana ity hafatra ity.%(brand)s mety misokatra amin'ny tabilao hafa koa, na feno ny kapilanao. Hamafa toerana kely azafady ary avereno avereno"
+    },
+    "error_database_closed_title": "%(brand)snijanona tsy niasa",
+    "error_dialog": {
+        "copy_room_link_failed": {
+            "description": "Tsy afaka mandika rohy mankany amin'ny efitrano mankany amin'ny solaitrabe.",
+            "title": "Tsy afaka mandika ny rohiny efitrano"
+        },
+        "error_loading_user_profile": "Tsy afaka mampiditra ny mombamomba ny mpampiasa",
+        "forget_room_failed": "Tsy nanadino ny efitrano%(errCode)s"
+    },
+    "error_user_not_logged_in": "Tsy tafiditra ny mpampiasa",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "Mizotra ny antso",
+            "user": "%(senderName)snanatevin-daharana ny antso",
+            "you": "Nandray anjara taminy antso ianao"
+        },
+        "m.call.hangup": {
+            "user": "%(senderName)snamarana ny antso",
+            "you": "Namarana ny antso ianao"
+        },
+        "m.call.invite": {
+            "dm_receive": "%(senderName)sdia miantso",
+            "dm_send": "Miandry valiny",
+            "user": "%(senderName)snanomboka antso",
+            "you": "Nanomboka niantso ianao"
+        },
+        "m.emote": "*%(senderName)s%(emote)s",
+        "m.reaction": {
+            "user": "%(sender)snaneho hevitra%(reaction)s ny%(message)s",
+            "you": "Naneho hevitra ianao%(reaction)s ny%(message)s"
+        },
+        "m.sticker": "%(senderName)s:%(stickerName)s",
+        "m.text": "%(senderName)s:%(message)s"
+    },
+    "export_chat": {
+        "cancelled": "Nofoanana ny fanondranana",
+        "cancelled_detail": "Nofoanana soa aman-tsara ny fanondranana",
+        "confirm_stop": "Tena te hampitsahatra ny fanondranana angonao ve ianao? Raha manao izany ianao dia mila manomboka indray.",
+        "creating_html": "Mamorona HTML…",
+        "creating_output": "Mamorona vokatra…",
+        "creator_summary": "%(creatorName)snamorona ity efitrano ity.",
+        "current_timeline": "Famataran'andro amin'izao fotoana izao",
+        "enter_number_between_min_max": "Ampidiro isa eo anelanelan'ny%(min)s sy%(max)s",
+        "error_fetching_file": "Hadisoana fakana rakitra",
+        "export_info": "Manomboka izao ny fanondranana ny<roomName/> . Naondrana tamin'ny<exporterDetails/> amin'ny%(exportDate)s .",
+        "export_successful": "Tafita ny fanondranana!",
+        "exported_n_events_in_time": {
+            "one": "Naondrana%(count)s hetsika ao%(seconds)s segondra",
+            "other": "Naondrana%(count)s hetsika ao%(seconds)s segondra"
+        },
+        "exporting_your_data": "Manondrana ny angonao",
+        "fetched_n_events": {
+            "one": "Averina alaina%(count)s hetsika hatramin'izao",
+            "other": "Averina alaina%(count)s hetsika hatreto"
+        },
+        "fetched_n_events_in_time": {
+            "one": "Averina alaina%(count)s hetsika ao%(seconds)s s",
+            "other": "Averina alaina%(count)s hetsika ao%(seconds)s s"
+        },
+        "fetched_n_events_with_total": {
+            "one": "Averina alaina%(count)s hetsika ivelan'ny%(total)s",
+            "other": "Averina alaina%(count)s hetsika ivelan'ny%(total)s"
+        },
+        "fetching_events": "Manarina ireo hetsika",
+        "file_attached": "Rakitra miraikitra",
+        "format": "Endrika",
+        "from_the_beginning": "Hatrany am-boalohany",
+        "generating_zip": "Mamorona ZIP",
+        "html": "HTML",
+        "html_title": "Data aondrana",
+        "include_attachments": "Ampidiro izay ampiarana aminy",
+        "json": "JSON",
+        "media_omitted": "Nesorina ny haino aman-jery",
+        "media_omitted_file_size": "Aina-manjery nesorina - nihaotra ny fetran'ny haben'ny rakitra",
+        "messages": "Hafatra",
+        "next_page": "Vondrona hafatra manaraka",
+        "num_messages": "Isany hafatra",
+        "num_messages_min_max": "Ny isany hafatra dia mety ho isa eo anelanelan'ny%(min)s sy%(max)s",
+        "number_of_messages": "Mametraha hafatra maromaro",
+        "previous_page": "Vondrona hafatra teo aloha",
+        "processing": "Mikarakara…",
+        "processing_event_n": "Hetsika fanodinana%(number)s ivelan'ny%(total)s",
+        "select_option": "Misafidiana amin'ireo safidy etsy ambany hanondranana chat avy amin'ny fandaharam-potoanao",
+        "size_limit": "Fetra habe",
+        "size_limit_min_max": "Ny habeny dia mety ho isa eo anelanelan'ny%(min)s MB ary%(max)s MB",
+        "size_limit_postfix": "MB",
+        "starting_export": "Manomboka manondrana…",
+        "successful": "Nahomby ny fanondranana",
+        "successful_detail": "Nahomby ny fanondrananao. Tadiavo ao amin'ny lahatahiry fakana izany.",
+        "text": "Lahatsoratra tsotra",
+        "title": "Afindra",
+        "topic": "Lohahevitra:%(topic)s",
+        "unload_confirm": "Tena te-hiala ve ianao mandritra ity fanondranana ity?"
+    },
+    "failed_load_async_component": "Tsy afaka mitondra entana! Jereo ny fifandraisanao amin'ny tambajotra ary andramo indray.",
+    "feedback": {
+        "can_contact_label": "Afaka mifandray amiko ianao raha manana fanontaniana manaraka",
+        "comment_label": "Fanehoan-kevitra",
+        "existing_issue_link": "Mba jereo<existingIssuesLink> misy bugs ao aminy Github</existingIssuesLink> voalohany. Tsy misy lalao?<newIssueLink> Manomboha vaovao</newIssueLink> .",
+        "may_contact_label": "Afaka mifandray amiko ianao raha te-hanaraka na hamela ahy hanandrana hevitra ho avy",
+        "platform_username": "Ny sehatra sy ny solonanaranao dia ho marihina mba hanampiana anay hampiasa ny valin-teninao araka izay tratranay.",
+        "pro_type": "SOSO-KEVITRA PRO: Raha manomboka bug ianao dia alefaso azafady<debugLogsLink> debug logs</debugLogsLink> mba hanampy antsika hanara-maso ny olana.",
+        "send_feedback_action": "Alefaso hevitra",
+        "sent": "Nalefa ny valiny! Misaotra, mankasitraka izany izahay!"
+    },
+    "file_panel": {
+        "empty_description": "Ampifandraiso ireo rakitra avy aminy chat na tariho fotsiny ary ampidino na aiza na aiza ao amin'ny efitrano iray.",
+        "empty_heading": "Tsy misy rakitra hita ao amin'ity efitrano ity",
+        "guest_note": "Ianao dia tsy maintsy<a> hisoratra anarana</a> hampiasa io fampiasa io",
+        "peek_note": "Tsy maintsy miditra ao amin'ny efitrano ianao raha te hahita ny rakitra"
+    },
+    "forward": {
+        "filter_placeholder": "Mitadiava efitrano na olona",
+        "message_preview_heading": "Fijerena ny hafatra",
+        "no_perms_title": "Tsy manana alalana hanao izany ianao",
+        "open_room": "Efitrano misokatra",
+        "send_label": "Alefaso",
+        "sending": "Fandefasana",
+        "sent": "Nalefa"
+    },
+    "identity_server": {
+        "change": "Hanova ny mpizara famantarana",
+        "change_prompt": "Atsaharo ny fifandraisana amin'ny mpizara famantarana<current /> ary mifandray amin'ny<new /> fa kosa?",
+        "change_server_prompt": "Raha tsy te hampiasa<server /> mba hahitana sy ho hitan'ny fifandraisana efa fantatrao, midira mpizara famantarana hafa eto ambany.",
+        "checking": "Fanamarinana ny mpizara",
+        "description_connected": "Ampiasainao amin'izao fotoana izao<server></server> mba hahitana sy ho hitany fifandraisana efa misy fantatrao. Azonao atao ny manova ny mpizara famantarana anao eto ambany.",
+        "description_disconnected": "Tsy mampiasa mpizara famantarana ianao amin'izao fotoana izao. Raha te hahita sy ho hitany fifandraisana efa fantatrao ianao dia ampio iray eto ambany.",
+        "description_optional": "Ny fampiasana mpizara famantarana dia tsy voatery. Raha misafidy ny tsy hampiasa mpizara famantarana ianao dia tsy ho hitany mpampiasa hafa ianao ary tsy afaka manasa olon-kafa aminy alàlany mailaka na telefaona.",
+        "disconnect": "Atsaharo ny mpizara famantarana",
+        "disconnect_anyway": "Tapaho ny fifandraisana",
+        "disconnect_offline_warning": "Ianao dia tokony<b> esory ny angon-drakitrao manokana</b> avy amin'ny mpizara famantarana<idserver /> alohan'ny hanapahana. Indrisy, mpizara famantarana<idserver /> an-tserasera amin'izao fotoana izao na tsy azo tratrarina.",
+        "disconnect_personal_data_warning_1": "Mbola ianao<b> mizara ny angonao manokana</b> amin'ny mpizara famantarana<idserver /> .",
+        "disconnect_personal_data_warning_2": "Manoro hevitra izahay ny hanaisotra ny adiresy mailaka sy ny nomeraon-telefaoninao amin'ny lohamilina famantarana alohany hanapahanao azy.",
+        "disconnect_server": "Atsaharo ny fifandraisana amin'ny mpizara famantarana<idserver /> ?",
+        "disconnect_warning": "Ny fanapahana amin'ny mpizara famantarana anao dia midika fa tsy ho hitany mpampiasa hafa ianao ary tsy afaka manasa olon-kafa amin'ny alàlany mailaka na telefaona.",
+        "do_not_use": "Aza mampiasa mpizara famantarana",
+        "error_connection": "Tsy afaka nifandray tamin'ny mpizara famantarana",
+        "error_invalid": "Tsy mpizara famantarana manan-kery (status code%(code)s )",
+        "error_invalid_or_terms": "Tsy ekena ny fepetran'ny serivisy na tsy mety ny mpizara famantarana.",
+        "no_terms": "Ny mpizara famantarana nofidinao dia tsy manana fepetran'ny serivisy.",
+        "suggestions": "Ianao dia tokony:",
+        "suggestions_1": "Jereo ny plugins toeram-pivohizana anao raha misy zavatra mety hanakana ny mpizara famantarana (toy ny Privacy Badger)",
+        "suggestions_2": "Mifandraisa amin'ny mpitantana ny mpizara famantarana<serveurid />",
+        "suggestions_3": "andraso ary andramo indray rehefa afaka kelikely",
+        "url": "Mpizara famantarana (%(server)s )",
+        "url_field_label": "Ampidiro mpizara famantarana vaovao",
+        "url_not_https": "Tsy maintsy HTTPS ny URL mpizara famantarana"
+    },
+    "in_space": "Ao amin'ny %( nom de l'espace ) s.",
+    "in_space1_and_space2": "Amin'ny habakabaka%(space1Name)s sy%(space2Name)s .",
+    "in_space_and_n_other_spaces": {
+        "one": "In%(spaceName)s ary toerana iray hafa.",
+        "other": "In%(spaceName)s sy%(count)s habakabaka hafa."
+    },
+    "info_tooltip_title": "Fampahalalana",
+    "integration_manager": {
+        "connecting": "Mifandray amin'ny mpitantana fampidirana…",
+        "error_connecting": "Ny mpitantana fampidirana dia ivelan'ny aterineto na tsy afaka mahatratra ny homeserver-nao.",
+        "error_connecting_heading": "Tsy afaka mifandray amin'ny mpitantana fampidirana",
+        "explainer": "Ny mpitantana ny fampidirana dia mahazo angon-drakitra fikirakirana, ary afaka manova widgets, mandefa fanasana efitrano ary mametraka ny ahavon'ny herinaratra ho anao.",
+        "manage_title": "Mitantana fampidirana",
+        "use_im": "Mampiasà mpitantana fampidirana hitantana bots, widgets ary fonosana sticker.",
+        "use_im_default": "Mampiasà mpitantana integration<b> (%(serverName)s )</b> hitantana bots, widgets ary fonosana sticker."
+    },
+    "integrations": {
+        "disabled_dialog_description": "Mandea%(manageIntegrations)s ao amin'ny fanitsiana hanaovana izany.",
+        "disabled_dialog_title": "Ny fampidirana dia kilemaina",
+        "impossible_dialog_description": "Ny%(brand)s tsy mamela anao hampiasa mpitantana fampidirana hanaovana izany. Mifandraisa amina admin iray azafady.",
+        "impossible_dialog_title": "Tsy azo atao ny fampidirana"
+    },
+    "invite": {
+        "ask_anyway_description": "Tsy afaka mahita ny mombamomba ireo ID voatanisa etsy ambany - te hanomboka DM ve ianao?",
+        "ask_anyway_label": "Manomboka DM na izany aza",
+        "ask_anyway_never_warn_label": "Manomboha DM na izany aza ary aza mampitandrina ahy intsony",
+        "email_caption": "Asao amin'ny imailaka",
+        "email_limit_one": "Ny fanasana amin'ny alàlan'ny imailaka ihany no azo alefa tsirairay",
+        "email_use_default_is": "Mampiasà mpizara famantarana hanasana amin'ny imailaka.<default> Ampiasao ny ara-pototra (%(defaultIdentityServerName)s )</default> na mitantana in<settings> Fikirana</settings> .",
+        "email_use_is": "Mampiasà mpizara famantarana hanasana amin'ny imailaka. Mitantana in<settings> Fikirana</settings> .",
+        "error_already_invited_room": "Ny mpampiasa dia efa nasaina ho any amin'ny efitrano",
+        "error_already_invited_space": "Ny mpampiasa dia efa nasaina ho any amin'ny toerana",
+        "error_already_joined_room": "Efa ao amin'ny efitrano ny mpampiasa",
+        "error_already_joined_space": "Efa ao amin'ny habakabaka ny mpampiasa",
+        "error_bad_state": "Tsy maintsy tsy voarara ny mpampiasa vao afaka manasa azy.",
+        "error_dm": "Tsy afaka namorona ny DM-nao izahay.",
+        "error_find_room": "Nisy zavatra tsy nety tamin'ny fiezahana hanasa ireo mpampiasa.",
+        "error_find_user_description": "Ireto mpampiasa manaraka ireto dia mety tsy misy na tsy manan-kery, ary tsy azo asaina: %(csvNames) s",
+        "error_find_user_title": "Tsy nahita ireto mpampiasa manaraka ireto",
+        "error_invite": "Tsy afaka nanasa ireo mpampiasa ireo izahay. Jereo azafady ny mpampiasa tianao hasaina ary andramo indray.",
+        "error_permissions_room": "Tsy manana alalana hanasa olona ho amin'ity efitrano ity ianao.",
+        "error_permissions_space": "Tsy manana alalana hanasa olona ho amin'ity habaka ity ianao.",
+        "error_profile_undisclosed": "Mety misy na tsy misy ny mpampiasa",
+        "error_transfer_multiple_target": "Ny antso dia tsy azo alefa afa-tsy amin'ny mpampiasa tokana.",
+        "error_unfederated_room": "Tsy federasiona ity efitrano ity. Tsy afaka manasa olona avy amin'ny mpizara ivelany ianao.",
+        "error_unfederated_space": "Ity habaka ity dia tsy federasiona. Tsy afaka manasa olona avy amin'ny mpizara ivelany ianao.",
+        "error_unknown": "Fahadisoana tsy fantatra teo amin'ny mpizara",
+        "error_user_not_found": "Tsy misy ny mpampiasa",
+        "error_version_unsupported_room": "Ny homeserver an'ny mpampiasa dia tsy manohana ny dikan'ny efitrano.",
+        "error_version_unsupported_space": "Ny homeserver any mpampiasa dia tsy mahazaka ny dikan'ny habaka.",
+        "failed_generic": "Tsy nahomby ny fandidiana",
+        "failed_title": "Tsy nahavita nanasa",
+        "invalid_address": "Adiresy tsy fantatra",
+        "name_email_mxid_share_room": "Manasà olona iray mampiasa ny anarany, ny adiresy imailaka, ny solon'anarana (toa ny<userId/> ) na<a> mizara ity efitrano ity</a> .",
+        "name_email_mxid_share_space": "Manasà olona iray mampiasa ny anarany, ny adiresy mailaka, ny solon'anarana (toa ny<userId/> ) na<a> zarao ity habaka ity</a> .",
+        "name_mxid_share_room": "Manasà olona iray mampiasa ny anarany, solonanarana (toy ny<userId/> ) na<a> mizara ity efitrano ity</a> .",
+        "name_mxid_share_space": "Manasà olona iray mampiasa ny anarany, solonanarana (toy ny<userId/> ) na<a> zarao ity habakabaka ity</a> .",
+        "recents_section": "Resadresaka vao haingana",
+        "room_failed_partial": "Nalefanay ny hafa, fa tsy azo nasaina ny olona teo ambany<RoomName/>",
+        "room_failed_partial_title": "Tsy azo nalefa ny fanasana sasany",
+        "room_failed_title": "Tsy nahavita nanasa ireo mpampiasa%(roomName)s",
+        "send_link_prompt": "Na alefaso rohy fanasana",
+        "start_conversation_name_email_mxid_prompt": "Manomboha resadresaka amin'ny olona iray mampiasa ny anarany, adiresy imailaka na solon'anarana (toy ny<userId/> ).",
+        "start_conversation_name_mxid_prompt": "Manomboha resadresaka amin'olona mampiasa ny anarany na solon'anarana (toy ny<userId/> ).",
+        "suggestions_disclaimer": "Mety hafenina hoany fiainana manokana ny soso-kevitra sasany.",
+        "suggestions_disclaimer_prompt": "Raha tsy hitanao hoe iza no tadiavinao dia alefaso eto ambany ny rohy fanasanao.",
+        "suggestions_section": "Vao aingana ialoa",
+        "to_room": "Asao ho%(roomName)s",
+        "to_space": "Asaina ho%(spaceName)s",
+        "transfer_dial_pad_tab": "Fehizoro nomerika",
+        "transfer_user_directory_tab": "Lahatahirin'ny mpampiasa",
+        "unable_find_profiles_description_default": "Tsy mahita ny mombamomba ireo ID voatanisa etsy ambany - te hanasa azy ireo ihany ve ianao?",
+        "unable_find_profiles_invite_label_default": "Manasa ihany",
+        "unable_find_profiles_invite_never_warn_label_default": "Asao ihany ary aza mampitandrina ahy intsony",
+        "unable_find_profiles_title": "Mety tsy misy ireto mpampiasa manaraka ireto",
+        "unban_first_title": "Tsy azo asaina ny mpampiasa raha tsy voarara"
+    },
+    "inviting_user1_and_user2": "Manintona%(user1)s sy%(user2)s",
+    "inviting_user_and_n_others": {
+        "one": "Manintona%(user)s ary ny iray hafa",
+        "other": "Manintona%(user)s SY%(count)s ny hafa"
+    },
+    "items_and_n_others": {
+        "one": "<Items/>ary ny iray hafa",
+        "other": "<Items/>sy%(count)s ny hafa"
+    },
+    "keyboard": {
+        "activate_button": "Anaiky ny bokotra voafantina",
+        "alt": "Alt",
+        "autocomplete_cancel": "Manafoana ny ampahany dika mizotra oazy ",
+        "autocomplete_force": "Hery feno",
+        "autocomplete_navigate_next": "Soso-kevitra manaraka aminy ampahany dika mizotra oazy ",
+        "autocomplete_navigate_prev": "Soso-kevitra dika mizotra oazy teo aloha",
+        "backspace": "Miverina an'i aoriana",
+        "cancel_reply": "Manafoana ny famaliana hafatra",
+        "category_autocomplete": "Dika mizotra oazy",
+        "category_calls": "Antso",
+        "category_navigation": "Ny toem-pivohizana",
+        "category_room_list": "Lisitry ny efitrano",
+        "close_dialog_menu": "Akatona ny dinika na ny lisitra context",
+        "composer_jump_end": "Hanketo amin'ny farany mpamoron-kira",
+        "composer_jump_start": "Hanketo amin'ny fanombohany mpamoron-kira",
+        "composer_navigate_next_history": "Mandehana mankany aminy hafatra manaraka amin'ny tantaran'ny mpamoron-kira",
+        "composer_navigate_prev_history": "Mandehana mankany amin'ny hafatra teo aloha aminy tantaran'ny mpamoron-kira",
+        "composer_new_line": "Andalana vaovao",
+        "composer_redo": "Avereno ny fanitsiana",
+        "composer_toggle_bold": "Ho alefa ho ngeza",
+        "composer_toggle_code_block": "Ho alefa ho fehizoro teny miafina",
+        "composer_toggle_italics": "Ho alefa ho italika",
+        "composer_toggle_link": "Ho alefa makany amin'ny rohy",
+        "composer_toggle_quote": "Ho alefa ho devis",
+        "composer_undo": "Ho foanana ny fanitsiana",
+        "control": "Ctrl",
+        "dismiss_read_marker_and_jump_bottom": "Esory ny marika vakiana ary mitsambikina mankany ambany",
+        "end": "Tapitra",
+        "enter": "Ampidiro",
+        "escape": "Ihao-pefy",
+        "go_home_view": "Mandehana any amin'ny fandraisana fijerena",
+        "home": "Trano",
+        "jump_first_message": "Hanketo amin'ny hafatra voalohany",
+        "jump_last_message": "Hanketo amin'ny hafatra farany",
+        "jump_room_search": "Hanketo amin'ny fikarohana efitrano",
+        "jump_to_read_marker": "Hanketo amin'ny hafatra tranainy indrindra tsy voavaky",
+        "keyboard_shortcuts_tab": "Sokafy ity tabilao fitendry ity",
+        "navigate_next_history": "Efitrano na habakabaka notsidihina taty aoriana",
+        "navigate_next_message_edit": "Mandehana mankany amin'ny hafatra manaraka hanitsiana",
+        "navigate_prev_history": "Efitrano na habakabaka notsidihina teo aloha",
+        "navigate_prev_message_edit": "Mankanesa any amin'ny hafatra teo aloha hanitsiana",
+        "next_room": "Efitrano manaraka na DM",
+        "next_unread_room": "Efitrano tsy vakiana manaraka na DM",
+        "number": "[isa]",
+        "open_user_settings": "Sokafy ny firafitry ny mpampiasa",
+        "page_down": "Pejy midina",
+        "page_up": "Pejy mirohy",
+        "prev_room": "Efitrano teo aloha na DM",
+        "prev_unread_room": "Efitrano tsy novakiana teo aloha na DM",
+        "room_list_collapse_section": "Atsaharo ny fizarana lisitry ny efitrano",
+        "room_list_expand_section": "Hanitatra ny fizarana lisitry ny efitrano",
+        "room_list_navigate_down": "Midira ao amin'ny lisitry ny efitrano",
+        "room_list_navigate_up": "Midira ao amin'ny lisitry ny efitrano",
+        "room_list_select_room": "Safidio ny efitrano ao amin'ny lisitry ny efitrano",
+        "scroll_down_timeline": "Miverena midina ao amin'ny fandaharam-potoana",
+        "scroll_up_timeline": "Hiverina any amin'ny timeline",
+        "search": "Fikarohana (tsy maintsy alefa)",
+        "send_sticker": "Mandefasa autocollants",
+        "shift": "Fiovàna",
+        "space": "Malalaka",
+        "switch_to_space": "Miverena any amin'ny habakabaka amin'ny isa",
+        "toggle_hidden_events": "Ampifamadiho ny fahitana hetsika miafina",
+        "toggle_microphone_mute": "Ampidiro mangina ny mikrofona",
+        "toggle_right_panel": "Ampifamadiho ny tontonana havanana",
+        "toggle_space_panel": "Alefa aminy tontonana toerana malalaka",
+        "toggle_top_left_menu": "Ampifamadiho ny sakafo ambony havia",
+        "toggle_webcam_mute": "Anaiky ny vata fakan-tsary amin'ny on/off",
+        "upload_file": "Mampiakara rakitra"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "Avelao ny fomba fizarana efijery ihany",
+        "ask_to_join": "Alefaso ny fangatahana hiditra",
+        "automatic_debug_logs": "Alefaso ho azy ny log debug amin'ny lesoka rehetra",
+        "automatic_debug_logs_decryption": "Alefaso ho azy ny diarin'ny debug momba ny lesoka decryption",
+        "automatic_debug_logs_key_backup": "Alefaso ho azy ny log debug rehefa tsy mandeha ny fanalahidiny vakorakitra",
+        "beta_description": "Inona no manaraka%(brand)s ? Labs no fomba tsara indrindra hahazoana zavatra mialoha, hizaha toetra vaovao ary hanampy amin'ny famolavolana azy ireo alohany hanombohany.",
+        "beta_feature": "Ity dia endri-javatra beta",
+        "beta_feedback_leave_button": "Raha te hiala amin'ny beta dia tsidiho ny kiranao.",
+        "beta_feedback_title": "%(featureName)s bêta fanehoan-kevitra",
+        "beta_section": "Endri-javatra ho avy",
+        "bridge_state": "Asehoy ny mombamomba ny tetezana ao aminy firafitry ny efitrano",
+        "bridge_state_channel": "Fantsona: <channelLink />",
+        "bridge_state_creator": "Ity tetezana ity dia nomen'ny<user /> .",
+        "bridge_state_manager": "Ny tetezana dia tarihan'ny <utilisateur />.",
+        "bridge_state_workspace": "Toeram-piasana:<networkLink/>",
+        "click_for_info": "Tsindrio raha mila fanazavana fanampiny",
+        "currently_experimental": "Andrana amin'izao fotoana izao.",
+        "custom_themes": "Tohano ny fampidirana lohahevitra manokana",
+        "dynamic_room_predecessors": "Efitrano dinamika taloha",
+        "dynamic_room_predecessors_description": "Alefaso ny MSC3946 (mba hanohanana ny tahirin'ny efitrano fahatongavana tara)",
+        "element_call_video_rooms": "Efitrano fandraisan'orinan-tsary",
+        "experimental_description": "Mahatsiaro andrana? Andramo ny hevitray farany amin'ny fampandrosoana. Tsy vita ireo endri-javatra ireo; mety hikorontana izy ireo, mety hiova, na mety ho latsaka tanteraka.<a> Hamantatra bebe kokoa</a> .",
+        "experimental_section": "Fijery mialoha",
+        "feature_disable_call_per_sender_encryption": "Atsaharo ny encryption isaky ny mpandefa hoany Element Call",
+        "feature_wysiwyg_composer_description": "Mampiasà lahatsoratra manankarena fa tsy Markdown ao amin'ny mpamoron-dahatsoratra.",
+        "group_calls": "Traikefa antso vondrona vaovao",
+        "group_developer": "Fampandrosoana",
+        "group_encryption": "Fanafenana",
+        "group_experimental": "Fanandramana",
+        "group_messaging": "Hafatra",
+        "group_moderation": "Fandaminana",
+        "group_profile": "Mombamomba",
+        "group_rooms": "Efitrano",
+        "group_spaces": "Ireo toerana malalaka",
+        "group_themes": "Lohahevitra",
+        "group_threads": "Kofehy",
+        "group_voip": "Feo sy Orinan-tsary",
+        "group_widgets": "Widgets",
+        "hidebold": "Afeno ny teboka fampandrenesana (asehoy fotsiny ny mari-pamantarana kaontinanta)",
+        "html_topic": "Asehoy ny fanehoana HTML momba ny lohahevitra efitrano",
+        "join_beta": "Midira amin'ny beta",
+        "join_beta_reload": "Hamerina ny fidirana amin'ny beta%(brand)s .",
+        "jump_to_date": "Hanketo amin'ny daty (manampy / hitsambikina ary hitsambikina amin'ny lohateniny daty)",
+        "jump_to_date_msc_support": "Mitaky ny mpizara anao hanohana ny MSC3030",
+        "latex_maths": "Alefaso amin'ny hafatra ny matematika LaTeX",
+        "leave_beta": "Iala ny fivondronana beta",
+        "leave_beta_reload": "Ny fialana amin'ny beta dia haverina%(brand)s .",
+        "location_share_live": "Fizarana toerana mivantana",
+        "location_share_live_description": "Fampiharana vonji-maika. Mijanona ao amin'ny tantarany efitrano ny toerana.",
+        "mjolnir": "Fomba vaovao fialana olona",
+        "msc3531_hide_messages_pending_moderation": "Avelao ny mpandrindra hanafina ny hafatra miandry ny fandrindrana.",
+        "notification_settings": "Fikirana fampahafantarana vaovao",
+        "notification_settings_beta_caption": "Fampidirana fomba tsotra kokoa hanovana ny fampandrenesanao. Amboary ny anao%(brand)s , araka izay tianao ihany.",
+        "notification_settings_beta_title": "Fikirana fampahafantarana",
+        "notifications": "Alefaso ny tontonana fampahafantarana ao amin'ny lohateniny efitrano",
+        "render_reaction_images": "Mandefa sary mahazatra amin'ny fanehoan-kevitra",
+        "render_reaction_images_description": "Indraindray antsoina hoe emojis mahazatra.",
+        "report_to_moderators": "Tatitra aminy mpandrindra",
+        "report_to_moderators_description": "Ao amin'ny efitrano izay manohana ny fampitoniana, ny bokotra \"Report\" dia hamela anao hitatitra ny fanararaotana amin'ireo mpandrindra ny efitrano.",
+        "sliding_sync": "Fomba atao",
+        "sliding_sync_description": "Eo ambany fampandrosoana mavitrika, tsy azo kilemaina.",
+        "sliding_sync_disabled_notice": "Miala aloa de miverena mba hahafahana mampiato",
+        "sliding_sync_server_no_support": "Tsy manana fanohanana avy amin'ny teratany ny mpizaranao",
+        "under_active_development": "Eo ambanin'ny fampandrosoana mavitrika.",
+        "unrealiable_e2e": "Tsy azo ianteherana amin'ny efitrano misy miafina",
+        "video_rooms": "Efi-trano fandraisan-tsary",
+        "video_rooms_a_new_way_to_chat": "Fomba vaovao hiresahana amin'ny feo sy sary mietsika%(brand)s .",
+        "video_rooms_always_on_voip_channels": "Ny efitranon'ny horonan-tsary dia fantsona VoIP ao anaty efitrano iray%(brand)s .",
+        "video_rooms_beta": "Ny efitrano fandraisam-bahiny dia endrika beta",
+        "video_rooms_faq1_answer": "Ampiasao ny bokotra &quot;+&quot; ao amin'ny fizarana efitrano amin'ny tontonana havia.",
+        "video_rooms_faq1_question": "Ahoana no ahafahako mamorona efitrano sari-mietsika?",
+        "video_rooms_faq2_answer": "Eny, ny fandaharam-potoana aminy chat dia aseho miaraka amin'ny sary mietsika.",
+        "video_rooms_faq2_question": "Afaka mampiasa chat an-tsoratra miaraka amin'ny antso an-tsary mietsika ve aho?",
+        "video_rooms_feedbackSubheading": "Misaotra anao nanandrana ny beta, azafady midira amin'ny antsipiriany araka izay azonao atao mba hahafahanay manatsara azy.",
+        "wysiwyg_composer": "Mpanonta lahatsoratra manankarena"
+    },
+    "labs_mjolnir": {
+        "advanced_warning": "⚠ Natao hoany mpampiasa efa mandroso ireo fanovana ireo.",
+        "ban_reason": "Tsy asiana topi-maso/ho sakanana",
+        "error_adding_ignore": "Error nandritrany fanatsofoana iray aminy mpampiasa/server tsy noraharahiana",
+        "error_adding_list_description": "Hamarino azafady ny ID na ny adiresy efitrano ary andramo indray.",
+        "error_adding_list_title": "Hadisoana amin'ny famandrihana lisitra",
+        "error_removing_ignore": "Error nandritrany fanala mpampiasa/server tsy noraharahiana",
+        "error_removing_list_description": "Andramo indray azafady na jereo ny console anao mba hahazoana torohevitra.",
+        "error_removing_list_title": "Hadisoana tamin'ny fanesorana ny lisitra",
+        "explainer_1": "Manampia mpampiasa sy mpizara tianao ho tsinontsinoavina eto. Mampiasà asterisk mba hananana%(brand)s mifanaraka amin'ny tarehintsoratra rehetra. Ohatra,<code> @bot:*</code> tsy hiraharaha ireo mpampiasa rehetra manana anarana bot amin'ny mpizara rehetra.",
+        "explainer_2": "Ny tsy firaharahiana ny olona dia atao aminy alàlan'ny lisitra fandraràna izay misy fitsipika momba ny hoe iza no handrara. Ny famandrihana lisitra fandraràna dia midika fa hafenina aminao ireo mpampiasa/server voasakana amin'io lisitra io.",
+        "lists": "Amin'izao fotoana izao ianao dia manaraka an'ny :",
+        "lists_description_1": "Ny fisoratana anarana amin'ny lisitry ny fandraràna dia hahatonga anao hiditra ao!",
+        "lists_description_2": "Raha tsy izany no tadiavinao dia ampiasao fitaovana hafa mba tsy hiraharaha ny mpampiasa.",
+        "lists_heading": "Lisitra nisoratra anarana",
+        "lists_new_label": "ID efitrano na adiresin'ny lisitry ny fandrarana",
+        "no_lists": "Tsy misoratra anarana amin'ny lisitra rehetra ianao",
+        "personal_description": "Ny lisitry ny fandraràna anao manokana dia mitazona ireo mpampiasa / servers rehetra izay tsy tianao manokana ny hahita hafatra. Rehefa avy tsy miraharaha ny mpampiasa / mpizara voalohany, efitrano vaovao dia hiseho ao amin'ny lisitry ny efitranonao antsoina hoe %(maListeBan) s mijanona ao amin'ity efitrano ity mba hitazonana ny lisitry ny fandraràna hanan-kery.",
+        "personal_empty": "Tsy niraharaha na iza na iza ianao.",
+        "personal_heading": "Lisitra fandrarana manokana",
+        "personal_new_label": "Serveur na mpampiasa ID tsy tokony hiraharaha",
+        "personal_new_placeholder": "Ohatra: @bot:* na ohatra.org",
+        "personal_section": "Tsy miraharaha ianao izao:",
+        "room_name": "Ny lisitry ny fandraràna ahy",
+        "room_topic": "Ity ny lisitry ny mpampiasa/server nosakananao - aza mivoaka ny efitrano!",
+        "rules_empty": "Tsy misy",
+        "rules_server": "Fitsipika mpizara",
+        "rules_title": "Fitsipika fandrarana lisitra -%(roomName)s",
+        "rules_user": "Fitsipika mpampiasa",
+        "something_went_wrong": "Nisy zavatra tsy nety. Andramo indray azafady na jereo ny console anao mba hahazoana torohevitra.",
+        "title": "Ireo mpampiasa tsy noraharahiana",
+        "view_rules": "Aneo ny fitsipika"
+    },
+    "language_dropdown_label": "Teny Dropdown",
+    "leave_room_dialog": {
+        "last_person_warning": "Ianao irery no eto. Raha miala ianao dia tsy hisy ho afaka hiditra amin'ny ho avy, anisan'izany ianao.",
+        "leave_room_question": "Tena te hiala ny efitrano ve ianao%(roomName)s ?",
+        "leave_space_question": "Tena te hiala amin'ilay toerana ve ianao%(spaceName)s ?",
+        "room_rejoin_warning": "Tsy ampahibemaso ity efitrano ity. Tsy afaka miditra indray ianao raha tsy misy fanasana.",
+        "space_rejoin_warning": "Tsy ampahibemaso ity habaka ity. Tsy afaka miditra indray ianao raha tsy misy fanasana."
+    },
+    "left_panel": {
+        "open_dial_pad": "Sokafy ny dial pad"
+    },
+    "lightbox": {
+        "rotate_left": "Ahodina ankavia",
+        "rotate_right": "Ahodina miankavanana",
+        "title": "Fijerena sary"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "Ity homeserver ity dia tsy namboarina hanehoana sarintany.",
+        "MapStyleUrlNotReachable": "Tsy voarafitra tsara mba hanehoana sari-tany ity mpizara trano ity, na mety tsy ho tratra ny mpizara sari-tany voarindra.",
+        "WebGLNotEnabled": "Ny WebGL dia takiana hanehoana sari-tany, azafady ampidiro ao amin'ny firafitry ny toeram-pivohizana.",
+        "click_drop_pin": "Kitio raha hametraka fiantonana",
+        "click_move_pin": "Tsindrio raha hamindra ny antona",
+        "close_sidebar": "Akatona ny sisiny",
+        "error_fetch_location": "Tsy afaka naka toerana",
+        "error_no_perms_description": "Mila manana alalana tsara ianao raha te hizara toerana ao amin'ity efitrano ity.",
+        "error_no_perms_title": "Tsy manana alalana hizara toerana ianao",
+        "error_send_description": "%(brand)stsy afaka nandefa ny toerana misy anao. Andramo indray rahatrizay.",
+        "error_send_title": "Tsy afaka nandefa ny toerana misy anao izahay",
+        "error_sharing_live_location": "Nisy hadisoana nitranga teo am-pizarana ny toerana misy anao",
+        "error_stopping_live_location": "Nisy hadisoana nitranga teo am-pijanonana ny toerana misy anao",
+        "expand_map": "Hanitatra ny sari-tany",
+        "failed_generic": "Tsy nahavita naka ny toerana misy anao. Andramo indray rahatrizay.",
+        "failed_load_map": "Tsy afaka mampiditra sari-tany",
+        "failed_permission": "%(brand)stsy nahazo alalana haka ny toerana misy anao. Azafady avelao ny fidirana amin'ny toerana ao amin'ny firafitry ny toeram-pivohizana.",
+        "failed_timeout": "Tapitra ny fotoana nanandrana naka ny toerana misy anao. Andramo indray rahatrizay.",
+        "failed_unknown": "Misy hadisoana tsy fantatra amin'ny fakana toerana. Andramo indray rahatrizay.",
+        "find_my_location": "Tadiavo ny toerana misy ahy",
+        "live_description": "%(displayName)stoerana mivantana",
+        "live_enable_description": "Mariho azafady: ity dia endri-javatra laboratoara mampiasa fampiharana vonjimaika. Midika izany fa tsy ho afaka hamafa ny tantaran'ny toerana misy anao ianao, ary ireo mpampiasa efa mandroso dia afaka mahita ny tantarany toerana misy anao na dia efa nijanona aza ny fizarana ny toerana misy anao amin'ity efitrano ity.",
+        "live_enable_heading": "Fizarana toerana mivantana",
+        "live_location_active": "Mizara ny toerana misy anao ianao",
+        "live_location_enabled": "Manomboka mivantana",
+        "live_location_ended": "Nifarana ny toerana mivantana",
+        "live_location_error": "Hadisoana toerana mivantana",
+        "live_locations_empty": "Tsy misy toerana mivantana",
+        "live_share_button": "Zarao hoany%(duration)s",
+        "live_toggle_label": "Alefaso ny fizarana toerana mivantana",
+        "live_until": "Miaina hatramin'ny % (expirationTime) s",
+        "live_update_time": "Anavao%(humanizedUpdateTime)s",
+        "loading_live_location": "Mametraka toerana mivantana…",
+        "location_not_available": "Tsy misy toerana",
+        "map_feedback": "Tamberin'ny sarintany",
+        "mapbox_logo": "Fangon'ny Mapbox",
+        "reset_bearing": "Avereno mianavaratra ny fitondra",
+        "share_button": "Mizara toerana",
+        "share_type_live": "Ny toerana misy ahy mivantana",
+        "share_type_own": "Ny toerana misy ahy ankehitriny",
+        "share_type_pin": "Hametraka fiantonana",
+        "share_type_prompt": "Karazana toerana inona no tianao hozaraina?",
+        "toggle_attribution": "Ekena/Anaka"
+    },
+    "member_list": {
+        "filter_placeholder": "Sivanina ny mpikambana ao amin'ny efitrano",
+        "invite_button_no_perms_tooltip": "Tsy manana alalana hanasana mpampiasa ianao",
+        "power_label": "%(userName)s(hery%(powerLevelNumber)s )"
+    },
+    "member_list_back_action_label": "Mpikambana ao amin'ny efitrano",
+    "message_edit_dialog_title": "Fanovana hafatra",
+    "migrating_crypto": "Mihantona mafy. Manavao ny Element izahay mba hahatonga ny fanafenana haingana kokoa sy azo antoka kokoa.",
+    "mobile_guide": {
+        "toast_accept": "Mampiasà fampiarana",
+        "toast_description": "%(brand)sandrana amin'ny mpitety tranonkala finday. Raha te hanana traikefa tsara kokoa sy ny endri-javatra farany indrindra, ampiasao ny fampiharana teratany maimaim-poana.",
+        "toast_title": "Ampiasao ny fampiharana ho an'ny traikefa tsara kokoa"
+    },
+    "name_and_id": "%(name)s(%(userId)s )",
+    "no_more_results": "Tsy misy valiny intsony",
+    "notif_panel": {
+        "empty_description": "Tsy manana fampandrenesana hita maso ianao.",
+        "empty_heading": "Efa tratra daholo ianareo"
+    },
+    "notifications": {
+        "all_messages": "Hafatra rehetra",
+        "all_messages_description": "Mahazoa fampahafantarana isaky ny hafatra",
+        "class_global": "Fifandraisam-pirenena",
+        "class_other": "Hafa",
+        "default": "Toerana misy anao",
+        "email_pusher_app_display_name": "Fampandrenesana avy aminy imailaka",
+        "enable_prompt_toast_description": "Alefaso ny fampandrenesana avy aminy birao",
+        "enable_prompt_toast_title": "Fampahatsiahivana",
+        "enable_prompt_toast_title_from_message_send": "Aza adino ny valiny",
+        "error_change_title": "Hanova ny firafitry ny fampahafantarana",
+        "keyword": "Teny fanalahidy",
+        "keyword_new": "Teny fototra vaovao",
+        "level_activity": "Asa atao",
+        "level_highlight": "Tsipihana",
+        "level_muted": "Nangina",
+        "level_none": "Tsy misy",
+        "level_notification": "Fampahafantarana",
+        "level_unsent": "Tsy lasa",
+        "mark_all_read": "Mariho ho voavaky ny rehetra",
+        "mentions_and_keywords": "@fanononana sy teny fanalahidy",
+        "mentions_and_keywords_description": "Mahazoa fampandrenesana afa-tsy amin'ny teny sy teny fanalahidy araka ny napetraka ao amin'ny anao<a> Fikirana</a>",
+        "mentions_keywords": "Fanononana sy teny fototra",
+        "message_didnt_send": "Tsy lasa ny hafatra. Tsindrio raha mila fanazavana.",
+        "mute_description": "Tsy hahazo fampandrenesana ianao"
+    },
+    "notifier": {
+        "m.key.verification.request": "%(name)sdia mangataka fanamarinana"
+    },
+    "onboarding": {
+        "create_room": "Mamorona chat vondrona",
+        "explore_rooms": "Tsidiho ny efitranom-bahoaka",
+        "has_avatar_label": "Tsara, hanampy ny olona hahafantatra fa ianao io",
+        "intro_byline": "Tompon'ny resakao.",
+        "intro_welcome": "Tongasoa eto%(appName)s",
+        "no_avatar_label": "Manampia sary mba hahafantaran'ny olona fa ianao io.",
+        "send_dm": "Mandefasa hafatra mivantana",
+        "welcome_detail": "Ankehitriny, andao hanampy anao hanomboka",
+        "welcome_user": "Fandraisana%(des noms"
+    },
+    "pill": {
+        "permalink_other_room": "Hafatra ao%(room)s",
+        "permalink_this_room": "Hafatra avy amin'ny%(user)s"
+    },
+    "poll": {
+        "create_poll_action": "Mamorona fitsapan-kevitra",
+        "create_poll_title": "Mamorona fitsapan-kevitra",
+        "disclosed_notes": "Mahita vokatra ny mpifidy raha vao nifidy",
+        "edit_poll_title": "Ahitsio fitsapan-kevitra",
+        "end_description": "Tena te hamarana ity fitsapan-kevitra ity ve ianao? Izany dia hampiseho ny vokatra farany amin'ny fitsapan-kevitra ary hanakana ny olona tsy hifidy.",
+        "end_message": "Tapitra ny fitsapan-kevitra. Valiny ambony:%(topAnswer)s",
+        "end_message_no_votes": "Tapitra ny fitsapan-kevitra. Tsy nisy vato azo.",
+        "end_title": "Farano ny fitsapan-kevitra",
+        "error_ending_description": "Miala tsiny fa tsy nifarana ny fitsapan-kevitra. Andramo indray azafady.",
+        "error_ending_title": "Tsy nahavita namarana ny fitsapan-kevitra",
+        "error_voting_description": "Miala tsiny fa tsy voasoratra ny vatonao. Andramo indray azafady.",
+        "error_voting_title": "Tsy nisoratra anarana ny vato",
+        "failed_send_poll_description": "Miala tsiny fa tsy navoaka ny fitsapan-kevitra nokasainao noforonina.",
+        "failed_send_poll_title": "Tsy nahavita namoaka fitsapan-kevitra",
+        "notes": "Rehefa tapitra ny fitsapan-kevitra ianao, vao miseho ny valiny",
+        "options_add_button": "Ampio safidy",
+        "options_heading": "Mamorona safidy",
+        "options_label": "SAFIDY%(number)s",
+        "options_placeholder": "Manorata safidy",
+        "topic_heading": "Inona ny fanontaniana na lohahevitra fitsapan-kevitrao?",
+        "topic_label": "Fanontaniana na lohahevitra",
+        "topic_placeholder": "Manorata zavatra…",
+        "total_decryption_errors": "Noho ny fahadisoana decryption, ny vato sasany dia mety tsy voaisa",
+        "total_n_votes": {
+            "one": "%(count)slatsa-bato. Mifidiana mba hahitana ny valiny",
+            "other": "%(count)slatsa-bato. Mifidiana mba hahitana ny valiny"
+        },
+        "total_n_votes_voted": {
+            "one": "Miankina amin'ny%(count)s fifidianana",
+            "other": "Miankina amin'ny%(count)s vato"
+        },
+        "total_no_votes": "Tsy misy vato azo",
+        "total_not_ended": "Ho hita ny valiny rehefa tapitra ny fitsapan-kevitra",
+        "type_closed": "Nakatona ny fitsapan-kevitra",
+        "type_heading": "Karazana fitsapan-kevitra",
+        "type_open": "Sokafy ny fitsapan-kevitra",
+        "unable_edit_description": "Miala tsiny fa tsy afaka manova fitsapan-kevitra ianao rehefa vita ny latsa-bato.",
+        "unable_edit_title": "Tsy afaka manova fitsapan-kevitra"
+    },
+    "power_level": {
+        "admin": "Tomponandraikitra",
+        "custom": "Fanao (%(les niveaux)",
+        "custom_level": "Ambaratonga manokana",
+        "default": "Mahavoa",
+        "label": "Ny ahavony hery",
+        "moderator": "Mpandamina",
+        "restricted": "Misy fetra"
+    },
+    "presence": {
+        "away": "Lavitra",
+        "busy": "Be asa",
+        "idle": "Tsy an-tserasera",
+        "idle_for": "Tsy iseo mandritra%(durée)s",
+        "offline": "Ivelany serasera",
+        "offline_for": "Ivelany serasera mandritran'ny%(durée)s",
+        "online": "Miserasera",
+        "online_for": "An-tserasera hoan'i%(duration)s",
+        "unknown": "Tsy fantatra",
+        "unknown_for": "Tsy fantatra mandritra%(durée)s",
+        "unreachable": "Tsy azo tratrarina ny mpizara mpampiasa"
+    },
+    "quick_settings": {
+        "all_settings": "Fikirana rehetra",
+        "metaspace_section": "Ampiantona aminy bar lateraly",
+        "sidebar_settings": "Safidy bebe kokoa",
+        "title": "Fandrindrana haingana"
+    },
+    "quit_warning": {
+        "call_in_progress": "Toa miantso antso ianao, tena te hiala ve ianao?",
+        "file_upload_in_progress": "Toa mampakatra rakitra ianao, tena te hiala ve ianao?"
+    },
+    "redact": {
+        "confirm_button": "Hamafiso ny fanesorana",
+        "confirm_description": "Tena te hanala (hamafa) ity hetsika ity ve ianao?",
+        "confirm_description_state": "Mariho fa ny fanesorana ny fiovan'ny efitrano toy izao dia mety hanafoana ny fanovana.",
+        "error": "Tsy afaka mamafa ity hafatra ity ianao. (%(code)s )",
+        "ongoing": "Manala ...",
+        "reason_label": "Antony (tsy voatery)"
+    },
+    "report_content": {
+        "description": "Ny tatitra an'ity hafatra ity dia handefa ny event ID azy manokana any amin'ny mpitantana ny homeserver-nao. Raha misy encryption ny hafatra ao amin'ity efitrano ity dia tsy afaka mamaky ny lahatsoratra na mijery rakitra na sary ny mpitantana ny homeserver anao.",
+        "disagree": "Tsy miombon-kevitra",
+        "error_create_room_moderation_bot": "Tsy afaka mamorona toerana miaraka amin'ny bot fandrindrana",
+        "hide_messages_from_user": "Jereo raha te hanafina ny hafatra rehetra ankehitriny sy ho avy amin'ity mpampiasa ity ianao.",
+        "ignore_user": "Tsy iraharaha ny mpampiasa",
+        "illegal_content": "Votoaty tsy ara-dalàna",
+        "missing_reason": "Fenoy azafady ny antony hanaovanao tatitra.",
+        "nature": "Misafidiana toetra iray ary lazao ny antony mahatonga an'ity hafatra ity hanararaotana.",
+        "nature_disagreement": "Diso ny zavatra soratan'ity mpampiasa ity. Hampandrenesina amin’ireo mpandrindra ny efitrano izany.",
+        "nature_illegal": "Ity mpampiasa ity dia mampiseho fitondran-tena tsy ara-dalàna, ohatra amin'ny fametavetana olona na fandrahonana herisetra. Hatao amin’ireo mpandrindra efitrano izay mety hampiakatra izany any amin’ny tompon’andraikitra ara-dalàna izany.",
+        "nature_nonstandard_admin": "Ity efitrano ity dia natokana hoany votoaty tsy ara-dalàna na misy poizina na ny mpandrindra dia tsy afaka mamatsy votoaty tsy ara-dalàna na misy poizina. Hatao amin’ny mpitantana ny%(homeserver)s .",
+        "nature_nonstandard_admin_encrypted": "Ity efitrano ity dia natokana hoany votoaty tsy ara-dalàna na misy poizina na ny mpandrindra dia tsy afaka mamatsy votoaty tsy ara-dalàna na misy poizina. Hatao amin’ny mpitantana ny%(homeserver)s . Tsy ho azon'ny mpitantana mamaky ny votoatin'ity efitrano ity.",
+        "nature_other": "Ny antony hafa. Farito ny olana azafady. Hampandrenesina amin’ireo mpandrindra ny efitrano izany.",
+        "nature_spam": "Ity mpampiasa ity dia mametaka ny efitrano miaraka aminy doka, rohy mankany amin'ny doka na propagandy. Hampandrenesina amin’ireo mpandrindra ny efitrano izany.",
+        "nature_toxic": "Maneho fihetsika misy poizina ity mpampiasa ity, ohatra amin'ny faniratsirana ny mpampiasa hafa na ny fizarana votoaty hoany olon-dehibe irery ao aminy efitranony fianakaviana na raha tsy izany dia manitsakitsaka ny fitsipika ao amin'ity efitrano ity. Hampandrenesina amin’ireo mpandrindra ny efitrano izany.",
+        "other_label": "Hafa",
+        "report_content_to_homeserver": "Mitatitra votoaty amin'ny Administrator Homeserver-nao",
+        "report_entire_room": "Tatitra ny efitrano manontolo",
+        "spam_or_propaganda": "Spam na propagandy",
+        "toxic_behaviour": "Fitondran-tena misy poizina"
+    },
+    "restore_key_backup_dialog": {
+        "count_of_decryption_failures": "Tsy nahomby ny decrypt%(failedCount)s sessions!",
+        "count_of_successfully_restored_keys": "Naverina tamin&#39;ny laoniny%(sessionCount)s lakilen'ilay",
+        "enter_key_description": "Midira amin'ny tantarany hafatrao azo antoka ary manangana hafatra azo antoka amin'ny alàlany fampidiranao ny Key Security.",
+        "enter_key_title": "Ampidiro ny fiarovana Key",
+        "enter_phrase_description": "Midira aminy tantaran'ny hafatrao azo antoka ary manangana hafatra azo antoka amin'ny alàlan'ny fampidirana ny fehezan-teny fiarovana anao.",
+        "enter_phrase_title": "Ampidiro ny Security Phrase",
+        "incorrect_security_phrase_dialog": "Ny vakorakitra dia tsy azo vakiana amin'ity fehezan-teny fiarovana ity: azafady, hamarino fa nampiditra ny fehezan-teny fiarovana marina ianao.",
+        "incorrect_security_phrase_title": "Andian-teny fiarovana diso",
+        "key_backup_warning": "<b>FAMPITANDREMANA</b> : tokony hametraka famandrihana fanalahidy avy amin'ny solosaina azo itokisana ihany ianao.",
+        "key_fetch_in_progress": "Maka fanalahidy amin'ny mpizara…",
+        "key_forgotten_text": "Raha adinonao ny Key Security anao dia azonao atao<button> manangana safidy fanarenana vaovao</button>",
+        "key_is_invalid": "Tsy manankery Security Key",
+        "key_is_valid": "Toa fanalahidy fiarovana manan-kery ity!",
+        "keys_restored_title": "Naverina tamin'ny laoniny ny fanalahidy",
+        "load_error_content": "Tsy afaka mampiditra sata backup",
+        "load_keys_progress": "%(completed)snY%(total)s naverina tamin'ny laoniny ny fanalahidy",
+        "no_backup_error": "Tsy misy vakorakitra hita!",
+        "phrase_forgotten_text": "Raha adinonao ny fehezan-teny fiarovana anao dia azonao atao<button1> ampiasao ny Security Key</button1> na<button2> manangana safidy fanarenana vaovao</button2>",
+        "recovery_key_mismatch_description": "Nomaniny ho solon'izay dia tsy azo crypted amin'ny fiarovana ity Key: azafady manamarina fa niditra ny marina Security Key.",
+        "recovery_key_mismatch_title": "Tsy mifanaraka amin'ny fiarovana ny Key",
+        "restore_failed_error": "Tsy afaka namerina ny vakorakitra"
+    },
+    "right_panel": {
+        "add_integrations": "Ampio widgets, tetezana sy bots",
+        "files_button": "Rakitra",
+        "pinned_messages": {
+            "limits": {
+                "one": "",
+                "other": "Azonao atao ihany ny manoratra%(count)s gadget"
+            }
+        },
+        "pinned_messages_button": "Mipaingotra",
+        "poll": {
+            "active_heading": "Fitsapan-kevitra mavitrika",
+            "empty_active": "Tsy misy fitsapan-kevitra mavitrika ao amin'ity efitrano ity",
+            "empty_active_load_more": "Tsy misy fitsapan-kevitra mavitrika. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa",
+            "empty_active_load_more_n_days": {
+                "one": "Tsy misy fitsapan-kevitra mavitrika nandritra ny andro lasa. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa",
+                "other": "Tsy misy fitsapan-kevitra mavitrika tamin'ny lasa%(count)s andro. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa"
+            },
+            "empty_past": "Tsy misy fitsapan-kevitra taloha tao amin'ity efitrano ity",
+            "empty_past_load_more": "Tsy misy fitsapan-kevitra taloha. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa",
+            "empty_past_load_more_n_days": {
+                "one": "Tsy misy fitsapan-kevitra lasa tamin'ny andro lasa. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa",
+                "other": "Tsy misy fitsapan-kevitra taloha ho an'ny lasa%(count)s andro. Hampiditra fitsapan-kevitra bebe kokoa hijerena fitsapan-kevitra tamin'ny volana lasa"
+            },
+            "final_result": {
+                "one": "Vokatra farany mifototra amin'ny%(count)s fifidianana",
+                "other": "Vokatra farany mifototra amin'ny%(count)s vato"
+            },
+            "load_more": "Hampiditra fitsapan-kevitra bebe kokoa",
+            "loading": "Mametraka fitsapan-kevitra",
+            "past_heading": "Fitsapan-kevitra taloha",
+            "view_in_timeline": "Jereo ny fitsapan-kevitra ao amin'ny fandaharam-potoana",
+            "view_poll": "Jereo ny fitsapan-kevitra"
+        },
+        "polls_button": "Tantaran'ny",
+        "room_summary_card": {
+            "title": "Momba ny efitrano"
+        },
+        "thread_list": {
+            "context_menu_label": "Safidy kofehy"
+        },
+        "video_room_chat": {
+            "title": "Hiresaka"
+        }
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "Nalefa tany aminy%(e-mail)s izay tsy mifandray amin'ny kaontinao",
+        "3pid_invite_email_not_found_account_room": "Ity fanasana ity dia% (nom de la pièce) s nalefa tany % (e-mail) amin'ny id izay tsy mifandray amin'ny kaontinao",
+        "3pid_invite_error_description": "Fahadisoana (%(errcode)s ) dia naverina teo am-piezahana hanamarina ny fanasanao. Azonao atao ny manandrana mampita an'io fampahalalana io amin'ny olona nanasa anao.",
+        "3pid_invite_error_invite_action": "Andramo ihany koa ny miditra",
+        "3pid_invite_error_invite_subtitle": "Afaka manatevin-daharana azy amin'ny fanasana miasa fotsiny ianao.",
+        "3pid_invite_error_public_subtitle": "Mbola afaka miditra eto ianao.",
+        "3pid_invite_error_title": "Nisy tsy nety tamin'ny fanasanao.",
+        "3pid_invite_error_title_room": "Nisy tsy nety tamin'ny fanasanao%(roomName)s",
+        "3pid_invite_no_is_subtitle": "Mampiasà mpizara famantarana ao aminy fanitsiana handraisana fanasana mivantana%(brand)s .",
+        "banned_by": "Nosakanan'ny %( nommembre ) s",
+        "banned_from_room_by": "Voarara ianao%(roomName)s ny%(memberName)s",
+        "context_menu": {
+            "copy_link": "Adikao rohy efitra",
+            "favourite": "Toerana tena",
+            "forget": "Adinoy ny efitrano",
+            "low_priority": "Laharam-pahamehana ambany",
+            "mark_read": "Mariho ho voavaky",
+            "notifications_default": "Mampifanaraka ny toerana misy anao",
+            "notifications_mute": "Efitra mangina",
+            "title": "Safidy efitrano",
+            "unfavourite": "Ankafizina"
+        },
+        "creating_room_text": "Mamorona efitrano miaraka amin'ny%(names)s",
+        "dm_invite_action": "Manomboka mifampiresaka",
+        "dm_invite_subtitle": "<userName/>te hiresaka",
+        "dm_invite_title": "Te hiresaka amin'ny%(user)s ?",
+        "drop_file_prompt": "Alefaso eto ny rakitra raha ampidirina",
+        "edit_topic": "Ahitsio lohahevitra",
+        "error_3pid_invite_email_lookup": "Tsy mahita mpampiasa amin'ny mailaka",
+        "error_cancel_knock_title": "Tsy nahavita nanafoana",
+        "error_join_403": "Mila fanasana ianao hidirana amin'ity efitrano ity.",
+        "error_join_404_1": "Nanandrana niditra taminy fampiasana ID efitrano ianao nefa tsy nanome lisitry ny lohamilina hidirana. Ny ID efitrano dia famantarana anatiny ary tsy azo ampiasaina hanatevin-daharana efitrano iray raha tsy misy fampahalalana fanampiny.",
+        "error_join_404_2": "Raha fantatrao ny adiresin'ny efitrano iray dia andramo miditra amin'izany kosa.",
+        "error_join_404_invite": "Efa niala ilay olona nanasa anao, na tsy an-tserasera ny mpizara azy.",
+        "error_join_404_invite_same_hs": "Efa lasa ilay nanasa anao.",
+        "error_join_connection": "Nisy lesoka tamin'ny fidirana.",
+        "error_join_incompatible_version_1": "Miala tsiny fa efa antitra loatra ny mpizara tranonao ka tsy afaka mandray anjara eto.",
+        "error_join_incompatible_version_2": "Mifandraisa amin'ny mpitantana ny homeserver anao azafady.",
+        "error_join_title": "Tsy afaka niditra",
+        "error_jump_to_date": "Niverina ny mpizara%(statusCode)s miaraka amin'ny kaody error%(errorCode)s",
+        "error_jump_to_date_connection": "Nisy hadisoana nitranga teo am-pikarohana sy nitsambikina tamin'ny daty nomena. Mety maty ny mpizara tranonao na nisy olana vonjimaika fotsiny taminy fifandraisana Internet anao. Andramo indray azafady. Raha mitohy izany dia mifandraisa amin'ny mpitantana ny homeserver anao.",
+        "error_jump_to_date_details": "Antsipirihan'ny hadisoana",
+        "error_jump_to_date_not_found": "Tsy nahita hetsika niandrandra izahay%(dateString)s. Andramo misafidy daty mialoha.",
+        "error_jump_to_date_send_logs_prompt": "Alefaso azafady<debugLogsLink> debug logs</debugLogsLink> mba hanampy antsika hanara-maso ny olana.",
+        "error_jump_to_date_title": "Tsy nahita hetsika tamin'io daty io",
+        "face_pile_summary": {
+            "one": "%(count)solona fantatrao dia efa tafiditra",
+            "other": "%(count)sefa nanatevin-daharana ny olona fantatrao"
+        },
+        "face_pile_tooltip_label": {
+            "one": "Hijery mpikambana 1",
+            "other": "Jereo daholo%(count)s Ireo mpikambana"
+        },
+        "face_pile_tooltip_shortcut": "anisan'izany ny%(commaSeparatedMembers)s",
+        "face_pile_tooltip_shortcut_joined": "anisan'izany ianao,%(commaSeparatedMembers)s",
+        "failed_reject_invite": "Tsy nanda ny fanasana",
+        "forget_room": "Adinoy ity efitrano ity",
+        "forget_space": "Adino ity toerana ity",
+        "header": {
+            "n_people_asking_to_join": {
+                "one": "Mangataka ny hiaraka",
+                "other": "%(count)solona mangataka ny hiditra"
+            },
+            "room_is_public": "Ampahibemaso ity efitrano ity"
+        },
+        "header_face_pile_tooltip": "Safidio ny lisitry ny mpikambana",
+        "header_untrusted_label": "Tsy atokisana",
+        "inaccessible": "Tsy azo idirana ity efitrano na toerana ity amin'izao fotoana izao.",
+        "inaccessible_name": "%(roomName)stsy azo idirana amin'izao fotoana izao.",
+        "inaccessible_subtitle_1": "Andramo indray rehefa afaka kelikely, na mangataha mpitantana efitrano na habaka hanamarina raha afaka miditra ianao.",
+        "inaccessible_subtitle_2": "%(errcode)snaverina teo am-piezahana hiditra ny efitrano na toerana. Raha heverinao fa diso ity hafatra ity, azafady<issueLink> mandefa tatitra momba ny bibikely</issueLink> .",
+        "intro": {
+            "dm_caption": "Ianareo roa ihany no ao anatin'ity resaka ity, raha tsy hoe misy manasa olona hiditra.",
+            "enable_encryption_prompt": "Alefaso ny encryption amin'ny firafitry.",
+            "encrypted_3pid_dm_pending_join": "Rehefa tafiditra daholo ny rehetra dia afaka mifampiresaka ianao",
+            "no_avatar_label": "Manampia sary, mba ho hitan'ny olona mora foana ny efitranonao.",
+            "no_topic": "<a>Manampia lohahevitra</a> mba hanampiana ny olona hahafantatra ny momba izany.",
+            "private_unencrypted_warning": "Ny hafatrao manokana dia matetika voarakotra, fa ity efitrano ity dia tsy. Matetika izany dia noho ny fitaovana na fomba tsy tohanana ampiasaina, toy ny fanasana mailaka.",
+            "room_invite": "Asao ho amin'ity efitrano ity ihany",
+            "send_message_start_dm": "Alefaso ny hafatrao voalohany hanasana<displayName/> hifampiresaka",
+            "start_of_dm_history": "Ity no fiandohan'ny tantarany hafatra mivantana nataonao<displayName/> .",
+            "start_of_room": "Izany no fiandohan'ny<roomName/> .",
+            "topic": "Lohahevitra:%(les sujets",
+            "topic_edit": "Lohahevitra:%(topic)s (<a> Ovay</a> )",
+            "unencrypted_warning": "Tsy azo atao ny mandefa ny kaody faran'ny farany",
+            "user_created": "%(displayName)snamorona ity efitrano ity.",
+            "you_created": "Ianao no namorona ity efitrano ity."
+        },
+        "invite_email_mismatch_suggestion": "Zarao amin'ny fanitsiana ity mailaka ity mba handraisana fanasana mivantana%(brand)s.",
+        "invite_sent_to_email": "Nalefa tany amin'ny%(email)s",
+        "invite_sent_to_email_room": "Invitation to%(roomName)s nalefa tany%(email)s",
+        "invite_subtitle": "<userName/>nanasa anao",
+        "invite_this_room": "Asao ho amin'ity efitrano ity",
+        "invite_title": "Te-hiditra ve ianao%(roomName)s ?",
+        "inviter_unknown": "TSY FANTATRA",
+        "invites_you_text": "<inviter/>manasa anao",
+        "join_button_account": "Hiditra Mpikambana",
+        "join_failed_needs_invite": "Hijery%(roomName)s , mila fanasana ianao",
+        "join_the_discussion": "Miaraha aminy fifanakalozan-kevitra",
+        "join_title": "Midira ao amin'ny efitrano mba handray anjara",
+        "join_title_account": "Midira aminy resaka miaraka amin'ny kaonty",
+        "joining": "Miditra…",
+        "joining_room": "Efitrano mitambatra…",
+        "joining_space": "Mikambana amin'ny habaka…",
+        "jump_read_marker": "Hanketo amin'ny hafatra tsy novakiana voalohany.",
+        "jump_to_bottom_button": "Mandehana mankany amin'ny hafatra farany indrindra",
+        "jump_to_date": "Hitsambikina hatramin'izao",
+        "jump_to_date_beginning": "Ny fiandohan'ny efitrano",
+        "jump_to_date_prompt": "Mifidiana daty hitsambikinana",
+        "kick_reason": "Antony:%(reason)s",
+        "kicked_by": "Nesorin'ny%(memberName)s",
+        "kicked_from_room_by": "Nesorina tamin'ny%(roomName)s ny%(memberName)s",
+        "knock_cancel_action": "Hanafoana ny fangatahana",
+        "knock_denied_subtitle": "Satria nolavina ny fidirana dia tsy afaka miditra indray ianao raha tsy asaina ataon'ny admin na mpandrindra ny vondrona.",
+        "knock_denied_title": "Nolavina ianao",
+        "knock_message_field_placeholder": "Hafatra (tsy voatery)",
+        "knock_prompt": "Angataka ny hiditra?",
+        "knock_prompt_name": "Angataho ny hiaraka%(roomName)s ?",
+        "knock_send_action": "Mangataka fidirana",
+        "knock_sent": "Nalefa ny fangatahana hiditra",
+        "knock_sent_subtitle": "Miandry ny fangatahanao hiditra.",
+        "knock_subtitle": "Mila omena fahafahana miditra amin'ity efitrano ity ianao raha te hijery na handray anjara amin'ny resaka. Azonao atao ny mandefa fangatahana hiditra eto ambany.",
+        "leave_error_title": "Hadisoana nivoaka ny efitrano",
+        "leave_server_notices_description": "Ity efitrano ity dia ampiasaina aminy hafatra manan-danja avy amin'ny Homeserver, ka tsy afaka miala amin'izany ianao.",
+        "leave_server_notices_title": "Tsy afaka mivoaka ny efitranon'ny Server Notice",
+        "leave_unexpected_error": "Fahadisoan'ny mpizara tsy nampoizina niezaka niala tao amin'ny efitrano",
+        "link_email_to_receive_3pid_invite": "Ampifandraiso aminy kaontinao ao amin'ny fanitsiana ity mailaka ity mba handraisana fanasana mivantana%(brand)s.",
+        "loading_preview": "Topi-maso aminy firosoana",
+        "no_peek_join_prompt": "%(roomName)stsy azo jerena. Te-hiditra amin'izany ve ianao?",
+        "no_peek_no_name_join_prompt": "Tsy misy vinavina, te-hiditra ve ianao?",
+        "not_found_subtitle": "Tena eo amin'ny toerana mety ve ianao?",
+        "not_found_title": "Tsy misy io efitrano na toerana malalaka io.",
+        "not_found_title_name": "%(roomName)stsy misy.",
+        "peek_join_prompt": "Mijery mialoha ianao%(roomName)s . Te-hiditra aminy?",
+        "read_topic": "Tsindrio raha hamaky lohahevitra",
+        "rejecting": "Mandà ny fanasana…",
+        "rejoin_button": "Amerina iditra",
+        "search": {
+            "all_rooms_button": "Karohy ny efitrano rehetra",
+            "this_room_button": "Karohy ity efitrano ity"
+        },
+        "status_bar": {
+            "delete_all": "Fafao daholo",
+            "exceeded_resource_limit": "Tsy nalefa ny hafatrao satria nihoatra ny fetran'ny loharanon-karena ity mpizara trano ity. Mba miangavy re<a> mifandraisa aminy mpitantana ny serivisy</a> hanohizana ny fampiasana ny serivisy.",
+            "homeserver_blocked": "Tsy nalefa ny hafatrao satria nosakanany mpitantana azy io server io. Mba miangavy re<a> mifandraisa amin'ny mpitantana ny serivisy</a> hanohizana ny fampiasana ny serivisy.",
+            "monthly_user_limit_reached": "Tsy nalefa ny hafatrao satria nahatratra ny fetran'ny mpampiasa mavitrika isam-bolana ity server ity. Mba miangavy re<a> mifandraisa amin'ny mpitantana ny serivisy</a> hanohizana ny fampiasana ny serivisy.",
+            "requires_consent_agreement": "Tsy afaka mandefa hafatra ianao raha tsy mandinika sy manaiky<consentLink> fepetra sy fepetra</consentLink> .",
+            "retry_all": "Andramo daholo",
+            "select_messages_to_retry": "Azonao atao ny misafidy ny hafatra rehetra na tsirairay mba hanandrana indray na hofafana",
+            "server_connectivity_lost_description": "Ny hafatra nalefa dia hotehirizina mandra-piverin'ny fifandraisanao.",
+            "server_connectivity_lost_title": "Very ny fifandraisana amin'ny mpizara.",
+            "some_messages_not_sent": "Ny sasany amin'ireo hafatrao dia mbola tsy nalefa"
+        },
+        "unknown_status_code_for_timeline_jump": "kaody sata tsy fantatra",
+        "unread_notifications_predecessor": {
+            "one": "Ianao dia manana%(count)s fampandrenesana tsy novakiana amin'ny dikan-teny teo aloha amin'ity efitrano ity.",
+            "other": "Ianao dia manana%(count)s fampandrenesana tsy novakiana amin&#39;ny dikan-teny teo aloha amin&#39;ity efitrano ity."
+        },
+        "upgrade_error_description": "Hamarino indroa fa manohana ny kinova efitrano nofidina ny mpizaranao ary andramo indray.",
+        "upgrade_error_title": "Hadisoana ny fanavaozana ny efitrano",
+        "upgrade_warning_bar": "Ny fanavaozana ity efitrano ity dia hanakatona ny efitrano misy ankehitriny ary hamorona efitrano nohavaozina miaraka amin'ny anarana mitovy.",
+        "upgrade_warning_bar_admins": "Ny mpitantana ny efitrano ihany no hahita ity fampitandremana ity",
+        "upgrade_warning_bar_unstable": "Ity efitrano ity dia dikan-trano mandeha<roomVersion /> , izay namarihan'ity homeserver ity ho<i> miovaova</i> .",
+        "upgrade_warning_bar_upgraded": "Efa nohavaozina ity efitrano ity.",
+        "upload": {
+            "uploading_multiple_file": {
+                "one": "Hampiditra%(filename)s sy%(count)s hafa",
+                "other": "Hampiditra%(filename)s sy%(count)s ny hafa"
+            },
+            "uploading_single_file": "Hampiditra%(filename)s"
+        },
+        "waiting_for_join_subtitle": "Rehefa niditra ny mpampiasa nasaina%(brand)s , dia ho afaka hifampiresaka ianao ary ny efitrano dia ho encryption amin'ny farany",
+        "waiting_for_join_title": "Miandry ny mpampiasa hiditra%(brand)s"
+    },
+    "room_list": {
+        "add_room_label": "Hanampy efitrano",
+        "add_space_label": "Ampio toerana",
+        "breadcrumbs_empty": "Tsy misy efitrano notsidihina vao haingana",
+        "breadcrumbs_label": "Efitrano notsidihina vao haingana",
+        "failed_add_tag": "Tsy nety nanampy tag%(tagName)s mankany amin'ny efitrano",
+        "failed_remove_tag": "Tsy nesorina ny marika%(tagName)s avy amin'ny efitrano",
+        "failed_set_dm_tag": "Tsy nahavita nametraka tenifototra hafatra mivantana",
+        "home_menu_label": "Safidy an-trano",
+        "join_public_room_label": "Midira amin'ny efitranom-bahoaka",
+        "joining_rooms_status": {
+            "one": "Miditra amin'izao fotoana izao%(count)s efitra",
+            "other": "Miditra amin'izao fotoana izao%(count)s ROOM"
+        },
+        "notification_options": "Safidy fampahafantarana",
+        "redacting_messages_status": {
+            "one": "Esory ny hafatra ao%(count)s efitra",
+            "other": "Esory ny hafatra ao%(count)s efitra"
+        },
+        "show_less": "Asehoy kely",
+        "show_n_more": {
+            "one": "Fampiseona%(nombre) s Bebekokoa",
+            "other": "Fampiseona%(nombre)s Bebekokoa"
+        },
+        "show_previews": "Asehoy mialoha ny hafatra",
+        "sort_by": "Sivanina araka",
+        "sort_by_activity": "Asa atao",
+        "sort_by_alphabet": "A-Z",
+        "sort_unread_first": "Asehoy aloha ny efitrano misy hafatra tsy novakiana",
+        "space_menu_label": "%(spaceName)ssakafo",
+        "sublist_options": "Lisitra safidy",
+        "suggested_rooms_heading": "Efitrano Soso-kevitra"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "Manapaha hevitra hoe iza no afaka mijery sy miditra%(spaceName)s .",
+            "title": "Fizorana"
+        },
+        "advanced": {
+            "error_upgrade_description": "Tsy vita ny fanavaozana ny efitrano",
+            "error_upgrade_title": "Tsy nahomby ny fanavaozana ny efitrano",
+            "information_section_room": "Fampahafantarana ny efitrano",
+            "information_section_space": "Fampahalalana momba ny habakabaka",
+            "room_id": "ID efitrano anatiny",
+            "room_predecessor": "Jereo ny hafatra tranainy ao%(roomName)s .",
+            "room_upgrade_button": "Havaozy ity efitrano ity amin'ny dikan-trano atolotra",
+            "room_upgrade_warning": "<b>FAMPITANDREMANA</b> : fanatsarana efitrano<i> tsy mifindra ho azy ny mpikambana ao amin'ny efitrano mankany amin'ny dikan-teny vaovaon'ny efitrano.</i> Hametraka rohy mankany amin'ny efitrano vaovao ao amin'ny kinova tranainy amin'ny efitrano izahay - ny mpikambana ao amin'ny efitrano dia tsy maintsy manindry ity rohy ity raha te hiditra amin'ny efitrano vaovao.",
+            "room_version": "Dikan'ny efitrano:",
+            "room_version_section": "Dikan-trano vaovao",
+            "space_predecessor": "Jereo ny dikan-teny taloha%(spaceName)s .",
+            "space_upgrade_button": "Havaozy ity habaka ity ho amin'ny dikan-trano atolotra",
+            "unfederated": "Ity efitrano ity dia tsy azon'ny mpizara Matrix lavitra",
+            "upgrade_button": "Havaozy ho kinova ity efitrano ity%(version)s",
+            "upgrade_dialog_description": "Ny fanavaozana ity efitrano ity dia mitaky fanakatonana ny efitrano misy ankehitriny ary mamorona efitrano vaovao eo amin'ny toerany. Mba hanomezana traikefa tsara indrindra hoany ny mpikambana ao amin'ny efitrano, dia:",
+            "upgrade_dialog_description_1": "Mamorona efitrano vaovao mitovy anarana, famaritana ary avatar",
+            "upgrade_dialog_description_2": "Havaozy izay solon'anarana efitrano eo an-toerana mba hanondroana ny efitrano vaovao",
+            "upgrade_dialog_description_3": "Atsaharo ny mpampiasa tsy hiteny amin'ny dikan-teny taloha amin'ny efitrano, ary mandefa hafatra manoro hevitra ny mpampiasa hifindra any amin'ny efitrano vaovao",
+            "upgrade_dialog_description_4": "Avereno rohy mankany amin'ny efitrano taloha eo am-piandohany efitrano vaovao mba hahitan'ny olona ireo hafatra taloha",
+            "upgrade_dialog_title": "Fanavaozana ny Efitrano Version",
+            "upgrade_dwarning_ialog_title_public": "Hatsarao ny efitranom-bahoaka",
+            "upgrade_warning_dialog_description": "Fanatsarana efitrano dia hetsika mandroso ary matetika no asaina rehefa tsy milamina ny efitrano iray noho ny bibikely, ny endri-javatra tsy hita na ny vulnerability amin'ny fiarovana.",
+            "upgrade_warning_dialog_explainer": "<b>Azafady, mariho fa ny fanavaozana dia hanana dikan-teny vaovao amin'ny efitrano</b> . Ny hafatra rehetra amin'izao fotoana izao dia hijanona ao amin'ity efitrano voatahiry ity.",
+            "upgrade_warning_dialog_footer": "Havaozinao ity efitrano ity avy amin'ny<oldVersion /> ny<newVersion /> .",
+            "upgrade_warning_dialog_invite_label": "Asao ho azy ny mpikambana avy amin'ity efitrano ity ho any amin'ilay vaovao",
+            "upgrade_warning_dialog_report_bug_prompt": "Matetika izany dia misy fiantraikany aminy fomba fikarakarana ny efitrano ao aminy server. Raha manana olana amin'ny tenanao ianao%(brand)s , mitatitra otrikaretina azafady.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "Matetika izany dia misy fiantraikany aminy fomba fikarakarana ny efitrano ao aminy server. Raha manana olana amin'ny tenanao ianao%(brand)s , Mba miangavy re<a> mitatitra bibikely</a> .",
+            "upgrade_warning_dialog_title": "Fanatsarana efitrano",
+            "upgrade_warning_dialog_title_private": "Hanavao ny efitrano manokana"
+        },
+        "alias_not_specified": "tsy voasoritra",
+        "bridges": {
+            "description": "Ity efitrano ity dia mampifandray hafatra amin'ireto sehatra manaraka ireto.<a> Hamantatra bebe kokoa.</a>",
+            "empty": "Ity efitrano ity dia tsy mampifandray hafatra amin'ny sehatra rehetra.<a> Hamantatra bebe kokoa.</a>",
+            "title": "Tetezana"
+        },
+        "delete_avatar_label": "Fafao ny avatar",
+        "general": {
+            "alias_field_has_domain_invalid": "Fisarahana ny toerana izay misy banga ohatra (:domaine.org)",
+            "alias_field_has_localpart_invalid": "Anaran'ny efitra na fanasarahana ohatra (efitranoko:domain.org)",
+            "alias_field_matches_invalid": "Ity adiresy ity dia tsy manondro ity efitrano ity",
+            "alias_field_placeholder_default": "ohatra ny efitranoko",
+            "alias_field_required_invalid": "Omeo adiresy azafady",
+            "alias_field_safe_localpart_invalid": "Tsy azo atao ny litera sasany",
+            "alias_field_taken_invalid": "Ity adiresy ity dia nanana mpizara tsy mety na efa ampiasaina",
+            "alias_field_taken_invalid_domain": "Ity adiresy ity dia efa ampiasaina",
+            "alias_field_taken_valid": "Ity adiresy ity dia azo ampiasaina",
+            "alias_heading": "Adiresy efitrano",
+            "aliases_items_label": "Adiresy hafa navoaka:",
+            "aliases_no_items_label": "Tsy mbola misy adiresy hafa navoaka, ampio iray eto ambany",
+            "aliases_section": "Adiresy ao amin'ny efitrano",
+            "avatar_field_label": "Avatar efitra",
+            "canonical_alias_field_label": "Adiresy lehibe",
+            "default_url_previews_off": "Ny fijeriny URL dia esorina hoany mpandray anjara amin'ity efitrano ity.",
+            "default_url_previews_on": "Ny fijerena URL dia alefa amin'ny alàlany default hoany mpandray anjara amin'ity efitrano ity.",
+            "description_space": "Ahitsio ny kira mifandraika amin'ny habakabaka.",
+            "error_creating_alias_description": "Nisy lesoka tamin'ny famoronana io adiresy io. Mety tsy avelany mpizara izany na nisy tsy fahombiazana vonjimaika.",
+            "error_creating_alias_title": "Hadisoana tamin'ny famoronana adiresy",
+            "error_deleting_alias_description": "Nisy lesoka taminy fanesorana io adiresy io. Mety tsy misy intsony izany na misy hadisoana vonjimaika.",
+            "error_deleting_alias_description_forbidden": "Tsy manana alalana hamafa ny adiresy ianao.",
+            "error_deleting_alias_title": "Hadisoana taminy fanesorana adiresy",
+            "error_save_space_settings": "Tsy nahomby ny fitahirizana ny habaka.",
+            "error_updating_alias_description": "Nisy lesoka taminy fanavaozana ny adiresin'ny efitrano hafa. Mety tsy avelany mpizara izany na nisy tsy fahombiazana vonjimaika.",
+            "error_updating_canonical_alias_description": "Nisy lesoka tamin'ny fanavaozana ny adiresiny efitrano. Mety tsy avelany mpizara izany na nisy tsy fahombiazana vonjimaika.",
+            "error_updating_canonical_alias_title": "Nisy olana teo am-panavaozana ny adiresy fototra",
+            "leave_space": "Avelao ny toerana",
+            "local_alias_field_label": "Adiresy eo an-toerana",
+            "local_aliases_explainer_room": "Mametraha adiresy hoan'ity efitrano ity mba ahafahany mpampiasa mahita ity efitrano ity amin'ny alàlany mpizara tranonao (%(localDomain)s )",
+            "local_aliases_explainer_space": "Mametraha adiresy hoanity habaka ity mba ahafahany mpampiasa mahita ity habaka ity aminy alàlany mpizara tranonao (%(localDomain)s )",
+            "local_aliases_section": "Adiresy eo an-toerana",
+            "name_field_label": "Anaran'ny efitrano",
+            "new_alias_placeholder": "Adiresy vaovao navoaka (oh #alias:server)",
+            "no_aliases_room": "Tsy manana adiresy eo an-toerana ity efitrano ity",
+            "no_aliases_space": "Tsy manana adiresy eo an-toerana ity habakabaka ity",
+            "other_section": "Hafa",
+            "publish_toggle": "Avoahy hoany besinimaro ity efitrano ity%(domain)s ny lisitry ny efitrano?",
+            "published_aliases_description": "Mba hamoahana adiresy dia mila apetraka ho adiresy eo an-toerana aloha.",
+            "published_aliases_explainer_room": "Ny adiresy navoaka dia azony rehetra ampiasaina aminy mpizara rehetra mba hiditra ao amin'ny efitranonao.",
+            "published_aliases_explainer_space": "Ny adiresy navoaka dia azon'ny olona ampiasaina aminy mpizara rehetra mba hiditra amin'ny habakao.",
+            "published_aliases_section": "Adiresy navoaka",
+            "save": "Alaina ny fanovana",
+            "topic_field_label": "Lohahevitra Efitrano",
+            "url_preview_encryption_warning": "Ao aminy efitrano misy miafina, toa an'ity iray ity, ny fijerena URL dia kilemaina amin'ny alàlany default mba hahazoana antoka fa tsy afaka manangona fampahalalana momba ny rohy hitanao ao amin'ity efitrano ity ny mpampiantrano anao (izay anaovana ny preview).",
+            "url_preview_explainer": "Rehefa misy olona mametraka URL ao amin'ny hafany, dia azo aseho ny fijerena URL mba hanomezana fampahalalana bebe kokoa momba an'io rohy io toy ny lohateny, famaritana, ary sary avy aminy tranokala.",
+            "url_previews_section": "Topi-maso amin'ny Url",
+            "user_url_previews_default_off": "Ianao dia manana<a> sembana</a> Zahao ny URL amin'ny alàlany default.",
+            "user_url_previews_default_on": "Ianao dia manana<a> afaka</a> Zahao ny URL aminy alàlan'ny default."
+        },
+        "notifications": {
+            "browse_button": "Mikaroka",
+            "custom_sound_prompt": "Mametraha feo mahazatra vaovao",
+            "notification_sound": "Feon'ny fampahafantarana",
+            "settings_link": "Mahazoa fampandrenesana apetraka ao amin'ny anao<a> Fikirana</a>",
+            "sounds_section": "Feo",
+            "upload_sound_label": "Ampiakatra feo voatokana",
+            "uploaded_sound": "Feo nampiakarina"
+        },
+        "people": {
+            "knock_empty": "Tsy misy fangatahana",
+            "knock_section": "Mangataka ny hiaraka",
+            "see_less": "Jereo kely kokoa",
+            "see_more": "Ijery mihaotra"
+        },
+        "permissions": {
+            "add_privileged_user_description": "Omeo tombontsoa bebe kokoa ny mpampiasa iray na maromaro ao amin'ity efitrano ity",
+            "add_privileged_user_filter_placeholder": "Mitadiava mpampiasa ao amin'ity efitrano ity…",
+            "add_privileged_user_heading": "Ampio ireo mpampiasa manana tombontsoa",
+            "ban": "Fandrarana ny mpampiasa",
+            "ban_reason": "Antony",
+            "banned_by": "Voarara ny%(displayName)s",
+            "banned_users_section": "Mpampiasa voarara",
+            "error_changing_pl_description": "Nisy hadisoana nitranga nanova ny haavon'ny herin'ny mpampiasa. Ataovy azo antoka fa manana fahazoan-dàlana ampy ianao ary andramo indray.",
+            "error_changing_pl_reqs_description": "Nisy hadisoana nitranga nanova ny fepetra takian'ny herin'ny efitrano. Ataovy azo antoka fa manana fahazoan-dàlana ampy ianao ary andramo indray.",
+            "error_changing_pl_reqs_title": "Hadisoana fanovana fepetra takian'ny herinaratra",
+            "error_changing_pl_title": "Error manova ny ahavon'ny hery",
+            "error_unbanning": "Tsy nahomby ny fandrarana",
+            "events_default": "Mandefa hafatra",
+            "invite": "Asao ireo mpampiasa",
+            "kick": "Esory ny mpampiasa",
+            "m.call": "Fanombohana%(brand)s antso",
+            "m.call.member": "Anjara%(brand)s antso",
+            "m.reaction": "Mandefasa fanehoan-kevitra",
+            "m.room.avatar": "Hanova ny avatar",
+            "m.room.avatar_space": "Hanova ny habakabaka avatar",
+            "m.room.canonical_alias": "Hanova ny adiresy fototra ny efitrano",
+            "m.room.canonical_alias_space": "Ovay ny adiresy fototra hoan'ny habaka",
+            "m.room.encryption": "Andefa",
+            "m.room.history_visibility": "Fahitany ny tantara ny fiovana",
+            "m.room.name": "Ovay ny anarana",
+            "m.room.name_space": "Hanova anarana toerana",
+            "m.room.pinned_events": "Mitantana ireo hetsika voatetika",
+            "m.room.power_levels": "Hanova ny alalana",
+            "m.room.redaction": "Esory dia nalefako avy aty aminahy",
+            "m.room.server_acl": "Hanova ny ACL mpizara",
+            "m.room.tombstone": "Hanavao ny efitrano",
+            "m.room.topic": "Hanova lohahevitra",
+            "m.room.topic_space": "Hanova famaritana",
+            "m.space.child": "Mitantana efitrano amin'ity toerana ity",
+            "m.widget": "Manova widgets",
+            "muted_users_section": "Mpampiasa moana",
+            "no_privileged_users": "Tsy misy mpampiasa manana tombontsoa manokana amin'ity efitrano ity",
+            "notifications.room": "Ampahafantaro ny rehetra",
+            "permissions_section": "alàlana",
+            "permissions_section_description_room": "Safidio ireo andraikitra ilaina mba hanovana ny faritra samihafa amin'ny efitrano",
+            "permissions_section_description_space": "Safidio ny andraikitra ilaina hanovana ny faritra samihafa amin'ny habaka",
+            "privileged_users_section": "Mpampiasa tombontsoa",
+            "redact": "Esory ny hafatra nalefany hafa",
+            "send_event_type": "Alefaso%(eventType)s zava-nitranga",
+            "state_default": "Anova ny fandrindrana",
+            "title": "Andraikitra sy fanomezana alàlana",
+            "users_default": "Andraikitra ara-pototra"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "Rehefa alefa dia tsy azo vonoina ny fanafenana hoany efitrano iray. Ny hafatra alefa ao amin'ny efitrano misy miafina dia tsy hitany mpizara, fa ny mpandray anjara ao amin'ilay efitrano ihany. Ny fampandehanana ny fanafenana dia mety hanakana ny bots sy tetezana maro tsy hiasa tsara.<a> Mianara bebe kokoa momba ny encryption.</a>",
+            "enable_encryption_confirm_title": "Alefaso ny fanafenana?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Tsy soso-kevitra ny hampidirana encryption amin'ny efitranom-bahoaka.</b> Na iza na iza dia afaka mahita sy miditra amin'ny efitranom-bahoaka, mba hahafahan'ny rehetra mamaky hafatra ao anatiny. Tsy hahazo tombony amin'ny fanafenana ianao, ary tsy ho afaka hamono azy ianao any aoriana. Hanamora ny fandraisana sy fandefasana hafatra ny fanafenana hafatra ao amin'ny efitranom-bahoaka.",
+            "enable_encryption_public_room_confirm_description_2": "Mba hisorohana ireo olana ireo dia mamorona a<a> efitrano vaovao misy encryption</a> hoany resaka kasainao hatao.",
+            "enable_encryption_public_room_confirm_title": "Tena te-hanampy fanafenana amin'ity efitranom-bahoaka ity tokoa ve ianao?",
+            "encrypted_room_public_confirm_description_1": "<b>Tsy soso-kevitra ny hampahafantatra ny efitrano misy miafina.</b> Midika izany fa afaka mahita sy miditra ao amin'ny efitrano ny olona rehetra, mba hahafahany rehetra mamaky hafatra. Tsy hahazo tombony amin&#39;ny fanafenana ianao. Hanamora ny fandraisana sy fandefasana hafatra ny fanafenana hafatra ao amin'ny efitranom-bahoaka.",
+            "encrypted_room_public_confirm_description_2": "Mba hisorohana ireo olana ireo dia mamorona a<a> efitranom-panjakana vaovao</a> hoany resaka kasainao hatao.",
+            "encrypted_room_public_confirm_title": "Tena tianao havoaka hoany daholobe ity efitrano misy miafina ity?",
+            "encryption_forced": "Mitaky encryption ny mpizara anao mba tsy ho kilemaina.",
+            "encryption_permanent": "Rehefa alefa dia tsy azo esorina ny encryption.",
+            "error_join_rule_change_title": "Tsy nahomby ny fanavaozana ny fitsipika fidirana",
+            "error_join_rule_change_unknown": "Tsy fahombiazana tsy fantatra",
+            "guest_access_warning": "Ny olona manana mpanjifa tohanana dia afaka miditra ao amin'ny efitrano tsy manana kaonty misoratra anarana.",
+            "history_visibility_invited": "Mpikambana ihany (hatramin'ny nanasana azy ireo)",
+            "history_visibility_joined": "Mpikambana ihany (hatramin'ny nidirany)",
+            "history_visibility_legend": "Iza no mahay mamaky tantara?",
+            "history_visibility_shared": "Mpikambana ihany (hatramin&#39;ny fotoana nifidianana ity safidy ity)",
+            "history_visibility_warning": "Ny fanovana aminy olona afaka mamaky tantara dia tsy mihatra afa-tsy amin'ny hafatra ho avy ato amin'ity efitrano ity. Tsy hiova ny fahitana ny tantara efa misy.",
+            "history_visibility_world_readable": "Na iza na iza",
+            "join_rule_description": "Manapaha hevitra hoe iza no afaka miditra%(roomName)s .",
+            "join_rule_invite": "Tsy miankina (manasa ihany)",
+            "join_rule_invite_description": "Ny olona nasaina ihany no afaka miditra.",
+            "join_rule_knock": "Mangataka ny hiaraka",
+            "join_rule_knock_description": "Tsy afaka miditra ny olona raha tsy omena alalana.",
+            "join_rule_public_description": "Na iza na iza afaka mahita sy miditra.",
+            "join_rule_restricted": "Mpikambana ao aminy habakabaka",
+            "join_rule_restricted_description": "Na iza na iza ao amin'ny habaka iray dia afaka mahita sy miditra.<a> Ahitsio izay habaka azo idirana eto.</a>",
+            "join_rule_restricted_description_active_space": "Na iza na iza ao<spaceName/> afaka mahita sy miditra. Afaka misafidy toerana hafa koa ianao.",
+            "join_rule_restricted_description_prompt": "Na iza na iza ao amin'ny habaka iray dia afaka mahita sy miditra. Afaka misafidy toerana maromaro ianao.",
+            "join_rule_restricted_description_spaces": "Toerana misy fidirana",
+            "join_rule_restricted_dialog_description": "Manapaha hevitra hoe aiza no toerana afaka miditra amin'ity efitrano ity. Raha misy toerana voafantina dia afaka mahita sy miditra ny mpikambana ao aminy<RoomName/> .",
+            "join_rule_restricted_dialog_empty_warning": "Esorinao ny habaka rehetra. Ny fidirana dia tsy maintsy manasa ihany",
+            "join_rule_restricted_dialog_filter_placeholder": "Fikarohana toerana",
+            "join_rule_restricted_dialog_heading_known": "Toerana hafa fantatrao",
+            "join_rule_restricted_dialog_heading_other": "Toerana na efitrano hafa mety tsy fantatrao",
+            "join_rule_restricted_dialog_heading_room": "Espace fantatrao fa misy ity efitrano ity",
+            "join_rule_restricted_dialog_heading_space": "Espace fantatrao fa misy io habakabaka io",
+            "join_rule_restricted_dialog_heading_unknown": "Ireo dia azo inoana fa anisan'ireo mpitantana efitrano hafa.",
+            "join_rule_restricted_dialog_title": "Mifidiana toerana",
+            "join_rule_restricted_n_more": {
+                "one": "&%(nombre) s Bebekokoa",
+                "other": "&%(nombre)s Bebekokoa"
+            },
+            "join_rule_restricted_summary": {
+                "one": "Amin'izao fotoana izao dia misy toerana azo idirana",
+                "other": "Amin'izao fotoana izao,%(count)s manana fidirana ny habaka"
+            },
+            "join_rule_restricted_upgrade_description": "Ity fanavaozana ity dia ahafahany mpikambana amin'ny toerana voafantina hiditra amin'ity efitrano ity tsy misy fanasana.",
+            "join_rule_restricted_upgrade_warning": "Ity efitrano ity dia any amin'ny toerana sasany tsy anao admin. Ao amin'ireo habaka ireo dia mbola haseho ny efitrano taloha, fa ny olona dia ho voataona hiditra ao amin'ilay efitrano vaovao.",
+            "join_rule_upgrade_awaiting_room": "Mametraka efitrano vaovao",
+            "join_rule_upgrade_required": "Ilaina ny fanavaozana",
+            "join_rule_upgrade_sending_invites": {
+                "one": "Mandefa fanasana...",
+                "other": "Mandefa fanasana... (%(progress)s ivelany%(count)s )"
+            },
+            "join_rule_upgrade_updating_spaces": {
+                "one": "Manavao ny toerana malalaka...",
+                "other": "Fanavaozana ny toerana malalaka... (%(progress)s ivelany%(count)s )"
+            },
+            "join_rule_upgrade_upgrading_room": "Efitrano fanavaozana",
+            "public_without_alias_warning": "Raha te hifandray amin'ity efitrano ity dia ampio adiresy azafady.",
+            "publish_room": "Ataovy hita ao aminy lahatahiry hoany daholobe ity efitrano ity.",
+            "publish_space": "Ataovy ho hita ao amin'ny lahatahiry hoany daholobe ity habaka ity.",
+            "strict_encryption": "Aza mandefa hafatra miafina na oviana na oviana amin'ireo fivoriana tsy voamarina ao amin'ity efitrano ity avy amin'ity fivoriana ity",
+            "title": "Fiarovana sy tsiambaratelo"
+        },
+        "title": "Fikirana efitrano -%(roomName)s",
+        "upload_avatar_label": "Ampiditra avatar",
+        "visibility": {
+            "alias_section": "Adiresy",
+            "error_failed_save": "Tsy nahomby ny fanavaozana ny fahitana an'ity habaka ity",
+            "error_update_guest_access": "Tsy nahomby ny fanavaozana ny fidirany vahiny amin'ity habaka ity",
+            "error_update_history_visibility": "Tsy nahomby ny fanavaozana ny tantaran'ity habaka ity",
+            "guest_access_explainer": "Afaka miditra amina toerana iray tsy manana kaonty ny vahiny.",
+            "guest_access_explainer_public_space": "Mety ilaina amin'ny toerana hoany daholobe izany.",
+            "guest_access_label": "Alefaso ny fidirana amin'ny vahiny",
+            "history_visibility_anyone_space": "Toerana fijerena mialoha",
+            "history_visibility_anyone_space_description": "Avelao ny olona hijery ny toerana misy anao alohan'ny hidirany.",
+            "history_visibility_anyone_space_recommendation": "Soso-kevitra hoany toerana hoany daholobe.",
+            "title": "Fahitana"
+        },
+        "voip": {
+            "call_type_section": "Karazana antso",
+            "enable_element_call_caption": "%(brand)sdia misy encryption aminy farany, saingy voafetra hoany mpampiasa vitsy kokoa amin'izao fotoana izao.",
+            "enable_element_call_label": "Tadiavo%(brand)s ho safidy fiantsoana fanampiny ao amin'ity efitrano ity",
+            "enable_element_call_no_permissions_tooltip": "Tsy manana fahazoan-dàlana ampy hanovana izany ianao."
+        }
+    },
+    "room_summary_card_back_action_label": "Fampahafantarana ny efitrano",
+    "scalar": {
+        "error_create": "Tsy afaka mamorona widget.",
+        "error_membership": "Tsy ao amin'ity ianao.",
+        "error_missing_room_id": "Tsy ampy id",
+        "error_missing_room_id_request": "Tsy misy room_id amin'ny fangatahana",
+        "error_missing_user_id_request": "Tsy misy user_id amin'ny fangatahana",
+        "error_permission": "Tsy mahazo alalana hanao izany ianao ato amin'ity efitrano ity.",
+        "error_power_level_invalid": "Ny ahavony hery dia tsy maintsy akamaroany tsara.",
+        "error_room_not_visible": "efitra%(roomId)s tsy hita",
+        "error_room_unknown": "Tsy fantatra ity efitrano ity.",
+        "error_send_request": "Tsy nahomby ny fandefasana fangatahana.",
+        "failed_read_event": "Tsy nahavaky ny zava-nitranga",
+        "failed_send_event": "Tsy nahomby ny fandefasana hetsika"
+    },
+    "server_offline": {
+        "description": "Tsy mamaly ny sasany amin'ireo fangatahanao ny mpizara anao. Ireto ambany ireto ny sasany amin'ireo antony mety hitranga.",
+        "description_1": "Ny mpizara (%(serverName)s ) ela loatra vao namaly.",
+        "description_2": "Ny firewall na anti-virus anao dia manakana ny fangatahana.",
+        "description_3": "Ny fanitarana toeram-pivohizana dia manakana ny fangatahana.",
+        "description_4": "An-tserasera ny mpizara.",
+        "description_5": "Nandà ny fangatahanao ny tompon'andraikitra.",
+        "description_6": "Misedra olana amin'ny fifandraisana amin'ny Internet ny faritra misy anao.",
+        "description_7": "Nisy hadisoana nitranga teo am-piezahana hifandray amin'ny mpizara.",
+        "description_8": "Ny mpizara dia tsy namboarina mba hanondroana ny olana (CORS).",
+        "empty_timeline": "Efa tratra daholo ianareo.",
+        "recent_changes_heading": "Fanovana vao haingana izay tsy mbola voaray",
+        "title": "Tsy mamaly ny mpizara"
+    },
+    "seshat": {
+        "error_initialising": "Tsy nahomby ny fanombohana fikarohana hafatra, jereo<a> ny fandrindranao</a> raha mila fanazavana fanampiny",
+        "reset_button": "Avereno ny fivarotana hetsika",
+        "reset_description": "Azo inoana fa tsy te hamerina ny fivarotam-panondro hetsikao ianao",
+        "reset_explainer": "Raha manao izany ianao, azafady, mariho fa tsy hisy ho voafafa ny hafatrao, fa ny traikefa amin'ny fikarohana dia mety hiharatsy mandritra ny fotoana fohy rehefa averina ny index.",
+        "reset_title": "Hamerina ny fivarotana hetsika?",
+        "warning_kind_files": "Ity dikany of%(brand)s tsy manohana ny fijerena rakitra sasany voafefy",
+        "warning_kind_files_app": "Ampiasao ny<a> Fampiharana Desktop</a> mba hijerena ny rakitra rehetra miafina",
+        "warning_kind_search": "Ity dikan-teny amin'ny ID %(brand)s dia tsy manohana ny fikarohana hafatra voaaro",
+        "warning_kind_search_app": "Ampiasao ny<a> Fampiharana Desktop</a> hikaroka hafatra misy miafina"
+    },
+    "setting": {
+        "help_about": {
+            "access_token_detail": "Ny token'ny fidiranao dia manome fidirana feno amin'ny kaontinao. Aza zaraina amin'iza na iza.",
+            "brand_version": "%(marquesversion:",
+            "clear_cache_reload": "Fadio ny cache ary avereno",
+            "crypto_version": "Version Crypto:",
+            "help_link": "Ho fanampiana amin'ny fampiasana%(brand)s , tsindrio<a> Eto</a> .",
+            "homeserver": "Homeserver dia<code>%(homeserverUrl)s</code>",
+            "identity_server": "Ny mpizara famantarana dia<code>%(identityServerUrl)s</code>",
+            "title": "Fanampiana avy amin'ny",
+            "versions": "Dikan-teny"
+        }
+    },
+    "settings": {
+        "all_rooms_home": "Asehoy ny efitrano rehetra ao an-trano",
+        "all_rooms_home_description": "Ny efitrano rehetra misy anao dia hiseho ao an-trano.",
+        "always_show_message_timestamps": "Asehoy foana ny mari-pamantarana hafatra",
+        "appearance": {
+            "bundled_emoji_font": "Mampiasà endritsoratra emoji mitambatra",
+            "custom_font": "Mampiasà endri-tsoratra rafitra",
+            "custom_font_description": "Mametraha anaran'ny endritsoratra napetraka ao amin'ny rafitrao &amp;%(brand)s dia hiezaka ny hampiasa azy io.",
+            "custom_font_name": "Anaran'ny endri-tsoratra",
+            "custom_font_size": "Ampiasao ny habeny manokana",
+            "custom_theme_error_downloading": "Hadisoana tamin'ny fampidinana fampahalalana lohahevitra.",
+            "custom_theme_invalid": "Tetika lohahevitra tsy mety.",
+            "font_size": "Haben'ny endri-tsoratra",
+            "font_size_default": "%(Haben'ny Endritsoratra)s (Lafiny Ankapobeny)",
+            "image_size_default": "Ara-pototra",
+            "image_size_large": "Ankamaroan'ireo",
+            "layout_bubbles": "Hafatra miboiboika",
+            "layout_irc": "IRC (Andrana)",
+            "match_system_theme": "Lohahevitra rafitra mifanandrify",
+            "timeline_image_size": "Haben'ny sary ao amin'ny tsipika"
+        },
+        "automatic_language_detection_syntax_highlight": "Alefaso ny fitadiavana fiteny mandeha ho azy amin'ny fanasongadinana syntaxe",
+        "autoplay_gifs": "Mandeha ho azy ny famakiana GIF",
+        "autoplay_videos": "Mandeha oay Orinan-tsary",
+        "big_emoji": "Alefaso ny emoji lehibe amin'ny chat",
+        "code_block_expand_default": "Manitatra ny sakana kaody amin'ny alàlan'ny lasitra",
+        "code_block_line_numbers": "Asehoy ny laharan'ny andalana amin'ny sakana kaody",
+        "disable_historical_profile": "Asehoy ny sary mpamantarana sy ny anaran'ny mpampiasa ao amin'ny tantaran'ny hafatra.",
+        "emoji_autocomplete": "Alefaso ny soso-kevitra Emoji rehefa manoratra",
+        "enable_markdown": "Alefaso ny Markdown",
+        "enable_markdown_description": "Manomboka hafatra amin'ny<code> /lemaka</code> mandefa tsy misy marika.",
+        "general": {
+            "account_management_section": "Fitantanana kaonty",
+            "account_section": "Kaonty",
+            "add_email_dialog_title": "Ampio adiresy imailaka",
+            "add_email_failed_verification": "Tsy nahavita nanamarina ny adiresy mailaka: ataovy azo antoka fa tsindrio ny rohy ao aminy imailaka",
+            "add_email_instructions": "Nandefa imailaka ho anao izahay hanamarina ny adiresinao. Araho azafady ny toromarika ao ary tsindrio ny bokotra etsy ambany.",
+            "add_msisdn_confirm_body": "Kitiho ny bokotra etsy ambany raha hanamarina ny fampidirana ity laharan-telefaona ity.",
+            "add_msisdn_confirm_button": "Hamafiso ny fampidirana laharan-telefaona",
+            "add_msisdn_confirm_sso_button": "Hamafiso ny fampidirana ity laharan-telefaona ity aminy fampiasana Single Sign On mba hanaporofoana ny maha-izy anao.",
+            "add_msisdn_dialog_title": "Ampio laharan-telefaona",
+            "add_msisdn_instructions": "Nisy hafatra an-tsoratra nalefa tany +%(msisdn)s . Ampidiro azafady ny kaody fanamarinana misy ao.",
+            "add_msisdn_misconfigured": "Ny fanampiana / fatotra amin'ny fikorianan'ny MSISDN dia diso",
+            "confirm_adding_email_body": "Kitiho ny bokotra etsy ambany raha hanamarina ny fampidirana ity adiresy mailaka ity.",
+            "confirm_adding_email_title": "Hamafiso ny fampidirana imailaka",
+            "deactivate_confirm_body": "Tena tianao ve ny hanafoana ny kaontinao? Tsy azo ovaina izany.",
+            "deactivate_confirm_body_sso": "Hamafiso ny fanafoanana ny kaontinao aminy fampiasana Single Sign On mba hanaporofoana ny maha-izy anao.",
+            "deactivate_confirm_content": "Hamafiso fa tianao ny hanafoana ny kaontinao. Raha mandroso ianao:",
+            "deactivate_confirm_content_1": "Tsy ho afaka hamerina ny kaontinao ianao",
+            "deactivate_confirm_content_2": "Tsy ho afaka miditra intsony ianao",
+            "deactivate_confirm_content_3": "Tsy misy olona afaka mampiasa indray ny anaranao (MXID), anisan'izany ianao: ity solonanarana ity dia tsy ho hita intsony",
+            "deactivate_confirm_content_4": "Hivoaka ny efitrano rehetra sy ny DM misy anao ianao",
+            "deactivate_confirm_content_5": "Hesorina aminy mpizara famantarana ianao: tsy ho hitany namanao intsony ianao aminy imailaka na nomeraon-telefaonanao",
+            "deactivate_confirm_content_6": "Mbola ho hitany olona nandray azy ny hafatrao taloha, toy ny mailaka nalefanao taloha. Te hanafina ny hafatra nalefanao aminy olona miditra amin&#39;ny efitrano ho avy ve ianao?",
+            "deactivate_confirm_continue": "Hamafiso ny fanafoanana ny kaonty",
+            "deactivate_confirm_erase_label": "Afeno aminy olona vaovao ny hafatro",
+            "deactivate_section": "Atsaharo ny kaonty",
+            "deactivate_warning": "Hetsika maharitra ny fanesorana ny kaontinao — mitandrema!",
+            "discovery_email_empty": "Hiseho ny safidy Discovery rehefa nanampy imailaka etsy ambony ianao.",
+            "discovery_email_verification_instructions": "Hamarino ny rohy ao anaty boaty fidiranao",
+            "discovery_msisdn_empty": "Hipoitra ny safidiny fikarohana rehefa nanampy laharan-telefaona etsy ambony ianao.",
+            "discovery_needs_terms": "Manaiky ny mpizara famantarana (%(serverName)s ) Fepetrany serivisy mamela anao ho hita aminy adiresy imailaka na laharan-telefaona.",
+            "email_address_in_use": "Ity adiresy imailaka ity dia efa nampiasaina",
+            "email_address_label": "Adiresy imailaka",
+            "email_not_verified": "Mbola tsy voamarina ny adiresy imailakao",
+            "email_verification_instructions": "Kitiho ny rohy ao aminy imailaka azonao hanamarinana ary tsindrio ny hanohy indray.",
+            "emails_heading": "Adiresy imailaka",
+            "error_add_email": "Tsy afaka manampy adiresy imailaka",
+            "error_deactivate_communication": "Nisy olana taminy fifandraisana tamin'ny mpizara. Andramo indray azafady.",
+            "error_deactivate_invalid_auth": "Tsy namerina ny mombamomba ny fanamarinana marina ny mpizara.",
+            "error_deactivate_no_auth": "Tsy mila fanamarinana ny mpizara",
+            "error_email_verification": "Tsy afaka manamarina ny adiresy imailaka.",
+            "error_invalid_email": "Adiresy imailaka tsy mety",
+            "error_invalid_email_detail": "Toa tsy adiresy imailaka manan-kery ity",
+            "error_msisdn_verification": "Tsy afaka manamarina ny laharan-telefaona.",
+            "error_password_change_403": "Tsy nahavita nanova ny tenimiafina. Marina ve ny tenimiafinao?",
+            "error_password_change_http": "%(errorMessage)s(Sata HTTP%(httpStatus)s )",
+            "error_password_change_title": "Hadisoana fanovana tenimiafina",
+            "error_password_change_unknown": "Fahadisoana fanovana tenimiafina tsy fantatra (%(stringifiedError)s )",
+            "error_remove_3pid": "Tsy afaka manala ny mombamomba ny fifandraisana",
+            "error_revoke_email_discovery": "Tsy afaka nanafoana ny fizarana ny adiresy imailaka",
+            "error_revoke_msisdn_discovery": "Tsy afaka nanafoana ny fizarana hoany laharan-telefaona",
+            "error_share_email_discovery": "Tsy afaka mizara adiresy imailaka",
+            "error_share_msisdn_discovery": "Tsy afaka mizara laharan-telefaona",
+            "identity_server_no_token": "Tsy hita ny mari-pamantarana fidirana",
+            "identity_server_not_set": "Tsy napetraka ny mpizara famantarana",
+            "language_section": "Fiteny sy faritra",
+            "msisdn_in_use": "Ity laharan-telefaona ity dia efa nampiasaina",
+            "msisdn_label": "Nomeraon-telefaona",
+            "msisdn_verification_field_label": "Kaody fanamarinana",
+            "msisdn_verification_instructions": "Ampidiro azafady ny kaody fanamarinana alefa aminy lahatsoratra.",
+            "msisdns_heading": "Laharana finday",
+            "oidc_manage_button": "Mitantana kaonty",
+            "password_change_section": "Mametraha tenimiafina kaonty vaovao…",
+            "password_change_success": "Niova soa aman-tsara ny tenimiafinao.",
+            "remove_email_prompt": "Esory%(email)s ?",
+            "remove_msisdn_prompt": "Esory%(phone)s ?",
+            "spell_check_locale_placeholder": "Misafidiana toerana iray"
+        },
+        "inline_url_previews_default": "Alefaso ny fijerena URL an-tserasera aminy alàlan'ny default",
+        "inline_url_previews_room": "Alefaso amin'ny alàlany ara-pototra hoany mpandray anjara amin'ity efitrano ity ny fijerena URL",
+        "inline_url_previews_room_account": "Alefaso ny fijerena URL ho an'ity efitrano ity (miantraika aminao ihany)",
+        "insert_trailing_colon_mentions": "Ampidiro tsangambato aoriana aorian'ny fiteny mpampiasa eo am-piandohany hafatra",
+        "jump_to_bottom_on_send": "Mankanesa any amin'ny farany fandaharam-potoana rehefa mandefa hafatra ianao",
+        "key_backup": {
+            "backup_in_progress": "Averina averina ny fanalahidinao (mety haharitra minitra vitsivitsy ny vakorakitra voalohany).",
+            "backup_starting": "Manomboka vakorakitra…",
+            "backup_success": "Fahombiazana!",
+            "cannot_create_backup": "Tsy afaka mamorona fanalahidin'ny vakorakitra",
+            "create_title": "Mamorona fanalahidin'ny Vakorakitra ",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "Averina averina amin'ity fitaovana ity izao ny fanalahidinao.",
+                "backup_setup_success_title": "Nahomby ny fiarovana vakorakitra",
+                "cancel_warning": "Raha manafoana izao ianao dia mety ho very ny hafatra sy angon-drakitra voafefy raha toa ka very ny fidirana amin'ny fidiranao.",
+                "confirm_security_phrase": "Hamafiso ny fehezan-teny fiarovana anao",
+                "description": "Arovy amin'ny fahaverezan'ny fidirana amin'ny hafatra sy angon-drakitra voatahiry amin'ny alàlan'ny fanohanana ny fanalahidin'ny fanafenana amin&#39;ny mpizaranao.",
+                "download_or_copy": "%(downloadButton)sna%(copyButton)s",
+                "enter_phrase_description": "Ampidiro fehezan-teny momba ny fiarovana ihany no fantatrao, satria ampiasaina amin'ny fiarovana ny angonao. Mba ho azo antoka dia tsy tokony hampiasainao indray ny tenimiafinao.",
+                "enter_phrase_title": "Ampidiro fehezanteny fiarovana",
+                "enter_phrase_to_confirm": "Ampidiro fanindroany ny fehezan-teny fiarovana anao hanamafisana izany.",
+                "generate_security_key_description": "Hamorona fanalahidin'ny vakorakitra izahay mba hitahirizanao any amin'ny toerana azo antoka, toy ny mpitantana ny tenimiafina na ny fitehirizana.",
+                "generate_security_key_title": "Mamorona fanalahidin'ny vakorakitra",
+                "pass_phrase_match_failed": "Tsy mifanentana izany.",
+                "pass_phrase_match_success": "Mifanaraka izany!",
+                "phrase_strong_enough": "Mahafinaritra! Ity fehezan-teny fiarovana ity dia toa matanjaka.",
+                "secret_storage_query_failure": "Tsy afaka manontany sata fitahirizana miafina",
+                "security_key_safety_reminder": "Tehirizo any amin'ny toerana azo antoka ny Key Security-nao, toy ny mpitantana ny tenimiafina na ny arofenitra, satria ampiasaina amin'ny fiarovana ny angona voarakotra.",
+                "set_phrase_again": "Miverena hametraka azy indray.",
+                "settings_reminder": "Azonao atao koa ny manangana fanalahidin'ny vakorakitra ny mitantana ny fanalahidinao ao amin'ny fanitsiana.",
+                "title_confirm_phrase": "Hamafiso ny fehezan-teny fiarovana",
+                "title_save_key": "Tehirizo ny lakilen'ny fiarovanao",
+                "title_set_phrase": "Mametraha fehezanteny fiarovana",
+                "unable_to_setup": "Tsy afaka nanangana fitahirizana miafina",
+                "use_different_passphrase": "Mampiasa fehezanteny hafa?",
+                "use_phrase_only_you_know": "Mampiasà andian-teny miafina izay fantatrao ihany, ary raha tsy izany dia mitahiry fanalahidin'ny vakorakitra ampiasaina amin'ny vakorakitra."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "Hamafiso ny fehezan-teny",
+            "enter_passphrase": "Ampidiro fehezanteny",
+            "export_description_1": "Ity dingana ity dia ahafahanao manondrana ny fanalahidiny hafatra azonao ao amin'ny efitrano misy miafina mankany amin'ny rakitra eo an-toerana. Azonao atao ny manafatra ny rakitra amin'ny mpanjifa hafa aminy ho avy, mba ho azony mpanjifa ihany koa ny mamadika ireo hafatra ireo.",
+            "export_description_2": "Ny rakitra aondrana dia ahafahan'izay afaka mamaky azy hamadika ny hafatra voafefy izay hitanao, ka tokony hitandrina ianao mba hitazonana azy io ho azo antoka. Mba hanampiana amin'izany dia tokony hampiditra fehezanteny tokana eto ambany ianao, izay hampiasaina hanafenana ny angona naondrana ihany. Tsy azo atao afa-tsy ny manafatra ny angona amin'ny alalan'ny fampiasana fehezanteny mitovy.",
+            "export_title": "Fanondranana fanalahidin'ny efitrano",
+            "file_to_import": "Rakitra hoany fanafarana",
+            "import_description_1": "Ity dingana ity dia ahafahanao manafatra fanalahidy fanafenana izay naondranao teo aloha avy amin'ny mpanjifa hafa. Avy eo ianao dia ho afaka hamadika ny hafatra rehetra azony mpanjifa hafa hamadika.",
+            "import_description_2": "Ny rakitra fanondranana dia ho voaharo amin'ny fehezan-teny. Tokony hampidirinao eto ny fehezan-teny, hamafa ny rakitra.",
+            "import_title": "Manafatra fanalahidin'ny efitrano",
+            "phrase_cannot_be_empty": "Tsy tokony ho foana ny fehezanteny",
+            "phrase_must_match": "Tsy maintsy mifanaraka ny fehezanteny",
+            "phrase_strong_enough": "Mahafinaritra! Ity fehezanteny ity dia toa matanjaka"
+        },
+        "keyboard": {
+            "title": "Kitendry"
+        },
+        "notifications": {
+            "default_setting_description": "Ity kira ity dia ampiharina aminy alàlan'ny ara-pototra aminy efitranonao rehetra.",
+            "default_setting_section": "Te-hampandrenesina aho momba ny (Fametrahana ara-pototra)",
+            "desktop_notification_message_preview": "Asehoy ny sariny hafatra amin'ny fampahafantarana birao",
+            "email_description": "Mahazoa famintinana imailaka momba ny fampandrenesana tsy hita",
+            "email_section": "Famintinana mailaka",
+            "email_select": "Fidio izay imailaka tianao handefasana famintinana. Tantano ny imailakao<button> jeneraly</button> .",
+            "enable_audible_notifications_session": "Alefaso ny fampahafantarana heno hoan'ity fivoriana ity",
+            "enable_desktop_notifications_session": "Alefaso ny fampandrenesana desktop hoan'ity session ity",
+            "enable_email_notifications": "Alefaso ny fampahafantarana mailaka hoany%(email)s",
+            "enable_notifications_account": "Alefaso ny fampahafantarana ho an'ity kaonty ity",
+            "enable_notifications_account_detail": "Atsaharo ny fampandrenesana amin'ny fitaovanao sy ny fotoam-pivorianao rehetra",
+            "enable_notifications_device": "Alefaso ny fampahafantarana hoan'ity fitaovana ity",
+            "error_loading": "Nisy lesoka teo am-pametahana ny tefiny fampahafantaranao.",
+            "error_permissions_denied": "%(brand)stsy manana alalana handefa fampandrenesana anao - azafady jereo ny firafitry ny navigateur",
+            "error_permissions_missing": "%(brand)stsy nahazo alalana handefa fampahafantarana - andramo indray azafady",
+            "error_saving": "Error amin'ny fitahirizana ny safidiny fampahafantarana",
+            "error_saving_detail": "Nisy hadisoana nitranga teo am-pantahiana ny safidinao fampahafantarana.",
+            "error_title": "Tsy afaka mamela ny Notifications",
+            "error_updating": "Nisy hadisoana nitranga taminy fanavaozana ny safidinao fampahafantarana. Andramo avereno indray ny safidinao azafady.",
+            "invites": "Nasaina hoany amin'ny efitrano iray",
+            "keywords": "Manehoa marika<badge/> rehefa ampiasaina ao anaty efitrano ny teny fanalahidy.",
+            "keywords_prompt": "Ampidiro eto ny teny fanalahidy, na ampiasao aminy fanovana tsipelina na anaram-bositra",
+            "labs_notice_prompt": "<strong>Fanavaozana:</strong> Nohamafisinay ny Settings Notifications mba hanamora ny fitadiavana. Tsy aseho eto ny kirakira manokana nofidinao taloha, fa mbola mavitrika ihany. Raha manohy ianao, dia mety hiova ny sasany amin'ireo fanovanao.<a> Hamantatra bebe kokoa</a>",
+            "mentions_keywords": "Fanononana sy teny fototra",
+            "mentions_keywords_only": "Fanononana sy teny fototra ihany",
+            "messages_containing_keywords": "Hafatra misy teny fanalahidy",
+            "noisy": "Tabataba",
+            "notices": "Hafatra nalefan'ny raobot",
+            "notify_at_room": "Ampahafantaro rehefa misy milaza fa mampiasa @ efitra",
+            "notify_keyword": "Ampahafantaro rehefa misy mampiasa teny fototra",
+            "notify_mention": "Ampahafantaro rehefa misy olona manonona mampiasa @fizotra ny anarana na%(mxid)s",
+            "other_section": "Zavatra hafa heverinay fa mety hahaliana anao:",
+            "people_mentions_keywords": "Vahoaka, fanononana sy teny fanalahidy",
+            "play_sound_for_description": "Ampiharo aminy alàlan'ny ara-pototra aminy efitrano rehetra aminy fitaovana rehetra.",
+            "play_sound_for_section": "Alefaso ny feo hoan'ny",
+            "push_targets": "Kendrena fampahafantarana",
+            "quick_actions_mark_all_read": "Mariho ho voavaky ny hafatra rehetra",
+            "quick_actions_reset": "Avereno amin'ny fanitsiana ara-pototra",
+            "quick_actions_section": "Hetsika haingana",
+            "room_activity": "Miseho ny hetsika efitrano vaovao, ny fanavaozana ary ny hafatra momba ny sata",
+            "rule_call": "Fanasana antso",
+            "rule_contains_display_name": "Hafatra misy ny anarako",
+            "rule_contains_user_name": "Hafatra misy solonanarana",
+            "rule_encrypted": "Hafatra voarakitra ao aminy chat vondrona",
+            "rule_encrypted_room_one_to_one": "Hafatra voasivana aminny chat tokana",
+            "rule_invite_for_me": "Rehefa asaina ho any amin’ny efitrano iray aho",
+            "rule_message": "Hafatra ao aminy vondrona",
+            "rule_room_one_to_one": "Hafatra amin'ny chat tokana",
+            "rule_roomnotif": "Hafatra misy @ efitra",
+            "rule_suppress_notices": "Hafatra nalefan'ny bot",
+            "rule_tombstone": "Rehefa havaozina ny efitrano",
+            "show_message_desktop_notification": "Asehoy ny hafatra aminy fampahafantarana desktop",
+            "voip": "Feo sy lahatsary antso"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "Alefaso haingana ny fitaovana (avereno alefa %(nom de l'application)s hanan-kery)",
+            "always_show_menu_bar": "Asehoy lalandava ny fikandrana lisitra bar",
+            "autocomplete_delay": "Fahatarana mandeha ho azy (ms)",
+            "code_blocks_heading": "Sakana kaody",
+            "compact_modern": "Mampiasà lamina Maoderina mirindra kokoa",
+            "composer_heading": "Mpamoron-kira",
+            "enable_hardware_acceleration": "Alefaso ny fanafainganana ny fitaovana",
+            "enable_tray_icon": "Asehoy ny kisary lovia ary ahenao ny varavarankely eo akaikiny",
+            "keyboard_heading": "Kitendry hitsin-dàlana",
+            "keyboard_view_shortcuts_button": "Raha hijery ny hitsin-dàlana fitendry rehetra,<a> kitiho eto</a> .",
+            "media_heading": "Sary, GIF ary horonan-tsary",
+            "presence_description": "Zarao amin'ny hafa ny asanao sy ny satanao.",
+            "rm_lifetime": "Mamaky Markera mandritra ny androm-pamakiana (ms)",
+            "rm_lifetime_offscreen": "Famakiana Markera mandritra ny androm-pamakiana ivelany efijery(ms)",
+            "room_directory_heading": "Lahatahiry efitrano",
+            "room_list_heading": "Lisitry ny efitrano",
+            "show_avatars_pills": "Asehoy ny avatar amin'ny anaran'ny mpampiasa, efitrano ary hetsika",
+            "show_polls_button": "Asehoy ny bokotra fitsapan-kevitra",
+            "surround_text": "Manodidina ny lahatsoratra voafantina rehefa manoratra tarehintsoratra manokana",
+            "time_heading": "Fampisehoana fotoana"
+        },
+        "prompt_invite": "Alefaso alohan'ny handefasana fanasana amin'ny ID mety tsy mety",
+        "replace_plain_emoji": "Soloy ho azy ny Emoji soratra tsotra",
+        "security": {
+            "analytics_description": "Mizarà angona tsy mitonona anarana hanampiana anay hamantatra olana. Tsy misy zavatra manokana. Tsy misy antoko fahatelo.",
+            "bulk_options_accept_all_invites": "Ekeo daholo%(invitedRooms)s fanasana",
+            "bulk_options_reject_all_invites": "Mandà daholo%(invitedRooms)s fanasana",
+            "bulk_options_section": "Safidin'ny ampahany",
+            "e2ee_default_disabled_warning": "Nosakanan'ny admin server-nao ny encryption aminy alàlany default ao aminy efitrano manokana Hafatra mivantana.",
+            "enable_message_search": "Alefaso ny fikarohana hafatra ao amin'ny efitrano misy miafina",
+            "encryption_section": "Fanafenana",
+            "ignore_users_empty": "Tsy manana mpampiasa tsy noraharahiana ianao.",
+            "ignore_users_section": "Ireo mpampiasa tsy noraharahiana",
+            "key_backup_algorithm": "Algorithm:",
+            "key_backup_connect": "Ampifandraiso amin'ny fanalahidin'ny vakorakitra ity fivoriana ity",
+            "message_search_disable_warning": "Raha kilemaina, dia tsy hiseho aminy valiny fikarohana ny hafatra avy aminy efitrano misy miafina.",
+            "message_search_disabled": "Ampidiro ao an-toerana ny hafatra miafina mba hiseho aminy valiny fikarohana.",
+            "message_search_enabled": {
+                "one": "Ampidiro ao an-toerana ny hafatra miafina mba hiseho amin'ny valiny fikarohana, aminy fampiasana azy%(size)s hitehirizana hafatra avy amin'ny%(rooms)s efitrano.",
+                "other": "Ampidiro ao an-toerana ny hafatra miafina mba hiseho amin'ny valiny fikarohana, aminy fampiasana azy%(size)s hitehirizana hafatra avy aminy%(rooms)s efitra."
+            },
+            "message_search_failed": "Tsy nahomby ny fanombohana fikarohana hafatra",
+            "message_search_indexed_messages": "Hafatra voarakitra:",
+            "message_search_indexed_rooms": "Efitrano misy tondro:",
+            "message_search_indexing": "Fanondroana amin'izao fotoana izao:%(currentRoom)s",
+            "message_search_indexing_idle": "Tsy manao indexing hafatra hoany efitrano rehetra.",
+            "message_search_intro": "%(brand)sdia mitahiry tsara ny hafatra miafina ao an-toerana mba hiseho amin'ny valiny fikarohana:",
+            "message_search_room_progress": "%(doneRooms)sivelan'ny%(totalRooms)s",
+            "message_search_section": "Fikarohana hafatra",
+            "message_search_sleep_time": "Haingana ny tokony hisintonana hafatra.",
+            "message_search_space_used": "Toerana ampiasaina:",
+            "message_search_unsupported": "%(brand)stsy ampy ny singa sasany ilaina amin'ny fikojakojana ny hafatra miafina ao an-toerana. Raha te hanandrana an'io endri-javatra io ianao dia manangana fomba fanao%(brand)s Desktop miaraka amin'ny<nativeLink> nampiana singa fikarohana</nativeLink> .",
+            "message_search_unsupported_web": "%(brand)stsy afaka mitahiry tsara ireo hafatra voafefy eo an-toerana raha mandeha amin'ny toeram-pivohizana web. Ampiasao<desktopLink>%(brand)s biraonao</desktopLink> mba hiseho amin&#39;ny valin&#39;ny fikarohana ireo hafatra voafefy.",
+            "record_session_details": "Raketo ny anaran'ny mpanjifa, ny dikan-teny ary ny url mba hamantarana mora kokoa ny fotoam-pivoriana ao aminy mpitantana fivoriana",
+            "send_analytics": "Mandefasa angona fitiliana",
+            "strict_encryption": "Aza mandefa hafatra miafina mihitsy amin'ireo fivoriana tsy voamarina avy amin'ity fivoriana ity"
+        },
+        "send_read_receipts": "Mandefa ny rosia voapanga novakiana",
+        "send_read_receipts_unsupported": "Tsy manohana ny fanafoanana ny fandefasana tapakila novakiana ny mpizaranao.",
+        "send_typing_notifications": "Alefaso fampandrenesana fanoratana",
+        "sessions": {
+            "best_security_note": "Ho fiarovana tsara indrindra, hamarino ny fotoam-pivorianao ary midira amin'ny fivoriana izay tsy fantatrao na tsy ampiasainao intsony.",
+            "browser": "Mpitety",
+            "confirm_sign_out": {
+                "one": "Hamafiso ny fivoahana ity fitaovana ity",
+                "other": "Hamafiso ny fisoratana anarana ireto fitaovana ireto"
+            },
+            "confirm_sign_out_body": {
+                "one": "Kitiho ny bokotra etsy ambany raha hanamarina ny hivoahan'ity fitaovana ity.",
+                "other": "Kitiho ny bokotra etsy ambany hanamafisana ny fisoratana anarana amin'ireo fitaovana ireo."
+            },
+            "confirm_sign_out_continue": {
+                "one": "Hivoaka ny fitaovana",
+                "other": "Hivoaka ny fitaovana"
+            },
+            "confirm_sign_out_sso": {
+                "one": "Hamafiso ny fivoahana ity fitaovana ity amin'ny fampiasana Single Sign On mba hanaporofoana ny maha-izy anao.",
+                "other": "Hamafiso ny fivoahana ireo fitaovana ireo amin'ny fampiasana Single Sign On mba hanaporofoana ny maha-izy anao."
+            },
+            "current_session": "Fivoriana ankehitriny",
+            "desktop_session": "Zotra Desktop",
+            "details_heading": "Ny antsipirian'ny fivoriana",
+            "device_unverified_description": "Hamarino na mivoaha amin'ity fotoam-pivoriana ity mba hahazoana antoka tsara sy azo ianteherana.",
+            "device_unverified_description_current": "Hamarino ny fotoam-pivorianao ankehitriny mba hahazoana hafatra azo antoka kokoa.",
+            "device_verified_description": "Ity fivoriana ity dia vonona amin'ny fandefasana hafatra azo antoka.",
+            "device_verified_description_current": "Vonona amin'ny fandefasana hafatra azo antoka ny fotoam-pivorianao ankehitriny.",
+            "error_pusher_state": "Tsy nahavita nametraka ny toetry ny pusher",
+            "error_set_name": "Tsy nahomby ny fametrahana ny anaran'ny fivoriana",
+            "filter_all": "Rehetra",
+            "filter_inactive": "Tsy mavitrika",
+            "filter_inactive_description": "Tsy mavitrika hoan'ny%(inactiveAgeDays)s andro na mihoatra",
+            "filter_label": "Fitaovana sivana",
+            "filter_unverified_description": "Tsy vonona amin'ny fandefasana hafatra azo antoka",
+            "filter_verified_description": "Vonona amin'ny fandefasana hafatra azo antoka",
+            "hide_details": "Afeno ny antsipiriany",
+            "inactive_days": "Tsy mavitrika ho an'ny%(inactiveAgeDays)s + andro",
+            "inactive_sessions": "Zotra tsy mavitrika",
+            "inactive_sessions_explainer_1": "Zotra tsy mavitrika dia fotoam-pivoriana tsy nampiasainao nandritra ny fotoana ela, fa mbola mahazo fanalahidin'ny fanafenana.",
+            "inactive_sessions_explainer_2": "Manatsara ny fiarovana sy ny fampandehanana ny fanesorana ireo fivoriana tsy mavitrika, ary manamora ny fahafantaranao raha misy fivoriana vaovao mampiahiahy.",
+            "inactive_sessions_list_description": "Eritrereto ny miala amin'ny fivoriana taloha (%(inactiveAgeDays)s andro na mihoatra) tsy ampiasainao intsony.",
+            "ip": "Adiresy IP",
+            "last_activity": "Hetsika farany",
+            "mobile_session": "Zotra ara-pinday",
+            "n_sessions_selected": {
+                "one": "%(count)s zotra voafantina",
+                "other": "%(count)s zotra voafantina"
+            },
+            "no_inactive_sessions": "Tsy misy fivoriana tsy mavitrika hita.",
+            "no_sessions": "Tsy misy fivoriana hita.",
+            "no_unverified_sessions": "Tsy misy fivoriana tsy voamarina hita.",
+            "no_verified_sessions": "Tsy misy fivoriana voamarina hita.",
+            "os": "Rafitra fikirakirana",
+            "other_sessions_heading": "Fivoriana hafa",
+            "push_heading": "Fampandrenesana manosika",
+            "push_subheading": "Mandraisa fampandrenesana fanosehana amin'ity fivoriana ity.",
+            "push_toggle": "Toggle fampandrenesana fanosehana amin'ity fivoriana ity.",
+            "rename_form_caption": "Azafady, fantaro fa ny anaran'ny session dia hitan'ny olona ifandraisanao ihany koa.",
+            "rename_form_heading": "Hanova anarana session",
+            "rename_form_learn_more": "Zotra fanovana anarana",
+            "rename_form_learn_more_description_1": "Ireo mpampiasa hafa amin'ny hafatra mivantana sy efitrano idiranao dia afaka mijery lisitra feno amin'ny fotoam-pivorianao.",
+            "rename_form_learn_more_description_2": "Izany dia manome azy ireo fahatokiana fa tena miresaka aminao izy ireo, saingy midika koa izany fa afaka mahita ny anaran'ny fivoriana ampidirinao eto izy ireo.",
+            "security_recommendations": "Tolo-kevitra momba ny fiarovana",
+            "security_recommendations_description": "Hatsarao ny fiarovana ny kaontinao amin'ny fanarahana ireto tolo-kevitra ireto.",
+            "session_id": "Zotra ID",
+            "show_details": "Asehoy ny antsipiriany",
+            "sign_in_with_qr": "Midira amin'ny kaody QR",
+            "sign_in_with_qr_button": "Asehoy ny kaody QR",
+            "sign_in_with_qr_description": "Azonao atao ny mampiasa an'io fitaovana io mba hidirana fitaovana vaovao miaraka amin'ny kaody QR. Mila mijery ny kaody QR aseho amin'ity fitaovana ity ianao miaraka amin&#39;ny fitaovanao mivoaka.",
+            "sign_out": "Mivoaha amin'ity fivoriana ity",
+            "sign_out_all_other_sessions": "Mialà amin'ny fivoriana hafa rehetra (%(otherSessionsCount)s )",
+            "sign_out_confirm_description": {
+                "one": "Tena te hivoaka ve ianao%(count)s zotra?",
+                "other": "Tena te hivoaka ve ianao%(count)s zotra?"
+            },
+            "sign_out_n_sessions": {
+                "one": "Mivoaha amin'ny%(count)s Zotra",
+                "other": "Mivoaha amin'ny%(count)s Zotra"
+            },
+            "title": "Fivoriana",
+            "unknown_session": "Karazana fivoriana tsy fantatra",
+            "unverified_session": "Zotra tsy voamarina",
+            "unverified_session_explainer_1": "Ity zotra ity dia tsy mahazaka encryption ka tsy azo hamarinina.",
+            "unverified_session_explainer_2": "Tsy ho afaka handray anjara amin'ny efitrano misy fanafenana ianao rehefa mampiasa an'ity session ity.",
+            "unverified_session_explainer_3": "Ho an'ny fiarovana tsara indrindra sy ny fiainana manokana, dia asaina mampiasa mpanjifa izay manohana ny fanafenana.",
+            "unverified_sessions": "Fivoriana tsy voamarina",
+            "unverified_sessions_explainer_1": "Zotra unverified dia fotoam-pivoriana izay niditra niaraka tamin'ny fanomezan-dàlanao nefa tsy nohamarinina.",
+            "unverified_sessions_explainer_2": "Tokony ho azonao antoka indrindra fa fantatrao ireo fotoam-pivoriana ireo satria mety maneho ny fampiasana tsy nahazoana alalana ny kaontinao.",
+            "unverified_sessions_list_description": "Hamarino ny fotoam-pivorianao mba hahazoana hafatra azo antoka kokoa na mivoaha amin'ireo izay tsy fantatrao na tsy ampiasainao intsony.",
+            "url": "URL",
+            "verified_session": "Zotra voamarina",
+            "verified_sessions": "Voamarina fotoam-pivoriana",
+            "verified_sessions_explainer_1": "Ny fotoam-pivoriana voamarina dia na aiza na aiza ampiasanao ity kaonty ity aorian'ny fampidiranao ny fehezan-teny na ny fanamafisana ny maha-izy anao amin&#39;ny fivoriana voamarina hafa.",
+            "verified_sessions_explainer_2": "Midika izany fa manana ny fanalahidy rehetra ilaina ianao mba hamahana ny hafatrao voahidy ary hanamarina amin'ny mpampiasa hafa fa matoky an'ity fivoriana ity ianao.",
+            "verified_sessions_list_description": "Mba hahazoana fiarovana tsara indrindra, midira amin'ny session izay tsy fantatrao na tsy ampiasainao intsony.",
+            "verify_session": "Hamarino ny fizorana",
+            "web_session": "Zotra amin'ny Web"
+        },
+        "show_avatar_changes": "Asehoy ny fanovana sari-mpahamatarana",
+        "show_breadcrumbs": "Asehoy ny hitsin-dàlana mankany amin'ireo efitrano hita vao haingana eo ambonin'ny lisitry ny efitrano",
+        "show_chat_effects": "Asehoy ny fiantraikan'ny chat (anima rehefa mandray oh confetti)",
+        "show_displayname_changes": "Asehoy ny fanovana anarana aseho",
+        "show_join_leave": "Asehoy ny hafatra miaraka / miala (manasa / manala / mandrara tsy misy fiantraikany)",
+        "show_nsfw_content": "Asehoy ny atiny NSFW",
+        "show_read_receipts": "Asehoy ny rosia novakiana nalefan'ny mpampiasa hafa",
+        "show_redaction_placeholder": "Asehoy ny toerana misy ny hafatra nesorina",
+        "show_stickers_button": "Asehoy ny bokotra an-tsary",
+        "show_typing_notifications": "Asehoy ny fampandrenesana fanoratana",
+        "sidebar": {
+            "metaspaces_favourites_description": "Vorio amin'ny toerana iray ny efitranonao sy ny olona tianao rehetra.",
+            "metaspaces_home_all_rooms": "Asehoy ny efitrano rehetra",
+            "metaspaces_home_all_rooms_description": "Asehoy ny efitranonao rehetra ao an-trano, na dia ao anaty habakabaka aza.",
+            "metaspaces_home_description": "Ny trano dia ilaina amin'ny fijerena ny zava-drehetra.",
+            "metaspaces_orphans": "Efitrano ivelan'ny toerana iray",
+            "metaspaces_orphans_description": "Vondrona amin'ny toerana iray ny efitranonao rehetra izay tsy anisan'ny habaka.",
+            "metaspaces_people_description": "Vorio amin'ny toerana iray ny olonao rehetra.",
+            "metaspaces_subsection": "Toerana hanehoana",
+            "spaces_explainer": "Ny habaka dia fomba ivondronana efitrano sy olona. Miaraka amin'ireo habaka misy anao, azonao atao koa ny mampiasa ireo efa vita.",
+            "title": "Sidebar"
+        },
+        "start_automatically": "Manomboka ho azy aoriany fidirana amin'ny rafitra",
+        "use_12_hour_format": "Asehoy ny mari-pamantarana amin'ny endrika adiny 12 (oh 2:30 ariva)",
+        "use_command_enter_send_message": "Ampiasao Command + Enter raha handefa hafatra",
+        "use_command_f_search": "Ampiasao ny baiko + F hitadiavana ny fandaharam-potoana",
+        "use_control_enter_send_message": "Ampiasao Ctrl + Enter raha handefa hafatra",
+        "use_control_f_search": "Ampiasao ny Ctrl + F raha hikaroka ny fandaharam-potoana",
+        "voip": {
+            "allow_p2p": "Avelao ny Peer-to-Peer amin'ny antso 1:1",
+            "allow_p2p_description": "Rehefa alefa dia mety ho hitan'ny antoko hafa ny adiresy IP-nao",
+            "audio_input_empty": "Tsy misy mikrofonina hita",
+            "audio_output": "Fivoaham-peo",
+            "audio_output_empty": "Tsy misy fivoaham-peo voatsikiaritra",
+            "auto_gain_control": "Fanaraha-maso ny fahazoana mandeha ho azy",
+            "connection_section": "Fifandraisana",
+            "echo_cancellation": "Fanafoanana ny echo",
+            "enable_fallback_ice_server": "Avelao ny mpizara fanampiana antso miverina (%(server)s )",
+            "enable_fallback_ice_server_description": "Mihatra ihany raha tsy manolotra iray ny homeserver anao. Ny adiresy IP anao dia hozaraina mandritra ny antso.",
+            "mirror_local_feed": "Taratra ny fahana an-tsary eo an-toerana",
+            "missing_permissions_prompt": "Tsy mahazo alalana amin'ny haino aman-jery, tsindrio ny bokotra eto ambany raha hangataka.",
+            "noise_suppression": "Fanafoanana ny tabataba",
+            "request_permissions": "Mangataka fahazoan-dàlana amin'ny haino aman-jery",
+            "title": "Feo sy fakan-tsary",
+            "video_input_empty": "Tsy misy fakan-tsary hita",
+            "video_section": "Fikirana vata fakan-tsary",
+            "voice_agc": "Ampifanaraho ho azy ny hamafin'ny mikrôfonina",
+            "voice_processing": "Fanodinana feo",
+            "voice_section": "Fikirana feo"
+        },
+        "warn_quit": "Mitandrema alohan'ny hialana",
+        "warning": "<w>WARNING:</w> description/>"
+    },
+    "share": {
+        "permalink_message": "Rohy mankany amin'ny hafatra voafantina",
+        "permalink_most_recent": "Rohy mankany amin'ny hafatra farany indrindra",
+        "title_message": "Zarao ny Hafatra Efitrano",
+        "title_room": "Hizara Efitrano",
+        "title_user": "Mizara mpampiasa"
+    },
+    "slash_command": {
+        "addwidget": "Manampy Widget manokana amin'ny alàlan'ny URL ao amin'ny efitrano",
+        "addwidget_iframe_missing_src": "iframe dia tsy manana toetra src",
+        "addwidget_invalid_protocol": "Omeo URL widget https:// na http:// azafady",
+        "addwidget_missing_url": "Omeo URL widget na kaody ampidirina azafady",
+        "addwidget_no_permissions": "Tsy afaka manova widgets ao amin'ity efitrano ity ianao.",
+        "ban": "Mandràra ny mpampiasa manana ID nomena",
+        "category_actions": "Hetsika",
+        "category_admin": "Tomponandraikitra",
+        "category_advanced": "Mandrosoa",
+        "category_effects": "Vokany",
+        "category_messages": "Hafatra",
+        "category_other": "Hafa",
+        "command_error": "Fahadisoana mpibaiko",
+        "converttodm": "Manova ny efitrano ho DM",
+        "converttoroom": "Manova ny DM ho efitra iray",
+        "could_not_find_room": "Tsy nahita efitrano",
+        "deop": "Deops mpampiasa manana ID nomena",
+        "devtools": "Manokatra ny fifanakalozan-dresaka fitaovana fampandrosoana",
+        "discardsession": "Manery ny fotoam-pivoriana mivoaka amin'izao fotoana izao ao amin'ny efitrano misy miafina mba hariana",
+        "error_invalid_rendering_type": "Hadisoana baiko: Tsy nahita karazana famandrihana (%(renderingType)s )",
+        "error_invalid_room": "Tsy nahomby ny baiko: Tsy nahita efitrano (%(roomId)s )",
+        "error_invalid_runfn": "Fahadisoan'ny baiko: Tsy mahazaka baiko slash.",
+        "error_invalid_user_in_room": "Tsy nahita mpampiasa tao amin'ny efitrano",
+        "help": "Mampiseho lisitry ny baiko misy fampiasana sy famaritana",
+        "help_dialog_title": "Fanampiana baiko",
+        "holdcall": "Mametraka ny antso ao amin'ny efitrano misy ankehitriny",
+        "html": "Mandefa hafatra iray aminy HTML, nefa tsy mandika itovy ohatrany markdown",
+        "ignore": "Tsy miraharaha ny mpampiasa iray, manafina ny hafatr'izy ireo aminao.",
+        "ignore_dialog_description": "Tsy miraharaha ianao izao%(userId)s",
+        "ignore_dialog_title": "Tsy noraharahiana ny mpampiasa",
+        "invite": "Manasa ny mpampiasa manana ID nomena ho any amin'ny efitrano misy ankehitriny",
+        "invite_3pid_needs_is_error": "Mampiasà mpizara famantarana hanasana aminy imailaka. Mitantana ao amin'ny fanitsiana.",
+        "invite_3pid_use_default_is_title": "Mampiasà mpizara famantarana",
+        "invite_3pid_use_default_is_title_description": "Mampiasà mpizara famantarana hanasana aminy imailaka. Kitiho ny tohizo hampiasa ny mpizara famantarana ny default (%(defaultIdentityServerName)s ) na mitantana ao amin'ny fanitsiana.",
+        "invite_failed": "Mpampiasa (%(user)s ) tsy nifarana araka ny fanasana%(roomId)s fa tsy nisy hadisoana nomena avy aminy fitaovana fanasana",
+        "join": "Mitambatra amin'ny efitrano misy adiresy nomena",
+        "jumptodate": "Mankanesa any amin'ny daty nomena ao amin'ny fandaharam-potoana",
+        "jumptodate_invalid_input": "Tsy afaka nahatakatra ny daty nomena izahay (%(date d'entrée) s). Andramo ampiasaina ny endrika AAAA-MM-JJ.",
+        "lenny": "Manisy ( ͡° ͜ʖ ͡°) amin'ny hafatra an-tsoratra tsotra",
+        "me": "Mampiseho hetsika",
+        "msg": "Mandefa hafatra amin'ny mpampiasa nomena",
+        "myavatar": "Ovay ny sarim-pahafantaranareo ao aminy efitrano rehetra",
+        "myroomavatar": "Manova ny sarim-pahafantaranareo ao amin'ny efitrano Ankehitriny ihany",
+        "myroomnick": "Ovay ny anaram-bositra asehonareo ao amin'ny efitrano Ankehitriny ihany",
+        "nick": "Ovay ny anaram-bositra izay asehonareo",
+        "no_active_call": "Tsy misy antso mavitrika ao amin'ity efitrano ity",
+        "op": "Farito ny ahavon'ny herin'ny mpampiasa iray",
+        "part_unknown_alias": "Adiresy efitrano tsy fantatra:%(roomAlias)s",
+        "plain": "Mandefa hafatra iray karazany lahatsora tsotra, nefa tsy mandika azy Io ho marika",
+        "query": "Manokatra chat miaraka amin'ny mpampiasa nomena",
+        "query_not_found_phone_number": "Tsy nahita ID ho an'ny laharan-telefaona",
+        "rageshake": "Mandefasa tatitra momba ny bibikely miaraka amin'ny diary",
+        "rainbow": "Mandefa hafatra miloko toy ny arkansielina",
+        "rainbowme": "Mandefa ny emote nomena miloko toy ny arkansielina",
+        "remove": "Esory ny mpampiasa manana ID nomena ao amin'ity efitrano ity",
+        "roomavatar": "Ovay ny avatar n'y efitrano misy Ankehitriny",
+        "roomname": "Farito ny anaran'ny efitrano",
+        "server_error": "Fahadisoana teo amin'ny mpizara",
+        "server_error_detail": "Tsy misy mpizara, na be loatra, na zavatra hafa tsy mety.",
+        "shrug": "Manisy hafatra antsoratra tsotra",
+        "spoiler": "Mandefa ny hafatra nomena ho toy ny mpandringana",
+        "tableflip": "Manisy hafatra amin'ny soratra tsotra",
+        "topic": "Mahazo na farito ny lohahevitra ny efitrano",
+        "topic_none": "Tsy misy lohahevitra ity efitrano ity.",
+        "topic_room_error": "Tsy tontonsa ny fahazoana efitrano ho lohahevitra. Tsy misy làlana mety hahazoana efitrano( %(numéro de pièce)s",
+        "unban": "Avereno ny mpampiasa manana ID nomena",
+        "unflip": "Manisy ┬──┬ ノ( ゜-゜ノ) amin'ny hafatra an-tsoratra tsotra",
+        "unholdcall": "Esory ny antso ao amin'ny efitrano misy ankehitriny",
+        "unignore": "Atsaharo ny tsy firaharahiana mpampiasa iray, mampiseho ny hafany mandroso",
+        "unignore_dialog_description": "Tsy miraharaha intsony ianao%(userId)s",
+        "unignore_dialog_title": "Mpampiasa tsy noraharahiana",
+        "unknown_command": "Baiko tsy fantatra",
+        "unknown_command_button": "Alefaso ho hafatra",
+        "unknown_command_detail": "Didy tsy fantatra:%(commandText)s",
+        "unknown_command_help": "Azonao ampiasaina<code> /Vonjeo</code> mitanisa baiko misy. Te handefa an'ity ho hafatra ve ianao?",
+        "unknown_command_hint": "Soso-kevitra: Manomboha amin&#39;ny hafatrao<code> //</code> hanombohana azy amin'ny slash.",
+        "upgraderoom": "Manatsara efitrano iray ho Tonga moderina vaovao",
+        "upgraderoom_permission_error": "Tsy manana ny fahazoan-dàlana ilaina hahafahana mampiasa ity fibaikona ity ianao.",
+        "usage": "Fampiasana",
+        "view": "Mijery efitrano misy adiresy nomena",
+        "whois": "Mampiseho fampahalalana momba ny mpampiasa iray"
+    },
+    "space": {
+        "add_existing_room_space": {
+            "create": "Te-hanampy efitrano vaovao kosa?",
+            "create_prompt": "Mamorona efitrano vaovao",
+            "dm_heading": "Hafatra mivantana",
+            "error_heading": "Tsy ny voafantina rehetra no nampiana",
+            "progress_text": {
+                "one": "Manampy efitrano...",
+                "other": "Manampy efitrano... (%(progress)s ivelan'ny%(count)s )"
+            },
+            "space_dropdown_label": "Fantenana habakabaka",
+            "space_dropdown_title": "Ampio efitrano misy",
+            "subspace_moved_note": "Nifindra ny fanampiana toerana."
+        },
+        "add_existing_subspace": {
+            "create_button": "Mamorona toerana vaovao",
+            "create_prompt": "Maniry hanampy toerana malalaka vaovao ?",
+            "filter_placeholder": "Mitadiava toerana malalaka",
+            "space_dropdown_title": "Ampio toerana malalaka efa misy"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "Jereo ny fandaharam-potoanan'ny efitrano (devtools)",
+            "explore": "Tsidiho ny efitrano",
+            "home": "Trano malalaka",
+            "manage_and_explore": "Mitantana sy mikaroka efitrano",
+            "options": "Safidy habakabaka"
+        },
+        "failed_load_rooms": "Tsy nahomby ny fampidirana ny lisitry ny efitrano.",
+        "failed_remove_rooms": "Tsy nesorina ny efitrano sasany. Andramo indray rehefa afaka kelikely",
+        "incompatible_server_hierarchy": "Ny mpizaranao dia tsy manohana ny fampisehoana an-tanan-tohatra.",
+        "invite": "Asao ny olona",
+        "invite_description": "Asao amin'ny imailaka na solon'anarana",
+        "invite_link": "Zarao ny rohy fanasana",
+        "joining_space": "Nandray anjara",
+        "landing_welcome": "Tongasoa eto<name/>",
+        "leave_dialog_action": "Avelao ny toerana",
+        "leave_dialog_description": "Efa handalana ny iala ianao<spaceName/> .",
+        "leave_dialog_only_admin_room_warning": "Ianao irery no mpitantana ny efitrano na habakabaka sasany tianao hialana. Ny fandaozana azy ireo dia hamela azy ireo tsy hisy admin.",
+        "leave_dialog_only_admin_warning": "Ianao irery no mpitantana an'ity habakabaka ity. Ny fandaozana azy dia midika fa tsy misy mahafehy azy.",
+        "leave_dialog_option_all": "Mialà amin'ny efitrano rehetra",
+        "leave_dialog_option_intro": "Te hiala amin'ireo efitrano amin'ity toerana ity ve ianao?",
+        "leave_dialog_option_none": "Alaio ary aza asiana tavela",
+        "leave_dialog_option_specific": "Amelao kely",
+        "leave_dialog_public_rejoin_warning": "Tsy afaka miditra indray ianao raha tsy asaina indray.",
+        "leave_dialog_title": "Fialan-tsasatra%(spaceName)s",
+        "mark_suggested": "Mariho araka ny soso-kevitra",
+        "no_search_result_hint": "Mety te hanandrana fikarohana hafa ianao na hanamarina raha misy diso.",
+        "preferences": {
+            "sections_section": "Fizarana aseho",
+            "show_people_in_space": "Manangona ny resakao miaraka amin'ny mpikambana amin'ity habaka ity. Ny famonoana an'ity dia hanafina ireo chat ireo tsy ho hitany masonao%(spaceName)s ."
+        },
+        "room_filter_placeholder": "Mitadiava efitrano",
+        "search_children": "karohy%(spaceName)s",
+        "search_placeholder": "Karoka anarana sy famaritana",
+        "select_room_below": "Misafidiana efitrano iray eto ambany aloha",
+        "share_public": "Zarao ny toerana misy anao",
+        "suggested": "Soso-kevitra",
+        "suggested_tooltip": "Ity efitrano ity dia soso-kevitra ho toerana tsara hidirana",
+        "title_when_query_available": "Vokatra",
+        "title_when_query_unavailable": "Efitrano sy habakabaka",
+        "unmark_suggested": "Mariho fa tsy soso-kevitra",
+        "user_lacks_permission": "Tsy manana alalana ianao"
+    },
+    "space_settings": {
+        "title": "Fikirana -%(spaceName)s"
+    },
+    "spaces": {
+        "error_no_permission_add_room": "Tsy manana alalana hanampy efitrano amin'ity habaka ity ianao",
+        "error_no_permission_add_space": "Tsy manana alalana hanisy habaka amin'ity habaka ity ianao",
+        "error_no_permission_create_room": "Tsy manana alalana hamorona efitrano vaovao amin'ity habaka ity ianao",
+        "error_no_permission_invite": "Tsy manana alalana hanasa olona ho amin'ity habaka ity ianao"
+    },
+    "spotlight": {
+        "public_rooms": {
+            "network_dropdown_add_dialog_description": "Ampidiro ny anaran'ny mpizara vaovao tianao hojerena.",
+            "network_dropdown_add_dialog_placeholder": "Anaran'ny mpizara",
+            "network_dropdown_add_dialog_title": "Manampia mpizara vaovao",
+            "network_dropdown_add_server_option": "Ampio mpizara vaovao…",
+            "network_dropdown_available_invalid": "Tsy hita ity mpizara ity na ny lisitry ny efitranony",
+            "network_dropdown_available_invalid_forbidden": "Tsy mahazo mijery ny lisitry ny efitranon'ity mpizara ity ianao",
+            "network_dropdown_available_valid": "Toa tsara",
+            "network_dropdown_remove_server_adornment": "Esory ny mpizara “%(roomServer)s ”",
+            "network_dropdown_required_invalid": "Ampidiro anarana mpizara",
+            "network_dropdown_selected_label": "Asehoy:",
+            "network_dropdown_selected_label_instance": "Asehoy:%(instance)s efitra (%(server)s )",
+            "network_dropdown_your_server_description": "Ny mpizara anao"
+        }
+    },
+    "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Raha tsy hitanao hoe iza no tadiavinao dia alefaso amin'izy ireo ny rohy fanasanao.",
+        "cant_find_room_helpful_hint": "Raha tsy hitanao ny efitrano tadiavinao dia mangataha fanasana na mamorona efitrano vaovao.",
+        "copy_link_text": "Adikao ny rohy fanasana",
+        "count_of_members": {
+            "one": "%(count)s mpikambana",
+            "other": "%(count)sIreo mpikambana"
+        },
+        "create_new_room_button": "Mamorona efitrano vaovao",
+        "failed_querying_public_rooms": "Tsy nahavita nanontany efitranom-bahoaka",
+        "failed_querying_public_spaces": "Tsy nahavita nanontany ny habaka hoany daholobe",
+        "group_chat_section_title": "Safidy hafa",
+        "heading_with_query": "Ampiasao %(query)s ny hikaroka",
+        "heading_without_query": "Hitady ny",
+        "join_button_text": "anjara%(roomAddress)s",
+        "keyboard_scroll_hint": "Ampiasao<arrows/> ny horonan-taratasy",
+        "other_rooms_in_space": "Efitrano hafa ao%(spaceName)s",
+        "public_rooms_label": "Efitranom-bahoaka",
+        "public_spaces_label": "Toeram-bahoaka",
+        "recent_searches_section_title": "Fikarohana vao haingana",
+        "recently_viewed_section_title": "Nojerena vao haingana",
+        "remove_filter": "Esory ny sivana fikarohana momba ny%(filter)s",
+        "result_may_be_hidden_privacy_warning": "Mety hafenina hoany fiainana manokana ny valiny sasany",
+        "result_may_be_hidden_warning": "Mety hafenina ny valiny sasany",
+        "search_dialog": "Fikarohana Dialog",
+        "spaces_title": "Toerana misy anao",
+        "start_group_chat_button": "Manomboha resaka vondrona"
+    },
+    "stickers": {
+        "empty": "Tsy misy ny fonosana misy anao amin'izao fotoana izao",
+        "empty_add_prompt": "Ampio Ankehitriny"
+    },
+    "terms": {
+        "column_document": "Tahirin-kevitra",
+        "column_service": "Servisy",
+        "column_summary": "FAMINTINANA",
+        "identity_server_no_terms_description_1": "Ity hetsika ity dia mitaky ny fidirana aminy mpizara famantarana ny ara-pototra<server /> hanamarina adiresy mailaka na laharan-telefaona, saingy tsy manana fepetrany serivisy ny mpizara.",
+        "identity_server_no_terms_description_2": "Tohizo ihany raha matoky ny tompon'ny mpizara ianao.",
+        "identity_server_no_terms_title": "Tsy manana fepetra momba ny serivisy ny mpizara famantarana",
+        "inline_intro_text": "Hanaiky<policyLink /> hanohy:",
+        "integration_manager": "Ampiasao bots, tetezana, widgets ary fonosana sticker",
+        "intro": "Mba hanohizana dia mila manaiky ny fepetran'ity serivisy ity ianao.",
+        "summary_identity_server_1": "Mitadiava hafa amin'ny finday na imailaka",
+        "summary_identity_server_2": "Aoka ho hita ao aminy finday na imailaka",
+        "tac_button": "Avereno jerena ny fepetra sy fifanarahana",
+        "tac_description": "Hanohy ny fampiasana ny%(homeserverDomain)s homeserver ianao dia tsy maintsy mijery sy manaiky ny fepetra sy fifanarahana.",
+        "tac_title": "Fepetra sy fifanarahana",
+        "tos": "Fepetra momba ny serivisy"
+    },
+    "theme": {
+        "light_high_contrast": "Mifanohitra be loatra",
+        "match_system": "Rafitra lalao"
+    },
+    "thread_view_back_action_label": "Miverina amin'ny kofehy",
+    "threads": {
+        "all_threads": "Ny zanaka rehetra",
+        "all_threads_description": "Mampiseho ny kofehy rehetra avy amin'ny efitrano ankehitriny",
+        "count_of_reply": {
+            "one": "%(count)smamaly",
+            "other": "%(count)svaliny"
+        },
+        "error_start_thread_existing_relation": "Tsy afaka mamorona kofehy avy amin'ny hetsika misy fifandraisana efa misy",
+        "my_threads": "Zanako",
+        "my_threads_description": "Asehoy ny zanako rehetra ianao nandray anjara Zao amin'ny ",
+        "open_thread": "Sokafy kofehy",
+        "show_thread_filter": "Asehoy:"
+    },
+    "threads_activity_centre": {
+        "header": "Hetsika kofehy"
+    },
+    "time": {
+        "about_day_ago": "Tokony ho iray andro izay",
+        "about_hour_ago": "Manakaiky adin'iray Teo ho eo",
+        "about_minute_ago": "Misy iray minitra Teo izay",
+        "date_at_time": "% (Mihaona amin'ny % (fotoana",
+        "few_seconds_ago": "Segondra vitsy lasa",
+        "hours_minutes_seconds_left": "% (ora) s h%(minitra) s m%(segondra) s ny tavela",
+        "in_about_day": "Anatiny iray andro eo ho eo",
+        "in_about_hour": "Adiny iray eo ho eo",
+        "in_about_minute": "Afaka iray minitra eo ho eo",
+        "in_few_seconds": "Afaka segondra vitsy",
+        "in_n_days": "%(num) s andro manomboka izao",
+        "in_n_hours": "% (num) sAnatiny ora vitsivitsy",
+        "in_n_minutes": "% (Num) sAfaka minitra vitsy",
+        "left": "%(timeRemaining) sAnkavia",
+        "minutes_seconds_left": "% (minutes) sm% (segondra) s ny ambiny",
+        "n_days_ago": "%(num)s Andro vitsivitsy izay",
+        "n_hours_ago": "%(num)sOra maromaro",
+        "n_minutes_ago": "%(Num)s Minitra vitsivitsy izay",
+        "seconds_left": "% (secondes) s ny ambiny",
+        "short_days": "% (Sandales d",
+        "short_days_hours_minutes_seconds": "%(andro) s d % (ora) sh% (minitra) s m % (segondra)s s",
+        "short_hours": "%(Sanda h",
+        "short_hours_minutes_seconds": "% (ora) s h % (minitra) s m % (segondra) s s",
+        "short_minutes": "%(valeurs m",
+        "short_minutes_seconds": "%(minitra) sm% (segondra) ss",
+        "short_seconds": "%(Sandas S"
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "Atsaharo ny kofehy valin-teny",
+            "external_url": "URL loharano",
+            "open_in_osm": "Misokatra amin'ny OpenStreetMap",
+            "report": "TATITRA",
+            "resent_unsent_reactions": "Alefaso indray%(unsentCount)s fanehoan-kevitra",
+            "show_url_preview": "Asehoy mialoha",
+            "view_related_event": "Jereo ny hetsika mifandraika",
+            "view_source": "Jereo ny fototra ipoirany"
+        },
+        "creation_summary_dm": "%(creator)snamorona ity DM ity.",
+        "creation_summary_room": "%(creator)snamorona sy nanamboatra ny efitrano.",
+        "disambiguated_profile": "%(displayName)s(%(matrixId)s )",
+        "download_action_decrypting": "Decryptering",
+        "download_action_downloading": "Misintona",
+        "edits": {
+            "tooltip_label": "Namboarina tamin'ny%(Rendez-vous . Kitiho raha hijery ny fanovana.",
+            "tooltip_sub": "Kitiho raha hijery ny fanovana",
+            "tooltip_title": "Namboarina tamin'ny%(Rendez-vous"
+        },
+        "error_no_renderer": "Tsy azo naseho ity hetsika ity",
+        "error_rendering_message": "Mety tsy afaka mampiditra ity hafatra ity",
+        "historical_messages_unavailable": "Tsy hitanao ny hafatra taloha",
+        "in_room_name": "Ao amin'ny<fort>%(pièces</strong>",
+        "io.element.widgets.layout": "%(senderName)snanavao ny firafitry ny efitrano",
+        "late_event_separator": "Nalefa tany am-boalohany%(dateTime)s",
+        "load_error": {
+            "no_permission": "Nanandrana nametraka teboka iray manokana tao amin'ny fandaharam-potoanan'ity efitrano ity ianao, saingy tsy nahazo alalana hijery ilay hafatra resahina ianao.",
+            "title": "Tsy nahomby ny fametahana ny toeran'ny fandaharam-potoana",
+            "unable_to_find": "Nanandrana nampiditra teboka iray manokana tao amin'ny fandaharam-potoanan'ity efitrano ity, saingy tsy nahita izany."
+        },
+        "m.audio": {
+            "error_downloading_audio": "Hadisoana tamin'ny fampidinana feo",
+            "error_processing_audio": "Hadisoana tamin'ny fanodinana hafatra audio",
+            "error_processing_voice_message": "Hadisoana tamin'ny fanodinana hafatra feo",
+            "unnamed_audio": "Audio tsy voatonona anarana"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Jereo ny toerana mivantana"
+        },
+        "m.call": {
+            "video_call_ended": "Tapitra ny antso an-tsary",
+            "video_call_started": "Antso mivantana aminy fahita lavitra anomboka ao anatin'ny % (nom de la pièce) s.",
+            "video_call_started_text": "%(name)snanomboka antso an-tsary",
+            "video_call_started_unsupported": "Antso video anomboka ao anatin'ny%(nom de la pièce) s. (tsy tohanany toheram-pivohizana misy anao)"
+        },
+        "m.call.hangup": {
+            "dm": "Nitsahatra ny antso"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "Valiana an-kafa",
+            "call_back_prompt": "Amerina hiantso",
+            "declined": "Nolavina ny antso",
+            "failed_connect_media": "Tsy afaka mampifandray ny haino aman-jery",
+            "failed_connection": "Tsy nahomby ny fifandraisana",
+            "failed_opponent_media": "Tsy afaka natomboka ny fakan-tsary na ny mikrô ny fitaovan'izy ireo",
+            "missed_call": "Antso tsy hita",
+            "no_answer": "Tsy misy valiny",
+            "unknown_error": "Nisy hadisoana tsy fantatra nitranga",
+            "unknown_failure": "Tsy fahombiazana tsy fantatra:%(reason)s",
+            "unknown_state": "Ny antso dia ao amin'ny fanjakana tsy fantatra!",
+            "video_call": "%(senderName) nametraka antso an-tsary.",
+            "video_call_unsupported": "%(senderName)snanao antso an-tsary (tsy tohanan'ity toeram-pivohizana ity)",
+            "voice_call": "%(senderName)snametraka antso an-tariby.",
+            "voice_call_unsupported": "%(senderName)snametraka antso an-tariby. (tsy tohanan'ity toeram-pivohizana ity)"
+        },
+        "m.file": {
+            "error_decrypting": "Nisy olana teo am-pamakiana ny famaha",
+            "error_invalid": "Rakitra tsy mety %(Supplémentaires"
+        },
+        "m.image": {
+            "error": "Tsy afaka mampiseho sary noho ny fahadisoana",
+            "error_decrypting": "Nisy olana teo am-pamakiana ny sary",
+            "error_downloading": "Hadisoana tamin'ny fampidinana sary",
+            "sent": "%(senderDisplayName)sandefa sary.",
+            "show_image": "Asehoy ny sary"
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "%(des noms te hanamarina",
+            "you_started": "Nandefa fangatahana fanamarinana ianao"
+        },
+        "m.location": {
+            "full": "%(senderName)snizara ny toerana misy azy ireo",
+            "location": "Nizara toerana: ",
+            "self_location": "Nizara ny misy azy ireo: "
+        },
+        "m.poll": {
+            "count_of_votes": {
+                "one": "%(count)sfifidianana",
+                "other": "%(count)svato"
+            }
+        },
+        "m.poll.end": {
+            "ended": "Namarana ny",
+            "sender_ended": "%(nom de l'expéditeur)s dia namarana ny fitsapan-kevitra"
+        },
+        "m.poll.start": "%(senderName)snanomboka ny fitsapan-kevitra -%(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "%(senderDisplayName)snanova ny avatar.",
+            "changed_img": "%(senderDisplayName)sniova ny avatar ho<img/>",
+            "lightbox_title": "%(senderDisplayName)sniova ny avatar ho%(roomName)s",
+            "removed": "%(senderDisplayName)snesorina ny avatar."
+        },
+        "m.room.canonical_alias": {
+            "alt_added": {
+                "one": "%(senderName)snampiana adiresy hafa%(addresses)s ho an&#39;ity ity.",
+                "other": "%(senderName)snampiana ireo adiresy hafa%(addresses)s ho an&#39;ity ity."
+            },
+            "alt_removed": {
+                "one": "%(senderName)snesorina ny adiresy hafa%(addresses)s ho an&#39;ity ity.",
+                "other": "%(senderName)snesorina ireo adiresy hafa%(addresses)s ho an&#39;ity ity."
+            },
+            "changed": "%(senderName)snanova ny adiresin'ity efitrano ity aho.",
+            "changed_alternative": "%(senderName)snanova ny adiresy hafa hoan'ity efitrano ity.",
+            "changed_main_and_alternative": "%(senderName)sovay ny adiresy fototra sy hafa hoan'ity efitrano ity.",
+            "removed": "%(senderName)sfafao ny adiresin'ity efitrano manokana ity.",
+            "set": "%(senderName)sapetraho ny adiresin'ity efitrano ity%(address)s ."
+        },
+        "m.room.create": {
+            "continuation": "Tohin'ny resaka hafa ity efitrano ity.",
+            "see_older_messages": "Kitiho eto raha hijery hafatra tranainy.",
+            "unknown_predecessor": "Tsy hita ny dikan-teny taloha amin'ity efitrano ity ( ID efitrano:%(roomId)s ), ary tsy nomena via_servers izahay hitadiavana azy.",
+            "unknown_predecessor_guess_server": "Tsy hita ny kinova tranainy an'ity efitrano ity ( ID efitrano:%(roomId)s ), ary tsy nomena via_servers izahay hitadiavana azy. Mety ho azo atao ny maminavina ny mpizara avy amin'ny ID efitrano. Raha te hanandrana ianao dia tsindrio ity rohy ity:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "Tsy noraharahiana ny fikasana hanafoana ny fanafenana",
+            "disabled": "Tsy mandeha ny encryption",
+            "enabled": "Ny hafatra ao amin'ity efitrano ity dia misy encryption. Rehefa miditra ny olona dia azonao atao ny manamarina azy ireo ao amin'ny mombamomba azy, tsindrio fotsiny ny sarin'ny mombamomba azy.",
+            "enabled_dm": "Ny hafatra eto dia misy encryption avy hatrany. manamarina%(displayName)s ao amin'ny mombamomba azy - tsindrio ny sarin'ny mombamomba azy.",
+            "enabled_local": "Ny hafatra ao amin'ity chat ity dia hatao encryption.",
+            "parameters_changed": "Misy masontsivana fanafenana novana.",
+            "unsupported": "Ny fanafenana ampiasain'ity efitrano ity dia tsy tohanana."
+        },
+        "m.room.guest_access": {
+            "can_join": "%(senderDisplayName)sdia namela ny vahiny hanatevin-daharana ny efitrano.",
+            "forbidden": "%(senderDisplayName)sdia nanakana ny vahiny tsy hiditra ao amin'ny efitrano.",
+            "unknown": "%(senderDisplayName)sniova ny fidirana amin'ny vahiny%(rule)s"
+        },
+        "m.room.history_visibility": {
+            "invited": "%(senderName)snanao ny tantaran'ny efitrano ho avy ho hitan'ny mpikambana rehetra ao amin'ny efitrano, manomboka amin'ny fotoana nanasana azy ireo.",
+            "joined": "%(senderName)snanao ny tantarany efitrano ho avy ho hitany mpikambana rehetra ao amin'ny efitrano, nanomboka taminy fotoana nidirany.",
+            "shared": "%(senderName)snatao ho hitany mpikambana rehetra ao amin'ny efitrano ny tantarany efitrano ho avy.",
+            "unknown": "%(senderName)snanao ny tantarany efitrano ho avy ho hitan'ny tsy fantatra (%(visibility)s ).",
+            "world_readable": "%(senderName)snatao ho hitany olona rehetra ny tantarany efitrano ho avy."
+        },
+        "m.room.join_rules": {
+            "invite": "%(senderDisplayName)snanao ny efitra fanasana ihany.",
+            "knock": "%(senderDisplayName)snanova ny fitsipika mikambana mba hangataka ny hiditra.",
+            "public": "%(senderDisplayName)snanao ny efitrano hoan'ny rehetra izay mahafantatra ny rohy.",
+            "restricted": "%(senderDisplayName)sniova izay afaka miditra amin'ity efitrano ity.",
+            "restricted_settings": "%(senderDisplayName)snanova izay afaka miditra amin'ity efitrano ity.<a> Jereo ny fanovana</a>.",
+            "unknown": "%(senderDisplayName)snanova ny fitsipika mitambatra ho%(rule)s"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "%(targetName)snanaiky ny fanasana ho%(displayName)s",
+            "accepted_invite": "%(targetName)snanaiky fanasana",
+            "ban": "%(senderName)svoarara%(targetName)s",
+            "ban_reason": "%(senderName)svoarara%(targetName)s :%(reason)s",
+            "change_avatar": "%(senderName)snanova ny sary pahamantarn'azy",
+            "change_name": "%(oldDisplayName)snovaina ho%(displayName)s",
+            "change_name_avatar": "%(oldDisplayName)snanova ny anarana asehony sy ny sarin'ny pahafantaran' azy",
+            "invite": "%(senderName)sHivahiny%(targetName)s",
+            "join": "%(targetName)snanatevin-daharana ny efitrano",
+            "kick": "%(senderName)snesorina%(targetName)s",
+            "kick_reason": "%(senderName)snesorina%(targetName)s :%(reason)s",
+            "left": "%(targetName)snandao ny efitrano",
+            "left_reason": "%(targetName)snandao ny efitrano:%(reason)s",
+            "no_change": "%(senderName)stsy nanao fiovana",
+            "reject_invite": "%(targetName)snandà ny fanasana",
+            "remove_avatar": "%(senderName)snesoriny ny sarin&#39;ny pahamantarn'azy",
+            "remove_name": "%(senderName)snesorina ny anaran&#39;izy ireo (%(oldDisplayName)s )",
+            "set_avatar": "%(senderName)smametraka sary mombamomba azy",
+            "set_name": "%(senderName)sfarito amin'ny%(displayName)s",
+            "unban": "%(senderName)stsy voarara%(targetName)s",
+            "withdrew_invite": "%(senderName)sniala%(targetName)s ny fanasana",
+            "withdrew_invite_reason": "%(senderName)snesorina%(targetName)s ny fanasana:%(reason)s"
+        },
+        "m.room.name": {
+            "change": "%(senderDisplayName)snanova ny anaran'ny efitrano tamin'ny%(oldRoomName)s ny%(newRoomName)s .",
+            "remove": "%(senderDisplayName)snesorina ny anaran&#39;ny efitrano.",
+            "set": "%(senderDisplayName)sniova ny anaran'ny efitrano ho%(roomName)s ."
+        },
+        "m.room.pinned_events": {
+            "changed": "%(senderName)snanova ny hafatra voapetaka hoan'ny efitrano.",
+            "changed_link": "%(senderName)sovay ny<a> hafatra voapetaka</a> hoan'ny efitrano.",
+            "pinned": "%(senderName)snanisy hafatra tamin'ity efitrano ity. Jereo ny hafatra voapetaka rehetra.",
+            "pinned_link": "%(senderName)smipaingotra<a> hafatra</a> mankany amin'ity efitrano ity. Jereo daholo<b> hafatra voapetaka</b> .",
+            "unpinned": "%(senderName)snesorina ny hafatra avy amin'ity efitrano ity. Jereo ny hafatra voapetaka rehetra.",
+            "unpinned_link": "%(senderName)snesorina<a> hafatra</a> avy amin'ity efitrano ity. Jereo daholo<b> hafatra voapetaka</b> ."
+        },
+        "m.room.power_levels": {
+            "changed": "%(senderName)snanova ny ahavon'ny herin'ny%(powerLevelDiffText)s .",
+            "user_from_to": "%(userId)snanomboka amin'ny%(fromPowerLevel)s ny%(toPowerLevel)s"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 Voarara tsy handray anjara ny mpizara rehetra! Tsy azo ampiasaina intsony ity efitrano ity.",
+            "changed": "%(senderDisplayName)snanova ny ACL mpizara hoan'ity efitrano ity.",
+            "set": "%(senderDisplayName)sfarito ny ACL mpizara ho an'ity efitrano ity."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "%(senderName)snanafoana ny fanasana ho%(targetDisplayName)s hiditra ao amin'ny efitrano.",
+            "sent": "%(senderName)smandefasa fanasana ho%(targetDisplayName)s hanatevin-daharana ny efitrano."
+        },
+        "m.room.tombstone": "%(senderDisplayName)snanatsara ity efitrano ity.",
+        "m.sticker": "%(senderDisplayName)snandefa rakin-tsary.",
+        "m.video": {
+            "error_decrypting": "Nisy olana teo am-pamakiana ny lahatsary"
+        },
+        "m.widget": {
+            "added": "%(widgetName)sWidget nampidirina avy%(senderName)s",
+            "jitsi_ended": "Rakin-tsary mpivoriana nifarana tamin'ny%(senderName)s",
+            "jitsi_join_right_prompt": "Miaraha aminy fihaonambe avy amin'ny karatra fampahalalana an'ny efitrano eo ankavanana",
+            "jitsi_join_top_prompt": "Miaraha amin'ny fihaonambe eo an-tampon'ity efitrano ity",
+            "jitsi_started": "Fihaonambe an-tsary natomboka tamin'ny %(nom de l'expéditeur)s",
+            "jitsi_updated": "Rakin-tsary mpivoriana nohavaozin'ny%(senderName)s",
+            "modified": "%(widgetName)swidget novaina avy%(senderName)s",
+            "removed": "%(widgetName)swidget nesorina avy%(senderName)s"
+        },
+        "mab": {
+            "collapse_reply_chain": "Atsaharo ny teny nindramina",
+            "copy_link_thread": "Adikao ny rohy mankany amin'ny kofehy",
+            "expand_reply_chain": "Hanitatra ny teny nindramina",
+            "label": "Asa atao amin'ny hafatra",
+            "view_in_room": "Jereo ao amin'ny"
+        },
+        "message_timestamp_received_at": "Voaray tamin'ny:%(dateTime)s",
+        "message_timestamp_sent_at": "Nalefa tamin'ny:%(dateTime)s",
+        "mjolnir": {
+            "changed_rule_glob": "%(senderName)snanavao ny fitsipika fandrarana izay nifanaraka%(oldGlob)s mifanaraka%(newGlob)s hoan'ny%(reason)s",
+            "changed_rule_rooms": "%(senderName)snanova fitsipika mandràra ny fampifanarahana ny efitrano%(oldGlob)s mifanaraka%(newGlob)s hoan'ny%(reason)s",
+            "changed_rule_servers": "%(senderName)snanova fitsipika izay mandrara ny mpizara mifanentana%(oldGlob)s mifanaraka%(newGlob)s hoan'ny%(reason)s",
+            "changed_rule_users": "%(senderName)snanova fitsipika mandrara ny mpampiasa mifanandrify%(oldGlob)s mifanaraka%(newGlob)s hoan'ny%(reason)s",
+            "created_rule": "%(senderName)snamorona fitsipika fandrarana mifanandrify%(glob)s hoan'ny%(reason)s",
+            "created_rule_rooms": "%(senderName)snamorona fitsipika mandràra ny efitrano mifanandrify%(glob)s Hoan'ny%(reason)s",
+            "created_rule_servers": "%(senderName)snamorona fitsipika mandrara ny mpizara mifanentana%(glob)s hoan'ny%(reason)s",
+            "created_rule_users": "%(senderName)snamorona fitsipika mandràra ny mpampiasa mifanandrify%(glob)s Hoan'ny%(reason)s",
+            "message_hidden": "Tsy niraharaha an'io mpampiasa io ianao, noho izany dia miafina ny hafatr'izy ireo. <a>Asehoy ihany.</a>",
+            "removed_rule": "%(senderName)snesorina ny fitsipika fandrarana mifanandrify%(glob)s",
+            "removed_rule_rooms": "%(senderName)snesorina ny fitsipika mandrara ny efitrano mifanentana%(glob)s",
+            "removed_rule_servers": "%(senderName)snesorina ny fitsipika mandràra ireo mpizara mifanentana%(glob)s",
+            "removed_rule_users": "%(senderName)snesorina ny fitsipika mandrara ny mpampiasa mifanentana%(glob)s",
+            "updated_invalid_rule": "%(senderName)snanavao fitsipika fandraràna tsy mety",
+            "updated_rule": "%(senderName)snohavaozina mifanaraka amin&#39;ny fitsipika fandrarana%(glob)s Hoan'ny%(reason)s",
+            "updated_rule_rooms": "%(senderName)snohavaozina ny fitsipika mandrara ny efitrano mifanentana%(glob)s Hoan'ny%(reason)s",
+            "updated_rule_servers": "%(senderName)snohavaozina ny fitsipika mandrara ireo mpizara mifanentana%(glob)s Hoan'ny%(reason)s",
+            "updated_rule_users": "%(senderName)snohavaozina ny fitsipika mandrara ny mpampiasa mifanentana%(glob)s Hoan'ny%(reason)s"
+        },
+        "no_permission_messages_before_invite": "Tsy manana alalana hijery hafatra ianao talohan'ny nanasana anao.",
+        "no_permission_messages_before_join": "Tsy manana alalana hijery hafatra avy amin'ny alohan'ny nidiranao ianao.",
+        "pending_moderation": "Hafatra miandry ny fandrindrana",
+        "pending_moderation_reason": "Hafatra miandry famotehana:%(les raisons",
+        "reactions": {
+            "add_reaction_prompt": "Manampy fanehoan-kevitra",
+            "custom_reaction_fallback_label": "Fanehoan-kevitra manokana",
+            "label": "%(reactors)snaneho hevitra tamin'ny%(content)s"
+        },
+        "read_receipt_title": {
+            "one": "Hitan'ny%(count)s OLONA",
+            "other": "Hitan'ny%(count)s OLONA"
+        },
+        "read_receipts_label": "Vakio ny rosia",
+        "redacted": {
+            "tooltip": "Voafafa ny hafatra tamin'ny%(Rendez-vous"
+        },
+        "redaction": "Hafatra nofafany avy aminy%(name)s",
+        "reply": {
+            "error_loading": "Tsy afaka nampiditra hetsika izay novaliana, na tsy misy izany na tsy manana alalana hijery azy ianao.",
+            "in_reply_to": "<a>Ho valin'ny</a><pilule>",
+            "in_reply_to_for_export": "Ho valin'ny<a> ity hafatra ity</a>"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "Efa hoentina any amin'ny tranokalan'ny antoko fahatelo ianao mba hahafahanao manamarina ny kaontinao hampiasaina%(integrationsUrl)s .Te hanohy ve ianao?",
+            "dialog_title": "Ampio ny fampidirana"
+        },
+        "self_redaction": "Voafafa ny hafatra",
+        "send_state_encrypting": "Manidy ny hafatrao…",
+        "send_state_failed": "Tsy nahomby ny fandefasana",
+        "send_state_sending": "Mandefa ny hafatrao…",
+        "send_state_sent": "Nalefa ny hafatrao",
+        "summary": {
+            "banned": {
+                "one": "voarara",
+                "other": "voarara%(count)s fotoana"
+            },
+            "banned_multiple": {
+                "one": "Voarara",
+                "other": "Voarara%(count)s fotoana"
+            },
+            "changed_avatar": {
+                "one": "%(oneUser)snanova ny sary mombamomba azy",
+                "other": "%(oneUser)snanova ny sary mombamomba azy%(count)s fotoana"
+            },
+            "changed_avatar_multiple": {
+                "one": "%(severalUsers)snanova ny sarin'ny mombamomba azy",
+                "other": "%(severalUsers)snanova ny sarin'ny mombamomba azy%(count)s fotoana"
+            },
+            "changed_name": {
+                "one": "%(oneUser)sniova ny anarany",
+                "other": "%(oneUser)sniova ny anarany%(count)s fotoana"
+            },
+            "changed_name_multiple": {
+                "one": "%(severalUsers)sniova ny anarany",
+                "other": "%(severalUsers)sniova ny anarany%(count)s fotoana"
+            },
+            "format": "%(nameList)s %(transitionList)s",
+            "hidden_event": {
+                "one": "%(oneUser)snandefa hafatra miafina",
+                "other": "%(oneUser)snadefa%(count)s hafatra miafina"
+            },
+            "hidden_event_multiple": {
+                "one": "%(severalUsers)snandefa hafatra miafina",
+                "other": "%(severalUsers)snalefa%(count)s hafatra miafina"
+            },
+            "invite_withdrawn": {
+                "one": "%(oneUser)snesorina ny fanasany",
+                "other": "%(oneUser)snesorina ny fanasany%(count)s fotoana"
+            },
+            "invite_withdrawn_multiple": {
+                "one": "%(severalUsers)snesorina ny fanasany",
+                "other": "%(severalUsers)snesorina ny fanasany%(count)s fotoana"
+            },
+            "invited": {
+                "one": "nasaina",
+                "other": "nasaina%(count)s isa"
+            },
+            "invited_multiple": {
+                "one": "Nasaina",
+                "other": "Nasaina%(count)s fotoana"
+            },
+            "joined": {
+                "one": "%(oneUser)snanatevin-daharana",
+                "other": "%(oneUser)snanatevin-daharana%(count)s fotoana"
+            },
+            "joined_and_left": {
+                "one": "%(oneUser)snikambana ary niala",
+                "other": "%(oneUser)snikambana ary niala%(count)s fotoana"
+            },
+            "joined_and_left_multiple": {
+                "one": "%(severalUsers)snikambana ary niala",
+                "other": "%(severalUsers)snikambana ary niala%(count)s fotoana"
+            },
+            "joined_multiple": {
+                "one": "%(severalUsers)snanatevin-daharana",
+                "other": "%(severalUsers)snanatevin-daharana%(count)s fotoana"
+            },
+            "kicked": {
+                "one": "nesorina",
+                "other": "nesorina%(count)s fotoana"
+            },
+            "kicked_multiple": {
+                "one": "nesorina",
+                "other": "nesorina%(count)s fotoana"
+            },
+            "left": {
+                "one": "%(oneUser)sAnkavia",
+                "other": "%(oneUser)sAnkavia%(count)s fotoana"
+            },
+            "left_multiple": {
+                "one": "%(severalUsers)sANKA",
+                "other": "%(severalUsers)sANKA%(count)s fotoana"
+            },
+            "no_change": {
+                "one": "%(oneUser)stsy nanao fiovana",
+                "other": "%(oneUser)stsy nanao fiovana%(count)s fotoana"
+            },
+            "no_change_multiple": {
+                "one": "%(severalUsers)stsy nanao fiovana",
+                "other": "%(severalUsers)stsy nanao fiovana%(count)s fotoana"
+            },
+            "pinned_events": {
+                "one": "%(oneUser)sniova ny<a> hafatra voapetaka</a> ho an&#39;ny efitrano",
+                "other": "%(oneUser)sniova ny<a> hafatra voapetaka</a> ho an&#39;ny efitrano%(count)s fotoana"
+            },
+            "pinned_events_multiple": {
+                "one": "%(severalUsers)sniova ny<a> hafatra voapetaka</a> ho an&#39;ny efitrano",
+                "other": "%(severalUsers)sniova ny<a> hafatra voapetaka</a> ho an&#39;ny efitrano%(count)s fotoana"
+            },
+            "redacted": {
+                "one": "%(oneUser)snesorina hafatra",
+                "other": "%(oneUser)snesorina%(count)s hafatra"
+            },
+            "redacted_multiple": {
+                "one": "%(severalUsers)snesorina hafatra",
+                "other": "%(severalUsers)snesorina%(count)s hafatra"
+            },
+            "rejected_invite": {
+                "one": "%(oneUser)snandà ny fanasan’izy ireo",
+                "other": "%(oneUser)snandà ny fanasan’izy ireo%(count)s fotoana"
+            },
+            "rejected_invite_multiple": {
+                "one": "%(severalUsers)snandà ny fanasan’izy ireo",
+                "other": "%(severalUsers)snandà ny fanasan’izy ireo%(count)s fotoana"
+            },
+            "rejoined": {
+                "one": "%(oneUser)sniala ary nitambatra indray",
+                "other": "%(oneUser)sniala ary nitambatra indray%(count)s fotoana"
+            },
+            "rejoined_multiple": {
+                "one": "%(severalUsers)sniala ary nitambatra indray",
+                "other": "%(severalUsers)sniala ary nitambatra indray%(count)s fotoana"
+            },
+            "server_acls": {
+                "one": "%(oneUser)sniova ny mpizara ACL",
+                "other": "%(oneUser)sniova ny mpizara ACL%(count)s fotoana"
+            },
+            "server_acls_multiple": {
+                "one": "%(severalUsers)sniova ny mpizara ACL",
+                "other": "%(severalUsers)sniova ny mpizara ACL%(count)s fotoana"
+            },
+            "unbanned": {
+                "one": "tsy voarara",
+                "other": "tsy voarara%(count)s fotoana"
+            },
+            "unbanned_multiple": {
+                "one": "tsy voarara",
+                "other": "tsy voarara%(count)s fotoana"
+            }
+        },
+        "thread_info_basic": "Avy amin'ny kofehy",
+        "typing_indicator": {
+            "more_users": {
+                "one": "%(names)sAry ny iray hafa manoratra…",
+                "other": "%(names)sSY%(count)s ny hafa manoratra…"
+            },
+            "one_user": "%(displayName)sdia manoratra",
+            "two_users": "%(names)sSY%(lastPerson)s mitendry…"
+        },
+        "undecryptable_tooltip": "Ity hafatra ity dia tsy azo decryption",
+        "url_preview": {
+            "close": "Akatona mialoha",
+            "show_n_more": {
+                "one": "FAMPISEHOANA%(count)s preview hafa",
+                "other": "FAMPISEHOANA%(count)s fijery hafa"
+            }
+        }
+    },
+    "truncated_list_n_more": {
+        "one": "",
+        "other": "SY%(count)s Bebe kokoa..."
+    },
+    "unsupported_server_description": "Ity mpizara ity dia mampiasa dikan-teny taloha any. Hiverina any amin'ny %(version)s mampiasa%(brand)s tsy misy hadisoana.",
+    "unsupported_server_title": "Tsy voatoana ny mpizara anao",
+    "update": {
+        "changelog": "Lisitra ny fanovahana",
+        "check_action": "Hamarino ny fanavaozana",
+        "checking": "Mijery fanavaozana…",
+        "downloading": "Misintona fanavaozana…",
+        "error_encountered": "Nisy hadisoana (%(errorDetail)s ).",
+        "error_unable_load_commit": "Tsy afaka mampiditra antsipirihany commit:%(msg)s",
+        "new_version_available": "Misy dikan-teny vaovao.<a> Fanavaozana izao.</a>",
+        "no_update": "Tsy misy fanavaozana azo.",
+        "release_notes_toast_title": "Inona ny vaovao",
+        "see_changes_button": "Inona ny vaovao?",
+        "toast_description": "Dikan-teny vaovao amin'ny %(marque dia misy",
+        "toast_title": "Vaovao farany%(brand)s",
+        "unavailable": "Tsy vonona"
+    },
+    "upload_failed_generic": "Ny rakitra&#39;%(fileName)s &#39; tsy afaka nampidirina.",
+    "upload_failed_size": "Ny rakitra %(fileName)s mihoatra ny fetrany haben'ny homeserver amin'ny fampiakarana",
+    "upload_failed_title": "Tsy nahomby ny fandefasana",
+    "upload_file": {
+        "cancel_all_button": "Foana daholo",
+        "error_file_too_large": "Ity rakitra ity dia<b> lehibe loatra</b> mampakatra. Ny fetran'ny habeny rakitra dia%(limit)s fa ity rakitra ity%(sizeOfThisFile)s .",
+        "error_files_too_large": "Ireo rakitra ireo dia<b> lehibe loatra</b> mampakatra. Ny fetran'ny habeny rakitra dia%(limit)s .",
+        "error_some_files_too_large": "Misy rakitra sasany<b> lehibe loatra</b> ampiakarina. Ny fetrany habeny rakitra dia%(limit)s .",
+        "error_title": "Hametraka hadisoana",
+        "title": "Mampiakatra rakitra",
+        "title_progress": "Mampiakatra rakitra (%(current)s ny%(total)s )",
+        "upload_all_button": "Ampidiro ny zava-drehetra",
+        "upload_n_others_button": {
+            "one": "Zavatra tsy%(count)s rakitra hafa",
+            "other": "Zavatra tsy%(count)s rakitra hafa"
+        }
+    },
+    "user_info": {
+        "admin_tools_section": "Fitaovan'i tomponandraikitra",
+        "ban_button_room": "Fandrarana ny efitrano",
+        "ban_button_space": "Fandrarana ny habakabaka",
+        "ban_room_confirm_title": "Fandrarana ny%(roomName)s",
+        "ban_space_everything": "Mandràra azy ireo amin'ny zavatra rehetra azoko atao",
+        "ban_space_specific": "Mandràra azy ireo amin'ny zavatra manokana azoko atao",
+        "deactivate_confirm_action": "Atsaharo ny mpampiasa",
+        "deactivate_confirm_description": "Ny fanesorana an'io mpampiasa io dia hampiditra azy ireo ary hisakana azy tsy hiditra indray. Fanampin'izany, handao ny efitrano rehetra misy azy izy ireo. Tsy azo averina io hetsika io. Tena tianao ve ny hanafoana ity mpampiasa ity?",
+        "deactivate_confirm_title": "Atsaharo ny mpampiasa?",
+        "demote_button": "Nankatoa",
+        "demote_self_confirm_description_space": "Tsy ho vitanao ny hanafoana an'io fanovana io rehefa midina ny tenanao ianao, raha ianao no mpampiasa manana tombontsoa farany aminy habaka dia tsy ho azo atao ny mahazo tombontsoa indray.",
+        "demote_self_confirm_room": "Tsy ho vitanao ny hanafoana an'io fanovana io rehefa midina ny tenanao ianao, raha ianao no mpampiasa nahazo tombontsoa farany tao amin'ny efitrano dia tsy ho azo atao ny mahazo tombontsoa indray.",
+        "demote_self_confirm_title": "Manetry tena ianareo?",
+        "disinvite_button_room": "Esory ny efitrano",
+        "disinvite_button_room_name": "Esory ny%(roomName)s",
+        "disinvite_button_space": "Esory ny habakabaka",
+        "error_ban_user": "Tsy nahavita nandrara ny mpampiasa",
+        "error_deactivate": "Tsy nahomby ny famafana ny mpampiasa",
+        "error_kicking_user": "Tsy tontonsa ny fanesorana ny mpampiasa",
+        "error_mute_user": "Tsy nahavita nampangina ny mpampiasa",
+        "error_revoke_3pid_invite_description": "Tsy afaka nanafoana ny fanasana. Mety sendra olana vonjimaika ny mpizara na tsy manana fahazoan-dàlana ampy hanesorana ny fanasana ianao.",
+        "error_revoke_3pid_invite_title": "Tsy nahavita nanafoana ny fanasana",
+        "ignore_confirm_description": "Hafenina avokoa ny hafatra sy ny fanasana avy amin'ity mpampiasa ity. Tena tianao tsy hiraharaha azy ireo ve ianao?",
+        "ignore_confirm_title": "Tsinotsinoavina%(user)s",
+        "invited_by": "Nasain'ny%(sender)s",
+        "jump_to_rr_button": "Hitsambikina hamaky ny rosia",
+        "kick_button_room": "Esory amin'ny efitrano",
+        "kick_button_room_name": "Esory amin'ny%(roomName)s",
+        "kick_button_space": "Esory amin'ny habakabaka",
+        "kick_button_space_everything": "Esory amin'izay rehetra azoko atao izy ireo",
+        "kick_space_specific": "Esory amin'ny zavatra manokana azoko atao izy ireo",
+        "kick_space_warning": "Zareo mbola afaka miditra amin'ny rehetra tsy admin izao.",
+        "promote_warning": "Tsy ho vitanao ny hanafoana an'io fanovana io satria mampiroborobo ny mpampiasa ianao mba hanana hery mitovy amin'ny tenanao.",
+        "redact": {
+            "confirm_button": {
+                "one": "Esory hafatra 1",
+                "other": "Esory%(count)s hafatra"
+            },
+            "confirm_description_1": {
+                "one": "Saika hanala ianao%(count)s hafatra avy amin'ny%(user)s . Hanala azy ireo mandrakizay ho an'ny rehetra ao amin'ny resaka izany. Te hanohy ve ianao?",
+                "other": "Saika hanala ianao%(count)s hafatra avy amin'ny%(user)s . Hanala azy ireo mandrakizay ho an'ny rehetra ao amin'ny resaka izany. Te hanohy ve ianao?"
+            },
+            "confirm_description_2": "Ho an'ny hafatra marobe dia mety haka fotoana kely izany. Azafady mamelombelona ny mpanjifanao mandritra izany fotoana izany.",
+            "confirm_keep_state_explainer": "Esory ny mari-pamantarana raha te hanaisotra ny hafatra rafitra amin'ity mpampiasa ity ianao (ohatra ny fanovana ny maha-mpikambana, ny fanovana ny mombamomba…)",
+            "confirm_keep_state_label": "Tehirizo ny hafatra rafitra",
+            "confirm_title": "Esory ny hafatra vao haingana amin'ny%(user)s",
+            "no_recent_messages_description": "Andramo ny mandehandeha miakatra ao amin'ny fandaharam-potoana hahitana raha misy ireo teo aloha.",
+            "no_recent_messages_title": "Tsy misy hafatra vao haingana avy aminy%(user)s hita"
+        },
+        "redact_button": "Esory ny hafatra vao haingana",
+        "revoke_invite": "Esory ny fanasana",
+        "room_encrypted": "Ny hafatra ao amin'ity efitrano ity dia misy encryption.",
+        "room_encrypted_detail": "Voaro ny hafatrao ary ianao sy ny mpandray ihany no manana ny fanalahidy tokana hamahana azy ireo.",
+        "room_unencrypted": "Ny hafatra ao amin'ity efitrano ity dia tsy misy teny miafina",
+        "room_unencrypted_detail": "Ao amin'ny efitrano misy miafina dia voaaro ny hafatrao ary ianao sy ny mpandray ihany no manana ny fanalahidy tokana hamahana azy ireo.",
+        "share_button": "Mizara rohy amin'ny mpampiasa",
+        "unban_button_room": "Manala ny fanesorana efitrano",
+        "unban_button_space": "Esory ny fanesorana aminy toerana malalaka ",
+        "unban_room_confirm_title": "Tsy misy fandraràna amin'ny %( nom de la pièce ) s",
+        "unban_space_everything": "Esory amin'ny zavatra rehetra azoko atao izy ireo",
+        "unban_space_specific": "Esory amin'ny zavatra manokana azoko atao izy ireo",
+        "unban_space_warning": "Nandresy ry zareo tsy afaka mankany amin'ny izay rehetra tadiavinareo izao tsy admin",
+        "verify_button": "Hamarino ny mpampiasa",
+        "verify_explainer": "Ho fiarovana fanampiny, hamarino ity mpampiasa ity amin'ny alàlany fanamarinana kaody indray mandeha amin'ny fitaovanao roa."
+    },
+    "user_menu": {
+        "settings": "Fikirana rehetra",
+        "switch_theme_dark": "Midira amin'ny maody maizina",
+        "switch_theme_light": "Ovay amin'ny fomba mazava"
+    },
+    "voip": {
+        "already_in_call": "Efa miantso",
+        "already_in_call_person": "Efa mifandray amin'io olona io ianao.",
+        "answered_elsewhere": "Valiny any an-kafa",
+        "answered_elsewhere_description": "Voaray tamin'ny fitaovana hafa ny antso.",
+        "call_failed": "Tsy nahomby ny antso",
+        "call_failed_description": "Tsy voa-arina ny antso izay natao",
+        "call_failed_media": "Tsy nahomby ny antso satria tsy azo nidirana ny fakan-tsary na ny mikrô finday. Hamarino tsara:",
+        "call_failed_media_applications": "Tsy misy fampiharana hafa mampiasa ny fakan-tsary",
+        "call_failed_media_connected": "Ny mikrô finday sy ny fakan-tsary dia mitsatoka tsara ary nipetraka araka ny tokony ho izy",
+        "call_failed_media_permissions": "Omena alalana hampiasa ny fakan-tsary",
+        "call_failed_microphone": "Tsy nahomby ny antso satria tsy velona ny mikrô finday. Hamarino fa misy mikrô iray napetraka ary apetraka aminy laoniny.",
+        "call_held": "%(peerName)snitana ny antso",
+        "call_held_resume": "Nitazona ny antso ianao<a> CV</a>",
+        "call_held_switch": "Nitazona ny antso ianao<a> jiro</a>",
+        "call_toast_unknown_room": "Efitrano tsy fantatra",
+        "camera_disabled": "Maty ny fakantsarinao",
+        "camera_enabled": "Mbola mandeha ny fakan-tsarinao",
+        "cannot_call_yourself_description": "Tsy afaka manao antso amin'ny tenanao ianao.",
+        "close_lobby": "Akatony ny hall",
+        "connecting": "Mampifandray",
+        "connection_lost": "Very ny fifandraisana amin'ny mpizara",
+        "connection_lost_description": "Tsy afaka mametraka antso ianao raha tsy misy fifandraisana amin'ny mpizara.",
+        "consulting": "Mifanakalo hevitra amin'ny%(transfereTarget)s .<a> alefa amin'ny%(transferee)s</a>",
+        "default_device": "Ara-pototra",
+        "dial": "Miantso",
+        "dialpad": "Fehizoro nomerika",
+        "disable_camera": "Vonoy ny fakantsary",
+        "disable_microphone": "Tapaina ny mikrô finday",
+        "disabled_no_one_here": "Tsy misy olona azo antsoina eto",
+        "disabled_no_perms_start_video_call": "Tsy manana alalana hanombohana antso an-tsary ianao",
+        "disabled_no_perms_start_voice_call": "Tsy manana alalana hanombohana antso an-tariby ianao",
+        "disabled_ongoing_call": "Antso mitohy",
+        "element_call": "Antso",
+        "enable_camera": "Alefaso ny fakantsary",
+        "enable_microphone": "Amerina ny micrô finday",
+        "expand": "Hiverina any amin'ny antso rehetra",
+        "hangup": "Iala",
+        "hide_sidebar_button": "Afeno ny sidebar",
+        "input_devices": "Fampidirana fitaovana",
+        "jitsi_call": "Fihaonambe Jitsi",
+        "join_button_tooltip_call_full": "Miala tsiny — feno ity antso ity amin'izao fotoana izao",
+        "legacy_call": "Antso",
+        "maximise": "Ameno ny efijery",
+        "maximise_call": "Ampitomboy ny antso",
+        "minimise_call": "Ampidino ny antso",
+        "misconfigured_server": "Tsy nahomby ny antso noho ny mpizara diso",
+        "misconfigured_server_description": "Anontanio ny mpitantana ny homeserver anao (<code>%(homeserverDomain)s</code> ) mba handrindrana mpizara TURN mba hahafahany antso hiasa azo antoka.",
+        "misconfigured_server_fallback": "Raha tsy izany, azonao atao ny manandrana mampiasa ny mpizara hoany daholobe amin'ny<server/>, saingy tsy ho azo ianteherana izany, ary hizara ny adiresy IP-nao amin'io mpizara io. Azonao atao koa ny mitantana izany ao aminy fanitsiana.",
+        "misconfigured_server_fallback_accept": "Andramo ampiasaina%(server)s",
+        "more_button": "Fanampiny",
+        "msisdn_lookup_failed": "Tsy afaka mijery ny laharan-telefaona",
+        "msisdn_lookup_failed_description": "Misy lesoka tamin'ny fijerena ny laharan-telefaona",
+        "msisdn_transfer_failed": "Tsy afaka nandefa antso",
+        "n_people_joined": {
+            "one": "%(count)s olona tafiditra",
+            "other": "%(count)s nanatevin-daharana ny olona"
+        },
+        "no_audio_input_description": "Tsy nahita mikrô tamin'ny fitaovanao izahay. Hamarino azafady ny fanitsiana ary andramo indray.",
+        "no_audio_input_title": "Tsy hita ny mikrô",
+        "no_media_perms_description": "Mila manome alalana amin'ny tanana ianao%(brand)s hidirana amin'ny mikrô/fakan-tsary-nao",
+        "no_media_perms_title": "Tsy misy fahazoan-dàlana amin'ny haino aman-jery",
+        "no_permission_conference": "Mila alalana",
+        "no_permission_conference_description": "Tsy mahazo alalana hanombohana antso kaonferansa ao amin'ity efitrano ity ianao",
+        "on_hold": "%(name)seo ampanatontonsana",
+        "output_devices": "Fivoana",
+        "screenshare_monitor": "Zarao ny efijery manontolo",
+        "screenshare_title": "Mizara votoaty",
+        "screenshare_window": "Varavarankely fampiharana",
+        "show_sidebar_button": "Asehoy ny anjan'ny sisiny",
+        "silence": "Antso tsy ahenoam-peo",
+        "silenced": "Fanairana nizotra aminy fahanginana",
+        "start_screenshare": "Manomboka Mizara ny efijery",
+        "stop_screenshare": "Atsaharo ny fizarana ny efijery",
+        "too_many_calls": "Be loatra ny antso",
+        "too_many_calls_description": "Nahatratra ny isa ambony indrindra amin'ny antso miaraka ianao.",
+        "transfer_consult_first_label": "Manoro hevitra aloha",
+        "transfer_failed": "Tsy nahomby ny famindrana",
+        "transfer_failed_description": "Tsy nahavita nandefa antso",
+        "unable_to_access_audio_input_description": "Tsy afaka niditra tamin'ny mikrofoninao izahay. Jereo azafady ny tefiny toeram-pivohizana ary andramo indray.",
+        "unable_to_access_audio_input_title": "Tsy afaka niditra tamin'ny mikrôfonina",
+        "unable_to_access_media": "Tsy afaka miditra amin'ny fakan-tsary/mikrô finday",
+        "unable_to_access_microphone": "Tsy afaka niditra tamin'ny mikrô finday",
+        "unknown_caller": "Antso miafina",
+        "unknown_person": "olona tsy fantatra",
+        "unsilence": "Mandeha ny feo",
+        "unsupported": "Tsy Mandray an-tànana ny antso",
+        "unsupported_browser": "Tsy afaka manao antso amin'ity toeram-pivohizana ity ianao.",
+        "user_busy": "Tsy manana fotoana ny mpampiasa",
+        "user_busy_description": "Mbola Sahirana ny mpampiasa niantsoanao.",
+        "user_is_presenting": "%(sharerName)sIzao",
+        "video_call": "Antso an-tsary",
+        "video_call_started": "Manomboka ny antso an-tsary",
+        "video_call_using": "Antso an-tsary mampiasa:",
+        "voice_call": "Antso an-tariby",
+        "you_are_presenting": "Mamkahalala anareo"
+    },
+    "widget": {
+        "added_by": "Widget nampidirin'i",
+        "capabilities_dialog": {
+            "content_starting_text": "Ity widget ity dia te:",
+            "decline_all_permission": "Nofoanana daholo",
+            "remember_Selection": "Tsarovy ny safidiko ho an'ity widget ity",
+            "title": "Ankatoavy ny fahazoan-dàlana widget"
+        },
+        "capability": {
+            "always_on_screen_generic": "Mijanòna eo amin'ny efijery rehefa mihazakazaka",
+            "always_on_screen_viewing_another_room": "Mijanona eo amin'ny efijery rehefa mijery efitrano hafa, rehefa mihazakazaka",
+            "any_room": "Ny etsy ambony, fa amin'ny efitrano rehetra iarahanao na asaina koa",
+            "byline_empty_state_key": "Miaraka amin'ny fanalahidiny fanjakana foana",
+            "byline_state_key": "Miaraka amin'ny fanalahidiny fanjakana%(stateKey)s",
+            "capability": "Ny<b>%(capability)s</b> fahaizany",
+            "change_avatar_active_room": "Hanova ny avatar ny efitranonao mavitrika",
+            "change_avatar_this_room": "Hanova ny avatar amin'ity efitrano ity",
+            "change_name_active_room": "Ovao ny anarany efitranonao mavitrika",
+            "change_name_this_room": "Ovay ny anaran'ity efitrano ity",
+            "change_topic_active_room": "Ovay ny lohahevitry ny efitranonao mavitrika",
+            "change_topic_this_room": "Hanova ny lohahevitra momba ity efitrano ity",
+            "receive_membership_active_room": "Jereo rehefa misy olona miditra, miala, na asaina ao amin'ny efitranonao mavitrika",
+            "receive_membership_this_room": "Jereo rehefa misy olona miditra, miala, na asaina ao amin'ity efitrano ity",
+            "remove_ban_invite_leave_active_room": "Esory, raràna, na asao ny olona ho ao amin'ny efitranonao mavitrika, ary asaivo miala ianao",
+            "remove_ban_invite_leave_this_room": "Esory, raràna, na asao ho ao amin'ity efitrano ity ny olona, ary asaivo miala ianao",
+            "see_avatar_change_active_room": "Jereo rehefa miova ny avatar ao amin'ny efitranonao mavitrika",
+            "see_avatar_change_this_room": "Jereo rehefa miova ny avatar amin'ity efitrano ity",
+            "see_event_type_sent_active_room": "Jereo ny<b>%(eventType)s</b> hetsika nalefa tao amin'ny efitranonao mavitrika",
+            "see_event_type_sent_this_room": "Jereo ny<b>%(eventType)s</b> hetsika nalefa tao amin'ity efitrano ity",
+            "see_images_sent_active_room": "Jereo ny sary navoaka tao amin'ny efitranonao mavitrika",
+            "see_images_sent_this_room": "Jereo ny sary navoaka tao amin'ity efitrano ity",
+            "see_messages_sent_active_room": "Jereo ny hafatra napetraka ao amin'ny efitranonao mavitrika",
+            "see_messages_sent_this_room": "Jereo ireo hafatra napetraka ao amin'ity efitrano ity",
+            "see_msgtype_sent_active_room": "Jereo ny<b>%(msgtype)s</b> hafatra nalefa tao amin'ny efitranonao mavitrika",
+            "see_msgtype_sent_this_room": "Jereo ny<b>%(msgtype)s</b> hafatra nalefa tao amin'ity efitrano ity",
+            "see_name_change_active_room": "Jereo rehefa miova ny anarana ao amin'ny efitranonao mavitrika",
+            "see_name_change_this_room": "Jereo rehefa miova ny anarana ao amin'ity efitrano ity",
+            "see_sent_emotes_active_room": "Jereo ireo fihetseham-po navoaka tao amin'ny efitranonao mavitrika",
+            "see_sent_emotes_this_room": "Jereo ireo fihetseham-po napetraka ao amin'ity efitrano ity",
+            "see_sent_files_active_room": "Jereo ny rakitra ankapobeny napetraka ao amin'ny efitranonao mavitrika",
+            "see_sent_files_this_room": "Jereo ny rakitra ankapobeny napetraka ao amin'ity efitrano ity",
+            "see_sticker_posted_active_room": "Jereo rehefa misy mandefa sticker ao amin'ny kaonty nao",
+            "see_sticker_posted_this_room": "Jereo rehefa misy sticker apetraka ao amin'ity efitrano ity",
+            "see_text_messages_sent_active_room": "Jereo ny hafatra an-tsoratra napetraka ao amin'ny efitranonao mavitrika",
+            "see_text_messages_sent_this_room": "Jereo ireo hafatra an-tsoratra navoaka tao amin'ity efitrano ity",
+            "see_topic_change_active_room": "Jereo rehefa miova ny lohahevitra ao amin'ny efitranonao mavitrika",
+            "see_topic_change_this_room": "Jereo rehefa miova ny lohahevitra ao amin'ity efitrano ity",
+            "see_videos_sent_active_room": "Jereo ireo horonan-tsary navoaka tao amin'ny efitranonao mavitrika",
+            "see_videos_sent_this_room": "Jereo ireo horonan-tsary navoaka tao amin'ity efitrano ity",
+            "send_emotes_active_room": "Alefaso emotes rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_emotes_this_room": "Alefaso ny fihetseham-po rehefa ao amin'ity efitrano ity ianao",
+            "send_event_type_active_room": "Alefaso<b>%(eventType)s</b> hetsika tahaka anao ao amin'ny efitranonao mavitrika",
+            "send_event_type_this_room": "Alefaso<b>%(eventType)s</b> hetsika tahaka anao ato amin'ity efitrano ity",
+            "send_files_active_room": "Alefaso ny rakitra ankapobeny rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_files_this_room": "Alefaso ny rakitra ankapobe rehefa ato amin'ity efitrano ity ianao",
+            "send_images_active_room": "Alefaso sary rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_images_this_room": "Mandefasa sary tahaka anao ato amin'ity efitrano ity",
+            "send_messages_active_room": "Mandefasa hafatra rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_messages_this_room": "Mandefa hafatra rehefa ao amin'ity efitrano ity ianao",
+            "send_msgtype_active_room": "Alefaso<b>%(msgtype)s</b> hafatra rehefa ao amin&#39;ny efitranonao mavitrika ianao",
+            "send_msgtype_this_room": "Alefaso<b>%(msgtype)s</b> hafatra tahaka anao ato amin'ity efitrano ity",
+            "send_stickers_active_room": "Alefaso ao amin'ny efitranonao mavitrika ny stickers",
+            "send_stickers_active_room_as_you": "Alefaso ny stickers any amin'ny efitranonao mavitrika tahaka anao",
+            "send_stickers_this_room": "Alefaso ao amin'ity efitrano ity ny stickers",
+            "send_stickers_this_room_as_you": "Mandefasa stickers amin'ity efitrano ity tahaka anao",
+            "send_text_messages_active_room": "Mandefasa hafatra an-tsoratra rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_text_messages_this_room": "Mandefa hafatra an-tsoratra rehefa ao amin'ity efitrano ity ianao",
+            "send_videos_active_room": "Alefaso rakin-tsary rehefa ao amin'ny efitranonao mavitrika ianao",
+            "send_videos_this_room": "Alefaso video tahaka anao ato amin'ity efitrano ity",
+            "specific_room": "Ny ambony, fa amin'ny<Room /> koa",
+            "switch_room": "Ovay ny efitrano hojerenao",
+            "switch_room_message_user": "Ovao izay efitrano, hafatra, na mpampiasa hitanao"
+        },
+        "close_to_view_right_panel": "Akatono ity widget ity raha hijery azy amin'ity tontonana ity",
+        "context_menu": {
+            "delete": "Fafao ny widget",
+            "delete_warning": "Ny famafana widget dia manala azy hoany mpampiasa rehetra ao amin'ity efitrano ity. Tena te-hamafa ity widget ity ve ianao?",
+            "move_left": "Mifindra eo ankavia",
+            "move_right": "Mandrosoa miankavanana",
+            "remove": "Esory avokoa hoany rehetra",
+            "revoke": "Manafoana ny fahazoan-dàlana",
+            "screenshot": "Maka sary",
+            "start_audio_stream": "Atombohy ny onjam-peo"
+        },
+        "cookie_warning": "Ity widget ity dia mety mampiasa cookies.",
+        "error_hangup_description": "Tapaka tamin'ny antso ianao. (Hadisoana:%(message)s )",
+        "error_hangup_title": "Very ny fifandraisana",
+        "error_loading": "Hadisoana amin'ny fametahana Widget",
+        "error_mixed_content": "Error - Votoaty mifangaro",
+        "error_need_invite_permission": "Mila mahay manasa mpampiasa hanao izany ianao.",
+        "error_need_kick_permission": "Mila mahay mandaka ny mpampiasa ianao hanao izany.",
+        "error_need_to_be_logged_in": "Mila miditra ianao.",
+        "error_unable_start_audio_stream_description": "Tsy afaka manomboka fandefasana feo.",
+        "error_unable_start_audio_stream_title": "Tsy nahavita nandefa mivantana",
+        "modal_data_warning": "Ny angona amin'ity efijery ity dia zaraina amin'ny%(widgetDomain)s",
+        "modal_title_default": "Widget modal",
+        "no_name": "Fampiharana tsy fantatra",
+        "open_id_permissions_dialog": {
+            "remember_selection": "Tsarovy ity",
+            "starting_text": "Ny widget dia hanamarina ny ID mpampiasa anao, saingy tsy afaka manao hetsika ho anao:",
+            "title": "Avelao ity widget ity hanamarina ny mombamomba anao"
+        },
+        "popout": "Widget contextuel",
+        "set_room_layout": "Apetraho hoany rehetra ny firafitry ny efitranoko",
+        "shared_data_avatar": "URL sarin'ny mombamomba anao",
+        "shared_data_device_id": "ID fitaovanao",
+        "shared_data_lang": "Ny fiteninao",
+        "shared_data_mxid": "ID mpampiasa anao",
+        "shared_data_name": "Anarana asehonao",
+        "shared_data_room_id": "ID efitrano",
+        "shared_data_theme": "Ny loha-hevitrao",
+        "shared_data_url": "%(brand)sURL",
+        "shared_data_warning": "Ny fampiasana an'io widget io dia mety hizara data<helpIcon /> amin'ny%(widgetDomain)s .",
+        "shared_data_warning_im": "Ny fampiasana an'io widget io dia mety hizara data<helpIcon /> amin'ny%(widgetDomain)s & mpitantana ny fampidirana anao.",
+        "shared_data_widget_id": "Widget ID",
+        "unencrypted_warning": "Ny Widget dia tsy mampiasa fanafenana hafatra.",
+        "unmaximise": "Lasa loatra",
+        "unpin_to_view_right_panel": "Esory ity widget ity raha hijery azy amin'ity tontonana ity"
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "Ny litera lehibe rehetra dia saika mora fantarina toy ny litera kely rehetra",
+            "anotherWord": "Manampia teny iray na roa hafa. Tsara kokoa ny teny tsy mahazatra.",
+            "associatedYears": "Halaviro ireo taona mifandray aminao",
+            "capitalization": "Tsy dia manampy loatra ny kapitalista",
+            "dates": "Halaviro ny daty sy taona mifandray aminao",
+            "l33t": "Ny fanoloana vinavina toa ny '@' fa tsy ny dia tsy 'iray'manao' dia manampy loatra",
+            "longerKeyboardPattern": "Mampiasà lamina kitendry lava kokoa miaraka amin'ny fihodinana bebe kokoa",
+            "noNeed": "Tsy mila marika, tarehimarika, na litera lehibe",
+            "pwned": "Raha mampiasa ity tenimiafina ity any an-kafa ianao dia tokony hanova azy io.",
+            "recentYears": "Halaviro ireo taona faramparany",
+            "repeated": "Halaviro ny teny sy ny tarehin-tsoratra miverimberina",
+            "reverseWords": "Tsy sarotra ny maminavina ny teny mivadika",
+            "sequences": "Halaviro ny filaharana",
+            "useWords": "Mampiasà teny vitsivitsy, ialao ny fehezanteny mahazatra"
+        },
+        "warnings": {
+            "common": "Ity dia tenimiafina mahazatra",
+            "commonNames": "Ny anarana mahazatra sy ny anaram-bositra dia mora maminavina",
+            "dates": "Matetika no mora vinavinaina ny daty",
+            "extendedRepeat": "Ny famerimberenana toy \"abcabcabc\" dia somary sarotra vinavinaina noho \"abc\"",
+            "keyPattern": "Mora vinavinaina ny lamina kitendry fohy",
+            "namesByThemselves": "Ny anarana sy ny anaram-bositra dia mora maminavina",
+            "pwned": "Ny tenimiafinao dia naharihary noho ny fanitsakitsahana ny angona tao amin'ny Internet.",
+            "recentYears": "Mora ny maminavina ny taona faramparany",
+            "sequences": "Ny filaharana toy ny abc na 6543 dia mora vinavinaina",
+            "similarToCommon": "Izany dia mitovy amin'ny tenimiafina ampiasaina matetika",
+            "simpleRepeat": "Ny miverimberina toy ny \"aaa\" dia mora vinavinaina",
+            "straightRow": "Mora maminavina ny andalana mahitsy amin'ny fanalahidy",
+            "topHundred": "Ity dia tenimiafina mahazatra 100 ambony indrindra",
+            "topTen": "Ity dia tenimiafina mahazatra 10 ambony indrindra",
+            "userInputs": "Tsy tokony hisy angona manokana na pejy mifandraika amin'izany.",
+            "wordByItself": "Mora fantarina ny teny iray"
+        }
+    }
+}
diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json
new file mode 100644
index 0000000000000000000000000000000000000000..cab5ac8a05d74b82501bef1cbb60ae1f3823f5f2
--- /dev/null
+++ b/src/i18n/strings/nb_NO.json
@@ -0,0 +1,4162 @@
+{
+    "a11y": {
+        "emoji_picker": "Emoji-velger",
+        "jump_first_invite": "Hopp til den første invitasjonen.",
+        "message_composer": "Meldingsfelt",
+        "n_unread_messages": {
+            "one": "1 ulest melding.",
+            "other": "%(count)s uleste meldinger."
+        },
+        "n_unread_messages_mentions": {
+            "one": "1 ulest omtale.",
+            "other": "%(count)s uleste meldinger inkludert der du nevnes."
+        },
+        "recent_rooms": "Nylige rom",
+        "room_messsage_not_sent": "Åpent rom %(roomName)s med en usendt melding.",
+        "room_n_unread_invite": "Invitasjon til det åpne rommet %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Åpne room %(roomName)s med 1 ulest message.",
+            "other": "Åpne room %(roomName)s med %(count)s uleste messages."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Åpne room %(roomName)s med 1 ulest omtale.",
+            "other": "Åpne room %(roomName)s med %(count)s uleste meldinger inkludert omtaler."
+        },
+        "room_name": "Rom %(name)s",
+        "room_status_bar": "Statuslinje for rommet",
+        "seek_bar_label": "Søkelinje for lyd",
+        "unread_messages": "Uleste meldinger.",
+        "user_menu": "Brukermeny"
+    },
+    "a11y_jump_first_unread_room": "Hopp til første uleste rom.",
+    "action": {
+        "accept": "Godta",
+        "add": "Legg til",
+        "add_existing_room": "Legg til et eksisterende rom",
+        "add_people": "Legg til personer",
+        "apply": "Bruk",
+        "approve": "Godkjenn",
+        "ask_to_join": "Be om å få bli med",
+        "back": "Tilbake",
+        "call": "Ring",
+        "cancel": "Avbryt",
+        "change": "Endre",
+        "clear": "Tøm",
+        "click": "Klikk",
+        "click_to_copy": "Klikk for å kopiere",
+        "close": "Lukk",
+        "collapse": "Trekk sammen",
+        "complete": "Fullført",
+        "confirm": "Bekreft",
+        "continue": "Fortsett",
+        "copy": "Kopier",
+        "copy_link": "Kopier lenke",
+        "create": "Lag",
+        "create_a_room": "Opprett rom",
+        "create_account": "Opprett konto",
+        "decline": "Avslå",
+        "decline_and_block": "Avvise og blokkere",
+        "decline_invite": "Avvis invitasjon",
+        "delete": "Slett",
+        "deny": "Avvis",
+        "disable": "Slå av",
+        "disconnect": "Koble fra",
+        "dismiss": "Avvis",
+        "done": "Fullført",
+        "download": "Nedlastning",
+        "edit": "Rediger",
+        "enable": "Slå på",
+        "enter_fullscreen": "Gå til fullskjermmodus",
+        "exit_fullscreeen": "Avslutt fullskjermmodus",
+        "expand": "Utvid",
+        "explore_public_rooms": "Utforsk offentlige rom",
+        "explore_rooms": "Se alle rom",
+        "export": "Eksporter",
+        "forward": "Videresend",
+        "go": "Gå",
+        "go_back": "Gå tilbake",
+        "got_it": "Jeg forstår",
+        "hide": "Skjul",
+        "hide_advanced": "Skjul avansert",
+        "hold": "Hold",
+        "ignore": "Ignorer",
+        "import": "Importer",
+        "invite": "Inviter",
+        "invite_to_space": "Inviter til område",
+        "invites_list": "Invitasjoner",
+        "join": "Bli med",
+        "learn_more": "Lær mer",
+        "leave": "Forlat",
+        "leave_room": "Forlat rommet",
+        "logout": "Logg ut",
+        "manage": "Administrér",
+        "maximise": "Maksimer",
+        "mention": "Nevn",
+        "minimise": "Minimer",
+        "new_message": "Ny melding",
+        "new_room": "Nytt rom",
+        "new_video_room": "Nytt videorom",
+        "next": "Neste",
+        "no": "Nei",
+        "ok": "OK",
+        "open": "Åpne",
+        "open_menu": "Åpne meny",
+        "pause": "Pause",
+        "pin": "Fest",
+        "play": "Spill av",
+        "proceed": "Fortsett",
+        "quote": "Sitat",
+        "react": "Reager",
+        "refresh": "Oppdater",
+        "register": "Registrer",
+        "reload": "Last inn på nytt",
+        "remove": "Fjern",
+        "rename": "Gi nytt navn",
+        "reply": "Svar",
+        "reply_in_thread": "Svar i tråd",
+        "report_content": "Rapporter innhold",
+        "report_room": "Rapporter rom",
+        "resend": "Send på nytt",
+        "reset": "Nullstill",
+        "resume": "Fortsett",
+        "retry": "Prøv igjen",
+        "review": "Gjennomgang",
+        "revoke": "Tilbakekall",
+        "save": "Lagre",
+        "search": "Søk",
+        "send_report": "Send inn rapport",
+        "set_avatar": "Angi profilbilde",
+        "share": "Del",
+        "show": "Vis",
+        "show_advanced": "Vis avansert",
+        "show_all": "Vis alt",
+        "sign_in": "Logg inn",
+        "sign_out": "Logg ut",
+        "skip": "Hopp over",
+        "start": "Begynn",
+        "start_chat": "Start chat",
+        "start_new_chat": "Start ny chat",
+        "stop": "Stopp",
+        "submit": "Send inn",
+        "subscribe": "Abonnér",
+        "transfer": "Overfør",
+        "trust": "Stol på",
+        "try_again": "Prøv igjen",
+        "unban": "Opphev utestengelse",
+        "unignore": "Opphev ignorering",
+        "unpin": "Løsne",
+        "unsubscribe": "Meld ut",
+        "update": "Oppdater",
+        "upgrade": "Oppgrader",
+        "upload": "Last opp",
+        "upload_file": "Last opp fil",
+        "verify": "Bekreft",
+        "view": "Vis",
+        "view_all": "Vis alle",
+        "view_list": "Vis liste",
+        "view_message": "Se melding",
+        "view_source": "Vis kilde",
+        "yes": "Ja",
+        "yes_dismiss": "Ja, avvis",
+        "zoom_in": "Forstørr",
+        "zoom_out": "Forminske"
+    },
+    "analytics": {
+        "accept_button": "Det er greit",
+        "bullet_1": "Vi registrerer eller profilerer <Bold>ikke</Bold> kontodata",
+        "bullet_2": "Vi <Bold>deler ikke</Bold> informasjon med tredjeparter",
+        "consent_migration": "Du har tidligere samtykket til å dele anonyme bruksdata med oss. Vi oppdaterer hvordan det fungerer.",
+        "disable_prompt": "Du kan slå av dette når som helst i innstillingene",
+        "enable_prompt": "Hjelp til å forbedre %(analyticsOwner)s",
+        "learn_more": "Del anonyme data for å hjelpe oss med å identifisere problemer. Ikke noe personlig. Ingen tredjeparter. <LearnMoreLink>Lære mer </LearnMoreLink>",
+        "privacy_policy": "Du kan lese alle våre vilkår <PrivacyPolicyUrl> her </PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "Hjelp oss med å identifisere problemer og forbedre %(analyticsOwner)s ved å dele anonyme bruksdata. For å forstå hvordan folk bruker flere enheter, genererer vi en tilfeldig identifikator som deles av dine enheter.",
+        "shared_data_heading": "Følgende data kan deles:"
+    },
+    "auth": {
+        "3pid_in_use": "E-postadressen eller telefonnummeret er allerede i bruk.",
+        "account_clash": "Din nye konto (%(newAccountId)s) er registrert, men du er allerede logget inn på en annen konto (%(loggedInUserId)s).",
+        "account_clash_previous_account": "Fortsett med tidligere konto",
+        "account_deactivated": "Denne kontoen har blitt deaktivert.",
+        "autodiscovery_generic_failure": "Kunne ikke hente autodiscovery-konfigurasjonen fra serveren",
+        "autodiscovery_hs_incompatible": "Hjemmeserveren din er for gammel og støtter ikke minimum API-versjon som kreves. Ta kontakt med din servereier, eller oppgrader din server.",
+        "autodiscovery_invalid": "Ugyldig svar på oppdagelse av hjemmeserver",
+        "autodiscovery_invalid_hs": "Hjemeserver-URL ser ikke ut til å være en gyldig Matrix-hjemmeserver",
+        "autodiscovery_invalid_hs_base_url": "Ugyldig base_url for m.homeserver",
+        "autodiscovery_invalid_is": "URL-adressen til identitetsserveren ser ikke ut til å være en gyldig identitetsserver",
+        "autodiscovery_invalid_is_base_url": "Ugyldig base_url for m.identity_server",
+        "autodiscovery_invalid_is_response": "Ugyldig svar på identifisering av identitetstjener",
+        "autodiscovery_invalid_json": "Ugyldig JSON",
+        "autodiscovery_no_well_known": "Ingen velkjent JSON-fil funnet",
+        "autodiscovery_unexpected_error_hs": "Uventet feil ved løsning av hjemmeserverkonfigurasjon",
+        "autodiscovery_unexpected_error_is": "Uventet feil ved konfigurasjon av identitetsserver",
+        "captcha_description": "Denne hjemmeserveren vil gjerne forsikre seg om at du ikke er en robot.",
+        "change_password_action": "Endre passordet",
+        "change_password_confirm_invalid": "Passordene samsvarer ikke",
+        "change_password_confirm_label": "Bekreft passord",
+        "change_password_current_label": "Nåværende passord",
+        "change_password_empty": "Passord kan ikke være tomme",
+        "change_password_error": "Feil under endring av passord: %(error)s",
+        "change_password_mismatch": "De nye passordene samsvarer ikke",
+        "change_password_new_label": "Nytt passord",
+        "check_email_explainer": "Følg instruksjonene som er sendt til <b> %(email)s </b>",
+        "check_email_resend_prompt": "Har du ikke mottatt den?",
+        "check_email_resend_tooltip": "E-post med bekreftelseslenke er sendt på nytt!",
+        "check_email_wrong_email_button": "Skriv inn e-postadressen på nytt",
+        "check_email_wrong_email_prompt": "Feil e-postadresse?",
+        "continue_with_idp": "Fortsett med %(provider)s",
+        "continue_with_sso": "Fortsett med %(ssoButtons)s",
+        "country_dropdown": "Nedfallsmeny over land",
+        "create_account_prompt": "Er du ny her? <a>Opprett en konto</a>",
+        "create_account_title": "Opprett konto",
+        "email_discovery_text": "Bruk e-post for å kunne oppdages av eksisterende kontakter.",
+        "email_field_label": "E-post",
+        "email_field_label_invalid": "Det ser ikke ut som en gyldig e-postadresse",
+        "email_field_label_required": "Legg inn e-postadresse",
+        "email_help_text": "Legg til en e-postadresse for å kunne tilbakestille passordet ditt.",
+        "email_phone_discovery_text": "Bruk e-post eller telefon for å være synlig for eksisterende kontakter.",
+        "enter_email_explainer": "<b>%(homeserver)s</b>vil sende deg en bekreftelseslenke for å la deg tilbakestille passordet ditt.",
+        "enter_email_heading": "Skriv inn din e-postadresse for å tilbakestille passordet",
+        "failed_connect_identity_server": "Kan ikke nå identitetsserveren",
+        "failed_connect_identity_server_other": "Du kan logge på, men noen funksjoner vil være utilgjengelige før identitetsserveren er tilbake på nett. Hvis du stadig ser denne advarselen, sjekk konfigurasjonen eller kontakt en serveradministrator.",
+        "failed_connect_identity_server_register": "Du kan registrere deg, men enkelte funksjoner vil ikke være tilgjengelige før identitetsserveren er tilbake på nett. Hvis du får denne advarselen flere ganger, bør du sjekke konfigurasjonen eller kontakte en serveradministrator.",
+        "failed_connect_identity_server_reset_password": "Du kan tilbakestille passordet ditt, men noen funksjoner vil ikke være tilgjengelige før identitetsserveren er tilbake på nett. Hvis du ser denne advarselen flere ganger, bør du sjekke konfigurasjonen eller kontakte en serveradministrator.",
+        "failed_homeserver_discovery": "Kunne ikke utføre hjemmeserveroppdagelse",
+        "failed_query_registration_methods": "Kan ikke spørre etter støttede registreringsmetoder.",
+        "failed_soft_logout_auth": "Kunne ikke autentisere på nytt",
+        "failed_soft_logout_homeserver": "Kunne ikke autentisere på nytt på grunn av et problem med hjemmeserveren",
+        "forgot_password_email_invalid": "E-postadressen ser ikke ut til å være gyldig.",
+        "forgot_password_email_required": "E-postadressen som er knyttet til kontoen din, må oppgis.",
+        "forgot_password_prompt": "Har du glemt passordet ditt?",
+        "forgot_password_send_email": "Send e-post",
+        "identifier_label": "Logg inn med",
+        "incorrect_credentials": "Feil brukernavn og/eller passord.",
+        "incorrect_credentials_detail": "Vær oppmerksom på at du logger på %(hs)s-serveren, ikke matrix.org.",
+        "incorrect_password": "Feil passord",
+        "log_in_new_account": "<a>Logg på</a> din nye konto.",
+        "logout_dialog": {
+            "description": "Er du sikker på at du vil logge av?",
+            "megolm_export": "Eksporter nøkler manuelt",
+            "setup_key_backup_title": "Du mister tilgangen til de krypterte meldingene dine",
+            "setup_secure_backup_description_1": "Krypterte meldinger er sikret med punkt-til-punkt-kryptering. Bare du og mottakeren(e) har nøklene til å lese disse meldingene.",
+            "setup_secure_backup_description_2": "Når du logger av, slettes disse nøklene fra denne enheten, noe som betyr at du ikke vil kunne lese krypterte meldinger med mindre du har nøklene til dem på dine andre enheter, eller har sikkerhetskopiert dem til serveren.",
+            "skip_key_backup": "Jeg vil ikke ha mine krypterte meldinger",
+            "use_key_backup": "Begynn å bruke Nøkkelsikkerhetskopiering"
+        },
+        "misconfigured_body": "Spør din%(brand)s administrator om å sjekke <a>konfigurasjonen din</a> for feil eller dupliserte oppføringer.",
+        "misconfigured_title": "Ditt %(brand)s-oppsett er feiloppsatt",
+        "mobile_create_account_title": "Du er i ferd med å opprette en konto på %(hsName)s",
+        "msisdn_field_description": "Andre brukere kan invitere deg til rom ved hjelp av din kontaktinformasjon",
+        "msisdn_field_label": "Telefon",
+        "msisdn_field_number_invalid": "Det telefonnummeret ser ikke helt riktig ut, vennligst sjekk og prøv igjen",
+        "msisdn_field_required_invalid": "Skriv inn telefonnummer",
+        "no_hs_url_provided": "Ingen hjemmeserver-URL oppgitt",
+        "oidc": {
+            "error_title": "Vi kunne ikke logge deg inn",
+            "generic_auth_error": "Noe gikk galt under autentisering. Gå til påloggingssiden og prøv igjen.",
+            "missing_or_invalid_stored_state": "Vi har bedt nettleseren om å huske hvilken hjemmeserver du bruker for å kunne logge deg på, men dessverre har nettleseren din glemt det. Gå til påloggingssiden og prøv på nytt."
+        },
+        "password_field_keep_going_prompt": "Fortsett...",
+        "password_field_label": "Skriv inn passord",
+        "password_field_strong_label": "Strålende, passordet er sterkt!",
+        "password_field_weak_label": "Passordet er tillatt, men er ikke trygt",
+        "phone_label": "Telefon",
+        "phone_optional_label": "Telefonnummer (valgfritt)",
+        "qr_code_login": {
+            "check_code_explainer": "Dette vil bekrefte at tilkoblingen til den andre enheten din er sikker.",
+            "check_code_heading": "Skriv inn nummeret som vises på din andre enhet",
+            "check_code_input_label": "2-sifret kode",
+            "check_code_mismatch": "Tallene stemmer ikke overens",
+            "completing_setup": "Fullføre konfigurasjonen av den nye enheten",
+            "error_etag_missing": "Det oppstod en uventet feil. Dette kan skyldes en nettleserutvidelse, proxy-server eller feilkonfigurasjon av serveren.",
+            "error_expired": "Påloggingen er utløpt. Vennligst prøv igjen.",
+            "error_expired_title": "Påloggingen ble ikke fullført i tide",
+            "error_insecure_channel_detected": "En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem.",
+            "error_insecure_channel_detected_instructions": "Hva nå?",
+            "error_insecure_channel_detected_instructions_1": "Prøv å logge på den andre enheten på nytt med en QR-kode i tilfelle dette var et nettverksproblem",
+            "error_insecure_channel_detected_instructions_2": "Hvis du støter på det samme problemet, kan du prøve et annet wifi-nettverk eller bruke mobildata i stedet for wifi",
+            "error_insecure_channel_detected_instructions_3": "Hvis det ikke fungerer, logger du på manuelt",
+            "error_insecure_channel_detected_title": "Tilkoblingen er ikke sikker",
+            "error_other_device_already_signed_in": "Du trenger ikke gjøre noe annet.",
+            "error_other_device_already_signed_in_title": "Den andre enheten din er allerede pålogget",
+            "error_rate_limited": "For mange forsøk på kort tid. Vent litt før du prøver igjen.",
+            "error_unexpected": "Det oppstod en uventet feil. Forespørselen om å koble til den andre enheten har blitt avbrutt.",
+            "error_unsupported_protocol": "Denne enheten støtter ikke pålogging på den andre enheten med en QR-kode.",
+            "error_unsupported_protocol_title": "Annen enhet ikke kompatibel",
+            "error_user_cancelled": "Påloggingen ble kansellert på den andre enheten.",
+            "error_user_cancelled_title": "Påloggingsforespørsel kansellert",
+            "error_user_declined": "Du eller kontoleverandøren avviste påloggingsforespørselen.",
+            "error_user_declined_title": "Pålogging avvist",
+            "follow_remaining_instructions": "Følg de gjenstående instruksjonene",
+            "open_element_other_device": "Åpen%(brand)s på din andre enhet",
+            "point_the_camera": "Skann QR-koden som vises her",
+            "scan_code_instruction": "Skann QR-koden med en annen enhet",
+            "scan_qr_code": "Logg på med QR-kode",
+            "security_code": "Sikkerhetskode",
+            "security_code_prompt": "Hvis du blir spurt, skriv inn koden nedenfor på den andre enheten.",
+            "select_qr_code": "Velg \"%(scanQRCode)s»",
+            "unsupported_explainer": "Din kontoleverandør støtter ikke pålogging på en ny enhet med en QR-kode.",
+            "unsupported_heading": "QR-kode støttes ikke",
+            "waiting_for_device": "Venter på at enheten skal logge på"
+        },
+        "register_action": "Opprett konto",
+        "registration": {
+            "continue_without_email_description": "Bare en advarsel: Hvis du ikke legger til en e-postadresse og glemmer passordet ditt, kan du <b>permanent miste tilgangen til kontoen din</b>.",
+            "continue_without_email_field_label": "E-post (valgfritt)",
+            "continue_without_email_title": "Fortsetter uten e-post"
+        },
+        "registration_disabled": "Registrering er deaktivert på denne hjemmeserveren.",
+        "registration_msisdn_field_required_invalid": "Skriv inn telefonnummer (påkrevd på denne hjemmeserveren)",
+        "registration_successful": "Registreringen var vellykket",
+        "registration_username_in_use": "Noen har allerede det brukernavnet. Prøv et annet, eller hvis det er deg, logg inn nedenfor.",
+        "registration_username_unable_check": "Kan ikke sjekke om brukernavnet er tatt. Prøv igjen senere.",
+        "registration_username_validation": "Bruk kun småbokstaver, numre, streker og understreker",
+        "reset_password": {
+            "confirm_new_password": "Bekreft nytt passord",
+            "devices_logout_success": "Du har blitt logget ut av alle enheter og vil ikke lenger motta push-varsler. For å aktivere varslinger på nytt må du logge på igjen på hver enhet.",
+            "other_devices_logout_warning_1": "Når du logger ut av enhetene dine, slettes krypteringsnøklene som er lagret på dem, slik at kryptert chattehistorikk ikke lenger kan leses.",
+            "other_devices_logout_warning_2": "Hvis du vil beholde tilgangen til chatloggen din i krypterte rom, må du konfigurere sikkerhetskopiering av nøkler eller eksportere meldingsnøklene fra en av de andre enhetene dine før du fortsetter.",
+            "password_not_entered": "Et nytt passord må bli skrevet inn.",
+            "passwords_mismatch": "De nye passordene må samsvare med hverandre.",
+            "rate_limit_error": "For mange forsøk på kort tid. Vent litt før du prøver igjen.",
+            "rate_limit_error_with_time": "For mange forsøk på kort tid. Prøv på nytt etter%(timeout)s.",
+            "reset_successful": "Passordet ditt har blitt tilbakestilt.",
+            "return_to_login": "Gå tilbake til påloggingsskjermen",
+            "sign_out_other_devices": "Logg av alle enheter"
+        },
+        "reset_password_action": "Tilbakestill passordet",
+        "reset_password_button": "Glemt passordet?",
+        "reset_password_email_field_description": "Bruk en E-postadresse til å gjenopprette kontoen din",
+        "reset_password_email_field_required_invalid": "Skriv inn en E-postadresse (Påkrevd på denne hjemmeserveren)",
+        "reset_password_email_not_associated": "E-postadressen din ser ikke ut til å være knyttet til en Matrix ID på denne hjemmeserveren.",
+        "reset_password_email_not_found_title": "Denne e-postadressen ble ikke funnet",
+        "reset_password_title": "Tilbakestill passordet ditt",
+        "server_picker_custom": "Annen hjemmeserver",
+        "server_picker_description": "Du kan bruke de egendefinerte serveralternativene til å logge på andre Matrix-servere ved å angi en annen hjemmeserver-URL. Dette gjør at du kan bruke %(brand)s med en eksisterende Matrix-konto på en annen hjemmeserver.",
+        "server_picker_description_matrix.org": "Bli med millioner av mennesker gratis på den største offentlige serveren",
+        "server_picker_dialog_title": "Bestem hvor kontoen din skal ligge",
+        "server_picker_explainer": "Bruk din foretrukne Matrix-hjemmeserver hvis du har en, eller host din egen.",
+        "server_picker_failed_validate_homeserver": "Kan ikke validere hjemmeserver",
+        "server_picker_intro": "Vi kaller stedene der du kan hoste kontoen din for \"hjemmeservere\".",
+        "server_picker_invalid_url": "Ugyldig URL",
+        "server_picker_learn_more": "Om hjemmeservere",
+        "server_picker_matrix.org": "Matrix.org er den største offentlige hjemmeserveren i verden, så det er et bra sted for mange.",
+        "server_picker_required": "Angi en hjemmeserver",
+        "server_picker_title": "Logg på hjemmeserveren din",
+        "server_picker_title_default": "Serveralternativer",
+        "server_picker_title_registration": "Host kontoen på",
+        "session_logged_out_description": "Av sikkerhetsgrunner har denne økten blitt logget ut. Vennligst logg på igjen.",
+        "session_logged_out_title": "Avlogget",
+        "set_email": {
+            "description": "Dette vil tillate deg å tilbakestille passordet ditt og motta varsler.",
+            "verification_pending_description": "Vennligst sjekk e-posten din og klikk på lenken den inneholder. Når dette er gjort, klikker du på Fortsett.",
+            "verification_pending_title": "Avventer verifisering"
+        },
+        "set_email_prompt": "Vil du velge en E-postadresse?",
+        "sign_in_description": "Bruk kontoen din for å fortsette.",
+        "sign_in_instead": "Logg på i stedet",
+        "sign_in_instead_prompt": "Har du allerede en konto? <a>Logg på</a>",
+        "sign_in_or_register": "Logg inn eller lag en konto",
+        "sign_in_or_register_description": "Bruk kontoen din eller opprett en ny for å fortsette.",
+        "sign_in_prompt": "Har du en konto? <a>Logg på</a>",
+        "sign_in_with_sso": "Logg på med single sign-on",
+        "signing_in": "Logger på...",
+        "soft_logout": {
+            "clear_data_button": "Tøm alle data",
+            "clear_data_description": "Sletting av alle data fra denne økten er permanent. Krypterte meldinger vil gå tapt med mindre det er tatt sikkerhetskopi av nøklene.",
+            "clear_data_title": "Slett alle data i denne økten?"
+        },
+        "soft_logout_heading": "Du er logget av",
+        "soft_logout_intro_password": "Skriv inn passordet ditt for å logge på og få tilgang til kontoen din igjen.",
+        "soft_logout_intro_sso": "Logg på og få tilgang til kontoen din igjen.",
+        "soft_logout_intro_unsupported_auth": "Du kan ikke logge på kontoen din. Ta kontakt med administratoren for hjemmeserver for mer informasjon.",
+        "soft_logout_subheading": "Tøm personlige data",
+        "soft_logout_warning": "Advarsel: Dine personlige data (inkludert krypteringsnøkler) lagres fortsatt i denne sesjonen. Fjern den hvis du er ferdig med denne sesjonen, eller hvis du vil logge på med en annen konto.",
+        "sso": "Single sign-on",
+        "sso_complete_in_browser_dialog_title": "Gå til nettleseren din for å fullføre påloggingen",
+        "sso_failed_missing_storage": "Vi ba nettleseren huske hvilken hjemmeserver du bruker for å la deg logge på, men dessverre har nettleseren din glemt det. Gå til påloggingssiden og prøv igjen.",
+        "sso_or_username_password": "%(ssoButtons)s eller %(usernamePassword)s",
+        "sync_footer_subtitle": "Hvis du har blitt med i mange rom, kan dette ta en stund",
+        "syncing": "Synkroniserer...",
+        "uia": {
+            "code": "Kode",
+            "email": "For å opprette kontoen din, åpne lenken i e-posten vi nettopp sendte til %(emailAddress)s.",
+            "email_auth_header": "Sjekk e-posten din for å fortsette",
+            "email_resend_prompt": "Har du ikke mottatt den? <a>Send den på nytt</a>",
+            "email_resent": "Sendt på nytt!",
+            "fallback_button": "Begynn autentisering",
+            "mas_cross_signing_reset_cta": "Gå til kontoen din",
+            "mas_cross_signing_reset_description": "Tilbakestill identiteten din gjennom kontoleverandøren din, og kom deretter tilbake og klikk \"Prøv på nytt\".",
+            "mas_cross_signing_reset_title": "Gå til kontoen din for å tilbakestille identiteten din",
+            "msisdn": "En SMS har blitt sendt til %(msisdn)s",
+            "msisdn_token_incorrect": "Sjetongen er feil",
+            "msisdn_token_prompt": "Vennligst skriv inn koden den inneholder:",
+            "password_prompt": "Bekreft identiteten din ved å skrive inn kontopassordet ditt nedenfor.",
+            "recaptcha_missing_params": "Mangler captcha offentlig nøkkel i hjemmeserverens konfigurasjon. Vennligst rapporter dette til hjemmeserveradministratoren.",
+            "registration_token_label": "Registreringstoken",
+            "registration_token_prompt": "Skriv inn et registreringstoken som du har fått fra hjemmeserveradministratoren.",
+            "sso_body": "Befrekt denne e-postadressen ved å bruke Single Sign On for å bevise din identitet.",
+            "sso_failed": "Noe gikk galt med å bekrefte identiteten din. Avbryt og prøv igjen.",
+            "sso_postauth_body": "Klikk på knappen nedenfor for å bekrefte identiteten din.",
+            "sso_postauth_title": "Bekreft for å fortsette",
+            "sso_preauth_body": "For å fortsette, bruk Single Sign On for å bevise identiteten din.",
+            "sso_title": "Bruk Single Sign On for å fortsette",
+            "terms": "Vennligst gjennomgå og godta retningslinjene til denne hjemmeserveren:",
+            "terms_invalid": "Vennligst gjennomgå og godta alle retningslinjene til hjemmeserveren"
+        },
+        "unsupported_auth": "Denne hjemmeserveren tilbyr ingen påloggingsflyter som støttes av denne klienten.",
+        "unsupported_auth_email": "Denne hjemmeserveren støtter ikke pålogging med e-postadresse.",
+        "unsupported_auth_msisdn": "Denne serveren støtter ikke autentisering med et telefonnummer.",
+        "username_field_required_invalid": "Skriv inn brukernavn",
+        "username_in_use": "Noen har allerede det brukernavnet, prøv et annet.",
+        "verify_email_explainer": "Vi må vite at det er deg før vi kan tilbakestille passordet ditt. Klikk på lenken i e-posten vi nettopp sendte til <b>%(email)s</b>",
+        "verify_email_heading": "Bekreft e-postadressen din for å fortsette"
+    },
+    "bug_reporting": {
+        "additional_context": "Hvis det er ekstra kontekst som kan hjelpe til med å analysere problemet, for eksempel hva du gjorde på tidspunktet, rom-ID-er, bruker-ID-er osv., vennligst ta med disse tingene her.",
+        "before_submitting": "Før du sender inn logger, må du <a> opprette et GitHub-problem </a> for å beskrive problemet ditt.",
+        "collecting_information": "Samler informasjon om appversjon",
+        "collecting_logs": "Samler logger",
+        "create_new_issue": "Vennligst <newIssueLink>opprett et nytt problem</newIssueLink> på GitHub slik at vi kan undersøke denne feilen.",
+        "description": "Feilsøkingslogger inneholder data om applikasjonsbruk, inkludert brukernavnet ditt, ID-ene eller aliasene til rommene du har besøkt, hvilke brukergrensesnittelementer du sist interagerte med, og brukernavnene til andre brukere. De inneholder ikke meldinger.",
+        "download_logs": "Last ned logger",
+        "downloading_logs": "Laster ned logger",
+        "error_empty": "Fortell oss hva som gikk galt, eller bedre, opprett et GitHub-problem som beskriver problemet.",
+        "failed_download_logs": "Kunne ikke laste ned feilsøkingslogger: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Feilrapporten din ble avvist. Rageshake-serveren støtter ikke denne applikasjonen.",
+            "rejected_generic": "Feilrapporten din ble avvist. Rageshake-serveren avviste innholdet i rapporten på grunn av en policy.",
+            "rejected_recovery_key": "Feilrapporten din ble avvist av sikkerhetsgrunner, da den inneholdt en gjenopprettingsnøkkel.",
+            "rejected_version": "Feilrapporten din ble avvist fordi versjonen du kjører er for gammel.",
+            "server_unknown_error": "Det oppstod en ukjent feil på rageshake-serveren, og rapporten kunne ikke håndteres.",
+            "unknown_error": "Kunne ikke sende logger."
+        },
+        "github_issue": "Github-saksrapport",
+        "introduction": "Hvis du har sendt inn en feil via GitHub, kan feilsøkingslogger hjelpe oss med å spore opp problemet. ",
+        "log_request": "For å hjelpe oss med å forhindre dette i fremtiden, vennligst <a>send oss loggfiler</a>.",
+        "logs_sent": "Loggbøkene ble sendt",
+        "matrix_security_issue": "For å rapportere inn et Matrix-relatert sikkerhetsproblem, vennligst less Matrix.org sine <a>Retningslinjer for sikkerhetspublisering</a>.",
+        "preparing_download": "Forbereder nedlasting av logger",
+        "preparing_logs": "Forbereder sending av logger",
+        "send_logs": "Send loggbøker",
+        "submit_debug_logs": "Send inn avlusingsloggbøker",
+        "textarea_label": "Merknader",
+        "thank_you": "Tusen takk!",
+        "title": "Feilrapportering",
+        "unsupported_browser": "En påminnelse: Nettleseren din støttes ikke, så opplevelsen din kan være uforutsigbar.",
+        "uploading_logs": "Laste opp logger",
+        "waiting_for_server": "Venter på svar fra serveren"
+    },
+    "cannot_invite_without_identity_server": "Det er ikke mulig å invitere bruker via e-post uten en identitetsserver. Du kan koble til en under «Innstillinger».",
+    "cannot_reach_homeserver": "Kan ikke nå hjemmeserver",
+    "cannot_reach_homeserver_detail": "Sjekk at du har en stabil internettforbindelse, eller ta kontakt med serveradministratoren",
+    "cant_load_page": "Klarte ikke å laste inn siden",
+    "chat_card_back_action_label": "Tilbake til chat",
+    "chat_effects": {
+        "confetti_description": "Sender den gitte meldingen med konfetti",
+        "confetti_message": "sender konfetti",
+        "fireworks_description": "Sender den gitte meldingen med fyrverkeri",
+        "fireworks_message": "sender fyrverkeri",
+        "hearts_description": "Sender den gitte meldingen med hjerter",
+        "hearts_message": "sender hjerter",
+        "rainfall_description": "Sender den gitte meldingen med regnvær",
+        "rainfall_message": "sender nedbør",
+        "snowfall_description": "Sender den gitte meldingen med snøfall",
+        "snowfall_message": "sender snøfall",
+        "spaceinvaders_description": "Sender den gitte meldingen med en spaceinvaders effekt",
+        "spaceinvaders_message": "sender space invaders"
+    },
+    "common": {
+        "access_token": "Tilgangstoken",
+        "accessibility": "Tilgjengelighet",
+        "advanced": "Avansert",
+        "all_chats": "Alle chatter",
+        "analytics": "Statistikk",
+        "and_n_others": {
+            "one": "og en annen...",
+            "other": "og %(count)s andre..."
+        },
+        "appearance": "Utseende",
+        "application": "Applikasjon",
+        "are_you_sure": "Er du sikker?",
+        "attachment": "Vedlegg",
+        "authentication": "Autentisering",
+        "avatar": "Profilbilde",
+        "beta": "Beta",
+        "camera": "Kamera",
+        "cameras": "Kameraer",
+        "cancel": "Avbryt",
+        "capabilities": "Evner",
+        "copied": "Kopiert!",
+        "credits": "Takk til",
+        "dark": "Mørk",
+        "description": "Beskrivelse",
+        "deselect_all": "Fravelg alle",
+        "device": "Enhet",
+        "edited": "redigert",
+        "email_address": "E-postadresse",
+        "emoji": "Emoji",
+        "encrypted": "Kryptert",
+        "encryption_enabled": "Kryptering er skrudd på",
+        "error": "Feil",
+        "faq": "Ofte Stilte Spørsmål",
+        "favourites": "Favoritter",
+        "feedback": "Tilbakemelding",
+        "filter_results": "Filtrerresultater",
+        "forward_message": "Videresend melding",
+        "general": "Generelt",
+        "go_to_settings": "Gå til Innstillinger",
+        "guest": "Gjest",
+        "help": "Hjelp",
+        "historical": "Historisk",
+        "home": "Hjem",
+        "homeserver": "Hjemmeserver",
+        "identity_server": "Identitetstjener",
+        "image": "Bilde",
+        "integration_manager": "Integreringsbehandler",
+        "joined": "Ble med",
+        "labs": "Laboratoriet",
+        "legal": "Juridisk",
+        "light": "Lys",
+        "loading": "Laster inn …",
+        "location": "Lokasjon",
+        "low_priority": "Lavprioritet",
+        "matrix": "Matrix",
+        "message": "Melding",
+        "message_layout": "Meldingsoppsett",
+        "message_timestamp_invalid": "Ugyldig tidsstempel",
+        "microphone": "Mikrofon",
+        "model": "Modell",
+        "moderation_and_safety": "Moderasjon og sikkerhet",
+        "modern": "Moderne",
+        "mute": "Demp",
+        "n_members": {
+            "one": "%(count)s medlem",
+            "other": "%(count)s medlemmer"
+        },
+        "n_rooms": {
+            "one": "%(count)s rom",
+            "other": "%(count)s rom"
+        },
+        "name": "Navn",
+        "no_results": "Ingen treff",
+        "no_results_found": "Ingen resultater ble funnet",
+        "not_trusted": "Ikke betrodd",
+        "off": "Av",
+        "offline": "Frakoblet",
+        "on": "På",
+        "options": "Innstillinger",
+        "orphan_rooms": "Andre rom",
+        "password": "Passord",
+        "people": "Folk",
+        "preferences": "Brukervalg",
+        "presence": "Tilstedeværelse",
+        "preview_message": "Hei der. Du er fantastisk!",
+        "privacy": "Personvern",
+        "private": "Privat",
+        "private_room": "Privat rom",
+        "private_space": "Privat område",
+        "profile": "Profil",
+        "public": "Offentlig",
+        "public_room": "Offentlig rom",
+        "public_space": "Offentlig område",
+        "qr_code": "QR-kode",
+        "random": "Tilfeldig",
+        "reactions": "Reaksjoner",
+        "recommended": "Anbefalt",
+        "report_a_bug": "Rapporter en feil",
+        "room": "Rom",
+        "room_name": "Rommets navn",
+        "rooms": "Rom",
+        "save": "Lagre",
+        "saved": "Lagret",
+        "saving": "Lagrer …",
+        "secure_backup": "Sikker backup",
+        "select_all": "Velg alle",
+        "server": "Server",
+        "settings": "Innstillinger",
+        "setup_secure_messages": "Sett opp sikre meldinger",
+        "show_more": "Vis mer",
+        "someone": "Noen",
+        "space": "Område",
+        "spaces": "Områder",
+        "sticker": "Klistremerke",
+        "stickerpack": "Klistremerkepakke",
+        "success": "Vellykket",
+        "suggestions": "Forslag",
+        "support": "Brukerstøtte",
+        "system_alerts": "Systemvarsler",
+        "theme": "Tema",
+        "thread": "Tråd",
+        "threads": "Tråder",
+        "timeline": "Tidslinje",
+        "unavailable": "ikke tilgjengelig",
+        "unencrypted": "Ukryptert",
+        "unmute": "Opphev demp",
+        "unnamed_room": "Navnløst rom",
+        "unnamed_space": "Ikke navngitt område",
+        "unverified": "Ubekreftet",
+        "updating": "Oppdaterer...",
+        "user": "Bruker",
+        "user_avatar": "Profilbilde",
+        "username": "Brukernavn",
+        "verification_cancelled": "Verifiseringen ble avbrutt",
+        "verified": "Verifisert",
+        "version": "Versjon",
+        "video": "Video",
+        "video_room": "Videorom",
+        "view_message": "Se melding",
+        "warning": "Advarsel"
+    },
+    "composer": {
+        "autocomplete": {
+            "@room_description": "Varsle hele rommet",
+            "command_a11y": "Autofullføring av kommandoer",
+            "command_description": "Kommandoer",
+            "emoji_a11y": "Auto-fullfør emojier",
+            "notification_a11y": "Automatisk utfylling av varsler",
+            "notification_description": "Romvarsling",
+            "room_a11y": "Automatisk utfylling av rom",
+            "space_a11y": "Automatisk utfylling av område",
+            "user_a11y": "Automatisk utfylling av bruker",
+            "user_description": "Brukere"
+        },
+        "close_sticker_picker": "Skjul klistremerker",
+        "edit_composer_label": "Rediger meldingen",
+        "format_bold": "Fet",
+        "format_code_block": "Kodefelt",
+        "format_decrease_indent": "Reduser innrykk",
+        "format_increase_indent": "Øk innrykk",
+        "format_inline_code": "Kode",
+        "format_insert_link": "Sett inn lenke",
+        "format_italic": "Kursiv",
+        "format_italics": "Kursiv",
+        "format_link": "Lenke",
+        "format_ordered_list": "Nummerert liste",
+        "format_strikethrough": "Gjennomstreking",
+        "format_underline": "Understrek",
+        "format_unordered_list": "Punktliste",
+        "formatting_toolbar_label": "Formatering",
+        "link_modal": {
+            "link_field_label": "Lenke",
+            "text_field_label": "Tekst",
+            "title_create": "Opprett en lenke",
+            "title_edit": "Rediger lenke"
+        },
+        "mode_plain": "Skjul formatering",
+        "mode_rich_text": "Vis formatering",
+        "no_perms_notice": "Du har ikke tillatelse til å legge ut innlegg i dette rommet",
+        "placeholder": "Send en melding …",
+        "placeholder_encrypted": "Send en kryptert beskjed …",
+        "placeholder_reply": "Send et svar …",
+        "placeholder_reply_encrypted": "Send et kryptert svar …",
+        "placeholder_thread": "Svar på tråden...",
+        "placeholder_thread_encrypted": "Svar på kryptert tråd...",
+        "poll_button": "Avstemning",
+        "poll_button_no_perms_description": "Du har ikke tillatelse til å starte avstemninger i dette rommet.",
+        "poll_button_no_perms_title": "Tillatelse kreves",
+        "replying_title": "Svarer på",
+        "room_upgraded_link": "Samtalen fortsetter her.",
+        "room_upgraded_notice": "Dette rommet har blitt erstattet og er ikke lenger aktivt.",
+        "send_button_title": "Send melding",
+        "send_button_voice_message": "Send talemelding",
+        "send_voice_message": "Send talemelding",
+        "stop_voice_message": "Stopp opptaket",
+        "voice_message_button": "Talemelding"
+    },
+    "console_dev_note": "Hvis du vet hva du driver med, Element er åpen kildekode, så sjekk ut GitHub (https://github.com/vector-im/element-web/) og bidra!",
+    "console_scam_warning": "Hvis noen ba deg om å kopiere/lime inn noe her, er det stor sannsynlighet for at du blir svindlet!",
+    "console_wait": "Vent!",
+    "create_room": {
+        "action_create_room": "Opprett rom",
+        "action_create_video_room": "Opprett videorom",
+        "encrypted_video_room_warning": "Du kan ikke deaktivere dette senere. Rommet vil bli kryptert, men det innebygde anropet vil ikke.",
+        "encrypted_warning": "Du kan ikke deaktivere dette senere. Broer og de fleste bot'er fungerer ikke ennå.",
+        "encryption_forced": "Serveren din krever at kryptering er aktivert i private rom.",
+        "encryption_label": "Aktiver start-til-mål-kryptering",
+        "error_title": "Klarte ikke å opprette rommet",
+        "generic_error": "Tjeneren kan være utilgjengelig, overbelastet, eller du fant en feil.",
+        "join_rule_change_notice": "Du kan endre dette når som helst fra rominnstillingene.",
+        "join_rule_invite": "Privat rom (kun for inviterte)",
+        "join_rule_invite_label": "Bare inviterte personer vil kunne finne og bli med i dette rommet.",
+        "join_rule_knock_label": "Alle kan be om å bli med, men administratorer eller moderatorer må gi tilgang. Du kan endre dette senere.",
+        "join_rule_public_label": "Hvem som helst vil kunne finne og bli med i dette rommet.",
+        "join_rule_public_parent_space_label": "Alle vil kunne finne og bli med i dette rommet, ikke bare medlemmer av <SpaceName/>.",
+        "join_rule_restricted": "Synlig for områdets medlemmer",
+        "join_rule_restricted_label": "Alle i <SpaceName/> vil kunne finne og bli med i dette rommet.",
+        "name_validation_required": "Vennligst skriv inn et navn for rommet",
+        "room_visibility_label": "Romsynlighet",
+        "title_private_room": "Opprett et privat rom",
+        "title_public_room": "Opprett et offentlig rom",
+        "title_video_room": "Opprett et videorom",
+        "topic_label": "Tema (valgfritt)",
+        "unfederated": "Blokker noen som ikke er en del av%(serverName)s fra noen gang å bli med i dette rommet.",
+        "unfederated_label_default_off": "Du kan aktivere dette hvis rommet bare skal brukes til å samarbeide med interne team på hjemmeserveren. Dette kan ikke endres senere.",
+        "unfederated_label_default_on": "Du kan deaktivere dette hvis rommet skal brukes til å samarbeide med eksterne team som har sine egne hjemmeservere. Dette kan ikke endres senere.",
+        "unsupported_version": "Tjeneren støtter ikke rom versjonen som ble spesifisert."
+    },
+    "create_space": {
+        "add_details_prompt": "Legg til mer detaljer for å gjøre det letter å gjenkjenne.",
+        "add_details_prompt_2": "Du kan endre disse når som helst.",
+        "add_existing_rooms_description": "Velg rom eller samtaler du vil legge til. Dette er bare et område for deg, ingen vil bli informert. Du kan legge til flere senere.",
+        "add_existing_rooms_heading": "Hva vil du organisere?",
+        "address_label": "Adresse",
+        "address_placeholder": "f.eks. my-space",
+        "creating": "Oppretter...",
+        "creating_rooms": "Oppretter rom …",
+        "done_action": "Gå til mitt område",
+        "done_action_first_room": "Gå til mitt første rom",
+        "explainer": "Områder er en ny måte å gruppere rom og mennesker på. Hva slags område vil du skape? Du kan endre dette senere.",
+        "failed_create_initial_rooms": "Kan ikke opprette innledende område rom",
+        "failed_invite_users": "Kunne ikke invitere følgende brukere til området ditt: %(csvUsers)s",
+        "invite_teammates_by_username": "Inviter etter brukernavn",
+        "invite_teammates_description": "Sørg for at de riktige personene har tilgang. Du kan invitere flere senere.",
+        "invite_teammates_heading": "Inviter teammedlemmene dine",
+        "inviting_users": "Inviterer...",
+        "label": "Opprett et område",
+        "name_required": "Vennligst skriv inn et navn for området",
+        "personal_space": "Bare meg selv",
+        "personal_space_description": "Et privat område for å organisere rommene dine",
+        "private_description": "Kun for inviterte, best for deg selv eller team",
+        "private_heading": "Ditt private område",
+        "private_personal_description": "Sørg for at de riktige personene har tilgang til %(name)s",
+        "private_personal_heading": "Hvem jobber du med?",
+        "private_space": "Meg og mine teammedlemmer",
+        "private_space_description": "Et privat område for deg og dine teammedlemmer",
+        "public_description": "Åpent område for alle, best for lokalsamfunn",
+        "public_heading": "Ditt offentlige område",
+        "search_public_button": "Søk etter offentlige områder",
+        "setup_rooms_community_description": "La oss opprette et rom for hver av dem.",
+        "setup_rooms_community_heading": "Hva er noen ting du ønsker å diskutere i%(spaceName)s ?",
+        "setup_rooms_description": "Du kan legge til flere senere også, inkludert allerede eksisterende.",
+        "setup_rooms_private_description": "Vi oppretter rom for hver av dem.",
+        "setup_rooms_private_heading": "Hvilke prosjekter jobber teamet ditt med?",
+        "share_description": "Det er bare deg for øyeblikket, det blir enda bedre med andre.",
+        "share_heading": "Del %(name)s",
+        "skip_action": "Hopp over for nå",
+        "subspace_adding": "Legger til...",
+        "subspace_beta_notice": "Legg til et område i et område du administrerer.",
+        "subspace_dropdown_title": "Opprett et område",
+        "subspace_existing_space_prompt": "Vil du legge til en eksisterende område i stedet?",
+        "subspace_join_rule_invite_description": "Bare personer som er invitert vil kunne finne og bli med i dette området.",
+        "subspace_join_rule_invite_only": "Privat <b>område</b> (kun invitasjon)",
+        "subspace_join_rule_label": "Synlighet for område",
+        "subspace_join_rule_public_description": "Hvem som helst vil kunne finne og bli med i dette området, ikke bare medlemmer av <SpaceName/>.",
+        "subspace_join_rule_restricted_description": "Hvem som helst i<SpaceName/> vil kunne finne og bli med."
+    },
+    "credits": {
+        "default_cover_photo": "<photo>Standard forsidebilde </photo> er © av<author> Jesús Roncero </author>og brukt under vilkårene i CC-BY-SA 4.0. <terms> </terms>.",
+        "twemoji": "Emoji-grafikken <twemoji>Twemoji</twemoji> er © av <author>Twitter, Inc og andre bidragsytere</author> og brukt under vilkårene i <terms>CC-BY 4.0</terms>.",
+        "twemoji_colr": "Fonten <colr>twemoji-colr</colr> er © <author>Mozilla Foundation</author> kan brukes under vilkårene i <terms>Apache 2.0</terms>."
+    },
+    "decline_invitation_dialog": {
+        "confirm": "Er du sikker på at du vil takke nei til invitasjonen om å bli med i \"%(roomName)s\"?",
+        "ignore_user_help": "Du vil ikke se noen meldinger eller rominvitasjoner fra denne brukeren.",
+        "reason_description": "Beskriv årsaken for å rapportere rommet.",
+        "report_room_description": "Rapporter dette rommet til din kontoleverandør.",
+        "title": "Avvis invitasjon"
+    },
+    "desktop_default_device_name": "%(brand)sSkrivebordet: %(platformName)s",
+    "devtools": {
+        "active_widgets": "Aktive moduler",
+        "category_other": "Andre",
+        "category_room": "Rom",
+        "caution_colon": "Advarsel:",
+        "client_versions": "Klientversjoner",
+        "crypto": {
+            "4s_public_key_in_account_data": "i kontodata",
+            "4s_public_key_not_in_account_data": "ikke funnet",
+            "4s_public_key_status": "Offentlig nøkkel for hemmelig lagring:",
+            "backup_key_cached": "bufret lokalt",
+            "backup_key_cached_status": "Sikkerhetskopieringsnøkkel bufret:",
+            "backup_key_not_stored": "ikke lagret",
+            "backup_key_stored": "i hemmelig lagring",
+            "backup_key_stored_status": "Sikkerhetskopinøkkel lagret:",
+            "backup_key_unexpected_type": "uventet type",
+            "backup_key_well_formed": "godt utformet",
+            "cross_signing": "Krysssignering",
+            "cross_signing_cached": "bufret lokalt",
+            "cross_signing_not_ready": "Krysssignering er ikke satt opp.",
+            "cross_signing_private_keys_in_storage": "i hemmelig lagring",
+            "cross_signing_private_keys_in_storage_status": "Krysssignering av private nøkler:",
+            "cross_signing_private_keys_not_in_storage": "ikke funnet lagret",
+            "cross_signing_public_keys_on_device": "i minnet",
+            "cross_signing_public_keys_on_device_status": "Krysssignering av offentlige nøkler:",
+            "cross_signing_ready": "Krysssignering er klar til bruk.",
+            "cross_signing_status": "Status for krysssignering:",
+            "cross_signing_untrusted": "Kontoen din har en krysssigneringsidentitet i hemmelig lagring, men den er ennå ikke klarert av denne sesjonen.",
+            "crypto_not_available": "Kryptografisk modul er ikke tilgjengelig",
+            "key_backup_active_version": "Aktiv sikkerhetskopiversion:",
+            "key_backup_active_version_none": "Ingen",
+            "key_backup_inactive_warning": "Nøklene dine blir ikke sikkerhetskopiert fra denne sesjonen.",
+            "key_backup_latest_version": "Siste sikkerhetskopi på serveren:",
+            "key_storage": "Oppbevaring av nøkler",
+            "master_private_key_cached_status": "Master privat nøkkel:",
+            "not_found": "ikke funnet",
+            "not_found_locally": "ikke funnet lokalt",
+            "secret_storage_not_ready": "ikke klar",
+            "secret_storage_ready": "klar",
+            "secret_storage_status": "Hemmelig lagringsplass:",
+            "self_signing_private_key_cached_status": "Selvsignert privat nøkkel:",
+            "title": "Ende-til-ende-kryptering",
+            "user_signing_private_key_cached_status": "Brukersignert privat nøkkel:"
+        },
+        "developer_mode": "Utviklermodus",
+        "developer_tools": "Utviklerverktøy",
+        "edit_setting": "Rediger innstilling",
+        "edit_values": "Rediger verdier",
+        "empty_string": "<empty string>",
+        "event_content": "Hendelsesinnhold",
+        "event_id": "Hendelses-ID: %(eventId)s",
+        "event_sent": "Hendelse sendt!",
+        "event_type": "Hendelsestype",
+        "explore_account_data": "Utforsk kontodata",
+        "explore_room_account_data": "Utforsk romkontodata",
+        "explore_room_state": "Utforsk romtilstand",
+        "failed_to_find_widget": "Det oppsto en feil ved å finne denne widgeten.",
+        "failed_to_load": "Kunne ikke laste inn.",
+        "failed_to_save": "Kunne ikke lagre innstillingene.",
+        "failed_to_send": "Kunne ikke sende hendelse!",
+        "id": "ID: ",
+        "invalid_json": "Ser ikke ut som gyldig JSON.",
+        "level": "Nivå",
+        "low_bandwidth_mode": "Lav båndbreddemodus",
+        "low_bandwidth_mode_description": "Krever kompatibel hjemmeserver.",
+        "main_timeline": "Hovedtidslinje",
+        "no_receipt_found": "Ingen kvittering funnet",
+        "notification_state": "Varslingsstatus er <strong>%(notificationState)s</strong>",
+        "notifications_debug": "Feilsøking av varsler",
+        "number_of_users": "Antall brukere",
+        "original_event_source": "Opprinnelig hendelseskilde",
+        "room_encrypted": "Rommet er <strong> kryptert ✅ </strong>",
+        "room_id": "Rom-ID: %(roomId)s",
+        "room_not_encrypted": "Rommet er <strong>ikke kryptert 🚨</strong>",
+        "room_notifications_dot": "Punkt: ",
+        "room_notifications_highlight": "Høydepunkt: ",
+        "room_notifications_last_event": "Siste begivenhet:",
+        "room_notifications_sender": "Avsender: ",
+        "room_notifications_thread_id": "Tråd-ID: ",
+        "room_notifications_total": "Totalt: ",
+        "room_notifications_type": "Type: ",
+        "room_status": "Romstatus",
+        "room_unread_status_count": {
+            "one": "Rom ulest status: <strong>%(status)s</strong>, antall: <strong>%(count)s</strong>",
+            "other": "Rom ulest status: <strong>%(status)s</strong>, antall: <strong>%(count)s</strong>"
+        },
+        "save_setting_values": "Lagre innstillingsverdier",
+        "see_history": "Se historikk",
+        "send_custom_account_data_event": "Send egendefinert kontodatahendelse",
+        "send_custom_room_account_data_event": "Send egendefinert romkontodatahendelse",
+        "send_custom_state_event": "Send egendefinert tilstandshendelse",
+        "send_custom_timeline_event": "Send egendefinert tidslinjehendelse",
+        "server_info": "Serverinformasjon",
+        "server_versions": "Serverversjoner",
+        "settable_global": "Kan settes på global",
+        "settable_room": "Kan stilles inn på rommet",
+        "setting_colon": "Innstilling:",
+        "setting_definition": "Innstillingsdefinisjon:",
+        "setting_id": "Innstillings-ID",
+        "settings": {
+            "elementCallUrl": "Element Call URL"
+        },
+        "settings_explorer": "Utforsker for innstillinger",
+        "show_hidden_events": "Vis skjulte hendelser i tidslinjen",
+        "spaces": {
+            "one": "<space>",
+            "other": "<%(count)s områder>"
+        },
+        "state_key": "Tilstandsnøkkel",
+        "thread_root_id": "Trådens rot-ID: %(threadRootId)s",
+        "threads_timeline": "Tidslinje for tråder",
+        "title": "Utviklerverktøy",
+        "toggle_event": "slå av/på hendelse",
+        "toolbox": "Verktøykasse",
+        "use_at_own_risk": "Dette brukergrensesnittet kontrollerer IKKE verditypene. Bruk på egen risiko.",
+        "user_read_up_to": "Brukeren har lest opp til: ",
+        "user_read_up_to_ignore_synthetic": "Bruker leste opp til (ignoreSynthetic): ",
+        "user_read_up_to_private": "Bruker leste opp til (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "Bruker leste opp til (m.read.private; ignoreSyntetisk): ",
+        "value": "Verdi",
+        "value_colon": "Verdi:",
+        "value_in_this_room": "Verdi i dette rommet",
+        "value_this_room_colon": "Verdi i dette rommet:",
+        "values_explicit": "Verdier på eksplisitte nivåer",
+        "values_explicit_colon": "Verdier på eksplisitte nivåer:",
+        "values_explicit_room": "Verdier på eksplisitte nivåer i dette rommet",
+        "values_explicit_this_room_colon": "Verdier på eksplisitte nivåer i dette rommet:",
+        "view_servers_in_room": "Vis servere i rommet",
+        "view_source_decrypted_event_source": "Dekryptert hendelseskilde",
+        "view_source_decrypted_event_source_unavailable": "Dekryptert kilde utilgjengelig",
+        "widget_screenshots": "Aktiver widgetskjermbilder på støttede widgets"
+    },
+    "dialog_close_label": "Lukk dialog",
+    "download_completed": "Nedlasting fullført",
+    "emoji": {
+        "categories": "Kategorier",
+        "category_activities": "Aktiviteter",
+        "category_animals_nature": "Dyreliv og natur",
+        "category_flags": "Flagg",
+        "category_food_drink": "Mat og drikke",
+        "category_frequently_used": "Ofte brukte",
+        "category_objects": "Objekter",
+        "category_smileys_people": "Smilefjes og folk",
+        "category_symbols": "Symboler",
+        "category_travel_places": "Reise og steder",
+        "quick_reactions": "Hurtigreaksjoner"
+    },
+    "emoji_picker": {
+        "cancel_search_label": "Avbryt søket"
+    },
+    "empty_room": "Tomt rom",
+    "empty_room_was_name": "Tomt rom (var %(oldName)s)",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "alternatives": "Hvis du har en sikkerhetsnøkkel eller en sikkerhetsfrase, vil dette også fungere.",
+            "key_validation_text": {
+                "wrong_security_key": "Gjenopprettingsnøkkelen du skrev inn er ikke riktig."
+            },
+            "privacy_warning": "Sørg for at ingen kan se denne skjermen!",
+            "restoring": "Gjenoppretter nøkler fra sikkerhetskopi",
+            "security_key_title": "Gjenopprettingsnøkkel"
+        },
+        "bootstrap_title": "Setter opp nøkler",
+        "cancel_entering_passphrase_description": "Er du sikker på at du vil avbryte inntasting av passordfrase?",
+        "cancel_entering_passphrase_title": "Avbryte inntastingen av passordfrase?",
+        "confirm_encryption_setup_body": "Klikk på knappen nedenfor for å bekrefte konfigureringen av kryptering.",
+        "confirm_encryption_setup_title": "Bekreft krypteringsoppsett",
+        "cross_signing_room_normal": "Dette rommet er ende-til-ende-kryptert",
+        "cross_signing_room_verified": "Alle i dette rommet er verifisert",
+        "cross_signing_room_warning": "Noen bruker en ukjent sesjon",
+        "cross_signing_user_normal": "Du har ikke bekreftet denne brukeren.",
+        "cross_signing_user_verified": "Du har bekreftet denne brukeren. Denne brukeren har bekreftet alle sesjonene sine.",
+        "cross_signing_user_warning": "Denne brukeren har ikke bekreftet alle sesjonene sine.",
+        "enter_recovery_key": "Skriv inn gjenopprettingsnøkkel",
+        "event_shield_reason_authenticity_not_guaranteed": "Autentisiteten av denne krypterte meldingen kan ikke garanteres på denne enheten.",
+        "event_shield_reason_mismatched_sender_key": "Kryptert av en uverifisert sesjon",
+        "event_shield_reason_unknown_device": "Kryptert av en ukjent eller slettet enhet.",
+        "event_shield_reason_unsigned_device": "Kryptert med en enhet som ikke er verifisert av eieren.",
+        "event_shield_reason_unverified_identity": "Kryptert av en uverifisert bruker.",
+        "export_unsupported": "Nettleseren din støtter ikke de nødvendige kryptografiutvidelsene",
+        "forgot_recovery_key": "Har du glemt gjenopprettingsnøkkelen?",
+        "import_invalid_keyfile": "Ikke en gyldig %(brand)s-nøkkelfil",
+        "import_invalid_passphrase": "Autentiseringssjekk mislyktes: Feil passord?",
+        "key_storage_out_of_sync": "Nøkkeloppbevaringen din er ikke synkronisert.",
+        "key_storage_out_of_sync_description": "Bekreft gjenopprettingsnøkkelen for å opprettholde tilgang til nøkkellagring og meldingshistorikk.",
+        "messages_not_secure": {
+            "cause_1": "Hjemmeserveren din",
+            "cause_2": "Hjemmeserveren brukeren du bekrefter er koblet til",
+            "cause_3": "Din, eller de andre brukernes internettforbindelse",
+            "cause_4": "Din eller de andre brukernes sesjon",
+            "heading": "Ett av følgende kan være kompromittert:",
+            "title": "Dine meldinger er ikke sikre"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "En ny sikkerhetsfrase og nøkkel for sikre meldinger er oppdaget.",
+            "description_2": "Denne sesjonen krypterer historikken ved hjelp av den nye gjenopprettingsmetoden.",
+            "title": "Ny gjenopprettingsmetode",
+            "warning": "Hvis du ikke har angitt den nye gjenopprettingsmetoden, kan det hende at en hacker prøver å få tilgang til kontoen din. Du må øyeblikkelig endre passordet for kontoen din og angi en ny gjenopprettingsmetode i Innstillinger."
+        },
+        "pinned_identity_changed": "%(displayName)ss (<b>%(userId)s</b>) identitet ser ut til å ha endret seg. <a>Finn ut mer</a>",
+        "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>s identitet ble tilbakestilt. <a>Finn ut mer</a>",
+        "recovery_method_removed": {
+            "description_1": "Denne sesjonen har oppdaget at din sikkerhetsfrase og nøkkelen for sikre meldinger har blitt fjernet.",
+            "description_2": "Hvis du gjorde dette ved et uhell, kan du konfigurere sikre meldinger på denne økten som vil kryptere denne øktens meldingshistorikk på nytt med en ny gjenopprettingsmetode.",
+            "title": "Gjenopprettingsmetode fjernet",
+            "warning": "Hvis det ikke var deg som fjernet gjenopprettingsmetoden, kan det være en hacker som prøver å få tilgang til kontoen din. Bytt passordet for kontoen din og angi en ny gjenopprettingsmetode øyeblikkelig i Innstillinger."
+        },
+        "reset_all_button": "Glemt eller mistet alle gjenopprettingsmetoder?<a> Tilbakestill alt</a>",
+        "set_up_recovery": "Sett opp gjenoppretting",
+        "set_up_recovery_later": "Ikke nå",
+        "set_up_recovery_toast_description": "Generer en gjenopprettingsnøkkel som kan brukes til å gjenopprette den krypterte meldingshistorikken i tilfelle du mister tilgangen til enhetene dine.",
+        "set_up_toast_description": "Sikre deg mot å miste tilgang til krypterte meldinger og data",
+        "set_up_toast_title": "Sett opp sikker sikkerhetskopiering",
+        "setup_secure_backup": {
+            "explainer": "Ta sikkerhetskopi av nøklene dine før du logger av for å unngå å miste dem."
+        },
+        "turn_on_key_storage": "Slå på nøkkeloppbevaring",
+        "turn_on_key_storage_description": "Lagre din kryptografiske identitet og nøkler til meldinger sikkert på serveren. Dette gjør at du kan se historikken for meldinger på alle nye enheter.",
+        "udd": {
+            "interactive_verification_button": "Interaktiv verifisering ved hjelp av emoji",
+            "other_ask_verify_text": "Be denne brukeren om å bekrefte sesjonen sin, eller bekreft den manuelt nedenfor.",
+            "other_new_session_text": "%(name)s (%(userId)s) logget på en ny sesjon uten å bekrefte den:",
+            "own_ask_verify_text": "Verifiser den andre økten din med en av metodene nedenfor.",
+            "own_new_session_text": "Du logget på en ny økt uten å bekrefte den:",
+            "title": "Ikke betrodd"
+        },
+        "unable_to_setup_keys_error": "Kan ikke sette opp nøkler",
+        "verification": {
+            "accepting": "Aksepterer …",
+            "after_new_login": {
+                "device_verified": "Enhet verifisert",
+                "skip_verification": "Hopp over verifisering for nå",
+                "unable_to_verify": "Kan ikke verifisere denne enheten",
+                "verify_this_device": "Verifiser denne enheten"
+            },
+            "cancelled": "Du avbrøt verifiseringen.",
+            "cancelled_self": "Du avbrøt verifikasjonen på din andre enheten.",
+            "cancelled_user": "%(displayName)s avbrøt verifikasjonen.",
+            "cancelling": "Avbryter …",
+            "complete_action": "Skjønner",
+            "complete_description": "Du har vellykket verifisert denne brukeren.",
+            "complete_title": "Verifisert!",
+            "error_starting_description": "Vi kunne ikke starte en chat med den andre brukeren.",
+            "error_starting_title": "Feil ved start av bekreftelse",
+            "explainer": "Sikre meldinger med denne brukeren er ende-til-ende-kryptert og kan ikke leses av tredjeparter.",
+            "in_person": "For å være sikker, gjør dette personlig eller bruk en pålitelig måte å kommunisere på.",
+            "incoming_sas_device_dialog_text_1": "Bekreft denne enheten for å merke den som klarert. Å stole på denne enheten gir deg og andre brukere ekstra trygghet når du bruker ende-til-ende-krypterte meldinger.",
+            "incoming_sas_device_dialog_text_2": "Bekreftelse av denne enheten vil markere den som klarert, og brukere som har bekreftet med deg, vil stole på denne enheten.",
+            "incoming_sas_dialog_title": "Innkommende verifikasjonsforespørsel",
+            "incoming_sas_dialog_waiting": "Venter på at partneren skal bekrefte...",
+            "incoming_sas_user_dialog_text_1": "Bekreft denne brukeren for å markere dem som klarerte. Tillit til brukere gir deg ekstra trygghet når du bruker ende-til-ende-krypterte meldinger.",
+            "incoming_sas_user_dialog_text_2": "Bekreftelse av denne brukeren vil markere økten som klarert, og også merke økten din som klarert for dem.",
+            "no_key_or_device": "Det ser ut til at du ikke har en gjenopprettingsnøkkel eller andre enheter du kan verifisere mot.  Denne enheten vil ikke kunne få tilgang til gamle krypterte meldinger. For å bekrefte identiteten din på denne enheten, må du tilbakestille verifiseringsnøklene dine.",
+            "no_support_qr_emoji": "Enheten du prøver å bekrefte støtter ikke skanning av en QR-kode eller emoji-verifikasjon, som er det som %(brand)s støtter. Prøv med en annen klient.",
+            "other_party_cancelled": "Den andre parten kansellerte verifiseringen.",
+            "prompt_encrypted": "Bekreft alle brukere i et rom for å sikre at det er sikkert.",
+            "prompt_self": "Start verfisering igjen fra varselet.",
+            "prompt_unencrypted": "Bekreft alle brukere i krypterte rom for å sikre at det er sikkert.",
+            "prompt_user": "Start bekreftelsen igjen fra profilen deres.",
+            "qr_or_sas": "%(qrCode)s eller %(emojiCompare)s",
+            "qr_or_sas_header": "Verifiser denne enheten ved å fullføre ett av følgende:",
+            "qr_prompt": "Skann denne unike koden",
+            "qr_reciprocate_same_shield_device": "Nesten der! Viser den andre enheten det samme skjoldet?",
+            "qr_reciprocate_same_shield_user": "Nesten der! Viser %(displayName)s det samme skjoldet?",
+            "request_toast_accept": "Verifiser sesjonen",
+            "request_toast_accept_user": "Verifiser bruker",
+            "request_toast_decline_counter": "Ignorer (%(counter)s)",
+            "request_toast_detail": "%(deviceId)s fra %(ip)s",
+            "reset_proceed_prompt": "Fortsett med tilbakestilling",
+            "sas_caption_self": "Bekreft denne enheten ved å bekrefte at følgende nummer vises på skjermen.",
+            "sas_caption_user": "Bekreft denne brukeren ved å bekrefte at følgende nummer vises på skjermen.",
+            "sas_description": "Sammenlign et unikt sett med emojier hvis du ikke har et kamera på noen av enhetene",
+            "sas_emoji_caption_self": "Bekreft at emojiene nedenfor vises på begge enhetene, i samme rekkefølge:",
+            "sas_emoji_caption_user": "Bekreft denne brukeren ved å bekrefte at følgende emoji vises på skjermen.",
+            "sas_match": "De samsvarer",
+            "sas_no_match": "De samsvarer ikke",
+            "sas_prompt": "Sammenlign unike emojier",
+            "scan_qr": "Verifiser med skanning",
+            "scan_qr_explainer": "Be %(displayName)s om å skanne koden:",
+            "self_verification_hint": "For å fortsette, godta bekreftelsesforespørselen på den andre enheten din.",
+            "start_button": "Begynn verifisering",
+            "successful_device": "Du har vellykket verifisert %(deviceName)s (%(deviceId)s)!",
+            "successful_own_device": "Du har vellykket verifisert enheten din!",
+            "successful_user": "Du har vellykket verifisert %(displayName)s!",
+            "timed_out": "Bekreftelsen ble tidsavbrutt.",
+            "unsupported_method": "Kan ikke finne en støttet bekreftelsesmetode.",
+            "unverified_session_toast_accept": "Ja, det var meg",
+            "unverified_session_toast_title": "En ny pålogging. Var det deg?",
+            "unverified_sessions_toast_description": "Se gjennom for å sikre at kontoen din er trygg",
+            "unverified_sessions_toast_reject": "Senere",
+            "unverified_sessions_toast_title": "Du har ubekreftede økter",
+            "verification_description": "Bekreft identiteten din for å få tilgang til krypterte meldinger og bevise identiteten din for andre.",
+            "verification_dialog_title_device": "Bekreft annen enhet",
+            "verification_dialog_title_user": "Verifiseringsforespørsel",
+            "verification_skip_warning": "Uten verifisering vil du ikke ha tilgang til alle meldingene dine, og du kan fremstå som upålitelig for andre.",
+            "verification_success_with_backup": "Den nye enheten din er nå bekreftet. Den har tilgang til dine krypterte meldinger, og andre brukere vil se det som klarert.",
+            "verification_success_without_backup": "Den nye enheten din er nå bekreftet. Andre brukere vil se det som pålitelig.",
+            "verify_emoji": "Verifiser med emoji",
+            "verify_emoji_prompt": "Bekreft ved å sammenligne unike emoji.",
+            "verify_emoji_prompt_qr": "Hvis du ikke kan skanne koden ovenfor, bekreft ved å sammenligne unike emoji.",
+            "verify_later": "Jeg bekrefter senere",
+            "verify_using_device": "Bekreft med en annen enhet",
+            "verify_using_key": "Bekreft med gjenopprettingsnøkkel",
+            "verify_using_key_or_phrase": "Bekreft med gjenopprettingsnøkkel eller -frase",
+            "waiting_for_user_accept": "Venter på at %(displayName)s skal akseptere …",
+            "waiting_other_device": "Venter på at du skal bekrefte på den andre enheten din...",
+            "waiting_other_device_details": "Venter på at du skal bekrefte på den andre enheten din, %(deviceName)s (%(deviceId)s)...",
+            "waiting_other_user": "Venter på at %(displayName)s skal bekrefte..."
+        },
+        "verification_requested_toast_title": "Verifisering ble forespurt",
+        "verified_identity_changed": "%(displayName)ss (<b>%(userId)s</b>) verifiserte identitet ble tilbakestilt. <a>Lær mer </a>",
+        "verified_identity_changed_no_displayname": "<b>%(userId)s</b>'s verifiserte identitet ble tilbakestilt. <a>Lær mer om dette</a>",
+        "verify_toast_description": "Andre brukere kan kanskje mistro den",
+        "verify_toast_title": "Verifiser denne økten",
+        "withdraw_verification_action": "Trekk tilbake verifisering"
+    },
+    "error": {
+        "admin_contact": "Venligst ta <a> kontakt med tjenesteadministratoren din </a> for å fortsette å bruke denne tjenesten.",
+        "admin_contact_short": "Kontakt <a>serveradministratoren</a> din.",
+        "app_launch_unexpected_error": "Uventet feil under klargjøring av appen. Se konsollen for detaljer.",
+        "cannot_load_config": "Kunne ikke laste inn konfigurasjonsfilen: Oppdater siden for å prøve på nytt.",
+        "connection": "Det oppstod et problem med å kommunisere med hjemmeserveren. Prøv igjen senere.",
+        "dialog_description_default": "En feil har oppstått.",
+        "download_media": "Kan ikke laste ned kildemedia, ingen kilde-url ble funnet",
+        "edit_history_unsupported": "Hjemmeserveren din ser ikke ut til å støtte denne funksjonen.",
+        "failed_copy": "Mislyktes i å kopiere",
+        "hs_blocked": "Denne hjemmeserveren har blitt blokkert av dens administrator.",
+        "invalid_configuration_mixed_server": "Ugyldig konfigurasjon: en default_hs_url kan ikke spesifiseres sammen med default_server_name eller default_server_config",
+        "invalid_configuration_no_server": "Ugyldig konfigurasjon: Ingen standardserver angitt.",
+        "invalid_json": "Element-konfigurasjonen din inneholder ugyldig JSON. Rett opp problemet og last inn siden på nytt.",
+        "invalid_json_detail": "Meldingen fra parseren er: %(message)s",
+        "invalid_json_generic": "Ugyldig JSON",
+        "mau": "Denne hjemmeserveren har nådd grensen for månedlige aktive brukere.",
+        "misconfigured": "Din Element er feilkonfigurert",
+        "mixed_content": "Kan ikke koble til hjemmeserveren via HTTP når en HTTPS-URL er i nettleserlinjen. Bruk enten HTTPS eller <a> aktiver usikre skript</a>.",
+        "non_urgent_echo_failure_toast": "Serveren din svarer ikke på noen <a> forespørsler</a>.",
+        "resource_limits": "Denne hjemmeserveren har overskredet en av ressursgrensene.",
+        "session_restore": {
+            "clear_storage_button": "Tøm lagring og logg av",
+            "clear_storage_description": "Logg av og fjern krypteringsnøkler?",
+            "description_1": "Det oppstod en feil da vi prøvde å gjenopprette den forrige økten din.",
+            "description_2": "Hvis du tidligere har brukt en nyere versjon av%(brand)s, kan økten være inkompatibel med denne versjonen. Lukk dette vinduet og gå tilbake til den nyere versjonen.",
+            "description_3": "Å tømme nettleserens lagring kan løse problemet, men vil logge deg av og føre til at kryptert chattelogg blir uleselig.",
+            "title": "Kan ikke gjenopprette sesjonen"
+        },
+        "something_went_wrong": "Noe gikk galt!",
+        "storage_evicted_description_1": "Noen øktdata, inkludert krypterte meldingsnøkler, mangler. Logg av og logg på for å fikse dette, gjenopprette nøkler fra sikkerhetskopien.",
+        "storage_evicted_description_2": "Nettleseren din har sannsynligvis fjernet disse dataene da den fikk lite diskplass.",
+        "storage_evicted_title": "Mangler øktdata",
+        "sync": "Kan ikke koble til hjemmeserveren. Prøver på nytt …",
+        "tls": "Kan ikke koble til hjemmeserveren - sjekk tilkoblingen din, sørg for at <a> hjemmeserverens SSL-sertifikat </a> er klarert, og at en nettleserutvidelse ikke blokkerer forespørsler.",
+        "unknown": "Ukjent feil",
+        "unknown_error_code": "ukjent feilkode",
+        "update_power_level": "Kan ikke endre tilgangsnivå"
+    },
+    "error_app_open_in_another_tab": "Bytt til den andre fanen for å koble til %(brand)s . Denne fanen kan nå lukkes.",
+    "error_app_open_in_another_tab_title": "%(brand)s er koblet til i en annen fane",
+    "error_app_opened_in_another_window": "%(brand)s er åpen i et annet vindu. Klikk \"%(label)s\" for å bruke %(brand)s her og koble fra det andre vinduet.",
+    "error_database_closed_description": {
+        "for_desktop": "Disken din kan være full. Rydd opp litt plass og last inn på nytt.",
+        "for_web": "Hvis du slettet nettleserdata, forventes denne meldingen. %(brand)s kan også være åpen i en annen fane, eller disken din er full. Vennligst rydde opp litt plass og last inn på nytt"
+    },
+    "error_database_closed_title": "%(brand)s sluttet å fungere",
+    "error_dialog": {
+        "copy_room_link_failed": {
+            "description": "Kan ikke kopiere en lenke til rommet til utklippstavlen.",
+            "title": "Kan ikke kopiere romlenken"
+        },
+        "error_loading_user_profile": "Klarte ikke å laste inn brukerprofilen",
+        "forget_room_failed": "Kunne ikke glemme rommet %(errCode)s"
+    },
+    "error_user_not_logged_in": "Brukeren er ikke logget inn",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "Anrop pågår",
+            "user": "%(senderName)s ble med i samtalen",
+            "you": "Du ble med i samtalen"
+        },
+        "m.call.hangup": {
+            "user": "%(senderName)s avsluttet samtalen",
+            "you": "Du avsluttet samtalen"
+        },
+        "m.call.invite": {
+            "dm_receive": "%(senderName)s ringer",
+            "dm_send": "Venter på svar",
+            "user": "%(senderName)s startet en samtale",
+            "you": "Du startet en samtale"
+        },
+        "m.emote": "* %(senderName)s %(emote)s",
+        "m.reaction": {
+            "user": "%(sender)s reagerte %(reaction)s på %(message)s",
+            "you": "Du reagerte %(reaction)s på %(message)s"
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Lyd",
+            "file": "Fil",
+            "image": "Bilde",
+            "poll": "Avstemning",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s: </bold> %(preview)s"
+    },
+    "export_chat": {
+        "cancelled": "Eksport avbrutt",
+        "cancelled_detail": "Eksporten ble avbrutt",
+        "confirm_stop": "Er du sikker på at du vil stoppe eksporteren av dine data? Hvis du gjør det, må du starte på nytt.",
+        "creating_html": "Oppretter HTML...",
+        "creating_output": "Opprette utdata...",
+        "creator_summary": "%(creatorName)s opprettet dette rommet.",
+        "current_timeline": "Gjeldende tidslinje",
+        "enter_number_between_min_max": "Skriv inn et tall mellom %(min)s og %(max)s",
+        "error_fetching_file": "Feil ved henting av fil",
+        "export_info": "Dette er starten på eksporten av <roomName/>. Eksportert av <exporterDetails/> den %(exportDate)s.",
+        "export_successful": "Eksport vellykket!",
+        "exported_n_events_in_time": {
+            "one": "Eksportert %(count)s hendelse på %(seconds)s sekunder",
+            "other": "Eksporterte %(count)s hendelser på %(seconds)s sekunder"
+        },
+        "exporting_your_data": "Eksportere dataene dine",
+        "fetched_n_events": {
+            "one": "Hentet %(count)s hendelse så langt",
+            "other": "Hentet %(count)s hendelser så langt"
+        },
+        "fetched_n_events_in_time": {
+            "one": "Hentet %(count)s hendelse på %(seconds)ss",
+            "other": "Hentet %(count)s hendelser på %(seconds)ss"
+        },
+        "fetched_n_events_with_total": {
+            "one": "Hentet %(count)s hendelse ut av %(total)s",
+            "other": "Hentet %(count)s hendelser ut av %(total)s"
+        },
+        "fetching_events": "Henter hendelser …",
+        "file_attached": "Fil vedlagt",
+        "format": "Format",
+        "from_the_beginning": "Fra begynnelsen",
+        "generating_zip": "Genererer en ZIP-fil",
+        "html": "HTML",
+        "html_title": "Eksporterte data",
+        "include_attachments": "Inkluder vedlegg",
+        "json": "JSON",
+        "media_omitted": "Media utelatt",
+        "media_omitted_file_size": "Media utelatt - filstørrelsesgrensen er overskredet",
+        "messages": "Meldinger",
+        "next_page": "Neste gruppe meldinger",
+        "num_messages": "Antall meldinger",
+        "num_messages_min_max": "Antall meldinger kan bare være et tall mellom %(min)s og %(max)s",
+        "number_of_messages": "Angi et antall meldinger",
+        "previous_page": "Forrige gruppe meldinger",
+        "processing": "Behandler ...",
+        "processing_event_n": "Behandler hendelse %(number)s ut av %(total)s",
+        "select_option": "Velg fra alternativene nedenfor for å eksportere chatter fra tidslinjen",
+        "size_limit": "Størrelsesgrense",
+        "size_limit_min_max": "Størrelse kan bare være et tall mellom %(min)s MB og %(max)s MB",
+        "size_limit_postfix": "MB",
+        "starting_export": "Starter eksport...",
+        "successful": "Eksport vellykket",
+        "successful_detail": "Eksporten var vellykket. Finn den i nedlastingsmappen.",
+        "text": "Ren tekst",
+        "title": "Eksporter chat",
+        "topic": "Emne: %(topic)s",
+        "unload_confirm": "Er du sikker på at du vil avslutte eksporten?"
+    },
+    "failed_load_async_component": "Klarte ikke laste! Sjekk nettverstilkoblingen din og prøv igjen.",
+    "feedback": {
+        "can_contact_label": "Dere kan kontakte meg hvis dere har oppfølgingsspørsmål",
+        "comment_label": "Kommentar",
+        "existing_issue_link": "Vennligst se <existingIssuesLink>eksisterende feil på Github</existingIssuesLink> først. Ingen match? <newIssueLink>Start en ny</newIssueLink>.",
+        "may_contact_label": "Dere kan kontakte meg hvis dere vil følge opp eller la meg teste ut kommende ideer",
+        "platform_username": "Plattformen og brukernavnet ditt vil bli notert, slik at vi kan bruke tilbakemeldingene dine så mye som mulig.",
+        "pro_type": "PROTIPS: Hvis du starter en feil, vennligst send inn <debugLogsLink> feilsøke logger</debugLogsLink> for å hjelpe oss med å spore opp problemet.",
+        "send_feedback_action": "Send tilbakemelding",
+        "sent": "Tilbakemelding sendt! Takk, vi setter pris på det!"
+    },
+    "file_panel": {
+        "empty_description": "Legg ved filer fra chat, eller bare dra og slipp dem hvor som helst i et rom.",
+        "empty_heading": "Ingen filer synlige i dette rommet",
+        "guest_note": "Du må <a>registrere deg</a> for å bruke denne funksjonaliteten",
+        "peek_note": "Du må bli med i rommet for å se filene dens"
+    },
+    "forward": {
+        "filter_placeholder": "Søk etter rom eller personer",
+        "message_preview_heading": "Meldingsforhåndsvisning",
+        "no_perms_title": "Du har ikke tillatelse til å gjøre dette",
+        "open_room": "Åpne rom",
+        "send_label": "Send",
+        "sending": "Sender",
+        "sent": "Sendt"
+    },
+    "identity_server": {
+        "change": "Bytt ut identitetstjener",
+        "change_prompt": "Koble fra identitetsserveren <current /> og koble til <new /> i stedet?",
+        "change_server_prompt": "Hvis du ikke ønsker å bruke <server /> til å oppdage og bli oppdaget av eksisterende kontakter som du kjenner, skriv inn en annen identitetstjener nedenfor.",
+        "changed": "Identitetsserveren din har blitt endret",
+        "checking": "Sjekker tjeneren",
+        "description_connected": "Du bruker for øyeblikket <server></server> til å oppdage og bli oppdaget av eksisterende kontakter du kjenner. Du kan endre identitetsserveren din nedenfor.",
+        "description_disconnected": "Du bruker for øyeblikket ikke en identitetsserver. For å oppdage og bli oppdaget av eksisterende kontakter du kjenner, legg til en nedenfor.",
+        "description_optional": "Å bruke en identitetstjener er valgfritt. Dersom du velger å ikke bruke en identitetstjener, vil du ikke kunne oppdages av andre brukere, og du vil ikke kunne invitere andre ut i fra E-postadresse eller telefonnummer.",
+        "disconnect": "Koble fra identitetsserveren",
+        "disconnect_anyway": "Koble fra likevel",
+        "disconnect_offline_warning": "Du bør <b>fjerne personopplysningene dine</b> fra identitetsserveren <idserver /> før du kobler fra. Dessverre er identitetsserveren <idserver /> for øyeblikket frakoblet eller kan ikke nås.",
+        "disconnect_personal_data_warning_1": "Du deler fortsatt <b>dine personopplysninger</b> på identitetsserveren <idserver />.",
+        "disconnect_personal_data_warning_2": "Vi anbefaler at du fjerner e-postadressene og telefonnumrene dine fra identitetsserveren før du kobler fra.",
+        "disconnect_server": "Koble fra identitetsserveren <idserver />?",
+        "disconnect_warning": "Hvis du kobler fra identitetsserveren din, betyr det at du ikke kan oppdages av andre brukere, og du kan ikke invitere andre via e-post eller telefon.",
+        "do_not_use": "Ikke bruk en identitetstjener",
+        "error_connection": "Kunne ikke koble til identitetsserveren",
+        "error_invalid": "Ikke en gyldig identitetsserver (statuskode%(code)s )",
+        "error_invalid_or_terms": "Tjenestevilkårene er ikke akseptert, eller identitetsserveren er ugyldig.",
+        "no_terms": "Identitetsserveren du har valgt, har ingen vilkår for bruk.",
+        "suggestions": "Du burde:",
+        "suggestions_1": "sjekk nettleserens plugins for alt som kan blokkere identitetsserveren (for eksempel Privacy Badger)",
+        "suggestions_2": "kontakt administratorene for identitetsserveren <idserver />",
+        "suggestions_3": "vent og prøv igjen senere",
+        "url": "Identitetstjener (%(server)s)",
+        "url_field_label": "Skriv inn en ny identitetstjener",
+        "url_not_https": "URL-adressen til identitetsserveren må være HTTPS"
+    },
+    "in_space": "I %(spaceName)s.",
+    "in_space1_and_space2": "I rom %(space1Name)s og%(space2Name)s.",
+    "in_space_and_n_other_spaces": {
+        "one": "I %(spaceName)s og et annet området",
+        "other": "I %(spaceName)s og %(count)s andre områder."
+    },
+    "incompatible_browser": {
+        "continue": "Fortsett uansett",
+        "description": "%(brand)s bruker noen nettleserfunksjoner som ikke er tilgjengelige i din nåværende nettleser. %(detail)s",
+        "detail_can_continue": "Hvis du fortsetter, kan noen funksjoner slutte å fungere, og det er en risiko for at du kan miste data i fremtiden.",
+        "detail_no_continue": "Prøv å oppdatere denne nettleseren hvis du ikke bruker den nyeste versjonen, og prøv igjen.",
+        "learn_more": "Les mer",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "For den beste opplevelsen, bruk<Chrome> Chrome</Chrome> ,<Firefox> Firefox</Firefox> ,<Edge> Edge</Edge> , eller<Safari> Safari</Safari> .",
+        "title": "%(brand)s støtter ikke denne nettleseren",
+        "use_desktop_heading": "Bruk %(brand)s Desktop i stedet",
+        "use_mobile_heading": "Bruk %(brand)s på mobilen i stedet",
+        "use_mobile_heading_after_desktop": "Eller bruk mobilappen vår",
+        "windows_64bit": "Windows (64-bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
+    },
+    "info_tooltip_title": "Informasjon",
+    "integration_manager": {
+        "connecting": "Kobler til integrasjonsadministrator …",
+        "error_connecting": "Integrasjonsadministratoren er frakoblet, eller den kan ikke nå hjemmeserveren din.",
+        "error_connecting_heading": "Kan ikke koble til integrasjonsadministrator",
+        "explainer": "Integreringsbehandlere mottar oppsettsdata, og kan endre på moduler, sende rominvitasjoner, og bestemme tilgangsnivår på dine vegne.",
+        "manage_title": "Behandle integreringer",
+        "toggle_label": "Aktiver integrasjonsbehandleren",
+        "use_im": "Bruk en integreringsbehandler til å behandle botter, moduler, og klistremerkepakker.",
+        "use_im_default": "Bruk en integreringsbehandler <b>(%(serverName)s)</b> til å behandle botter, moduler, og klistremerkepakker."
+    },
+    "integrations": {
+        "disabled_dialog_description": "Aktiver '%(manageIntegrations)s' i Innstillinger for å gjøre dette.",
+        "disabled_dialog_title": "Integreringer er skrudd av",
+        "impossible_dialog_description": "%(brand)s tillater ikke at du bruker en integrasjonsbehandler til å gjøre dette. Ta kontakt med en administrator.",
+        "impossible_dialog_title": "Integreringer er ikke tillatt"
+    },
+    "invite": {
+        "ask_anyway_description": "Kan ikke finne profiler for Matrix ID-ene som er oppført nedenfor - vil du starte en DM likevel?",
+        "ask_anyway_label": "Start DM uansett",
+        "ask_anyway_never_warn_label": "Start DM uansett og advar meg aldri igjen",
+        "email_caption": "Inviter gjennom E-post",
+        "email_limit_one": "Invitasjoner via e-post kan bare sendes én om gangen",
+        "email_use_default_is": "Bruk en identitetsserver til å invitere via e-post. <default>Bruk standard (%(defaultIdentityServerName)s) </default> eller administrer i <settings> Innstillinger</settings>.",
+        "email_use_is": "Bruk en identitetsserver til å invitere via e-post. Administreres i <settings> Innstillinger</settings>.",
+        "error_already_invited_room": "Brukeren er allerede invitert til rommet",
+        "error_already_invited_space": "Brukeren er allerede invitert til området",
+        "error_already_joined_room": "Brukeren er allerede i rommet",
+        "error_already_joined_space": "Brukeren er allerede med i området",
+        "error_bad_state": "Brukeren må få utestengelsen opphevet før vedkommende kan inviteres.",
+        "error_dm": "Vi kunne ikke opprette DM-en din.",
+        "error_find_room": "Noe gikk galt med å invitere brukerne.",
+        "error_find_user_description": "Følgende brukere eksisterer kanskje ikke eller er ugyldige, og kan ikke inviteres: %(csvNames)s",
+        "error_find_user_title": "Kunne ikke finne følgende brukere",
+        "error_invite": "Vi kunne ikke invitere disse brukerne. Sjekk av brukerne du vil invitere, og prøv igjen.",
+        "error_permissions_room": "Du har ikke tilgang til å invitere personer til dette rommet.",
+        "error_permissions_space": "Du har ikke tillatelse til å invitere folk til dette området.",
+        "error_profile_undisclosed": "Bruker eksisterer kanskje eller kanskje ikke",
+        "error_transfer_multiple_target": "En samtale kan bare overføres til en enkelt bruker.",
+        "error_unfederated_room": "Dette rommet er uføderert. Du kan ikke invitere personer fra eksterne servere.",
+        "error_unfederated_space": "Dette området er uføderert. Du kan ikke invitere personer fra eksterne servere.",
+        "error_unknown": "Ukjent tjenerfeil",
+        "error_user_not_found": "Brukeren eksisterer ikke",
+        "error_version_unsupported_room": "Brukerens hjemmeserver støtter ikke versjonen av rommet.",
+        "error_version_unsupported_space": "Brukerens hjemmeserver støtter ikke versjonen av området.",
+        "failed_generic": "Operasjon mislyktes",
+        "failed_title": "Klarte ikke invitere",
+        "invalid_address": "Adressen ble ikke gjenkjent",
+        "name_email_mxid_share_room": "Inviter noen ved å bruke navn, e-postadresse, brukernavn (lik <userId/>) eller <a> del dette rommet</a>.",
+        "name_email_mxid_share_space": "Inviter noen ved å bruke navn, e-postadresse, brukernavn (lik <userId/>) eller <a> del dette området</a>.",
+        "name_mxid_share_room": "Inviter noen ved å bruke navnet, brukernavnet (som <userId/>) eller <a>del dette rommet</a>.",
+        "name_mxid_share_space": "Inviter noen ved å bruke navnet sitt, brukernavnet (lik <userId/>) eller <a> dele dette området</a>.",
+        "recents_section": "Nylige samtaler",
+        "room_failed_partial": "Vi sendte de andre, men folkene nedenfor kunne ikke inviteres til <RoomName/>",
+        "room_failed_partial_title": "Noen invitasjoner kunne ikke sendes",
+        "room_failed_title": "Kunne ikke invitere brukere til %(roomName)s",
+        "send_link_prompt": "Eller send invitasjonslenke",
+        "start_conversation_name_email_mxid_prompt": "Start en samtale med noen ved å bruke navn, e-postadresse eller brukernavn (som<userId/>).",
+        "start_conversation_name_mxid_prompt": "Start en samtale med noen som bruker navnet eller brukernavnet deres (som<userId/>).",
+        "suggestions_disclaimer": "Noen forslag kan være skjult av personvernhensyn.",
+        "suggestions_disclaimer_prompt": "Hvis du ikke finner den du leter etter, kan du sende dem invitasjonslenken nedenfor.",
+        "suggestions_section": "Nylig direktemeldinger",
+        "to_room": "Inviter til %(roomName)s",
+        "to_space": "Inviter til %(spaceName)s",
+        "transfer_dial_pad_tab": "Nummerpanel",
+        "transfer_user_directory_tab": "Brukerkatalog",
+        "unable_find_profiles_description_default": "Kan ikke finne profiler for Matrix ID-ene som er oppført nedenfor - vil du invitere dem uansett?",
+        "unable_find_profiles_invite_label_default": "Inviter likevel",
+        "unable_find_profiles_invite_never_warn_label_default": "Inviter likevel, og advar meg aldri igjen",
+        "unable_find_profiles_title": "Følgende brukere eksisterer kanskje ikke",
+        "unban_first_title": "Brukeren kan ikke inviteres før utestengelsen er opphevet"
+    },
+    "inviting_user1_and_user2": "Inviterer %(user1)s og %(user2)s",
+    "inviting_user_and_n_others": {
+        "one": "Inviterer %(user)s og en annen",
+        "other": "Invitere %(user)s og %(count)s andre"
+    },
+    "items_and_n_others": {
+        "one": "<Items/> og en annen",
+        "other": "<Items/> og %(count)s andre"
+    },
+    "keyboard": {
+        "activate_button": "Aktiver valgt knapp",
+        "alt": "Alt",
+        "autocomplete_cancel": "Avbryt autofullføring",
+        "autocomplete_force": "Tving fullført",
+        "autocomplete_navigate_next": "Neste autofullføringsforslag",
+        "autocomplete_navigate_prev": "Forrige forslag til autofullføring",
+        "backspace": "Tilbake",
+        "cancel_reply": "Avbryt å svare på en melding",
+        "category_autocomplete": "Autofullfør",
+        "category_calls": "Samtaler",
+        "category_navigation": "Navigering",
+        "category_room_list": "Romliste",
+        "close_dialog_menu": "Lukk dialogboksen eller kontekstmenyen",
+        "composer_jump_end": "Hopp til slutten av editoren",
+        "composer_jump_start": "Gå til starten av editoren",
+        "composer_navigate_next_history": "Naviger til neste melding i editor historikken",
+        "composer_navigate_prev_history": "Naviger til forrige melding i editor historikken",
+        "composer_new_line": "Ny linje",
+        "composer_redo": "Gjør om redigering",
+        "composer_toggle_bold": "Veksle fet",
+        "composer_toggle_code_block": "Slå kodeblokk av/på",
+        "composer_toggle_italics": "Veksle kursiv",
+        "composer_toggle_link": "Slå av/på kobling",
+        "composer_toggle_quote": "Veksle siteringsformat",
+        "composer_undo": "Angre redigering",
+        "control": "Ctrl",
+        "dismiss_read_marker_and_jump_bottom": "Avføy lesekvitteringen og hopp ned til bunnen",
+        "end": "Slutt",
+        "enter": "Send",
+        "escape": "Esc",
+        "go_home_view": "Gå til Hjemvisning",
+        "home": "Hjem",
+        "jump_first_message": "Gå til første melding",
+        "jump_last_message": "Gå til siste melding",
+        "jump_room_search": "Gå til romsøk",
+        "jump_to_read_marker": "Gå til eldste uleste melding",
+        "keyboard_shortcuts_tab": "Åpne denne innstillingsfanen",
+        "navigate_next_history": "Neste nylig besøkte rom eller område",
+        "navigate_next_message_edit": "Naviger til neste melding for å redigere",
+        "navigate_prev_history": "Tidligere nylig besøkte rom eller område",
+        "navigate_prev_message_edit": "Naviger til forrige melding for å redigere",
+        "next_landmark": "Gå til neste landemerke",
+        "next_room": "Neste rom eller DM",
+        "next_unread_room": "Neste uleste rom eller DM",
+        "number": "[nummer]",
+        "open_user_settings": "Åpne brukerinnstillinger",
+        "page_down": "Side ned",
+        "page_up": "Side opp",
+        "prev_landmark": "Gå til forrige landemerke",
+        "prev_room": "Forrige rom eller DM",
+        "prev_unread_room": "Forrige ulest rom eller DM",
+        "room_list_collapse_section": "Skjul romlistedelen",
+        "room_list_expand_section": "Utvid romlistedelen",
+        "room_list_navigate_down": "Naviger ned i romlisten",
+        "room_list_navigate_up": "Naviger opp i romlisten",
+        "room_list_select_room": "Velg rom fra romlisten",
+        "scroll_down_timeline": "Rull ned i tidslinjen",
+        "scroll_up_timeline": "Rull opp i tidslinjen",
+        "search": "Søk (må være aktivert)",
+        "send_sticker": "Send et klistremerke",
+        "shift": "Shift",
+        "space": "Mellomrom",
+        "switch_to_space": "Bytt til område etter nummer",
+        "toggle_hidden_events": "Slå skjult hendelsessynlighet av/på",
+        "toggle_microphone_mute": "Slå av/på mikrofondemping",
+        "toggle_right_panel": "Slå av/på høyre panel",
+        "toggle_space_panel": "Slå av/på områdepanel",
+        "toggle_top_left_menu": "Slå på menyen øverst til venstre",
+        "toggle_webcam_mute": "Slå webkamera på/av",
+        "upload_file": "Last opp en fil"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "Tillat modus for bare skjermdeling",
+        "ask_to_join": "Aktiver be om å bli med",
+        "automatic_debug_logs": "Send automatisk feilsøkingslogger ved eventuelle feil",
+        "automatic_debug_logs_decryption": "Send automatisk feilsøkingslogger ved dekrypteringsfeil",
+        "automatic_debug_logs_key_backup": "Send automatisk feilsøkingslogger når sikkerhetskopiering av nøkler ikke fungerer",
+        "beta_description": "Hva er det neste for %(brand)s? Labs er den beste måten å få ting tidlig på, teste ut nye funksjoner og hjelpe til med å forme dem før de lanseres.",
+        "beta_feature": "Dette er en betafunksjon",
+        "beta_feedback_leave_button": "For å forlate betaen, gå til innstillingene dine.",
+        "beta_feedback_title": "%(featureName)s Beta-tilbakemeldinger",
+        "beta_section": "Kommende funksjonalitet",
+        "bridge_state": "Vis informasjon om broer i rominnstillinger",
+        "bridge_state_channel": "Kanal: <channelLink/>",
+        "bridge_state_creator": "Denne broen ble levert av <user />.",
+        "bridge_state_manager": "Denne broen administreres av <user />.",
+        "bridge_state_workspace": "Arbeidsområde: <networkLink/>",
+        "click_for_info": "Klikk for mer info",
+        "currently_experimental": "For tiden eksperimentell.",
+        "custom_themes": "Støtte for å legge til tilpassede temaer",
+        "dynamic_room_predecessors": "Dynamiske romforgjengere",
+        "dynamic_room_predecessors_description": "Aktiver MSC3946 (for å støtte sent ankomne romarkiver)",
+        "element_call_video_rooms": "Element Call videorom",
+        "exclude_insecure_devices": "Ekskluder usikre enheter når du sender/mottar meldinger",
+        "exclude_insecure_devices_description": "Når denne modusen er aktivert, deles ikke krypterte meldinger med ubekreftede enheter, og meldinger fra ubekreftede enheter vises som en feilmelding. Merk at hvis du aktiverer denne modusen, kan det hende at du ikke kan kommunisere med brukere som ikke har verifisert enhetene sine.",
+        "experimental_description": "Er du litt vågal? Prøv ut de nyeste ideene våre under utvikling. Disse funksjonene er ikke ferdigutviklet; de kan være ustabile, endres eller droppes helt. <a>Finn ut mer</a>.",
+        "experimental_section": "Tidlige forhåndsvisninger",
+        "extended_profiles_msc_support": "Krever at serveren din støtter MSC4133",
+        "feature_disable_call_per_sender_encryption": "Deaktiver kryptering per avsender for Element Call",
+        "feature_wysiwyg_composer_description": "Bruk rik tekst i stedet for Markdown i meldingsskriveren.",
+        "group_calls": "Ny opplevelse for gruppesamtaler",
+        "group_developer": "Utvikler",
+        "group_encryption": "Kryptering",
+        "group_experimental": "Eksperimentelt",
+        "group_messaging": "Meldinger",
+        "group_moderation": "Moderering",
+        "group_profile": "Profil",
+        "group_rooms": "Rom",
+        "group_spaces": "Områder",
+        "group_themes": "Temaer",
+        "group_threads": "Tråder",
+        "group_ui": "Brukergrensesnitt",
+        "group_voip": "Stemme og video",
+        "group_widgets": "Komponenter",
+        "hidebold": "Skjul varslingspunkt (bare vise tellemerker)",
+        "html_topic": "Vis HTML-visning av romemner",
+        "join_beta": "Bli med i betaen",
+        "join_beta_reload": "Hvis du blir med i betaen, lastes %(brand)s på nytt.",
+        "jump_to_date": "Gå til dato (legger til/jumptodate og hopp til datooverskrifter)",
+        "jump_to_date_msc_support": "Krever at serveren din støtter MSC3030",
+        "latex_maths": "Gjengi LaTeX-matematikk i meldinger",
+        "leave_beta": "Forlat betaen",
+        "leave_beta_reload": "Hvis du forlater betaen, lastes %(brand)s på nytt.",
+        "location_share_live": "Posisjonsdeling i sanntid",
+        "location_share_live_description": "Midlertidig implementering. Plasseringer beholdes i romhistorikken.",
+        "mjolnir": "Nye måter å ignorere folk på",
+        "msc3531_hide_messages_pending_moderation": "La moderatorer skjule meldinger i påvente av moderering.",
+        "new_room_list": "Aktiver ny romliste",
+        "notification_settings": "Nye varslingsinnstillinger",
+        "notification_settings_beta_caption": "Vi introduserer en enklere måte å endre varslingsinnstillingene dine på. Tilpass %(brand)s, akkurat slik du vil.",
+        "notification_settings_beta_title": "Innstillinger for varsler",
+        "notifications": "Aktiver varslingspanelet i romoverskriften",
+        "release_announcement": "Kunngjøring om ny versjon",
+        "render_reaction_images": "Gjengi egendefinerte bilder i reaksjoner",
+        "render_reaction_images_description": "Noen ganger omtalt som \"egendefinerte emojier\".",
+        "report_to_moderators": "Rapporter til moderatorene",
+        "report_to_moderators_description": "I rom som støtter moderering, kan du bruke \"Rapporter\"-knappen for å rapportere misbruk til rommoderatorene.",
+        "sliding_sync": "Sliding Sync modus",
+        "sliding_sync_description": "Under aktiv utvikling, kan ikke deaktiveres.",
+        "sliding_sync_disabled_notice": "Logg ut og inn igjen for å deaktivere",
+        "sliding_sync_server_no_support": "Serveren din mangler støtte",
+        "under_active_development": "Under aktiv utvikling.",
+        "unrealiable_e2e": "Upålitelig i krypterte rom",
+        "video_rooms": "Videorom",
+        "video_rooms_a_new_way_to_chat": "En ny måte å chatte over tale og video i %(brand)s.",
+        "video_rooms_always_on_voip_channels": "Videorom er VoIP-kanaler som alltid er på, og som er innebygd i et rom på %(brand)s.",
+        "video_rooms_beta": "Videorom er betafunksjonalitet",
+        "video_rooms_faq1_answer": "Bruk \"+\"-knappen i romdelen av venstre panel.",
+        "video_rooms_faq1_question": "Hvordan kan jeg opprette et videorom?",
+        "video_rooms_faq2_answer": "Ja, tidslinjen for chatten vises ved siden av videoen.",
+        "video_rooms_faq2_question": "Kan jeg bruke tekstchat ved siden av videosamtalen?",
+        "video_rooms_feedbackSubheading": "Takk for at du har prøver betaen, og vær så snill å gå inn i så mange detaljer som mulig, slik at vi kan forbedre den.",
+        "wysiwyg_composer": "Redigeringsprogram for rik tekst"
+    },
+    "labs_mjolnir": {
+        "advanced_warning": "⚠ Disse innstillingene er ment for avanserte brukere.",
+        "ban_reason": "Ignorert/Blokkert",
+        "error_adding_ignore": "Feil ved å legge til ignorert bruker/server",
+        "error_adding_list_description": "Bekreft rom-ID eller adresse, og prøv på nytt.",
+        "error_adding_list_title": "Feil ved abonnement på liste",
+        "error_removing_ignore": "Feil ved fjerning av ignorert bruker/server",
+        "error_removing_list_description": "Prøv på nytt, eller se konsollen for tips.",
+        "error_removing_list_title": "Feil ved avmelding fra listen",
+        "explainer_1": "Legg til brukere og servere du vil ignorere her. Bruk stjerner for å få %(brand)s til å samsvare med alle tegn. For eksempel vil <code>@bot:*</code> ignorere alle brukere som har navnet 'bot' på en hvilken som helst server.",
+        "explainer_2": "Ignorering av personer gjøres ved hjelp av utestengelseslister som inneholder regler for hvem som skal utestenges. Hvis du abonnerer på en utestengelsesliste, vil brukerne/serverne som er blokkert av denne listen, være skjult for deg.",
+        "lists": "Du abonnerer for øyeblikket på:",
+        "lists_description_1": "Å abonnere på en utestengelsesliste vil føre til at du blir med på den!",
+        "lists_description_2": "Hvis dette ikke er det du ønsker, bruk et annet verktøy for å ignorere brukere.",
+        "lists_heading": "Abonnerte lister",
+        "lists_new_label": "Rom-ID eller adressen til utestengelseslisten",
+        "no_lists": "Du er ikke abonnert på noen lister",
+        "personal_description": "Din personlige utestengelsesliste inneholder alle brukerne/serverne du personlig ikke vil se meldinger fra. Etter å ha ignorert din første bruker/server, vil et nytt rom dukke opp i romlisten din kalt '%(myBanList)s' - bli i dette rommet for å holde utestengelseslisten i kraft.",
+        "personal_empty": "Du har ikke ignorert noen.",
+        "personal_heading": "Personlig utestengelsesliste",
+        "personal_new_label": "Tjener- eller bruker-ID-en som skal ignoreres",
+        "personal_new_placeholder": "f.eks.: @bot:* eller example.org",
+        "personal_section": "Du ignorerer for øyeblikket:",
+        "room_name": "Min bannlysningsliste",
+        "room_topic": "Dette er listen over brukere/servere du har blokkert - ikke forlat rommet!",
+        "rules_empty": "Ingen",
+        "rules_server": "Tjenerregler",
+        "rules_title": "Regler for utestengelsesliste - %(roomName)s",
+        "rules_user": "Brukerregler",
+        "something_went_wrong": "Noe gikk galt. Prøv igjen eller se konsollen for tips.",
+        "title": "Ignorerte brukere",
+        "view_rules": "Vis reglene"
+    },
+    "language_dropdown_label": "Språk-nedfallsmeny",
+    "leave_room_dialog": {
+        "last_person_warning": "Du er den eneste personen her. Hvis du forlater rommet, vil ingen kunne bli med i det i fremtiden, inkludert deg.",
+        "leave_room_question": "Er du sikker på at du vil forlate rommet '%(roomName)s'?",
+        "leave_space_question": "Er du sikker på at du vil forlate området '%(spaceName)s'?",
+        "room_leave_admin_warning": "Du er den eneste administratoren i dette rommet. Hvis du forlater rommet, vil ingen kunne endre rominnstillinger eller utføre andre viktige handlinger.",
+        "room_leave_mod_warning": "Du er den eneste moderatoren i dette rommet. Hvis du forlater rommet, vil ingen kunne endre rominnstillinger eller utføre andre viktige handlinger.",
+        "room_rejoin_warning": "Dette rommet er ikke offentlig. Du vil ikke kunne bli med igjen uten en invitasjon.",
+        "space_rejoin_warning": "Dette området er ikke offentlig. Du vil ikke kunne bli med igjen uten en invitasjon."
+    },
+    "left_panel": {
+        "open_dial_pad": "Åpne nummerpanelet"
+    },
+    "lightbox": {
+        "rotate_left": "Roter til venstre",
+        "rotate_right": "Roter til høyre",
+        "title": "Bildevisning"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "Denne hjemmeserveren er ikke konfigurert til å vise kart.",
+        "MapStyleUrlNotReachable": "Denne hjemmeserveren er ikke riktig konfigurert til å vise kart, eller den konfigurerte kartserveren kan være utilgjengelig.",
+        "WebGLNotEnabled": "WebGL er nødvendig for å vise kart, aktiver det i nettleserinnstillingene.",
+        "click_drop_pin": "Klikk for å slippe en pin",
+        "click_move_pin": "Klikk for å flytte pin",
+        "close_sidebar": "Lukk sidepanel",
+        "error_fetch_location": "Kunne ikke hente posisjon",
+        "error_no_perms_description": "Du må ha de riktige tillatelsene for å dele posisjon i dette rommet.",
+        "error_no_perms_title": "Du har ikke tillatelse til å dele lokasjoner",
+        "error_send_description": "%(brand)s kunne ikke sende lokasjonen din. Prøv igjen senere.",
+        "error_send_title": "Vi kunne ikke sende lokasjonen din",
+        "error_sharing_live_location": "Det oppstod en feil under deling av live-posisjonen din",
+        "error_stopping_live_location": "Det oppstod en feil mens du stoppet live-posisjonen din",
+        "expand_map": "Utvid kartet",
+        "failed_generic": "Kunne ikke hente posisjonen din. Vennligst prøv igjen senere.",
+        "failed_load_map": "Kan ikke laste inn kartet",
+        "failed_permission": "%(brand)s ble nektet tillatelse til å hente posisjonen din. Tillat posisjonstilgang i nettleserinnstillingene.",
+        "failed_timeout": "Tidsavbrudd for forsøk på å hente posisjonen din. Prøv igjen senere.",
+        "failed_unknown": "Ukjent feil under henting av lokasjon. Prøv igjen senere.",
+        "find_my_location": "Finn min lokasjon",
+        "live_description": "%(displayName)s Live-posisjon",
+        "live_enable_description": "Merk: Dette er en laboratoriefunksjon som bruker en midlertidig implementering. Dette betyr at du ikke vil kunne slette posisjonsloggen din, og avanserte brukere vil kunne se posisjonsloggen din selv etter at du slutter å dele posisjonen din med dette rommet.",
+        "live_enable_heading": "Deling av lokasjon i sanntid",
+        "live_location_active": "Du deler posisjonen din i sanntid",
+        "live_location_enabled": "Live-posisjonering aktivert",
+        "live_location_ended": "Live posisjon avsluttet",
+        "live_location_error": "Feil på direktesendt posisjon",
+        "live_locations_empty": "Ingen live posisjon",
+        "live_share_button": "Del i %(duration)s",
+        "live_toggle_label": "Aktiver deling av posisjoner i sanntid",
+        "live_until": "Live til %(expiryTime)s",
+        "live_update_time": "Oppdatert %(humanizedUpdateTime)s",
+        "loading_live_location": "Laster inn direkte posisjon …",
+        "location_not_available": "Lokasjon er ikke tilgjengelig",
+        "map_feedback": "Tilbakemelding på kart",
+        "mapbox_logo": "Mapbox-logo",
+        "reset_bearing": "Tilbakestill peiling mot nord",
+        "share_button": "Del posisjon",
+        "share_type_live": "Min posisjon i sanntid",
+        "share_type_own": "Min nåværende lokasjon",
+        "share_type_pin": "Slipp en pin",
+        "share_type_prompt": "Hvilken type lokasjon ønsker du å dele?",
+        "toggle_attribution": "Slå av/på attribusjon"
+    },
+    "member_list": {
+        "count": {
+            "one": "%(count)s Medlem",
+            "other": "%(count)s Medlemmer"
+        },
+        "filter_placeholder": "Filtrer rommets medlemmer",
+        "invite_button_no_perms_tooltip": "Du har ikke tillatelse til å invitere brukere",
+        "invited_label": "Invitert",
+        "no_matches": "Ingen treff",
+        "power_label": "%(userName)s (styrkenivå %(powerLevelNumber)s)"
+    },
+    "member_list_back_action_label": "Medlemmer av rommet",
+    "message_edit_dialog_title": "Meldingsredigeringer",
+    "migrating_crypto": "Hold deg fast. Vi oppdaterer %(brand)s for å gjøre kryptering raskere og mer pålitelig.",
+    "mobile_guide": {
+        "toast_accept": "Bruk app",
+        "toast_description": "%(brand)s er eksperimentell på en mobil nettleser. For en bedre opplevelse og de nyeste funksjonene, bruk vår gratis app.",
+        "toast_title": "Bruk appen for en bedre opplevelse"
+    },
+    "name_and_id": "%(name)s (%(userId)s)",
+    "no_more_results": "Ingen flere resultater",
+    "notif_panel": {
+        "empty_description": "Du har ingen synlige varsler.",
+        "empty_heading": "Du har lest alt."
+    },
+    "notifications": {
+        "all_messages": "Alle meldinger",
+        "all_messages_description": "Bli varslet for hver melding",
+        "class_global": "Globalt",
+        "class_other": "Andre",
+        "default": "Standard",
+        "default_settings": "Match standardinnstillingene",
+        "email_pusher_app_display_name": "E-postvarsler",
+        "enable_prompt_toast_description": "Aktiver skrivebordsvarsler",
+        "enable_prompt_toast_title": "Varsler",
+        "enable_prompt_toast_title_from_message_send": "Ikke gå glipp av noen svar",
+        "error_change_title": "Endre varslingsinnstillinger",
+        "keyword": "Nøkkelord",
+        "keyword_new": "Nytt nøkkelord",
+        "level_activity": "Aktivitet",
+        "level_highlight": "Fremheve",
+        "level_muted": "Dempet",
+        "level_none": "Ingen",
+        "level_notification": "Varsel",
+        "level_unsent": "Ikke sendt",
+        "mark_all_read": "Merk alle som lest",
+        "mentions_and_keywords": "@mentions & nøkkelord",
+        "mentions_and_keywords_description": "Bli varslet bare med omtaler og nøkkelord som konfigurert i <a> innstillingene dine </a>",
+        "mentions_keywords": "Omtaler og nøkkelord",
+        "message_didnt_send": "Meldingen ble ikke sendt. Klikk for informasjon.",
+        "mute_description": "Du vil ikke få noen varsler",
+        "mute_room": "Demp rommet"
+    },
+    "notifier": {
+        "m.key.verification.request": "%(name)s ber om verifisering"
+    },
+    "onboarding": {
+        "create_room": "Opprett en gruppechat",
+        "explore_rooms": "Utforsk offentlige rom",
+        "has_avatar_label": "Flott, det vil hjelp folk å ha tillit til at det er deg",
+        "intro_byline": "Ta eierskap til samtalene dine.",
+        "intro_welcome": "Velkommen til %(appName)s",
+        "no_avatar_label": "Legg til et bilde slik at folk vet at det er deg.",
+        "send_dm": "Send en direktemelding",
+        "welcome_detail": "Nå, la oss hjelpe deg med å komme i gang",
+        "welcome_user": "Velkommen, %(name)s"
+    },
+    "pill": {
+        "permalink_other_room": "Melding i %(room)s",
+        "permalink_this_room": "Melding fra %(user)s"
+    },
+    "poll": {
+        "create_poll_action": "Opprett avstemning",
+        "create_poll_title": "Opprett avstemning",
+        "disclosed_notes": "Deltakerne ser resultatene så snart de har stemt",
+        "edit_poll_title": "Rediger avstemning",
+        "end_description": "Er du sikker på at du vil avslutte denne avstemningen? Dette vil vise de endelige resultatene av avstemningen og hindre folk i å kunne stemme.",
+        "end_message": "Avstemningen er avsluttet. Topp svar: %(topAnswer)s",
+        "end_message_no_votes": "Avstemningen er avsluttet. Ingen stemmer ble avgitt.",
+        "end_title": "Avslutt avstemning",
+        "error_ending_description": "Beklager, avstemningen ble ikke avsluttet. Prøv igjen.",
+        "error_ending_title": "Kunne ikke avslutte avstemningen",
+        "error_voting_description": "Beklager, stemmen din ble ikke registrert. Prøv igjen.",
+        "error_voting_title": "Stemme ikke registrert",
+        "failed_send_poll_description": "Beklager, avstemningen du prøvde å opprette ble ikke lagt ut.",
+        "failed_send_poll_title": "Kunne ikke legge ut avstemningen",
+        "notes": "Resultatene avsløres først når du avslutter avstemningen",
+        "options_add_button": "Legg til alternativ",
+        "options_heading": "Opprett alternativer",
+        "options_label": "Alternativ %(number)s",
+        "options_placeholder": "Skriv et alternativ",
+        "topic_heading": "Hva er avstemningsspørsmålet eller emnet ditt?",
+        "topic_label": "Spørsmål eller emne",
+        "topic_placeholder": "Skriv noe...",
+        "total_decryption_errors": "På grunn av dekrypteringsfeil kan det hende at noen stemmer ikke blir talt opp",
+        "total_n_votes": {
+            "one": "%(count)s avgitte stemme. Stem for å se resultatene",
+            "other": "%(count)s avgitte stemmer. Stem for å se resultatene"
+        },
+        "total_n_votes_voted": {
+            "one": "Basert på %(count)s stemme",
+            "other": "Basert på %(count)s stemmer"
+        },
+        "total_no_votes": "Ingen stemmer avgitt",
+        "total_not_ended": "Resultatene vil være synlige når avstemningen er avsluttet",
+        "type_closed": "Lukket avstemning",
+        "type_heading": "Type avstemning",
+        "type_open": "Åpen avstemning",
+        "unable_edit_description": "Beklager, du kan ikke redigere en meningsmåling etter at det er avgitt stemmer.",
+        "unable_edit_title": "Kan ikke redigere avstemningen"
+    },
+    "power_level": {
+        "admin": "Administrator",
+        "custom": "Egendefinert (%(level)s)",
+        "custom_level": "Tilpasset nivå",
+        "default": "Standard",
+        "label": "Tilgangsnivå",
+        "moderator": "Moderator",
+        "restricted": "Begrenset"
+    },
+    "powered_by_matrix": "Drevet av Matrix",
+    "powered_by_matrix_with_logo": "Desentralisert, kryptert chat og samarbeid drevet av $matrixLogo",
+    "presence": {
+        "away": "Borte",
+        "busy": "Opptatt",
+        "idle": "Inaktiv",
+        "idle_for": "Inaktiv for %(duration)s",
+        "offline": "Frakoblet",
+        "offline_for": "Frakoblet for %(duration)s",
+        "online": "Tilkoblet",
+        "online_for": "På nett i %(duration)s",
+        "unknown": "Ukjent",
+        "unknown_for": "Ukjent i %(duration)s",
+        "unreachable": "Brukerens server er utilgjengelig"
+    },
+    "quick_settings": {
+        "all_settings": "Alle innstillinger",
+        "metaspace_section": "Fest til sidepanel",
+        "sidebar_settings": "Flere alternativer",
+        "title": "Hurtiginnstillinger"
+    },
+    "quit_warning": {
+        "call_in_progress": "Du ser ut til å være i en samtale, er du sikker på at du vil slutte?",
+        "file_upload_in_progress": "Du ser til å laste opp filer, er du sikker på at du vil avslutte?"
+    },
+    "redact": {
+        "confirm_button": "Bekreft fjerning",
+        "confirm_description": "Er du sikker på at du ønsker å fjerne (slette) denne hendelsen?",
+        "confirm_description_state": "Vær oppmerksom på at fjerning av romendringer som dette, kan endre romendringen.",
+        "error": "Du kan ikke slette denne meldingen. (%(code)s)",
+        "ongoing": "Fjerner …",
+        "reason_label": "Årsak (valgfritt)"
+    },
+    "report_content": {
+        "description": "Rapportering av denne meldingen vil sende sin unike 'hendelses-ID' til administratoren av hjemmeserveren din. Hvis meldinger i dette rommet er kryptert, vil ikke hjemmeserveradministratoren kunne lese meldingsteksten eller se noen filer eller bilder.",
+        "disagree": "Uenig",
+        "error_create_room_moderation_bot": "Kan ikke opprette rom med modereringsbot",
+        "hide_messages_from_user": "Sjekk om du vil skjule alle nåværende og fremtidige meldinger fra denne brukeren.",
+        "ignore_user": "Ignorer bruker",
+        "illegal_content": "Ulovlig innhold",
+        "missing_reason": "Vennligst fyll ut hvorfor du rapporterer.",
+        "nature": "Velg en type og beskriv hva som gjør denne meldingen støtende.",
+        "nature_disagreement": "Det denne brukeren skriver er feil.\nDette vil bli rapportert til rommoderatorene.",
+        "nature_illegal": "Denne brukeren viser ulovlig oppførsel, for eksempel ved å utlevere mennesker eller true med vold.\nDette vil bli rapportert til rommoderatorene som kan eskalere dette til juridiske myndigheter.",
+        "nature_nonstandard_admin": "Dette rommet er dedikert til ulovlig eller giftig innhold, eller moderatorene klarer ikke å moderere ulovlig eller giftig innhold.\nDette vil bli rapportert til administratorene av%(homeserver)s.",
+        "nature_nonstandard_admin_encrypted": "Dette rommet er dedikert til ulovlig eller giftig innhold, eller moderatorene klarer ikke å moderere ulovlig eller giftig innhold.\nDette vil bli rapportert til administratorene av%(homeserver)s. Administratorene vil IKKE kunne lese det krypterte innholdet i dette rommet.",
+        "nature_other": "En hvilken som helst annen grunn. Vennligst beskriv problemet.\nDette vil bli rapportert til rommoderatorene.",
+        "nature_spam": "Denne brukeren spammer rommet med annonser, lenker til annonser eller til propaganda.\nDette vil bli rapportert til rommoderatorene.",
+        "nature_toxic": "Denne brukeren viser giftig oppførsel, for eksempel ved å fornærme andre brukere eller dele innhold kun for voksne i et familievennlig rom eller på annen måte bryte reglene i dette rommet.\nDette vil bli rapportert til rommoderatorene.",
+        "other_label": "Andre",
+        "report_content_to_homeserver": "Rapportere innhold til hjemmeserveradministratoren",
+        "report_entire_room": "Rapporter hele rommet",
+        "spam_or_propaganda": "Spam eller propaganda",
+        "toxic_behaviour": "Giftig oppførsel"
+    },
+    "report_room": {
+        "description": "Rapporter dette rommet til hjemmeserveradministratoren din. Dette vil sende rommets unike ID, men hvis meldinger er kryptert, vil ikke administratoren kunne lese dem eller se delte filer.",
+        "reason_label": "Beskriv årsaken"
+    },
+    "restore_key_backup_dialog": {
+        "count_of_decryption_failures": "Kunne ikke dekryptere %(failedCount)s økter!",
+        "count_of_successfully_restored_keys": "Vellykket gjenoppretting av %(sessionCount)s nøkler",
+        "enter_key_description": "Få tilgang til den sikre meldingsloggen din og konfigurer sikker melding ved å skrive inn sikkerhetsnøkkelen.",
+        "enter_key_title": "Skriv inn gjenopprettingsnøkkel",
+        "enter_phrase_description": "Få tilgang til den sikre meldingsloggen din og sett opp sikre meldinger ved å skrive inn sikkerhetsfrasen.",
+        "enter_phrase_title": "Skriv inn sikkerhetsfrase",
+        "incorrect_security_phrase_dialog": "Sikkerhetskopiering kunne ikke dekrypteres med denne sikkerhetsfrasen: Kontroller at du skrev inn riktig sikkerhetsfrase.",
+        "incorrect_security_phrase_title": "Feil sikkerhetsfrase",
+        "key_backup_warning": "<b>Advarsel</b>: Du bør bare konfigurere sikkerhetskopiering av nøkler fra en pålitelig datamaskin.",
+        "key_fetch_in_progress": "Henter nøkler fra serveren...",
+        "key_forgotten_text": "Hvis du har glemt sikkerhetsnøkkelen, kan du <button> konfigurere nye gjenopprettingsalternativer </button>",
+        "key_is_invalid": "Ikke en gyldig gjenopprettingsnøkkel",
+        "key_is_valid": "Dette ser ut som en gyldig sikkerhetsnøkkel!",
+        "keys_restored_title": "Nøklene ble gjenopprettet",
+        "load_error_content": "Klarte ikke å laste sikkerhetskopi-status",
+        "load_keys_progress": "%(completed)s av %(total)s nøkler gjenopprettet",
+        "no_backup_error": "Ingen sikkerhetskopier ble funnet!",
+        "phrase_forgotten_text": "Hvis du har glemt sikkerhetsfrasen, kan du <button1>bruke sikkerhetsnøkkelen</button1> eller <button2>konfigurere nye gjenopprettingsalternativer</button2>",
+        "recovery_key_mismatch_description": "Sikkerhetskopien kunne ikke dekrypteres med denne gjenopprettingsnøkkelen: Kontroller at du har angitt riktig gjenopprettingsnøkkel.",
+        "recovery_key_mismatch_title": "Manglende samsvar mellom gjenopprettingsnøkler",
+        "restore_failed_error": "Kan ikke gjenopprette sikkerhetskopien"
+    },
+    "right_panel": {
+        "add_integrations": "Legg til utvidelser",
+        "add_topic": "Legg til emne",
+        "extensions_button": "Utvidelser",
+        "extensions_empty_description": "Velg \"%(addIntegrations)s\" for å bla gjennom og legge til utvidelser i dette rommet",
+        "extensions_empty_title": "Øk produktiviteten med flere verktøy, widgets og bots",
+        "files_button": "Filer",
+        "pinned_messages": {
+            "empty_description": "Velg en melding og velg «%(pinAction)s» for å inkludere den her.",
+            "empty_title": "Fest viktige meldinger slik at de er lette å finne igjen",
+            "header": {
+                "one": "1 festet melding",
+                "other": "%(count)s festede meldinger"
+            },
+            "limits": {
+                "one": "",
+                "other": "Du kan bare feste opptil %(count)s widgets"
+            },
+            "menu": "Åpne meny",
+            "release_announcement": {
+                "close": "Ok",
+                "description": "Her finner du alle festede meldinger. Hold musepekeren over en melding og velg \"Fest\" for å legge den til.",
+                "title": "Alle nye festede meldinger"
+            },
+            "reply_thread": "Svar på en <link> trådmelding </link>",
+            "unpin_all": {
+                "button": "Løsne alle meldinger",
+                "content": "Vær sikker på at du virkelig ønsker å fjerne alle festede meldinger. Denne handlingen kan ikke angres.",
+                "title": "Løsne alle meldinger?"
+            },
+            "view": "Vis i tidslinjen"
+        },
+        "pinned_messages_button": "Festede meldinger",
+        "poll": {
+            "active_heading": "Aktive avstemninger",
+            "empty_active": "Det er ingen aktive avstemninger i dette rommet",
+            "empty_active_load_more": "Det er ingen aktive avstemninger. Last inn flere avstemninger for å se avstemninger for tidligere måneder",
+            "empty_active_load_more_n_days": {
+                "one": "Det er ingen aktive meningsmålinger det siste døgnet. Last inn flere meningsmålinger for å se avstemninger for tidligere måneder",
+                "other": "Det er ingen aktive meningsmålinger for de siste %(count)s dager. Last inn flere meningsmålinger for å se avstemninger for tidligere måneder"
+            },
+            "empty_past": "Det er ingen tidligere avstemninger i dette rommet",
+            "empty_past_load_more": "Det finnes ingen tidligere avstemninger. Last inn flere avstemninger for å se avstemninger for tidligere måneder",
+            "empty_past_load_more_n_days": {
+                "one": "Det er ingen tidligere meningsmålinger for den siste dagen. Last inn flere meningsmålinger for å se avstemninger for tidligere måneder",
+                "other": "Det er ingen tidligere meningsmålinger for de siste %(count)s dager. Last inn flere meningsmålinger for å se avstemninger for tidligere måneder"
+            },
+            "final_result": {
+                "one": "Endelig resultat basert på %(count)s stemme",
+                "other": "Endelig resultat basert på %(count)s stemmer"
+            },
+            "load_more": "Last inn flere avstemninger",
+            "loading": "Laster inn avstemninger",
+            "past_heading": "Tidligere avstemninger",
+            "view_in_timeline": "Se avstemningen i tidslinjen",
+            "view_poll": "Se avstemningen"
+        },
+        "polls_button": "Avstemninger",
+        "room_summary_card": {
+            "title": "Rominfo"
+        },
+        "thread_list": {
+            "context_menu_label": "Trådalternativer"
+        },
+        "video_room_chat": {
+            "title": "Chat"
+        }
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "Denne invitasjonen ble sendt til %(email)s som ikke er knyttet til kontoen din",
+        "3pid_invite_email_not_found_account_room": "Denne invitasjonen %(roomName)s ble sendt til %(email)s som ikke er knyttet til kontoen din",
+        "3pid_invite_error_description": "En feil (%(errcode)s ) ble returnert mens du prøvde å validere invitasjonen din. Du kan prøve å gi denne informasjonen videre til personen som inviterte deg.",
+        "3pid_invite_error_invite_action": "Forsøk å bli med likevel",
+        "3pid_invite_error_invite_subtitle": "Du kan bare bli med med en fungerende invitasjon.",
+        "3pid_invite_error_public_subtitle": "Du kan fortsatt bli med her.",
+        "3pid_invite_error_title": "Noe gikk galt med invitasjonen din.",
+        "3pid_invite_error_title_room": "Noe gikk galt med invitasjonen din til %(roomName)s",
+        "3pid_invite_no_is_subtitle": "Bruk en identitetstjener i Innstillinger for å motta invitasjoner direkte i %(brand)s.",
+        "banned_by": "Du ble utestengt av %(memberName)s",
+        "banned_from_room_by": "Du ble bannlyst fra %(roomName)s av %(memberName)s",
+        "context_menu": {
+            "copy_link": "Kopier romlenke",
+            "favourite": "Favoritt",
+            "forget": "Glem rommet",
+            "low_priority": "Lav Prioritet",
+            "mark_read": "Marker som lest",
+            "mark_unread": "Marker som ulest",
+            "notifications_default": "Match standardinnstillingen",
+            "notifications_mute": "Demp rom",
+            "title": "Rominnstillinger",
+            "unfavourite": "Favorittmerket"
+        },
+        "creating_room_text": "Vi oppretter et rom med %(names)s",
+        "dm_invite_action": "Begynn å chatte",
+        "dm_invite_subtitle": "<userName/> ønsker å chatte",
+        "dm_invite_title": "Vil du prate med %(user)s?",
+        "drop_file_prompt": "Slipp ned en fil her for å laste opp",
+        "edit_topic": "Rediger emne",
+        "error_3pid_invite_email_lookup": "Kan ikke finne bruker via e-post",
+        "error_cancel_knock_title": "Kunne ikke avbryte",
+        "error_join_403": "Du trenger en invitasjon for å få tilgang til dette rommet.",
+        "error_join_404_1": "Du forsøkte å bli med ved hjelp av en rom-ID uten å oppgi en liste over servere du kan bli med gjennom. Rom-ID-er er interne identifikatorer og kan ikke brukes til å bli med i et rom uten ytterligere informasjon.",
+        "error_join_404_2": "Hvis du vet en romadresse, prøv å bli med gjennom adressen i stedet.",
+        "error_join_404_invite": "Personen som inviterte deg har allerede forlatt, eller serveren deres er frakoblet.",
+        "error_join_404_invite_same_hs": "Personen som inviterte deg har allerede forlatt rommet.",
+        "error_join_connection": "Det oppstod en feil ved å bli med.",
+        "error_join_incompatible_version_1": "Beklager, hjemmeserveren din er for gammel til å delta her.",
+        "error_join_incompatible_version_2": "Vennligst kontakt din hjemmeserveradministrator.",
+        "error_join_title": "Kunne ikke bli med",
+        "error_jump_to_date": "Server returnert %(statusCode)s med feilkode %(errorCode)s",
+        "error_jump_to_date_connection": "Det oppstod en nettverksfeil mens du prøvde å finne og hoppe til den gitte datoen. Hjemmeserveren din kan være nede, eller det var bare et midlertidig problem med internettforbindelsen. Vennligst prøv igjen. Hvis dette fortsetter, vennligst kontakt din hjemmeserveradministrator.",
+        "error_jump_to_date_details": "Feildetaljer",
+        "error_jump_to_date_not_found": "Vi klarte ikke å finne en hendelse som ser fremover fra %(dateString)s. Prøv å velge en tidligere dato.",
+        "error_jump_to_date_send_logs_prompt": "Send inn <debugLogsLink> feilsøkingslogger </debugLogsLink> for å hjelpe oss med å spore opp problemet.",
+        "error_jump_to_date_title": "Kan ikke finne hendelsen på den datoen",
+        "face_pile_summary": {
+            "one": "%(count)s person du kjenner har allerede blitt med",
+            "other": "%(count)s personer du kjenner har allerede blitt med"
+        },
+        "face_pile_tooltip_label": {
+            "one": "Vis 1 medlem",
+            "other": "Vis alle %(count)s medlemmer"
+        },
+        "face_pile_tooltip_shortcut": "Inkludert %(commaSeparatedMembers)s",
+        "face_pile_tooltip_shortcut_joined": "Inkludert deg, %(commaSeparatedMembers)s",
+        "failed_reject_invite": "Kunne ikke avvise invitasjonen",
+        "forget_room": "Glem dette rommet",
+        "forget_space": "Glem dette området",
+        "header": {
+            "n_people_asking_to_join": {
+                "one": "Ber om å få bli med",
+                "other": "%(count)s personer som ber om å bli med"
+            },
+            "room_is_public": "Dette rommet er offentlig"
+        },
+        "header_avatar_open_settings_label": "Åpne rominnstillinger",
+        "header_face_pile_tooltip": "Personer",
+        "header_untrusted_label": "Ikke betrodd",
+        "inaccessible": "Dette området er ikke tilgjengelig på dette tidspunktet.",
+        "inaccessible_name": "%(roomName)s er ikke tilgjengelig for øyeblikket.",
+        "inaccessible_subtitle_1": "Prøv igjen senere, eller be en områdeadministrator om å sjekke om du har tilgang.",
+        "inaccessible_subtitle_2": "%(errcode)s ble returnert mens du prøvde å få tilgang til rommet eller området. Hvis du tror du ser denne meldingen ved en feil, kan du <issueLink>sende inn en feilrapport</issueLink>.",
+        "intro": {
+            "dm_caption": "Bare dere to er i denne samtalen, med mindre noen av dere inviterer noen til å bli med.",
+            "enable_encryption_prompt": "Aktiver kryptering i innstillinger.",
+            "encrypted_3pid_dm_pending_join": "Når alle har blitt med, vil du kunne chatte",
+            "no_avatar_label": "Legg til et bilde så folk lettere kan finne rommet ditt.",
+            "no_topic": "<a>Legg til et tema</a> for hjelpe folk å forstå hva dette handler om.",
+            "private_unencrypted_warning": "Dine private meldinger er vanligvis kryptert, men dette rommet er det ikke. Vanligvis skyldes dette at en enhet eller metode som ikke støttes, som e-postinvitasjoner.",
+            "room_invite": "Inviter til bare dette rommet",
+            "send_message_start_dm": "Send din første melding for å invitere <displayName/> til chat",
+            "start_of_dm_history": "Dette er begynnelsen på direktemeldingshistorikken din med <displayName/>.",
+            "start_of_room": "Dette er starten på <roomName/>.",
+            "topic": "Emne: %(topic)s ",
+            "topic_edit": "Emne: %(topic)s (<a>rediger</a>)",
+            "unencrypted_warning": "Ende-til-ende-kryptering er ikke aktivert",
+            "user_created": "%(displayName)s opprettet dette rommet.",
+            "you_created": "Du opprettet dette rommet."
+        },
+        "invite_email_mismatch_suggestion": "Del denne e-posten i Innstillinger for å motta invitasjoner direkte i %(brand)s.",
+        "invite_sent_to_email": "Denne invitasjonen ble sendt til %(email)s",
+        "invite_sent_to_email_room": "Denne invitasjonen %(roomName)s ble sendt til %(email)s",
+        "invite_subtitle": "<userName/> inviterte deg",
+        "invite_this_room": "Inviter til dette rommet",
+        "invite_title": "Vil du bli med i %(roomName)s?",
+        "inviter_unknown": "Ukjent",
+        "invites_you_text": "<inviter/> inviterer deg",
+        "join_button_account": "Registrer deg",
+        "join_failed_needs_invite": "For å se %(roomName)s, trenger du en invitasjon",
+        "join_the_discussion": "Bli med i diskusjonen",
+        "join_title": "Bli med på rommet for å delta",
+        "join_title_account": "Bli med i samtalen med en konto",
+        "joining": "Blir med …",
+        "joining_room": "Blir med i rommet …",
+        "joining_space": "Bli med i området...",
+        "jump_read_marker": "Gå til første uleste melding.",
+        "jump_to_bottom_button": "Hopp bort til de nyeste meldingene",
+        "jump_to_date": "Gå til dato",
+        "jump_to_date_beginning": "Begynnelsen av rommet",
+        "jump_to_date_prompt": "Velg en dato å hoppe til",
+        "kick_reason": "Årsak: %(reason)s",
+        "kicked_by": "Du ble fjernet av %(memberName)s",
+        "kicked_from_room_by": "Du ble fjernet fra %(roomName)s av %(memberName)s",
+        "knock_cancel_action": "Avbryt forespørsel",
+        "knock_denied_subtitle": "Siden du har blitt nektet tilgang, kan du ikke bli med på nytt med mindre du er invitert av administratoren eller moderatoren for gruppen.",
+        "knock_denied_title": "Du har blitt nektet tilgang",
+        "knock_message_field_placeholder": "Melding (valgfritt)",
+        "knock_prompt": "Be om å bli med?",
+        "knock_prompt_name": "Be om å få bli med %(roomName)s?",
+        "knock_send_action": "Be om tilgang",
+        "knock_sent": "Forespørsel om å bli med sendt",
+        "knock_sent_subtitle": "Din forespørsel om å bli med venter.",
+        "knock_subtitle": "Du må få tilgang til dette rommet for å se eller delta i samtalen. Du kan sende en forespørsel om å bli med nedenfor.",
+        "leave_error_title": "En feil oppsto når du prøvde å forlate rommet",
+        "leave_server_notices_description": "Dette rommet brukes til viktige meldinger fra hjemmeserveren, så du kan ikke forlate det.",
+        "leave_server_notices_title": "Kan ikke forlate servermeldingsrommet",
+        "leave_unexpected_error": "Uventet serverfeil ved å prøve å forlate rommet",
+        "link_email_to_receive_3pid_invite": "Koble denne e-posten til kontoen din i Innstillinger for å motta invitasjoner direkte i %(brand)s.",
+        "loading_preview": "Laster forhåndsvisning",
+        "no_peek_join_prompt": "%(roomName)s kan ikke forhåndsvises. Vil du bli med i den?",
+        "no_peek_no_name_join_prompt": "Det er ingen forhåndsvisning, vil du bli med?",
+        "not_found_subtitle": "Er du sikker på at du er på rett sted?",
+        "not_found_title": "Dette rommet eller området eksisterer ikke.",
+        "not_found_title_name": "%(roomName)s eksisterer ikke.",
+        "peek_join_prompt": "Du forhåndsviser %(roomName)s. Vil du bli med?",
+        "pinned_message_badge": "Festet melding",
+        "pinned_message_banner": {
+            "button_close_list": "Lukk liste",
+            "button_view_all": "Vis alle",
+            "description": "Dette rommet har festede meldinger. Klikk for å se dem.",
+            "go_to_message": "Vis den festede meldingen i tidslinjen.",
+            "title": "<bold>%(index)s av %(length)s</bold> festede meldinger"
+        },
+        "read_topic": "Klikk for å lese emnet",
+        "rejecting": "Avviser invitasjon...",
+        "rejoin_button": "Bli med igjen",
+        "search": {
+            "all_rooms_button": "Søk i alle rom",
+            "placeholder": "Søk i meldinger...",
+            "summary": {
+                "one": "1 resultat funnet for \"<query/>\"",
+                "other": "%(count)s resultater funnet for \"<query/>\""
+            },
+            "this_room_button": "Søk i dette rommet"
+        },
+        "status_bar": {
+            "delete_all": "Slett alle",
+            "exceeded_resource_limit": "Meldingen din ble ikke sendt fordi denne hjemmeserveren har overskredet en ressursgrense. Ta <a> kontakt med tjenesteadministratoren din </a> for å fortsette å bruke tjenesten.",
+            "homeserver_blocked": "Meldingen din ble ikke sendt fordi denne hjemmeserveren er blokkert av administratoren. Ta <a> kontakt med tjenesteadministratoren din </a> for å fortsette å bruke tjenesten.",
+            "monthly_user_limit_reached": "Meldingen din ble ikke sendt fordi denne hjemmeserveren har nådd sin månedlige aktive brukergrense. Ta <a> kontakt med tjenesteadministratoren din </a> for å fortsette å bruke tjenesten.",
+            "requires_consent_agreement": "Du kan ikke sende noen meldinger før du har lest gjennom og godtar <consentLink>våre vilkår og betingelser</consentLink>.",
+            "retry_all": "Prøv alle igjen",
+            "select_messages_to_retry": "Du kan velge alle eller individuelle meldinger for å prøve på nytt eller slette",
+            "server_connectivity_lost_description": "Sendte meldinger vil bli lagret til tilkoblingen er tilbake.",
+            "server_connectivity_lost_title": "Tilkoblingen til tjeneren er nede.",
+            "some_messages_not_sent": "Noen av meldingene dine er ikke sendt"
+        },
+        "unknown_status_code_for_timeline_jump": "ukjent statuskode",
+        "unread_notifications_predecessor": {
+            "one": "Du har %(count)s ulest varsel i en tidligere versjon av dette rommet.",
+            "other": "Du har %(count)s uleste varsler i en tidligere versjon av dette rommet."
+        },
+        "upgrade_error_description": "Dobbeltsjekk at serveren støtter romversjonen som er valgt, og prøv igjen.",
+        "upgrade_error_title": "Feil ved oppgradering av rom",
+        "upgrade_warning_bar": "Oppgradering av dette rommet vil stenge den nåværende forekomsten av rommet og opprette et oppgradert rom med samme navn.",
+        "upgrade_warning_bar_admins": "Bare romadministratorer vil se denne advarselen",
+        "upgrade_warning_bar_unstable": "Dette rommet kjører romversjon<roomVersion /> , som denne hjemmetjeneren har merket som<i> ustabil</i> .",
+        "upgrade_warning_bar_upgraded": "Dette rommet har allerede blitt oppgradert.",
+        "upload": {
+            "uploading_multiple_file": {
+                "one": "Laste opp %(filename)s og %(count)s annen",
+                "other": "Laste opp %(filename)s og %(count)s andre"
+            },
+            "uploading_single_file": "Laster opp %(filename)s"
+        },
+        "video_room": "Dette rommet er et videorom",
+        "waiting_for_join_subtitle": "Når inviterte brukere har blitt med i %(brand)s, vil du kunne chatte og rommet vil bli ende-til-ende-kryptert",
+        "waiting_for_join_title": "Venter på at brukerne skal bli med %(brand)s"
+    },
+    "room_list": {
+        "add_room_label": "Legg til et rom",
+        "add_space_label": "Legg til område",
+        "appearance": "Utseende",
+        "breadcrumbs_empty": "Ingen nylig besøkte rom",
+        "breadcrumbs_label": "Nylig besøkte rom",
+        "empty": {
+            "no_chats": "Ingen chatter ennå",
+            "no_chats_description": "Kom i gang ved å sende meldinger til noen eller ved å opprette et rom",
+            "no_chats_description_no_room_rights": "Kom i gang med å sende meldinger til noen",
+            "no_favourites": "Du har ikke favorittchat ennå",
+            "no_favourites_description": "Du kan legge til en chat til dine favoritter i chat-innstillingene",
+            "no_invites": "Du har ingen uleste invitasjoner",
+            "no_mentions": "Du har ingen uleste omtaler",
+            "no_people": "Du har ikke direkte chatter med noen ennå",
+            "no_people_description": "Du kan fjerne merket for filtre for å se de andre chattene dine",
+            "no_rooms": "Du er ikke med i noen rom ennå",
+            "no_rooms_description": "Du kan fjerne merket for filtre for å se de andre chattene dine",
+            "no_unread": "Gratulerer! Du har ingen uleste meldinger",
+            "show_activity": "Se alle aktiviteter",
+            "show_chats": "Vis alle chatter"
+        },
+        "failed_add_tag": "Kunne ikke legge til tagg %(tagName)s til rom",
+        "failed_remove_tag": "Kunne ikke fjerne tagg %(tagName)s fra rommet",
+        "failed_set_dm_tag": "Kan ikke sette kode på direktemeldingen",
+        "filters": {
+            "favourite": "Favoritter",
+            "invites": "Invitasjoner",
+            "mentions": "Omtaler",
+            "people": "Personer",
+            "rooms": "Rom",
+            "unread": "Uleste"
+        },
+        "home_menu_label": "Hjem alternativer",
+        "join_public_room_label": "Bli med i offentlig rom",
+        "joining_rooms_status": {
+            "one": "Blir for øyeblikket med i %(count)s rom",
+            "other": "Blir for øyeblikket med i %(count)s rom"
+        },
+        "list_title": "Romliste",
+        "more_options": {
+            "copy_link": "Kopier romlenke",
+            "favourited": "Favorittmerket",
+            "leave_room": "Forlat rommet",
+            "low_priority": "Lav prioritet",
+            "mark_read": "Marker som lest",
+            "mark_unread": "Marker som ulest"
+        },
+        "notification_options": "Varselsinnstillinger",
+        "open_space_menu": "Åpne Område-meny",
+        "primary_filters": "Filtre for romliste",
+        "redacting_messages_status": {
+            "one": "Fjerner for øyeblikket meldinger i %(count)s rom",
+            "other": "Fjerner for øyeblikket meldinger i %(count)s rom"
+        },
+        "room": {
+            "more_options": "Flere alternativer",
+            "open_room": "Åpne rom %(roomName)s"
+        },
+        "room_options": "Rominnstillinger",
+        "show_less": "Vis mindre",
+        "show_message_previews": "Aktiver forhåndsvisning av meldinger",
+        "show_n_more": {
+            "Vis %(count)s til": "Vis %(count)s mer"
+        },
+        "show_previews": "Vis forhåndsvisninger av meldinger",
+        "sort": "Sorter",
+        "sort_by": "Sorter etter",
+        "sort_by_activity": "Aktivitet",
+        "sort_by_alphabet": "A-Å",
+        "sort_type": {
+            "activity": "Aktivitet",
+            "atoz": "A-Å"
+        },
+        "sort_unread_first": "Vis rom med uleste meldinger først",
+        "space_menu": {
+            "home": "Område hjem",
+            "space_settings": "Område innstillinger"
+        },
+        "space_menu_label": "",
+        "sublist_options": "Liste alternativer",
+        "suggested_rooms_heading": "Foreslåtte rom"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "Bestem hvem som kan se og bli med i %(spaceName)s.",
+            "title": "Tilgang"
+        },
+        "advanced": {
+            "error_upgrade_description": "Romoppgraderingen kunne ikke fullføres",
+            "error_upgrade_title": "Kunne ikke oppgradere rommet",
+            "information_section_room": "Rominformasjon",
+            "information_section_space": "Informasjon om område",
+            "room_id": "Intern rom-ID",
+            "room_predecessor": "Vis eldre meldinger i %(roomName)s.",
+            "room_upgrade_button": "Oppgrader dette rommet til anbefalt romversjon",
+            "room_upgrade_warning": "<b>Advarsel</b>: Hvis du oppgraderer et rom, vil <i>ikke automatisk migrere rommedlemmene til den nye versjonen av rommet.</i> Vi legger ut en lenke til det nye rommet i den gamle versjonen av rommet - rommedlemmene må klikke på denne lenken for å bli med i det nye rommet.",
+            "room_version": "Romversjon:",
+            "room_version_section": "Romversjon",
+            "space_predecessor": "Vis eldre versjon av %(spaceName)s.",
+            "space_upgrade_button": "Oppgrader dette området til den anbefalte romversjonen",
+            "unfederated": "Dette rommet er ikke tilgjengelig fra eksterne Matrix-servere",
+            "upgrade_button": "Oppgrader dette rommet til versjon %(version)s",
+            "upgrade_dialog_description": "Oppgradering av dette rommet krever å stenge den nåværende forekomsten av rommet og opprette et nytt rom i stedet. For å gi rommedlemmene den best mulige opplevelsen, vil vi:",
+            "upgrade_dialog_description_1": "Opprett et nytt rom med samme navn, beskrivelse og avatar",
+            "upgrade_dialog_description_2": "Oppdater eventuelle lokale romalias for å peke på det nye rommet",
+            "upgrade_dialog_description_3": "Stopp brukere fra å snakke i den gamle versjonen av rommet, og legg ut en melding som ber brukerne flytte til det nye rommet",
+            "upgrade_dialog_description_4": "Legg inn en lenke tilbake til det gamle rommet i starten av det nye rommet slik at folk kan finne eldre meldinger",
+            "upgrade_dialog_title": "Oppgrader romversjon",
+            "upgrade_dwarning_ialog_title_public": "Oppgrader offentlig rom",
+            "upgrade_warning_dialog_description": "Oppgradering av et rom er en avansert handling og anbefales vanligvis når et rom er ustabilt på grunn av feil, manglende funksjoner eller sikkerhetssårbarheter.",
+            "upgrade_warning_dialog_explainer": "<b>Vær oppmerksom på at oppgradering vil gjøre en ny versjon av rommet</b> . Alle gjeldende meldinger blir værende i dette arkiverte rommet.",
+            "upgrade_warning_dialog_footer": "Du oppgraderer dette rommet fra <oldVersion /> til <newVersion />.",
+            "upgrade_warning_dialog_invite_label": "Inviter automatisk medlemmer fra dette rommet til det nye",
+            "upgrade_warning_dialog_report_bug_prompt": "Dette påvirker vanligvis bare hvordan rommet behandles på serveren. Hvis du har problemer med %(brand)s, vennligst rapporter en feil.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "Dette påvirker vanligvis bare hvordan rommet behandles på serveren. Hvis du har problemer med %(brand)s, kan du <a>rapportere en feil</a>.",
+            "upgrade_warning_dialog_title": "Oppgrader rom",
+            "upgrade_warning_dialog_title_private": "Oppgrader privat rom"
+        },
+        "alias_not_specified": "ikke spesifisert",
+        "bridges": {
+            "description": "Dette rommet bygger bro mellom meldinger til følgende plattformer. <a>Lær mer. </a>",
+            "empty": "Dette rommet bygger ikke bro mellom meldinger til noen plattformer. <a>Finn ut mer. </a>",
+            "title": "Broer"
+        },
+        "delete_avatar_label": "Slett profilbilde",
+        "general": {
+            "alias_field_has_domain_invalid": "Manglende domeneseparator, f.eks. (:domain.org)",
+            "alias_field_has_localpart_invalid": "Manglende romnavn eller skilletegn, f.eks. (my-room:domain.org)",
+            "alias_field_matches_invalid": "Denne adressen peker ikke på dette rommet",
+            "alias_field_placeholder_default": "f.eks. mitt-rom",
+            "alias_field_required_invalid": "Vennligst oppgi en adresse",
+            "alias_field_safe_localpart_invalid": "Noen tegn er ikke tillatt",
+            "alias_field_taken_invalid": "Denne adressen hadde ugyldig server eller er allerede i bruk",
+            "alias_field_taken_invalid_domain": "Denne adressen er allerede i bruk",
+            "alias_field_taken_valid": "Denne adressen er allerede i bruk",
+            "alias_heading": "Rommets adresse",
+            "aliases_items_label": "Andre publiserte adresser:",
+            "aliases_no_items_label": "Det er ingen publiserte adresser enda, legg til en nedenfor",
+            "aliases_section": "Rom-adresser",
+            "avatar_field_label": "Rommets avatar",
+            "canonical_alias_field_label": "Hovedadresse",
+            "default_url_previews_off": "URL-forhåndsvisninger er skrudd av som standard for deltakerene i dette rommet.",
+            "default_url_previews_on": "URL-forhåndsvisninger er skrudd på som standard for deltakerene i dette rommet.",
+            "description_space": "Rediger innstillinger knyttet til området ditt.",
+            "error_creating_alias_description": "Det oppstod en feil ved opprettelsen av adressen. Det kan hende at serveren ikke tillater det, eller at det oppstod en midlertidig feil.",
+            "error_creating_alias_title": "Feil ved oppretting av adresse",
+            "error_deleting_alias_description": "Det oppstod en feil ved fjerning av adressen. Det kan hende at den ikke lenger finnes, eller at det oppstod en midlertidig feil.",
+            "error_deleting_alias_description_forbidden": "Du har ikke tillatelse til å slette adressen.",
+            "error_deleting_alias_title": "Feil ved fjerning av adresse",
+            "error_publishing": "Kan ikke publisere rom",
+            "error_publishing_detail": "Det oppstod en feil ved publisering av dette rommet",
+            "error_save_space_settings": "Kan ikke lagre områdeinnstillinger.",
+            "error_updating_alias_description": "Det oppstod en feil ved oppdatering av rommets alternative adresser. Det kan hende at serveren ikke tillater det, eller at det oppstod en midlertidig feil.",
+            "error_updating_canonical_alias_description": "Det oppstod en feil ved oppdatering av rommets hovedadresse. Det kan hende at serveren ikke tillater det, eller at det oppstod en midlertidig feil.",
+            "error_updating_canonical_alias_title": "Feil ved oppdatering av hovedadresse",
+            "leave_space": "Forlat området",
+            "local_alias_field_label": "Lokal adresse",
+            "local_aliases_explainer_room": "Velg adresser for dette rommet slik at brukere kan finne dette rommet gjennom hjemmeserveren din (%(localDomain)s)",
+            "local_aliases_explainer_space": "Angi adresser for dette området slik at brukerne kan finne denne området gjennom hjemmeserveren (%(localDomain)s)",
+            "local_aliases_section": "Lokale adresser",
+            "name_field_label": "Rommets navn",
+            "new_alias_placeholder": "Ny publisert adresse (f.eks. #alias:tjener)",
+            "no_aliases_room": "Dette rommet har ikke noen lokale adresser",
+            "no_aliases_space": "Dette området har ingen lokale adresser",
+            "other_section": "Andre",
+            "publish_toggle": "Vil du publisere dette rommet til offentligheten i %(domain)s sitt rom-arkiv?",
+            "published_aliases_description": "For å publisere en adresse, må den angis som en lokal adresse først.",
+            "published_aliases_explainer_room": "Publiserte adresser kan brukes av alle, på hvilken som helst server, for å bli med i rommet ditt.",
+            "published_aliases_explainer_space": "Publiserte adresser kan brukes av hvem som helst på hvilken som helst server for å bli med i ditt område.",
+            "published_aliases_section": "Publiserte adresser",
+            "save": "Lagre endringer",
+            "topic_field_label": "Rommets tema",
+            "url_preview_encryption_warning": "I krypterte rom som denne, er URL-forhåndsvisninger skrudd av som standard for å sikre at hjemmeserveren din (der forhåndsvisningene blir generert) ikke kan samle inn informasjon om lenkene som du ser i dette rommet.",
+            "url_preview_explainer": "Når noen legger til en URL i meldingene deres, kan en URL-forhåndsvisning bli vist for å gi mere informasjonen om den lenken, f.eks. tittelen, beskrivelsen, og et bilde fra nettstedet.",
+            "url_previews_section": "URL-forhåndsvisninger",
+            "user_url_previews_default_off": "Du har <a>skrudd av</a> URL-forhåndsvisninger som standard.",
+            "user_url_previews_default_on": "Du har <a>skrudd på</a> URL-forhåndsvisninger som standard."
+        },
+        "notifications": {
+            "browse_button": "Bla",
+            "custom_sound_prompt": "Velg en ny selvvalgt lyd",
+            "notification_sound": "Varslingslyd",
+            "settings_link": "Få varsler som konfigurert i dine <a>innstillinger</a>",
+            "sounds_section": "Lyder",
+            "upload_sound_label": "Last opp egendefinert lyd",
+            "uploaded_sound": "Lastet opp lyd"
+        },
+        "people": {
+            "knock_empty": "Ingen forespørsler",
+            "knock_section": "Ber om å få bli med",
+            "see_less": "Se mindre",
+            "see_more": "Se mer"
+        },
+        "permissions": {
+            "add_privileged_user_description": "Gi en eller flere brukere i dette rommet flere privilegier",
+            "add_privileged_user_filter_placeholder": "Søk etter brukere i dette rommet...",
+            "add_privileged_user_heading": "Legg til privilegerte brukere",
+            "ban": "Bannlys brukere",
+            "ban_reason": "Årsak",
+            "banned_by": "Bannlyst av %(displayName)s",
+            "banned_users_section": "Bannlyste brukere",
+            "error_changing_pl_description": "Det oppstod en feil ved endring av brukerens tilgangsnivå. Forsikre deg om at du har tilstrekkelige tillatelser, og prøv igjen.",
+            "error_changing_pl_reqs_description": "Det oppstod en feil ved endring av rommets tilgangsnivå krav. Forsikre deg om at du har tilstrekkelige tillatelser, og prøv igjen.",
+            "error_changing_pl_reqs_title": "Feil ved endring av tilgangsnivå krav",
+            "error_changing_pl_title": "Feil ved endring av tilgangsnivå",
+            "error_unbanning": "Kunne ikke oppheve utestengelsen",
+            "events_default": "Send meldinger",
+            "invite": "Inviter brukere",
+            "kick": "Fjern brukere",
+            "m.call": "Start %(brand)s samtaler",
+            "m.call.member": "Bli med %(brand)s samtaler",
+            "m.reaction": "Send reaksjoner",
+            "m.room.avatar": "Endre rommets avatar",
+            "m.room.avatar_space": "Endre områdeavatar",
+            "m.room.canonical_alias": "Endre hovedadressen til rommet",
+            "m.room.canonical_alias_space": "Endre hovedadresse for området",
+            "m.room.encryption": "Skru på kryptering av rommet",
+            "m.room.history_visibility": "Endre historikkens synlighet",
+            "m.room.name": "Endre rommets navn",
+            "m.room.name_space": "Endre navn på område",
+            "m.room.pinned_events": "Administrer festede hendelser",
+            "m.room.power_levels": "Endre tillatelser",
+            "m.room.redaction": "Fjern meldinger sendt av meg",
+            "m.room.server_acl": "Endre server ACLer",
+            "m.room.tombstone": "Oppgrader rommet",
+            "m.room.topic": "Endre samtaletema",
+            "m.room.topic_space": "Endre beskrivelse",
+            "m.space.child": "Administrer rom i dette området",
+            "m.widget": "Endre på moduler",
+            "muted_users_section": "Dempede brukere",
+            "no_privileged_users": "Ingen brukere har spesifikke rettigheter i dette rommet",
+            "notifications.room": "Varsle alle",
+            "permissions_section": "Tillatelser",
+            "permissions_section_description_room": "Velg rollene som kreves for å endre på diverse deler av rommet",
+            "permissions_section_description_space": "Velg rollene som kreves for å endre ulike deler av området",
+            "privileged_users_section": "Priviligerte brukere",
+            "redact": "Fjern meldinger sendt av andre",
+            "send_event_type": "Send %(eventType)s-hendelser",
+            "state_default": "Endre innstillinger",
+            "title": "Roller og tillatelser",
+            "users_default": "Forvalgt rolle"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldinger som sendes i et kryptert rom, kan ikke ses av serveren, bare av deltakerne i rommet. Aktivering av kryptering kan hindre mange boter og broer i å fungere korrekt. <a>Finn ut mer om kryptering.</a>",
+            "enable_encryption_confirm_title": "Vil du skru på kryptering?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Det anbefales ikke å legge til kryptering i offentlige rom. </b>Alle kan finne og bli med i offentlige rom, slik at alle kan lese meldinger i dem. Du får ingen av fordelene med kryptering, og du vil ikke kunne slå den av senere. Kryptering av meldinger i et offentlig rom vil gjøre mottak og sending av meldinger tregere.",
+            "enable_encryption_public_room_confirm_description_2": "For å unngå disse problemene, opprett et <a> nytt kryptert rom </a> for samtalen du planlegger å ha.",
+            "enable_encryption_public_room_confirm_title": "Er du sikker på at du vil legge til kryptering til dette offentlige rommet?",
+            "encrypted_room_public_confirm_description_1": "<b>Det anbefales ikke å gjøre krypterte rom offentlige. </b>Det vil bety at alle kan finne og bli med i rommet, slik at alle kan lese meldinger. Du får ingen av fordelene med kryptering. Kryptering av meldinger i et offentlig rom vil gjøre mottak og sending av meldinger tregere.",
+            "encrypted_room_public_confirm_description_2": "For å unngå disse problemene, opprett et <a>nytt offentlig rom</a> for samtalen du planlegger å ha.",
+            "encrypted_room_public_confirm_title": "Er du sikker på at du vil gjøre dette krypterte rommet offentlig?",
+            "encryption_forced": "Serveren din krever at kryptering er deaktivert.",
+            "encryption_permanent": "Dersom dette først har blitt skrudd på, kan kryptering aldri bli skrudd av.",
+            "error_join_rule_change_title": "Kunne ikke oppdatere reglene for medlemskap",
+            "error_join_rule_change_unknown": "Ukjent feil",
+            "guest_access_warning": "Personer med støttede klienter vil kunne bli med i rommet uten å ha en registrert konto.",
+            "history_visibility_invited": "Kun medlemmer (f.o.m. da de ble invitert)",
+            "history_visibility_joined": "Kun medlemmer (f.o.m. de ble med)",
+            "history_visibility_legend": "Hvem kan lese historikken?",
+            "history_visibility_shared": "Kun medlemmer (f.o.m. da denne innstillingen ble valgt)",
+            "history_visibility_warning": "Endringer for hvem som kan lese historikken, vil kun bli benyttet for fremtidige meldinger i dette rommet. Synligheten til den eksisterende historikken vil forbli uendret.",
+            "history_visibility_world_readable": "Alle",
+            "join_rule_description": "Bestem hvem som kan bli med i %(roomName)s.",
+            "join_rule_invite": "Privat (kun invitasjon)",
+            "join_rule_invite_description": "Bare inviterte personer kan bli med.",
+            "join_rule_knock": "Spør om å få bli med",
+            "join_rule_knock_description": "Folk kan ikke bli med med mindre tilgang er gitt.",
+            "join_rule_public_description": "Alle kan finne og bli med.",
+            "join_rule_restricted": "Områdemedlemmer",
+            "join_rule_restricted_description": "Alle i et område kan finne og bli med. <a>Rediger hvilke områder du kan få tilgang til her. </a>",
+            "join_rule_restricted_description_active_space": "Alle i <spaceName/> kan finne og bli med. Du kan også velge andre områder.",
+            "join_rule_restricted_description_prompt": "Alle i et område kan finne og bli med. Du kan velge flere områder.",
+            "join_rule_restricted_description_spaces": "Områder med tilgang",
+            "join_rule_restricted_dialog_description": "Bestem hvilke områder som kan få tilgang til dette rommet. Hvis et område er valgt, kan medlemmene finne og bli med<RoomName/>.",
+            "join_rule_restricted_dialog_empty_warning": "Du fjerner alle områder. Tilgang vil som standard kun være invitasjoner",
+            "join_rule_restricted_dialog_filter_placeholder": "Søk etter områder",
+            "join_rule_restricted_dialog_heading_known": "Andre områder du kjenner",
+            "join_rule_restricted_dialog_heading_other": "Andre områder eller rom du kanskje ikke kjenner",
+            "join_rule_restricted_dialog_heading_room": "Områder du kjenner som inneholder dette rommet",
+            "join_rule_restricted_dialog_heading_space": "Områder du kjenner som inneholder dette området",
+            "join_rule_restricted_dialog_heading_unknown": "Dette er sannsynligvis de andre romadministratorer er en del av.",
+            "join_rule_restricted_dialog_title": "Velg områder",
+            "join_rule_restricted_n_more": {
+                "one": "& %(count)s mer",
+                "other": "& %(count)s mer"
+            },
+            "join_rule_restricted_summary": {
+                "one": "Foreløpig, et område har tilgang",
+                "other": "Foreløpig, %(count)s områder har tilgang"
+            },
+            "join_rule_restricted_upgrade_description": "Denne oppgraderingen vil gi medlemmer av utvalgte områder tilgang til dette rommet uten invitasjon.",
+            "join_rule_restricted_upgrade_warning": "Dette rommet er i noen områder du ikke er administrator av. I disse områdene vil det gamle rommet fortsatt vises, men folk vil bli bedt om å bli med i det nye.",
+            "join_rule_upgrade_awaiting_room": "Laster inn nytt rom",
+            "join_rule_upgrade_required": "Oppgradering kreves",
+            "join_rule_upgrade_sending_invites": {
+                "one": "Sender invitasjon...",
+                "other": "Sender invitasjoner... (%(progress)s av %(count)s)"
+            },
+            "join_rule_upgrade_updating_spaces": {
+                "one": "Oppdaterer område...",
+                "other": "Oppdaterer områder... (%(progress)s out of %(count)s)"
+            },
+            "join_rule_upgrade_upgrading_room": "Oppgraderer rom",
+            "public_without_alias_warning": "For å lenke til dette rommet, vennligst legg til en adresse.",
+            "publish_room": "Gjør dette rommet synlig i katalogen for offentlige rom.",
+            "publish_space": "Gjør dette området synlig i katalogen for offentlige rom.",
+            "strict_encryption": "Send bare meldinger til bekreftede brukere.",
+            "title": "Sikkerhet og personvern"
+        },
+        "title": "Rominnstillinger - %(roomName)s",
+        "upload_avatar_label": "Last opp en avatar",
+        "visibility": {
+            "alias_section": "Adresse",
+            "error_failed_save": "Kan ikke oppdatere synligheten til dette området",
+            "error_update_guest_access": "Kunne ikke oppdatere gjestetilgangen til dette området",
+            "error_update_history_visibility": "Kunne ikke oppdatere historikkens synlighet for dette området",
+            "guest_access_explainer": "Gjester kan bli med i et område uten å ha en konto.",
+            "guest_access_explainer_public_space": "Dette kan være nyttig for offentlige områder.",
+            "guest_access_label": "Aktiver gjestetilgang",
+            "history_visibility_anyone_space": "Forhåndsvisning av områder",
+            "history_visibility_anyone_space_description": "La folk få en forhåndsvisning av området ditt før de blir med.",
+            "history_visibility_anyone_space_recommendation": "Anbefales for offentlige områder.",
+            "title": "Synlighet"
+        },
+        "voip": {
+            "call_type_section": "Anropstype",
+            "enable_element_call_caption": "%(brand)s er ende-til-ende-kryptert, men er foreløpig begrenset til et mindre antall brukere.",
+            "enable_element_call_label": "Aktiver %(brand)s som et ekstra anropsalternativ i dette rommet",
+            "enable_element_call_no_permissions_tooltip": "Du har ikke tilstrekkelige rettigheter til å endre dette."
+        }
+    },
+    "room_summary_card_back_action_label": "Rominformasjon",
+    "scalar": {
+        "error_create": "Klarte ikke lage widgeten.",
+        "error_membership": "Du er ikke i dette rommet.",
+        "error_missing_room_id": "Manglende rom-ID.",
+        "error_missing_room_id_request": "Manglende room_id i forespørselen",
+        "error_missing_user_id_request": "Manglende user_id i forespørselen",
+        "error_permission": "Du har ikke tillatelse til å gjøre det i dette rommet.",
+        "error_power_level_invalid": "Tilgangsnivå må være et positivt heltall.",
+        "error_room_not_visible": "Rom %(roomId)s er ikke synlig",
+        "error_room_unknown": "Dette rommet ble ikke gjenkjent.",
+        "error_send_request": "Klarte ikke sende forespørsel.",
+        "failed_read_event": "Kunne ikke lese hendelser",
+        "failed_send_event": "Kunne ikke sende hendelsen"
+    },
+    "server_offline": {
+        "description": "Serveren din svarer ikke på noen av forespørslene dine. Nedenfor er noen av de mest sannsynlige årsakene.",
+        "description_1": "Serveren (%(serverName)s) brukte for lang tid på å svare.",
+        "description_2": "Brannmuren eller antiviruset blokkerer forespørselen.",
+        "description_3": "En nettleserutvidelse forhindrer forespørselen.",
+        "description_4": "Denne tjeneren er offline.",
+        "description_5": "Serveren har avslått forespørselen din.",
+        "description_6": "Området ditt opplever problemer med å koble til internett.",
+        "description_7": "Det oppstod en tilkoblingsfeil mens du forsøkte å kontakte serveren.",
+        "description_8": "Tjeneren er ikke konfigurert til å indikere hva problemet er (CORS).",
+        "empty_timeline": "Du har ikke mer.",
+        "recent_changes_heading": "Nylige endringer som ennå ikke er mottatt",
+        "title": "Serveren svarer ikke"
+    },
+    "service_worker_error": {
+        "description": "%(brand)s krever en tjenestearbeider for å laste inn autentiserte medier fra Matrix-innholdslagre. Dette støttes ikke av nettleseren din, så du kan oppleve at media ikke lastes inn.",
+        "title": "Kunne ikke laste inn servicearbeider"
+    },
+    "seshat": {
+        "error_initialising": "Initialisering av meldingssøk mislyktes, sjekk innstillingene <a> dine </a> for mer informasjon",
+        "reset_button": "Tilbakestill hendelseslageret",
+        "reset_description": "Du vil sannsynligvis ikke tilbakestille hendelsesindeks lageret",
+        "reset_explainer": "Hvis du gjør det, vær oppmerksom på at ingen av meldingene dine blir slettet, men søkeopplevelsen kan bli forringet i noen øyeblikk mens indeksen gjenopprettes",
+        "reset_title": "Tilbakestill hendelseslager?",
+        "warning_kind_files": "Denne versjonen av %(brand)s støtter ikke visning av enkelte krypterte filer",
+        "warning_kind_files_app": "Bruke <a>Desktop-app</a> for å se alle krypterte filer",
+        "warning_kind_search": "Denne versjonen av %(brand)s støtter ikke søk i krypterte meldinger",
+        "warning_kind_search_app": "Bruk <a>Desktop-app</a> for å søke etter krypterte meldinger"
+    },
+    "setting": {
+        "help_about": {
+            "access_token_detail": "Tilgangstokenet ditt gir full tilgang til kontoen din. Ikke del det med noen.",
+            "brand_version": "'%(brand)s'-versjon:",
+            "clear_cache_reload": "Tøm mellomlageret og last inn siden på nytt",
+            "crypto_version": "Crypto versjon:",
+            "dialog_title": "<strong>Innstillinger: </strong> Hjelp & Om",
+            "help_link": "For å få hjelp til å bruke %(brand)s, klikk <a>her</a>.",
+            "homeserver": "Hjemmeserveren er <code>%(homeserverUrl)s</code>",
+            "identity_server": "Identitetsserveren er <code>%(identityServerUrl)s</code>",
+            "title": "Hjelp/Om",
+            "versions": "Versjoner"
+        }
+    },
+    "settings": {
+        "account": {
+            "dialog_title": "<strong>Innstillinger: </strong> Konto",
+            "title": "Konto"
+        },
+        "all_rooms_home": "Vis alle rom i Hjem",
+        "all_rooms_home_description": "Alle rom du er med i, vises i Hjem.",
+        "always_show_message_timestamps": "Alltid vis meldingenes tidsstempler",
+        "appearance": {
+            "bundled_emoji_font": "Bruk medfølgende emoji-skrift",
+            "compact_layout": "Vis kompakt tekst og meldinger",
+            "compact_layout_description": "Moderne layout må velges for å bruke denne funksjonen.",
+            "custom_font": "Bruk en systemskrifttype",
+            "custom_font_description": "Angi navnet på en skrifttype som er installert på systemet ditt, og %(brand)s vil forsøke å bruke den.",
+            "custom_font_name": "Systemskrifttypenavn",
+            "custom_font_size": "Bruk tilpasset størrelse",
+            "custom_theme_add": "Legg til egendefinert tema",
+            "custom_theme_downloading": "Laster ned tilpasset tema...",
+            "custom_theme_error_downloading": "Feil ved nedlasting av tema",
+            "custom_theme_help": "Skriv inn URL-adressen til et egendefinert tema du vil bruke.",
+            "custom_theme_invalid": "Ugyldig temaskjema.",
+            "dialog_title": "<strong>Innstillinger:</strong> Utseende",
+            "font_size": "Skriftstørrelse",
+            "font_size_default": "%(fontSize)s (standard)",
+            "high_contrast": "Høy kontrast",
+            "image_size_default": "Standard",
+            "image_size_large": "Stor",
+            "layout_bubbles": "Meldingsbobler",
+            "layout_irc": "IRC (eksperimentell)",
+            "match_system_theme": "Bind fast til systemtemaet",
+            "timeline_image_size": "Bildestørrelse i tidslinjen"
+        },
+        "automatic_language_detection_syntax_highlight": "Skru på automatisk kodespråkoppdagelse for syntaksfremheving",
+        "autoplay_gifs": "Spill av GIF-er automatisk",
+        "autoplay_videos": "Spill av videoer automatisk",
+        "big_emoji": "Skru på store emojier i chatrom",
+        "code_block_expand_default": "Utvid kodeblokker som standard",
+        "code_block_line_numbers": "Vis linjenumre i kodeblokker",
+        "disable_historical_profile": "Vis gjeldende profilbilde og navn for brukere i meldingsloggen",
+        "discovery": {
+            "title": "Hvordan finne deg"
+        },
+        "emoji_autocomplete": "Skru på emoji-forslag mens du skriver",
+        "enable_markdown": "Aktiver Markdown",
+        "enable_markdown_description": "Start meldinger med <code>/plain</code> for å sende uten markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Kontodetaljer, kontakter, preferanser og chatliste vil bli beholdt",
+                "breadcrumb_page": "Tilbakestill kryptering",
+                "breadcrumb_second_description": "Du mister all meldingshistorikk som bare er lagret på serveren",
+                "breadcrumb_third_description": "Du må bekrefte alle eksisterende enheter og kontakter på nytt",
+                "breadcrumb_title": "Er du sikker på at du vil tilbakestille identiteten din?",
+                "breadcrumb_title_forgot": "Har du glemt gjenopprettingsnøkkelen din? Du må tilbakestille identiteten din.",
+                "breadcrumb_title_sync_failed": "Kunne ikke synkronisere nøkkellageret. Du må tilbakestille identiteten din.",
+                "breadcrumb_warning": "Gjør dette bare hvis du tror at kontoen din har blitt kompromittert.",
+                "details_title": "Krypteringsdetaljer",
+                "do_not_close_warning": "Ikke lukk dette vinduet før tilbakestillingen er fullført",
+                "export_keys": "Eksporter nøkler",
+                "import_keys": "Importer nøkler",
+                "other_people_device_description": "Advarsel: Brukere som ikke eksplisitt har verifisert med deg (f.eks. ved hjelp av emoji) vil ikke motta dine krypterte meldinger. Ubekreftede enheter av bekreftede brukere vil heller ikke motta krypterte meldinger.",
+                "other_people_device_label": "I krypterte rom, send kun meldinger til verifiserte brukere",
+                "other_people_device_title": "Andre personers enheter",
+                "reset_identity": "Tilbakestill kryptografisk identitet",
+                "reset_in_progress": "Tilbakestilling pågår...",
+                "session_id": "Sesjons-ID:",
+                "session_key": "Sesjonsnøkkel:",
+                "title": "Avansert"
+            },
+            "confirm_key_storage_off": "Er du sikker på at du vil ha nøkkeloppbevaring skrudd av?",
+            "confirm_key_storage_off_description": "Hvis du logger deg av alle enhetene dine, mister du meldingshistorikken og må bekrefte alle eksisterende kontakter igjen. <a>Lær mer </a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Slett nøkkellagring",
+                "confirm": "Slett nøkkellagring",
+                "description": "Hvis du sletter nøkkellagring, fjernes den kryptografiske identiteten og meldingsnøklene fra serveren og deaktivere følgende sikkerhetsfunksjoner:",
+                "list_first": "Du vil ikke ha kryptert meldingshistorikk på nye enheter",
+                "list_second": "Du vil miste tilgangen til de krypterte meldingene dine hvis du logger av %(brand)s overalt",
+                "title": "Er du sikker på at du vil slå av nøkkellagring og slette den?"
+            },
+            "device_not_verified_button": "Bekreft denne enheten",
+            "device_not_verified_description": "Du må bekrefte denne enheten for å kunne se krypteringsinnstillingene dine.",
+            "device_not_verified_title": "Enhet er ikke verifisert",
+            "dialog_title": "<strong>Innstillinger:</strong> Kryptering",
+            "key_storage": {
+                "allow_key_storage": "Tillat lagring av nøkler",
+                "description": "Lagre din kryptografiske identitet og meldingsnøkler sikkert på serveren. Dette lar deg se meldingsloggen din på alle nye enheter. <a>Lær mer </a>",
+                "title": "Nøkkellager"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Bekreft ny gjenopprettingsnøkkel",
+                "change_recovery_confirm_description": "Skriv inn den nye gjenopprettingsnøkkelen nedenfor for å fullføre. Den gamle vil ikke lenger fungere.",
+                "change_recovery_confirm_title": "Skriv inn den nye gjenopprettingsnøkkelen",
+                "change_recovery_key": "Endre gjenopprettingsnøkkel",
+                "change_recovery_key_description": "",
+                "change_recovery_key_title": "Vil du endre gjenopprettingsnøkkel?",
+                "description": "Gjenopprett din kryptografiske identitet og meldingshistorikk med en gjenopprettingsnøkkel hvis du har mistet alle dine eksisterende enheter.",
+                "enter_key_error": "Gjenopprettingsnøkkelen du skrev inn er ikke riktig.",
+                "enter_recovery_key": "Skriv inn gjenopprettingsnøkkel",
+                "forgot_recovery_key": "Glemt gjenopprettingsnøkkelen?",
+                "key_storage_warning": "Nøkkellagringen din er ikke synkronisert. Klikk på knappen nedenfor for å løse problemet.",
+                "save_key_description": "Ikke del dette med noen!",
+                "save_key_title": "Gjenopprettingsnøkkel",
+                "set_up_recovery": "Konfigurer gjenoppretting",
+                "set_up_recovery_confirm_button": "Fullfør oppsettet",
+                "set_up_recovery_confirm_description": "Skriv inn gjenopprettingsnøkkelen som ble vist på forrige skjermbilde for å fullføre konfigureringen av gjenopprettingen.",
+                "set_up_recovery_confirm_title": "Skriv inn gjenopprettingsnøkkelen din for å bekrefte",
+                "set_up_recovery_description": "Nøkkeloppbevaringen er beskyttet av en gjenopprettingsnøkkel. Hvis du trenger en ny gjenopprettingsnøkkel etter oppsett, kan du opprette den på nytt ved å velge «%(changeRecoveryKeyButton)s».",
+                "set_up_recovery_save_key_description": "Skriv ned denne gjenopprettingsnøkkelen et trygt sted, for eksempel en passordbehandler, et kryptert notat eller en fysisk safe.",
+                "set_up_recovery_save_key_title": "Du bør lagre din gjenopprettingsnøkkelen på et trygt sted",
+                "set_up_recovery_secondary_description": "Etter å ha klikket fortsett, genererer vi en gjenopprettingsnøkkel for deg.",
+                "title": "Gjenoppretting"
+            },
+            "title": "Kryptering"
+        },
+        "general": {
+            "account_management_section": "Kontobehandling",
+            "account_section": "Konto",
+            "add_email_dialog_title": "Legg til E-postadresse",
+            "add_email_failed_verification": "Klarte ikke verifisere e-postadressen: dobbelsjekk at du trykket på lenken i e-posten",
+            "add_email_instructions": "Vi har sendt deg en e-post for å bekrefte adressen din. Følg instruksjonene der, og klikk deretter på knappen nedenfor.",
+            "add_msisdn_confirm_body": "Klikk knappen nedenfor for å bekrefte dette telefonnummeret.",
+            "add_msisdn_confirm_button": "Bekreft tillegging av telefonnummer",
+            "add_msisdn_confirm_sso_button": "Bekreft dette telefonnummeret ved å bruke Single Sign On for å bevise din identitet.",
+            "add_msisdn_dialog_title": "Legg til telefonnummer",
+            "add_msisdn_instructions": "En tekstmelding har blitt sendt til +%(msisdn)s. Vennligst skriv inn bekreftelseskoden den inneholder.",
+            "add_msisdn_misconfigured": "Flyten Legg til / bind med MSISDN er feilkonfigurert",
+            "allow_spellcheck": "Tillat stavekontroll",
+            "application_language": "Applikasjonsspråk",
+            "application_language_reload_hint": "Appen lastes inn på nytt etter å ha valgt et annet språk",
+            "avatar_remove_progress": "Fjerner bilde...",
+            "avatar_save_progress": "Laster opp bilde...",
+            "avatar_upload_error_text": "Filformatet støttes ikke, eller bildet er større enn %(size)s.",
+            "avatar_upload_error_text_generic": "Filformatet er kanskje ikke støttet.",
+            "avatar_upload_error_title": "Avatarbildet kunne ikke lastes opp",
+            "confirm_adding_email_body": "Klikk på knappen under for å bekrefte at du vil legge til denne e-postadressen.",
+            "confirm_adding_email_title": "Bekreft tillegging av E-postadresse",
+            "deactivate_confirm_body": "Er du sikker på at du vil deaktivere kontoen din? Dette kan ikke reverseres.",
+            "deactivate_confirm_body_sso": "Bekreft deaktiveringen av kontoen din ved å bruke Single Sign On for å bevise identiteten din.",
+            "deactivate_confirm_content": "Bekreft at du vil deaktivere kontoen din. Hvis du fortsetter:",
+            "deactivate_confirm_content_1": "Du vil ikke kunne aktivere kontoen din på nytt",
+            "deactivate_confirm_content_2": "Du vil ikke lenger kunne logge inn",
+            "deactivate_confirm_content_3": "Ingen vil kunne gjenbruke brukernavnet ditt (MXID), inkludert deg: dette brukernavnet vil forbli utilgjengelig",
+            "deactivate_confirm_content_4": "Du vil forlate alle rom og DM-er du er i",
+            "deactivate_confirm_content_5": "Du blir fjernet fra identitetsserveren: vennene dine vil ikke lenger kunne finne deg med din e-postadresse eller telefonnummer",
+            "deactivate_confirm_content_6": "De gamle meldingene dine vil fortsatt være synlige for folk som har mottatt dem, akkurat som e-poster du har sendt tidligere. Vil du skjule de sendte meldingene dine for personer som blir med i rommene i fremtiden?",
+            "deactivate_confirm_continue": "Bekreft deaktivering av kontoen",
+            "deactivate_confirm_erase_label": "Skjul meldingene mine for nye deltakere",
+            "deactivate_section": "Deaktiver kontoen",
+            "deactivate_warning": "Deaktivering av kontoen din er en permanent handling - vær forsiktig!",
+            "discovery_email_empty": "Oppdagelsesalternativer vises når du har lagt til en e-postkonto.",
+            "discovery_email_verification_instructions": "Verifiser lenken i innboksen din",
+            "discovery_msisdn_empty": "Oppdagelsesalternativer vises når du har lagt til et telefonnummer.",
+            "discovery_needs_terms": "Godkjenn identitetstjenerens (%(serverName)s) brukervilkår, slik at du kan bli oppdaget ut i fra E-postadresse eller telefonnummer.",
+            "discovery_needs_terms_title": "La folk finne deg",
+            "display_name": "Visningsnavn",
+            "display_name_error": "Kan ikke angi visningsnavn",
+            "email_adding_unsupported_by_hs": "Denne hjemmeserveren støtter ikke å legge til flere e-postadresser til kontoen din.",
+            "email_address_in_use": "Denne e-postadressen er allerede i bruk",
+            "email_address_label": "E-postadresse",
+            "email_not_verified": "E-postadressen din er ikke verifisert ennå",
+            "email_verification_instructions": "Klikk på lenken i e-posten du mottok for å verifisere, og klikk deretter på Fortsett igjen.",
+            "emails_heading": "E-postadresser",
+            "error_add_email": "Klarte ikke å legge til E-postadressen",
+            "error_deactivate_communication": "Det oppstod et problem med å kommunisere med serveren. Vennligst prøv igjen.",
+            "error_deactivate_invalid_auth": "Serveren returnerte ikke gyldig autentiseringsinformasjon.",
+            "error_deactivate_no_auth": "Serveren krevde ingen autentisering",
+            "error_email_verification": "Kan ikke bekrefte e-postadressen.",
+            "error_invalid_email": "Ugyldig E-postadresse",
+            "error_invalid_email_detail": "Dette ser ikke ut til å være en gyldig e-postadresse",
+            "error_msisdn_verification": "Klarte ikke å verifisere telefonnummeret.",
+            "error_password_change_403": "Kunne ikke endre passord. Er passordet ditt riktig?",
+            "error_password_change_http": "%(errorMessage)s (HTTP-status %(httpStatus)s)",
+            "error_password_change_title": "Feil ved endring av passord",
+            "error_password_change_unknown": "Ukjent passordendring feil (%(stringifiedError)s)",
+            "error_remove_3pid": "Kan ikke fjerne kontaktinformasjon",
+            "error_revoke_email_discovery": "Klarte ikke å trekke tilbake deling for denne e-postadressen",
+            "error_revoke_msisdn_discovery": "Klarte ikke trekke tilbake deling for telefonnummer",
+            "error_share_email_discovery": "Kan ikke dele e-postadresse",
+            "error_share_msisdn_discovery": "Kan ikke dele telefonnummer",
+            "identity_server_no_token": "Ingen identitetstilgangstoken funnet",
+            "identity_server_not_set": "Identitetsserver er ikke angitt",
+            "invalid_phone_number": "Telefonnummeret som er oppgitt ser ikke ut til å være gyldig.",
+            "language_section": "Språk",
+            "msisdn_adding_unsupported_by_hs": "Denne hjemmeserveren støtter ikke å legge til telefonnumre til kontoen din.",
+            "msisdn_in_use": "Dette mobilnummeret er allerede i bruk",
+            "msisdn_label": "Telefonnummer",
+            "msisdn_verification_field_label": "Verifikasjonskode",
+            "msisdn_verification_instructions": "Vennligst skriv inn bekreftelseskoden sendt som tekstmelding.",
+            "msisdns_heading": "Telefonnumre",
+            "oidc_manage_button": "Administrer konto",
+            "password_change_section": "Angi et nytt passord for kontoen...",
+            "password_change_success": "Passordet ditt ble endret.",
+            "personal_info": "Personlig informasjon",
+            "profile_subtitle": "Slik fremstår du for andre på appen.",
+            "profile_subtitle_oidc": "Kontoen din administreres separat av en identitetsleverandør, så noen av dine personlige opplysninger kan ikke endres her.",
+            "remove_email_prompt": "Vil du fjerne %(email)s?",
+            "remove_msisdn_prompt": "Vil du fjerne %(phone)s?",
+            "spell_check_locale_placeholder": "Velg en nasjonal innstilling",
+            "unable_to_load_emails": "Kan ikke laste inn e-postadresser",
+            "unable_to_load_msisdns": "Kan ikke laste inn telefonnumre",
+            "username": "Brukernavn"
+        },
+        "inline_url_previews_default": "Skru på URL-forhåndsvisninger inni meldinger som standard",
+        "inline_url_previews_room": "Skru på URL-forhåndsvisninger som standard for deltakerne i dette rommet",
+        "inline_url_previews_room_account": "Skru på URL-forhåndsvisninger for dette rommet (Påvirker bare deg)",
+        "insert_trailing_colon_mentions": "Sett inn et etterfølgende kolon etter at brukeromtaler i starten av en melding",
+        "jump_to_bottom_on_send": "Gå til bunnen av tidslinjen når du vil sende en melding",
+        "key_backup": {
+            "backup_in_progress": "Nøklene dine blir sikkerhetskopiert (den første sikkerhetskopieringen kan ta noen minutter).",
+            "backup_starting": "Starter sikkerhetskopiering...",
+            "backup_success": "Suksess!",
+            "cannot_create_backup": "Kan ikke opprette sikkerhetskopiering av nøkler",
+            "create_title": "Opprett nøkkelsikkerhetskopi",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "Nøklene dine blir nå sikkerhetskopiert fra denne enheten.",
+                "backup_setup_success_title": "Sikker sikkerhetskopiering vellykket",
+                "cancel_warning": "Hvis du avbryter nå, kan du miste krypterte meldinger og data hvis du mister tilgang til påloggingene dine.",
+                "confirm_security_phrase": "Bekreft sikkerhetsfrasen din",
+                "description": "Beskytt deg mot å miste tilgang til krypterte meldinger og data ved å sikkerhetskopiere krypteringsnøkler på serveren din.",
+                "download_or_copy": "%(downloadButton)s eller %(copyButton)s",
+                "enter_phrase_description": "Skriv inn en sikkerhetsfrase som bare du kjenner, da den brukes til å beskytte dataene dine. For å være sikker, bør du ikke bruke kontopassordet ditt på nytt.",
+                "enter_phrase_title": "Skriv inn en sikkerhetsfrase",
+                "enter_phrase_to_confirm": "Skriv inn sikkerhetsfrasen en gang til for å bekrefte den.",
+                "generate_security_key_description": "Vi genererer en sikkerhetsnøkkel som du kan lagre et trygt sted, for eksempel en passordbehandling eller en safe.",
+                "generate_security_key_title": "Generer en gjenopprettingsnøkkel",
+                "pass_phrase_match_failed": "Det samsvarer ikke.",
+                "pass_phrase_match_success": "Det samsvarer!",
+                "phrase_strong_enough": "Flott! Denne sikkerhetsfrasen ser sterk nok ut.",
+                "secret_storage_query_failure": "Kan ikke spørre om hemmelig lagringsstatus",
+                "security_key_safety_reminder": "Lagre sikkerhetsnøkkelen et trygt sted, for eksempel en passordbehandling eller en safe, da den brukes til å beskytte dine krypterte data.",
+                "set_phrase_again": "Gå tilbake for å velge på nytt.",
+                "settings_reminder": "Du kan også konfigurere Sikker sikkerhetskopiering og administrere nøklene dine i Innstillinger.",
+                "title_confirm_phrase": "Bekreft sikkerhetsfrasen",
+                "title_save_key": "Lagre gjenopprettingsnøkkelen din",
+                "title_set_phrase": "Angi en sikkerhetsfrase",
+                "unable_to_setup": "Kan ikke sette opp hemmelig lagring",
+                "use_different_passphrase": "Vil du bruke en annen passfrase?",
+                "use_phrase_only_you_know": "Bruk en hemmelig frase som bare du kjenner, og lagre eventuelt en sikkerhetsnøkkel som du kan bruke til sikkerhetskopiering."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "Bekreft passfrasen",
+            "enter_passphrase": "Skriv inn passordfrase",
+            "export_description_1": "Denne prosessen lar deg eksportere nøklene for meldinger du har mottatt i krypterte rom til en lokal fil. Du vil da kunne importere filen til en annen Matrix-klient i fremtiden, slik at klienten også vil kunne dekryptere disse meldingene.",
+            "export_description_2": "Den eksporterte filen vil tillate alle som kan lese den å dekryptere eventuelle krypterte meldinger du kan se, så du bør være forsiktig med å holde den sikker. For å hjelpe med dette, bør du skrive inn en unik passordfrase nedenfor, som bare vil bli brukt til å kryptere de eksporterte dataene. Det vil bare være mulig å importere dataene ved å bruke samme passordfrase.",
+            "export_title": "Eksporter romnøkler",
+            "file_to_import": "Filen som skal importeres",
+            "import_description_1": "Denne prosessen lar deg importere krypteringsnøkler som du tidligere hadde eksportert fra en annen Matrix-klient. Du vil da kunne dekryptere eventuelle meldinger som den andre klienten kan dekryptere.",
+            "import_description_2": "Eksportfilen vil bli beskyttet med en passordfrase. Du bør skrive inn passordfrasen her, for å dekryptere filen.",
+            "import_title": "Importer romnøkler",
+            "phrase_cannot_be_empty": "Passfrasen kan ikke være tom",
+            "phrase_must_match": "Passfrasene må samsvare",
+            "phrase_strong_enough": "Flott! Denne passordfrasen ser sterk nok ut"
+        },
+        "keyboard": {
+            "dialog_title": "<strong>Innstillinger:</strong> Tastatur",
+            "title": "Tastatur"
+        },
+        "labs": {
+            "dialog_title": "<strong>Innstillinger: </strong> Labs"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Innstillinger:</strong> Ignorerte brukere"
+        },
+        "media_preview": {
+            "hide_avatars": "Skjul avatarer for rom og inviterer",
+            "hide_media": "Skjul alltid",
+            "media_preview_description": "Et skjult medium kan alltid vises ved å trykke på det",
+            "media_preview_label": "Vis media i tidslinjen",
+            "show_in_private": "I private rom",
+            "show_media": "Vis alltid"
+        },
+        "notifications": {
+            "default_setting_description": "Denne innstillingen vil bli brukt som standard for alle rommene dine.",
+            "default_setting_section": "Jeg ønsker å bli varslet for (standardinnstilling)",
+            "desktop_notification_message_preview": "Vis forhåndsvisning av meldinger i skrivebordsvarsler",
+            "dialog_title": "<strong>Innstillinger:</strong> Varsler",
+            "email_description": "Motta en e-postoppsummering av ubesvarte varsler",
+            "email_section": "E-postsammendrag",
+            "email_select": "Velg hvilke e-poster du vil sende sammendrag til. Administrer e-postene dine i <button>Generelt</button> .",
+            "enable_audible_notifications_session": "Skru på hørbare varsler for denne økten",
+            "enable_desktop_notifications_session": "Skru på skrivebordsvarsler for denne økten",
+            "enable_email_notifications": "Aktiver e-postvarsler for %(email)s",
+            "enable_notifications_account": "Aktivere varsler for denne kontoen",
+            "enable_notifications_account_detail": "Slå av for å deaktivere varslinger på alle enhetene og sesjonene dine",
+            "enable_notifications_device": "Aktiver varsler for denne enheten",
+            "error_loading": "Det oppstod en feil ved innlasting av varslingsinnstillingene dine.",
+            "error_permissions_denied": "%(brand)s har ikke tillatelse til å sende deg varsler - vennligst sjekk nettleserinnstillingene",
+            "error_permissions_missing": "%(brand)s fikk ikke tillatelse til å sende deg varsler - vennligst prøv igjen",
+            "error_saving": "Feil ved lagring av varslingsinnstillinger",
+            "error_saving_detail": "Det oppstod en feil under lagring av varslingsinnstillingene dine.",
+            "error_title": "Klarte ikke slå på Varslinger",
+            "error_updating": "Det oppstod en feil under oppdateringen av varslingsinnstillingene dine. Prøv å slå på alternativet igjen.",
+            "invites": "Invitert til et rom",
+            "keywords": "Vis et merke <badge/> når nøkkelord brukes i et rom.",
+            "keywords_prompt": "Skriv inn nøkkelord her, eller bruk for stavevarianter eller kallenavn",
+            "labs_notice_prompt": "<strong>Oppdatering: </strong> Vi har forenklet varslingsinnstillingene for å gjøre alternativene enklere å finne. Noen egendefinerte innstillinger du har valgt tidligere, vises ikke her, men de er fortsatt aktive. Hvis du fortsetter, kan noen av innstillingene dine endres. <a>Lær mer </a>",
+            "mentions_keywords": "Omtaler og nøkkelord",
+            "mentions_keywords_only": "Bare omtaler og nøkkelord",
+            "messages_containing_keywords": "Meldinger som inneholder nøkkelord",
+            "noisy": "Bråkete",
+            "notices": "Meldinger sendt av bots",
+            "notify_at_room": "Varsle når noen nevner at de bruker @room",
+            "notify_keyword": "Varsle når noen bruker et nøkkelord",
+            "notify_mention": "Varsle når noen nevner å bruke @visningsnavn eller %(mxid)s",
+            "other_section": "Andre ting vi tror du kan være interessert i:",
+            "people_mentions_keywords": "Personer, omtaler og nøkkelord",
+            "play_sound_for_description": "Brukes som standard på alle rom på alle enheter.",
+            "play_sound_for_section": "Spill av en lyd for",
+            "push_targets": "Mål for varsel",
+            "quick_actions_mark_all_read": "Marker alle meldinger som lest",
+            "quick_actions_reset": "Tilbakestill til standardinnstillinger",
+            "quick_actions_section": "Hurtighandlinger",
+            "room_activity": "Ny romaktivitet, oppgraderinger og statusmeldinger forekommer",
+            "rule_call": "Anropsinvitasjon",
+            "rule_contains_display_name": "Meldinger som inneholder mitt visningsnavn",
+            "rule_contains_user_name": "Meldinger som nevner brukernavnet mitt",
+            "rule_encrypted": "Krypterte meldinger i gruppesamtaler",
+            "rule_encrypted_room_one_to_one": "Krypterte meldinger i samtaler under fire øyne",
+            "rule_invite_for_me": "Når jeg blir invitert til et rom",
+            "rule_message": "Meldinger i gruppesamtaler",
+            "rule_room_one_to_one": "Meldinger i en-til-en samtaler",
+            "rule_roomnotif": "Medlinger som inneholder @room",
+            "rule_suppress_notices": "Meldinger sendt av bot",
+            "rule_tombstone": "Når rom blir oppgradert",
+            "show_message_desktop_notification": "Vis meldingen i skrivebordsvarselet",
+            "voip": "Lyd- og videosamtaler"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "Aktiver maskinvareakselerasjon (start %(appName)s på nytt for at det skal tre i kraft)",
+            "always_show_menu_bar": "Vis alltid vinduets menylinje",
+            "autocomplete_delay": "Autofullføringsforsinkelse (ms)",
+            "code_blocks_heading": "Kodeblokker",
+            "compact_modern": "Bruk et mer kompakt \"moderne\" oppsett",
+            "composer_heading": "Komposør",
+            "default_timezone": "Nettleserens standard (%(timezone)s)",
+            "dialog_title": "<strong>Innstillinger:</strong> Preferanser",
+            "enable_hardware_acceleration": "Aktiver maskinvareakselerasjon",
+            "enable_tray_icon": "Vis skuffikonet og minimer vinduet til det når det lukkes",
+            "keyboard_heading": "Tastatursnarveier",
+            "keyboard_view_shortcuts_button": "For å se alle hurtigtaster, <a>klikk her</a>.",
+            "media_heading": "Bilder, GIF-er og videoer",
+            "presence_description": "Del din aktivitet og status med andre.",
+            "publish_timezone": "Publiser tidssone på offentlig profil",
+            "rm_lifetime": "Lesemarkørens visningstid (ms)",
+            "rm_lifetime_offscreen": "Lesemarkørens visningstid utenfor skjermen (ms)",
+            "room_directory_heading": "Romkatalog",
+            "room_list_heading": "Romliste",
+            "show_avatars_pills": "Vis avatarer i bruker-, rom- og hendelsesomtaler",
+            "show_polls_button": "Vis avstemninger-knapp",
+            "surround_text": "Omgi valgt tekst når du skriver spesialtegn",
+            "time_heading": "Viser tid",
+            "user_timezone": "Angi tidssone"
+        },
+        "prompt_invite": "Si ifra før det sendes invitasjoner til potensielt ugyldige Matrix-ID-er",
+        "replace_plain_emoji": "Bytt automatisk ut råtekst-emojier",
+        "security": {
+            "analytics_description": "Del anonyme data for å hjelpe oss med å identifisere problemer. Ikke noe personlig. Ingen tredjeparter.",
+            "bulk_options_accept_all_invites": "Aksepter alle %(invitedRooms)s-invitasjoner",
+            "bulk_options_reject_all_invites": "Avslå alle %(invitedRooms)s-invitasjoner",
+            "bulk_options_section": "Bulk alternativer",
+            "dehydrated_device_description": "Funksjonen for frakoblet enhet lar deg motta krypterte meldinger selv når du ikke er logget inn på noen enheter",
+            "dehydrated_device_enabled": "Frakoblet enhet aktivert",
+            "dialog_title": "<strong>Innstillinger: </strong> Sikkerhet og personvern",
+            "e2ee_default_disabled_warning": "Serveradministratoren din har deaktivert ende-til-ende-kryptering som standard i private rom og direktemeldinger.",
+            "enable_message_search": "Aktiver meldingssøk i krypterte rom",
+            "encryption_section": "Kryptering",
+            "ignore_users_empty": "Du har ingen ignorerte brukere.",
+            "ignore_users_section": "Ignorerte brukere",
+            "key_backup_algorithm": "Algoritme:",
+            "key_backup_connect": "Koble denne økten til hoved Backup",
+            "message_search_disable_warning": "Hvis deaktivert, vises ikke meldinger fra krypterte rom i søkeresultatene.",
+            "message_search_disabled": "Bufre krypterte meldinger lokalt på en sikker måte, slik at de vises i søkeresultatene.",
+            "message_search_enabled": {
+                "one": "Lagre krypterte meldinger lokalt slik at de vises i søkeresultatene, ved hjelp av %(size)s for å lagre meldinger fra %(rooms)s rom.",
+                "other": "Lagre krypterte meldinger lokalt slik at de vises i søkeresultatene, ved hjelp av %(size)s for å lagre meldinger fra %(rooms)s -rom."
+            },
+            "message_search_failed": "Initialisering av meldingssøk mislyktes",
+            "message_search_indexed_messages": "Indekserte meldinger:",
+            "message_search_indexed_rooms": "Indekserte rom:",
+            "message_search_indexing": "Indeksering for øyeblikket: %(currentRoom)s",
+            "message_search_indexing_idle": "Indekserer for øyeblikket ikke meldinger for noen rom.",
+            "message_search_intro": "%(brand)s bufrer krypterte meldinger lokalt på en sikker måte slik at de vises i søkeresultatene:",
+            "message_search_room_progress": "%(doneRooms)s av %(totalRooms)s",
+            "message_search_section": "Meldingssøk",
+            "message_search_sleep_time": "Hvor raskt skal meldinger lastes ned.",
+            "message_search_space_used": "Plass brukt:",
+            "message_search_unsupported": "%(brand)s mangler noen komponenter som kreves for sikker hurtigbufring av krypterte meldinger lokalt. Hvis du vil eksperimentere med denne funksjonen, kan du bygge et tilpasset %(brand)s skrivebord med <nativeLink> søkekomponenter lagt til</nativeLink>.",
+            "message_search_unsupported_web": "%(brand)s kan ikke sikkert bufre krypterte meldinger lokalt mens de kjører i en nettleser. Bruk <desktopLink>%(brand)s Skrivebord</desktopLink> for at krypterte meldinger skal vises i søkeresultatene.",
+            "record_session_details": "Registrer klientnavn, versjon og url for å gjøre det enklere å gjenkjenne sesjoner i sesjonsadministratoren",
+            "send_analytics": "Send analytiske data",
+            "strict_encryption": "Send bare meldinger til verifiserte brukere"
+        },
+        "send_read_receipts": "Send lesebekreftelser",
+        "send_read_receipts_unsupported": "Serveren din støtter ikke deaktivering av sending av lesebekreftelser.",
+        "send_typing_notifications": "Send varsler om at du skriver",
+        "sessions": {
+            "best_security_note": "For best mulig sikkerhet bør du verifisere sesjonene dine og logge ut av alle sesjoner du ikke kjenner igjen eller ikke bruker lenger.",
+            "browser": "Nettleser",
+            "confirm_sign_out": {
+                "one": "Bekreft at du logger av denne enheten",
+                "other": "Bekreft at du logger av disse enhetene"
+            },
+            "confirm_sign_out_body": {
+                "one": "Klikk på knappen nedenfor for å bekrefte at du logger av denne enheten.",
+                "other": "Klikk på knappen nedenfor for å bekrefte at du logger av disse enhetene."
+            },
+            "confirm_sign_out_continue": {
+                "one": "Logg av enheten",
+                "other": "Logg av enheter"
+            },
+            "confirm_sign_out_sso": {
+                "one": "Bekreft å logge av denne enheten ved å bruke Single Sign On for å bevise identiteten din.",
+                "other": "Bekreft å logge av disse enhetene ved å bruke enkel pålogging for å bevise identiteten din."
+            },
+            "current_session": "Nåværende sesjon",
+            "desktop_session": "Skrivebordsøkt",
+            "details_heading": "Sesjonsdetaljer",
+            "device_unverified_description": "Bekreft eller logg ut av denne sesjonen for best mulig sikkerhet og pålitelighet.",
+            "device_unverified_description_current": "Bekreft den nåværende sesjonen for forbedret sikker meldingsutveksling.",
+            "device_verified_description": "Denne sesjonen er klar for sikker meldingsutveksling.",
+            "device_verified_description_current": "Din pågående sesjon er klar for sikker meldingsutveksling.",
+            "dialog_title": "<strong>Innstillinger:</strong> Sesjoner",
+            "error_pusher_state": "Kunne ikke angi pusher-tilstand",
+            "error_set_name": "Kunne ikke angi sesjonsnavn",
+            "filter_all": "Alle",
+            "filter_inactive": "Inaktiv",
+            "filter_inactive_description": "Inaktiv i %(inactiveAgeDays)s dager eller lenger",
+            "filter_label": "Filtrer enheter",
+            "filter_unverified_description": "Ikke klar for sikker meldingsutveksling",
+            "filter_verified_description": "Klar for sikker meldingsutveksling",
+            "hide_details": "Skjul detaljer",
+            "inactive_days": "Inaktiv i %(inactiveAgeDays)s+ dager",
+            "inactive_sessions": "Inaktive sesjoner",
+            "inactive_sessions_explainer_1": "Inaktive økter er økter du ikke har brukt på en stund, men som fortsatt mottar krypteringsnøkler.",
+            "inactive_sessions_explainer_2": "Fjerning av inaktive sesjoner forbedrer sikkerheten og ytelsen, og gjør det enklere for deg å identifisere om en ny økt er mistenkelig.",
+            "inactive_sessions_list_description": "Vurder å logge ut fra gamle økter (%(inactiveAgeDays)s dager eller eldre) som du ikke bruker lenger.",
+            "ip": "IP adresse",
+            "last_activity": "Siste aktivitet",
+            "manage": "Administrer denne sesjonen",
+            "mobile_session": "Mobil økt",
+            "n_sessions_selected": {
+                "one": "%(count)s sesjon valgt",
+                "other": "%(count)s sesjoner valgt"
+            },
+            "no_inactive_sessions": "Ingen inaktive sesjoner funnet.",
+            "no_sessions": "Ingen sesjoner funnet.",
+            "no_unverified_sessions": "Ingen ubekreftede sesjoner funnet.",
+            "no_verified_sessions": "Ingen verifiserte sesjoner funnet.",
+            "os": "Operativsystem",
+            "other_sessions_heading": "Andre sesjoner",
+            "push_heading": "Push-varsler",
+            "push_subheading": "Motta push-varsler på denne sesjonen.",
+            "push_toggle": "Slå på push-varsler på denne økten.",
+            "rename_form_caption": "Vær oppmerksom på at sesjonsnavn også er synlige for folk du kommuniserer med.",
+            "rename_form_heading": "Gi nytt navn til sesjonen",
+            "rename_form_learn_more": "Endre navn på sesjoner",
+            "rename_form_learn_more_description_1": "Andre brukere i direktemeldinger og rom som du blir med på, kan se en fullstendig liste over alle dine økter.",
+            "rename_form_learn_more_description_2": "Dette gir dem trygghet for at de virkelig snakker med deg, men det betyr også at de kan se sesjonsnavnet du skriver inn her.",
+            "security_recommendations": "Sikkerhetsanbefalinger",
+            "security_recommendations_description": "Forbedre kontosikkerheten din ved å følge disse anbefalingene.",
+            "session_id": "Sesjons-ID",
+            "show_details": "Vis detaljer",
+            "sign_in_with_qr": "Koble til ny enhet",
+            "sign_in_with_qr_button": "Vis QR-kode",
+            "sign_in_with_qr_description": "Bruk en QR-kode for å logge på en annen enhet og konfigurere sikker meldingsutveksling.",
+            "sign_in_with_qr_unsupported": "Støttes ikke av din kontoleverandør",
+            "sign_out": "Logg ut av denne sesjonen",
+            "sign_out_all_other_sessions": "Logg av alle andre sesjoner (%(otherSessionsCount)s)",
+            "sign_out_confirm_description": {
+                "one": "Er du sikker på at du vil logge ut av %(count)s sesjonen?",
+                "other": "Er du sikker på at du vil logge av %(count)s sesjoner?"
+            },
+            "sign_out_n_sessions": {
+                "one": "Logg ut av %(count)s sesjon",
+                "other": "Logg ut av %(count)s sesjoner"
+            },
+            "title": "Sesjoner",
+            "unknown_session": "Ukjent sesjonstype",
+            "unverified_session": "Uverifisert sesjon",
+            "unverified_session_explainer_1": "Denne sesjonen støtter ikke kryptering og kan derfor ikke verifiseres.",
+            "unverified_session_explainer_2": "Du vil ikke kunne delta i rom der kryptering er aktivert når du bruker denne sesjonen.",
+            "unverified_session_explainer_3": "For best mulig sikkerhet og personvern anbefales det å bruke Matrix-klienter som støtter kryptering.",
+            "unverified_sessions": "Uverifiserte sesjoner",
+            "unverified_sessions_explainer_1": "Ubekreftede sesjoner er sesjoner som har logget på med legitimasjonen din, men som ikke er kryssverifisert.",
+            "unverified_sessions_explainer_2": "Du bør være spesielt sikker på at du gjenkjenner disse øktene, da de kan representere en uautorisert bruk av kontoen din.",
+            "unverified_sessions_list_description": "Verifiser sesjonene dine for forbedret sikker meldingsutveksling, eller logg ut fra de du ikke kjenner igjen eller ikke bruker lenger.",
+            "url": "URL",
+            "verified_session": "Verifisert sesjon",
+            "verified_sessions": "Verifiserte sesjoner",
+            "verified_sessions_explainer_1": "Bekreftede økter er hvor som helst du bruker denne kontoen etter at du har skrevet inn passordet ditt eller bekreftet identiteten din med en annen bekreftet økt.",
+            "verified_sessions_explainer_2": "Dette betyr at du har alle nøklene som trengs for å låse opp krypterte meldinger og bekrefte overfor andre brukere at du stoler på denne økten.",
+            "verified_sessions_list_description": "For best mulig sikkerhet bør du logge ut av alle sesjoner du ikke kjenner igjen eller ikke bruker lenger.",
+            "verify_session": "Verifiser økten",
+            "web_session": "Websesjon"
+        },
+        "show_avatar_changes": "Vis endringer i profilbilde",
+        "show_breadcrumbs": "Vis snarveier til de nyligst viste rommene ovenfor romlisten",
+        "show_chat_effects": "Vis chatteffekter (animasjoner når du mottar f.eks. konfetti)",
+        "show_displayname_changes": "Vis visningsnavnendringer",
+        "show_join_leave": "Vis bli med/forlat meldinger (invitasjoner/fjernede/utestengte upåvirket)",
+        "show_nsfw_content": "Vis NSFW-innhold",
+        "show_read_receipts": "Vis lesekvitteringer sendt av andre brukere",
+        "show_redaction_placeholder": "Vis en stattholder for fjernede meldinger",
+        "show_stickers_button": "Vis klistremerkeknappen",
+        "show_typing_notifications": "Vis varsler om at det skrives",
+        "showbold": "Vis all aktivitet i romlisten (prikker eller antall uleste meldinger)",
+        "sidebar": {
+            "dialog_title": "<strong>Innstillinger:</strong> Sidepanel",
+            "metaspaces_favourites_description": "Grupper alle dine favorittrom og -personer på ett sted.",
+            "metaspaces_home_all_rooms": "Vis alle rom",
+            "metaspaces_home_all_rooms_description": "Vis alle rommene dine i Hjem, selv om de er i et område.",
+            "metaspaces_home_description": "Hjem er nyttig for å få oversikt over alt.",
+            "metaspaces_orphans": "Rom utenfor et område",
+            "metaspaces_orphans_description": "Grupper alle rommene dine som ikke er en del av et område på ett sted.",
+            "metaspaces_people_description": "Grupper alle dine personer på ett sted.",
+            "metaspaces_subsection": "Områder å vise",
+            "metaspaces_video_rooms": "Videorom og konferanser",
+            "metaspaces_video_rooms_description": "Grupper alle private videorom og konferanser.",
+            "metaspaces_video_rooms_description_invite_extension": "På konferanser kan du invitere personer utenfor matrix.",
+            "spaces_explainer": "Områder er måter å gruppere rom og mennesker på. Ved siden av områdene du er i, kan du også bruke noen forhåndsbygde.",
+            "title": "Sidepanel"
+        },
+        "start_automatically": "Start automatisk etter systempålogging",
+        "tac_only_notifications": "Vis bare varsler i trådens aktivitetssenter",
+        "use_12_hour_format": "Vis tidsstempler i 12-timersformat (f.eks. 2:30pm)",
+        "use_command_enter_send_message": "Bruk Kommando + Enter for å sende en melding",
+        "use_command_f_search": "Bruk Kommando + F for å søke i tidslinjen",
+        "use_control_enter_send_message": "Bruk Ctrl + Enter for å sende en melding",
+        "use_control_f_search": "Bruk Ctrl + F for å søke i tidslinjen",
+        "voip": {
+            "allow_p2p": "Tillat node-til-node for 1:1-samtaler",
+            "allow_p2p_description": "Når aktivert, kan den andre parten kanskje se IP-adressen din",
+            "audio_input_empty": "Ingen mikrofoner ble oppdaget",
+            "audio_output": "Lydutdata",
+            "audio_output_empty": "Ingen lydutdataer ble oppdaget",
+            "auto_gain_control": "Automatisk forsterkningskontroll",
+            "connection_section": "Tilkobling",
+            "dialog_title": "<strong>Innstillinger:</strong> Tale og video",
+            "echo_cancellation": "Ekko-kansellering",
+            "enable_fallback_ice_server": "Tillat reserveserver for anropsassistanse (%(server)s)",
+            "enable_fallback_ice_server_description": "Gjelder bare hvis hjemmeserveren din ikke tilbyr dette. IP-adressen din vil bli delt under en samtale.",
+            "mirror_local_feed": "Speil den lokale videostrømmen",
+            "missing_permissions_prompt": "Manglende mediatillatelser, klikk på knappen nedenfor for å be om dem.",
+            "noise_suppression": "Støydemping",
+            "request_permissions": "Be om mediatillatelser",
+            "title": "Tale og video",
+            "video_input_empty": "Ingen USB-kameraer ble oppdaget",
+            "video_section": "Videoinnstillinger",
+            "voice_agc": "Juster mikrofonvolumet automatisk",
+            "voice_processing": "Talebehandling",
+            "voice_section": "Tale innstillinger"
+        },
+        "warn_quit": "Advar før avslutning",
+        "warning": "<w>ADVARSEL: </w> <description/>"
+    },
+    "share": {
+        "link_copied": "Lenke kopiert",
+        "permalink_message": "Lenke til valgt melding",
+        "permalink_most_recent": "Lenke til siste melding",
+        "share_call": "Lenke til invitasjon til konferansen",
+        "share_call_subtitle": "Lenke for eksterne brukere for å bli med i samtalen uten en matrix-konto:",
+        "title_link": "Del lenke",
+        "title_message": "Del rommelding",
+        "title_room": "Del rommet",
+        "title_user": "Del brukeren"
+    },
+    "slash_command": {
+        "addwidget": "Legger til en tilpasset widget med URL i rommet",
+        "addwidget_iframe_missing_src": "iframe har ingen src-attributt",
+        "addwidget_invalid_protocol": "Oppgi en https: // eller http: // widget-URL",
+        "addwidget_missing_url": "Vennligst oppgi en widget-URL eller innebyggingskode",
+        "addwidget_no_permissions": "Du kan ikke endre widgets i dette rommet.",
+        "ban": "Nekter tilgang til bruker med gitt id",
+        "category_actions": "Handlinger",
+        "category_admin": "Administrator",
+        "category_advanced": "Avansert",
+        "category_effects": "Effekter",
+        "category_messages": "Meldinger",
+        "category_other": "Andre",
+        "command_error": "Kommandofeil",
+        "converttodm": "Konverterer rommet til en DM",
+        "converttoroom": "Konverterer DM til et rom",
+        "could_not_find_room": "Kunne ikke finne rom",
+        "deop": "Fjerner OP nivå til bruker med gitt ID",
+        "devtools": "Åpner Utviklings Verktøy dialogen",
+        "discardsession": "Tvinger den gjeldende utgående gruppeøkten i et kryptert rom til å stoppe",
+        "error_invalid_rendering_type": "Kommandofeil: Kan ikke finne gjengivelsestype (%(renderingType)s)",
+        "error_invalid_room": "Kommandoen mislyktes: Kan ikke finne rom (%(roomId)s)",
+        "error_invalid_runfn": "Kommandofeil: Kan ikke håndtere skråstrekkommandoen.",
+        "error_invalid_user_in_room": "Kunne ikke finne bruker i rommet",
+        "help": "Viser liste over kommandoer med bruks eksempler og beskrivelser",
+        "help_dialog_title": "Kommandohjelp",
+        "holdcall": "Setter anropet i gjeldende rom på vent",
+        "html": "Sender en melding som html, uten å tolke den som markdown",
+        "ignore": "Ignorerer en bruker og skjuler meldingene deres hos deg",
+        "ignore_dialog_description": "%(userId)s er nå ignorert",
+        "ignore_dialog_title": "Ignorert(e) bruker",
+        "invite": "Inviterer brukeren med gitt id til dette rommet",
+        "invite_3pid_needs_is_error": "Bruk en identitetsserver til å invitere via e-post. Administrer i Innstillinger.",
+        "invite_3pid_use_default_is_title": "Bruk en identitetstjener",
+        "invite_3pid_use_default_is_title_description": "Bruk en identitetsserver til å invitere via e-post. Klikk på Fortsett for å bruke standardidentitetsserveren (%(defaultIdentityServerName)s) eller administrer i Innstillinger.",
+        "invite_failed": "Bruker (%(user)s) endte ikke opp som invitert til%(roomId)s, men ingen feil ble gitt fra inviterverktøyet",
+        "join": "Kobler til rom med oppgitt adresse",
+        "jumptodate": "Gå til den gitte datoen i tidslinjen",
+        "jumptodate_invalid_input": "Vi klarte ikke å forstå den gitte datoen (%(inputDate)s). Prøv å bruke formatet ÅÅÅÅ-MM-DD.",
+        "lenny": "Legger til ( ͡° ͜ʖ ͡°) foran en ren tekstmelding",
+        "me": "Viser handling",
+        "msg": "Sender en melding til den angitte brukeren",
+        "myavatar": "Endrer profilbildet ditt i alle rom",
+        "myroomavatar": "Endrer profilbildet ditt kun i dette rommet",
+        "myroomnick": "Endrer visningsnavnet ditt kun i det nåværende rommet",
+        "nick": "Endrer visningsnavnet ditt",
+        "no_active_call": "Ingen aktivt anrop i dette rommet",
+        "op": "Definer tilgangnivå til en bruker",
+        "part_unknown_alias": "Ukjent romadresse: %(roomAlias)s",
+        "plain": "Sender en melding som ren tekst, uten å tolke den som markdown",
+        "query": "Åpner chat med den gitte brukeren",
+        "query_not_found_phone_number": "Kan ikke finne Matrix ID for telefonnummeret",
+        "rageshake": "Send en feilrapport med logger",
+        "rainbow": "Sender gitte melding i regnbuens farger",
+        "rainbowme": "Sender gitte emote i regnbuens farger",
+        "remove": "Fjerner brukeren med oppgitt ID fra dette rommet",
+        "roomavatar": "Endrer avataren for det gjeldende rommet",
+        "roomname": "Setter rommets navn",
+        "server_error": "Serverfeil",
+        "server_error_detail": "Server utilgjengelig, overbelastet, eller noe annet gikk galt.",
+        "shrug": "Føyer til ¯\\_(ツ)_/¯ på en råtekstmelding",
+        "spoiler": "Sender den gitte meldingen som en spoiler",
+        "tableflip": "Legger (╯°□°)╯︵ ┻━┻ foran en ren tekstmelding",
+        "topic": "Henter eller setter rommets overskrift",
+        "topic_none": "Dette rommet har ingen overskrift.",
+        "topic_room_error": "Kunne ikke hente rommets emne: Kan ikke finne rom (%(roomId)s",
+        "unban": "Gir tilbake tilgang til bruker med gitt ID",
+        "unflip": "Legger til ┬──┬ ノ( ゜-゜ノ) foran en ren tekstmelding",
+        "unholdcall": "Tar samtalen i gjeldende rom ut av vent",
+        "unignore": "Fjerner ignorering av bruker og viser meldingene fra nå av",
+        "unignore_dialog_description": "%(userId)s blir ikke lengre ignorert",
+        "unignore_dialog_title": "Uignorert bruker",
+        "unknown_command": "Ukjent kommando",
+        "unknown_command_button": "Send som en melding",
+        "unknown_command_detail": "Ukjent kommando: %(commandText)s",
+        "unknown_command_help": "Du kan bruke <code> /help </code> til å liste opp tilgjengelige kommandoer. Mente du å sende dette som en melding?",
+        "unknown_command_hint": "Hint: Begynn meldingen med<code>//</code>for å starte den med en skråstrek.",
+        "upgraderoom": "Oppgraderer et rom til en ny versjon",
+        "upgraderoom_permission_error": "Du har ikke de rette tilgangene til å bruke denne kommandoen.",
+        "usage": "Bruk",
+        "view": "Viser rom med oppgitt adresse",
+        "whois": "Viser informasjon om en bruker"
+    },
+    "sliding_sync_legacy_no_longer_supported": "Eldre sliding sync støttes ikke lenger: logg ut og inn igjen for å aktivere det nye sliding sync-flagget",
+    "space": {
+        "add_existing_room_space": {
+            "create": "Vil du legge til et nytt rom i stedet?",
+            "create_prompt": "Opprett et nytt område",
+            "dm_heading": "Direktemeldinger",
+            "error_heading": "Ikke alle valgte ble lagt til",
+            "progress_text": {
+                "one": "Legger til rom...",
+                "other": "Legge til rom ... (%(progress)s ut av %(count)s)"
+            },
+            "space_dropdown_label": "Område valg",
+            "space_dropdown_title": "Legg til eksisterende rom",
+            "subspace_moved_note": "Legge til område er flyttet."
+        },
+        "add_existing_subspace": {
+            "create_button": "Opprett et nytt område",
+            "create_prompt": "Vil du legge til et nytt område i stedet?",
+            "filter_placeholder": "Søk etter områder",
+            "space_dropdown_title": "Legg til eksisterende område"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "Se tidslinje for rommet (devtools)",
+            "explore": "Se alle rom",
+            "home": "Område hjem",
+            "manage_and_explore": "Administrer og utforsk rom",
+            "options": "Alternativer for område"
+        },
+        "failed_load_rooms": "Kunne ikke laste inn liste over rom.",
+        "failed_remove_rooms": "Kunne ikke fjerne noen rom. Prøv igjen senere",
+        "incompatible_server_hierarchy": "Serveren din støtter ikke visning av områdehierarkier.",
+        "invite": "Inviter personer",
+        "invite_description": "Inviter med e-post eller brukernavn",
+        "invite_link": "Del invitasjonslenke",
+        "joining_space": "Blir med",
+        "landing_welcome": "Velkommen til <name/>",
+        "leave_dialog_action": "Forlat området",
+        "leave_dialog_description": "Du er i ferd med å forlate <spaceName/>.",
+        "leave_dialog_only_admin_room_warning": "Du er den eneste administratoren av noen av rommene eller områdene du ønsker å forlate. Å forlate dem vil etterlate dem uten administratorer.",
+        "leave_dialog_only_admin_warning": "Du er den eneste administratoren av dette området. Å forlate det vil bety at ingen har kontroll over det.",
+        "leave_dialog_option_all": "Forlat alle rom",
+        "leave_dialog_option_intro": "Vil du forlate rommene på dette området?",
+        "leave_dialog_option_none": "Ikke forlat noen rom",
+        "leave_dialog_option_specific": "Forlat noen rom",
+        "leave_dialog_public_rejoin_warning": "Du vil ikke kunne bli med igjen med mindre du blir invitert på nytt.",
+        "leave_dialog_title": "Forlat %(spaceName)s",
+        "mark_suggested": "Merk som foreslått",
+        "no_search_result_hint": "Det kan være lurt å prøve et annet søk eller se etter skrivefeil.",
+        "preferences": {
+            "sections_section": "Seksjoner som skal vises",
+            "show_people_in_space": "Dette grupperer samtalene dine med medlemmer av dette området. Hvis du slår dette av, skjuler du disse samtalene fra ditt syn på %(spaceName)s."
+        },
+        "room_filter_placeholder": "Søk etter rom",
+        "search_children": "Søk %(spaceName)s",
+        "search_placeholder": "Søk etter navn og beskrivelser",
+        "select_room_below": "Velg et rom nedenfor først",
+        "share_public": "Del ditt offentlige område",
+        "suggested": "Anbefalte",
+        "suggested_tooltip": "Dette rommet er foreslått som et godt rom å bli med i",
+        "title_when_query_available": "Resultat",
+        "title_when_query_unavailable": "Rom og områder",
+        "unmark_suggested": "Merk som ikke foreslått",
+        "user_lacks_permission": "Du har ikke tillatelse"
+    },
+    "space_settings": {
+        "title": "Innstillinger - %(spaceName)s"
+    },
+    "spaces": {
+        "error_no_permission_add_room": "Du har ikke tillatelse til å legge til rom på dette området",
+        "error_no_permission_add_space": "Du har ikke tillatelse til å legge til områder på dette området",
+        "error_no_permission_create_room": "Du har ikke tillatelse til å opprette nye rom på dette området",
+        "error_no_permission_invite": "Du har ikke tillatelse til å invitere folk til dette området"
+    },
+    "spotlight": {
+        "public_rooms": {
+            "network_dropdown_add_dialog_description": "Skriv inn navnet på en ny server du vil utforske.",
+            "network_dropdown_add_dialog_placeholder": "Navn på server",
+            "network_dropdown_add_dialog_title": "Legg til en ny server",
+            "network_dropdown_add_server_option": "Legg til ny server...",
+            "network_dropdown_available_invalid": "Kan ikke finne denne serveren eller dens romliste",
+            "network_dropdown_available_invalid_forbidden": "Du har ikke lov til å se romlisten til denne serveren",
+            "network_dropdown_available_valid": "Ser bra ut",
+            "network_dropdown_remove_server_adornment": "Fjern serveren \"%(roomServer)s\"",
+            "network_dropdown_required_invalid": "Skriv inn et tjenernavn",
+            "network_dropdown_selected_label": "Vis: Matrix-rom",
+            "network_dropdown_selected_label_instance": "Vis: %(instance)s rom (%(server)s)",
+            "network_dropdown_your_server_description": "Serveren din"
+        }
+    },
+    "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Hvis du ikke kan se hvem du leter etter, send dem invitasjonslenken din.",
+        "cant_find_room_helpful_hint": "Hvis du ikke finner rommet du leter etter, kan du be om en invitasjon eller opprette et nytt rom.",
+        "copy_link_text": "Kopier invitasjonskobling",
+        "count_of_members": {
+            "one": "%(count)s Medlem",
+            "other": "%(count)s Medlemmer"
+        },
+        "create_new_room_button": "Opprett et nytt rom",
+        "failed_querying_public_rooms": "Kunne ikke søke opp offentlige rom",
+        "failed_querying_public_spaces": "Kunne ikke spørre om offentlige områder",
+        "group_chat_section_title": "Andre alternativer",
+        "heading_with_query": "Bruk \"%(query)s\" for å søke",
+        "heading_without_query": "Søk etter",
+        "join_button_text": "Bli med i %(roomAddress)s",
+        "keyboard_scroll_hint": "Bruk <arrows/> for å scrolle",
+        "messages_label": "Meldinger",
+        "other_rooms_in_space": "Andre rom i %(spaceName)s",
+        "public_rooms_label": "Offentlige rom",
+        "public_spaces_label": "Offentlige områder",
+        "recent_searches_section_title": "Nylige søk",
+        "recently_viewed_section_title": "Nylig vist",
+        "remove_filter": "Fjern søkefilter for %(filter)s",
+        "result_may_be_hidden_privacy_warning": "Noen resultater kan være skjult av personvernhensyn",
+        "result_may_be_hidden_warning": "Noen resultater kan være skjult",
+        "search_dialog": "Søkedialog",
+        "spaces_title": "Områder du er i",
+        "start_group_chat_button": "Start en gruppechat"
+    },
+    "stickers": {
+        "empty": "Du har ikke skrudd på noen klistremerkepakker for øyeblikket",
+        "empty_add_prompt": "Legg til noen nå"
+    },
+    "terms": {
+        "column_document": "Dokument",
+        "column_service": "Tjeneste",
+        "column_summary": "Oppsummering",
+        "identity_server_no_terms_description_1": "Denne handlingen krever tilgang til standard identitetsserver <server /> for å kunne validere en epostaddresse eller telefonnummer, men serveren har ikke bruksvilkår.",
+        "identity_server_no_terms_description_2": "Fortsett kun om du stoler på eieren av serveren.",
+        "identity_server_no_terms_title": "Identitetstjeneren har ingen brukervilkår",
+        "inline_intro_text": "Aksepter <policyLink /> for å fortsette:",
+        "integration_manager": "Bruk boter, broer, widgeter og klistremerkepakker",
+        "intro": "For å gå videre må du akseptere brukervilkårene til denne tjenesten.",
+        "summary_identity_server_1": "Finn andre med telefonnummer eller e-postadresse",
+        "summary_identity_server_2": "Bli funnet med telefonnummer eller e-postadresse",
+        "tac_button": "Gå gjennom betingelser og vilkår",
+        "tac_description": "For å fortsette å bruke hjemmeserveren %(homeserverDomain)s må du lese gjennom og godta våre vilkår og betingelser.",
+        "tac_title": "Betingelser og vilkår",
+        "tos": "Vilkår for bruk"
+    },
+    "theme": {
+        "light_high_contrast": "Lys høy kontrast",
+        "match_system": "Match systemet"
+    },
+    "thread_view_back_action_label": "Tilbake til tråden",
+    "threads": {
+        "all_threads": "Alle tråder",
+        "all_threads_description": "Viser alle tråder fra gjeldende rom",
+        "count_of_reply": {
+            "one": "%(count)s svar",
+            "other": "%(count)s svar"
+        },
+        "empty_description": "Bruk «%(replyInThread)s» når du holder musepekeren over en melding.",
+        "empty_title": "Tråder bidrar til å holde samtalene innenfor temaet og gjør det enkelt å spore dem.",
+        "error_start_thread_existing_relation": "Kan ikke opprette en tråd fra en hendelse med en eksisterende relasjon",
+        "mark_all_read": "Marker alle som lest",
+        "my_threads": "Mine tråder",
+        "my_threads_description": "Viser alle tråder du har deltatt i",
+        "open_thread": "Åpne tråd",
+        "show_thread_filter": "Vis:"
+    },
+    "threads_activity_centre": {
+        "header": "Trådaktivitet",
+        "no_rooms_with_threads_notifs": "Du har ikke rom med trådvarsler ennå.",
+        "no_rooms_with_unread_threads": "Du har ikke rom med uleste tråder ennå."
+    },
+    "time": {
+        "about_day_ago": "cirka 1 dag siden",
+        "about_hour_ago": "cirka 1 time siden",
+        "about_minute_ago": "cirka 1 minutt siden",
+        "date_at_time": "%(date)s klokken %(time)s",
+        "few_seconds_ago": "noen sekunder siden",
+        "hours_minutes_seconds_left": "%(hours)st%(minutes)s m%(seconds)s s igjen",
+        "in_about_day": "rundt en dag fra nå",
+        "in_about_hour": "rundt en time fra nå",
+        "in_about_minute": "rundt et minutt fra nå",
+        "in_few_seconds": "om noen sekunder fra nå",
+        "in_n_days": "%(num)s dager fra nå",
+        "in_n_hours": "%(num)s timer fra nå",
+        "in_n_minutes": "%(num)s minutter fra nå",
+        "left": "%(timeRemaining)s igjen",
+        "minutes_seconds_left": "%(minutes)sm%(seconds)s s igjen",
+        "n_days_ago": "%(num)s dager siden",
+        "n_hours_ago": "%(num)s timer siden",
+        "n_minutes_ago": "%(num)s minutter siden",
+        "seconds_left": "%(seconds)ser igjen",
+        "short_days": "%(value)sd",
+        "short_days_hours_minutes_seconds": "%(days)sd %(hours)s t %(minutes)s m %(seconds)s er",
+        "short_hours": "%(value)st",
+        "short_hours_minutes_seconds": "%(hours)sh %(minutes)sm %(seconds)ss",
+        "short_minutes": "%(value)sm",
+        "short_minutes_seconds": "%(minutes)sm %(seconds)s s",
+        "short_seconds": "%(value)ss"
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "Skjul svartråden",
+            "external_url": "Kilde-URL",
+            "open_in_osm": "Åpne i OpenStreetMap",
+            "report": "Rapporter",
+            "resent_unsent_reactions": "Send %(unsentCount)s reaksjon (er) på nytt",
+            "show_url_preview": "Vis forhåndsvisning",
+            "view_related_event": "Vis relatert hendelse",
+            "view_source": "Vis kilde"
+        },
+        "creation_summary_dm": "%(creator)s sendte denne DMen.",
+        "creation_summary_room": "%(creator)s opprettet og satte opp rommet.",
+        "decryption_failure": {
+            "blocked": "Avsenderen har blokkert deg fra å motta denne meldingen fordi enheten din ikke er bekreftet",
+            "historical_event_no_key_backup": "Historiske meldinger er ikke tilgjengelige på denne enheten",
+            "historical_event_unverified_device": "Du må verifisere denne enheten for å få tilgang til historiske meldinger",
+            "historical_event_user_not_joined": "Du har ikke tilgang til denne meldingen",
+            "sender_identity_previously_verified": "Avsenderens verifiserte identitet er endret",
+            "sender_unsigned_device": "Sendt fra en usikker enhet.",
+            "unable_to_decrypt": "Kan ikke dekryptere meldingen"
+        },
+        "disambiguated_profile": "%(displayName)s(%(matrixId)s)",
+        "download_action_decrypting": "Dekrypterer",
+        "download_action_downloading": "Laster ned",
+        "download_failed": "Nedlasting mislyktes",
+        "download_failed_description": "Det oppstod en feil under nedlasting av denne filen",
+        "e2e_state": "Tilstand for ende-til-ende-kryptering",
+        "edits": {
+            "tooltip_label": "Redigert den %(date)s. Klikk for å se endringer.",
+            "tooltip_sub": "Klikk for å vise redigeringer",
+            "tooltip_title": "Redigert den %(date)s"
+        },
+        "error_no_renderer": "Denne hendelsen kunne ikke vises",
+        "error_rendering_message": "Klarte ikke å laste inn denne meldingen",
+        "historical_messages_unavailable": "Du kan ikke se tidligere meldinger",
+        "in_room_name": " i <strong> %(room)s </strong>",
+        "io.element.widgets.layout": "%(senderName)s har oppdatert romoppsettet",
+        "late_event_separator": "Opprinnelig sendt %(dateTime)s",
+        "load_error": {
+            "no_permission": "Prøvde å laste inn et bestemt punkt i dette rommets tidslinje, men du har ikke tillatelse til å se den aktuelle meldingen.",
+            "title": "Kunne ikke laste inn tidslinjeposisjon",
+            "unable_to_find": "Prøvde å laste inn et bestemt punkt i dette rommets tidslinje, men klarte ikke å finne det."
+        },
+        "m.audio": {
+            "error_downloading_audio": "Feil ved nedlasting av lyd",
+            "error_processing_audio": "Feil ved prosessering av lydmelding",
+            "error_processing_voice_message": "Feil ved prosessering av talemelding",
+            "unnamed_audio": "Ikke navngitt lyd"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Se lokasjon i sanntid"
+        },
+        "m.call": {
+            "video_call_ended": "Videosamtale avsluttet",
+            "video_call_started": "Videosamtale startet i %(roomName)s.",
+            "video_call_started_text": "%(name)s startet en videosamtale",
+            "video_call_started_unsupported": "Videosamtale startet i %(roomName)s. (støttes ikke av denne nettleseren)"
+        },
+        "m.call.hangup": {
+            "dm": "Samtalen ble avsluttet"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "Besvart annet sted",
+            "call_back_prompt": "Ring tilbake",
+            "declined": "Anrop avvist",
+            "failed_connect_media": "Kunne ikke koble til media",
+            "failed_connection": "Tilkobling mislyktes",
+            "failed_opponent_media": "Enheten deres kunne ikke starte kameraet eller mikrofonen",
+            "missed_call": "Ubesvarte anrop",
+            "no_answer": "Ingen svar",
+            "unknown_error": "En ukjent feil oppstod",
+            "unknown_failure": "Ukjent feil: %(reason)s",
+            "unknown_state": "Anropet er i en ukjent tilstand!",
+            "video_call": "%(senderName)s gjorde en videosamtale.",
+            "video_call_unsupported": "%(senderName)s startet en videosamtale. (støttes ikke av denne nettleseren)",
+            "voice_call": "%(senderName)s gjorde et taleanrop.",
+            "voice_call_unsupported": "%(senderName)s gjorde et taleanrop. (støttes ikke av denne nettleseren)"
+        },
+        "m.file": {
+            "error_decrypting": "Feil ved dekryptering av vedlegg",
+            "error_invalid": "Ugyldig fil"
+        },
+        "m.image": {
+            "error": "Kan ikke vise bilde på grunn av feil",
+            "error_decrypting": "Feil ved dekryptering av bilde",
+            "error_downloading": "Feil ved nedlasting av bilde",
+            "sent": "%(senderDisplayName)s sendte et bilde.",
+            "show_image": "Vis bilde"
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "%(name)s ønsker å verifisere",
+            "you_started": "Du sendte en verifiseringsforespørsel"
+        },
+        "m.location": {
+            "full": "%(senderName)s har delt deres posisjon",
+            "location": "Delte en plassering: ",
+            "self_location": "Delte posisjonen deres: "
+        },
+        "m.poll": {
+            "count_of_votes": {
+                "one": "%(count)s stemme",
+                "other": "%(count)s stemmer"
+            }
+        },
+        "m.poll.end": {
+            "ended": "Avsluttet en avstemning",
+            "sender_ended": "%(senderName)s har avsluttet en avstemning"
+        },
+        "m.poll.start": "%(senderName)s har startet en avstemning - %(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "%(senderDisplayName)s endret rommets avatar.",
+            "changed_img": "%(senderDisplayName)s endret rommets avatar til <img/>",
+            "lightbox_title": "%(senderDisplayName)s endret avataren for %(roomName)s",
+            "removed": "%(senderDisplayName)s fjernet romavataren."
+        },
+        "m.room.canonical_alias": {
+            "alt_added": {
+                "one": "%(senderName)s la til alternativ adresse %(addresses)s for dette rommet.",
+                "other": "%(senderName)s la til de alternative adressene %(addresses)s for dette rommet."
+            },
+            "alt_removed": {
+                "one": "%(senderName)s fjernet alternativ adresse %(addresses)s for dette rommet.",
+                "other": "%(senderName)s fjernet de alternative adressene %(addresses)s for dette rommet."
+            },
+            "changed": "%(senderName)s endret adressene til dette rommet.",
+            "changed_alternative": "%(senderName)s endret de alternative adressene for dette rommet.",
+            "changed_main_and_alternative": "%(senderName)s endret hovedadressen og de alternative adressene for dette rommet.",
+            "removed": "%(senderName)s fjernet hovedadressen til dette rommet.",
+            "set": "%(senderName)s angir hovedadressen for dette rommet til %(address)s."
+        },
+        "m.room.create": {
+            "continuation": "Dette rommet er en fortsettelse av en annen samtale.",
+            "see_older_messages": "Klikk for å se eldre meldinger.",
+            "unknown_predecessor": "Finner ikke den gamle versjonen av dette rommet (rom-ID:%(roomId)s), og vi har ikke fått 'via_servers' for å lete etter det.",
+            "unknown_predecessor_guess_server": "Finner ikke den gamle versjonen av dette rommet (rom-ID:%(roomId)s), og vi har ikke fått 'via_servers' for å lete etter det. Det er mulig at gjette serveren fra rom-ID-en vil fungere. Hvis du vil prøve, klikk på denne lenken:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "Ignorert forsøk på å deaktivere kryptering",
+            "disabled": "Kryptering er ikke skrudd på",
+            "enabled": "Meldinger i dette rommet er ende-til-ende-kryptert. Når folk blir med, kan du bekrefte dem i profilen deres, bare trykk på profilbildet deres.",
+            "enabled_dm": "Meldinger her er ende-til-ende-kryptert. Bekreft %(displayName)s i profilen deres - trykk på profilbildet deres.",
+            "enabled_local": "Meldinger i denne chatten vil være ende-til-ende-kryptert.",
+            "parameters_changed": "Noen krypteringsparametere er endret.",
+            "unsupported": "Krypteringen som brukes av dette rommet støttes ikke."
+        },
+        "m.room.guest_access": {
+            "can_join": "%(senderDisplayName)s har tillatt gjester å bli med i rommet.",
+            "forbidden": "%(senderDisplayName)s har hindret gjester fra å bli med i rommet.",
+            "unknown": "%(senderDisplayName)s endret gjestetilgangen til %(rule)s"
+        },
+        "m.room.history_visibility": {
+            "invited": "%(senderName)s gjorde fremtidig romhistorikk synlig for alle rommedlemmer, fra det tidspunktet de ble/blir invitert.",
+            "joined": "%(senderName)s gjorde fremtidig romhistorikk synlig for alle rommedlemmer, fra det tidspunktet de ble med.",
+            "shared": "%(senderName)s gjorde fremtidig romhistorikk synlig for alle rommedlemmer.",
+            "unknown": "%(senderName)s gjorde fremtidig romhistorikk synlig for alle rommedlemmer (%(visibility)s).",
+            "world_readable": "%(senderName)s gjorde fremtidig romhistorikk synlig for alle."
+        },
+        "m.room.join_rules": {
+            "invite": "%(senderDisplayName)s merket rommet som kun for inviterte.",
+            "knock": "%(senderDisplayName)s endret regelen for medlemsskap til å be om å bli med.",
+            "public": "%(senderDisplayName)s gjorde rommet offentlig for alle som kjenner til denne lenken.",
+            "restricted": "%(senderDisplayName)s endret hvem som kan bli med i dette rommet.",
+            "restricted_settings": "%(senderDisplayName)s endret hvem som kan bli med i dette rommet. <a>Vis innstillinger</a>.",
+            "unknown": "%(senderDisplayName)s endret regelen for medlemsskap til %(rule)s"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "%(targetName)s aksepterte invitasjonen til %(displayName)s",
+            "accepted_invite": "%(targetName)s aksepterte en invitasjon",
+            "ban": "%(senderName)s bannlyste %(targetName)s",
+            "ban_reason": "%(senderName)s bannlyste %(targetName)s: %(reason)s",
+            "change_avatar": "%(senderName)s endret profilbildet sitt",
+            "change_name": "%(oldDisplayName)s endret visningsnavnet sitt til %(displayName)s",
+            "change_name_avatar": "%(oldDisplayName)s endret visningsnavn og profilbilde",
+            "invite": "%(senderName)s inviterte %(targetName)s",
+            "join": "%(targetName)s ble med i rommet",
+            "kick": "%(senderName)s fjernet %(targetName)s",
+            "kick_reason": "%(senderName)s fjernet %(targetName)s: %(reason)s",
+            "left": "%(targetName)s forlot rommet",
+            "left_reason": "%(targetName)s forlot rommet: %(reason)s",
+            "no_change": "%(senderName)s gjorde ingen endringer",
+            "reject_invite": "%(targetName)s avslo invitasjonen",
+            "reject_invite_reason": "%(targetName)savviste invitasjonen:%(reason)s",
+            "remove_avatar": "%(senderName)s fjernet profilbildet sitt",
+            "remove_name": "%(senderName)s fjernet visningsnavnet sitt (%(oldDisplayName)s)",
+            "set_avatar": "%(senderName)s valgte seg et profilbilde",
+            "set_name": "%(senderName)s satte visningsnavnet sitt til %(displayName)s",
+            "unban": "%(senderName)s opphevde bannlysningen av %(targetName)s",
+            "withdrew_invite": "%(senderName)s trakk tilbake invitasjonen til %(targetName)s",
+            "withdrew_invite_reason": "%(senderName)s trakk tilbake invitasjonen til %(targetName)s: %(reason)s"
+        },
+        "m.room.name": {
+            "change": "%(senderDisplayName)s endret rommets navn fra %(oldRoomName)s til %(newRoomName)s.",
+            "remove": "%(senderDisplayName)s fjernet rommets navn.",
+            "set": "%(senderDisplayName)s endret rommets navn til %(roomName)s."
+        },
+        "m.room.pinned_events": {
+            "changed": "%(senderName)s endret de festede meldingene for rommet.",
+            "changed_link": "%(senderName)s endret <a>festede meldinger</a> for rommet.",
+            "pinned": "%(senderName)s festet en melding til dette rommet. Se alle festede meldinger.",
+            "pinned_link": "%(senderName)s festet <a>en melding</a> til dette rommet. Se alle <b>festede meldinger</b>.",
+            "unpinned": "%(senderName)s fjernet en festet melding fra dette rommet. Se alle festede meldinger.",
+            "unpinned_link": "%(senderName)s løsnet <a>en melding</a> fra dette rommet. Se alle <b>festede meldinger</b>."
+        },
+        "m.room.power_levels": {
+            "changed": "%(senderName)s endret effektnivået på %(powerLevelDiffText)s.",
+            "user_from_to": "%(userId)s gikk fra %(fromPowerLevel)s til %(toPowerLevel)s"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 Alle servere er utestengt fra å delta! Dette rommet kan ikke lenger brukes.",
+            "changed": "%(senderDisplayName)s endret serverens tilgangskontrollister (ACL) for dette rommet.",
+            "set": "%(senderDisplayName)s har satt serverens tilgangskontrollister (ACL) for dette rommet."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "%(senderName)s trakk tilbake invitasjonen dette rommet for %(targetDisplayName)s.",
+            "sent": "%(senderName)s sendte en invitasjon til %(targetDisplayName)s om å bli med i rommet."
+        },
+        "m.room.tombstone": "%(senderDisplayName)s oppgraderte dette rommet.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s endret emnet til \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s fjernet emnet."
+        },
+        "m.sticker": "%(senderDisplayName)s sendte et klistremerke.",
+        "m.video": {
+            "error_decrypting": "Feil under dekryptering av video",
+            "show_video": "Vis video"
+        },
+        "m.widget": {
+            "added": "%(widgetName)s-modulen ble lagt til av %(senderName)s",
+            "jitsi_ended": "Videokonferansen ble avsluttet av %(senderName)s",
+            "jitsi_join_right_prompt": "Bli med på konferansen fra rominformasjonskortet til høyre",
+            "jitsi_join_top_prompt": "Bli med på konferansen øverst i dette rommet",
+            "jitsi_started": "Videokonferanse startet av %(senderName)s",
+            "jitsi_updated": "Videokonferanse oppdatert av %(senderName)s",
+            "modified": "%(widgetName)s-modulen ble endret på av %(senderName)s",
+            "removed": "%(widgetName)s-modulen ble fjernet av %(senderName)s"
+        },
+        "mab": {
+            "collapse_reply_chain": "Skjul sitater",
+            "copy_link_thread": "Kopier lenke til tråd",
+            "expand_reply_chain": "Utvid sitater",
+            "label": "Meldingshandlinger",
+            "view_in_room": "Vis i rom"
+        },
+        "message_timestamp_received_at": "Mottatt den: %(dateTime)s",
+        "message_timestamp_sent_at": "Sendt den: %(dateTime)s",
+        "mjolnir": {
+            "changed_rule_glob": "%(senderName)s oppdaterte en forbudsregel som matchet %(oldGlob)s til å matche %(newGlob)s for %(reason)s",
+            "changed_rule_rooms": "%(senderName)s endret en regel som forbød rom som samsvarte %(oldGlob)s til å matche %(newGlob)s for %(reason)s",
+            "changed_rule_servers": "%(senderName)s endret en regel som forbød servere som matchet %(oldGlob)s til å matche %(newGlob)s for %(reason)s",
+            "changed_rule_users": "%(senderName)s endret en regel som utestengte brukere som matchet %(oldGlob)s for å matche %(newGlob)s for %(reason)s",
+            "created_rule": "%(senderName)s opprettet en utestengelsesregel som samsvarer %(glob)s for %(reason)s",
+            "created_rule_rooms": "%(senderName)s opprettet en regel som forbyr rom som samsvarer med %(glob)s for %(reason)s",
+            "created_rule_servers": "%(senderName)s opprettet en regel som forbyr servere som samsvarer med %(glob)s for %(reason)s",
+            "created_rule_users": "%(senderName)s opprettet en regel som utestenger brukere som matcher %(glob)s for %(reason)s",
+            "message_hidden": "Du har ignorert denne brukeren, så meldingen er skjult. <a>Vis likevel.</a>",
+            "removed_rule": "%(senderName)s fjernet en forbudsregel som samsvarer %(glob)s",
+            "removed_rule_rooms": "%(senderName)s fjernet regelen som forbyr rom som matcher %(glob)s",
+            "removed_rule_servers": "%(senderName)s fjernet regelen som utestenger servere som matcher %(glob)s",
+            "removed_rule_users": "%(senderName)s fjernet regelen som utestenger brukere som matcher %(glob)s",
+            "updated_invalid_rule": "%(senderName)s oppdaterte en ugyldig utestengelsesregel",
+            "updated_rule": "%(senderName)s oppdaterte en utestengelsesregel som samsvarer %(glob)s for %(reason)s",
+            "updated_rule_rooms": "%(senderName)s oppdaterte regelen som forbyr rom som matcher %(glob)s for %(reason)s",
+            "updated_rule_servers": "%(senderName)s oppdaterte regelen som forbyr servere som samsvarer med %(glob)s for %(reason)s",
+            "updated_rule_users": "%(senderName)s oppdaterte regelen som utestenger brukere som matcher %(glob)s for %(reason)s"
+        },
+        "no_permission_messages_before_invite": "Du har ikke tillatelse til å se meldinger fra før du ble invitert.",
+        "no_permission_messages_before_join": "Du har ikke tillatelse til å se meldinger fra før du ble medlem.",
+        "pending_moderation": "Melding venter på moderering",
+        "pending_moderation_reason": "Melding venter på moderering: %(reason)s",
+        "reactions": {
+            "add_reaction_prompt": "Legg til reaksjon",
+            "custom_reaction_fallback_label": "Egendefinert reaksjon",
+            "label": "%(reactors)s reagerte med %(content)s",
+            "tooltip_caption": "reagerte med %(shortName)s"
+        },
+        "read_receipt_title": {
+            "one": "Sett av %(count)s person",
+            "other": "Sett av %(count)s personer"
+        },
+        "read_receipts_label": "Lesebekreftelser",
+        "redacted": {
+            "tooltip": "Meldingen ble slettet den %(date)s"
+        },
+        "redaction": "Meldingen ble slettet av %(name)s",
+        "reply": {
+            "error_loading": "Kan ikke laste inn hendelsen som ble besvart, enten eksisterer den ikke eller du har ikke tillatelse til å se den.",
+            "in_reply_to": "<a>Som svar på</a> <pill>",
+            "in_reply_to_for_export": "Som svar på <a>denne meldingen</a>"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "Du er i ferd med å bli ført til et tredjeparts nettsted slik at du kan autentisere kontoen din for bruk med%(integrationsUrl)s. Ønsker du å fortsette?",
+            "dialog_title": "Legg til en integrering"
+        },
+        "self_redaction": "Meldingen ble slettet",
+        "send_state_encrypting": "Krypterer meldingen din …",
+        "send_state_failed": "Kunne ikke sende",
+        "send_state_sending": "Sender meldingen din...",
+        "send_state_sent": "Meldingen ble sendt",
+        "summary": {
+            "banned": {
+                "one": "ble utestengt",
+                "other": "ble utestengt %(count)s ganger"
+            },
+            "banned_multiple": {
+                "one": "ble utestengt",
+                "other": "ble utestengt %(count)s ganger"
+            },
+            "changed_avatar": {
+                "one": "%(oneUser)sendret profilbildet deres",
+                "other": "%(oneUser)sendret profilbildet deres %(count)s ganger"
+            },
+            "changed_avatar_multiple": {
+                "one": "%(severalUsers)sendret profilbildet deres",
+                "other": "%(severalUsers)sendret profilbildet deres %(count)s ganger"
+            },
+            "changed_name": {
+                "one": "%(oneUser)s endret navn",
+                "other": "%(oneUser)s endret navn %(count)s ganger"
+            },
+            "changed_name_multiple": {
+                "%(severalUsers)s endret navnene sine": "one"
+            },
+            "format": "%(nameList)s%(transitionList)s",
+            "hidden_event": {
+                "one": "%(oneUser)ssendte en skjult melding",
+                "other": "%(oneUser)ssendte %(count)s skjulte meldinger"
+            },
+            "hidden_event_multiple": {
+                "one": "%(severalUsers)ssendte en skjult melding",
+                "other": "%(severalUsers)ssendte %(count)s skjulte meldinger"
+            },
+            "invite_withdrawn": {
+                "one": "%(oneUser)sfikk invitasjonen trukket tilbake",
+                "other": "%(oneUser)sfikk invitasjonen trukket tilbake %(count)s ganger"
+            },
+            "invite_withdrawn_multiple": {
+                "%(severalUsers)sfikk sine invitasjoner trukket tilbake": "one"
+            },
+            "invited": {
+                "one": "ble invitert",
+                "other": "ble invitert %(count)s ganger"
+            },
+            "invited_multiple": {
+                "one": "ble invitert",
+                "other": "ble invitert %(count)s ganger"
+            },
+            "joined": {
+                "one": "%(oneUser)sble med",
+                "other": "%(oneUser)sble med %(count)s ganger"
+            },
+            "joined_and_left": {
+                "%(oneUser)sble med og forlot igjen": "one"
+            },
+            "joined_and_left_multiple": {
+                "one": "%(severalUsers)sble med og forlot",
+                "other": "%(severalUsers)sble med og forlot %(count)s ganger"
+            },
+            "joined_multiple": {
+                "one": "%(severalUsers)sble med",
+                "other": "%(severalUsers)sble med %(count)s ganger"
+            },
+            "kicked": {
+                "one": "ble fjernet",
+                "other": "ble fjernet %(count)s ganger"
+            },
+            "kicked_multiple": {
+                "one": "ble fjernet",
+                "other": "ble fjernet %(count)s ganger"
+            },
+            "left": {
+                "one": "%(oneUser)s forlot",
+                "other": "%(oneUser)s forlot %(count)s ganger"
+            },
+            "left_multiple": {
+                "one": "%(severalUsers)s forlot",
+                "other": "%(severalUsers)s forlot %(count)s ganger"
+            },
+            "no_change": {
+                "one": "%(oneUser)sgjorde ingen endringer",
+                "other": "%(oneUser)sgjorde ingen endringer %(count)s ganger"
+            },
+            "no_change_multiple": {
+                "one": "%(severalUsers)sgjorde ingen endringer",
+                "other": "%(severalUsers)sgjorde ingen endringer %(count)s ganger"
+            },
+            "pinned_events": {
+                "one": "%(oneUser)sendret <a>festede meldinger</a> for rommet",
+                "other": "%(oneUser)sendret <a>festede meldinger</a> for rommet %(count)s ganger"
+            },
+            "pinned_events_multiple": {
+                "one": "%(severalUsers)sendret <a>festede meldinger</a> for rommet",
+                "other": "%(severalUsers)sendret <a>festede meldinger</a> for rommet %(count)s ganger"
+            },
+            "redacted": {
+                "one": "%(oneUser)sfjernet en melding",
+                "other": "%(oneUser)sfjernet %(count)s meldinger"
+            },
+            "redacted_multiple": {
+                "one": "%(severalUsers)sfjernet en melding",
+                "other": "%(severalUsers)sfjernet %(count)s meldinger"
+            },
+            "rejected_invite": {
+                "one": "%(oneUser)savviste invitasjonen",
+                "other": "%(oneUser)savviste invitasjonen deres %(count)s ganger"
+            },
+            "rejected_invite_multiple": {
+                "one": "%(severalUsers)savviste invitasjonene deres",
+                "other": "%(severalUsers)savviste invitasjonene deres %(count)s ganger"
+            },
+            "rejoined": {
+                "one": "%(oneUser)sforlot og ble med igjen",
+                "other": "%(oneUser)sforlot og ble med igjen %(count)s ganger"
+            },
+            "rejoined_multiple": {
+                "one": "%(severalUsers)sforlot og ble med igjen",
+                "other": "%(severalUsers)sforlot og ble med igjen%(count)s ganger"
+            },
+            "server_acls": {
+                "one": "%(oneUser)sendret serverens ACLer",
+                "other": "%(oneUser)sendret serverens ACLer %(count)s ganger"
+            },
+            "server_acls_multiple": {
+                "one": "%(severalUsers)sendret serverens ACLer",
+                "other": "%(severalUsers)sendret serverens ACLer %(count)s ganger"
+            },
+            "unbanned": {
+                "one": "fikk opphevet forbudet",
+                "other": "fikk opphevet forbudet %(count)s ganger"
+            },
+            "unbanned_multiple": {
+                "one": "fikk opphevet forbudet",
+                "other": "fikk opphevet forbudet %(count)s ganger"
+            }
+        },
+        "thread_info_basic": "Fra en tråd",
+        "typing_indicator": {
+            "more_users": {
+                "other": "%(names)s og %(count)s andre skriver …",
+                "one": "%(names)s og én annen bruker skriver …"
+            },
+            "one_user": "%(displayName)s skriver …",
+            "two_users": "%(names)s og %(lastPerson)s skriver …"
+        },
+        "undecryptable_tooltip": "Denne meldingen kunne ikke dekrypteres",
+        "url_preview": {
+            "close": "Lukk forhåndsvisning",
+            "show_n_more": {
+                "one": "Vis %(count)s annen forhåndsvisning",
+                "other": "Vis %(count)s andre forhåndsvisninger"
+            }
+        }
+    },
+    "truncated_list_n_more": {
+        "Og %(count)s til...": "other"
+    },
+    "unsupported_browser": {
+        "description": "Hvis du fortsetter, kan noen funksjoner slutte å fungere, og det er en risiko for at du kan miste data i fremtiden. Oppdater nettleseren din for å fortsette å bruke%(brand)s.",
+        "title": "%(brand)s støtter ikke denne nettleseren"
+    },
+    "unsupported_server_description": "Denne serveren bruker en eldre versjon av Matrix. Oppgrader til Matrix %(version)s for å bruke %(brand)s uten feil.",
+    "unsupported_server_title": "Serveren din støttes ikke",
+    "update": {
+        "changelog": "Endringslogg",
+        "check_action": "Let etter oppdateringer",
+        "checking": "Ser etter en oppdatering...",
+        "downloading": "Laster ned oppdatering...",
+        "error_encountered": "Feil oppstått (%(errorDetail)s).",
+        "error_unable_load_commit": "Kan ikke laste inn forpliktelsesdetaljer: %(msg)s",
+        "new_version_available": "Ny versjon tilgjengelig. <a>Oppdater nå.</a>",
+        "no_update": "Ingen oppdateringer er tilgjengelige.",
+        "release_notes_toast_title": "Hva er nytt",
+        "see_changes_button": "Hva er nytt?",
+        "toast_description": "Ny versjon av %(brand)s er tilgjengelig",
+        "toast_title": "Oppdater %(brand)s",
+        "unavailable": "Ikke tilgjengelig"
+    },
+    "update_room_access_modal": {
+        "description": "For å opprette en delingslenke, lag dette rommet <b>offentlig</b> eller aktiver alternativet for brukere å <b>be om å bli med</b> Dette lar gjester bli med uten å bli invitert.",
+        "dont_change_description": "Hvis du ikke ønsker å endre tilgangen til dette rommet, kan du opprette et nytt rom for lenken.",
+        "no_change": "Jeg vil ikke endre tilgangsnivået.",
+        "revert_access_description": "(Dette kan tilbakestilles til den forrige verdien i Rominnstillinger: <b>Sikkerhet og personvern</b> / <b>Tilgang</b>)",
+        "title": "Tillat gjestebrukere å bli med i dette rommet"
+    },
+    "upload_failed_generic": "Filen '%(fileName)s' kunne ikke lastes opp.",
+    "upload_failed_size": "Filen \"%(fileName)s\" er større enn hjemmeserverens grense for opplastninger",
+    "upload_failed_title": "Opplasting feilet",
+    "upload_file": {
+        "cancel_all_button": "Avbryt alt",
+        "error_file_too_large": "Denne filen er <b>for stor</b> til å lastes opp. Grensen for filstørrelse er %(limit)s mens denne filen er %(sizeOfThisFile)s.",
+        "error_files_too_large": "Disse filene er <b>for store</b> å laste opp. Grensen for filstørrelse er %(limit)s.",
+        "error_some_files_too_large": "Noen filer er <b>for store</b> til å kunne lastes opp. Grensen for filstørrelse er %(limit)s.",
+        "error_title": "Opplastingsfeil",
+        "not_image": "Filen du har valgt, er ikke en gyldig bildefil.",
+        "title": "Last opp filer",
+        "title_progress": "Last opp filer (%(current)s av %(total)s)",
+        "upload_all_button": "Last opp alle",
+        "upload_n_others_button": {
+            "Last opp %(count)s andre filer": "Last opp %(count)s andre filer",
+            "Last opp %(count)s annen fil": "Last opp %(count)s en annen fil"
+        }
+    },
+    "user_info": {
+        "admin_tools_section": "Adminverktøy",
+        "ban_button_room": "Utesteng fra rommet",
+        "ban_button_space": "Utesteng fra området",
+        "ban_room_confirm_title": "Utestenge fra %(roomName)s",
+        "ban_space_everything": "Utesteng dem fra alt jeg kan",
+        "ban_space_specific": "Utesteng dem fra spesifikke ting jeg er i stand til",
+        "deactivate_confirm_action": "Deaktiver brukeren",
+        "deactivate_confirm_description": "Deaktivering av denne brukeren vil logge dem ut og forhindre dem i å logge på igjen. I tillegg vil de forlate alle rommene de er i. Denne handlingen kan ikke reverseres. Er du sikker på at du vil deaktivere denne brukeren?",
+        "deactivate_confirm_title": "Vil du deaktivere brukeren?",
+        "demote_button": "Degrader",
+        "demote_self_confirm_description_space": "Du vil ikke kunne angre denne endringen mens du degraderer deg selv, hvis du er den siste privilegerte brukeren i området, vil det være umulig å gjenvinne privilegier.",
+        "demote_self_confirm_room": "Du vil ikke kunne angre denne endringen mens du degraderer deg selv, hvis du er den siste privilegerte brukeren i rommet, vil det være umulig å gjenvinne privilegier.",
+        "demote_self_confirm_title": "Vil du degradere deg selv?",
+        "disinvite_button_room": "Fjern invitasjonen fra rommet",
+        "disinvite_button_room_name": "Fjern invitasjonen fra %(roomName)s",
+        "disinvite_button_space": "Fjern invitasjonen fra området",
+        "error_ban_user": "Mislyktes i å bannlyse brukeren",
+        "error_deactivate": "Mislyktes i å deaktivere brukeren",
+        "error_kicking_user": "Kunne ikke fjerne brukeren",
+        "error_mute_user": "Kunne ikke dempe brukeren",
+        "error_revoke_3pid_invite_description": "Klarte ikke å trekke tilbake invitasjonen. Tjener kan ha et forbigående problem, eller det kan hende at du ikke har tilstrekkelige rettigheter for å trekke tilbake invitasjonen.",
+        "error_revoke_3pid_invite_title": "Klarte ikke å trekke tilbake invitasjon",
+        "ignore_button": "Ignorer",
+        "ignore_confirm_description": "Alle meldinger og invitasjoner fra denne brukeren vil bli skjult. Er du sikker på at du vil ignorere dem?",
+        "ignore_confirm_title": "Ignorer %(user)s",
+        "invited_by": "Invitert av %(sender)s",
+        "jump_to_rr_button": "Hopp til lesekvitteringen",
+        "kick_button_room": "Fjern fra rommet",
+        "kick_button_room_name": "Fjern fra %(roomName)s",
+        "kick_button_space": "Fjern fra området",
+        "kick_button_space_everything": "Fjern dem fra alt jeg kan",
+        "kick_space_specific": "Fjern dem fra spesifikke ting jeg kan",
+        "kick_space_warning": "De vil fortsatt kunne få tilgang til det du ikke er administrator av.",
+        "promote_warning": "Du vil ikke kunne angre denne endringen ettersom du promoterer brukeren til å ha samme tilgangsnivå som deg selv.",
+        "redact": {
+            "confirm_button": {
+                "Slett %(count)s meldinger": "Fjern 1 melding",
+                "Slett 1 melding": "Fjern %(count)s meldinger"
+            },
+            "confirm_description_1": {
+                "one": "Du er i ferd med å fjerne %(count)s melding fra %(user)s. Dette vil fjerne dem permanent for alle i samtalen. Ønsker du å fortsette?",
+                "other": "Du er i ferd med å fjerne %(count)s meldinger fra %(user)s. Dette vil fjerne dem permanent for alle i samtalen. Ønsker du å fortsette?"
+            },
+            "confirm_description_2": "For en stor mengde meldinger kan dette ta litt tid. Vennligst ikke oppdater klienten din i mellomtiden.",
+            "confirm_keep_state_explainer": "Fjern merket hvis du også vil fjerne systemmeldinger på denne brukeren (f.eks. medlemskapsendring, profilendring...)",
+            "confirm_keep_state_label": "Ta vare på systemmeldinger",
+            "confirm_title": "Fjern nylige meldinger fra %(user)s",
+            "no_recent_messages_description": "Prøv å bla opp i tidslinjen for å se om det er noen tidligere.",
+            "no_recent_messages_title": "Ingen nyere meldinger fra %(user)s funnet"
+        },
+        "redact_button": "Fjern meldinger",
+        "revoke_invite": "Trekk tilbake invitasjonen",
+        "room_encrypted": "Meldinger i dette rommet er start-til-slutt-kryptert.",
+        "room_encrypted_detail": "Meldingene dine er sikret, og det er bare du og mottakeren som har de unike nøklene til å låse dem opp.",
+        "room_unencrypted": "Meldinger i dette rommet er ikke start-til-slutt-kryptert.",
+        "room_unencrypted_detail": "I krypterte rom er meldingene dine sikret, og det er bare du og mottakeren som har de unike nøklene til å låse dem opp.",
+        "send_message": "Send melding",
+        "share_button": "Del profil",
+        "unban_button_room": "Opphev utestengelse fra rommet",
+        "unban_button_space": "Opphev utestengelsen fra området",
+        "unban_room_confirm_title": "Opphev utestengelsen fra %(roomName)s",
+        "unban_space_everything": "Opphev utestengelsen av dem fra alt jeg er i stand til",
+        "unban_space_specific": "Opphev utestengelsen av dem fra spesifikke ting jeg er i stand til",
+        "unban_space_warning": "De vil ikke kunne få tilgang til det du ikke er administrator av.",
+        "unignore_button": "Ikke ignorer",
+        "verification_unavailable": "Brukerverifisering ikke tilgjengelig",
+        "verify_button": "Verifiser bruker",
+        "verify_explainer": "For ekstra sikkerhet, bekreft denne brukeren ved å sjekke en engangskode på begge enhetene dine."
+    },
+    "user_menu": {
+        "link_new_device": "Lenke til ny enhet",
+        "settings": "Alle innstillinger",
+        "switch_theme_dark": "Bytt til mørk modus",
+        "switch_theme_light": "Bytt til lys modus"
+    },
+    "voip": {
+        "already_in_call": "Allerede i en samtale",
+        "already_in_call_person": "Du er allerede i en samtale med denne personen.",
+        "answered_elsewhere": "Besvart andre steder",
+        "answered_elsewhere_description": "Samtalen ble besvart på en annen enhet.",
+        "call_failed": "Oppringning mislyktes",
+        "call_failed_description": "Samtalen kunne ikke etableres",
+        "call_failed_media": "Samtalen mislyktes fordi du fikk ikke tilgang til webkamera eller mikrofon. Sørg for at:",
+        "call_failed_media_applications": "Ingen andre applikasjoner bruker webkameraet",
+        "call_failed_media_connected": "En mikrofon og webkamera er koblet til og satt opp riktig",
+        "call_failed_media_permissions": "Tillatelse er gitt til å bruke webkameraet",
+        "call_failed_microphone": "Samtalen mislyktes fordi mikrofonen ikke var tilgjengelig. Sjekk at en mikrofon er koblet til og satt opp riktig.",
+        "call_held": "%(peerName)s holdt samtalen",
+        "call_held_resume": "Du holdt samtalen <a> Fortsett </a>",
+        "call_held_switch": "Du holdt samtalen <a>Bytt</a>",
+        "call_toast_unknown_room": "Ukjent rom",
+        "camera_disabled": "Kameraet ditt er slått av",
+        "camera_enabled": "Kameraet ditt er fortsatt aktivert",
+        "cannot_call_yourself_description": "Du kan ikke ringe deg selv.",
+        "close_lobby": "Lukk lobbyen",
+        "connecting": "Kobler til",
+        "connection_lost": "Mistet forbindelsen til serveren",
+        "connection_lost_description": "Du kan ikke ringe uten tilkobling til serveren.",
+        "consulting": "Rådføring med %(transferTarget)s. <a>Overfør til %(transferee)s</a>",
+        "default_device": "Standardenhet",
+        "dial": "Ring",
+        "dialpad": "Nummerpanel",
+        "disable_camera": "Stopp kameraet",
+        "disable_microphone": "Demp mikrofonen",
+        "disabled_no_one_here": "Det er ingen her å ringe",
+        "disabled_no_perms_start_video_call": "Du har ikke tillatelse til å starte videosamtaler",
+        "disabled_no_perms_start_voice_call": "Du har ikke tillatelse til å starte taleanrop",
+        "disabled_ongoing_call": "Pågående samtale",
+        "element_call": "Element Call",
+        "enable_camera": "Start kameraet",
+        "enable_microphone": "Opphev demping av mikrofonen",
+        "expand": "Gå tilbake til samtalen",
+        "get_call_link": "Del anropslenke",
+        "hangup": "Legg på røret",
+        "hide_sidebar_button": "Skjul sidepanel",
+        "input_devices": "Inndataenheter",
+        "jitsi_call": "Jitsi Conference",
+        "join_button_tooltip_call_full": "Beklager - denne samtalen er for øyeblikket full",
+        "legacy_call": "Eldre samtale",
+        "maximise": "Fyll skjermen",
+        "maximise_call": "Maksimer samtalen",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferanser"
+        },
+        "minimise_call": "Minimer samtalen",
+        "misconfigured_server": "Oppringingen feilet på grunn av feil-konfigurert tjener",
+        "misconfigured_server_description": "Vennligst be administratoren av din hjemmetjener (<code>%(homeserverDomain)s</code>) til å konfigurere en TURN tjener slik at samtaler vil fungere best mulig.",
+        "misconfigured_server_fallback": "Alternativt, du kan prøve å bruke den offentlige serveren på<server/>, men dette vil ikke være like pålitelig, og det vil dele IP-adressen din med den serveren. Du kan også administrere dette i Innstillinger.",
+        "misconfigured_server_fallback_accept": "Prøv å bruke %(server)s",
+        "more_button": "Mer",
+        "msisdn_lookup_failed": "Kan ikke slå opp telefonnummeret",
+        "msisdn_lookup_failed_description": "Det oppstod en feil ved oppslag av telefonnummeret",
+        "msisdn_transfer_failed": "Kan ikke overføre samtalen",
+        "n_people_joined": {
+            "one": "%(count)s person ble med",
+            "other": "%(count)s personer ble med"
+        },
+        "no_audio_input_description": "Vi fant ingen mikrofon på enheten din. Kontroller innstillingene og prøv igjen.",
+        "no_audio_input_title": "Ingen mikrofon funnet",
+        "no_media_perms_description": "Du må kanskje manuelt gi %(brand)s tilgang til mikrofonen/webkameraet",
+        "no_media_perms_title": "Ingen mediatillatelser",
+        "no_permission_conference": "Tillatelse kreves",
+        "no_permission_conference_description": "Du har ikke tillatelse til å starte en konferansesamtale i dette rommet",
+        "on_hold": "%(name)s står på vent",
+        "output_devices": "Utgangsenheter",
+        "screenshare_monitor": "Del hele skjermen",
+        "screenshare_title": "Del innhold",
+        "screenshare_window": "Applikasjonsvindu",
+        "show_sidebar_button": "Vis sidepanel",
+        "silence": "Demp samtale",
+        "silenced": "Varslinger er dempet",
+        "start_screenshare": "Begynn å dele skjermen din",
+        "stop_screenshare": "Slutt å dele skjermen din",
+        "too_many_calls": "For mange samtaler",
+        "too_many_calls_description": "Du har nådd det maksimale antallet samtidige anrop.",
+        "transfer_consult_first_label": "Rådfør deg først",
+        "transfer_failed": "Overføring mislyktes",
+        "transfer_failed_description": "Kunne ikke overføre samtalen",
+        "unable_to_access_audio_input_description": "Vi fikk ikke tilgang til mikrofonen din. Vennligst sjekk nettleserinnstillingene og prøv igjen.",
+        "unable_to_access_audio_input_title": "Får ikke tilgang til mikrofonen din",
+        "unable_to_access_media": "Ingen tilgang til webkamera / mikrofon",
+        "unable_to_access_microphone": "Kan ikke få tilgang til mikrofonen",
+        "unknown_caller": "Ukjent oppringer",
+        "unknown_person": "ukjent person",
+        "unsilence": "Lyd på",
+        "unsupported": "Samtaler støttes ikke",
+        "unsupported_browser": "Du kan ikke ringe i denne nettleseren.",
+        "user_busy": "Bruker opptatt",
+        "user_busy_description": "Brukeren du ringte er opptatt.",
+        "user_is_presenting": "%(sharerName)s presenterer",
+        "video_call": "Videosamtale",
+        "video_call_started": "Videosamtale startet",
+        "video_call_using": "Videosamtale ved hjelp av:",
+        "voice_call": "Stemmesamtale",
+        "you_are_presenting": "Du presenterer"
+    },
+    "web_default_device_name": "%(appName)s: %(browserName)s på %(osName)s",
+    "welcome_to_element": "Velkommen til Element",
+    "widget": {
+        "added_by": "Modulen ble lagt til av",
+        "capabilities_dialog": {
+            "content_starting_text": "Denne widgeten vil gjerne:",
+            "decline_all_permission": "Avslå alle",
+            "remember_Selection": "Husk mitt valg for denne widgeten",
+            "title": "Godkjenn widgettillatelser"
+        },
+        "capability": {
+            "always_on_screen_generic": "Forbli på skjermen mens programmet kjører",
+            "always_on_screen_viewing_another_room": "Forbli på skjermen når du ser på et annet rom, når programmet kjører",
+            "any_room": "Ovennevnte, men i alle rom blir du med eller invitert til også",
+            "byline_empty_state_key": "med en tom tilstandsnøkkel",
+            "byline_state_key": "med tilstandsnøkkel %(stateKey)s",
+            "capability": "Kapasiteten til <b>%(capability)s</b>",
+            "change_avatar_active_room": "Endre avataren til det aktive rommet ditt",
+            "change_avatar_this_room": "Endre avataren til dette rommet",
+            "change_name_active_room": "Endre navnet på det aktive rommet ditt",
+            "change_name_this_room": "Endre rommets navn",
+            "change_topic_active_room": "Endre tema for det aktive rommet ditt",
+            "change_topic_this_room": "Endre dette rommets tema",
+            "receive_membership_active_room": "Se når folk blir med, forlater eller blir invitert til det aktive rommet ditt",
+            "receive_membership_this_room": "Se når folk blir med, forlater eller blir invitert til dette rommet",
+            "remove_ban_invite_leave_active_room": "Fjern, utesteng eller inviter folk til det aktive rommet ditt, og få deg til å forlate",
+            "remove_ban_invite_leave_this_room": "Fjern, utesteng eller inviter folk til dette rommet, og få deg til å forlate rommet",
+            "see_avatar_change_active_room": "Se når avataren endres i det aktive rommet ditt",
+            "see_avatar_change_this_room": "Se når avataren endres i dette rommet",
+            "see_event_type_sent_active_room": "Se <b> %(eventType)s </b> hendelser lagt ut i ditt aktive rom",
+            "see_event_type_sent_this_room": "Se <b> %(eventType)s </b> hendelser lagt ut i dette rommet",
+            "see_images_sent_active_room": "Se bilder lagt ut på ditt aktive rom",
+            "see_images_sent_this_room": "Se bilder som er lagt ut i dette rommet",
+            "see_messages_sent_active_room": "Se meldinger som er lagt ut i ditt aktive rom",
+            "see_messages_sent_this_room": "Se meldinger som er lagt ut i dette rommet",
+            "see_msgtype_sent_active_room": "Se <b> %(msgtype)s </b> meldinger som er lagt ut i ditt aktive rom",
+            "see_msgtype_sent_this_room": "Se <b> %(msgtype)s </b> meldinger som er lagt ut i dette rommet",
+            "see_name_change_active_room": "Se når navnet endres i ditt aktive rom",
+            "see_name_change_this_room": "Se når navnet endres i dette rommet",
+            "see_sent_emotes_active_room": "Se emoter lagt ut i ditt aktive rom",
+            "see_sent_emotes_this_room": "Se emoter lagt ut i dette rommet",
+            "see_sent_files_active_room": "Se generelle filer som er lagt ut i det aktive rommet",
+            "see_sent_files_this_room": "Se generelle filer som er lagt ut i dette rommet",
+            "see_sticker_posted_active_room": "Se når noen legger ut et klistremerke i det aktive rommet ditt",
+            "see_sticker_posted_this_room": "Se når et klistremerke er lagt ut i dette rommet",
+            "see_text_messages_sent_active_room": "Se tekstmeldinger som er lagt ut i ditt aktive rom",
+            "see_text_messages_sent_this_room": "Se tekstmeldinger som er lagt ut i dette rommet",
+            "see_topic_change_active_room": "Se når emnet endres i det aktive rommet ditt",
+            "see_topic_change_this_room": "Se når emnet endres i dette rommet",
+            "see_videos_sent_active_room": "Se videoer lagt ut på ditt aktive rom",
+            "see_videos_sent_this_room": "Se videoer lagt ut i dette rommet",
+            "send_emotes_active_room": "Send emotes som deg i det aktive rommet ditt",
+            "send_emotes_this_room": "Send emotes som deg i dette rommet",
+            "send_event_type_active_room": "Send <b> %(eventType)s </b> hendelser som deg i ditt aktive rom",
+            "send_event_type_this_room": "Send <b> %(eventType)s </b> hendelser som deg i dette rommet",
+            "send_files_active_room": "Send generelle filer som deg i ditt aktive rom",
+            "send_files_this_room": "Send generelle filer som deg i dette rommet",
+            "send_images_active_room": "Send bilder som deg i ditt aktive rom",
+            "send_images_this_room": "Send bilder som deg i dette rommet",
+            "send_messages_active_room": "Send meldinger som deg i det aktive rommet ditt",
+            "send_messages_this_room": "Send meldinger som deg i dette rommet",
+            "send_msgtype_active_room": "Send <b>%(msgtype)s</b> meldinger som deg i det aktive rommet ditt",
+            "send_msgtype_this_room": "Send <b> %(msgtype)s </b> meldinger som deg i dette rommet",
+            "send_stickers_active_room": "Send klistremerker inn i det aktive rommet ditt",
+            "send_stickers_active_room_as_you": "Send klistremerker til det aktive rommet ditt mens du er",
+            "send_stickers_this_room": "Send klistremerker inn i dette rommet",
+            "send_stickers_this_room_as_you": "Send klistremerker til dette rommet mens du",
+            "send_text_messages_active_room": "Send tekstmeldinger som deg i ditt aktive rom",
+            "send_text_messages_this_room": "Send tekstmeldinger som deg i dette rommet",
+            "send_videos_active_room": "Send videoer som deg i ditt aktive rom",
+            "send_videos_this_room": "Send videoer som deg i dette rommet",
+            "specific_room": "Ovenstående, men <Room /> også",
+            "switch_room": "Endre hvilket rom du ser på",
+            "switch_room_message_user": "Endre hvilket rom, melding eller bruker du ser på"
+        },
+        "close_to_view_right_panel": "Lukk denne widgeten for å se den i dette panelet",
+        "context_menu": {
+            "delete": "Slett modul",
+            "delete_warning": "Hvis du sletter en widget, fjernes den for alle brukere i dette rommet. Er du sikker på at du vil slette denne widgeten?",
+            "move_left": "Gå til venstre",
+            "move_right": "Gå til høyre",
+            "remove": "Fjern for alle",
+            "revoke": "Trekk tilbake rettigheter",
+            "screenshot": "Ta et bilde",
+            "start_audio_stream": "Start lydstrøm"
+        },
+        "cookie_warning": "Denne modulen bruker kanskje infokapsler.",
+        "error_hangup_description": "Du ble koblet fra samtalen. (Feil:%(message)s)",
+        "error_hangup_title": "Forbindelse brutt",
+        "error_loading": "Feil ved lasting av widget",
+        "error_mixed_content": "Feil - Blandet innhold",
+        "error_need_invite_permission": "Du må kunne invitere andre brukere for å gjøre det.",
+        "error_need_kick_permission": "Du må kunne sparke brukere for å gjøre det.",
+        "error_need_to_be_logged_in": "Du må være logget inn.",
+        "error_unable_start_audio_stream_description": "Kan ikke starte lydstrømming.",
+        "error_unable_start_audio_stream_title": "Kan ikke starte livestream",
+        "modal_data_warning": "Dataene nedenfor deles med %(widgetDomain)s",
+        "modal_title_default": "Modal Widget",
+        "no_name": "Ukjent app",
+        "open_id_permissions_dialog": {
+            "remember_selection": "Husk dette",
+            "starting_text": "Widgeten vil bekrefte bruker-ID-en din, men vil ikke kunne utføre handlinger for deg:",
+            "title": "Tillat denne widgeten å bekrefte identiteten din"
+        },
+        "popout": "Utsprettsmodul",
+        "set_room_layout": "Angi romoppsettet mitt for alle",
+        "shared_data_avatar": "URL til profilbildet ditt",
+        "shared_data_device_id": "Din enhets-ID",
+        "shared_data_lang": "Ditt språk",
+        "shared_data_mxid": "Din bruker-ID",
+        "shared_data_name": "Ditt visningsnavn",
+        "shared_data_room_id": "Rom-ID",
+        "shared_data_theme": "Ditt tema",
+        "shared_data_url": "%(brand)s-URL",
+        "shared_data_warning": "Bruk av denne widgeten kan dele data <helpIcon /> med%(widgetDomain)s.",
+        "shared_data_warning_im": "Bruk av denne widgeten kan dele data <helpIcon /> med %(widgetDomain)s & integrasjonsadministratoren din.",
+        "shared_data_widget_id": "Modul-ID",
+        "unencrypted_warning": "Widgets bruker ikke meldingskryptering.",
+        "unmaximise": "Fjern maksimering",
+        "unpin_to_view_right_panel": "Fjern denne widgeten for å vise den i dette panelet"
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "Bare store bokstaver er nesten like enkelt å gjette som bare små bokstaver",
+            "anotherWord": "Legg til et ord eller to til. Uvanlige ord er bedre.",
+            "associatedYears": "Unngå år som er knyttet til deg",
+            "capitalization": "Store bokstaver er ikke spesielt nyttig",
+            "dates": "Unngå datoer og år som er knyttet til deg",
+            "l33t": "Forutsigbar erstatninger som ‘ @‘ istedet for ‘a’ hjelper ikke mye",
+            "longerKeyboardPattern": "Bruke et lengre og mer uventet tastatur mønster",
+            "noNeed": "Ikke nødvendig med symboler, sifre eller bokstaver",
+            "pwned": "Hvis du bruker dette passordet andre steder, bør du endre det.",
+            "recentYears": "Unngå nylige år",
+            "repeated": "Unngå gjentatte ord og tegn",
+            "reverseWords": "Ord som er skrevet baklengs er vanskeligere å huske.",
+            "sequences": "Unngå sekvenser",
+            "useWords": "Bruk noen få ord, unngå vanlig fraser"
+        },
+        "warnings": {
+            "common": "Dette er et veldig vanlig passord",
+            "commonNames": "Vanlige navn og etternavn er enkle å gjette",
+            "dates": "Datoer er ofte lette å gjette",
+            "extendedRepeat": "Gjentakelser som «abcabcabc» er bare litt vanskeligere å gjette enn «abc»",
+            "keyPattern": "Korte tastatur mønstre er lett å gjette",
+            "namesByThemselves": "Navn og etternavn er i seg selv lette å gjette",
+            "pwned": "Passordet ditt ble avslørt ved et datainnbrudd på Internett.",
+            "recentYears": "Nylige år er lette å gjette",
+            "sequences": "Sekvenser som abc eller 6543 er enkle å gjette",
+            "similarToCommon": "Dette ligner på et passord som er brukt mye",
+            "simpleRepeat": "Gjentakelser som «aaa» er enkle å gjette",
+            "straightRow": "En rad av taster etter hverandre er enklt å gjette",
+            "topHundred": "Dette er et topp-100 vanlig passord",
+            "topTen": "Dette er et topp-10 vanlig passord",
+            "userInputs": "Det bør ikke være noen personlige eller siderelaterte data.",
+            "wordByItself": "Et ord alene er lett å gjette"
+        }
+    }
+}
diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json
index 07380f4b984be5bcb9e846afb177b8209a372ffe..c8cfd1e8ba581048854c3185ae0da25e990d5ae1 100644
--- a/src/i18n/strings/nl.json
+++ b/src/i18n/strings/nl.json
@@ -94,7 +94,6 @@
         "react": "Reageren",
         "refresh": "Herladen",
         "register": "Registreren",
-        "reject": "Weigeren",
         "reload": "Herladen",
         "remove": "Verwijderen",
         "rename": "Hernoemen",
@@ -336,7 +335,6 @@
         "download_logs": "Logs downloaden",
         "downloading_logs": "Logs downloaden",
         "error_empty": "Laat ons weten wat er verkeerd is gegaan, of nog beter, maak een foutrapport aan op GitHub, waarin je het probleem beschrijft.",
-        "failed_send_logs": "Versturen van logs mislukt: ",
         "github_issue": "GitHub-melding",
         "introduction": "Als je een bug via GitHub hebt ingediend, kunnen foutopsporingslogboeken ons helpen het probleem op te sporen. ",
         "log_request": "<a>Stuur ons jouw logs</a> om dit in de toekomst te helpen voorkomen.",
@@ -375,7 +373,6 @@
         "access_token": "Toegangstoken",
         "accessibility": "Toegankelijkheid",
         "advanced": "Geavanceerd",
-        "all_rooms": "Alle kamers",
         "analytics": "Gebruiksgegevens",
         "and_n_others": {
             "other": "en %(count)s anderen…",
@@ -393,7 +390,6 @@
         "capabilities": "Mogelijkheden",
         "copied": "Gekopieerd!",
         "credits": "Met dank aan",
-        "cross_signing": "Kruiselings ondertekenen",
         "dark": "Donker",
         "description": "Omschrijving",
         "deselect_all": "Allemaal deselecteren",
@@ -470,7 +466,6 @@
         "room_name": "Kamernaam",
         "rooms": "Kamers",
         "secure_backup": "Beveiligde back-up",
-        "security": "Beveiliging",
         "select_all": "Allemaal selecteren",
         "server": "Server",
         "settings": "Instellingen",
@@ -488,7 +483,6 @@
         "thread": "Draad",
         "threads": "Onderwerpen",
         "timeline": "Tijdslijn",
-        "trusted": "Vertrouwd",
         "unencrypted": "Onversleuteld",
         "unmute": "Niet dempen",
         "unnamed_room": "Naamloze Kamer",
@@ -698,44 +692,23 @@
     "empty_room_was_name": "Lege ruimte (was %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Voer uw veiligheidswachtwoord in of <button>gebruik uw veiligheidssleutel</button> om door te gaan.",
             "key_validation_text": {
-                "invalid_security_key": "Ongeldige veiligheidssleutel",
-                "recovery_key_is_correct": "Ziet er goed uit!",
-                "wrong_file_type": "Verkeerd bestandstype",
                 "wrong_security_key": "Verkeerde veiligheidssleutel"
             },
-            "reset_title": "Alles opnieuw instellen",
-            "reset_warning_1": "Doe dit alleen als u geen ander apparaat hebt om de verificatie mee uit te voeren.",
-            "reset_warning_2": "Als u alles reset zult u opnieuw opstarten zonder vertrouwde sessies, zonder vertrouwde personen, en zult u misschien geen oude berichten meer kunnen zien.",
             "restoring": "Sleutels herstellen vanaf back-up",
-            "security_key_title": "Veiligheidssleutel",
-            "security_phrase_incorrect_error": "Geen toegang tot geheime opslag. Controleer of u het juiste veiligheidswachtwoord hebt ingevoerd.",
-            "security_phrase_title": "Veiligheidswachtwoord",
-            "separator": "%(securityKey)s of %(recoveryFile)s",
-            "use_security_key_prompt": "Gebruik uw veiligheidssleutel om verder te gaan."
+            "security_key_title": "Veiligheidssleutel"
         },
         "bootstrap_title": "Sleutelconfiguratie",
         "cancel_entering_passphrase_description": "Weet je zeker, dat je het invoeren van je wachtwoord wilt afbreken?",
         "cancel_entering_passphrase_title": "Wachtwoord annuleren?",
         "confirm_encryption_setup_body": "Klik op de knop hieronder om het instellen van de versleuting te bevestigen.",
         "confirm_encryption_setup_title": "Bevestig versleuting instelling",
-        "cross_signing_not_ready": "Kruiselings ondertekenen is niet ingesteld.",
-        "cross_signing_ready": "Kruiselings ondertekenen is klaar voor gebruik.",
-        "cross_signing_ready_no_backup": "Kruiselings ondertekenen is klaar, maar de sleutels zijn nog niet geback-upt.",
         "cross_signing_room_normal": "Deze kamer is eind-tot-eind-versleuteld",
         "cross_signing_room_verified": "Iedereen in deze kamer is geverifieerd",
         "cross_signing_room_warning": "Iemand gebruikt een onbekende sessie",
-        "cross_signing_unsupported": "Jouw homeserver biedt geen ondersteuning voor kruiselings ondertekenen.",
-        "cross_signing_untrusted": "Jouw account heeft een identiteit voor kruiselings ondertekenen in de sleutelopslag, maar die wordt nog niet vertrouwd door de huidige sessie.",
         "cross_signing_user_normal": "Je hebt deze persoon niet geverifieerd.",
         "cross_signing_user_verified": "Je hebt deze persoon geverifieerd. Deze persoon heeft al zijn sessies geverifieerd.",
         "cross_signing_user_warning": "Deze persoon heeft niet al zijn sessies geverifieerd.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Sleutels voor kruiselings ondertekenen wissen",
-            "title": "Sleutels voor kruiselings ondertekenen verwijderen?",
-            "warning": "Het verwijderen van sleutels voor kruiselings ondertekenen is niet terug te draaien. Iedereen waarmee u geverifieerd heeft zal beveiligingswaarschuwingen te zien krijgen. U wilt dit hoogstwaarschijnlijk niet doen, tenzij u alle apparaten heeft verloren waarmee u kruiselings kon ondertekenen."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "De echtheid van dit versleutelde bericht kan op dit apparaat niet worden gegarandeerd.",
         "event_shield_reason_mismatched_sender_key": "Versleuteld door een niet-geverifieerde sessie",
         "export_unsupported": "Jouw browser ondersteunt de benodigde cryptografie-extensies niet",
@@ -755,7 +728,6 @@
             "title": "Nieuwe herstelmethode",
             "warning": "Als je deze nieuwe herstelmethode niet hebt ingesteld, is het mogelijk dat een aanvaller toegang tot jouw account probeert te krijgen. Wijzig onmiddellijk je wachtwoord en stel bij instellingen een nieuwe herstelmethode in."
         },
-        "not_supported": "<niet ondersteund>",
         "recovery_method_removed": {
             "description_1": "Deze sessie heeft ontdekt dat je veiligheidswachtwoord en -sleutel voor versleutelde berichten zijn verwijderd.",
             "description_2": "Als je dit per ongeluk hebt gedaan, kan je beveiligde berichten op deze sessie instellen, waarmee de berichtgeschiedenis van deze sessie opnieuw zal versleuteld worden aan de hand van een nieuwe herstelmethode.",
@@ -766,8 +738,7 @@
         "set_up_toast_description": "Beveiliging tegen verlies van toegang tot versleutelde berichten en gegevens",
         "set_up_toast_title": "Beveiligde back-up instellen",
         "setup_secure_backup": {
-            "explainer": "Maak een back-up van je sleutels voordat je jezelf afmeldt om ze niet te verliezen.",
-            "title": "Instellen"
+            "explainer": "Maak een back-up van je sleutels voordat je jezelf afmeldt om ze niet te verliezen."
         },
         "udd": {
             "interactive_verification_button": "Interactief verifiëren door emoji",
@@ -778,12 +749,10 @@
             "title": "Niet vertrouwd"
         },
         "unable_to_setup_keys_error": "Kan geen sleutels instellen",
-        "unsupported": "Deze cliënt biedt geen ondersteuning voor eind-tot-eind-versleuteling.",
         "verification": {
             "accepting": "Toestaan…",
             "after_new_login": {
                 "device_verified": "Apparaat geverifieerd",
-                "reset_confirmation": "Echt je verificatiesleutels resetten?",
                 "skip_verification": "Verificatie voorlopig overslaan",
                 "unable_to_verify": "Kan dit apparaat niet verifiëren",
                 "verify_this_device": "Verifieer dit apparaat"
@@ -845,7 +814,6 @@
             "verify_emoji_prompt": "Verifieer door unieke emoji te vergelijken.",
             "verify_emoji_prompt_qr": "Als je bovenstaande code niet kan scannen, verifieer dan door unieke emoji te vergelijken.",
             "verify_later": "Ik verifieer het later",
-            "verify_reset_warning_1": "Het resetten van je verificatiesleutels kan niet ongedaan worden gemaakt. Na het resetten heb je geen toegang meer tot oude versleutelde berichten, en vrienden die je eerder hebben geverifieerd zullen veiligheidswaarschuwingen zien totdat je opnieuw bij hen geverifieert bent.",
             "verify_using_device": "Verifieer met andere apparaat",
             "verify_using_key": "Verifieer met veiligheidssleutel",
             "verify_using_key_or_phrase": "Verifieer met veiligheidssleutel of -wachtwoord",
@@ -900,11 +868,7 @@
             "title": "Kopiëren van kamerlink is mislukt"
         },
         "error_loading_user_profile": "Kon persoonsprofiel niet laden",
-        "forget_room_failed": "Vergeten van kamer is mislukt %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "De server is misschien onbereikbaar of overbelast, of het zoeken duurde te lang :(",
-            "title": "Zoeken mislukt"
-        }
+        "forget_room_failed": "Vergeten van kamer is mislukt %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1420,11 +1384,6 @@
         "ongoing": "Bezig met verwijderen…",
         "reason_label": "Reden (niet vereist)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Weet je zeker dat je de uitnodiging wilt weigeren?",
-        "failed": "Weigeren van uitnodiging is mislukt",
-        "title": "Uitnodiging weigeren"
-    },
     "report_content": {
         "description": "Dit bericht melden zal zijn unieke ‘gebeurtenis-ID’ versturen naar de beheerder van jouw homeserver. Als de berichten in deze kamer versleuteld zijn, zal de beheerder van jouw homeserver het bericht niet kunnen lezen, noch enige bestanden of afbeeldingen zien.",
         "disagree": "Niet mee eens",
@@ -1556,7 +1515,6 @@
             "you_created": "Jij hebt deze kamer gemaakt."
         },
         "invite_email_mismatch_suggestion": "Deel in de instellingen dit e-mailadres om uitnodigingen direct in %(brand)s te ontvangen.",
-        "invite_reject_ignore": "Weigeren en persoon negeren",
         "invite_sent_to_email": "De uitnodiging is verzonden naar %(email)s",
         "invite_sent_to_email_room": "Deze uitnodiging tot %(roomName)s was verstuurd naar %(email)s",
         "invite_subtitle": "<userName/> heeft je uitgenodigd",
@@ -2017,7 +1975,6 @@
             "remove_msisdn_prompt": "%(phone)s verwijderen?",
             "spell_check_locale_placeholder": "Kies een landinstelling"
         },
-        "image_thumbnails": "Miniaturen voor afbeeldingen tonen",
         "inline_url_previews_default": "Inline URL-voorvertoning standaard inschakelen",
         "inline_url_previews_room": "URL-voorvertoning voor alle deelnemers aan deze kamer standaard inschakelen",
         "inline_url_previews_room_account": "URL-voorvertoning in dit kamer inschakelen (geldt alleen voor jou)",
@@ -2118,48 +2075,16 @@
         "prompt_invite": "Uitnodigingen naar mogelijk ongeldige Matrix-ID’s bevestigen",
         "replace_plain_emoji": "Tekst automatisch vervangen door emoji",
         "security": {
-            "4s_public_key_in_account_data": "in accountinformatie",
-            "4s_public_key_status": "Sleutelopslag publieke sleutel:",
-            "backup_key_cached_status": "Back-up sleutel cached:",
-            "backup_key_stored_status": "Back-up sleutel bewaard:",
-            "backup_key_unexpected_type": "Onverwacht type",
-            "backup_key_well_formed": "goed gevormd",
-            "backup_keys_description": "Maak een back-up van je versleutelingssleutels met je accountgegevens voor het geval je de toegang tot je sessies verliest. Je sleutels worden beveiligd met een unieke veiligheidssleutel.",
             "bulk_options_accept_all_invites": "Alle %(invitedRooms)s de uitnodigingen aannemen",
             "bulk_options_reject_all_invites": "Alle %(invitedRooms)s de uitnodigingen weigeren",
             "bulk_options_section": "Bulkopties",
-            "cross_signing_cached": "lokaal opgeslagen",
-            "cross_signing_homeserver_support": "Homeserver functie ondersteuning:",
-            "cross_signing_homeserver_support_exists": "aanwezig",
-            "cross_signing_in_4s": "in de sleutelopslag",
-            "cross_signing_in_memory": "in het geheugen",
-            "cross_signing_master_private_Key": "Hoofdprivésleutel:",
-            "cross_signing_not_cached": "lokaal niet gevonden",
-            "cross_signing_not_found": "niet gevonden",
-            "cross_signing_not_in_4s": "Niet gevonden in de opslag",
-            "cross_signing_not_stored": "niet opgeslagen",
-            "cross_signing_private_keys": "Privésleutels voor kruiselings ondertekenen:",
-            "cross_signing_public_keys": "Publieke sleutels voor kruiselings ondertekenen:",
-            "cross_signing_self_signing_private_key": "Zelfondertekening-privésleutel:",
-            "cross_signing_user_signing_private_key": "Persoonsondertekening-privésleutel:",
-            "cryptography_section": "Cryptografie",
-            "delete_backup": "Back-up verwijderen",
-            "delete_backup_confirm_description": "Weet je het zeker? Je zal je versleutelde berichten verliezen als je sleutels niet correct geback-upt zijn.",
             "e2ee_default_disabled_warning": "De beheerder van je server heeft eind-tot-eind-versleuteling standaard uitgeschakeld in alle privékamers en directe gesprekken.",
             "enable_message_search": "Zoeken in versleutelde kamers inschakelen",
             "encryption_section": "Versleuteling",
-            "error_loading_key_backup_status": "Kan sleutelback-upstatus niet laden",
-            "export_megolm_keys": "E2E-kamersleutels exporteren",
             "ignore_users_empty": "Je hebt geen persoon genegeerd.",
             "ignore_users_section": "Genegeerde personen",
-            "import_megolm_keys": "E2E-kamersleutels importeren",
-            "key_backup_active_version_none": "Geen",
             "key_backup_algorithm": "Algoritme:",
-            "key_backup_complete": "Alle sleutels zijn geback-upt",
             "key_backup_connect": "Verbind deze sessie met de sleutelback-up",
-            "key_backup_connect_prompt": "Verbind deze sessie met de sleutelback-up voordat je jezelf afmeldt. Dit voorkomt dat je sleutels verliest die alleen op deze sessie voorkomen.",
-            "key_backup_inactive": "Deze sessie <b>maakt geen back-ups van je sleutels</b>, maar je beschikt over een reeds bestaande back-up waaruit je kan herstellen en waaraan je nieuwe sleutels vanaf nu kunt toevoegen.",
-            "key_backup_inactive_warning": "Jouw sleutels worden <b>niet geback-upt van deze sessie</b>.",
             "message_search_disable_warning": "Dit moet aan staan om te kunnen zoeken in versleutelde kamers.",
             "message_search_disabled": "Sla versleutelde berichten veilig lokaal op om ze doorzoekbaar te maken.",
             "message_search_enabled": {
@@ -2179,13 +2104,7 @@
             "message_search_unsupported": "In %(brand)s ontbreken enige modulen vereist voor het veilig lokaal bewaren van versleutelde berichten. Wil je deze functie uittesten, compileer dan een aangepaste versie van %(brand)s Desktop <nativeLink>die de zoekmodulen bevat</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s kan versleutelde berichten niet veilig lokaal opslaan in een webbrowser. Gebruik <desktopLink>%(brand)s Desktop</desktopLink> om versleutelde berichten in zoekresultaten te laten verschijnen.",
             "record_session_details": "Noteer de naam, versie en url van de applicatie om sessies gemakkelijker te herkennen in sessiebeheer",
-            "restore_key_backup": "Uit back-up herstellen",
-            "secret_storage_not_ready": "Niet gereed",
-            "secret_storage_ready": "Gereed",
-            "secret_storage_status": "Sleutelopslag:",
             "send_analytics": "Gebruiksgegevens delen",
-            "session_id": "Sessie-ID:",
-            "session_key": "Sessiesleutel:",
             "strict_encryption": "Vanaf deze sessie nooit versleutelde berichten naar ongeverifieerde sessies versturen"
         },
         "send_read_receipts": "Stuur leesbevestigingen",
@@ -2368,8 +2287,6 @@
         "topic": "Verkrijgt het onderwerp van de kamer of stelt het in",
         "topic_none": "Deze kamer heeft geen onderwerp.",
         "topic_room_error": "Kameronderwerp laden mislukt: Kan kamer niet vinden (%(roomId)s",
-        "tovirtual": "Schakelt over naar de virtuele kamer van deze kamer, als die er is",
-        "tovirtual_not_found": "Geen virtuele ruimte voor deze ruimte",
         "unban": "Ontbant de persoon met de gegeven ID",
         "unflip": "Plakt ┬──┬ ノ( ゜-゜ノ) vóór een bericht zonder opmaak",
         "unholdcall": "De huidige oproep in huidige kamer in de wacht zetten",
@@ -2487,7 +2404,6 @@
         "heading_without_query": "Zoeken naar",
         "join_button_text": "%(roomAddress)s toetreden",
         "keyboard_scroll_hint": "Gebruik <arrows/> om te scrollen",
-        "message_search_section_title": "Andere zoekopdrachten",
         "other_rooms_in_space": "Andere kamers in %(spaceName)s",
         "public_rooms_label": "Publieke kamers",
         "recent_searches_section_title": "Recente zoekopdrachten",
@@ -2496,7 +2412,6 @@
         "result_may_be_hidden_privacy_warning": "Sommige resultaten kunnen om privacyredenen verborgen zijn",
         "result_may_be_hidden_warning": "Sommige resultaten zijn mogelijk verborgen",
         "search_dialog": "Dialoogvenster Zoeken",
-        "search_messages_hint": "Om berichten te zoeken, zoek naar dit icoon bovenaan een kamer <icon/>",
         "spaces_title": "Spaces waar u in zit",
         "start_group_chat_button": "Start een groepsgesprek"
     },
@@ -2756,7 +2671,9 @@
             "sent": "%(senderName)s heeft %(targetDisplayName)s in deze kamer uitgenodigd."
         },
         "m.room.tombstone": "%(senderDisplayName)s heeft deze kamer geüpgraded.",
-        "m.room.topic": "%(senderDisplayName)s heeft het onderwerp gewijzigd naar ‘%(topic)s’.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s heeft het onderwerp gewijzigd naar ‘%(topic)s’."
+        },
         "m.sticker": "%(senderDisplayName)s Verstuurde een sticker.",
         "m.video": {
             "error_decrypting": "Fout bij het ontsleutelen van de video"
@@ -3014,14 +2931,6 @@
         "ban_room_confirm_title": "Verban van %(roomName)s",
         "ban_space_everything": "Verban ze van alles waar ik dit kan",
         "ban_space_specific": "Ban ze van specifieke plekken waar ik dit kan",
-        "count_of_sessions": {
-            "other": "%(count)s sessies",
-            "one": "%(count)s sessie"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s geverifieerde sessies",
-            "one": "1 geverifieerde sessie"
-        },
         "deactivate_confirm_action": "Persoon deactiveren",
         "deactivate_confirm_description": "Deze persoon deactiveren zal deze persoon uitloggen en verhinderen dat de persoon weer inlogt. Bovendien zal de persoon alle kamers waaraan de persoon deelneemt verlaten. Deze actie is niet terug te draaien. Weet je zeker dat je deze persoon wilt deactiveren?",
         "deactivate_confirm_title": "Persoon deactiveren?",
@@ -3032,15 +2941,12 @@
         "disinvite_button_room": "Uitnodiging van kamer afwijzen",
         "disinvite_button_room_name": "Uitnodiging intrekken voor %(roomName)s",
         "disinvite_button_space": "Uitnodiging van space afwijzen",
-        "edit_own_devices": "Apparaten bewerken",
         "error_ban_user": "Verbannen van persoon is mislukt",
         "error_deactivate": "Deactiveren van persoon is mislukt",
         "error_kicking_user": "Kan persoon niet verwijderen",
         "error_mute_user": "Dempen van persoon is mislukt",
         "error_revoke_3pid_invite_description": "Kon de uitnodiging niet intrekken. De server ondervindt mogelijk een tijdelijk probleem, of je hebt niet het recht de uitnodiging in te trekken.",
         "error_revoke_3pid_invite_title": "Intrekken van uitnodiging is mislukt",
-        "hide_sessions": "Sessies verbergen",
-        "hide_verified_sessions": "Geverifieerde sessies verbergen",
         "invited_by": "Uitgenodigd door %(sender)s",
         "jump_to_rr_button": "Naar het laatst gelezen bericht gaan",
         "kick_button_room": "Verwijderen uit kamer",
@@ -3126,7 +3032,6 @@
         "hide_sidebar_button": "Zijbalk verbergen",
         "input_devices": "Invoer apparaten",
         "join_button_tooltip_call_full": "Sorry — dit gesprek is momenteel vol",
-        "join_button_tooltip_connecting": "Verbinden",
         "maximise": "Scherm vullen",
         "misconfigured_server": "Oproep mislukt door verkeerd geconfigureerde server",
         "misconfigured_server_description": "Vraag je homeserver-beheerder (<code>%(homeserverDomain)s</code>) een TURN-server te configureren voor de betrouwbaarheid van de oproepen.",
diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json
index 3103c622486b027d1fc69597aec43653838696f2..42eb0de4af3c34747cabd6843848e0b931ba9601 100644
--- a/src/i18n/strings/pl.json
+++ b/src/i18n/strings/pl.json
@@ -12,6 +12,18 @@
             "one": "1 nieprzeczytana wzmianka."
         },
         "recent_rooms": "Ostatnie pokoje",
+        "room_messsage_not_sent": "Otwórz pokój %(roomName)s z niewysłaną wiadomością.",
+        "room_n_unread_invite": "Otwórz zaproszenie pokoju %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Otwórz pokój %(roomName)s z 1 nieprzeczytaną wiadomością.",
+            "few": "Otwórz pokój %(roomName)s z %(count)s nieprzeczytanymi wiadomościami.",
+            "many": "Otwórz pokój %(roomName)s z %(count)s nieprzeczytanymi wiadomościami."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Otwórz pokój %(roomName)s z 1 nieprzeczytaną wzmianką.",
+            "few": "Otwórz pokój %(roomName)s z %(count)s nieprzeczytanymi wzmiankami.",
+            "many": "Otwórz pokój %(roomName)s z %(count)s nieprzeczytanymi wzmiankami."
+        },
         "room_name": "Pokój %(name)s",
         "room_status_bar": "Pasek stanu pokoju",
         "seek_bar_label": "Pasek wyszukiwania audio",
@@ -45,6 +57,8 @@
         "create_a_room": "Utwórz pokój",
         "create_account": "Utwórz konto",
         "decline": "Odrzuć",
+        "decline_and_block": "Odrzuć i zablokuj",
+        "decline_invite": "Odrzuć zaproszenie",
         "delete": "Usuń",
         "deny": "Odmów",
         "disable": "Wyłącz",
@@ -64,6 +78,7 @@
         "go": "Przejdź",
         "go_back": "Wróć",
         "got_it": "Rozumiem",
+        "hide": "Ukryj",
         "hide_advanced": "Ukryj zaawansowane",
         "hold": "Wstrzymaj",
         "ignore": "Ignoruj",
@@ -80,12 +95,14 @@
         "maximise": "Maksymalizuj",
         "mention": "Wzmianka",
         "minimise": "Minimalizuj",
+        "new_message": "Nowa wiadomość",
         "new_room": "Nowy pokój",
         "new_video_room": "Nowy pokój wideo",
         "next": "Dalej",
         "no": "Nie",
         "ok": "OK",
         "open": "Otwórz",
+        "open_menu": "Otwórz menu",
         "pause": "Wstrzymaj",
         "pin": "Przypnij",
         "play": "Odtwórz",
@@ -94,13 +111,13 @@
         "react": "Dodaj reakcję",
         "refresh": "Odśwież",
         "register": "Rejestracja",
-        "reject": "Odrzuć",
         "reload": "Przeładuj",
         "remove": "Usuń",
         "rename": "Zmień nazwę",
         "reply": "Odpowiedz",
         "reply_in_thread": "Odpowiedz w wątku",
         "report_content": "Zgłoś treść",
+        "report_room": "Zgłoś pokój",
         "resend": "Wyślij jeszcze raz",
         "reset": "Resetuj",
         "resume": "Wznów",
@@ -119,7 +136,7 @@
         "sign_out": "Wyloguj",
         "skip": "Pomiń",
         "start": "Rozpocznij",
-        "start_chat": "Rozpocznij rozmowę",
+        "start_chat": "Rozpocznij czat",
         "start_new_chat": "Nowy czat",
         "stop": "Zatrzymaj",
         "submit": "Wyślij",
@@ -222,7 +239,7 @@
             "description": "Czy na pewno chcesz się wylogować?",
             "megolm_export": "Ręcznie eksportuj klucze",
             "setup_key_backup_title": "Utracisz dostęp do swoich wiadomości szyfrowanych",
-            "setup_secure_backup_description_1": "Zaszyfrowane wiadomości są zabezpieczone przy użyciu szyfrowania end-to-end. Tylko Ty oraz ich adresaci posiadają klucze do ich rozszyfrowania.",
+            "setup_secure_backup_description_1": "Wiadomości szyfrowane są zabezpieczone szyfrowaniem end-to-end. Tylko Ty i Twój odbiorca mają klucze, aby je wyświetlić.",
             "setup_secure_backup_description_2": "Po wylogowaniu, te klucze zostaną usunięte z urządzenia, co oznacza, że nie będziesz w stanie czytać wiadomości szyfrowanych, chyba że posiadasz je na swoich innych urządzeniach lub zapisałeś je na serwerze.",
             "skip_key_backup": "Nie chcę moich zaszyfrowanych wiadomości",
             "use_key_backup": "Rozpocznij z użyciem klucza kopii zapasowej"
@@ -278,7 +295,7 @@
             "scan_qr_code": "Zaloguj się kodem QR",
             "security_code": "Kod bezpieczeństwa",
             "security_code_prompt": "Jeśli zostaniesz poproszony, wprowadź poniższy kod na drugim urządzeniu.",
-            "select_qr_code": "Wybierz \"%(scanQRCode)s\"",
+            "select_qr_code": "Wybierz '%(scanQRCode)s'",
             "unsupported_explainer": "Twój dostawca konta nie obsługuje logowania nowego urządzenia za pomocą kodu QR.",
             "unsupported_heading": "Kod QR nie jest wspierany",
             "waiting_for_device": "Oczekiwanie na logowanie urządzenia"
@@ -371,6 +388,7 @@
             "fallback_button": "Rozpocznij uwierzytelnienie",
             "mas_cross_signing_reset_cta": "Przejdź do swojego konta",
             "mas_cross_signing_reset_description": "Zresetuj swoją tożsamość poprzez dostawcę swojego konta, wróć i kliknij „Ponów”.",
+            "mas_cross_signing_reset_title": "Przejdź do swojego konta, aby zresetować swoją tożsamość",
             "msisdn": "Wysłano wiadomość tekstową do %(msisdn)s",
             "msisdn_token_incorrect": "Niepoprawny token",
             "msisdn_token_prompt": "Wpisz kod, który jest tam zawarty:",
@@ -405,7 +423,15 @@
         "download_logs": "Pobierz dzienniki",
         "downloading_logs": "Pobieranie logów",
         "error_empty": "Powiedz nam, co poszło nie tak, lub nawet lepiej - utwórz zgłoszenie na platformie GitHub, które opisuje problem.",
-        "failed_send_logs": "Nie udało się wysłać dzienników: ",
+        "failed_download_logs": "Nie udało się pobrać dzienników debugowania: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Twój raport o błędzie został odrzucony. Serwer gniewnego potrząśnięcia nie obsługuje tej aplikacji.",
+            "rejected_generic": "Twój raport o błędzie został odrzucony. Serwer gniewnego potrząśnięcia odrzucił zawartość zgłoszenia ze względu na jej politykę.",
+            "rejected_recovery_key": "Twój raport o błędzie został odrzucony ze względów bezpieczeństwa, ponieważ zawierał on klucz przywracania.",
+            "rejected_version": "Twój raport o błędzie został odrzucony, ponieważ wersja aplikacji jest zbyt stara.",
+            "server_unknown_error": "Serwer gniewnego potrząśnięcia napotkał nieznany błąd i nie mógł obsłużyć raportu.",
+            "unknown_error": "Nie udało się wysłać logów."
+        },
         "github_issue": "Zgłoszenie GitHub",
         "introduction": "Jeśli zgłosiłeś błąd za pomocą serwisu GitHub, dzienniki debugowania mogą pomóc nam w namierzeniu problemu. ",
         "log_request": "Aby uniknąć tego problemu w przyszłości, <a>wyślij nam dzienniki</a>.",
@@ -445,8 +471,8 @@
         "access_token": "Token dostępu",
         "accessibility": "Ułatwienia dostępu",
         "advanced": "Zaawansowane",
-        "all_rooms": "Wszystkie pokoje",
-        "analytics": "Analityka",
+        "all_chats": "Wszystkie czaty",
+        "analytics": "Dane analityczne",
         "and_n_others": {
             "one": "i jeden inny...",
             "few": "i %(count)s innych...",
@@ -465,7 +491,6 @@
         "capabilities": "Możliwości",
         "copied": "Skopiowano!",
         "credits": "Podziękowania",
-        "cross_signing": "Weryfikacja krzyżowa",
         "dark": "Ciemny",
         "description": "Opis",
         "deselect_all": "Odznacz wszystkie",
@@ -478,7 +503,7 @@
         "error": "Błąd",
         "faq": "Najczęściej zadawane pytania",
         "favourites": "Ulubione",
-        "feedback": "Opinia użytkownika",
+        "feedback": "Feedback",
         "filter_results": "Filtruj wyniki",
         "forward_message": "Przekaż wiadomość",
         "general": "Ogólne",
@@ -496,7 +521,6 @@
         "legal": "Zasoby prawne",
         "light": "Jasny",
         "loading": "Wczytywanie…",
-        "lobby": "Poczekalnia",
         "location": "Lokalizacja",
         "low_priority": "Niski priorytet",
         "matrix": "Matrix",
@@ -505,6 +529,7 @@
         "message_timestamp_invalid": "Nieprawidłowy znacznik czasu",
         "microphone": "Mikrofon",
         "model": "Model",
+        "moderation_and_safety": "Moderacja i bezpieczeństwo",
         "modern": "Współczesny",
         "mute": "Wycisz",
         "n_members": {
@@ -535,12 +560,13 @@
         "private_room": "Pokój prywatny",
         "private_space": "Przestrzeń prywatna",
         "profile": "Profil",
-        "public": "Publiczny",
+        "public": "Publiczna",
         "public_room": "Pokój publiczny",
         "public_space": "Przestrzeń publiczna",
         "qr_code": "Kod QR",
         "random": "Losowe",
         "reactions": "Reakcje",
+        "recommended": "Polecane",
         "report_a_bug": "Zgłoś błąd",
         "room": "Pokój",
         "room_name": "Nazwa pokoju",
@@ -549,7 +575,6 @@
         "saved": "Zapisano",
         "saving": "Zapisywanie…",
         "secure_backup": "Bezpieczna kopia zapasowa",
-        "security": "Bezpieczeństwo",
         "select_all": "Zaznacz wszystkie",
         "server": "Serwer",
         "settings": "Ustawienia",
@@ -568,7 +593,6 @@
         "thread": "Wątek",
         "threads": "Wątki",
         "timeline": "Oś czasu",
-        "trusted": "Zaufane",
         "unavailable": "niedostępne",
         "unencrypted": "Nieszyfrowany",
         "unmute": "Wyłącz wyciszenie",
@@ -728,6 +752,13 @@
         "twemoji": "Czcionka <twemoji>Twemoji</twemoji> jest w użyciu na warunkach licencji <terms>CC-BY 4.0</terms>. <author>© Twitter, Inc i pozostali kontrybutorzy</author>.",
         "twemoji_colr": "Czcionka <colr>twemoji-colr</colr> jest w użyciu na warunkach licencji <terms>Apache 2.0</terms>. <author>© Mozilla Foundation</author>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Czy na pewno chcesz odrzucić zaproszenie dołączenia do \"%(roomName)s\"?",
+        "ignore_user_help": "Nie zobaczysz żadnych wiadomości ani zaproszeń od tego użytkownika.",
+        "reason_description": "Opisz powód zgłoszenia pokoju.",
+        "report_room_description": "Zgłoś pokój dostawcy swojego konta.",
+        "title": "Odrzuć zaproszenie"
+    },
     "desktop_default_device_name": "Komputer %(brand)s: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktywne widżety",
@@ -735,6 +766,44 @@
         "category_room": "Pokój",
         "caution_colon": "Ostrzeżenie:",
         "client_versions": "Wersje klientów",
+        "crypto": {
+            "4s_public_key_in_account_data": "w danych konta",
+            "4s_public_key_not_in_account_data": "nie znaleziono",
+            "4s_public_key_status": "Klucz publiczny sekretnego magazynu:",
+            "backup_key_cached": "w lokalnej pamięci podręcznej",
+            "backup_key_cached_status": "Klucz zapasowy zapisany w pamięci podręcznej:",
+            "backup_key_not_stored": "nie przechowywany",
+            "backup_key_stored": "w sekretnym magazynie",
+            "backup_key_stored_status": "Magazyn klucza kopii zapasowej:",
+            "backup_key_unexpected_type": "nieoczekiwany typ",
+            "backup_key_well_formed": "dobrze ukształtowany",
+            "cross_signing": "Weryfikacja krzyżowa",
+            "cross_signing_cached": "w lokalnej pamięci podręcznej",
+            "cross_signing_not_ready": "Weryfikacja krzyżowa nie jest ustawiona.",
+            "cross_signing_private_keys_in_storage": "w sekretnym magazynie",
+            "cross_signing_private_keys_in_storage_status": "Klucze prywatne weryfikacji krzyżowej:",
+            "cross_signing_private_keys_not_in_storage": "nie odnaleziono w pamięci",
+            "cross_signing_public_keys_on_device": "w pamięci",
+            "cross_signing_public_keys_on_device_status": "Weryfikacja krzyżowa kluczy publicznych:",
+            "cross_signing_ready": "Weryfikacja krzyżowa jest gotowa do użycia.",
+            "cross_signing_status": "Status weryfikacji krzyżowej:",
+            "cross_signing_untrusted": "Twoje konto ma tożsamość weryfikacji krzyżowej w sekretnym magazynie, ale nie jest jeszcze zaufane przez tę sesję.",
+            "crypto_not_available": "Moduł kryptograficzny nie jest dostępny",
+            "key_backup_active_version": "Aktywna wersja kopii zapasowej:",
+            "key_backup_active_version_none": "Brak",
+            "key_backup_inactive_warning": "Twoje klucze nie są przywracane na tej sesji.",
+            "key_backup_latest_version": "Najnowsza wersja kopii zapasowej na serwerze:",
+            "key_storage": "Magazyn kluczy",
+            "master_private_key_cached_status": "Główny klucz prywatny:",
+            "not_found": "nie znaleziono",
+            "not_found_locally": "nie znaleziono lokalnie",
+            "secret_storage_not_ready": "niegotowe",
+            "secret_storage_ready": "gotowe",
+            "secret_storage_status": "Sekretny magazyn:",
+            "self_signing_private_key_cached_status": "Samopodpisujący klucz prywatny:",
+            "title": "Szyfrowanie end-to-end",
+            "user_signing_private_key_cached_status": "Podpisany przez użytkownika klucz prywatny:"
+        },
         "developer_mode": "Tryb programisty",
         "developer_tools": "Narzędzia programistyczne",
         "edit_setting": "Edytuj ustawienie",
@@ -774,9 +843,9 @@
         "room_notifications_type": "Typ: ",
         "room_status": "Status pokoju",
         "room_unread_status_count": {
-            "one": "Status nieprzeczytanej w pokoju: <strong>%(status)s</strong>, liczba: <strong>%(count)s</strong>",
-            "few": "Status nieprzeczytanych w pokoju: <strong>%(status)s</strong>, liczba: <strong>%(count)s</strong>",
-            "many": "Status nieprzeczytanych w pokoju: <strong>%(status)s</strong>, liczba: <strong>%(count)s</strong>"
+            "one": "Status nieprzeczytanej pokoju: <strong>%(status)s</strong>, ilość: <strong>%(count)s</strong>",
+            "few": "Status nieprzeczytanych pokoju: <strong>%(status)s</strong>, ilość: <strong>%(count)s</strong>",
+            "many": "Status nieprzeczytanych pokoju: <strong>%(status)s</strong>, ilość: <strong>%(count)s</strong>"
         },
         "save_setting_values": "Zapisz ustawione wartości",
         "see_history": "Pokaż historię",
@@ -791,6 +860,9 @@
         "setting_colon": "Ustawienie:",
         "setting_definition": "Definicja ustawienia:",
         "setting_id": "ID ustawienia",
+        "settings": {
+            "elementCallUrl": "Adres URL Element Call"
+        },
         "settings_explorer": "Eksplorator ustawień",
         "show_hidden_events": "Pokaż ukryte wydarzenia na linii czasowej",
         "spaces": {
@@ -844,52 +916,35 @@
     "empty_room_was_name": "Pusty pokój (poprzednio %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Wprowadź swoją frazę zabezpieczającą lub <button>użyj klucza zabezpieczającego</button>, aby kontynuować.",
             "key_validation_text": {
-                "invalid_security_key": "Nieprawidłowy klucz bezpieczeństwa",
-                "recovery_key_is_correct": "Wygląda dobrze!",
-                "wrong_file_type": "Błędny typ pliku",
-                "wrong_security_key": "Niewłaściwy klucz bezpieczeństwa"
-            },
-            "reset_title": "Resetuj wszystko",
-            "reset_warning_1": "Zrób to tylko wtedy, gdy nie masz innego urządzenia, za pomocą którego mógłbyś zakończyć weryfikację.",
-            "reset_warning_2": "Jeśli zresetujesz wszystko, stracisz wszystkie sesje zaufane, użytkowników zaufanych i możliwe, że nie będziesz w stanie przeglądać historii czatu.",
+                "wrong_security_key": "Błędny klucz przywracania"
+            },
             "restoring": "Przywracanie kluczy z kopii zapasowej",
-            "security_key_title": "Klucz bezpieczeństwa",
-            "security_phrase_incorrect_error": "Nie można uzyskać dostępu do sekretnego magazynu. Upewnij się, że wprowadzono poprawne Hasło bezpieczeństwa.",
-            "security_phrase_title": "Hasło bezpieczeństwa",
-            "separator": "%(securityKey)s lub %(recoveryFile)s",
-            "use_security_key_prompt": "Użyj swojego klucza bezpieczeństwa, aby kontynuować."
+            "security_key_title": "Klucz przywracania"
         },
         "bootstrap_title": "Konfigurowanie kluczy",
         "cancel_entering_passphrase_description": "Czy na pewno chcesz anulować wpisywanie hasła?",
         "cancel_entering_passphrase_title": "Anulować wpisywanie hasła?",
         "confirm_encryption_setup_body": "Kliknij przycisk poniżej, aby potwierdzić ustawienie szyfrowania.",
         "confirm_encryption_setup_title": "Potwierdź ustawienie szyfrowania",
-        "cross_signing_not_ready": "Weryfikacja krzyżowa nie jest ustawiona.",
-        "cross_signing_ready": "Weryfikacja krzyżowa jest gotowa do użycia.",
-        "cross_signing_ready_no_backup": "Weryfikacja krzyżowa jest gotowa, ale klucze nie mają kopii zapasowej.",
         "cross_signing_room_normal": "Ten pokój jest szyfrowany end-to-end",
         "cross_signing_room_verified": "Wszyscy w tym pokoju są zweryfikowani",
         "cross_signing_room_warning": "Ktoś używa nieznanej sesji",
-        "cross_signing_unsupported": "Twój serwer domowy nie obsługuje weryfikacji krzyżowej.",
-        "cross_signing_untrusted": "Twoje konto ma tożsamość weryfikacji krzyżowej w sekretnej pamięci, ale nie jest jeszcze zaufane przez tę sesję.",
         "cross_signing_user_normal": "Nie zweryfikowałeś tego użytkownika.",
         "cross_signing_user_verified": "Zweryfikowałeś tego użytkownika. Użytkownik zweryfikował wszystkie swoje sesje.",
         "cross_signing_user_warning": "Ten użytkownik nie zweryfikował wszystkich swoich sesji.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Wyczyść klucze weryfikacji krzyżowej",
-            "title": "Zniszczyć klucze weryfikacji krzyżowej?",
-            "warning": "Usunięcie kluczy weryfikacji krzyżowej jest trwałe. Każdy, z kim dokonano weryfikacji, zobaczy alerty bezpieczeństwa. Prawie na pewno nie chcesz tego robić, chyba że straciłeś każde urządzenie, z którego możesz weryfikować."
-        },
+        "enter_recovery_key": "Wprowadź klucz przywracania",
         "event_shield_reason_authenticity_not_guaranteed": "Autentyczność tej wiadomości szyfrowanej nie jest gwarantowana na tym urządzeniu.",
         "event_shield_reason_mismatched_sender_key": "Zaszyfrowano przez sesję niezweryfikowaną",
         "event_shield_reason_unknown_device": "Zaszyfrowano przez nieznane lub usunięte urządzenie.",
         "event_shield_reason_unsigned_device": "Zaszyfrowano przez urządzenie niezweryfikowane przez właściciela.",
         "event_shield_reason_unverified_identity": "Zaszyfrowano przez niezweryfikowanego użytkownika",
         "export_unsupported": "Twoja przeglądarka nie wspiera wymaganych rozszerzeń kryptograficznych",
+        "forgot_recovery_key": "Nie pamiętasz klucza przywracania?",
         "import_invalid_keyfile": "Niepoprawny plik klucza %(brand)s",
         "import_invalid_passphrase": "Próba autentykacji nieudana: nieprawidłowe hasło?",
+        "key_storage_out_of_sync": "Twój magazyn kluczy nie jest zsynchronizowany.",
+        "key_storage_out_of_sync_description": "Potwierdź klucz przywracania, aby zachować dostęp do magazynu kluczy i historii wiadomości.",
         "messages_not_secure": {
             "cause_1": "Twój serwer domowy",
             "cause_2": "Użytkownik, którego weryfikujesz jest połączony z serwerem domowym",
@@ -904,9 +959,8 @@
             "title": "Nowy sposób odzyskiwania",
             "warning": "Jeżeli nie ustawiłeś nowej metody odzyskiwania, atakujący może uzyskać dostęp do Twojego konta. Zmień hasło konta i natychmiast ustaw nową metodę odzyskiwania w Ustawieniach."
         },
-        "not_supported": "<niewspierany>",
-        "pinned_identity_changed": "Tożsamość użytkownika %(displayName)s (<b>%(userId)s</b>) uległa zmianie. <a>Dowiedz się więcej</a>",
-        "pinned_identity_changed_no_displayname": "Tożsamość użytkownika <b>%(userId)s</b> uległa zmianie <a>Dowiedz się więcej</a>",
+        "pinned_identity_changed": "Tożsamość %(displayName)s (<b>%(userId)s</b>) została zresetowana. <a>Dowiedz się więcej</a>",
+        "pinned_identity_changed_no_displayname": "Tożsamość <b>%(userId)s</b> została zresetowana. <a>Dowiedz się więcej</a>",
         "recovery_method_removed": {
             "description_1": "Ta sesja wykryła, że Twoja fraza bezpieczeństwa i klucz dla bezpiecznych wiadomości zostały usunięte.",
             "description_2": "Jeśli zrobiłeś to przez pomyłkę, możesz ustawić bezpieczne wiadomości w tej sesji, co zaszyfruje ponownie historię wiadomości za pomocą nowej metody odzyskiwania.",
@@ -920,8 +974,7 @@
         "set_up_toast_description": "Zabezpiecz się przed utratą dostępu do szyfrowanych wiadomości i danych",
         "set_up_toast_title": "Skonfiguruj bezpieczną kopię zapasową",
         "setup_secure_backup": {
-            "explainer": "Utwórz kopię zapasową kluczy przed wylogowaniem, aby ich nie utracić.",
-            "title": "Konfiguruj"
+            "explainer": "Utwórz kopię zapasową kluczy przed wylogowaniem, aby ich nie utracić."
         },
         "udd": {
             "interactive_verification_button": "Zweryfikuj interaktywnie za pomocą emoji",
@@ -932,12 +985,10 @@
             "title": "Niezaufany"
         },
         "unable_to_setup_keys_error": "Nie można ustawić kluczy",
-        "unsupported": "Ten klient nie obsługuje szyfrowania end-to-end.",
         "verification": {
             "accepting": "Akceptowanie…",
             "after_new_login": {
                 "device_verified": "Urządzenie zweryfikowane",
-                "reset_confirmation": "Czy na pewno zresetować klucze weryfikacyjne?",
                 "skip_verification": "Pomiń weryfikację na razie",
                 "unable_to_verify": "Nie można zweryfikować tego urządzenia",
                 "verify_this_device": "Zweryfikuj to urządzenie"
@@ -959,7 +1010,7 @@
             "incoming_sas_dialog_waiting": "Oczekiwanie na potwierdzenie partnera…",
             "incoming_sas_user_dialog_text_1": "Zweryfikuj tego użytkownika, aby oznaczyć go jako zaufanego. Użytkownicy zaufani dodają większej pewności, gdy korzystasz z wiadomości szyfrowanych end-to-end.",
             "incoming_sas_user_dialog_text_2": "Weryfikacja tego użytkownika oznaczy Twoją i jego sesję jako zaufaną.",
-            "no_key_or_device": "Wygląda na to, że nie masz klucza bezpieczeństwa ani żadnych innych urządzeń, które mogą weryfikować Twoją tożsamość.  To urządzenie nie będzie mogło uzyskać dostępu do wcześniejszych zaszyfrowanych wiadomości. Aby zweryfikować swoją tożsamość na tym urządzeniu, należy zresetować klucze weryfikacyjne.",
+            "no_key_or_device": "Wygląda na to, że nie masz klucza przywracania ani żadnych innych urządzeń, które mogłyby zweryfikować Twoją tożsamość.  To urządzenie nie będzie mogło odczytać wcześniej zaszyfrowanych wiadomości. Aby zweryfikować swoją tożsamość na tym urządzeniu, musisz zresetować klucze weryfikacyjne.",
             "no_support_qr_emoji": "Urządzenie, które próbujesz zweryfikować nie wspiera skanowania kodu QR lub weryfikacji emoji, czyli tego co obsługuje %(brand)s. Spróbuj użyć innego klienta.",
             "other_party_cancelled": "Druga strona anulowała weryfikację.",
             "prompt_encrypted": "Zweryfikuj wszystkich użytkowników w pokoju, aby upewnić się, że jest bezpieczny.",
@@ -1008,19 +1059,20 @@
             "verify_emoji_prompt": "Zweryfikuj, porównując unikalne emotikony.",
             "verify_emoji_prompt_qr": "Jeśli nie jesteś w stanie skanować kodu powyżej, zweryfikuj porównując emoji.",
             "verify_later": "Zweryfikuję później",
-            "verify_reset_warning_1": "Zresetowanie kluczy weryfikacyjnych nie może być cofnięte. Po zresetowaniu, nie będziesz mieć dostępu do starych wiadomości szyfrowanych, a wszyscy znajomi, którzy wcześniej Cię zweryfikowali, będą widzieć ostrzeżenia do czasu ponownej weryfikacji.",
-            "verify_reset_warning_2": "Kontynuuj tylko wtedy, gdy jesteś pewien, że straciłeś wszystkie inne urządzenia i swój klucz bezpieczeństwa.",
             "verify_using_device": "Zweryfikuj innym urządzeniem",
-            "verify_using_key": "Weryfikacja za pomocą klucza bezpieczeństwa",
-            "verify_using_key_or_phrase": "Weryfikacja za pomocą klucza lub frazy bezpieczeństwa",
+            "verify_using_key": "Zweryfikuj kluczem przywracania",
+            "verify_using_key_or_phrase": "Zweryfikuj kluczem przywracania lub frazą",
             "waiting_for_user_accept": "Oczekiwanie na akceptację przez %(displayName)s…",
             "waiting_other_device": "Oczekiwanie na zweryfikowanie przez ciebie twojego innego urządzenia…",
             "waiting_other_device_details": "Oczekiwanie na zweryfikowanie przez ciebie twojego innego urządzenia, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Oczekiwanie na weryfikację przez %(displayName)s…"
         },
         "verification_requested_toast_title": "Zażądano weryfikacji",
+        "verified_identity_changed": "Tożsamość %(displayName)s (<b>%(userId)s</b>) została zresetowana. <a>Dowiedz się więcej</a>",
+        "verified_identity_changed_no_displayname": "Tożsamość <b>%(userId)s</b> została zresetowana. <a>Dowiedz się więcej</a>",
         "verify_toast_description": "Inni użytkownicy mogą temu nie ufać",
-        "verify_toast_title": "Zweryfikuj tę sesję"
+        "verify_toast_title": "Zweryfikuj tę sesję",
+        "withdraw_verification_action": "Wycofaj weryfikację"
     },
     "error": {
         "admin_contact": "Proszę, <a>skontaktuj się z administratorem</a> aby korzystać dalej z funkcji.",
@@ -1075,11 +1127,7 @@
             "title": "Nie można skopiować linku do pokoju"
         },
         "error_loading_user_profile": "Nie udało się załadować profilu",
-        "forget_room_failed": "Nie mogłem zapomnieć o pokoju %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Serwer może być niedostępny, przeciążony, lub upłynął czas wyszukiwania :(",
-            "title": "Wyszukiwanie nie powiodło się"
-        }
+        "forget_room_failed": "Nie mogłem zapomnieć o pokoju %(errCode)s"
     },
     "error_user_not_logged_in": "Użytkownik nie jest zalogowany",
     "event_preview": {
@@ -1180,10 +1228,10 @@
     },
     "failed_load_async_component": "Nie można załadować! Sprawdź połączenie sieciowe i spróbuj ponownie.",
     "feedback": {
-        "can_contact_label": "Możesz się ze mną skontaktować, jeśli masz jakiekolwiek pytania",
+        "can_contact_label": "Wyrażam zgodę na kontakt ze mną, jeśli są jakiekolwiek pytania",
         "comment_label": "Komentarz",
         "existing_issue_link": "Najpierw zobacz <existingIssuesLink>istniejące zgłoszenia na GitHubie</existingIssuesLink>. Nic nie znalazłeś? <newIssueLink>Utwórz nowe</newIssueLink>.",
-        "may_contact_label": "Możesz się ze mną skontaktować, jeśli chcesz mnie śledzić lub pomóc wypróbować nadchodzące pomysły",
+        "may_contact_label": "Wyrażam zgodę na kontakt ze mną, jeśli pojawią się nowe informacje lub trzeba wypróbować nowe pomysły",
         "platform_username": "Twoja platforma i nazwa użytkownika zostaną zapisane, aby pomóc nam ulepszyć nasze produkty.",
         "pro_type": "PRO TIP: Jeżeli zgłaszasz błąd, wyślij <debugLogsLink>dzienniki debugowania</debugLogsLink>, aby pomóc nam znaleźć problem.",
         "send_feedback_action": "Wyślij opinię użytkownika",
@@ -1208,6 +1256,7 @@
         "change": "Zmień serwer tożsamości",
         "change_prompt": "Rozłączyć się z bieżącym serwerem tożsamości <current /> i połączyć się z <new />?",
         "change_server_prompt": "Jeżeli nie chcesz używać <server /> do odnajdywania i bycia odnajdywanym przez osoby, które znasz, wpisz inny serwer tożsamości poniżej.",
+        "changed": "Twój serwer tożsamości został zmieniony",
         "checking": "Sprawdzanie serwera",
         "description_connected": "Używasz <server></server>, aby odnajdywać i móc być odnajdywanym przez istniejące kontakty, które znasz. Możesz zmienić serwer tożsamości poniżej.",
         "description_disconnected": "Nie używasz serwera tożsamości. Aby odkrywać i być odkrywanym przez istniejące kontakty które znasz, dodaj jeden poniżej.",
@@ -1252,17 +1301,19 @@
         "use_desktop_heading": "Zamiast tego użyj %(brand)s Desktop",
         "use_mobile_heading": "Zamiast tego użyj %(brand)s Mobile",
         "use_mobile_heading_after_desktop": "lub skorzystaj z naszej aplikacji mobilnej",
-        "windows": "Windows (%(bits)s-bity)"
+        "windows_64bit": "Windows (64-bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
     },
     "info_tooltip_title": "Informacje",
     "integration_manager": {
         "connecting": "Łączenie z menedżerem integracji…",
         "error_connecting": "Menedżer integracji jest offline, lub nie może połączyć się z Twoim homeserverem.",
         "error_connecting_heading": "Nie udało się połączyć z menedżerem integracji",
-        "explainer": "Zarządcy integracji otrzymują dane konfiguracji, mogą modyfikować widżety, wysyłać zaproszenia do pokojów i ustawiać poziom uprawnień w Twoim imieniu.",
+        "explainer": "Menedżerowie integracji otrzymują dane konfiguracyjne, przez co mogą modyfikować widżety, wysyłać zaproszenia i ustawiać uprawnienia w Twoim imieniu.",
         "manage_title": "Zarządzaj integracjami",
-        "use_im": "Użyj zarządcy integracji aby zarządzać botami, widżetami i pakietami naklejek.",
-        "use_im_default": "Użyj zarządcy Integracji <b>%(serverName)s</b> aby zarządzać botami, widżetami i pakietami naklejek."
+        "toggle_label": "Włącz menedżer integracji",
+        "use_im": "Użyj menedżera integracji, aby zarządzać botami, widżetami i pakietami naklejek.",
+        "use_im_default": "Użyj menedżera integracji <b>%(serverName)s</b>, aby zarządzać botami, widżetami i pakietami naklejek."
     },
     "integrations": {
         "disabled_dialog_description": "Włącz '%(manageIntegrations)s' w ustawieniach, aby to zrobić.",
@@ -1332,8 +1383,9 @@
         "many": "Zapraszanie %(user)s i %(count)s innych"
     },
     "items_and_n_others": {
-        "other": "<Items/> i %(count)s innych",
-        "one": "<Items/> i jedna inna osoba"
+        "one": "<Items/> i jeszcze jedna osoba",
+        "few": "<Items/> i %(count)s inne",
+        "many": "<Items/> i %(count)s innych"
     },
     "keyboard": {
         "activate_button": "Aktywuj wybrany przycisk",
@@ -1463,6 +1515,7 @@
         "location_share_live_description": "Implementacja tymczasowa. Lokalizacje są zapisywane w historii pokoju.",
         "mjolnir": "Nowe sposoby na ignorowanie osób",
         "msc3531_hide_messages_pending_moderation": "Daj moderatorom ukrycie wiadomości które są sprawdzane.",
+        "new_room_list": "Włącz nową listę pokojów",
         "notification_settings": "Ustawienia nowych powiadomień",
         "notification_settings_beta_caption": "Przedstawiamy prostszy sposób zmiany ustawień powiadomień. Dostosuj %(brand)s wedle swojego upodobania.",
         "notification_settings_beta_title": "Ustawienia powiadomień",
@@ -1481,7 +1534,7 @@
         "video_rooms": "Pokoje wideo",
         "video_rooms_a_new_way_to_chat": "Nowy sposób prowadzenia rozmów audio-wideo w %(brand)s.",
         "video_rooms_always_on_voip_channels": "Pokoje wideo są stale dostępnymi kanałami VoIP osadzonymi w pokoju w %(brand)s.",
-        "video_rooms_beta": "Rozmowy wideo to funkcja beta",
+        "video_rooms_beta": "Pokoje wideo są funkcją Beta",
         "video_rooms_faq1_answer": "Użyj przycisku \"+\" w sekcji pokoju lewego panelu.",
         "video_rooms_faq1_question": "Jak mogę stworzyć kanał wideo?",
         "video_rooms_faq2_answer": "Tak, oś czasu czatu jest wyświetlana wraz z wideo.",
@@ -1586,8 +1639,15 @@
         "toggle_attribution": "Przełącz atrybucje"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s Członek",
+            "few": "%(count)s Członki",
+            "many": "%(count)s Członków"
+        },
         "filter_placeholder": "Filtruj członków pokoju",
         "invite_button_no_perms_tooltip": "Nie posiadasz uprawnień, aby zapraszać użytkowników",
+        "invited_label": "Zaproszono",
+        "no_matches": "Brak wyników",
         "power_label": "%(userName)s (moc uprawnień administratorskich %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Członkowie pokoju",
@@ -1610,6 +1670,7 @@
         "class_global": "Globalne",
         "class_other": "Inne",
         "default": "Domyślne",
+        "default_settings": "Ustawienia domyślne",
         "email_pusher_app_display_name": "Powiadomienia e-mail",
         "enable_prompt_toast_description": "Włącz powiadomienia na pulpicie",
         "enable_prompt_toast_title": "Powiadomienia",
@@ -1628,7 +1689,8 @@
         "mentions_and_keywords_description": "Otrzymuj powiadomienia tylko z wzmiankami i słowami kluczowymi zgodnie z Twoimi <a>ustawieniami</a>",
         "mentions_keywords": "Wzmianki i słowa kluczowe",
         "message_didnt_send": "Nie wysłano wiadomości. Kliknij po więcej informacji.",
-        "mute_description": "Nie otrzymasz żadnych powiadomień"
+        "mute_description": "Nie otrzymasz żadnych powiadomień",
+        "mute_room": "Wycisz pokój"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s prosi o weryfikację"
@@ -1730,11 +1792,6 @@
         "ongoing": "Usuwanie…",
         "reason_label": "Powód (opcjonalne)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Czy na pewno chcesz odrzucić zaproszenie?",
-        "failed": "Nie udało się odrzucić zaproszenia",
-        "title": "Odrzuć zaproszenie"
-    },
     "report_content": {
         "description": "Zgłoszenie tej wiadomości wyśle administratorowi serwera unikatowe „ID wydarzenia”. Jeżeli wiadomości w tym pokoju są szyfrowane, administrator serwera może nie być w stanie przeczytać treści wiadomości, lub zobaczyć plików bądź zdjęć.",
         "disagree": "Nie zgadzam się",
@@ -1757,27 +1814,31 @@
         "spam_or_propaganda": "Spam lub propaganda",
         "toxic_behaviour": "Toksyczne zachowanie"
     },
+    "report_room": {
+        "description": "Zgłoś ten pokój swojemu dostawcy konta. Jeśli wiadomości są zaszyfrowane, administrator nie będzie mógł ich odczytać.",
+        "reason_label": "Opisz powód"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Nie udało się odszyfrować %(failedCount)s sesji!",
         "count_of_successfully_restored_keys": "Przywrócono pomyślnie %(sessionCount)s kluczy",
-        "enter_key_description": "Uzyskaj dostęp do historii bezpiecznych wiadomości i skonfiguruj bezpieczne wiadomości, wprowadzając swój klucz bezpieczeństwa.",
-        "enter_key_title": "Wprowadź klucz bezpieczeństwa",
+        "enter_key_description": "Uzyskaj dostęp do zabezpieczonej historii wiadomości i ustaw bezpieczną komunikację wprowadzając swój klucz przywracania.",
+        "enter_key_title": "Wprowadź klucz przywracania",
         "enter_phrase_description": "Uzyskaj dostęp do swojej bezpiecznej historii wiadomości i skonfiguruj bezpieczne wiadomości, wprowadzając Hasło bezpieczeństwa.",
         "enter_phrase_title": "Wprowadź hasło bezpieczeństwa",
         "incorrect_security_phrase_dialog": "Kopia zapasowa nie mogła zostać rozszyfrowana za pomocą tego Hasła bezpieczeństwa: upewnij się, że wprowadzono prawidłowe Hasło bezpieczeństwa.",
         "incorrect_security_phrase_title": "Nieprawidłowe hasło bezpieczeństwa",
         "key_backup_warning": "<b>Ostrzeżenie</b>: kopia zapasowa klucza powinna być konfigurowana tylko z zaufanego komputera.",
         "key_fetch_in_progress": "Pobieranie kluczy z serwera…",
-        "key_forgotten_text": "Jeśli zapomniałeś swojego klucza bezpieczeństwa, możesz <button>skonfigurować nowe opcje odzyskiwania</button>",
-        "key_is_invalid": "Nieprawidłowy klucz bezpieczeństwa",
-        "key_is_valid": "Wygląda to na prawidłowy klucz bezpieczeństwa!",
+        "key_forgotten_text": "Jeśli zapomniałeś swojego klucza przywracania, możesz <button>skonfigurować nowe opcje odzyskiwania</button>",
+        "key_is_invalid": "Nieprawidłowy klucz przywracania",
+        "key_is_valid": "Wygląda to na prawidłowy klucz przywracania!",
         "keys_restored_title": "Klucze przywrócone",
         "load_error_content": "Nie udało się załadować stanu kopii zapasowej",
         "load_keys_progress": "%(completed)s z %(total)s kluczy przywrócono",
         "no_backup_error": "Nie znaleziono kopii zapasowej!",
-        "phrase_forgotten_text": "Jeśli zapomniałeś(aś) swojej frazy bezpieczeństwa, możesz <button1>wykorzystać swój klucz bezpieczeństwa</button1> lub <button2>skonfigurować nowe opcje odzyskiwania</button2>",
-        "recovery_key_mismatch_description": "Kopia zapasowa nie mogła zostać rozszyfrowana za pomocą tego Klucza: upewnij się, że wprowadzono prawidłowy Klucz bezpieczeństwa.",
-        "recovery_key_mismatch_title": "Klucze bezpieczeństwa nie pasują do siebie",
+        "phrase_forgotten_text": "Jeśli zapomniałeś swojej frazy bezpieczeństwa, możesz <button1>użyć swojego klucza przywracania</button1>lub<button2>ustawić nowe opcje odzyskiwania</button2>",
+        "recovery_key_mismatch_description": "Nie udało się odszyfrować kopii zapasowej za pomocą podanego klucza: upewnij się, że wprowadzono prawidłowy klucz przywracania.",
+        "recovery_key_mismatch_title": "Klucze przywracania nie pasują do siebie",
         "restore_failed_error": "Przywrócenie kopii zapasowej jest niemożliwe"
     },
     "right_panel": {
@@ -1941,14 +2002,13 @@
             "you_created": "Utworzyłeś ten pokój."
         },
         "invite_email_mismatch_suggestion": "Udostępnij ten e-mail w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w %(brand)s.",
-        "invite_reject_ignore": "Odrzuć i zignoruj użytkownika",
         "invite_sent_to_email": "To zaproszenie zostało wysłane do %(email)s",
         "invite_sent_to_email_room": "To zaproszenie do %(roomName)s zostało wysłane do %(email)s",
         "invite_subtitle": "Zaproszony przez <userName/>",
         "invite_this_room": "Zaproś do tego pokoju",
         "invite_title": "Czy chcesz dołączyć do %(roomName)s?",
         "inviter_unknown": "Nieznany",
-        "invites_you_text": "<inviter/> zaprasza cię",
+        "invites_you_text": "<inviter/> wysłał Ci zaproszenie",
         "join_button_account": "Zarejestruj się",
         "join_failed_needs_invite": "Aby wyświetlić %(roomName)s, potrzebujesz zaproszenia",
         "join_the_discussion": "Dołącz do dyskusji",
@@ -2015,7 +2075,7 @@
             "monthly_user_limit_reached": "Wiadomość nie została wysłana, ponieważ serwer domowy przekroczył miesięczny limit aktywnych użytkowników. <a>Skontaktuj się z administratorem serwisu</a>, aby kontynuować.",
             "requires_consent_agreement": "Nie możesz wysłać żadnej wiadomości, dopóki nie zaakceptujesz <consentLink>naszych warunków i kondycji</consentLink>.",
             "retry_all": "Spróbuj ponownie wszystkie",
-            "select_messages_to_retry": "Możesz zaznaczyć wszystkie lub wybrane wiadomości aby spróbować ponownie lub usunąć je",
+            "select_messages_to_retry": "Możesz zaznaczyć wszystkie lub wybrane wiadomości, aby spróbować ponownie lub je usunąć",
             "server_connectivity_lost_description": "Wysłane wiadomości będą przechowywane aż do momentu odzyskania połączenia.",
             "server_connectivity_lost_title": "Połączenie z serwerem zostało utracone.",
             "some_messages_not_sent": "Niektóre z Twoich wiadomości nie zostały wysłane"
@@ -2038,39 +2098,86 @@
             },
             "uploading_single_file": "Przesyłanie %(filename)s"
         },
+        "video_room": "Ten pokój jest pokojem wideo",
         "waiting_for_join_subtitle": "Jak tylko zaproszeni użytkownicy dołączą do %(brand)s, będziesz mógł czatować w pokoju szyfrowanym end-to-end",
         "waiting_for_join_title": "Czekanie na użytkowników %(brand)s"
     },
     "room_list": {
         "add_room_label": "Dodaj pokój",
         "add_space_label": "Dodaj przestrzeń",
+        "appearance": "Wygląd",
         "breadcrumbs_empty": "Brak ostatnio odwiedzonych pokojów",
         "breadcrumbs_label": "Ostatnio odwiedzane pokoje",
+        "empty": {
+            "no_chats": "Nie ma jeszcze czatów",
+            "no_chats_description": "Zacznij od wysłania wiadomości lub utworzenia pokoju",
+            "no_chats_description_no_room_rights": "Wyślij komuś wiadomość, aby rozpocząć.",
+            "no_favourites": "Nie masz jeszcze ulubionego czatu",
+            "no_favourites_description": "Dodaj czat do ulubionych w ustawieniach czatu",
+            "no_people": "Nie prowadzisz jeszcze z nikim czatów prywatnych",
+            "no_people_description": "Wyczyść filtry, aby zobaczyć pozostałe czaty",
+            "no_rooms": "Nie jesteś jeszcze w żadnym pokoju",
+            "no_rooms_description": "Wyczyść filtry, aby zobaczyć pozostałe czaty",
+            "no_unread": "Brawo! Nie masz żadnych nieprzeczytanych wiadomości",
+            "show_chats": "Pokaż wszystkie czaty"
+        },
         "failed_add_tag": "Nie można dodać tagu %(tagName)s do pokoju",
         "failed_remove_tag": "Nie udało się usunąć tagu %(tagName)s z pokoju",
         "failed_set_dm_tag": "Nie udało się ustawić tagu wiadomości prywatnych",
+        "filters": {
+            "favourite": "Ulubione",
+            "people": "Osoby",
+            "rooms": "Pokoje",
+            "unread": "Nieprzeczytane"
+        },
         "home_menu_label": "Opcje głównej",
         "join_public_room_label": "Dołącz do publicznego pokoju",
         "joining_rooms_status": {
             "one": "Aktualnie dołączanie do %(count)s pokoju",
             "other": "Aktualnie dołączanie do %(count)s pokoi"
         },
+        "list_title": "Lista pokoi",
+        "more_options": {
+            "copy_link": "Kopiuj link pokoju",
+            "favourited": "Ulubiony",
+            "leave_room": "Opuść pokój",
+            "low_priority": "Niski priorytet",
+            "mark_read": "Oznacz jako przeczytane",
+            "mark_unread": "Oznacz jako nieprzeczytane"
+        },
         "notification_options": "Opcje powiadomień",
+        "open_space_menu": "Otwórz menu przestrzeni",
+        "primary_filters": "Filtry listy pokoi",
         "redacting_messages_status": {
             "one": "Aktualnie usuwanie wiadomości z %(count)s pokoju",
             "other": "Aktualnie usuwanie wiadomości z %(count)s pokoi"
         },
+        "room": {
+            "more_options": "Więcej opcji",
+            "open_room": "Pokój otwarty %(roomName)s"
+        },
+        "room_options": "Opcje pokoju",
         "show_less": "Pokaż mniej",
+        "show_message_previews": "Pokaż podglądy wiadomości",
         "show_n_more": {
             "one": "Pokaż %(count)s więcej",
             "few": "Pokaż %(count)s więcej",
             "many": "Pokaż %(count)s więcej"
         },
         "show_previews": "Pokazuj podgląd wiadomości",
+        "sort": "Sortuj",
         "sort_by": "Sortuj według",
         "sort_by_activity": "Aktywności",
         "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Aktywność",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Pokazuj najpierw pokoje z nieprzeczytanymi wiadomościami",
+        "space_menu": {
+            "home": "Główna przestrzeni",
+            "space_settings": "Ustawienia przestrzeni"
+        },
         "space_menu_label": "menu %(spaceName)s",
         "sublist_options": "Ustawienia listy",
         "suggested_rooms_heading": "Sugerowane pokoje"
@@ -2101,7 +2208,7 @@
             "upgrade_dialog_description_3": "Zakaż rozmawiania w starej wersji pokoju i opublikuj wiadomość, aby użytkownicy przenieśli się do nowego",
             "upgrade_dialog_description_4": "Opublikuj link do starego pokoju na początku nowego, aby można było czytać stare wiadomości",
             "upgrade_dialog_title": "Uaktualnij wersję pokoju",
-            "upgrade_dwarning_ialog_title_public": "Aktualizuj pokój publiczny",
+            "upgrade_dwarning_ialog_title_public": "Ulepsz pokój publiczny",
             "upgrade_warning_dialog_description": "Aktualizowanie pokoju to zaawansowane działanie i zaleca się je głównie, kiedy pokój jest niestabilny, brakuje w nim funkcji lub znajdują się w nim luki bezpieczeństwa.",
             "upgrade_warning_dialog_explainer": "<b>Aktualizacja spowoduje utworzenie pokoju w nowej wersji</b>. Wszystkie bieżące wiadomości zostaną zarchiwizowane w tym pokoju.",
             "upgrade_warning_dialog_footer": "Zaktualizujesz ten pokój z wersji <oldVersion /> do <newVersion />.",
@@ -2255,7 +2362,7 @@
             "history_visibility_shared": "Tylko członkowie (od momentu włączenia tej opcji)",
             "history_visibility_warning": "Zmiany tego, kto może przeglądać historię wyszukiwania dotyczą tylko przyszłych wiadomości w pokoju. Widoczność wcześniejszej historii nie zmieni się.",
             "history_visibility_world_readable": "Każdy",
-            "join_rule_description": "Decyduj kto może dołączyć %(roomName)s.",
+            "join_rule_description": "Decyduj kto może dołączyć do %(roomName)s.",
             "join_rule_invite": "Prywatny (tylko na zaproszenie)",
             "join_rule_invite_description": "Tylko zaproszone osoby mogą dołączyć",
             "join_rule_knock": "Poproś o dołączenie",
@@ -2299,7 +2406,7 @@
             "public_without_alias_warning": "Aby powiązać ten pokój, dodaj adres.",
             "publish_room": "Uczyń ten pokój widocznym w katalogu pokoi publicznych.",
             "publish_space": "Uczyń tę przestrzeń widoczną w katalogu pomieszczeń publicznych.",
-            "strict_encryption": "Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesji z tej sesji w tym pokoju",
+            "strict_encryption": "Wysyłaj wiadomości tylko do zweryfikowanych użytkowników.",
             "title": "Bezpieczeństwo i prywatność"
         },
         "title": "Ustawienia pokoju - %(roomName)s",
@@ -2313,7 +2420,7 @@
             "guest_access_explainer_public_space": "Może to być przydatne dla publicznych przestrzeni.",
             "guest_access_label": "Włącz dostęp dla gości",
             "history_visibility_anyone_space": "Podgląd przestrzeni",
-            "history_visibility_anyone_space_description": "Pozwól ludziom na podgląd twojej przestrzeni zanim dołączą.",
+            "history_visibility_anyone_space_description": "Pozwól ludziom na podgląd Twojej przestrzeni zanim dołączą.",
             "history_visibility_anyone_space_recommendation": "Zalecane dla publicznych przestrzeni.",
             "title": "Widoczność"
         },
@@ -2353,6 +2460,10 @@
         "recent_changes_heading": "Najnowsze zmiany nie zostały jeszcze wprowadzone",
         "title": "Serwer nie odpowiada"
     },
+    "service_worker_error": {
+        "description": "%(brand)s wymaga workera usługi, aby móc załadować zabezpieczone media z repozytoriów Matrix. Ta funkcja nie jest obsługiwana przez Twoją przeglądarkę, więc niektóre media mogą się nie załadować.",
+        "title": "Nie udało się załadować workera usługi"
+    },
     "seshat": {
         "error_initialising": "Wystąpił błąd inicjalizacji wyszukiwania wiadomości, sprawdź <a>swoje ustawienia</a> po więcej informacji",
         "reset_button": "Resetuj bank wydarzeń",
@@ -2422,7 +2533,73 @@
         },
         "emoji_autocomplete": "Włącz podpowiedzi Emoji podczas pisania",
         "enable_markdown": "Włącz Markdown",
-        "enable_markdown_description": "Rozpocznij wiadomość z <code>/plain</code>, aby była bez markdownu.",
+        "enable_markdown_description": "Rozpocznij wiadomość z <code>/plain</code>, aby nie zawierała markdownu.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Dane konta, kontakty, preferencje i lista czatów zostaną zachowane",
+                "breadcrumb_page": "Resetuj szyfrowanie",
+                "breadcrumb_second_description": "Stracisz całą historię wiadomości przechowywaną na serwerze",
+                "breadcrumb_third_description": "Konieczne będzie ponowne zweryfikowanie wszystkich istniejących urządzeń i kontaktów",
+                "breadcrumb_title": "Czy na pewno chcesz zresetować swoją tożsamość?",
+                "breadcrumb_title_forgot": "Nie pamiętasz klucza przywracania? Musisz zresetować swoją tożsamość.",
+                "breadcrumb_title_sync_failed": "Nie udało się zsynchronizować magazynu kluczy. Zresetuj swoją tożsamość.",
+                "breadcrumb_warning": "Zrób to tylko wtedy, gdy uważasz, że Twoje konto zostało naruszone.",
+                "details_title": "Szczegóły szyfrowania",
+                "do_not_close_warning": "Nie zamykaj tego okna, dopóki reset nie zostanie zakończony",
+                "export_keys": "Eksportuj klucze",
+                "import_keys": "Importuj klucze",
+                "other_people_device_description": "Ostrzeżenie: użytkownicy, którzy nie zweryfikowali Twojej tożsamości bezpośrednio z Tobą (np. za pomocą emoji), nie będą mogli czytać Twoich wiadomości szyfrowanych. Również niezweryfikowane urządzenia zweryfikowanych użytkowników nie będą otrzymywać zaszyfrowanych wiadomości.",
+                "other_people_device_label": "W pokojach szyfrowanych wysyłaj wiadomości tylko do zweryfikowanych użytkowników",
+                "other_people_device_title": "Urządzenia innych osób",
+                "reset_identity": "Zresetuj tożsamość kryptograficzną",
+                "reset_in_progress": "Resetowanie w toku...",
+                "session_id": "ID sesji:",
+                "session_key": "Klucz sesji:",
+                "title": "Zaawansowane"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Usuń magazyn kluczy",
+                "confirm": "Usuń magazyn kluczy",
+                "description": "Usunięcie magazynu kluczy usunie Twoją tożsamość kryptograficzną, klucze wiadomości z serwera i wyłączy następujące funkcje bezpieczeństwa:",
+                "list_first": "Nowe urządzenia nie będą posiadały Twojej historii wiadomości szyfrowanych",
+                "list_second": "Utracisz dostęp do swoich wiadomości szyfrowanych, jeśli wylogujesz się wszędzie z %(brand)s",
+                "title": "Czy na pewno chcesz wyłączyć magazyn kluczy i go wyłączyć?"
+            },
+            "device_not_verified_button": "Zweryfikuj to urządzenie",
+            "device_not_verified_description": "Aby wyświetlić ustawienia szyfrowania, musisz zweryfikować to urządzenie.",
+            "device_not_verified_title": "Urządzenie niezweryfikowane",
+            "dialog_title": "<strong>Ustawienia:</strong> Szyfrowanie",
+            "key_storage": {
+                "allow_key_storage": "Zezwól na magazynowanie kluczy",
+                "description": "Przechowuj swoją tożsamość kryptograficzną i klucze wiadomości bezpiecznie na serwerze. Umożliwi to na przeglądanie historii wiadomości na wszystkich nowych urządzeniach. <a>Dowiedz się więcej</a>",
+                "title": "Magazyn kluczy"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Potwierdź nowy klucz przywracania",
+                "change_recovery_confirm_description": "Wprowadź poniżej nowy klucz przywracania, aby zakończyć. Stary klucz przestanie działać.",
+                "change_recovery_confirm_title": "Wprowadź nowy klucz przywracania",
+                "change_recovery_key": "Zmień klucz przywracania",
+                "change_recovery_key_description": "Zapisz nowy klucz przywracania w bezpiecznym miejscu. Następnie kliknij Kontynuuj, aby zastosować zmiany.",
+                "change_recovery_key_title": "Zmienić klucz przywracania?",
+                "description": "Odzyskaj swoją tożsamość kryptograficzną i historię wiadomości za pomocą klucza przywracania, jeśli stracisz dostęp do swoich urządzeń.",
+                "enter_key_error": "Wprowadzony klucz przywracania nie jest poprawny.",
+                "enter_recovery_key": "Wprowadź klucz przywracania",
+                "forgot_recovery_key": "Zapomniałeś klucza przywracania?",
+                "key_storage_warning": "Magazyn kluczy nie jest zsynchronizowany. Kliknij jeden z przycisków poniżej, aby rozwiązać problem.",
+                "save_key_description": "Nie udostępniaj go nikomu!",
+                "save_key_title": "Klucz przywracania",
+                "set_up_recovery": "Skonfiguruj przywracanie",
+                "set_up_recovery_confirm_button": "Zakończ konfigurację",
+                "set_up_recovery_confirm_description": "Wprowadź klucz przywracania pokazany na poprzednim ekranie, aby zakończyć konfigurowanie przywracania.",
+                "set_up_recovery_confirm_title": "Wprowadź klucz przywracania, aby potwierdzić",
+                "set_up_recovery_description": "Magazyn kluczy jest chroniony przez klucz przywracania. Jeśli po skonfigurowaniu potrzebujesz klucza przywracania, wygeneruj go zaznaczając ‘%(changeRecoveryKeyButton)s’.",
+                "set_up_recovery_save_key_description": "Zapisz ten klucz przywracania w bezpiecznym miejscu, takim jak menedżer haseł, notatka szyfrowana lub sejf.",
+                "set_up_recovery_save_key_title": "Zapisz klucz przywracania w bezpiecznym miejscu",
+                "set_up_recovery_secondary_description": "Klikając przycisk Kontynuuj, wygenerujemy dla Ciebie klucz przywracania.",
+                "title": "Przywracanie"
+            },
+            "title": "Szyfrowanie"
+        },
         "general": {
             "account_management_section": "Zarządzanie kontem",
             "account_section": "Konto",
@@ -2457,14 +2634,15 @@
             "deactivate_confirm_continue": "Potwierdź dezaktywację konta",
             "deactivate_confirm_erase_label": "Ukryj moje wiadomości dla nowych osób",
             "deactivate_section": "Dezaktywuj konto",
-            "deactivate_warning": "Dezaktywacja konta jest akcją trwałą — bądź ostrożny!",
+            "deactivate_warning": "Dezaktywacja konta jest nieodwracalna — bądź ostrożny!",
             "discovery_email_empty": "Opcje odkrywania pojawią się, gdy dodasz adres e-mail.",
             "discovery_email_verification_instructions": "Zweryfikuj link w swojej skrzynce odbiorczej",
             "discovery_msisdn_empty": "Opcje odkrywania pojawią się, gdy dodasz numer telefonu.",
-            "discovery_needs_terms": "Musisz wyrazić zgodę na warunki świadczenia usług serwera (%(serverName)s), aby umożliwić odkrywanie Cię za pomocą adresu e-mail oraz numeru telefonu.",
-            "discovery_needs_terms_title": "Zezwól ludziom na znalezienie Cię",
+            "discovery_needs_terms": "Musisz wyrazić zgodę na warunki świadczenia usług serwera (%(serverName)s), aby można było Cię odkryć za pomocą adresu e-mail oraz numeru telefonu.",
+            "discovery_needs_terms_title": "Pozwól ludziom Cię znaleźć",
             "display_name": "Wyświetlana nazwa",
             "display_name_error": "Nie można ustawić wyświetlanej nazwy",
+            "email_adding_unsupported_by_hs": "Ten serwer domowy nie obsługuje dodawania adresów e-mail do Twojego konta.",
             "email_address_in_use": "Podany adres e-mail jest już w użyciu",
             "email_address_label": "Adres e-mail",
             "email_not_verified": "Twój adres e-mail nie został jeszcze zweryfikowany",
@@ -2489,7 +2667,9 @@
             "error_share_msisdn_discovery": "Nie udało się udostępnić numeru telefonu",
             "identity_server_no_token": "Nie znaleziono tokena dostępu tożsamości",
             "identity_server_not_set": "Serwer tożsamości nie jest ustawiony",
+            "invalid_phone_number": "Podany numer telefonu nie wydaje się być poprawny.",
             "language_section": "Język",
+            "msisdn_adding_unsupported_by_hs": "Ten serwer domowy nie obsługuje dodawania numerów telefonu do Twojego konta.",
             "msisdn_in_use": "Ten numer telefonu jest już zajęty",
             "msisdn_label": "Numer telefonu",
             "msisdn_verification_field_label": "Kod weryfikacyjny",
@@ -2508,7 +2688,6 @@
             "unable_to_load_msisdns": "Nie można załadować numerów telefonu",
             "username": "Nazwa użytkownika"
         },
-        "image_thumbnails": "Pokaż podgląd/miniatury obrazów",
         "inline_url_previews_default": "Włącz domyślny podgląd URL w tekście",
         "inline_url_previews_room": "Włącz domyślny podgląd URL dla uczestników w tym pokoju",
         "inline_url_previews_room_account": "Włącz podgląd URL dla tego pokoju (dotyczy tylko Ciebie)",
@@ -2527,24 +2706,24 @@
                 "confirm_security_phrase": "Potwierdź swoje hasło bezpieczeństwa",
                 "description": "Zabezpiecz się przed utratą dostępu do szyfrowanych wiadomości i danych, tworząc kopię zapasową kluczy szyfrowania na naszym serwerze.",
                 "download_or_copy": "%(downloadButton)s lub %(copyButton)s",
-                "enter_phrase_description": "Wprowadź hasło bezpieczeństwa, które znasz tylko Ty, ponieważ będzie użyte do ochrony Twoich danych. Ze względów bezpieczeństwa, nie wprowadzaj hasła Twojego konta.",
+                "enter_phrase_description": "Wprowadź frazę bezpieczeństwa, którą znasz tylko Ty, ponieważ wykorzystamy ją ochrony Twoich danych. Ze względów bezpieczeństwa, nie wprowadzaj tutaj swojego hasła.",
                 "enter_phrase_title": "Wprowadź hasło bezpieczeństwa",
                 "enter_phrase_to_confirm": "Wprowadź hasło bezpieczeństwa ponownie, aby potwierdzić.",
-                "generate_security_key_description": "Wygenerujemy dla Ciebie klucz bezpieczeństwa, który możesz przechowywać w bezpiecznym miejscu, np. w menedżerze haseł lub w sejfie.",
-                "generate_security_key_title": "Wygeneruj klucz bezpieczeństwa",
+                "generate_security_key_description": "Wygenerujemy dla Ciebie klucz przywracania, który możesz przechowywać w bezpiecznym miejscu, np. w menedżerze haseł lub sejfie.",
+                "generate_security_key_title": "Wygeneruj klucz przywracania",
                 "pass_phrase_match_failed": "To się nie zgadza.",
                 "pass_phrase_match_success": "Zgadza się!",
                 "phrase_strong_enough": "Wspaniale! Hasło bezpieczeństwa wygląda na silne.",
                 "secret_storage_query_failure": "Nie udało się uzyskać statusu sekretnego magazynu",
-                "security_key_safety_reminder": "Przechowuj swój klucz bezpieczeństwa w bezpiecznym miejscu, takim jak menedżer haseł lub sejf, ponieważ jest on używany do ochrony zaszyfrowanych danych.",
+                "security_key_safety_reminder": "Przechowuj swój klucz przywracania w bezpiecznym miejscu, takim jak menedżer haseł lub sejf, ponieważ jest on używany do ochrony zaszyfrowanych danych.",
                 "set_phrase_again": "Wróć, aby skonfigurować to ponownie.",
                 "settings_reminder": "W ustawieniach możesz również skonfigurować bezpieczną kopię zapasową i zarządzać swoimi kluczami.",
                 "title_confirm_phrase": "Potwierdź hasło bezpieczeństwa",
-                "title_save_key": "Zapisz swój klucz bezpieczeństwa",
+                "title_save_key": "Zapisz klucz przywracania",
                 "title_set_phrase": "Ustaw hasło bezpieczeństwa",
                 "unable_to_setup": "Nie można ustawić sekretnego magazynu",
                 "use_different_passphrase": "Użyć innego hasła?",
-                "use_phrase_only_you_know": "Użyj sekretnej frazy, którą znasz tylko Ty, i opcjonalnie zapisz klucz bezpieczeństwa, który będzie używany do tworzenia kopii zapasowych."
+                "use_phrase_only_you_know": "Użyj sekretnej frazy, którą znasz tylko Ty, i opcjonalnie zapisz klucz przywracania do tworzenia kopii zapasowych."
             }
         },
         "key_export_import": {
@@ -2571,6 +2750,14 @@
         "labs_mjolnir": {
             "dialog_title": "<strong>Ustawienia:</strong> Ignorowani użytkownicy"
         },
+        "media_preview": {
+            "hide_avatars": "Ukryj awatary pokoju i zapraszającego",
+            "hide_media": "Zawsze ukrywaj",
+            "media_preview_description": "Ukryte media można zawsze wyświetlić, dotykając ich",
+            "media_preview_label": "Pokaż media na osi czasu",
+            "show_in_private": "W pokojach prywatnych",
+            "show_media": "Zawsze pokazuj"
+        },
         "notifications": {
             "default_setting_description": "To ustawienie zastosuje się do wszystkich Twoich pokoi.",
             "default_setting_section": "Chce otrzymywać powiadomienia (Domyślne ustawienie)",
@@ -2599,7 +2786,7 @@
             "mentions_keywords": "Wzmianki i słowa kluczowe",
             "mentions_keywords_only": "Wyłącznie wzmianki i słowa kluczowe",
             "messages_containing_keywords": "Wiadomości zawierające słowa kluczowe",
-            "noisy": "Głośny",
+            "noisy": "Głośno",
             "notices": "Wiadomości wysłane przez boty",
             "notify_at_room": "Powiadom mnie, gdy ktoś użyje wzmianki @room",
             "notify_keyword": "Powiadom, gdy ktoś używa słowa kluczowego",
@@ -2616,11 +2803,11 @@
             "rule_call": "Zaproszenie do rozmowy",
             "rule_contains_display_name": "Wiadomości zawierające moją wyświetlaną nazwę",
             "rule_contains_user_name": "Wiadomości zawierające moją nazwę użytkownika",
-            "rule_encrypted": "Zaszyfrowane wiadomości w rozmowach grupowych",
-            "rule_encrypted_room_one_to_one": "Zaszyfrowane wiadomości w rozmowach jeden-do-jednego",
+            "rule_encrypted": "Wiadomości szyfrowane w czatach grupowych",
+            "rule_encrypted_room_one_to_one": "Wiadomości szyfrowane w czatach jeden-na-jeden",
             "rule_invite_for_me": "Kiedy zostanę zaproszony do pokoju",
             "rule_message": "Wiadomości w czatach grupowych",
-            "rule_room_one_to_one": "Wiadomości w rozmowach jeden-na-jeden",
+            "rule_room_one_to_one": "Wiadomości w czatach jeden-na-jeden",
             "rule_roomnotif": "Wiadomości zawierające @room",
             "rule_suppress_notices": "Wiadomości wysłane przez bota",
             "rule_tombstone": "Kiedy pokoje są uaktualniane",
@@ -2656,57 +2843,20 @@
         "prompt_invite": "Powiadamiaj przed wysłaniem zaproszenia do potencjalnie nieprawidłowych ID matrix",
         "replace_plain_emoji": "Automatycznie zastępuj tekstowe emotikony",
         "security": {
-            "4s_public_key_in_account_data": "w danych konta",
-            "4s_public_key_status": "Publiczny klucz sekretnego magazynu:",
             "analytics_description": "Udostępnij anonimowe dane, aby pomóc nam zidentyfikować problemy. Nic osobistego. Żadnych stron trzecich.",
-            "backup_key_cached_status": "Klucz zapasowy zapisany w pamięci podręcznej:",
-            "backup_key_stored_status": "Klucz zapasowy zapisany:",
-            "backup_key_unexpected_type": "niespodziewany typ",
-            "backup_key_well_formed": "dobrze ukształtowany",
-            "backup_keys_description": "Utwórz kopię zapasową kluczy szyfrujących wraz z danymi konta na wypadek utraty dostępu do sesji. Twoje klucze będą zabezpieczone unikalnym kluczem bezpieczeństwa.",
             "bulk_options_accept_all_invites": "Zaakceptuj wszystkie zaproszenia do %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Odrzuć wszystkie zaproszenia do %(invitedRooms)s",
             "bulk_options_section": "Masowe działania",
-            "cross_signing_cached": "w lokalnej pamięci podręcznej",
-            "cross_signing_homeserver_support": "Wsparcie funkcji serwera domowego:",
-            "cross_signing_homeserver_support_exists": "istnieje",
-            "cross_signing_in_4s": "w tajnej pamięci",
-            "cross_signing_in_memory": "w pamięci",
-            "cross_signing_master_private_Key": "Główny klucz prywatny:",
-            "cross_signing_not_cached": "nie odnaleziono lokalnie",
-            "cross_signing_not_found": "nie znaleziono",
-            "cross_signing_not_in_4s": "nie odnaleziono w pamięci",
-            "cross_signing_not_stored": "nieprzechowywany",
-            "cross_signing_private_keys": "Klucze prywatne weryfikacji krzyżowej:",
-            "cross_signing_public_keys": "Klucze publiczne weryfikacji krzyżowej:",
-            "cross_signing_self_signing_private_key": "Samo-podpisujący klucz prywatny:",
-            "cross_signing_user_signing_private_key": "Podpisany przez użytkownika klucz prywatny:",
-            "cryptography_section": "Kryptografia",
             "dehydrated_device_description": "Funkcja urządzenia offline umożliwia odbieranie wiadomości szyfrowanych, nawet jeśli nie jesteś zalogowany na żadnym urządzeniu",
             "dehydrated_device_enabled": "Urządzenie offline włączone",
-            "delete_backup": "Usuń kopię zapasową",
-            "delete_backup_confirm_description": "Czy jesteś pewien? Stracisz dostęp do wszystkich swoich zaszyfrowanych wiadomości, jeżeli nie utworzyłeś poprawnej kopii zapasowej kluczy.",
             "dialog_title": "<strong>Ustawienia:</strong> Bezpieczeństwo i prywatność",
             "e2ee_default_disabled_warning": "Twój administrator serwera wyłączył domyślne szyfrowanie end-to-end w pokojach i wiadomościach prywatnych.",
             "enable_message_search": "Włącz wyszukiwanie wiadomości w szyfrowanych pokojach",
             "encryption_section": "Szyfrowanie",
-            "error_loading_key_backup_status": "Nie można załadować stanu kopii zapasowej klucza",
-            "export_megolm_keys": "Eksportuj klucze E2E pokojów",
             "ignore_users_empty": "Nie posiadasz ignorowanych użytkowników.",
             "ignore_users_section": "Ignorowani użytkownicy",
-            "import_megolm_keys": "Importuj klucze pokoju E2E",
-            "key_backup_active": "Ta sesja tworzy kopię zapasową kluczy.",
-            "key_backup_active_version": "Aktywna wersja kopii zapasowej:",
-            "key_backup_active_version_none": "Brak",
             "key_backup_algorithm": "Algorytm:",
-            "key_backup_can_be_restored": "Tę kopię zapasową można przywrócić w tej sesji",
-            "key_backup_complete": "Utworzono kopię zapasową wszystkich kluczy",
             "key_backup_connect": "Połącz tę sesję z kopią zapasową kluczy",
-            "key_backup_connect_prompt": "Połącz tę sesję z kopią zapasową kluczy przed wylogowaniem, aby uniknąć utraty kluczy które mogą istnieć tylko w tej sesji.",
-            "key_backup_in_progress": "Tworzenie kopii zapasowej %(sessionsRemaining)s kluczy…",
-            "key_backup_inactive": "Ta sesja <b>nie wykonuje kopii zapasowej twoich kluczy</b>, ale masz istniejącą kopię którą możesz przywrócić i uzupełniać w przyszłości.",
-            "key_backup_inactive_warning": "Twoje klucze <b>nie są zapisywanie na tej sesji</b>.",
-            "key_backup_latest_version": "Najnowsza wersja kopii zapasowej na serwerze:",
             "message_search_disable_warning": "Jeśli wyłączone, wiadomości z szyfrowanych pokojów nie pojawią się w wynikach wyszukiwania.",
             "message_search_disabled": "Bezpiecznie przechowuj lokalnie wiadomości szyfrowane, aby mogły się wyświetlać w wynikach wyszukiwania.",
             "message_search_enabled": {
@@ -2718,22 +2868,16 @@
             "message_search_indexed_rooms": "Pokoje indeksowane:",
             "message_search_indexing": "Aktualnie indeksowanie: %(currentRoom)s",
             "message_search_indexing_idle": "Aktualnie nie są indeksowane wiadomości z żadnego pokoju.",
-            "message_search_intro": "%(brand)s bezpiecznie przechowuje wiadomości szyfrowane lokalnie, aby mogły pojawić się w wynikach wyszukiwania:",
+            "message_search_intro": "%(brand)s bezpiecznie i lokalnie przechowuje wiadomości szyfrowane, aby mogły pojawić się w wynikach wyszukiwania:",
             "message_search_room_progress": "%(doneRooms)s z %(totalRooms)s",
             "message_search_section": "Wyszukiwanie wiadomości",
             "message_search_sleep_time": "Jak szybko powinny być pobierane wiadomości.",
-            "message_search_space_used": "Użyte miejsce:",
+            "message_search_space_used": "Wykorzystane miejsce:",
             "message_search_unsupported": "%(brand)s brakuje niektórych komponentów wymaganych do bezpiecznego przechowywania wiadomości szyfrowanych lokalnie. Jeśli chcesz eksperymentować z tą funkcją, zbuduj własny %(brand)s Desktop z <nativeLink> dodanymi komponentami wyszukiwania</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s nie jest w stanie bezpiecznie przechowywać wiadomości szyfrowanych lokalnie, gdy działa w przeglądarce. Użyj <desktopLink> Desktop</desktopLink>, aby wiadomości pojawiły się w wynikach wyszukiwania.",
             "record_session_details": "Zapisz nazwę klienta, wersję i URL, aby łatwiej rozpoznawać sesje w menedżerze sesji",
-            "restore_key_backup": "Przywróć z kopii zapasowej",
-            "secret_storage_not_ready": "nie gotowe",
-            "secret_storage_ready": "gotowy",
-            "secret_storage_status": "Sekretny magazyn:",
             "send_analytics": "Wysyłaj dane analityczne",
-            "session_id": "Identyfikator sesji:",
-            "session_key": "Klucz sesji:",
-            "strict_encryption": "Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesji z tej sesji"
+            "strict_encryption": "Wysyłaj wiadomości tylko do zweryfikowanych użytkowników"
         },
         "send_read_receipts": "Wysyłaj potwierdzenia przeczytania",
         "send_read_receipts_unsupported": "Twój serwer nie wspiera wyłączenia wysyłania potwierdzeń przeczytania.",
@@ -2781,6 +2925,7 @@
             "inactive_sessions_list_description": "Rozważ wylogowanie się ze starych sesji (%(inactiveAgeDays)s dni lub starsze), jeśli już z nich nie korzystasz.",
             "ip": "Adres IP",
             "last_activity": "Ostatnia aktywność",
+            "manage": "Zarządzaj tą sesją",
             "mobile_session": "Sesja mobilna",
             "n_sessions_selected": {
                 "one": "Zaznaczono %(count)s sesję",
@@ -2804,7 +2949,7 @@
             "security_recommendations_description": "Zwiększ bezpieczeństwo swojego konta kierując się tymi rekomendacjami.",
             "session_id": "Identyfikator sesji",
             "show_details": "Pokaż szczegóły",
-            "sign_in_with_qr": "Połącz nowe urządzenie",
+            "sign_in_with_qr": "Powiąż nowe urządzenie",
             "sign_in_with_qr_button": "Pokaż kod QR",
             "sign_in_with_qr_description": "Użyj kodu QR, aby zalogować się na innym urządzeniu i skonfigurować bezpieczne przesyłanie wiadomości.",
             "sign_in_with_qr_unsupported": "Nieobsługiwane przez dostawcę konta",
@@ -2872,7 +3017,7 @@
         "use_control_enter_send_message": "Użyj Ctrl + Enter, aby wysłać wiadomość",
         "use_control_f_search": "Użyj Ctrl + F aby przeszukać oś czasu",
         "voip": {
-            "allow_p2p": "Zezwól Peer-to-Peer dla połączeń 1:1",
+            "allow_p2p": "Zezwól na Peer-to-Peer dla połączeń 1:1",
             "allow_p2p_description": "Po włączeniu, inni użytkownicy będą mogli zobaczyć twój adres IP",
             "audio_input_empty": "Nie wykryto żadnego mikrofonu",
             "audio_output": "Wyjście audio",
@@ -2974,8 +3119,6 @@
         "topic": "Wyświetla lub ustawia temat pokoju",
         "topic_none": "Ten pokój nie ma tematu.",
         "topic_room_error": "Nie można znaleźć tematu pokoju: Nie można znaleźć pokoju (%(roomId)s",
-        "tovirtual": "Przełącza do wirtualnego pokoju tego pokoju, jeśli taki istnieje",
-        "tovirtual_not_found": "Brak wirtualnego pokoju dla tego pokoju",
         "unban": "Odblokowuje użytkownika o danym ID",
         "unflip": "Dodaje ┬──┬ ノ( ゜-゜ノ) na początku wiadomości tekstowej",
         "unholdcall": "Odwiesza połączenie w obecnym pokoju",
@@ -2993,6 +3136,7 @@
         "view": "Przegląda pokój z podanym adresem",
         "whois": "Pokazuje informacje na temat użytkownika"
     },
+    "sliding_sync_legacy_no_longer_supported": "Starsza metoda synchronizacji sliding sync nie jest już obsługiwana: zaloguj się ponownie, aby włączyć nową flagę synchronizacji sliding sync",
     "space": {
         "add_existing_room_space": {
             "create": "Chcesz zamiast tego dodać nowy pokój?",
@@ -3097,7 +3241,7 @@
         "heading_without_query": "Szukaj",
         "join_button_text": "Dołącz %(roomAddress)s",
         "keyboard_scroll_hint": "Użyj <arrows/>, aby przewijać",
-        "message_search_section_title": "Inne wyszukiwania",
+        "messages_label": "Wiadomości",
         "other_rooms_in_space": "Inne pokoje w %(spaceName)s",
         "public_rooms_label": "Pokoje publiczne",
         "public_spaces_label": "Przestrzenie publiczne",
@@ -3107,7 +3251,6 @@
         "result_may_be_hidden_privacy_warning": "Niektóre wyniki zostały ukryte dla ochrony prywatności",
         "result_may_be_hidden_warning": "Niektóre wyniki mogą być ukryte",
         "search_dialog": "Pasek wyszukiwania",
-        "search_messages_hint": "Aby szukać wiadomości, poszukaj tej ikony na górze pokoju <icon/>",
         "spaces_title": "Przestrzenie, w których jesteś",
         "start_group_chat_button": "Rozpocznij czat grupowy"
     },
@@ -3145,7 +3288,7 @@
             "other": "%(count)s odpowiedzi"
         },
         "empty_description": "Użyj „%(replyInThread)s” po najechaniu kursorem na wiadomość",
-        "empty_title": "Wątki pomagają utrzymać tematykę rozmów i łatwo za nimi podążyć.",
+        "empty_title": "Wątki pomagają utrzymać tematykę rozmów i łatwo za nimi podążać.",
         "error_start_thread_existing_relation": "Nie można utworzyć wątku z wydarzenia z istniejącą relacją",
         "mark_all_read": "Oznacz wszystkie jako przeczytane",
         "my_threads": "Moje wątki",
@@ -3156,9 +3299,7 @@
     "threads_activity_centre": {
         "header": "Aktywność wątków",
         "no_rooms_with_threads_notifs": "Nie masz jeszcze pokoi z powiadomieniami w wątku.",
-        "no_rooms_with_unread_threads": "Nie masz jeszcze pokoi z nieprzeczytanymi wątkami.",
-        "release_announcement_description": "Powiadomienia w wątkach zostały przeniesione, teraz znajdziesz je tutaj.",
-        "release_announcement_header": "Centrum aktywności wątków"
+        "no_rooms_with_unread_threads": "Nie masz jeszcze pokoi z nieprzeczytanymi wątkami."
     },
     "time": {
         "about_day_ago": "około dzień temu",
@@ -3206,7 +3347,7 @@
             "historical_event_no_key_backup": "Historia wiadomości nie jest dostępna na tym urządzeniu",
             "historical_event_unverified_device": "Musisz zweryfikować to urządzenie, aby wyświetlić historię wiadomości",
             "historical_event_user_not_joined": "Nie masz dostępu do tej wiadomości",
-            "sender_identity_previously_verified": "Zweryfikowana tożsamość nadawcy uległa zmianie",
+            "sender_identity_previously_verified": "Zweryfikowana tożsamość nadawcy została zresetowana",
             "sender_unsigned_device": "Wysłano z niezabezpieczonego urządzenia.",
             "unable_to_decrypt": "Nie można rozszyfrować wiadomości"
         },
@@ -3370,6 +3511,7 @@
             "left_reason": "%(targetName)s opuścił pokój: %(reason)s",
             "no_change": "%(senderName)s nie dokonał żadnych zmian",
             "reject_invite": "%(targetName)s odrzucił zaproszenie",
+            "reject_invite_reason": "%(targetName)s odrzucił zaproszenie: %(reason)s",
             "remove_avatar": "%(senderName)s usunął swoje zdjęcie profilowe",
             "remove_name": "%(senderName)s usunął swoją widoczną nazwę (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s ustawił zdjęcie profilowe",
@@ -3405,10 +3547,14 @@
             "sent": "%(senderName)s wysłał zaproszenie do %(targetDisplayName)s do dołączenia do pokoju."
         },
         "m.room.tombstone": "%(senderDisplayName)s ulepszył ten pokój.",
-        "m.room.topic": "%(senderDisplayName)s zmienił temat na \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s zmienił temat na \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s usunął temat."
+        },
         "m.sticker": "%(senderDisplayName)s wysłał naklejkę.",
         "m.video": {
-            "error_decrypting": "Błąd deszyfrowania wideo"
+            "error_decrypting": "Błąd deszyfrowania wideo",
+            "show_video": "Pokaż wideo"
         },
         "m.widget": {
             "added": "Widżet %(widgetName)s został dodany przez %(senderName)s",
@@ -3532,16 +3678,19 @@
                 "other": "zostało zaproszonych %(count)s razy"
             },
             "joined": {
-                "other": "%(oneUser)s dołączył %(count)s razy",
-                "one": "%(oneUser)s dołączył"
+                "one": "%(oneUser)s dołączył",
+                "few": "%(oneUser)s dołączył %(count)s raz",
+                "many": "%(oneUser)s dołączył %(count)s razy"
             },
             "joined_and_left": {
-                "other": "%(oneUser)s dołączył i wyszedł %(count)s razy",
-                "one": "%(oneUser)s dołączył i wyszedł"
+                "one": "%(oneUser)s dołączył i wyszedł",
+                "few": "%(oneUser)s dołączył i wyszedł %(count)s razy",
+                "many": "%(oneUser)s dołączył i wyszedł %(count)s razy"
             },
             "joined_and_left_multiple": {
                 "one": "%(severalUsers)s dołączyło i wyszło",
-                "other": "%(severalUsers)s dołączyło i wyszło %(count)s razy"
+                "few": "%(severalUsers)s dołączyło i wyszło %(count)s razy",
+                "many": "%(severalUsers)s dołączyło i wyszło %(count)s razy"
             },
             "joined_multiple": {
                 "one": "%(severalUsers)s dołączył",
@@ -3597,12 +3746,14 @@
                 "other": "%(severalUsers)sodrzuciło ich zaproszenia %(count)s razy"
             },
             "rejoined": {
-                "other": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy",
-                "one": "%(oneUser)s wyszedł i dołączył ponownie"
+                "one": "%(oneUser)s wyszedł i dołączył ponownie",
+                "few": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy",
+                "many": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy"
             },
             "rejoined_multiple": {
-                "other": "%(severalUsers)swyszło i dołączyło ponownie %(count)s razy",
-                "one": "%(severalUsers)swyszło i dołączyło ponownie"
+                "one": "%(severalUsers)s wyszło i dołączyło ponownie",
+                "few": "%(severalUsers)s wyszło i dołączyło ponownie %(count)s razy",
+                "many": "%(severalUsers)s wyszło i dołączyło ponownie %(count)s razy"
             },
             "server_acls": {
                 "one": "%(oneUser)szmienił ACL serwera",
@@ -3666,10 +3817,11 @@
         "unavailable": "Niedostępny"
     },
     "update_room_access_modal": {
-        "description": "Aby utworzyć link udostępniania, musisz zezwolić gościom na dołączenie do tego pokoju. Może to zmniejszyć bezpieczeństwo pokoju. Gdy zakończysz połączenie, możesz ustawić pokój jako prywatny z powrotem.",
-        "dont_change_description": "Możesz również zadzwonić w innym pokoju.",
+        "description": "Aby utworzyć link udostępniania, ustaw ten pokój jako <b>publiczny</b> lub włącz opcję umożliwiającą użytkownikom <b>poprosić o dołączenie</b>. Dzięki temu goście będą mogli dołączyć bez zaproszenia.",
+        "dont_change_description": "Jeśli nie chcesz zmieniać uprawnień dostępu tego pokoju, możesz utworzyć link połączenia w nowym pokoju.",
         "no_change": "Nie chce zmieniać poziomu uprawnień.",
-        "title": "Zmień poziom dostępu pokoju"
+        "revert_access_description": "(Przywróć poprzednią wartość w ustawieniach pokoju: <b>Bezpieczeństwo i prywatność</b> / <b>Dostęp</b>)",
+        "title": "Zezwól gościom na dołączenie do tego pokoju"
     },
     "upload_failed_generic": "Nie udało się przesłać pliku '%(fileName)s'.",
     "upload_failed_size": "Plik '%(fileName)s' przekracza limit rozmiaru dla tego serwera głównego",
@@ -3696,18 +3848,9 @@
         "ban_room_confirm_title": "Zbanuj z %(roomName)s",
         "ban_space_everything": "Zbanuj ich z wszystkiego co mogę",
         "ban_space_specific": "Zbanuj ich z określonych rzeczy, które mogę",
-        "count_of_sessions": {
-            "other": "%(count)s sesji",
-            "one": "%(count)s sesja"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s zweryfikowanych sesji",
-            "one": "1 zweryfikowana sesja"
-        },
         "deactivate_confirm_action": "Dezaktywuj użytkownika",
         "deactivate_confirm_description": "Dezaktywacja tego użytkownika, wyloguje go i uniemożliwi logowanie ponowne. Dodatkowo, opuści wszystkie pokoje, w których się znajdują. Tej akcji nie można cofnąć. Czy na pewno chcesz dezaktywować tego użytkownika?",
         "deactivate_confirm_title": "Dezaktywować użytkownika?",
-        "dehydrated_device_enabled": "Urządzenie offline włączone",
         "demote_button": "Degraduj",
         "demote_self_confirm_description_space": "Nie będziesz mógł cofnąć tej zmiany, ponieważ degradujesz swoje uprawnienia. Jeśli jesteś ostatnim użytkownikiem uprzywilejowanym w tej przestrzeni, nie będziesz mógł ich odzyskać.",
         "demote_self_confirm_room": "Nie będziesz mógł cofnąć tej zmiany, ponieważ degradujesz swoje uprawnienia. Jeśli jesteś ostatnim użytkownikiem uprzywilejowanym w tym pokoju, nie będziesz mógł ich odzyskać.",
@@ -3715,15 +3858,12 @@
         "disinvite_button_room": "Cofnij zaproszenie do pokoju",
         "disinvite_button_room_name": "Cofnij zaproszenia z %(roomName)s",
         "disinvite_button_space": "Cofnij zaproszenie do przestrzeni",
-        "edit_own_devices": "Edytuj urządzenia",
         "error_ban_user": "Nie udało się zbanować użytkownika",
         "error_deactivate": "Nie udało się zdezaktywować użytkownika",
         "error_kicking_user": "Nie udało się usunąć użytkownika",
         "error_mute_user": "Nie udało się wyciszyć użytkownika",
         "error_revoke_3pid_invite_description": "Nie udało się odwołać zaproszenia. Serwer może posiadać tymczasowy problem lub nie masz wystarczających uprawnień, aby odwołać zaproszenie.",
         "error_revoke_3pid_invite_title": "Nie udało się odwołać zaproszenia",
-        "hide_sessions": "Ukryj sesje",
-        "hide_verified_sessions": "Ukryj zweryfikowane sesje",
         "ignore_button": "Ignoruj",
         "ignore_confirm_description": "Wszystkie wiadomości i zaproszenia od tego użytkownika zostaną ukryte. Czy na pewno chcesz zignorować?",
         "ignore_confirm_title": "Ignoruj %(user)s",
@@ -3767,6 +3907,7 @@
         "unban_space_specific": "Odbanuj ich z określonych rzeczy, które mogę",
         "unban_space_warning": "Nie będą w stanie uzyskać dostępu gdziekolwiek, gdzie nie jesteś administratorem.",
         "unignore_button": "Przestań ignorować",
+        "verification_unavailable": "Weryfikacja użytkownika jest niedostępna",
         "verify_button": "Weryfikuj użytkownika",
         "verify_explainer": "Dla dodatkowego bezpieczeństwa, zweryfikuj tego użytkownika za pomocą jednorazowego kodu na obu waszych urządzeniach."
     },
@@ -3819,7 +3960,6 @@
         "input_devices": "Urządzenia wejściowe",
         "jitsi_call": "Konferencja Jitsi",
         "join_button_tooltip_call_full": "Przepraszamy — to połączenie jest już zapełnione",
-        "join_button_tooltip_connecting": "Łączenie",
         "legacy_call": "Połączenie Legacy",
         "maximise": "Wypełnij ekran",
         "maximise_call": "Maksymalizuj połączenie",
@@ -3837,7 +3977,8 @@
         "msisdn_transfer_failed": "Nie udało się przekazać połączenia",
         "n_people_joined": {
             "one": "%(count)s osoba dołączyła",
-            "other": "%(count)s osób dołączyło"
+            "few": "%(count)s osoby dołączyły",
+            "many": "%(count)s osób dołączyło"
         },
         "no_audio_input_description": "Nie udało się znaleźć żadnego mikrofonu w twoim urządzeniu. Sprawdź ustawienia i spróbuj ponownie.",
         "no_audio_input_title": "Nie znaleziono mikrofonu",
@@ -3974,7 +4115,7 @@
         "error_need_to_be_logged_in": "Musisz być zalogowany.",
         "error_unable_start_audio_stream_description": "Nie można rozpocząć przesyłania strumienia audio.",
         "error_unable_start_audio_stream_title": "Nie udało się rozpocząć transmisji na żywo",
-        "modal_data_warning": "Dane na tym ekranie są współdzielone z %(widgetDomain)s",
+        "modal_data_warning": "Poniższe dane są współdzielone z %(widgetDomain)s",
         "modal_title_default": "Widżet modalny",
         "no_name": "Nieznana aplikacja",
         "open_id_permissions_dialog": {
diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json
index d8d9857c6db4c99c0d7434292e342ffde7581513..db6df857a6d31587bb634ea4a308b8bde3cc5f36 100644
--- a/src/i18n/strings/pt.json
+++ b/src/i18n/strings/pt.json
@@ -43,6 +43,7 @@
         "copy_link": "Copiar Link",
         "create": "Criar",
         "create_a_room": "Criar uma sala",
+        "create_account": "Criar uma conta",
         "decline": "Recusar",
         "delete": "Apagar",
         "deny": "Negar",
@@ -63,7 +64,8 @@
         "go": "Ir",
         "go_back": "Voltar",
         "got_it": "Entendi",
-        "hide_advanced": "Ocultar avançado",
+        "hide": "Ocultar",
+        "hide_advanced": "Ocultar avançadas",
         "hold": "Espera",
         "ignore": "Ignorar",
         "import": "Importar",
@@ -79,11 +81,14 @@
         "maximise": "Maximizar",
         "mention": "Mencionar",
         "minimise": "Minimizar",
+        "new_message": "Nova mensagem",
         "new_room": "Nova sala",
         "new_video_room": "Nova sala de vídeo",
         "next": "Próximo",
         "no": "Não",
         "ok": "OK",
+        "open": "Abrir",
+        "open_menu": "Abrir menu",
         "pause": "Pausar",
         "pin": "Fixar",
         "play": "Reproduzir",
@@ -92,7 +97,6 @@
         "react": "Reagir",
         "refresh": "Atualizar",
         "register": "Registar",
-        "reject": "Rejeitar",
         "reload": "Recarregar",
         "remove": "Remover",
         "rename": "Renomear",
@@ -227,6 +231,7 @@
         },
         "misconfigured_body": "Pede ao teu administrador %(brand)s para verificar <a>a tua configuração</a> para ver se há entradas incorretas ou duplicadas.",
         "misconfigured_title": "O teu %(brand)s está mal configurado",
+        "mobile_create_account_title": "Estás prestes a criar uma conta no %(hsName)s",
         "msisdn_field_description": "Outros utilizadores podem convidar-te para salas utilizando os teus dados de contacto",
         "msisdn_field_label": "Telefone",
         "msisdn_field_number_invalid": "Este número de telefone não parece estar correto, por favor verifica e tenta novamente",
@@ -276,6 +281,8 @@
             "security_code": "Código de segurança",
             "security_code_prompt": "Se solicitado, insira o código abaixo no seu outro dispositivo.",
             "select_qr_code": "Seleciona \"%(scanQRCode)s\"",
+            "unsupported_explainer": "O teu fornecedor de conta não suporta o início de sessão num novo dispositivo com um código QR.",
+            "unsupported_heading": "Código QR não suportado",
             "waiting_for_device": "A aguardar que o dispositivo inicie sessão"
         },
         "register_action": "Criar conta",
@@ -352,6 +359,7 @@
         "soft_logout_subheading": "Limpar dados pessoais",
         "soft_logout_warning": "Aviso: os teus dados pessoais (incluindo chaves de encriptação) ainda estão armazenados nesta sessão. Limpa-a se já tiveres terminado de utilizar esta sessão ou se quiseres iniciar sessão noutra conta.",
         "sso": "Registo único",
+        "sso_complete_in_browser_dialog_title": "Vai para o teu browser para concluir o início de sessão",
         "sso_failed_missing_storage": "Pedimos ao navegador que se lembrasse do homeserver que usa para permitir o início de sessão, mas infelizmente o seu navegador esqueceu. Aceda à página de início de sessão e tente novamente.",
         "sso_or_username_password": "%(ssoButtons)s Ou %(usernamePassword)s",
         "sync_footer_subtitle": "Se já te juntaste a muitas salas, isto pode demorar um pouco",
@@ -363,6 +371,8 @@
             "email_resend_prompt": "Não o recebeste? <a>Reenvia-o</a>",
             "email_resent": "Reenviado!",
             "fallback_button": "Iniciar autenticação",
+            "mas_cross_signing_reset_cta": "Ir para a tua conta",
+            "mas_cross_signing_reset_description": "Repõe a tua identidade através do teu fornecedor de conta e, em seguida, volta a clicar em \"Repetir\".",
             "msisdn": "Foi enviada uma mensagem de texto para %(msisdn)s",
             "msisdn_token_incorrect": "Token incorreto",
             "msisdn_token_prompt": "Por favor, entre com o código que está na mensagem:",
@@ -397,7 +407,15 @@
         "download_logs": "Descarrega os registos",
         "downloading_logs": "Descarregando registos",
         "error_empty": "Diz-nos o que correu mal ou, melhor ainda, cria uma questão no GitHub que descreva o problema.",
-        "failed_send_logs": "Falha ao enviar registos: ",
+        "failed_download_logs": "Falha ao descarregar os registos de depuração: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "O teu relatório de erro foi rejeitado. O servidor rageshake não suporta esta aplicação.",
+            "rejected_generic": "O teu relatório de bug foi rejeitado. O servidor do rageshake rejeitou o conteúdo do relatório devido a uma política.",
+            "rejected_recovery_key": "O teu relatório de erro foi rejeitado por razões de segurança, pois continha uma chave de recuperação.",
+            "rejected_version": "O teu relatório de erro foi rejeitado porque a versão que estás a executar é demasiado antiga.",
+            "server_unknown_error": "O servidor do rageshake encontrou um erro desconhecido e não pôde processar o relatório.",
+            "unknown_error": "Não conseguiste enviar os registos."
+        },
         "github_issue": "Problema no GitHub",
         "introduction": "Se submeteste um erro através do GitHub, os registos de depuração podem ajudar-nos a localizar o problema. ",
         "log_request": "Para nos ajudar a evitar esta situação no futuro, envia-nos os registos para <a></a> .",
@@ -437,7 +455,7 @@
         "access_token": "Token de acesso",
         "accessibility": "Acessibilidade",
         "advanced": "Avançado",
-        "all_rooms": "Todas as salas",
+        "all_chats": "Todas as conversas",
         "analytics": "Análise",
         "and_n_others": {
             "other": "e %(count)s outros...",
@@ -456,7 +474,6 @@
         "capabilities": "Capacidades",
         "copied": "Copiado!",
         "credits": "Créditos",
-        "cross_signing": "Assinatura cruzada",
         "dark": "Escuro",
         "description": "Descrição",
         "deselect_all": "Desmarcar todos",
@@ -487,12 +504,12 @@
         "legal": "Legal",
         "light": "Claro",
         "loading": "A carregar…",
-        "lobby": "Átrio",
         "location": "Localização",
         "low_priority": "Baixa prioridade",
         "matrix": "Matrix",
         "message": "Mensagem",
         "message_layout": "Disposição da mensagem",
+        "message_timestamp_invalid": "Carimbo de data/hora inválido",
         "microphone": "Microfone",
         "model": "Modelo",
         "modern": "Moderno",
@@ -530,6 +547,7 @@
         "qr_code": "Código QR",
         "random": "Aleatório",
         "reactions": "Reações",
+        "recommended": "Recomendado",
         "report_a_bug": "Comunicar falha",
         "room": "Sala",
         "room_name": "Nome da sala",
@@ -538,7 +556,6 @@
         "saved": "Guardado",
         "saving": "A guardar…",
         "secure_backup": "Cópia de segurança segura",
-        "security": "Segurança",
         "select_all": "Selecionar tudo",
         "server": "Servidor",
         "settings": "Configurações",
@@ -557,7 +574,6 @@
         "thread": "Tópico",
         "threads": "Tópicos",
         "timeline": "Cronologia",
-        "trusted": "Confiável",
         "unavailable": "indisponível",
         "unencrypted": "Não encriptado",
         "unmute": "Tirar do mudo",
@@ -717,12 +733,51 @@
         "twemoji": "A arte do emoji <twemoji>Twemoji</twemoji> é © <author>Twitter, Inc e outros colaboradores</author> utilizada ao abrigo dos termos de <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "O tipo de letra <colr>twemoji-colr</colr> é © <author>Mozilla Foundation</author> utilizado nos termos de <terms>Apache 2.0</terms>."
     },
+    "desktop_default_device_name": "%(brand)s Desktop: %(platformName)s",
     "devtools": {
         "active_widgets": "Widgets ativos",
         "category_other": "Outros",
         "category_room": "Sala",
         "caution_colon": "Atenção:",
         "client_versions": "Versões de cliente",
+        "crypto": {
+            "4s_public_key_in_account_data": "nos dados da conta",
+            "4s_public_key_not_in_account_data": "não encontrado",
+            "4s_public_key_status": "Chave pública de armazenamento secreto:",
+            "backup_key_cached": "armazenado em cache localmente",
+            "backup_key_cached_status": "Chave de backup armazenada em cache:",
+            "backup_key_not_stored": "não armazenado",
+            "backup_key_stored": "em armazenamento secreto",
+            "backup_key_stored_status": "Chave de backup armazenada:",
+            "backup_key_unexpected_type": "tipo inesperado",
+            "backup_key_well_formed": "bem formado",
+            "cross_signing": "Assinatura cruzada",
+            "cross_signing_cached": "armazenados em cache localmente",
+            "cross_signing_not_ready": "A assinatura cruzada não está configurada.",
+            "cross_signing_private_keys_in_storage": "em armazenamento secreto",
+            "cross_signing_private_keys_in_storage_status": "Assinatura cruzada de chaves privadas:",
+            "cross_signing_private_keys_not_in_storage": "não encontrado no armazenamento",
+            "cross_signing_public_keys_on_device": "na memória",
+            "cross_signing_public_keys_on_device_status": "Assinatura cruzada de chaves públicas:",
+            "cross_signing_ready": "A assinatura cruzada está pronta para uso.",
+            "cross_signing_status": "Status da assinatura cruzada:",
+            "cross_signing_untrusted": "Sua conta tem uma identidade de assinatura cruzada no armazenamento secreto, mas ainda não é confiável para esta sessão.",
+            "crypto_not_available": "O módulo criptográfico não está disponível",
+            "key_backup_active_version": "Versão de backup ativo:",
+            "key_backup_active_version_none": "Nenhum",
+            "key_backup_inactive_warning": "Não está a ser feito o backup das suas chaves a partir desta sessão.",
+            "key_backup_latest_version": "Versão de backup mais recente no servidor:",
+            "key_storage": "Armazenamento de chaves",
+            "master_private_key_cached_status": "Chave mestra privada:",
+            "not_found": "não encontrado",
+            "not_found_locally": "não encontrado localmente",
+            "secret_storage_not_ready": "não está pronto",
+            "secret_storage_ready": "pronto",
+            "secret_storage_status": "Armazenamento secreto:",
+            "self_signing_private_key_cached_status": "Chave privada de auto-assinatura:",
+            "title": "Criptografia de ponta a ponta",
+            "user_signing_private_key_cached_status": "Chave privada de assinatura do usuário:"
+        },
         "developer_mode": "Modo de desenvolvedor",
         "developer_tools": "Ferramentas de desenvolvedor",
         "edit_setting": "Editar configuração",
@@ -809,6 +864,7 @@
         "widget_screenshots": "Ativar capturas de ecrã de widgets em widgets suportados"
     },
     "dialog_close_label": "Fechar diálogo",
+    "download_completed": "Download Concluído",
     "emoji": {
         "categories": "Categorias",
         "category_activities": "Atividades",
@@ -829,52 +885,35 @@
     "empty_room_was_name": "Sala vazia (era %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Introduz a tua frase de segurança ou <button>utiliza a tua chave de segurança</button> para continuar.",
             "key_validation_text": {
-                "invalid_security_key": "Chave de segurança inválida",
-                "recovery_key_is_correct": "Parece bom!",
-                "wrong_file_type": "Tipo de ficheiro errado",
-                "wrong_security_key": "Chave de segurança errada"
-            },
-            "reset_title": "Repor tudo",
-            "reset_warning_1": "Faz isto apenas se não tiveres outro dispositivo para completar a verificação.",
-            "reset_warning_2": "Se reiniciares tudo, irás reiniciar sem sessões de confiança, sem utilizadores de confiança e poderás não conseguir ver mensagens anteriores.",
+                "wrong_security_key": "Chave de recuperação errada"
+            },
             "restoring": "Restaurar chaves a partir de uma cópia de segurança",
-            "security_key_title": "Chave de segurança",
-            "security_phrase_incorrect_error": "Não é possível aceder ao armazenamento secreto. Verifica se introduziste a frase de segurança correcta.",
-            "security_phrase_title": "Frase de segurança",
-            "separator": "%(securityKey)s ou %(recoveryFile)s",
-            "use_security_key_prompt": "Utiliza a tua chave de segurança para continuar."
+            "security_key_title": "Chave de recuperação"
         },
         "bootstrap_title": "A configurar chaves",
         "cancel_entering_passphrase_description": "Tem a certeza que quer cancelar a introdução da frase-passe?",
         "cancel_entering_passphrase_title": "Cancelar a introdução da frase-passe?",
         "confirm_encryption_setup_body": "Clica no botão abaixo para confirmar a configuração da encriptação.",
         "confirm_encryption_setup_title": "Confirma a configuração da encriptação",
-        "cross_signing_not_ready": "A assinatura cruzada não está configurada.",
-        "cross_signing_ready": "A assinatura cruzada está pronta a ser utilizada.",
-        "cross_signing_ready_no_backup": "A assinatura cruzada está pronta, mas as chaves não têm cópia de segurança.",
         "cross_signing_room_normal": "Esta sala é encriptada de ponta a ponta",
         "cross_signing_room_verified": "Toda a gente nesta sala é verificada",
         "cross_signing_room_warning": "Alguém está a utilizar uma sessão desconhecida",
-        "cross_signing_unsupported": "O teu servidor doméstico não suporta assinatura cruzada.",
-        "cross_signing_untrusted": "A tua conta tem uma identidade de assinatura cruzada no armazenamento secreto, mas ainda não é de confiança para esta sessão.",
         "cross_signing_user_normal": "Não verificaste este utilizador.",
         "cross_signing_user_verified": "Verificaste este utilizador. Este utilizador verificou todas as suas sessões.",
         "cross_signing_user_warning": "Este utilizador não verificou todas as suas sessões.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Chaves de assinatura cruzada claras",
-            "title": "Destrói as chaves de assinatura cruzada?",
-            "warning": "A eliminação das chaves de assinatura cruzada é permanente. Qualquer pessoa com quem tenhas feito a verificação verá alertas de segurança. É quase certo que não vais querer fazer isto, a menos que tenhas perdido todos os dispositivos a partir dos quais podes fazer a assinatura cruzada."
-        },
+        "enter_recovery_key": "Introduzir a chave de recuperação",
         "event_shield_reason_authenticity_not_guaranteed": "A autenticidade desta mensagem encriptada não pode ser garantida neste dispositivo.",
         "event_shield_reason_mismatched_sender_key": "Encriptado por uma sessão não verificada",
         "event_shield_reason_unknown_device": "Encriptado por um dispositivo desconhecido ou apagado.",
         "event_shield_reason_unsigned_device": "Encriptado por um dispositivo não verificado pelo seu proprietário.",
         "event_shield_reason_unverified_identity": "Encriptado por um utilizador não verificado.",
         "export_unsupported": "O seu navegador não suporta as extensões de criptografia necessárias",
+        "forgot_recovery_key": "Esqueceste-te da chave de recuperação?",
         "import_invalid_keyfile": "Não é um ficheiro de chaves %(brand)s válido",
         "import_invalid_passphrase": "Erro de autenticação: palavra-passe incorreta?",
+        "key_storage_out_of_sync": "O teu armazenamento de chaves está dessincronizado.",
+        "key_storage_out_of_sync_description": "Confirma a tua chave de recuperação para manteres o acesso ao teu armazenamento de chaves e ao histórico de mensagens.",
         "messages_not_secure": {
             "cause_1": "O teu servidor doméstico",
             "cause_2": "O servidor doméstico ao qual o utilizador que estás a verificar está ligado",
@@ -889,7 +928,8 @@
             "title": "Novo método de recuperação",
             "warning": "Se não tiveres definido o novo método de recuperação, um atacante pode estar a tentar aceder à tua conta. Altera a palavra-passe da tua conta e define imediatamente um novo método de recuperação nas Definições."
         },
-        "not_supported": "<não suportado>",
+        "pinned_identity_changed": "A identidade de %(displayName)s (<b>%(userId)s</b> ) foi alterada. <a> Saber mais</a>",
+        "pinned_identity_changed_no_displayname": "A identidade de <b>%(userId)s</b> foi alterada. <a>Saber mais</a>",
         "recovery_method_removed": {
             "description_1": "Esta sessão detectou que a tua frase de segurança e a chave para as mensagens seguras foram removidas.",
             "description_2": "Se o fizeste acidentalmente, podes configurar as Mensagens seguras nesta sessão, o que criptografará novamente o histórico de mensagens desta sessão com um novo método de recuperação.",
@@ -897,11 +937,13 @@
             "warning": "Se não tiveres removido o método de recuperação, um atacante pode estar a tentar aceder à tua conta. Altera a palavra-passe da tua conta e define imediatamente um novo método de recuperação nas Definições."
         },
         "reset_all_button": "Esqueceste-te ou perdeste todos os métodos de recuperação? <a>Repor tudo</a>",
+        "set_up_recovery": "Configurar a recuperação",
+        "set_up_recovery_later": "Agora não",
+        "set_up_recovery_toast_description": "Gera uma chave de recuperação que pode ser utilizada para restaurar o teu histórico de mensagens encriptadas, caso percas o acesso aos teus dispositivos.",
         "set_up_toast_description": "Protege-te contra a perda de acesso a mensagens e dados encriptados",
         "set_up_toast_title": "Configura uma cópia de segurança segura",
         "setup_secure_backup": {
-            "explainer": "Guarda as tuas chaves antes de saíres para evitar perdê-las.",
-            "title": "Configurar"
+            "explainer": "Guarda as tuas chaves antes de saíres para evitar perdê-las."
         },
         "udd": {
             "interactive_verification_button": "Verifica interactivamente por emoji",
@@ -912,12 +954,10 @@
             "title": "Não é confiável"
         },
         "unable_to_setup_keys_error": "Não é possível configurar as chaves",
-        "unsupported": "Este cliente não suporta encriptação de ponta a ponta.",
         "verification": {
             "accepting": "Aceitando...",
             "after_new_login": {
                 "device_verified": "Dispositivo verificado",
-                "reset_confirmation": "Repõe mesmo as chaves de verificação?",
                 "skip_verification": "Ignora a verificação por enquanto",
                 "unable_to_verify": "Não é possível verificar este dispositivo",
                 "verify_this_device": "Verifica este dispositivo"
@@ -939,7 +979,7 @@
             "incoming_sas_dialog_waiting": "Aguarda a confirmação do parceiro...",
             "incoming_sas_user_dialog_text_1": "Verifica este utilizador para o marcar como fiável. A confiança nos utilizadores dá-te uma tranquilidade extra quando utilizas mensagens encriptadas de ponta a ponta.",
             "incoming_sas_user_dialog_text_2": "A verificação deste utilizador marcará a sua sessão como sendo de confiança e também marcará a tua sessão como sendo de confiança para ele.",
-            "no_key_or_device": "Parece que não tens uma chave de segurança ou quaisquer outros dispositivos que possam ser verificados.  Este dispositivo não poderá aceder a mensagens encriptadas antigas. Para verificares a tua identidade neste dispositivo, terás de repor as tuas chaves de verificação.",
+            "no_key_or_device": "Parece que não tens uma chave de recuperação ou qualquer outro dispositivo com o qual possas fazer a verificação.  Este dispositivo não poderá aceder a mensagens encriptadas antigas. Para verificares a tua identidade neste dispositivo, terás de repor as tuas chaves de verificação.",
             "no_support_qr_emoji": "O dispositivo que estás a tentar verificar não suporta a leitura de um código QR nem a verificação de emoji, os dois métodos suportados pela %(brand)s. Tenta com um cliente diferente.",
             "other_party_cancelled": "A outra parte cancelou a verificação.",
             "prompt_encrypted": "Verifica todos os utilizadores de uma sala para garantir a sua segurança.",
@@ -988,30 +1028,39 @@
             "verify_emoji_prompt": "Verifica comparando o emoji único.",
             "verify_emoji_prompt_qr": "Se não conseguires ler o código acima, verifica-o comparando emojis.",
             "verify_later": "Verificarei mais tarde",
-            "verify_reset_warning_1": "A reposição das tuas chaves de verificação não pode ser anulada. Após a reposição, não terás acesso a mensagens encriptadas antigas e todos os amigos que te tenham verificado anteriormente verão avisos de segurança até voltares a verificar com eles.",
-            "verify_reset_warning_2": "Só avances se tiveres a certeza de que perdeste todos os teus outros dispositivos e a tua chave de segurança.",
             "verify_using_device": "Verifica com outro dispositivo",
-            "verify_using_key": "Verifica com a chave de segurança",
-            "verify_using_key_or_phrase": "Verifica com a chave ou frase de segurança",
+            "verify_using_key": "Verifica com a chave de recuperação",
+            "verify_using_key_or_phrase": "Verifica com a chave ou frase de recuperação",
             "waiting_for_user_accept": "À espera de %(displayName)s para aceitar...",
             "waiting_other_device": "À espera que verifiques no teu outro dispositivo...",
             "waiting_other_device_details": "À espera que verifiques no teu outro dispositivo, %(deviceName)s (%(deviceId)s)...",
             "waiting_other_user": "À espera de %(displayName)s para verificar..."
         },
         "verification_requested_toast_title": "Verificação solicitada",
+        "verified_identity_changed": "%(displayName)s(<b>%(userId)s</b>) mudou a tua identidade verificada. <a>Saber mais</a>",
+        "verified_identity_changed_no_displayname": "A identidade de <b>%(userId)s</b> foi alterada. <a>Saber mais</a>",
         "verify_toast_description": "Outros utilizadores podem não confiar nisto",
-        "verify_toast_title": "Verifica esta sessão"
+        "verify_toast_title": "Verifica esta sessão",
+        "withdraw_verification_action": "Retirar verificação"
     },
     "error": {
         "admin_contact": "Por favor, <a>contacta o teu administrador de serviços</a> para continuares a utilizar este serviço.",
         "admin_contact_short": "Contacta o teu administrador do servidor <a></a> .",
+        "app_launch_unexpected_error": "Erro inesperado ao preparar a aplicação. Consulta a consola para mais detalhes.",
+        "cannot_load_config": "Não foi possível carregar o ficheiro de configuração: actualiza a página para tentares novamente.",
         "connection": "Houve um problema de comunicação com o servidor doméstico, por favor tenta novamente mais tarde.",
         "dialog_description_default": "Ocorreu um erro.",
         "download_media": "Falha ao transferir o media de origem, não foi encontrado nenhum url de origem",
         "edit_history_unsupported": "O teu servidor doméstico não parece suportar esta funcionalidade.",
         "failed_copy": "Falha ao copiar",
         "hs_blocked": "Este servidor doméstico foi bloqueado pelo seu administrador.",
+        "invalid_configuration_mixed_server": "Configuração inválida: não pode ser especificado um default_hs_url juntamente com default_server_name ou default_server_config",
+        "invalid_configuration_no_server": "Configuração inválida: não especificaste o servidor predefinido.",
+        "invalid_json": "A tua configuração do Element contém um JSON inválido. Corrige o problema e volta a carregar a página.",
+        "invalid_json_detail": "A mensagem do analisador é: %(message)s",
+        "invalid_json_generic": "JSON inválido",
         "mau": "Este servidor doméstico atingiu o seu limite mensal de utilizadores activos.",
+        "misconfigured": "O teu Element está mal configurado",
         "mixed_content": "Não consigo conectar ao servidor padrão através de HTTP quando uma URL HTTPS está na barra de endereços do seu navegador. Use HTTPS ou então <a>habilite scripts não seguros no seu navegador</a>.",
         "non_urgent_echo_failure_toast": "O teu servidor não está a responder a alguns pedidos <a></a> .",
         "resource_limits": "Este servidor doméstico excedeu um dos seus limites de recursos.",
@@ -1040,18 +1089,14 @@
         "for_desktop": "O teu disco pode estar cheio. Limpa algum espaço e volta a carregar.",
         "for_web": "Se limpaste os dados de navegação, esta mensagem é esperada. %(brand)s também pode estar aberto noutro separador ou o teu disco está cheio. Limpa algum espaço e volta a carregar"
     },
-    "error_database_closed_title": "Base de dados fechada inesperadamente",
+    "error_database_closed_title": "%(brand)s parou de funcionar",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Não é possível copiar uma ligação à sala para a área de transferência.",
             "title": "Não é possível copiar o link da sala"
         },
         "error_loading_user_profile": "Não foi possível carregar o perfil do utilizador",
-        "forget_room_failed": "Falha ao esquecer a sala %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "O servidor pode estar indisponível, sobrecarregado, ou a busca ultrapassou o tempo limite :(",
-            "title": "Busca falhou"
-        }
+        "forget_room_failed": "Falha ao esquecer a sala %(errCode)s"
     },
     "error_user_not_logged_in": "Utilizador não tem sessão iniciada",
     "event_preview": {
@@ -1076,7 +1121,15 @@
             "you": "Reagiste %(reaction)s a %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Áudio",
+            "file": "Ficheiro",
+            "image": "Imagem",
+            "poll": "Sondagem",
+            "video": "Vídeo"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Exportação cancelada",
@@ -1168,6 +1221,7 @@
         "change": "Altera o servidor de identidade",
         "change_prompt": "Desligar do servidor de identidade <current /> e ligar a <new /> em vez disso?",
         "change_server_prompt": "Se não pretenderes utilizar <server /> para descobrir e ser descoberto pelos contactos existentes que conheces, introduz outro servidor de identidade abaixo.",
+        "changed": "O teu servidor de identidade foi alterado",
         "checking": "A verificar o servidor",
         "description_connected": "Atualmente, estás a utilizar <server></server> para descobrires e seres descoberto pelos contactos existentes que conheces. Podes alterar o teu servidor de identidade abaixo.",
         "description_disconnected": "Atualmente, não estás a utilizar um servidor de identidade. Para descobrires e seres descoberto pelos contactos existentes que conheces, adiciona um abaixo.",
@@ -1198,6 +1252,22 @@
         "one": "Em %(spaceName)s e noutro espaço.",
         "other": "Em %(spaceName)s e %(count)s outros espaços."
     },
+    "incompatible_browser": {
+        "continue": "Continuar assim mesmo",
+        "description": "%(brand)s usa alguns recursos do navegador que não estão disponíveis no seu navegador atual. %(detail)s",
+        "detail_can_continue": "Se continuares, algumas funcionalidades podem deixar de funcionar e existe o risco de perderes dados no futuro.",
+        "detail_no_continue": "Tenta atualizar este browser se não estiveres a utilizar a versão mais recente e tenta novamente.",
+        "learn_more": "Saber mais",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Para uma melhor experiência, utiliza <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge>, ou <Safari>Safari</Safari>.",
+        "title": "%(brand)s não suporta este browser",
+        "use_desktop_heading": "Utiliza antes o %(brand)s Desktop",
+        "use_mobile_heading": "Utiliza antes o %(brand)s no telemóvel",
+        "use_mobile_heading_after_desktop": "Ou utiliza a nossa aplicação móvel",
+        "windows_64bit": "Windows (64 bits)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
+    },
     "info_tooltip_title": "Informação",
     "integration_manager": {
         "connecting": "Conectando ao gerenciador de integração…",
@@ -1205,6 +1275,7 @@
         "error_connecting_heading": "Não é possível conectar-se ao gerenciador de integração",
         "explainer": "Os gestores de integração recebem dados de configuração e podem modificar widgets, enviar convites para salas e definir níveis de potência em teu nome.",
         "manage_title": "Gere as integrações",
+        "toggle_label": "Ativar o gestor de integração",
         "use_im": "Utiliza um gestor de integração para gerir bots, widgets e pacotes de autocolantes.",
         "use_im_default": "Utiliza um gestor de integração <b>(%(serverName)s)</b> para gerir bots, widgets e pacotes de autocolantes."
     },
@@ -1372,8 +1443,11 @@
         "dynamic_room_predecessors": "Antecessores de sala dinâmica",
         "dynamic_room_predecessors_description": "Ativar MSC3946 (para suportar arquivos de salas que chegam tarde)",
         "element_call_video_rooms": "Salas de Chamada de vídeo Element",
+        "exclude_insecure_devices": "Exclui dispositivos inseguros ao enviar/receber mensagens",
+        "exclude_insecure_devices_description": "Quando este modo está ativado, as mensagens encriptadas não serão partilhadas com dispositivos não verificados e as mensagens de dispositivos não verificados serão apresentadas como um erro. Tem em atenção que, se activares este modo, poderás não conseguir comunicar com utilizadores que não tenham verificado os seus dispositivos.",
         "experimental_description": "Estás a sentir-te experimental? Experimenta as nossas ideias mais recentes em desenvolvimento. Estas funcionalidades não estão finalizadas; podem ser instáveis, podem ser alteradas ou podem ser completamente abandonadas. <a>Sabe mais em</a>.",
         "experimental_section": "Pré-visualizações antecipadas",
+        "extended_profiles_msc_support": "Requer que o teu servidor suporte MSC4133",
         "feature_disable_call_per_sender_encryption": "Desativar a cifragem por remetente na Element Call",
         "feature_wysiwyg_composer_description": "Utiliza texto rico em vez de Markdown no compositor de mensagens.",
         "group_calls": "Nova experiência de chamada de grupo",
@@ -1403,6 +1477,7 @@
         "location_share_live_description": "Implementação temporária. As localizações permanecem no histórico da sala.",
         "mjolnir": "Novas formas de ignorar pessoas",
         "msc3531_hide_messages_pending_moderation": "Permitir que os moderadores ocultem mensagens pendentes de moderação.",
+        "new_room_list": "Ativar nova lista de salas",
         "notification_settings": "Novas Definições de Notificação",
         "notification_settings_beta_caption": "Apresentamos uma forma mais simples de alterar as tuas definições de notificação. Personaliza o teu %(brand)s, tal como gostas.",
         "notification_settings_beta_title": "Definições de Notificação",
@@ -1526,8 +1601,14 @@
         "toggle_attribution": "Alterna a atribuição"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s Membro",
+            "other": "%(count)s Membros"
+        },
         "filter_placeholder": "Filtrar integrantes da sala",
         "invite_button_no_perms_tooltip": "Não tens permissão para convidar utilizadores",
+        "invited_label": "Convidado",
+        "no_matches": "Sem correspondências",
         "power_label": "%(userName)s (nível de permissão %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Membros da sala",
@@ -1637,6 +1718,8 @@
         "moderator": "Moderador/a",
         "restricted": "Restrito"
     },
+    "powered_by_matrix": "Fornecido por Matrix",
+    "powered_by_matrix_with_logo": "Conversa e colaboração descentralizadas e encriptadas com a tecnologia $matrixLogo",
     "presence": {
         "away": "Ausente",
         "busy": "Ocupado",
@@ -1668,11 +1751,6 @@
         "ongoing": "A remover...",
         "reason_label": "Motivo (opcional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Você tem certeza que deseja rejeitar este convite?",
-        "failed": "Falha ao tentar rejeitar convite",
-        "title": "Rejeitar convite"
-    },
     "report_content": {
         "description": "Ao reportar esta mensagem, envias o seu \"ID de evento\" único para o administrador do teu servidor. Se as mensagens nesta sala estiverem cifradas, o administrador não poderá ler o texto ou ver quaisquer ficheiros ou imagens.",
         "disagree": "Não concordo",
@@ -1698,29 +1776,30 @@
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Falha ao descriptografar%(failedCount)s sessões!",
         "count_of_successfully_restored_keys": "%(sessionCount)s Chaves restauradas com sucesso",
-        "enter_key_description": "Acede ao teu histórico de mensagens seguras e configura o envio seguro de mensagens introduzindo a tua chave de segurança.",
-        "enter_key_title": "Introduzir chave de segurança",
+        "enter_key_description": "Acede ao teu histórico de mensagens seguras e configura o envio de mensagens seguras introduzindo a tua chave de recuperação.",
+        "enter_key_title": "Introduzir a chave de recuperação",
         "enter_phrase_description": "Acede ao teu histórico de mensagens seguras e configura o envio de mensagens seguras introduzindo a tua frase de segurança.",
         "enter_phrase_title": "Introduzir frase de segurança",
         "incorrect_security_phrase_dialog": "Não foi possível desencriptar a cópia de segurança com esta frase de segurança: verifica se introduziste a frase de segurança correcta.",
         "incorrect_security_phrase_title": "Frase de segurança incorreta",
         "key_backup_warning": "<b>Aviso</b>: só deves configurar a cópia de segurança das chaves a partir de um computador de confiança.",
         "key_fetch_in_progress": "A obter chaves do servidor…",
-        "key_forgotten_text": "Se te esqueceste da tua chave de segurança, podes <button>configurar novas opções de recuperação</button>",
-        "key_is_invalid": "Não é uma chave de segurança válida",
-        "key_is_valid": "Isto parece ser uma chave de segurança válida!",
+        "key_forgotten_text": "Se te esqueceste da tua chave de recuperação, podes <button>configurar novas opções de recuperação</button>",
+        "key_is_invalid": "Não é uma chave de recuperação válida",
+        "key_is_valid": "Esta parece ser uma chave de recuperação válida!",
         "keys_restored_title": "Chaves restauradas",
         "load_error_content": "Não foi possível carregar o estado da cópia de segurança",
         "load_keys_progress": "%(completed)s de %(total)s chaves restauradas",
         "no_backup_error": "Nenhuma cópia de segurança encontrada!",
-        "phrase_forgotten_text": "Se te esqueceste da tua Frase de Segurança, podes <button1>utilizar a tua Chave de Segurança</button1> ou <button2>definir novas opções de recuperação</button2>",
-        "recovery_key_mismatch_description": "A cópia de segurança não pôde ser desencriptada com esta chave de segurança: verifica se introduziste a chave de segurança correcta.",
-        "recovery_key_mismatch_title": "Incompatibilidade da chave de segurança",
+        "phrase_forgotten_text": "Se te esqueceste da tua Frase de Segurança, podes <button1>utilizar a tua Chave de Recuperação</button1> ou <button2>definir novas opções de recuperação</button2>",
+        "recovery_key_mismatch_description": "Não foi possível desencriptar a cópia de segurança com esta chave de recuperação: verifica se introduziste a chave de recuperação correta.",
+        "recovery_key_mismatch_title": "Chave de recuperação não coincide",
         "restore_failed_error": "Não foi possível restaurar a cópia de segurança"
     },
     "right_panel": {
         "add_integrations": "Adiciona widgets, pontes e bots",
         "add_topic": "Adicionar descrição",
+        "extensions_button": "Extensões",
         "extensions_empty_description": "Seleciona \"%(addIntegrations)s\" para procurar e adicionar extensões a esta sala",
         "extensions_empty_title": "Aumenta a produtividade com mais ferramentas, widgets e bots",
         "files_button": "Ficheiros",
@@ -1736,9 +1815,18 @@
                 "other": "Só podes fixar até %(count)s widgets"
             },
             "menu": "Abrir o menu",
+            "release_announcement": {
+                "close": "Ok",
+                "description": "Encontra todas as mensagens fixadas aqui. Passa o cursor sobre qualquer mensagem e seleciona \"Fixar\" para a adicionar.",
+                "title": "Todas as novas mensagens afixadas"
+            },
+            "reply_thread": "Responde a uma <link> mensagem do tópico</link>",
             "unpin_all": {
-                "button": "Desafixa todas as mensagens"
-            }
+                "button": "Desafixa todas as mensagens",
+                "content": "Certifica-te de que queres mesmo remover todas as mensagens fixadas. Esta ação não pode ser anulada.",
+                "title": "Desfixar todas as mensagens?"
+            },
+            "view": "Ver na linha do tempo"
         },
         "pinned_messages_button": "Fixado",
         "poll": {
@@ -1843,6 +1931,7 @@
             },
             "room_is_public": "Esta sala é pública"
         },
+        "header_avatar_open_settings_label": "Abre as definições da sala",
         "header_face_pile_tooltip": "Alternar lista de membros",
         "header_untrusted_label": "Não confiável",
         "inaccessible": "De momento, esta sala ou espaço não está acessível.",
@@ -1867,10 +1956,9 @@
             "you_created": "Tu criaste esta sala."
         },
         "invite_email_mismatch_suggestion": "Partilha este e-mail nas Definições para receberes convites diretamente em %(brand)s.",
-        "invite_reject_ignore": "Rejeitar e ignorar o utilizador",
         "invite_sent_to_email": "Este convite foi enviado para %(email)s",
         "invite_sent_to_email_room": "Este convite para %(roomName)s foi enviado para %(email)s",
-        "invite_subtitle": "<userName/> convidou-o",
+        "invite_subtitle": "Convidado por <userName/>",
         "invite_this_room": "Convidar para esta sala",
         "invite_title": "Queres juntar-te a %(roomName)s?",
         "inviter_unknown": "Desconhecido",
@@ -1913,6 +2001,14 @@
         "not_found_title": "Esta sala ou espaço não existe.",
         "not_found_title_name": "%(roomName)s não existe.",
         "peek_join_prompt": "Estás a visualizar %(roomName)s. Queres juntar-te a ele?",
+        "pinned_message_badge": "Mensagem fixada",
+        "pinned_message_banner": {
+            "button_close_list": "Fechar lista",
+            "button_view_all": "Ver tudo",
+            "description": "Esta sala tem mensagens afixadas. Clica para as veres.",
+            "go_to_message": "Visualiza a mensagem fixada na linha do tempo.",
+            "title": "<bold>%(index)s de %(length)s</bold> Mensagens fixadas"
+        },
         "read_topic": "Clica para ler o tópico",
         "rejecting": "Rejeitando o convite...",
         "rejoin_button": "Junta-te novamente",
@@ -1963,20 +2059,54 @@
         "add_space_label": "Adiciona espaço",
         "breadcrumbs_empty": "Nenhuma sala visitada recentemente",
         "breadcrumbs_label": "Salas visitadas recentemente",
+        "empty": {
+            "no_chats": "Ainda sem conversas",
+            "no_chats_description": "Começa a enviar mensagens a alguém ou a crie uma sala",
+            "no_chats_description_no_room_rights": "Começa por enviar uma mensagem a alguém",
+            "no_favourites": "Ainda não tem um conversa favorita",
+            "no_favourites_description": "Pode adicionar uma conversa aos seus favoritos nas definições de conversa",
+            "no_people": "Ainda não tem conversas diretas com ninguém",
+            "no_people_description": "Pode desseleccionar filtros para veres as suas outras conversas",
+            "no_rooms": "Você ainda não está em nenhuma sala",
+            "no_rooms_description": "Pode desmarcar filtros para ver as suas outras conversas",
+            "no_unread": "Parabéns! Não tens nenhuma mensagem por ler",
+            "show_chats": "Mostra todas as conversas"
+        },
         "failed_add_tag": "Falha ao adicionar %(tagName)s à sala",
         "failed_remove_tag": "Não foi possível remover a marcação %(tagName)s desta sala",
         "failed_set_dm_tag": "Falha ao definir a etiqueta de mensagem direta",
+        "filters": {
+            "favourite": "Favoritos",
+            "people": "Pessoas",
+            "rooms": "Salas",
+            "unread": "Não lido"
+        },
         "home_menu_label": "Opções de casa",
         "join_public_room_label": "Participa na sala pública",
         "joining_rooms_status": {
             "one": "Atualmente ingressando%(count)s sala",
             "other": "Atualmente ingressando%(count)s salas"
         },
+        "list_title": "Lista de salas",
+        "more_options": {
+            "copy_link": "Copiar link da sala",
+            "favourited": "Adicionado aos favoritos",
+            "leave_room": "Sair da sala",
+            "low_priority": "Baixa prioridade",
+            "mark_read": "Marcar como lido",
+            "mark_unread": "Marcar como não lido"
+        },
         "notification_options": "Opções de notificação",
+        "open_space_menu": "Abrir menu do espaço",
+        "primary_filters": "Filtros da lista de salas",
         "redacting_messages_status": {
             "one": "Atualmente removendo mensagens na %(count)s sala",
             "other": "Atualmente removendo mensagens em %(count)s salas"
         },
+        "room": {
+            "more_options": "Mais opções",
+            "open_room": "Abrir a sala %(roomName)s"
+        },
         "show_less": "Mostrar menos",
         "show_n_more": {
             "one": "Mostrar %(count)s mais",
@@ -1987,6 +2117,10 @@
         "sort_by_activity": "Atividade",
         "sort_by_alphabet": "A-Z",
         "sort_unread_first": "Mostra primeiro as salas com mensagens não lidas",
+        "space_menu": {
+            "home": "Casa do espaço",
+            "space_settings": "Configurações de espaço"
+        },
         "space_menu_label": "%(spaceName)s menu",
         "sublist_options": "Lista de opções",
         "suggested_rooms_heading": "Salas sugeridas"
@@ -2020,7 +2154,7 @@
             "upgrade_dwarning_ialog_title_public": "Atualizar sala pública",
             "upgrade_warning_dialog_description": "A atualização de uma divisão é uma ação avançada e é normalmente recomendada quando uma divisão está instável devido a erros, funcionalidades em falta ou vulnerabilidades de segurança.",
             "upgrade_warning_dialog_explainer": "<b>Tem em atenção que a atualização fará uma nova versão da sala</b>. Todas as mensagens actuais permanecerão nesta sala arquivada.",
-            "upgrade_warning_dialog_footer": "Actualizarás este quarto de <oldVersion /> para <newVersion />.",
+            "upgrade_warning_dialog_footer": "Esta sala será atualizada de <oldVersion /> para <newVersion />.",
             "upgrade_warning_dialog_invite_label": "Convida automaticamente os membros desta sala para a nova sala",
             "upgrade_warning_dialog_report_bug_prompt": "Normalmente, isto só afecta a forma como a sala é processada no servidor. Se estiveres a ter problemas com o teu %(brand)s, por favor reporta um erro.",
             "upgrade_warning_dialog_report_bug_prompt_link": "Normalmente, isto apenas afecta a forma como a sala é processada no servidor. Se estiveres a ter problemas com o teu %(brand)s, por favor <a>reporta um bug</a>.",
@@ -2058,6 +2192,8 @@
             "error_deleting_alias_description": "Ocorreu um erro ao removeres esse endereço. Pode já não existir ou ocorreu um erro temporário.",
             "error_deleting_alias_description_forbidden": "Não tens autorização para apagar o endereço.",
             "error_deleting_alias_title": "Erro ao remover o endereço",
+            "error_publishing": "Não é possível publicar a sala",
+            "error_publishing_detail": "Houve um erro ao publicar este sala",
             "error_save_space_settings": "Não conseguiste guardar as definições de espaço.",
             "error_updating_alias_description": "Ocorreu um erro ao atualizar os endereços alternativos da sala. Pode não ser permitido pelo servidor ou ocorreu uma falha temporária.",
             "error_updating_canonical_alias_description": "Ocorreu um erro ao atualizar o endereço principal da sala. Pode não ser permitido pelo servidor ou ocorreu uma falha temporária.",
@@ -2158,7 +2294,7 @@
             "encrypted_room_public_confirm_description_1": "<b>Não é recomendável tornar públicas as salas encriptadas.</b> Isso significa que qualquer pessoa pode encontrar e entrar na sala, pelo que qualquer pessoa pode ler as mensagens. Não terás nenhum dos benefícios da encriptação. Encriptar mensagens numa sala pública tornará a receção e o envio de mensagens mais lento.",
             "encrypted_room_public_confirm_description_2": "Para evitar estes problemas, cria uma <a>nova sala pública</a> para a conversa que pretendes ter.",
             "encrypted_room_public_confirm_title": "Tens a certeza de que queres tornar pública esta sala encriptada?",
-            "encryption_forced": "O teu servidor requer que a encriptação seja desactivada.",
+            "encryption_forced": "O servidor requer que a encriptação seja desativada.",
             "encryption_permanent": "Uma vez activada, a encriptação não pode ser desactivada.",
             "error_join_rule_change_title": "Falha ao atualizar as regras de adesão",
             "error_join_rule_change_unknown": "Falha desconhecida",
@@ -2213,7 +2349,7 @@
             "public_without_alias_warning": "Para estabelecer uma ligação a esta sala, adiciona um endereço.",
             "publish_room": "Torna esta sala visível no diretório público de salas.",
             "publish_space": "Torna este espaço visível no diretório de salas públicas.",
-            "strict_encryption": "Nunca envia mensagens encriptadas para sessões não verificadas nesta sala a partir desta sessão",
+            "strict_encryption": "Enviar mensagens apenas a utilizadores verificados.",
             "title": "Segurança e privacidade"
         },
         "title": "Configurações da sala -%(roomName)s",
@@ -2337,6 +2473,71 @@
         "emoji_autocomplete": "Permitir sugestões de Emoji durante a escrita",
         "enable_markdown": "Ativar Markdown",
         "enable_markdown_description": "Inicia as mensagens com <code>/plain</code> para enviar sem marcação.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Os detalhes da sua conta, contactos, preferências e lista de conversação serão mantidos",
+                "breadcrumb_page": "Repor a encriptação",
+                "breadcrumb_second_description": "Perderás qualquer histórico de mensagens que esteja armazenado apenas no servidor",
+                "breadcrumb_third_description": "Terá de verificar novamente todos os seus dispositivos e contactos existentes",
+                "breadcrumb_title": "Tens a certeza de que queres redefinir a tua identidade?",
+                "breadcrumb_title_forgot": "Esqueceu-se da sua chave de recuperação? Terá de repor a sua identidade.",
+                "breadcrumb_warning": "Faz isto apenas se acreditares que a tua conta foi comprometida.",
+                "details_title": "Detalhes da encriptação",
+                "do_not_close_warning": "Não feches esta janela até que a reposição esteja concluída",
+                "export_keys": "Exportar chaves",
+                "import_keys": "Importar chaves",
+                "other_people_device_description": "Por predefinição, nas salas encriptadas, não envies mensagens encriptadas a ninguém até as teres verificado",
+                "other_people_device_label": "Nunca enviar mensagens encriptadas para dispositivos não verificados",
+                "other_people_device_title": "Dispositivos de outras pessoas",
+                "reset_identity": "Redefinir identidade criptográfica",
+                "reset_in_progress": "Reposição em curso...",
+                "session_id": "ID da sessão:",
+                "session_key": "Chave da sessão:",
+                "title": "Avançadas"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Excluir armazenamento de chaves",
+                "confirm": "Excluir armazenamento de chaves",
+                "description": "A exclusão do armazenamento de chaves removerá sua identidade criptográfica e chaves de mensagem do servidor e desativará os seguintes recursos de segurança:",
+                "list_first": "Você não terá histórico de mensagens criptografadas em novos dispositivos",
+                "list_second": "Irá perder o acesso às suas mensagens encriptadas se se desconectar de %(brand)s em qualquer lugar",
+                "title": "Tem certeza de que deseja desativar o armazenamento de chaves e excluí-lo?"
+            },
+            "device_not_verified_button": "Verificar este dispositivo",
+            "device_not_verified_description": "Você precisa verificar este dispositivo para visualizar suas configurações de criptografia.",
+            "device_not_verified_title": "Dispositivo não verificado",
+            "dialog_title": "<strong>Configurações:</strong> Encriptação",
+            "key_storage": {
+                "allow_key_storage": "Permitir armazenamento de chaves",
+                "description": "Armazene sua identidade criptográfica e chaves de mensagem com segurança no servidor. Isso permitirá que você visualize seu histórico de mensagens em quaisquer novos dispositivos. <a>Saiba mais</a>",
+                "title": "Armazenamento de chaves"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Confirmar nova chave de recuperação",
+                "change_recovery_confirm_description": "Introduz a tua nova chave de recuperação abaixo para terminar. A tua chave antiga deixará de funcionar.",
+                "change_recovery_confirm_title": "Introduz a tua nova chave de recuperação",
+                "change_recovery_key": "Altera a chave de recuperação",
+                "change_recovery_key_description": "Anota esta nova chave de recuperação num local seguro. Em seguida, clica em Continuar para confirmar a alteração.",
+                "change_recovery_key_title": "Alterar a chave de recuperação?",
+                "description": "Recupera a tua identidade criptográfica e o histórico de mensagens com uma chave de recuperação, caso tenhas perdido todos os teus dispositivos existentes.",
+                "enter_key_error": "A chave de recuperação inserida não está correta.",
+                "enter_recovery_key": "Insira a chave de recuperação",
+                "forgot_recovery_key": "Esqueceu-se da chave de recuperação?",
+                "key_storage_warning": "O teu armazenamento de chaves não está sincronizado. Clica num dos botões abaixo para resolveres o problema.",
+                "save_key_description": "Não partilhes isto com ninguém!",
+                "save_key_title": "Chave de recuperação",
+                "set_up_recovery": "Configurar a recuperação",
+                "set_up_recovery_confirm_button": "Concluir configuração",
+                "set_up_recovery_confirm_description": "Digite a chave de recuperação mostrada na tela anterior para concluir a configuração da recuperação.",
+                "set_up_recovery_confirm_title": "Introduza a sua chave de recuperação para confirmar",
+                "set_up_recovery_description": "O armazenamento da sua chave está protegido por uma chave de recuperação. Se precisar de uma nova chave de recuperação após a configuração, pode recriá-la selecionando '%(changeRecoveryKeyButton)s'.",
+                "set_up_recovery_save_key_description": "Anote essa chave de recuperação em algum lugar seguro, como um gerenciador de senhas, uma nota criptografada ou um cofre físico.",
+                "set_up_recovery_save_key_title": "Guarda a tua chave de recuperação num local seguro",
+                "set_up_recovery_secondary_description": "Depois de clicares em continuar, iremos gerar uma chave de recuperação para ti.",
+                "title": "Recuperação"
+            },
+            "title": "Encriptação"
+        },
         "general": {
             "account_management_section": "Gestão de conta",
             "account_section": "Conta",
@@ -2372,7 +2573,7 @@
             "deactivate_confirm_erase_label": "Esconde as minhas mensagens dos novos aderentes",
             "deactivate_section": "Desativar conta",
             "deactivate_warning": "A desativação da tua conta é uma ação permanente - tem cuidado!",
-            "discovery_email_empty": "As opções de descoberta vão aparecer assim que adicione um e-mail acima.",
+            "discovery_email_empty": "As opções de descoberta serão apresentadas quando adicionares um e-mail.",
             "discovery_email_verification_instructions": "Verifica o link na tua caixa de entrada",
             "discovery_msisdn_empty": "As opções de descoberta aparecerão quando tiveres adicionado um número de telefone acima.",
             "discovery_needs_terms": "Concorda com os Termos de Serviço do servidor de identidade (%(serverName)s) para que possas ser descoberto através do teu endereço de e-mail ou número de telefone.",
@@ -2422,7 +2623,6 @@
             "unable_to_load_msisdns": "Não é possível carregar números de telefone",
             "username": "Nome de utilizador"
         },
-        "image_thumbnails": "Mostrar pré-visualizações/miniaturas de imagens",
         "inline_url_previews_default": "Ativar pré-visualizações de URL embutidas por predefinição",
         "inline_url_previews_room": "Ativar pré-visualizações de URL por defeito para os participantes nesta sala",
         "inline_url_previews_room_account": "Ativar pré-visualizações URL para esta sala (só te afeta a ti)",
@@ -2444,21 +2644,21 @@
                 "enter_phrase_description": "Introduz uma frase de segurança que só tu conheças, pois é utilizada para proteger os teus dados. Para estares seguro, não deves reutilizar a palavra-passe da tua conta.",
                 "enter_phrase_title": "Introduzir uma frase de segurança",
                 "enter_phrase_to_confirm": "Introduz a tua frase de segurança uma segunda vez para a confirmar.",
-                "generate_security_key_description": "Iremos gerar uma chave de segurança para guardares num local seguro, como um gestor de palavras-passe ou um cofre.",
-                "generate_security_key_title": "Gera uma chave de segurança",
+                "generate_security_key_description": "Iremos gerar uma chave de recuperação para guardares num local seguro, como um gestor de palavras-passe ou um cofre.",
+                "generate_security_key_title": "Gerar uma chave de recuperação",
                 "pass_phrase_match_failed": "Isso não combina.",
                 "pass_phrase_match_success": "Isso combina!",
                 "phrase_strong_enough": "Ótimo! Esta frase de segurança parece suficientemente forte.",
                 "secret_storage_query_failure": "Não é possível consultar o estado do armazenamento secreto",
-                "security_key_safety_reminder": "Guarda a tua chave de segurança num local seguro, como um gestor de palavras-passe ou um cofre, uma vez que é utilizada para proteger os teus dados encriptados.",
+                "security_key_safety_reminder": "Guarda a tua chave de recuperação num local seguro, como um gestor de palavras-passe ou um cofre, uma vez que é utilizada para proteger os teus dados encriptados.",
                 "set_phrase_again": "Volta atrás para o definir novamente.",
                 "settings_reminder": "Também podes configurar a Cópia de segurança segura e gerir as tuas chaves nas Definições.",
                 "title_confirm_phrase": "Confirma a frase de segurança",
-                "title_save_key": "Guarda a tua chave de segurança",
+                "title_save_key": "Guarda a tua chave de recuperação",
                 "title_set_phrase": "Define uma frase de segurança",
                 "unable_to_setup": "Não é possível configurar o armazenamento secreto",
                 "use_different_passphrase": "Utiliza uma frase-chave diferente?",
-                "use_phrase_only_you_know": "Utiliza uma frase secreta que só tu sabes e, opcionalmente, guarda uma chave de segurança para utilizar como cópia de segurança."
+                "use_phrase_only_you_know": "Utiliza uma frase secreta que só tu sabes e, opcionalmente, guarda uma chave de recuperação para utilizar como cópia de segurança."
             }
         },
         "key_export_import": {
@@ -2548,6 +2748,7 @@
             "code_blocks_heading": "Blocos de código",
             "compact_modern": "Utiliza um layout \"moderno\" mais compacto",
             "composer_heading": "Compositor",
+            "default_timezone": "Predefinição do browser (%(timezone)s)",
             "dialog_title": "<strong>Definições:</strong> Preferências",
             "enable_hardware_acceleration": "Ativar a aceleração de hardware",
             "enable_tray_icon": "Mostra o ícone do tabuleiro e minimiza a janela ao fechar",
@@ -2555,6 +2756,7 @@
             "keyboard_view_shortcuts_button": "Para ver todos os atalhos de teclado, <a>clica aqui</a>.",
             "media_heading": "Imagens, GIFs e vídeos",
             "presence_description": "Partilha a tua atividade e o teu estado com outros.",
+            "publish_timezone": "Publica o fuso horário no perfil público",
             "rm_lifetime": "Vida útil do marcador de leitura (ms)",
             "rm_lifetime_offscreen": "Vida útil do marcador de leitura fora do ecrã (ms)",
             "room_directory_heading": "Diretório de salas",
@@ -2562,62 +2764,26 @@
             "show_avatars_pills": "Mostra os avatares nas menções de utilizadores, salas e eventos",
             "show_polls_button": "Mostrar botão de sondagens",
             "surround_text": "Rodeia o texto selecionado ao escrever caracteres especiais",
-            "time_heading": "Mostra a hora"
+            "time_heading": "Mostra a hora",
+            "user_timezone": "Define o fuso horário"
         },
         "prompt_invite": "Avisa antes de enviar convites para IDs de matrix potencialmente inválidos",
         "replace_plain_emoji": "Substituir Emoji de texto automaticamente",
         "security": {
-            "4s_public_key_in_account_data": "nos dados da conta",
-            "4s_public_key_status": "Chave pública de armazenamento secreto:",
             "analytics_description": "Partilha dados anónimos para nos ajudar a identificar problemas. Nada de pessoal. Sem terceiros.",
-            "backup_key_cached_status": "Chave de backup armazenada em cache:",
-            "backup_key_stored_status": "Chave de backup armazenada:",
-            "backup_key_unexpected_type": "tipo inesperado",
-            "backup_key_well_formed": "bem formado",
-            "backup_keys_description": "Faz uma cópia de segurança das tuas chaves de encriptação com os dados da tua conta para o caso de perderes o acesso às tuas sessões. As tuas chaves serão protegidas com uma chave de segurança única.",
             "bulk_options_accept_all_invites": "Aceita todos os convites de %(invitedRooms)s ",
             "bulk_options_reject_all_invites": "Rejeitar todos os %(invitedRooms)s convites",
             "bulk_options_section": "Opções em massa",
-            "cross_signing_cached": "armazenado em cache localmente",
-            "cross_signing_homeserver_support": "Suporte a recursos de servidor doméstico:",
-            "cross_signing_homeserver_support_exists": "existe",
-            "cross_signing_in_4s": "em armazenamento secreto",
-            "cross_signing_in_memory": "na memória",
-            "cross_signing_master_private_Key": "Chave mestra privada:",
-            "cross_signing_not_cached": "não encontrado localmente",
-            "cross_signing_not_found": "não encontrado",
-            "cross_signing_not_in_4s": "não encontrado no armazenamento",
-            "cross_signing_not_stored": "não armazenado",
-            "cross_signing_private_keys": "Assinatura cruzada de chaves privadas:",
-            "cross_signing_public_keys": "Assinatura cruzada de chaves públicas:",
-            "cross_signing_self_signing_private_key": "Chave privada com assinatura automática:",
-            "cross_signing_user_signing_private_key": "Chave privada de assinatura do utilizador:",
-            "cryptography_section": "Criptografia",
             "dehydrated_device_description": "A funcionalidade de dispositivo offline permite-te receber mensagens encriptadas mesmo quando não tens sessão iniciada em nenhum dispositivo",
             "dehydrated_device_enabled": "Dispositivo offline ativado",
-            "delete_backup": "Eliminar cópia de segurança",
-            "delete_backup_confirm_description": "Tens a certeza? Perderás as tuas mensagens encriptadas se as tuas chaves não forem guardadas corretamente.",
             "dialog_title": "<strong>Definições:</strong> Segurança e privacidade",
             "e2ee_default_disabled_warning": "O administrador do teu servidor desactivou a encriptação de ponta a ponta por defeito nas salas privadas e nas Mensagens directas.",
             "enable_message_search": "Ativar a pesquisa de mensagens em salas encriptadas",
             "encryption_section": "Encriptação",
-            "error_loading_key_backup_status": "Não é possível carregar o estado da cópia de segurança da chave",
-            "export_megolm_keys": "Exportar chaves ponta-a-ponta da sala",
             "ignore_users_empty": "Não tens utilizadores ignorados.",
             "ignore_users_section": "Utilizadores ignorados",
-            "import_megolm_keys": "Importar chave de criptografia ponta-a-ponta (E2E) da sala",
-            "key_backup_active": "Esta sessão está a realizar as cópias de segurança das tuas chaves.",
-            "key_backup_active_version": "Versão de cópia de segurança ativa:",
-            "key_backup_active_version_none": "Nenhum",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_can_be_restored": "Esta cópia de segurança pode ser restaurada nesta sessão",
-            "key_backup_complete": "Cópia de segurança de todas as chaves realizada",
             "key_backup_connect": "Liga esta sessão à cópia de segurança das chaves",
-            "key_backup_connect_prompt": "Liga esta sessão à cópia de segurança de chaves antes de terminares a sessão para evitar perderes quaisquer chaves que possam estar apenas nesta sessão.",
-            "key_backup_in_progress": "Fazendo backup das chaves de %(sessionsRemaining)s...",
-            "key_backup_inactive": "Esta sessão não <b>está a realizar as cópias de segurança das tuas chaves</b>, mas tens uma cópia de segurança existente a partir da qual podes restaurar e adicionar mais.",
-            "key_backup_inactive_warning": "As tuas chaves <b>não estão a ser copiadas em segurança a partir desta sessão</b>.",
-            "key_backup_latest_version": "Versão mais recente da cópia de segurança no servidor:",
             "message_search_disable_warning": "Se estiver desactivada, as mensagens de salas encriptadas não aparecerão nos resultados da pesquisa.",
             "message_search_disabled": "Armazena em cache de forma segura as mensagens encriptadas localmente para que apareçam nos resultados de pesquisa.",
             "message_search_enabled": {
@@ -2637,14 +2803,8 @@
             "message_search_unsupported": "%(brand)s não tem alguns componentes necessários para guardar mensagens encriptadas localmente de forma segura. Se quiseres experimentar esta funcionalidade, constrói um ambiente de trabalho %(brand)s personalizado com <nativeLink>componentes de pesquisa adicionados</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s não é possível armazenar mensagens encriptadas em cache com segurança localmente durante a execução num navegador da Web. Usa <desktopLink>%(brand)s Desktop</desktopLink> para que as mensagens encriptadas apareçam nos resultados da pesquisa.",
             "record_session_details": "Grava o nome do cliente, a versão e o URL para reconhecer mais facilmente as sessões no gestor de sessões",
-            "restore_key_backup": "Restaurar a partir da cópia de segurança",
-            "secret_storage_not_ready": "não está pronto",
-            "secret_storage_ready": "pronto",
-            "secret_storage_status": "Armazenamento secreto:",
             "send_analytics": "Envia dados analíticos",
-            "session_id": "Identificação de sessão:",
-            "session_key": "Chave da sessão:",
-            "strict_encryption": "Nunca envies mensagens encriptadas para sessões não verificadas a partir desta sessão"
+            "strict_encryption": "Enviar mensagens apenas a utilizadores verificados"
         },
         "send_read_receipts": "Enviar recibos lidos",
         "send_read_receipts_unsupported": "O teu servidor não suporta a desativação do envio de recibos de leitura.",
@@ -2692,6 +2852,7 @@
             "inactive_sessions_list_description": "Considera a possibilidade de sair de sessões antigas (%(inactiveAgeDays)s dias ou mais) que já não utilizas.",
             "ip": "Endereço IP",
             "last_activity": "Última atividade",
+            "manage": "Gerir esta sessão",
             "mobile_session": "Sessão móvel",
             "n_sessions_selected": {
                 "one": "%(count)s sessão selecionada",
@@ -2718,6 +2879,7 @@
             "sign_in_with_qr": "Ligar novo dispositivo",
             "sign_in_with_qr_button": "Mostrar código QR",
             "sign_in_with_qr_description": "Utiliza um código QR para iniciar sessão noutro dispositivo e configurar as mensagens seguras.",
+            "sign_in_with_qr_unsupported": "Não suportado pelo teu fornecedor de conta",
             "sign_out": "Sair desta sessão",
             "sign_out_all_other_sessions": "Sair de todas as outras sessões (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2808,6 +2970,7 @@
         "warning": "<w>AVISO:</w><description/>"
     },
     "share": {
+        "link_copied": "Link copiado",
         "permalink_message": "Link para a mensagem selecionada",
         "permalink_most_recent": "Link para a mensagem mais recente",
         "share_call": "Link de convite para conferência",
@@ -2883,8 +3046,6 @@
         "topic": "Obtém ou define o tópico da sala",
         "topic_none": "Esta sala não tem tópico.",
         "topic_room_error": "Falha ao obter o tópico da sala: Não foi possível encontrar a sala (%(roomId)s",
-        "tovirtual": "Muda para a sala virtual desta sala, se houver uma",
-        "tovirtual_not_found": "Não há sala virtual para esta sala",
         "unban": "Cancela o utilizador com um determinado ID",
         "unflip": "Pré-anexa ┬──┬ ノ( ゜-゜ノ) a uma mensagem de texto simples",
         "unholdcall": "Tira a chamada da sala atual da espera",
@@ -2902,6 +3063,7 @@
         "view": "Visualiza a sala com o endereço indicado",
         "whois": "Apresenta informações sobre um utilizador"
     },
+    "sliding_sync_legacy_no_longer_supported": "A Sliding Sync Legacy não é mais suportada: faça logout e login novamente para ativar a nova opção de Sliding Sync",
     "space": {
         "add_existing_room_space": {
             "create": "Queres acrescentar uma nova sala em vez disso?",
@@ -3006,7 +3168,6 @@
         "heading_without_query": "Pesquisar por",
         "join_button_text": "Aderir %(roomAddress)s",
         "keyboard_scroll_hint": "Utiliza <arrows/> para te deslocares",
-        "message_search_section_title": "Outras pesquisas",
         "other_rooms_in_space": "Outras salas em %(spaceName)s",
         "public_rooms_label": "Salas públicas",
         "public_spaces_label": "Espaços públicos",
@@ -3016,7 +3177,6 @@
         "result_may_be_hidden_privacy_warning": "Alguns resultados podem estar ocultos por motivos de privacidade",
         "result_may_be_hidden_warning": "Alguns resultados podem estar ocultos",
         "search_dialog": "Diálogo de pesquisa",
-        "search_messages_hint": "Para procurar mensagens, procura este ícone na parte superior de uma sala <icon/>",
         "spaces_title": "Espaços em que te encontras",
         "start_group_chat_button": "Inicia uma conversa de grupo"
     },
@@ -3065,9 +3225,7 @@
     "threads_activity_centre": {
         "header": "Atividade de tópicos",
         "no_rooms_with_threads_notifs": "Ainda não tens salas com notificações de tópicos.",
-        "no_rooms_with_unread_threads": "Ainda não tens salas com tópicos não lidos.",
-        "release_announcement_description": "As notificações de tópicos foram movidas, encontre-as aqui a partir de agora.",
-        "release_announcement_header": "Centro de Actividades Tópicos"
+        "no_rooms_with_unread_threads": "Ainda não tens salas com tópicos não lidos."
     },
     "time": {
         "about_day_ago": "há cerca de um dia",
@@ -3115,6 +3273,8 @@
             "historical_event_no_key_backup": "As mensagens históricas não estão disponíveis neste dispositivo",
             "historical_event_unverified_device": "Tens de verificar este dispositivo para acederes às mensagens históricas",
             "historical_event_user_not_joined": "Não tens acesso a esta mensagem",
+            "sender_identity_previously_verified": "A identidade verificada do remetente foi alterada",
+            "sender_unsigned_device": "Enviado de um dispositivo inseguro.",
             "unable_to_decrypt": "Não é possível desencriptar a mensagem"
         },
         "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
@@ -3122,6 +3282,7 @@
         "download_action_downloading": "A transferir…",
         "download_failed": "A transferência falhou",
         "download_failed_description": "Ocorreu um erro ao transferir este ficheiro",
+        "e2e_state": "Estado da encriptação de ponta a ponta",
         "edits": {
             "tooltip_label": "Editado em %(date)s. Clica para ver as edições.",
             "tooltip_sub": "Clica para ver as edições",
@@ -3175,7 +3336,7 @@
         },
         "m.file": {
             "error_decrypting": "Erro ao descriptografar o anexo",
-            "error_invalid": "Arquivo inválido %(extra)s"
+            "error_invalid": "Ficheiro inválido"
         },
         "m.image": {
             "error": "Não é possível mostrar a imagem devido a um erro",
@@ -3276,6 +3437,7 @@
             "left_reason": "%(targetName)s saiu da sala:%(reason)s",
             "no_change": "%(senderName)s não fez nenhuma alteração",
             "reject_invite": "%(targetName)s rejeitou o convite",
+            "reject_invite_reason": "%(targetName)s rejeitou o convite: %(reason)s",
             "remove_avatar": "%(senderName)s removeu a sua fotografia de perfil",
             "remove_name": "%(senderName)s removeu o seu nome de apresentação (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s colocou uma foto de perfil",
@@ -3311,7 +3473,10 @@
             "sent": "%(senderName)s enviou um convite para %(targetDisplayName)s entrar na sala."
         },
         "m.room.tombstone": "%(senderDisplayName)s atualizou a sala.",
-        "m.room.topic": "%(senderDisplayName)s mudou o tópico para \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s mudou o tópico para \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s removeu o tópico."
+        },
         "m.sticker": "%(senderDisplayName)s enviou um sticker.",
         "m.video": {
             "error_decrypting": "Erro ao descriptografar o vídeo"
@@ -3585,6 +3750,7 @@
         "error_files_too_large": "Estes ficheiros são <b>demasiado grandes</b> para serem carregados. O limite de tamanho do ficheiro é %(limit)s.",
         "error_some_files_too_large": "Alguns ficheiros são <b>demasiado grandes</b> para serem carregados. O limite de tamanho dos ficheiros é %(limit)s.",
         "error_title": "Erro de carregamento",
+        "not_image": "O ficheiro que escolheu não é um ficheiro de imagem válido.",
         "title": "Carregar ficheiros",
         "title_progress": "Carrega ficheiros (%(current)s de %(total)s)",
         "upload_all_button": "Carregar tudo",
@@ -3600,18 +3766,9 @@
         "ban_room_confirm_title": "Banir de %(roomName)s",
         "ban_space_everything": "Baní-los de tudo que eu estiver autorizado",
         "ban_space_specific": "Bani-los de coisas específicas que estou autorizado",
-        "count_of_sessions": {
-            "one": "%(count)s sessão",
-            "other": "%(count)s sessões"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 sessão verificada",
-            "other": "%(count)s sessões verificadas"
-        },
         "deactivate_confirm_action": "Desativar utilizador",
         "deactivate_confirm_description": "Se desactivares este utilizador, ele sairá da tua conta e não poderá voltar a entrar. Além disso, sai de todas as salas em que se encontra. Esta ação não pode ser revertida. Tens a certeza de que queres desativar este utilizador?",
         "deactivate_confirm_title": "Desativar utilizador?",
-        "dehydrated_device_enabled": "Dispositivo offline ativado",
         "demote_button": "Despromove",
         "demote_self_confirm_description_space": "Não poderás desfazer esta alteração, uma vez que te estás a despromover. Se fores o último utilizador privilegiado no espaço, será impossível recuperar os privilégios.",
         "demote_self_confirm_room": "Não poderás desfazer esta alteração, uma vez que te estás a despromover. Se fores o último utilizador privilegiado na sala, será impossível recuperar os privilégios.",
@@ -3619,15 +3776,12 @@
         "disinvite_button_room": "Desconvidar da sala",
         "disinvite_button_room_name": "Desconvidar de %(roomName)s",
         "disinvite_button_space": "Desconvidar do espaço",
-        "edit_own_devices": "Editar dispositivos",
         "error_ban_user": "Não foi possível banir o/a usuário/a",
         "error_deactivate": "Falha ao desativar o utilizador",
         "error_kicking_user": "Falha ao remover o utilizador",
         "error_mute_user": "Não foi possível remover notificações da/do usuária/o",
         "error_revoke_3pid_invite_description": "Não foi possível revogar o convite. O servidor pode estar com um problema temporário ou não tens permissões suficientes para revogar o convite.",
         "error_revoke_3pid_invite_title": "Falha ao revogar o convite",
-        "hide_sessions": "Ocultar sessões",
-        "hide_verified_sessions": "Ocultar sessões verificadas",
         "ignore_button": "Ignorar",
         "ignore_confirm_description": "Todas as mensagens e convites deste utilizador serão ocultados. Tens a certeza de que queres ignorá-los?",
         "ignore_confirm_title": "Ignora %(user)s",
@@ -3671,6 +3825,7 @@
         "unban_space_specific": "Desbani-los de coisas específicas que estou autorizado",
         "unban_space_warning": "Não poderão aceder a nada que não seja administrado por ti.",
         "unignore_button": "Designorar",
+        "verification_unavailable": "Verificação do usuário indisponível",
         "verify_button": "Verificar utilizador",
         "verify_explainer": "Para maior segurança, verifica este utilizador através de um código de utilização única em ambos os dispositivos."
     },
@@ -3723,7 +3878,6 @@
         "input_devices": "Dispositivos de entrada",
         "jitsi_call": "Conferência Jitsi",
         "join_button_tooltip_call_full": "Desculpa, neste momento, esta chamada está cheia",
-        "join_button_tooltip_connecting": "A conectar...",
         "legacy_call": "Chamada Legacy",
         "maximise": "Preenche o ecrã",
         "maximise_call": "Maximiza a chamada",
@@ -3782,6 +3936,8 @@
         "voice_call": "Chamada de voz",
         "you_are_presenting": "Estás a apresentar"
     },
+    "web_default_device_name": "%(appName)s: %(browserName)s em %(osName)s",
+    "welcome_to_element": "Bem-vindo ao Element",
     "widget": {
         "added_by": "Widget adicionado por",
         "capabilities_dialog": {
@@ -3876,7 +4032,7 @@
         "error_need_to_be_logged_in": "Você tem que estar logado.",
         "error_unable_start_audio_stream_description": "Não é possível iniciar a transmissão de áudio.",
         "error_unable_start_audio_stream_title": "Falha ao iniciar a transmissão ao vivo",
-        "modal_data_warning": "Os dados neste ecrã são partilhados com %(widgetDomain)s",
+        "modal_data_warning": "Os dados abaixo são partilhados com %(widgetDomain)s",
         "modal_title_default": "Widget Modal",
         "no_name": "Aplicação desconhecida",
         "open_id_permissions_dialog": {
diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json
index efee2781b06408eccfb72fe99efc7d95cc24f888..84a70406ebf5915c42016008b3b9eb7b0076450f 100644
--- a/src/i18n/strings/pt_BR.json
+++ b/src/i18n/strings/pt_BR.json
@@ -1,6 +1,8 @@
 {
     "a11y": {
+        "emoji_picker": "Seletor de emojis",
         "jump_first_invite": "Ir para o primeiro convite.",
+        "message_composer": "Compositor de mensagens",
         "n_unread_messages": {
             "other": "%(count)s mensagens não lidas.",
             "one": "1 mensagem não lida."
@@ -9,7 +11,20 @@
             "other": "%(count)s mensagens não lidas, incluindo menções.",
             "one": "1 menção não lida."
         },
+        "recent_rooms": "Salas recentes",
+        "room_messsage_not_sent": "Abra a sala %(roomName)s com uma mensagem não enviada.",
+        "room_n_unread_invite": "Abra o convite da sala %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Sala aberta %(roomName)s com 1 mensagem não lida.",
+            "other": "Sala aberta %(roomName)s com mensagens %(count)s não lidas."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Sala aberta %(roomName)s com 1 menção não lida.",
+            "other": "Sala aberta %(roomName)s com mensagens %(count)s não lidas, incluindo menções."
+        },
         "room_name": "Sala %(name)s",
+        "room_status_bar": "Barra de status da sala",
+        "seek_bar_label": "Barra de busca de áudio",
         "unread_messages": "Mensagens não lidas.",
         "user_menu": "Menu do usuário"
     },
@@ -19,22 +34,31 @@
         "add": "Adicionar",
         "add_existing_room": "Adicionar sala existente",
         "add_people": "Adicionar pessoas",
+        "apply": "Aplicar",
         "approve": "Autorizar",
+        "ask_to_join": "Pedir para participar",
         "back": "Voltar",
+        "call": "Ligar",
         "cancel": "Cancelar",
         "change": "Alterar",
+        "clear": "Limpar",
+        "click": "Clicar",
         "click_to_copy": "Clique para copiar",
         "close": "Fechar",
-        "collapse": "Colapsar",
+        "collapse": "Recolher",
         "complete": "Concluir",
         "confirm": "Confirmar",
         "continue": "Continuar",
         "copy": "Copiar",
+        "copy_link": "Copiar link",
         "create": "Criar",
         "create_a_room": "Criar uma sala",
         "create_account": "Criar Conta",
         "decline": "Recusar",
+        "decline_and_block": "Recusar e bloquear",
+        "decline_invite": "Recusar convite",
         "delete": "Excluir",
+        "deny": "Negar",
         "disable": "Desativar",
         "disconnect": "Desconectar",
         "dismiss": "Dispensar",
@@ -48,9 +72,11 @@
         "explore_public_rooms": "Explorar salas públicas",
         "explore_rooms": "Explorar salas",
         "export": "Exportar",
+        "forward": "Encaminhar",
         "go": "Próximo",
         "go_back": "Voltar",
         "got_it": "Ok, entendi",
+        "hide": "Ocultar",
         "hide_advanced": "Esconder configurações avançadas",
         "hold": "Pausar",
         "ignore": "Bloquear",
@@ -64,24 +90,32 @@
         "leave_room": "Sair da sala",
         "logout": "Sair",
         "manage": "Gerenciar",
+        "maximise": "Maximizar",
         "mention": "Mencionar",
+        "minimise": "Minimizar",
+        "new_message": "Nova mensagem",
         "new_room": "Nova sala",
         "new_video_room": "Nova sala de vídeo",
         "next": "Próximo",
         "no": "Não",
         "ok": "Ok",
         "open": "Abrir",
+        "open_menu": "Abrir menu",
+        "pause": "Pausar",
         "pin": "Alfinete",
+        "play": "Reproduzir",
+        "proceed": "Prosseguir",
         "quote": "Citar",
         "react": "Adicionar reação",
         "refresh": "Recarregar",
         "register": "Registre-se",
-        "reject": "Recusar",
+        "reload": "Recarregar",
         "remove": "Apagar",
         "rename": "Renomear",
         "reply": "Responder",
         "reply_in_thread": "Responder no tópico",
         "report_content": "Denunciar conteúdo",
+        "report_room": "Reportar sala",
         "resend": "Reenviar",
         "reset": "Redefinir",
         "resume": "Retomar",
@@ -91,7 +125,9 @@
         "save": "Salvar",
         "search": "Buscar",
         "send_report": "Enviar relatório",
+        "set_avatar": "Definir foto do perfil",
         "share": "Compartilhar",
+        "show": "Mostrar",
         "show_advanced": "Mostrar configurações avançadas",
         "show_all": "Mostrar tudo",
         "sign_in": "Entrar",
@@ -113,26 +149,37 @@
         "update": "Atualizar",
         "upgrade": "Atualizar",
         "upload": "Enviar",
+        "upload_file": "Carregar arquivo",
         "verify": "Confirmar",
         "view": "Ver",
+        "view_all": "Ver tudo",
+        "view_list": "Exibir lista",
         "view_message": "Ver mensagem",
         "view_source": "Ver código-fonte",
         "yes": "Sim",
+        "yes_dismiss": "Sim, dispensar",
         "zoom_in": "Aumentar zoom",
         "zoom_out": "Diminuir zoom"
     },
     "analytics": {
         "accept_button": "Isso é bom",
+        "bullet_1": "Nós <Bold>não</Bold> registramos nem traçamos o perfil de nenhum dado da conta",
+        "bullet_2": "Nós <Bold>não</Bold> compartilhamos informações com terceiros",
         "consent_migration": "Você consentiu anteriormente em compartilhar dados de uso anônimos conosco. Estamos atualizando como isso funciona.",
+        "disable_prompt": "Você pode desativar isso a qualquer momento nas configurações",
         "enable_prompt": "Ajude a melhorar %(analyticsOwner)s",
         "learn_more": "Compartilhe dados anônimos para nos ajudar a identificar problemas. Nada pessoal. Sem terceiros. <LearnMoreLink>Saiba mais</LearnMoreLink>",
+        "privacy_policy": "Você pode ler todos os nossos termos <PrivacyPolicyUrl> aqui </PrivacyPolicyUrl>",
+        "pseudonymous_usage_data": "Ajude-nos a identificar problemas e melhorar %(analyticsOwner)s compartilhando dados de uso anônimos. Para entender como as pessoas usam vários dispositivos, geraremos um identificador aleatório, compartilhado por seus dispositivos.",
         "shared_data_heading": "Qualquer um dos seguintes dados pode ser compartilhado:"
     },
     "auth": {
+        "3pid_in_use": "Esse endereço de e-mail ou número de telefone já está em uso.",
         "account_clash": "Sua nova conta (%(newAccountId)s) foi registrada, mas você já está conectado a uma conta diferente (%(loggedInUserId)s).",
         "account_clash_previous_account": "Continuar com a conta anterior",
         "account_deactivated": "Esta conta foi desativada.",
         "autodiscovery_generic_failure": "Houve uma falha para obter do servidor a configuração de encontrar contatos",
+        "autodiscovery_hs_incompatible": "Seu homeserver é muito antigo e não suporta a versão mínima de API necessária. Entre em contato com o proprietário do servidor ou atualize o servidor.",
         "autodiscovery_invalid": "Resposta de descoberta de homeserver inválida",
         "autodiscovery_invalid_hs": "O endereço do servidor local não parece indicar um servidor local válido na Matrix",
         "autodiscovery_invalid_hs_base_url": "base_url inválido para m.homeserver",
@@ -140,6 +187,7 @@
         "autodiscovery_invalid_is_base_url": "base_url inválido para m.identity_server",
         "autodiscovery_invalid_is_response": "Resposta de descoberta do servidor de identidade inválida",
         "autodiscovery_invalid_json": "JSON inválido",
+        "autodiscovery_no_well_known": "Nenhum arquivo .well-known JSON foi encontrado",
         "autodiscovery_unexpected_error_hs": "Erro inesperado buscando a configuração do servidor",
         "autodiscovery_unexpected_error_is": "Erro inesperado buscando a configuração do servidor de identidade",
         "captcha_description": "Este servidor local quer se certificar de que você não é um robô.",
@@ -148,8 +196,14 @@
         "change_password_confirm_label": "Confirme a nova senha",
         "change_password_current_label": "Senha atual",
         "change_password_empty": "As senhas não podem estar em branco",
+        "change_password_error": "Erro ao alterar a senha: %(error)s",
         "change_password_mismatch": "As novas senhas não conferem",
         "change_password_new_label": "Nova senha",
+        "check_email_explainer": "Siga as instruções enviadas para <b>%(email)s </b>",
+        "check_email_resend_prompt": "Não recebeu?",
+        "check_email_resend_tooltip": "E-mail com link de verificação reenviado!",
+        "check_email_wrong_email_button": "Insira o endereço de e-mail novamente",
+        "check_email_wrong_email_prompt": "Endereço de e-mail errado?",
         "continue_with_idp": "Continuar com %(provider)s",
         "continue_with_sso": "Continuar com %(ssoButtons)s",
         "country_dropdown": "Selecione o país",
@@ -161,6 +215,8 @@
         "email_field_label_required": "Digite o endereço de e-mail",
         "email_help_text": "Adicione um e-mail para depois poder redefinir sua senha.",
         "email_phone_discovery_text": "Seja encontrado por seus contatos a partir de um e-mail ou número de telefone.",
+        "enter_email_explainer": "<b>%(homeserver)s</b>enviará um link de verificação para permitir que você redefina sua senha.",
+        "enter_email_heading": "Digite seu e-mail para redefinir a senha",
         "failed_connect_identity_server": "Não consigo acessar o servidor de identidade",
         "failed_connect_identity_server_other": "Você pode fazer login, mas alguns recursos estarão indisponíveis até que o servidor de identidade estiver no ar novamente. Se você continuar vendo este alerta, verifique suas configurações ou entre em contato com os administradores do servidor.",
         "failed_connect_identity_server_register": "Você pode se registrar, mas alguns recursos não estarão disponíveis até que o servidor de identidade esteja no ar novamente. Se você continuar vendo este alerta, verifique sua configuração ou entre em contato com um dos administradores do servidor.",
@@ -169,8 +225,10 @@
         "failed_query_registration_methods": "Não foi possível consultar as opções de registro suportadas.",
         "failed_soft_logout_auth": "Falha em autenticar novamente",
         "failed_soft_logout_homeserver": "Falha em autenticar novamente devido à um problema no servidor local",
+        "forgot_password_email_invalid": "O endereço de e-mail não parece ser válido.",
         "forgot_password_email_required": "O e-mail vinculado à sua conta precisa ser informado.",
         "forgot_password_prompt": "Esqueceu sua senha?",
+        "forgot_password_send_email": "Enviar e-mail",
         "identifier_label": "Entrar com",
         "incorrect_credentials": "Nome de usuário e/ou senha incorreto.",
         "incorrect_credentials_detail": "Note que você está se conectando ao servidor %(hs)s, e não ao servidor matrix.org.",
@@ -181,11 +239,13 @@
             "megolm_export": "Exportar chaves manualmente",
             "setup_key_backup_title": "Você perderá acesso às suas mensagens criptografadas",
             "setup_secure_backup_description_1": "As mensagens estão protegidas com a criptografia de ponta a ponta. Somente você e o(s) destinatário(s) têm as chaves para ler essas mensagens.",
+            "setup_secure_backup_description_2": "Quando você se desconectar, essas chaves serão excluídas desse dispositivo, o que significa que você não poderá ler mensagens criptografadas, a menos que tenha as chaves delas em seus outros dispositivos ou tenha feito backup delas no servidor.",
             "skip_key_backup": "Não quero minhas mensagens criptografadas",
             "use_key_backup": "Comece a usar backup de chave"
         },
         "misconfigured_body": "Entre em contato com o administrador do %(brand)s para verificar se há entradas inválidas ou duplicadas nas <a>suas configurações</a>.",
         "misconfigured_title": "O %(brand)s está mal configurado",
+        "mobile_create_account_title": "Você está prestes a criar uma conta em %(hsName)s",
         "msisdn_field_description": "Outros usuários podem convidá-lo para salas usando seus detalhes de contato",
         "msisdn_field_label": "Telefone",
         "msisdn_field_number_invalid": "Esse número de telefone não é válido, verifique e tente novamente",
@@ -193,13 +253,52 @@
         "no_hs_url_provided": "Nenhum endereço fornecido do servidor local",
         "oidc": {
             "error_title": "Não foi possível fazer login",
+            "generic_auth_error": "Algo deu errado durante a autenticação. Acesse a página de login e tente novamente.",
             "missing_or_invalid_stored_state": "Anteriormente, pedimos ao seu navegador para lembrar qual servidor local você usa para fazer login, mas infelizmente o navegador se esqueceu disso. Vá para a página de login e tente novamente."
         },
+        "password_field_keep_going_prompt": "Continue...",
         "password_field_label": "Digite a senha",
         "password_field_strong_label": "Muito bem, uma senha forte!",
         "password_field_weak_label": "Esta senha é permitida, mas não é segura",
         "phone_label": "Telefone",
         "phone_optional_label": "Número de celular (opcional)",
+        "qr_code_login": {
+            "check_code_explainer": "Isso verificará se a conexão com seu outro dispositivo é segura.",
+            "check_code_heading": "Digite o número mostrado em seu outro dispositivo",
+            "check_code_input_label": "Código de 2 dígitos",
+            "check_code_mismatch": "Os números não coincidem",
+            "completing_setup": "Concluindo a configuração do seu novo dispositivo",
+            "error_etag_missing": "Ocorreu um erro inesperado. Isso pode ser devido a uma extensão do navegador, servidor proxy ou configuração incorreta do servidor.",
+            "error_expired": "O login expirou. Por favor, tente novamente.",
+            "error_expired_title": "O login não foi concluído a tempo",
+            "error_insecure_channel_detected": "Não foi possível estabelecer uma conexão segura com o novo dispositivo. Seus dispositivos existentes ainda estão seguros e você não precisa se preocupar com eles.",
+            "error_insecure_channel_detected_instructions": "E agora?",
+            "error_insecure_channel_detected_instructions_1": "Tente entrar no outro dispositivo novamente com um código QR, caso seja um problema de rede",
+            "error_insecure_channel_detected_instructions_2": "Se o problema persistir, tente uma rede Wi-Fi diferente ou use seus dados móveis em vez de Wi-Fi",
+            "error_insecure_channel_detected_instructions_3": "Se isso não funcionar, faça login manualmente",
+            "error_insecure_channel_detected_title": "Conexão não segura",
+            "error_other_device_already_signed_in": "Você não precisa fazer mais nada.",
+            "error_other_device_already_signed_in_title": "Seu outro dispositivo já está conectado",
+            "error_rate_limited": "Muitas tentativas em um curto espaço de tempo. Aguarde algum tempo antes de tentar novamente.",
+            "error_unexpected": "Ocorreu um erro inesperado. A solicitação para conectar seu outro dispositivo foi cancelada.",
+            "error_unsupported_protocol": "Este dispositivo não suporta o login em outro dispositivo com um código QR.",
+            "error_unsupported_protocol_title": "Outro dispositivo não compatível",
+            "error_user_cancelled": "O login foi cancelado no outro dispositivo.",
+            "error_user_cancelled_title": "Solicitação de login cancelada",
+            "error_user_declined": "Você ou o provedor da conta recusaram a solicitação de login.",
+            "error_user_declined_title": "Login recusado",
+            "follow_remaining_instructions": "Siga as instruções restantes",
+            "open_element_other_device": "Abra %(brand)s em seu outro dispositivo",
+            "point_the_camera": "Escaneie o código QR mostrado aqui",
+            "scan_code_instruction": "Digitalize o código QR com outro dispositivo",
+            "scan_qr_code": "Iniciar sessão com código QR",
+            "security_code": "Código de segurança",
+            "security_code_prompt": "Se solicitado, insira o código abaixo em seu outro dispositivo.",
+            "select_qr_code": "Selecione \"%(scanQRCode)s”",
+            "unsupported_explainer": "Seu provedor de conta não suporta o login em um novo dispositivo com um código QR.",
+            "unsupported_heading": "Código QR não suportado",
+            "waiting_for_device": "Aguardando o login do dispositivo"
+        },
         "register_action": "Criar Conta",
         "registration": {
             "continue_without_email_description": "Apenas um aviso: se você não adicionar um e-mail e depois esquecer sua senha, poderá <b>perder permanentemente o acesso à sua conta</b>.",
@@ -209,23 +308,39 @@
         "registration_disabled": "O registro de contas foi desativado neste servidor local.",
         "registration_msisdn_field_required_invalid": "Digite o número de celular (necessário neste servidor)",
         "registration_successful": "Registro bem-sucedido",
+        "registration_username_in_use": "Alguém já tem esse nome de usuário. Tente outro ou, se for você, faça login abaixo.",
+        "registration_username_unable_check": "Não foi possível verificar se o nome de usuário foi usado. Tente novamente mais tarde.",
         "registration_username_validation": "Use apenas letras minúsculas, números, traços e sublinhados",
         "reset_password": {
+            "confirm_new_password": "Confirmar nova senha",
+            "devices_logout_success": "Você foi desconectado de todos os dispositivos e não receberá mais notificações push. Para reativar as notificações, faça login novamente em cada dispositivo.",
+            "other_devices_logout_warning_1": "Desconectar seus dispositivos excluirá as chaves de criptografia de mensagens armazenadas neles, tornando o histórico de bate-papo criptografado ilegível.",
+            "other_devices_logout_warning_2": "Se você quiser manter o acesso ao seu histórico de bate-papo em salas criptografadas, configure o Key Backup ou exporte suas chaves de mensagem de um de seus outros dispositivos antes de continuar.",
             "password_not_entered": "Uma nova senha precisa ser inserida.",
             "passwords_mismatch": "As novas senhas informadas precisam ser idênticas.",
+            "rate_limit_error": "Muitas tentativas em pouco tempo. Espere um pouco antes de tentar novamente.",
+            "rate_limit_error_with_time": "Muitas tentativas em pouco tempo. Tente novamente depois%(timeout)s.",
             "reset_successful": "Sua senha foi alterada.",
-            "return_to_login": "Retornar à tela de login"
+            "return_to_login": "Retornar à tela de login",
+            "sign_out_other_devices": "Saia de todos os dispositivos"
         },
+        "reset_password_action": "Redefinir senha",
+        "reset_password_button": "Esqueceu a senha?",
         "reset_password_email_field_description": "Use um endereço de e-mail para recuperar sua conta",
         "reset_password_email_field_required_invalid": "Digite o endereço de e-mail (necessário neste servidor)",
+        "reset_password_email_not_associated": "Seu endereço de e-mail não parece estar associado a um Matrix ID neste servidor doméstico.",
         "reset_password_email_not_found_title": "Este endereço de e-mail não foi encontrado",
+        "reset_password_title": "Redefinir sua senha",
         "server_picker_custom": "Outro servidor local",
+        "server_picker_description": "Você pode usar as opções personalizadas do servidor para entrar em outros servidores Matrix especificando um URL de servidor doméstico diferente. Isso permite que você use %(brand)s com uma conta Matrix existente em um servidor doméstico diferente.",
         "server_picker_description_matrix.org": "Junte-se a milhões de pessoas gratuitamente no maior servidor público",
         "server_picker_dialog_title": "Decida onde a sua conta será hospedada",
         "server_picker_explainer": "Use o seu servidor local Matrix preferido, ou hospede o seu próprio servidor.",
         "server_picker_failed_validate_homeserver": "Não foi possível validar o servidor local",
+        "server_picker_intro": "Chamamos os locais onde você pode hospedar sua conta de 'homeservers'.",
         "server_picker_invalid_url": "URL inválido",
         "server_picker_learn_more": "Sobre os servidores locais",
+        "server_picker_matrix.org": "O Matrix.org é o maior servidor doméstico público do mundo, então é um bom lugar para muitos.",
         "server_picker_required": "Digite um servidor local",
         "server_picker_title": "Faça login em seu servidor local",
         "server_picker_title_default": "Opções do servidor",
@@ -238,34 +353,48 @@
             "verification_pending_title": "Confirmação pendente"
         },
         "set_email_prompt": "Você deseja definir um endereço de e-mail?",
+        "sign_in_description": "Use sua conta para continuar.",
+        "sign_in_instead": "Em vez disso, faça login",
         "sign_in_instead_prompt": "Já tem uma conta? <a>Entre aqui</a>",
         "sign_in_or_register": "Faça login ou crie uma conta",
         "sign_in_or_register_description": "Use sua conta ou crie uma nova para continuar.",
         "sign_in_prompt": "Já tem uma conta? <a>Login</a>",
         "sign_in_with_sso": "Entre com o logon único",
+        "signing_in": "Entrando…",
         "soft_logout": {
             "clear_data_button": "Limpar todos os dados",
             "clear_data_description": "Apagar todos os dados desta sessão é uma ação permanente. Mensagens criptografadas serão perdidas, a não ser que as chaves delas tenham sido copiadas para o backup.",
             "clear_data_title": "Limpar todos os dados nesta sessão?"
         },
-        "soft_logout_heading": "Você está desconectada/o",
+        "soft_logout_heading": "Você está desconectado",
         "soft_logout_intro_password": "Digite sua senha para entrar e recuperar o acesso à sua conta.",
         "soft_logout_intro_sso": "Entre e recupere o acesso à sua conta.",
         "soft_logout_intro_unsupported_auth": "Você não pôde se conectar na sua conta. Entre em contato com o administrador do servidor para obter mais informações.",
         "soft_logout_subheading": "Limpar dados pessoais",
+        "soft_logout_warning": "Aviso: seus dados pessoais (incluindo chaves de criptografia) ainda serão armazenados nesta sessão. Limpe-o se você terminar de usar esta sessão ou quiser fazer login em outra conta.",
         "sso": "Autenticação Única",
         "sso_complete_in_browser_dialog_title": "Vá em seu navegador para completar o Registro",
         "sso_failed_missing_storage": "Anteriormente, pedimos ao seu navegador para lembrar qual servidor local você usa para fazer login, mas infelizmente o navegador se esqueceu disso. Vá para a página de login e tente novamente.",
         "sso_or_username_password": "%(ssoButtons)s ou %(usernamePassword)s",
         "sync_footer_subtitle": "Se você participa em muitas salas, isso pode demorar um pouco",
+        "syncing": "Sincronizando...",
         "uia": {
             "code": "Código",
+            "email": "Para criar sua conta, abra o link no e-mail que acabamos de enviar para%(emailAddress)s .",
+            "email_auth_header": "Verifique seu e-mail para continuar",
+            "email_resend_prompt": "Não recebeu?<a> Reenvie</a>",
+            "email_resent": "Reenviar!",
             "fallback_button": "Iniciar autenticação",
+            "mas_cross_signing_reset_cta": "Vá para sua conta",
+            "mas_cross_signing_reset_description": "Redefina sua identidade por meio do provedor da conta e, em seguida, volte e clique em “Tentar novamente”.",
+            "mas_cross_signing_reset_title": "Acesse sua conta para redefinir sua identidade",
             "msisdn": "Uma mensagem de texto foi enviada para %(msisdn)s",
             "msisdn_token_incorrect": "Token incorreto",
             "msisdn_token_prompt": "Por favor, entre com o código que está na mensagem:",
             "password_prompt": "Confirme sua identidade digitando sua senha abaixo.",
             "recaptcha_missing_params": "Está faltando a chave pública do captcha no Servidor (homeserver). Por favor, reporte isso aos(às) administradores(as) do servidor.",
+            "registration_token_label": "Token de registro",
+            "registration_token_prompt": "Insira um token de registro fornecido pelo administrador do servidor doméstico.",
             "sso_body": "Confirme a inclusão deste endereço de e-mail usando o Single Sign On para comprovar sua identidade.",
             "sso_failed": "Algo deu errado ao confirmar a sua identidade. Cancele e tente novamente.",
             "sso_postauth_body": "Clique no botão abaixo para confirmar sua identidade.",
@@ -275,35 +404,50 @@
             "terms": "Por favor, revise e aceite as políticas deste servidor local:",
             "terms_invalid": "Por favor, revise e aceite todas as políticas do homeserver"
         },
+        "unsupported_auth": "Esse servidor doméstico não oferece nenhum fluxo de login compatível com esse cliente.",
         "unsupported_auth_email": "Este servidor local não suporta login usando endereço de e-mail.",
         "unsupported_auth_msisdn": "Este servidor não permite a autenticação através de números de telefone.",
-        "username_field_required_invalid": "Digite o nome de usuário"
+        "username_field_required_invalid": "Digite o nome de usuário",
+        "username_in_use": "Alguém já tem esse nome de usuário, tente outro.",
+        "verify_email_explainer": "Precisamos saber sua identidade antes de redefinir sua senha. Clique no link do e-mail que acabamos de enviar para<b>%(email)s</b>",
+        "verify_email_heading": "Verifique seu e-mail para continuar"
     },
     "bug_reporting": {
         "additional_context": "Se houver um contexto adicional que ajude a analisar o problema, tal como o que você estava fazendo no momento, IDs de salas, IDs de usuários etc, inclua essas coisas aqui.",
         "before_submitting": "Antes de enviar os relatórios, você deve <a>criar um bilhete de erro no GitHub</a> para descrever seu problema.",
         "collecting_information": "Coletando informação sobre a versão do app",
         "collecting_logs": "Coletando logs",
-        "create_new_issue": "Por favor, <newIssueLink>crie um novo bilhete de erro</newIssueLink> no GitHub para que possamos investigar esta falha.",
-        "download_logs": "Baixar relatórios",
-        "downloading_logs": "Baixando relatórios",
-        "error_empty": "Por favor, diga-nos o que aconteceu de errado ou, ainda melhor, crie um bilhete de erro no GitHub que descreva o problema.",
-        "failed_send_logs": "Falha ao enviar os relatórios:· ",
-        "github_issue": "Bilhete de erro no GitHub",
-        "log_request": "Para nos ajudar a evitar isso no futuro, <a>envie-nos os relatórios</a>.",
-        "logs_sent": "Relatórios enviados",
+        "create_new_issue": "Por favor, <newIssueLink>crie um novo ticket de erro</newIssueLink> no GitHub para que possamos investigar esta falha.",
+        "description": "Os logs de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou aliases das salas que você visitou, com quais elementos da IU você interagiu pela última vez e os nomes de usuários de outros usuários. Eles não contêm mensagens.",
+        "download_logs": "Baixar logs",
+        "downloading_logs": "Baixando logs",
+        "error_empty": "Por favor, diga-nos o que aconteceu de errado ou, ainda melhor, crie um ticket de erro no GitHub que descreva o problema.",
+        "failed_download_logs": "Falha ao baixar os logs: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Seu relatório de bug foi rejeitado. O servidor rageshake não suporta esse aplicativo.",
+            "rejected_generic": "Seu relatório de bug foi rejeitado. O servidor rageshake rejeitou o conteúdo do relatório devido a uma política.",
+            "rejected_recovery_key": "Seu relatório de bug foi rejeitado por motivos de segurança, pois continha uma chave de recuperação.",
+            "rejected_version": "Seu relatório de bug foi rejeitado porque a versão que você está executando é muito antiga.",
+            "server_unknown_error": "O servidor rageshake encontrou um erro desconhecido e não conseguiu lidar com o relatório.",
+            "unknown_error": "Falha ao enviar logs."
+        },
+        "github_issue": "Ticket de erro no GitHub",
+        "introduction": "Se você enviou um bug pelo GitHub, os logs de depuração podem nos ajudar a rastrear o problema. ",
+        "log_request": "Para nos ajudar a evitar que isso aconteça no futuro, <a>envie-nos logs</a>.",
+        "logs_sent": "Logs enviados",
         "matrix_security_issue": "Para relatar um problema de segurança relacionado à tecnologia Matrix, leia a <a>Política de Divulgação de Segurança</a> da Matrix.org.",
-        "preparing_download": "Preparando os relatórios para download",
-        "preparing_logs": "Preparando para enviar relatórios",
-        "send_logs": "Enviar relatórios",
-        "submit_debug_logs": "Enviar relatórios de erros",
+        "preparing_download": "Preparando para baixar logs",
+        "preparing_logs": "Preparando para enviar logs",
+        "send_logs": "Enviar logs",
+        "submit_debug_logs": "Enviar logs de depuração",
         "textarea_label": "Notas",
         "thank_you": "Obrigado!",
         "title": "Relato de Erros",
         "unsupported_browser": "Lembrete: seu navegador não é compatível; portanto, sua experiência pode ser imprevisível.",
-        "uploading_logs": "Enviando relatórios",
+        "uploading_logs": "Enviando logs",
         "waiting_for_server": "Aguardando a resposta do servidor"
     },
+    "cannot_invite_without_identity_server": "Não é possível convidar usuários por e-mail sem um servidor de identidade. Você pode se conectar a um em “Configurações”.",
     "cannot_reach_homeserver": "Não consigo acessar o servidor",
     "cannot_reach_homeserver_detail": "Verifique se está com uma conexão de internet estável, ou entre em contato com os administradores do servidor",
     "cant_load_page": "Não foi possível carregar a página",
@@ -313,8 +457,10 @@
         "confetti_message": "envia confetes",
         "fireworks_description": "Envia a mensagem com fogos de artifício",
         "fireworks_message": "envia fogos de artifício",
+        "hearts_description": "Envia a mensagem dada com corações",
+        "hearts_message": "envia corações",
         "rainfall_description": "Envia a mensagem dada com um efeito de chuva",
-        "rainfall_message": "Enviar efeito de chuva",
+        "rainfall_message": "enviar efeito de chuva",
         "snowfall_description": "Envia a mensagem com neve caindo",
         "snowfall_message": "envia neve caindo",
         "spaceinvaders_description": "Envia a mensagem com um efeito com tema espacial",
@@ -322,8 +468,9 @@
     },
     "common": {
         "access_token": "Símbolo de acesso",
+        "accessibility": "Acessibilidade",
         "advanced": "Avançado",
-        "all_rooms": "Todas as salas",
+        "all_chats": "Todos os bate-papos",
         "analytics": "Análise",
         "and_n_others": {
             "one": "e um outro...",
@@ -334,55 +481,76 @@
         "are_you_sure": "Você tem certeza?",
         "attachment": "Anexo",
         "authentication": "Autenticação",
+        "avatar": "Avatar",
+        "beta": "Beta",
         "camera": "Câmera",
+        "cameras": "Câmeras",
+        "cancel": "Cancelar",
+        "capabilities": "Capacidades",
         "copied": "Copiado!",
         "credits": "Licenças",
-        "cross_signing": "Autoverificação",
         "dark": "Escuro",
         "description": "Descrição",
         "deselect_all": "Desmarcar todos",
         "device": "Dispositivo",
         "edited": "editado",
         "email_address": "Endereço de e-mail",
+        "emoji": "Emoji",
         "encrypted": "Criptografada",
         "encryption_enabled": "Criptografia ativada",
         "error": "Erro",
+        "faq": "Perguntas frequentes",
         "favourites": "Favoritos",
         "feedback": "Fale conosco",
         "filter_results": "Filtrar resultados",
         "forward_message": "Encaminhar",
         "general": "Geral",
         "go_to_settings": "Ir para as configurações",
-        "guest": "Convidada(o)",
+        "guest": "Convidado",
         "help": "Ajuda",
         "historical": "Histórico",
+        "home": "Início",
         "homeserver": "Servidor local",
         "identity_server": "Servidor de identidade",
         "image": "Imagem",
         "integration_manager": "Gerenciador de integrações",
+        "joined": "Ingressou",
         "labs": "Laboratório",
+        "legal": "Avisos legais",
         "light": "Claro",
+        "loading": "Carregando...",
+        "location": "Localidade",
         "low_priority": "Baixa prioridade",
+        "matrix": "Matrix",
         "message": "Mensagem",
         "message_layout": "Aparência da mensagem",
+        "message_timestamp_invalid": "Data/hora inválido",
         "microphone": "Microfone",
         "model": "Modelo",
+        "moderation_and_safety": "Moderação e segurança",
         "modern": "Moderno",
         "mute": "Mudo",
         "n_members": {
             "one": "%(count)s integrante",
             "other": "%(count)s integrantes"
         },
+        "n_rooms": {
+            "one": "%(count)s sala",
+            "other": "%(count)s salas"
+        },
         "name": "Nome",
         "no_results": "Nenhum resultado",
+        "no_results_found": "Nenhum resultado encontrado",
         "not_trusted": "Não confiável",
         "off": "Desativado",
+        "offline": "Offline",
         "on": "Ativado",
         "options": "Opções",
         "orphan_rooms": "Outras salas",
         "password": "Senha",
         "people": "Pessoas",
         "preferences": "Preferências",
+        "presence": "Presença",
         "preview_message": "Ei, você aí. Você é incrível!",
         "privacy": "Privacidade",
         "private": "Privado",
@@ -395,13 +563,17 @@
         "qr_code": "Código QR",
         "random": "Aleatório",
         "reactions": "Reações",
+        "recommended": "Recomendado",
         "report_a_bug": "Reportar um erro",
         "room": "Sala",
         "room_name": "Nome da sala",
         "rooms": "Salas",
+        "save": "Salvar",
+        "saved": "Salvo",
+        "saving": "Salvando...",
         "secure_backup": "Backup online",
-        "security": "Segurança",
         "select_all": "Selecionar tudo",
+        "server": "Servidor",
         "settings": "Configurações",
         "setup_secure_messages": "Configurar mensagens seguras",
         "show_more": "Mostrar mais",
@@ -415,14 +587,17 @@
         "support": "Suporte",
         "system_alerts": "Alertas do sistema",
         "theme": "Tema",
+        "thread": "Tópico",
         "threads": "Tópicos",
         "timeline": "Conversas",
-        "trusted": "Confiável",
+        "unavailable": "indisponível",
         "unencrypted": "Descriptografada",
         "unmute": "Tirar do mudo",
         "unnamed_room": "Sala sem nome",
         "unnamed_space": "Espaço sem nome",
         "unverified": "Não verificado",
+        "updating": "Atualizando…",
+        "user": "Usuário",
         "user_avatar": "Foto de perfil",
         "username": "Nome de usuário",
         "verification_cancelled": "Confirmação cancelada",
@@ -442,12 +617,16 @@
             "notification_a11y": "Notificação do preenchimento automático",
             "notification_description": "Notificação da sala",
             "room_a11y": "Preenchimento automático de sala",
+            "space_a11y": "Preenchimento automático de espaço",
             "user_a11y": "Preenchimento automático de usuário",
             "user_description": "Usuários"
         },
+        "close_sticker_picker": "Ocultar stickers",
         "edit_composer_label": "Editar mensagem",
         "format_bold": "Negrito",
         "format_code_block": "Bloco de código",
+        "format_decrease_indent": "Diminuição do recuo",
+        "format_increase_indent": "Aumento do recuo",
         "format_inline_code": "Código",
         "format_insert_link": "Inserir link",
         "format_italic": "Itálico",
@@ -456,12 +635,16 @@
         "format_ordered_list": "Lista numerada",
         "format_strikethrough": "Riscado",
         "format_underline": "Sublinhar",
+        "format_unordered_list": "Lista com marcadores",
+        "formatting_toolbar_label": "Formatação",
         "link_modal": {
             "link_field_label": "Ligação",
             "text_field_label": "Texto",
             "title_create": "Criar uma ligação",
             "title_edit": "Editar ligação"
         },
+        "mode_plain": "Ocultar formatação",
+        "mode_rich_text": "Mostrar formatação",
         "no_perms_notice": "Você não tem permissão para digitar nesta sala",
         "placeholder": "Digite uma mensagem…",
         "placeholder_encrypted": "Digite uma mensagem criptografada…",
@@ -469,6 +652,7 @@
         "placeholder_reply_encrypted": "Digite sua resposta criptografada…",
         "placeholder_thread": "Responder ao tópico…",
         "placeholder_thread_encrypted": "Responder ao tópico criptografado…",
+        "poll_button": "Enquete",
         "poll_button_no_perms_description": "Você não tem permissão para iniciar enquetes nesta sala.",
         "poll_button_no_perms_title": "Permissão necessária",
         "replying_title": "Em resposta a",
@@ -477,11 +661,17 @@
         "send_button_title": "Enviar mensagem",
         "send_button_voice_message": "Enviar uma mensagem de voz",
         "send_voice_message": "Enviar uma mensagem de voz",
-        "stop_voice_message": "Parar a gravação"
+        "stop_voice_message": "Parar a gravação",
+        "voice_message_button": "Mensagem de voz"
     },
+    "console_dev_note": "Se você sabe o que está fazendo, o Element é de código aberto, não deixe de conferir nosso GitHub (https://github.com/vector-im/element-web/) e contribuir!",
+    "console_scam_warning": "Se alguém lhe disse para copiar/colar algo aqui, há uma grande probabilidade de você estar sendo enganado!",
+    "console_wait": "Aguarde!",
     "create_room": {
         "action_create_room": "Criar sala",
         "action_create_video_room": "Criar sala de vídeo",
+        "encrypted_video_room_warning": "Você não pode desabilitar isso mais tarde. A sala será criptografada mas a chamada incorporada não.",
+        "encrypted_warning": "Você não pode desativar isso mais tarde. As pontes e a maioria dos bots ainda não funcionarão.",
         "encryption_forced": "O seu servidor demanda que a criptografia esteja ativada em salas privadas.",
         "encryption_label": "Ativar a criptografia de ponta a ponta",
         "error_title": "Não foi possível criar a sala",
@@ -489,6 +679,7 @@
         "join_rule_change_notice": "Você pode mudar isto em qualquer momento nas configurações da sala.",
         "join_rule_invite": "Sala privada (apenas com convite)",
         "join_rule_invite_label": "Apenas convidados poderão encontrar e entrar nesta sala.",
+        "join_rule_knock_label": "Qualquer pessoa pode solicitar participação, mas os administradores ou moderadores precisam conceder acesso. Você pode alterar isso mais tarde.",
         "join_rule_public_label": "Qualquer um poderá encontrar e entrar nesta sala.",
         "join_rule_public_parent_space_label": "Qualquer um poderá encontrar e entrar nesta sala, não somente membros de <SpaceName/>.",
         "join_rule_restricted": "Visível para membros do espaço",
@@ -507,50 +698,184 @@
     "create_space": {
         "add_details_prompt": "Adicione alguns detalhes para ajudar as pessoas a reconhecê-lo.",
         "add_details_prompt_2": "Você pode mudá-los a qualquer instante.",
+        "add_existing_rooms_description": "Escolha salas ou conversas para adicionar. Este é apenas um espaço para você, ninguém será informado. Você pode adicionar mais posteriormente.",
+        "add_existing_rooms_heading": "O que você deseja organizar?",
         "address_label": "Endereço",
         "address_placeholder": "e.g. meu-espaco",
+        "creating": "Criando...",
+        "creating_rooms": "Criando salas...",
+        "done_action": "Ir para o meu espaço",
+        "done_action_first_room": "Ir para minha primeira sala",
+        "explainer": "Os espaços são uma nova forma de agrupar salas e pessoas. Que tipo de espaço você quer criar? Você pode mudar isso mais tarde.",
         "failed_create_initial_rooms": "Falha ao criar salas de espaço iniciais",
+        "failed_invite_users": "Falha ao convidar os seguintes usuários para seu espaço: %(csvUsers)s",
         "invite_teammates_by_username": "Convidar por nome de usuário",
+        "invite_teammates_description": "Certifique-se de que as pessoas certas tenham acesso. Você pode convidar mais tarde.",
+        "invite_teammates_heading": "Convide seus colegas de equipe",
+        "inviting_users": "Convidando...",
         "label": "Criar um espaço",
         "name_required": "Por favor entre o nome do espaço",
-        "private_description": "Somente convite, melhor para si mesmo(a) ou para equipes",
+        "personal_space": "Apenas eu",
+        "personal_space_description": "Um espaço privado para organizar suas salas",
+        "private_description": "Somente para convidados, melhor para você ou para equipes",
         "private_heading": "O seu espaço privado",
+        "private_personal_description": "Certifique-se de que as pessoas certas tenham acesso a %(name)s",
+        "private_personal_heading": "Com quem você está trabalhando?",
+        "private_space": "Eu e meus colegas de equipe",
+        "private_space_description": "Um espaço privado para você e seus colegas de equipe",
         "public_description": "Abra espaços para todos, especialmente para comunidades",
         "public_heading": "O seu espaço público",
+        "search_public_button": "Pesquise espaços públicos",
+        "setup_rooms_community_description": "Vamos criar uma sala para cada um deles.",
+        "setup_rooms_community_heading": "O que você gostaria de discutir em %(spaceName)s?",
+        "setup_rooms_description": "Você também pode adicionar mais tarde, incluindo os já existentes.",
+        "setup_rooms_private_description": "Criaremos salas para cada um deles.",
+        "setup_rooms_private_heading": "Em quais projetos sua equipe está trabalhando?",
+        "share_description": "No momento é só você, será ainda melhor com outros.",
+        "share_heading": "Compartilhar %(name)s",
         "skip_action": "Ignorar por enquanto",
+        "subspace_adding": "Adicionando...",
         "subspace_beta_notice": "Adicionar um espaço à um espaço que você gerencia.",
         "subspace_dropdown_title": "Criar um espaço",
+        "subspace_existing_space_prompt": "Em vez disso, quer adicionar um espaço existente?",
         "subspace_join_rule_invite_description": "Apenas convidados poderão encontrar e entrar neste espaço.",
         "subspace_join_rule_invite_only": "Espaço privado (apenas com convite)",
         "subspace_join_rule_label": "Visibilidade do Espaço",
         "subspace_join_rule_public_description": "Qualquer um poderá encontrar e entrar neste espaço, não somente membros de <SpaceName/>.",
         "subspace_join_rule_restricted_description": "Todos em <SpaceName/> poderão ver e entrar."
     },
+    "credits": {
+        "default_cover_photo": "A <photo>foto de capa padrão</photo> é ©<author> Jesus Roncero</author> usada sob os termos de<terms> CC-BY-SA 4.0</terms> .",
+        "twemoji": "A arte do <twemoji> emoji </twemoji> Twemoji é © <author> Twitter, Inc e outros colaboradores </author> usados sob os termos da CC-BY 4.0. <terms> </terms>",
+        "twemoji_colr": "A fonte <colr>twemoji-colr</colr> é © <author>Mozilla Foundation</author> usada sob os termos de <terms>Apache 2.0</terms>."
+    },
+    "decline_invitation_dialog": {
+        "confirm": "Tem certeza de que deseja recusar o convite para participar de \"%(roomName)s“?",
+        "ignore_user_help": "Você não verá nenhuma mensagem ou convite para salas desse usuário.",
+        "reason_description": "Descreva o motivo da denúncia da sala.",
+        "report_room_description": "Denuncie esta sala ao provedor da sua conta.",
+        "title": "Recusar convite"
+    },
+    "desktop_default_device_name": "%(brand)sÁrea de trabalho: %(platformName)s",
     "devtools": {
         "active_widgets": "Widgets ativados",
         "category_other": "Outros",
         "category_room": "Sala",
         "caution_colon": "Atenção:",
+        "client_versions": "Versões do cliente",
+        "crypto": {
+            "4s_public_key_in_account_data": "nos dados de conta",
+            "4s_public_key_not_in_account_data": "não encontrado",
+            "4s_public_key_status": "Chave pública do armazenamento secreto:",
+            "backup_key_cached": "armazenado localmente",
+            "backup_key_cached_status": "Backup da chave em cache:",
+            "backup_key_not_stored": "não armazenado",
+            "backup_key_stored": "em armazenamento secreto",
+            "backup_key_stored_status": "Backup da chave armazenada:",
+            "backup_key_unexpected_type": "tipo inesperado",
+            "backup_key_well_formed": "bem formado",
+            "cross_signing": "Assinatura cruzada",
+            "cross_signing_cached": "armazenado localmente",
+            "cross_signing_not_ready": "A assinatura cruzada não está configurada.",
+            "cross_signing_private_keys_in_storage": "em armazenamento secreto",
+            "cross_signing_private_keys_in_storage_status": "Chaves privadas de assinatura cruzada:",
+            "cross_signing_private_keys_not_in_storage": "não encontrado no armazenamento",
+            "cross_signing_public_keys_on_device": "na memória",
+            "cross_signing_public_keys_on_device_status": "Chaves públicas de assinatura cruzada:",
+            "cross_signing_ready": "A assinatura cruzada está pronta para ser usada.",
+            "cross_signing_status": "Status de assinatura cruzada:",
+            "cross_signing_untrusted": "Sua conta tem uma identidade de assinatura cruzada no armazenamento secreto, mas ela ainda não é confiável para esta sessão.",
+            "crypto_not_available": "O módulo criptográfico não está disponível",
+            "key_backup_active_version": "Versão de backup ativo:",
+            "key_backup_active_version_none": "Nenhuma",
+            "key_backup_inactive_warning": "Suas chaves não estão sendo copiadas nesta sessão.",
+            "key_backup_latest_version": "Versão mais recente do backup no servidor:",
+            "key_storage": "Armazenamento de chaves",
+            "master_private_key_cached_status": "Chave privada principal:",
+            "not_found": "não encontrado",
+            "not_found_locally": "não encontrado localmente",
+            "secret_storage_not_ready": "não está pronto",
+            "secret_storage_ready": "pronto",
+            "secret_storage_status": "Armazenamento secreto:",
+            "self_signing_private_key_cached_status": "Chave privada auto-assinada:",
+            "title": "Criptografia de ponta a ponta",
+            "user_signing_private_key_cached_status": "Chave privada de assinatura do usuário:"
+        },
         "developer_mode": "Modo desenvolvedor",
         "developer_tools": "Ferramentas do desenvolvedor",
+        "edit_setting": "Editar configuração",
+        "edit_values": "Editar valores",
+        "empty_string": "<empty string>",
         "event_content": "Conteúdo do Evento",
+        "event_id": "ID do evento: %(eventId)s",
         "event_sent": "Evento enviado!",
         "event_type": "Tipo do Evento",
+        "explore_account_data": "Explore os dados da conta",
+        "explore_room_account_data": "Explore os dados da conta da sala",
+        "explore_room_state": "Explore o estado da sala",
         "failed_to_find_widget": "Ocorreu um erro ao encontrar este widget.",
+        "failed_to_load": "Falha ao carregar.",
+        "failed_to_save": "Falha ao salvar as configurações.",
+        "failed_to_send": "Falha ao enviar o evento!",
+        "id": "ID: ",
+        "invalid_json": "Não parece um JSON válido.",
         "level": "Nível",
+        "low_bandwidth_mode": "Modo de baixa largura de banda",
+        "low_bandwidth_mode_description": "Requer servidor doméstico compatível.",
+        "main_timeline": "Linha do tempo principal",
+        "no_receipt_found": "Nenhum recibo encontrado",
+        "notification_state": "O estado da notificação é<strong>%(notificationState)s</strong>",
+        "notifications_debug": "Depuração de notificações",
+        "number_of_users": "Número de usuários",
         "original_event_source": "Fonte do evento original",
+        "room_encrypted": "A sala está <strong> criptografada ✅ </strong>",
         "room_id": "ID da sala: %(roomId)s",
+        "room_not_encrypted": "A sala <strong> não está criptografada 🚨 </strong>",
+        "room_notifications_dot": "Ponto: ",
+        "room_notifications_highlight": "Destaque: ",
+        "room_notifications_last_event": "Último evento:",
+        "room_notifications_sender": "Remetente: ",
+        "room_notifications_thread_id": "ID do tópico: ",
+        "room_notifications_total": "Total: ",
+        "room_notifications_type": "Tipo: ",
+        "room_status": "Status da sala",
+        "room_unread_status_count": {
+            "one": "Status da sala não lida: <strong> %(status)s</strong>, contagem: <strong> %(count)s </strong>",
+            "other": "Status da sala não lida: <strong> %(status)s</strong>, contagem: <strong> %(count)s </strong>"
+        },
         "save_setting_values": "Salvar valores de configuração",
+        "see_history": "Ver histórico",
+        "send_custom_account_data_event": "Enviar evento de dados de conta personalizado",
+        "send_custom_room_account_data_event": "Enviar evento personalizado de dados da conta da sala",
+        "send_custom_state_event": "Enviar evento de estado personalizado",
+        "send_custom_timeline_event": "Enviar evento de cronograma personalizado",
+        "server_info": "Informações do servidor",
+        "server_versions": "Versões do servidor",
         "settable_global": "Definido globalmente",
         "settable_room": "Definido em cada sala",
         "setting_colon": "Configuração:",
         "setting_definition": "Definição da configuração:",
         "setting_id": "ID da configuração",
+        "settings": {
+            "elementCallUrl": "URL de chamada do Element"
+        },
+        "settings_explorer": "Explorador de configurações",
         "show_hidden_events": "Mostrar eventos ocultos nas conversas",
+        "spaces": {
+            "one": "<space>",
+            "other": "<%(count)s spaces>"
+        },
         "state_key": "Chave do Estado",
+        "thread_root_id": "ID raiz do tópico: %(threadRootId)s",
+        "threads_timeline": "Linha do tempo dos tópicos",
         "title": "Ferramentas de desenvolvimento",
+        "toggle_event": "alternar evento",
         "toolbox": "Ferramentas",
         "use_at_own_risk": "Esta interface de usuário NÃO verifica os tipos de valores. Use por sua conta e risco.",
+        "user_read_up_to": "O usuário leu até: ",
+        "user_read_up_to_ignore_synthetic": "O usuário leu até (ignoreSynthetic): ",
+        "user_read_up_to_private": "O usuário leu até (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "O usuário leu até (m.read.private; ignoreSynthetic): ",
         "value": "Valor",
         "value_colon": "Valor:",
         "value_in_this_room": "Valor nessa sala",
@@ -559,7 +884,9 @@
         "values_explicit_colon": "Valores em níveis explícitos:",
         "values_explicit_room": "Valores em níveis explícitos nessa sala",
         "values_explicit_this_room_colon": "Valores em níveis explícitos nessa sala:",
+        "view_servers_in_room": "Exibir servidores na sala",
         "view_source_decrypted_event_source": "Fonte de evento descriptografada",
+        "view_source_decrypted_event_source_unavailable": "Fonte descriptografada indisponível",
         "widget_screenshots": "Ativar capturas de tela do widget em widgets suportados"
     },
     "dialog_close_label": "Fechar caixa de diálogo",
@@ -584,44 +911,37 @@
     "empty_room_was_name": "Sala vazia (era %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
+            "alternatives": "Se você tiver uma chave de segurança ou frase de segurança, isso também funcionará.",
             "key_validation_text": {
-                "invalid_security_key": "Chave de Segurança inválida",
-                "recovery_key_is_correct": "Muito bem!",
-                "wrong_file_type": "Tipo errado de arquivo",
-                "wrong_security_key": "Chave de Segurança errada"
+                "wrong_security_key": "A chave de recuperação que você inseriu não está correta."
             },
+            "privacy_warning": "Certifique-se de que ninguém possa ver essa tela!",
             "restoring": "Restaurando chaves do backup",
-            "security_key_title": "Chave de Segurança",
-            "security_phrase_incorrect_error": "Não foi possível acessar o armazenamento secreto. Verifique se você digitou a Frase de Segurança correta.",
-            "security_phrase_title": "Frase de segurança",
-            "use_security_key_prompt": "Use sua Chave de Segurança para continuar."
+            "security_key_title": "Chave de recuperação"
         },
         "bootstrap_title": "Configurar chaves",
         "cancel_entering_passphrase_description": "Tem certeza que quer cancelar a introdução da frase de senha?",
         "cancel_entering_passphrase_title": "Cancelar a introdução da frase de senha?",
         "confirm_encryption_setup_body": "Clique no botão abaixo para confirmar a configuração da criptografia.",
         "confirm_encryption_setup_title": "Confirmar a configuração de criptografia",
-        "cross_signing_not_ready": "A autoverificação não está configurada.",
-        "cross_signing_ready": "A autoverificação está pronta para uso.",
-        "cross_signing_ready_no_backup": "A verificação está pronta mas as chaves não tem um backup configurado.",
         "cross_signing_room_normal": "Esta sala é criptografada de ponta a ponta",
         "cross_signing_room_verified": "Todos nesta sala estão confirmados",
         "cross_signing_room_warning": "Alguém está usando uma sessão desconhecida",
-        "cross_signing_unsupported": "Seu servidor não suporta a autoverificação.",
-        "cross_signing_untrusted": "Sua conta tem uma identidade autoverificada em armazenamento secreto, mas ainda não é considerada confiável por esta sessão.",
         "cross_signing_user_normal": "Você não confirmou este usuário.",
         "cross_signing_user_verified": "Você confirmou este usuário. Este usuário confirmou todas as próprias sessões.",
         "cross_signing_user_warning": "Este usuário não confirmou todas as próprias sessões.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Limpar chaves autoverificadas",
-            "title": "Destruir chaves autoverificadas?",
-            "warning": "Apagar chaves de autoverificação é permanente. Qualquer pessoa com quem você se confirmou receberá alertas de segurança. Não é aconselhável fazer isso, a menos que você tenha perdido todos os aparelhos nos quais fez a autoverificação."
-        },
+        "enter_recovery_key": "Insira a chave de recuperação",
         "event_shield_reason_authenticity_not_guaranteed": "A autenticidade desta mensagem criptografada não pode ser garantida neste aparelho.",
         "event_shield_reason_mismatched_sender_key": "Criptografada por uma sessão não confirmada",
+        "event_shield_reason_unknown_device": "Criptografado por um dispositivo desconhecido ou excluído.",
+        "event_shield_reason_unsigned_device": "Criptografado por um dispositivo não verificado por seu proprietário.",
+        "event_shield_reason_unverified_identity": "Criptografado por um usuário não verificado.",
         "export_unsupported": "O seu navegador não suporta as extensões de criptografia necessárias",
+        "forgot_recovery_key": "Esqueceu a chave de recuperação?",
         "import_invalid_keyfile": "Não é um arquivo de chave válido do %(brand)s",
         "import_invalid_passphrase": "Falha ao checar a autenticação: senha incorreta?",
+        "key_storage_out_of_sync": "Seu armazenamento de chaves está fora de sincronia.",
+        "key_storage_out_of_sync_description": "Confirme sua chave de recuperação para manter o acesso ao seu armazenamento de chaves e histórico de mensagens.",
         "messages_not_secure": {
             "cause_1": "Seu servidor local",
             "cause_2": "O servidor doméstico do usuário que você está verificando está conectado",
@@ -636,20 +956,27 @@
             "title": "Nova opção de recuperação",
             "warning": "Se você não definiu a nova opção de recuperação, um invasor pode estar tentando acessar sua conta. Altere a senha da sua conta e defina uma nova opção de recuperação imediatamente nas Configurações."
         },
-        "not_supported": "<não suportado>",
+        "pinned_identity_changed": "A identidade de %(displayName)s (<b>%(userId)s</b>) parece ter mudado. <a>Saiba mais</a>",
+        "pinned_identity_changed_no_displayname": "A identidade de <b>%(userId)s</b> parece ter mudado. <a>Saiba mais</a>",
         "recovery_method_removed": {
             "description_1": "Esta sessão detectou que a sua Frase de Segurança e a chave para mensagens seguras foram removidas.",
             "description_2": "Se você fez isso acidentalmente, você pode configurar Mensagens Seguras nesta sessão, o que vai re-criptografar o histórico de mensagens desta sessão com uma nova opção de recuperação.",
             "title": "Opção de recuperação removida",
             "warning": "Se você não excluiu a opção de recuperação, um invasor pode estar tentando acessar sua conta. Altere a senha da sua conta e defina imediatamente uma nova opção de recuperação nas Configurações."
         },
+        "reset_all_button": "Esqueceu ou perdeu todos os métodos de recuperação? <a>Redefinir tudo</a>",
+        "set_up_recovery": "Configurar a recuperação",
+        "set_up_recovery_later": "Agora não",
+        "set_up_recovery_toast_description": "Gere uma chave de recuperação que possa ser usada para restaurar seu histórico de mensagens criptografadas caso você perca o acesso aos seus dispositivos.",
         "set_up_toast_description": "Proteja-se contra a perda de acesso a mensagens e dados criptografados",
         "set_up_toast_title": "Configurar o backup online",
         "setup_secure_backup": {
-            "explainer": "Faça o backup das suas chaves antes de sair, para evitar perdê-las.",
-            "title": "Configurar"
+            "explainer": "Faça o backup das suas chaves antes de sair, para evitar perdê-las."
         },
+        "turn_on_key_storage": "Ativar o armazenamento de chaves",
+        "turn_on_key_storage_description": "Armazene sua identidade criptográfica e as chaves de mensagem com segurança no servidor. Isso permitirá que você visualize seu histórico de mensagens em qualquer novo dispositivo.",
         "udd": {
+            "interactive_verification_button": "Verificar interativamente por emoji",
             "other_ask_verify_text": "Peça a este usuário para confirmar a sessão dele, ou confirme-a manualmente abaixo.",
             "other_new_session_text": "%(name)s (%(userId)s) entrou em uma nova sessão sem confirmá-la:",
             "own_ask_verify_text": "Confirme suas outras sessões usando uma das opções abaixo.",
@@ -657,57 +984,94 @@
             "title": "Não confiável"
         },
         "unable_to_setup_keys_error": "Não foi possível configurar as chaves",
-        "unsupported": "A sua versão do aplicativo não suporta a criptografia de ponta a ponta.",
         "verification": {
             "accepting": "Aceitando…",
+            "after_new_login": {
+                "device_verified": "Dispositivo verificado",
+                "skip_verification": "Ignorar a verificação por enquanto",
+                "unable_to_verify": "Não foi possível verificar este dispositivo",
+                "verify_this_device": "Verifique este dispositivo"
+            },
             "cancelled": "Você cancelou a confirmação.",
+            "cancelled_self": "Você cancelou a verificação em seu outro dispositivo.",
             "cancelled_user": "%(displayName)s cancelou a confirmação.",
             "cancelling": "Cancelando…",
             "complete_action": "Ok, entendi",
             "complete_description": "Você confirmou este usuário com sucesso.",
             "complete_title": "Confirmado!",
+            "error_starting_description": "Não foi possível iniciar um bate-papo com o outro usuário.",
+            "error_starting_title": "Erro ao iniciar a verificação",
             "explainer": "As mensagens com este usuário estão protegidas com a criptografia de ponta a ponta e não podem ser lidas por terceiros.",
             "in_person": "Para sua segurança, faça isso pessoalmente ou use uma forma confiável de comunicação.",
             "incoming_sas_device_dialog_text_1": "Confirme este aparelho para torná-lo confiável. Confiar neste aparelho fornecerá segurança adicional para você e aos outros ao trocarem mensagens criptografadas de ponta a ponta.",
             "incoming_sas_device_dialog_text_2": "Confirmar este aparelho o marcará como confiável para você e para os usuários que se confirmaram com você.",
             "incoming_sas_dialog_title": "Recebendo solicitação de confirmação",
+            "incoming_sas_dialog_waiting": "Aguardando a confirmação do parceiro…",
             "incoming_sas_user_dialog_text_1": "Confirme este usuário para torná-lo confiável. Confiar nos usuários fornece segurança adicional ao trocar mensagens criptografadas de ponta a ponta.",
             "incoming_sas_user_dialog_text_2": "Se você confirmar esse usuário, a sessão será marcada como confiável para você e para ele.",
+            "no_key_or_device": "Parece que você não tem uma chave de segurança ou qualquer outro dispositivo que possa ser verificado. Este dispositivo não poderá acessar mensagens criptografadas antigas. Para verificar sua identidade neste dispositivo, você precisará redefinir suas chaves de verificação.",
+            "no_support_qr_emoji": "O dispositivo que você está tentando verificar não suporta a leitura de um código QR ou verificação de emoji, que é o que %(brand)s suporta. Tente com um cliente diferente.",
             "other_party_cancelled": "Seu contato cancelou a confirmação.",
             "prompt_encrypted": "Verifique todos os usuários em uma sala para se certificar de que ela está segura.",
             "prompt_self": "Iniciar a confirmação novamente, após a notificação.",
             "prompt_unencrypted": "Em salas criptografadas, verifique todos os usuários para garantir a segurança.",
             "prompt_user": "Iniciar a confirmação novamente, a partir do perfil deste usuário.",
             "qr_or_sas": "%(qrCode)s ou %(emojiCompare)s",
+            "qr_or_sas_header": "Verifique este dispositivo concluindo um dos seguintes procedimentos:",
             "qr_prompt": "Escaneie este código único",
+            "qr_reciprocate_same_shield_device": "Quase lá! O seu outro dispositivo está mostrando o mesmo escudo?",
             "qr_reciprocate_same_shield_user": "Quase lá! Este escudo também aparece para %(displayName)s?",
+            "request_toast_accept": "Verificar sessão",
+            "request_toast_accept_user": "Verificar usuário",
+            "request_toast_decline_counter": "Ignorar (%(counter)s)",
             "request_toast_detail": "%(deviceId)s de %(ip)s",
+            "reset_proceed_prompt": "Prosseguir com a reposição",
+            "sas_caption_self": "Verifique este dispositivo confirmando que o seguinte número aparece em sua tela.",
             "sas_caption_user": "Confirme este usuário, comparando os números a seguir que serão exibidos na sua e na tela dele.",
             "sas_description": "Compare um conjunto único de emojis se você não tem uma câmera em nenhum dos dois aparelhos",
+            "sas_emoji_caption_self": "Confirme se os emojis abaixo são exibidos em ambos os dispositivos, na mesma ordem:",
             "sas_emoji_caption_user": "Confirme este usuário confirmando os emojis a seguir exibidos na tela dele.",
             "sas_match": "São coincidentes",
             "sas_no_match": "Elas não são correspondentes",
             "sas_prompt": "Comparar emojis únicos",
             "scan_qr": "Confirmar através de QR Code",
             "scan_qr_explainer": "Peça para %(displayName)s escanear o seu código:",
+            "self_verification_hint": "Para continuar, aceite a solicitação de verificação em seu outro dispositivo.",
             "start_button": "Iniciar confirmação",
             "successful_device": "Você confirmou %(deviceName)s (%(deviceId)s) com êxito!",
             "successful_own_device": "Você confirmou o seu aparelho com êxito!",
             "successful_user": "Você confirmou %(displayName)s com sucesso!",
             "timed_out": "O tempo de confirmação se esgotou.",
             "unsupported_method": "Nenhuma opção de confirmação é suportada.",
+            "unverified_session_toast_accept": "Sim, fui eu",
             "unverified_session_toast_title": "Novo login. Foi você?",
             "unverified_sessions_toast_description": "Revise para assegurar que sua conta está segura",
             "unverified_sessions_toast_reject": "Mais tarde",
+            "unverified_sessions_toast_title": "Você tem sessões não verificadas",
+            "verification_description": "Verifique sua identidade para acessar mensagens criptografadas e provar sua identidade para outras pessoas.",
+            "verification_dialog_title_device": "Verifique outro dispositivo",
             "verification_dialog_title_user": "Solicitação de confirmação",
+            "verification_skip_warning": "Sem verificar, você não terá acesso a todas as suas mensagens e poderá aparecer como não confiável para outras pessoas.",
+            "verification_success_with_backup": "O seu novo dispositivo agora está verificado. Ele tem acesso às suas mensagens criptografadas, e outros usuários o verão como confiável.",
+            "verification_success_without_backup": "Seu novo dispositivo agora foi verificado. Outros usuários o verão como confiável.",
             "verify_emoji": "Confirmar por emojis",
             "verify_emoji_prompt": "Confirmar comparando emojis únicos.",
             "verify_emoji_prompt_qr": "Se você não consegue escanear o código acima, confirme comparando emojis únicos.",
+            "verify_later": "Vou verificar mais tarde",
+            "verify_using_device": "Verifique com outro dispositivo",
+            "verify_using_key": "Verifique com a chave de segurança",
+            "verify_using_key_or_phrase": "Verificar com chave de segurança ou frase",
             "waiting_for_user_accept": "Aguardando %(displayName)s aceitar…",
+            "waiting_other_device": "Aguardando sua verificação em seu outro dispositivo…",
+            "waiting_other_device_details": "Esperando que você verifique em seu outro dispositivo, %(deviceName)s (%(deviceId)s)...",
             "waiting_other_user": "Aguardando %(displayName)s confirmar…"
         },
+        "verification_requested_toast_title": "Verificação solicitada",
+        "verified_identity_changed": "A identidade verificada de %(displayName)s (<b>%(userId)s</b>) foi alterada. <a>Saiba mais</a>",
+        "verified_identity_changed_no_displayname": "A identidade verificada de <b>%(userId)s</b> foi alterada. <a>Saiba mais</a>",
         "verify_toast_description": "Outras(os) usuárias(os) podem não confiar nela",
-        "verify_toast_title": "Confirmar esta sessão"
+        "verify_toast_title": "Confirmar esta sessão",
+        "withdraw_verification_action": "Retirar verificação"
     },
     "error": {
         "admin_contact": "Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando este serviço.",
@@ -716,9 +1080,11 @@
         "cannot_load_config": "Incapaz de carregar arquivo de configuração: por favor atualize a página para tentar de novo.",
         "connection": "Ocorreu um problema de comunicação com o servidor local. Tente novamente mais tarde.",
         "dialog_description_default": "Ocorreu um erro.",
+        "download_media": "Falha ao baixar a mídia de origem, nenhum URL de origem foi encontrado",
         "edit_history_unsupported": "O seu servidor local não parece suportar este recurso.",
         "failed_copy": "Não foi possível copiar",
         "hs_blocked": "Este servidor local foi bloqueado pelo seu administrador.",
+        "invalid_configuration_mixed_server": "Configuração inválida: um default_hs_url não pode ser especificado junto com default_server_name ou default_server_config",
         "invalid_configuration_no_server": "Configuração inválida: nenhum servidor default especificado.",
         "invalid_json": "Sua configuração do Element contém JSON inválido. Por favor corrija o problema e recarregue a página.",
         "invalid_json_detail": "A mensagem do parser é: %(message)s",
@@ -740,23 +1106,29 @@
         "storage_evicted_description_1": "Alguns dados de sessão, incluindo chaves de mensagens criptografadas, estão faltando. Desconecte-se e entre novamente para resolver isso, o que restaurará as chaves do backup.",
         "storage_evicted_description_2": "O seu navegador provavelmente removeu esses dados quando o espaço de armazenamento ficou insuficiente.",
         "storage_evicted_title": "Dados de sessão ausentes",
+        "sync": "Não foi possível conectar ao Homeserver. Tentando novamente…",
         "tls": "Não foi possível conectar ao Servidor de Base. Por favor, confira sua conectividade à internet, garanta que o <a>certificado SSL do Servidor de Base</a> é confiável, e que uma extensão do navegador não esteja bloqueando as requisições de rede.",
         "unknown": "Erro desconhecido",
         "unknown_error_code": "código de erro desconhecido",
         "update_power_level": "Não foi possível alterar o nível de permissão"
     },
+    "error_app_open_in_another_tab": "Mude para a outra guia para se conectar em %(brand)s. Essa guia agora pode ser fechada.",
+    "error_app_open_in_another_tab_title": "%(brand)s está conectado em outra guia",
+    "error_app_opened_in_another_window": "%(brand)s está aberto em outra janela. Clique em \"%(label)s\" para usar %(brand)s aqui e desconectar a outra janela.",
+    "error_database_closed_description": {
+        "for_desktop": "Seu disco pode estar cheio. Por favor, libere algum espaço e recarregue.",
+        "for_web": "Se você limpou os dados de navegação, esta mensagem é esperada. %(brand)s também pode estar aberto em outra guia ou seu disco está cheio. Por favor, libere algum espaço e recarregue"
+    },
+    "error_database_closed_title": "%(brand)s parou de funcionar",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Não foi possível copiar um link da sala para a área de transferência.",
             "title": "Não foi possível copiar o link da sala"
         },
         "error_loading_user_profile": "Não foi possível carregar o perfil do usuário",
-        "forget_room_failed": "Falhou ao esquecer a sala %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "O servidor pode estar indisponível, sobrecarregado, ou a busca ultrapassou o tempo limite :(",
-            "title": "Busca falhou"
-        }
+        "forget_room_failed": "Falhou ao esquecer a sala %(errCode)s"
     },
+    "error_user_not_logged_in": "O usuário não está logado",
     "event_preview": {
         "m.call.answer": {
             "dm": "Chamada em andamento",
@@ -772,9 +1144,29 @@
             "dm_send": "Aguardando a resposta",
             "user": "%(senderName)s iniciou uma chamada",
             "you": "Você iniciou uma chamada"
-        }
+        },
+        "m.emote": "* %(senderName)s %(emote)s",
+        "m.reaction": {
+            "user": "%(sender)s reagiu %(reaction)s a %(message)s",
+            "you": "Você reagiu %(reaction)s a %(message)s"
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Áudio",
+            "file": "Arquivo",
+            "image": "Imagem",
+            "poll": "Enquete",
+            "video": "Vídeo"
+        },
+        "preview": "<bold>%(prefix)s: </bold> %(preview)s"
     },
     "export_chat": {
+        "cancelled": "Exportação cancelada",
+        "cancelled_detail": "A exportação foi cancelada com sucesso",
+        "confirm_stop": "Tem certeza de que deseja parar de exportar seus dados? Se o fizer, terá de começar de novo.",
+        "creating_html": "Criando HTML...",
+        "creating_output": "Criando saída...",
         "creator_summary": "%(creatorName)s criou esta sala.",
         "current_timeline": "Linha do tempo atual",
         "enter_number_between_min_max": "Insira um número entre %(min)s e %(max)s",
@@ -785,20 +1177,47 @@
             "one": "%(count)s evento exportado em %(seconds)s segundos",
             "other": "%(count)s eventos exportados em %(seconds)s segundos"
         },
+        "exporting_your_data": "Exportando seus dados",
+        "fetched_n_events": {
+            "one": "Buscado %(count)s evento até agora",
+            "other": "Buscado %(count)s eventos até agora"
+        },
+        "fetched_n_events_in_time": {
+            "one": "Buscado %(count)s evento em%(seconds)s s",
+            "other": "Buscado %(count)s eventos em%(seconds)s s"
+        },
+        "fetched_n_events_with_total": {
+            "one": "Buscado%(count)s evento fora de%(total)s",
+            "other": "Buscado%(count)s eventos fora de%(total)s"
+        },
+        "fetching_events": "Buscando eventos...",
         "file_attached": "Arquivo Anexado",
+        "format": "Formato",
         "from_the_beginning": "Do começo",
         "generating_zip": "Gerando um ZIP",
+        "html": "HTML",
+        "html_title": "Dados Exportados",
         "include_attachments": "Incluir Anexos",
+        "json": "JSON",
         "media_omitted": "Mídia omitida",
         "media_omitted_file_size": "Mídia omitida - tamanho do arquivo excede o limite",
         "messages": "Mensagens",
+        "next_page": "Próximo grupo de mensagens",
         "num_messages": "Número de mensagens",
         "num_messages_min_max": "Número de mensagens pode ser apenas um número entre %(min)s e %(max)s",
         "number_of_messages": "Especifique um número de mensagens",
+        "previous_page": "Grupo anterior de mensagens",
+        "processing": "Processando...",
         "processing_event_n": "Processando evento %(number)s de %(total)s",
+        "select_option": "Selecione uma das opções abaixo para exportar bate-papos da sua linha do tempo",
         "size_limit": "Limite de Tamanho",
         "size_limit_min_max": "O tamanho pode ser apenas um número entre %(min)s MB e %(max)s MB",
+        "size_limit_postfix": "MB",
+        "starting_export": "Iniciando exportação…",
+        "successful": "Exportação bem-sucedida",
+        "successful_detail": "Sua exportação foi bem-sucedida. Encontre na sua pasta de Downloads.",
         "text": "Texto Simples",
+        "title": "Exportar bate-papo",
         "topic": "Tópico: %(topic)s",
         "unload_confirm": "Tem certeza de que deseja sair durante esta exportação?"
     },
@@ -807,7 +1226,9 @@
         "can_contact_label": "Vocês podem me contactar se tiverem quaisquer perguntas subsequentes",
         "comment_label": "Comentário",
         "existing_issue_link": "Por favor, consulte os <existingIssuesLink> erros conhecidos no Github </existingIssuesLink> antes de enviar o seu. Se ninguém tiver mencionado o seu erro, <newIssueLink> informe-nos sobre um erro novo </newIssueLink>.",
-        "pro_type": "DICA: se você nos informar um erro, envie <debugLogsLink> relatórios de erro </debugLogsLink> para nos ajudar a rastrear o problema.",
+        "may_contact_label": "Você pode entrar em contato comigo se quiser acompanhar ou me deixar testar as próximas ideias",
+        "platform_username": "Sua plataforma e nome de usuário serão registrados para nos ajudar a usar seu feedback o máximo possível.",
+        "pro_type": "DICA: se você nos informar um erro, envie <debugLogsLink> relatórios de erro</debugLogsLink> para nos ajudar a rastrear o problema.",
         "send_feedback_action": "Enviar comentário",
         "sent": "Comentário enviado"
     },
@@ -819,7 +1240,9 @@
     },
     "forward": {
         "filter_placeholder": "Procurar por salas ou pessoas",
+        "message_preview_heading": "Pré-visualização da mensagem",
         "no_perms_title": "Você não tem permissão para fazer isso",
+        "open_room": "Sala aberta",
         "send_label": "Enviar",
         "sending": "Enviando",
         "sent": "Enviado"
@@ -828,6 +1251,7 @@
         "change": "Alterar o servidor de identidade",
         "change_prompt": "Desconectar-se do servidor de identidade <current /> e conectar-se em <new /> em vez disso?",
         "change_server_prompt": "Se você não quiser usar <server /> para descobrir e ser detectável pelos contatos existentes, digite outro servidor de identidade abaixo.",
+        "changed": "Seu servidor de identidade foi alterado",
         "checking": "Verificando servidor",
         "description_connected": "No momento, você está usando <server></server> para descobrir e ser descoberto pelos contatos existentes que você conhece. Você pode alterar seu servidor de identidade abaixo.",
         "description_disconnected": "No momento, você não está usando um servidor de identidade. Para descobrir e ser descoberto pelos contatos existentes, adicione um abaixo.",
@@ -859,24 +1283,44 @@
         "other": "Em %(spaceName)s e %(count)s outros espaços."
     },
     "incompatible_browser": {
-        "title": "Browser insuportado"
+        "continue": "Continuar mesmo assim",
+        "description": "%(brand)s usa alguns recursos do navegador que não estão disponíveis no seu navegador atual. %(detail)s",
+        "detail_can_continue": "Se você continuar, alguns recursos podem parar de funcionar e há o risco de você perder dados no futuro.",
+        "detail_no_continue": "Tente atualizar este navegador se não estiver usando a versão mais recente e tente novamente.",
+        "learn_more": "Saber mais",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "",
+        "title": "Browser insuportado",
+        "use_desktop_heading": "Use %(brand)s Desktop em vez disso",
+        "use_mobile_heading": "Em vez disso %(brand)s, use no celular",
+        "use_mobile_heading_after_desktop": "Ou use nosso aplicativo móvel",
+        "windows_64bit": "Windows (64 bits)",
+        "windows_arm_64bit": "Windows (ARM 64 bits)"
     },
     "info_tooltip_title": "Informação",
     "integration_manager": {
+        "connecting": "Conectando-se ao gerenciador de integração...",
         "error_connecting": "Ou o gerenciador de integrações está indisponível, ou ele não conseguiu acessar o seu servidor.",
         "error_connecting_heading": "Não foi possível conectar ao gerenciador de integrações",
         "explainer": "O gerenciador de integrações recebe dados de configuração e pode modificar widgets, enviar convites para salas e definir níveis de permissão em seu nome.",
         "manage_title": "Gerenciar integrações",
+        "toggle_label": "Habilitar o gerenciador de integração",
         "use_im": "Use o gerenciador de integrações para gerenciar bots, widgets e pacotes de figurinhas.",
         "use_im_default": "Use o gerenciador de integrações em <b>(%(serverName)s)</b> para gerenciar bots, widgets e pacotes de figurinhas."
     },
     "integrations": {
+        "disabled_dialog_description": "Habilite '%(manageIntegrations)s' em Configurações para fazer isto.",
         "disabled_dialog_title": "As integrações estão desativadas",
         "impossible_dialog_description": "Seu %(brand)s não permite que você use o gerenciador de integrações para fazer isso. Entre em contato com o administrador.",
         "impossible_dialog_title": "As integrações não estão permitidas"
     },
     "invite": {
+        "ask_anyway_description": "Não foi possível encontrar perfis para os Matrix IDs listados abaixo - você gostaria de iniciar um DM mesmo assim?",
+        "ask_anyway_label": "Inicie o DM de qualquer maneira",
+        "ask_anyway_never_warn_label": "Inicie o DM mesmo assim e nunca mais me avise",
         "email_caption": "Convidar por e-mail",
+        "email_limit_one": "Os convites por e-mail só podem ser enviados um de cada vez",
         "email_use_default_is": "Use um servidor de identidade para convidar por e-mail. <default>Use o padrão (%(defaultIdentityServerName)s)</default> ou um servidor personalizado em <settings>Configurações</settings>.",
         "email_use_is": "Use um servidor de identidade para convidar por e-mail. Gerencie o servidor em <settings>Configurações</settings>.",
         "error_already_invited_room": "O usuário já foi convidado para a sala",
@@ -884,6 +1328,7 @@
         "error_already_joined_room": "O usuário já está na sala",
         "error_already_joined_space": "O usuário já está no espaço",
         "error_bad_state": "O banimento do usuário precisa ser removido antes de ser convidado.",
+        "error_dm": "Não foi possível criar seu DM.",
         "error_find_room": "Ocorreu um erro ao tentar convidar os usuários.",
         "error_find_user_description": "Os seguintes usuários não puderam ser convidados porque não existem ou são inválidos: %(csvNames)s",
         "error_find_user_title": "Falha ao encontrar os seguintes usuários",
@@ -892,9 +1337,12 @@
         "error_permissions_space": "Você não tem permissão para convidar pessoas para este espaço.",
         "error_profile_undisclosed": "O usuário pode ou não existir",
         "error_transfer_multiple_target": "Uma chamada só pode ser transferida para um único usuário.",
+        "error_unfederated_room": "Esta sala não é federada. Você não pode convidar pessoas de servidores externos.",
+        "error_unfederated_space": "Esse espaço não é federado. Você não pode convidar pessoas de servidores externos.",
         "error_unknown": "Erro de servidor desconhecido",
         "error_user_not_found": "O usuário não existe",
-        "error_version_unsupported_room": "O servidor desta(e) usuária(o) não suporta a versão desta sala.",
+        "error_version_unsupported_room": "O servidor doméstico do usuário não é compatível com a versão da sala.",
+        "error_version_unsupported_space": "O servidor doméstico do usuário não é compatível com a versão do espaço.",
         "failed_generic": "A operação falhou",
         "failed_title": "Falha ao enviar o convite",
         "invalid_address": "Endereço não reconhecido",
@@ -906,15 +1354,21 @@
         "room_failed_partial": "Nós enviamos aos outros, mas as pessoas abaixo não puderam ser convidadas para <RoomName/>",
         "room_failed_partial_title": "Alguns convites não puderam ser enviados",
         "room_failed_title": "Falha ao convidar usuários para %(roomName)s",
+        "send_link_prompt": "Ou envie o link do convite",
         "start_conversation_name_email_mxid_prompt": "Comece uma conversa, a partir do nome, e-mail ou nome de usuário de alguém (por exemplo: <userId/>).",
         "start_conversation_name_mxid_prompt": "Comece uma conversa, a partir do nome ou nome de usuário de alguém (por exemplo: <userId/>).",
+        "suggestions_disclaimer": "Algumas sugestões podem estar ocultas por motivos de privacidade.",
+        "suggestions_disclaimer_prompt": "Se você não consegue ver quem está procurando, envie o link do convite abaixo.",
         "suggestions_section": "Conversas recentes",
+        "to_room": "Convide para %(roomName)s",
         "to_space": "Convidar para %(spaceName)s",
         "transfer_dial_pad_tab": "Teclado de discagem",
+        "transfer_user_directory_tab": "Diretório de usuários",
         "unable_find_profiles_description_default": "Não é possível encontrar perfis para os IDs da Matrix listados abaixo - você gostaria de convidá-los mesmo assim?",
         "unable_find_profiles_invite_label_default": "Convide mesmo assim",
         "unable_find_profiles_invite_never_warn_label_default": "Convide mesmo assim e nunca mais me avise",
-        "unable_find_profiles_title": "Os seguintes usuários podem não existir"
+        "unable_find_profiles_title": "Os seguintes usuários podem não existir",
+        "unban_first_title": "O usuário não pode ser convidado até que seja desbanido"
     },
     "inviting_user1_and_user2": "Convidando %(user1)s e %(user2)s",
     "inviting_user_and_n_others": {
@@ -927,52 +1381,109 @@
     },
     "keyboard": {
         "activate_button": "Apertar no botão selecionado",
+        "alt": "Alt",
         "autocomplete_cancel": "Cancelar o preenchimento automático",
+        "autocomplete_force": "Forçar conclusão",
+        "autocomplete_navigate_next": "Próxima sugestão de preenchimento automático",
+        "autocomplete_navigate_prev": "Sugestão anterior de preenchimento automático",
+        "backspace": "Backspace",
         "cancel_reply": "Cancelar resposta à mensagem",
         "category_autocomplete": "Preencher automaticamente",
         "category_calls": "Chamadas",
         "category_navigation": "Navegação",
         "category_room_list": "Lista de salas",
         "close_dialog_menu": "Fechar caixa de diálogo ou menu",
+        "composer_jump_end": "Ir para o final do compositor",
+        "composer_jump_start": "Ir para o início do compositor",
+        "composer_navigate_next_history": "Navegue para a próxima mensagem no histórico do compositor",
+        "composer_navigate_prev_history": "Navegue até a mensagem anterior no histórico do compositor",
         "composer_new_line": "Nova linha",
         "composer_redo": "Refazer edição",
         "composer_toggle_bold": "Negrito",
+        "composer_toggle_code_block": "Alternar para bloco de código",
         "composer_toggle_italics": "Itálico",
-        "composer_toggle_quote": "Citar",
+        "composer_toggle_link": "Alternar para link",
+        "composer_toggle_quote": "Alternar para citação",
         "composer_undo": "Desfazer edição",
+        "control": "Ctrl",
         "dismiss_read_marker_and_jump_bottom": "Ignorar o marcador de leitura e ir para o final",
+        "end": "Fim",
+        "enter": "Entrar",
+        "escape": "Esc",
         "go_home_view": "Ir para a tela inicial",
+        "home": "Início",
         "jump_first_message": "Ir para primeira mensagem",
         "jump_last_message": "Ir para a última mensagem",
         "jump_room_search": "Ir para a pesquisa de salas",
         "jump_to_read_marker": "Ir para a mensagem não lida mais antiga",
+        "keyboard_shortcuts_tab": "Abra esta guia de configurações",
         "navigate_next_history": "Próxima sala ou espaço visitado recentemente",
+        "navigate_next_message_edit": "Navegar até a próxima mensagem para editar",
         "navigate_prev_history": "Sala ou espaço visitado recentemente",
+        "navigate_prev_message_edit": "Navegar até a mensagem anterior para editar",
+        "next_landmark": "Ir para o próximo ponto de referência",
+        "next_room": "Próxima sala ou mensagem privada",
+        "next_unread_room": "Próxima sala não lida ou mensagem direta",
         "number": "[número]",
         "open_user_settings": "Abrir as configurações do usuário",
+        "page_down": "Página para baixo",
+        "page_up": "Página para cima",
+        "prev_landmark": "Ir para o ponto de referência anterior",
+        "prev_room": "Sala anterior ou mensagem privada",
+        "prev_unread_room": "Sala anterior não lida ou mensagem privada",
         "room_list_collapse_section": "Esconder seção da lista de salas",
         "room_list_expand_section": "Mostrar seção da lista de salas",
+        "room_list_navigate_down": "Navegar para baixo na lista de salas",
+        "room_list_navigate_up": "Navegar para cima na lista de salas",
         "room_list_select_room": "Selecionar sala da lista de salas",
+        "scroll_down_timeline": "Rola para baixo no histórico",
+        "scroll_up_timeline": "Rola para cima no histórico",
         "search": "Pesquisar (deve estar ativado)",
         "send_sticker": "Enviar uma figurinha",
+        "shift": "Shift",
         "space": "Barra de espaço",
         "switch_to_space": "Mudar para o espaço por número",
+        "toggle_hidden_events": "Ativar a visibilidade de eventos ocultos",
         "toggle_microphone_mute": "Ativar/desativar som do microfone",
         "toggle_right_panel": "Alternar o painel na direita",
+        "toggle_space_panel": "Alternar para painel de espaço",
         "toggle_top_left_menu": "Alternar o menu superior esquerdo",
+        "toggle_webcam_mute": "Ligar/desligar a webcam",
         "upload_file": "Enviar um arquivo"
     },
     "labs": {
+        "allow_screen_share_only_mode": "Permitir somente o modo de compartilhamento de tela",
+        "ask_to_join": "Habilitar pedir para participar",
         "automatic_debug_logs": "Enviar automaticamente logs de depuração em qualquer erro",
+        "automatic_debug_logs_decryption": "Enviar automaticamente logs sobre erros de descriptografia",
+        "automatic_debug_logs_key_backup": "Envie automaticamente logs quando o backup da chave não estiver funcionando",
+        "beta_description": "O que vem por aí no %(brand)s? Os laboratórios são a melhor maneira de obter informações antecipadas, testar novos recursos e ajudar a moldá-los antes do lançamento.",
+        "beta_feature": "Este é um recurso beta",
         "beta_feedback_leave_button": "Para sair do beta, vá nas suas configurações.",
+        "beta_feedback_title": "Comentários beta para %(featureName)s",
+        "beta_section": "Próximos recursos",
         "bridge_state": "Exibir informações sobre integrações nas configurações das salas",
         "bridge_state_channel": "Canal: <channelLink/>",
         "bridge_state_creator": "Esta integração foi disponibilizada por <user />.",
         "bridge_state_manager": "Esta integração é desenvolvida por <user />.",
         "bridge_state_workspace": "Espaço de trabalho: <networkLink/>",
+        "click_for_info": "Clique para mais informações",
+        "currently_experimental": "Atualmente experimental.",
         "custom_themes": "Permite adicionar temas personalizados",
+        "dynamic_room_predecessors": "Predecessores de salas dinâmicas",
+        "dynamic_room_predecessors_description": "Habilitar MSC3946 (para dar suporte a arquivos de chat para quem chegar mais tarde)",
+        "element_call_video_rooms": "Salas de vídeo Element Call",
+        "exclude_insecure_devices": "Excluir dispositivos inseguros ao enviar/receber mensagens",
+        "exclude_insecure_devices_description": "Quando esse modo estiver ativado, as mensagens criptografadas não serão compartilhadas com dispositivos não verificados e as mensagens de dispositivos não verificados serão mostradas como um erro. Observe que, se você ativar esse modo, talvez não consiga se comunicar com usuários que não verificaram seus dispositivos.",
+        "experimental_description": "Está se sentindo experimental? Experimente nossas últimas ideias em desenvolvimento. Esses recursos não estão finalizados; eles podem ser instáveis, podem mudar ou podem ser descartados completamente.<a> Saiba mais</a> .",
+        "experimental_section": "Pré-visualizações antecipadas",
+        "extended_profiles_msc_support": "Requer que seu servidor ofereça suporte ao MSC4133",
+        "feature_disable_call_per_sender_encryption": "Desativar a criptografia por remetente para Element Call",
+        "feature_wysiwyg_composer_description": "Use rich text em vez de Markdown no compositor de mensagens.",
+        "group_calls": "Nova experiência de chamada em grupo",
         "group_developer": "Desenvolvedor",
         "group_encryption": "Criptografia",
+        "group_experimental": "Experimental",
         "group_messaging": "Mensagens",
         "group_moderation": "Moderação",
         "group_profile": "Perfil",
@@ -980,9 +1491,48 @@
         "group_spaces": "Espaços",
         "group_themes": "Temas",
         "group_threads": "Tópicos",
+        "group_ui": "Interface do usuário",
         "group_voip": "Voz e vídeo",
+        "group_widgets": "Widgets",
+        "hidebold": "Ocultar ponto de notificação (exibir somente emblemas de contadores)",
+        "html_topic": "Mostrar representação HTML dos tópicos da sala",
+        "join_beta": "Participe da versão beta",
+        "join_beta_reload": "Aderir à versão beta irá recarregar %(brand)s.",
+        "jump_to_date": "Ir para a data (adiciona cabeçalhos /jumptodate e pular para a data)",
+        "jump_to_date_msc_support": "Requer que seu servidor suporte MSC3030",
         "latex_maths": "Renderizar fórmulas matemáticas LaTeX em mensagens",
-        "video_rooms": "Salas de vídeo"
+        "leave_beta": "Sair da versão beta",
+        "leave_beta_reload": "Sair da versão beta irá recarregar %(brand)s.",
+        "location_share_live": "Compartilhamento de localização ao vivo",
+        "location_share_live_description": "Implementação temporária. As localizações persistem no histórico da sala.",
+        "mjolnir": "Novas formas de ignorar as pessoas",
+        "msc3531_hide_messages_pending_moderation": "Permitir que os moderadores ocultem mensagens com moderação pendente.",
+        "new_room_list": "Habilitar nova lista de salas",
+        "notification_settings": "Novas configurações de notificação",
+        "notification_settings_beta_caption": "Apresentamos uma maneira mais simples de alterar suas configurações de notificação. Personalize o seu %(brand)s, da forma que quiser.",
+        "notification_settings_beta_title": "Configurações de notificação",
+        "notifications": "Ative o painel de notificações no cabeçalho da sala",
+        "release_announcement": "Anúncio de lançamento",
+        "render_reaction_images": "Renderizar imagens personalizadas em reações",
+        "render_reaction_images_description": "Às vezes chamados de “emojis personalizados”.",
+        "report_to_moderators": "Reportar aos moderadores",
+        "report_to_moderators_description": "Em salas que aceitam moderação, o botão \"Denunciar\" permitirá que você denuncie abusos aos moderadores da sala.",
+        "sliding_sync": "Modo Sliding Sync",
+        "sliding_sync_description": "Em desenvolvimento ativo, não pode ser desativado.",
+        "sliding_sync_disabled_notice": "Saia e entre novamente para desativar",
+        "sliding_sync_server_no_support": "Seu servidor não tem suporte",
+        "under_active_development": "Em desenvolvimento ativo.",
+        "unrealiable_e2e": "Não confiável em salas criptografadas",
+        "video_rooms": "Salas de vídeo",
+        "video_rooms_a_new_way_to_chat": "Uma nova maneira de conversar por voz e vídeo em %(brand)s .",
+        "video_rooms_always_on_voip_channels": "As salas de vídeo são canais VoIP sempre ativos incorporados em uma sala em %(brand)s .",
+        "video_rooms_beta": "As salas de vídeo são um recurso beta",
+        "video_rooms_faq1_answer": "Use o botão “+” na seção da sala do painel esquerdo.",
+        "video_rooms_faq1_question": "Como posso criar uma sala de vídeo?",
+        "video_rooms_faq2_answer": "Sim, a linha do tempo do chat é exibida ao lado do vídeo.",
+        "video_rooms_faq2_question": "Posso usar o chat de texto junto com a videochamada?",
+        "video_rooms_feedbackSubheading": "Obrigado por experimentar a versão beta. Forneça o máximo de detalhes que você puder para que possamos aprimorá-la.",
+        "wysiwyg_composer": "Editor de texto rico"
     },
     "labs_mjolnir": {
         "advanced_warning": "⚠ Essas configurações são destinadas a usuários avançados.",
@@ -1001,6 +1551,7 @@
         "lists_heading": "Listas inscritas",
         "lists_new_label": "ID da sala ou endereço da lista de banidos",
         "no_lists": "Você não está inscrito em nenhuma lista",
+        "personal_description": "Sua lista pessoal de banimento contém todos os usuários/servidores dos quais você pessoalmente não deseja receber mensagens. Depois de ignorar o primeiro usuário/servidor, uma nova sala aparecerá na sua lista de salas chamada '%(myBanList)s' - permaneça nessa sala para manter a lista de banimento em vigor.",
         "personal_empty": "Você não bloqueou ninguém.",
         "personal_heading": "Lista pessoal de banidos",
         "personal_new_label": "Servidor ou ID de usuário para bloquear",
@@ -1018,9 +1569,12 @@
     },
     "language_dropdown_label": "Menu suspenso de idiomas",
     "leave_room_dialog": {
+        "last_person_warning": "Você é a única pessoa aqui. Se você sair, ninguém poderá se juntar no futuro, inclusive você.",
         "leave_room_question": "Tem certeza de que deseja sair da sala '%(roomName)s'?",
         "leave_space_question": "Tem certeza de que deseja sair desse espaço '%(spaceName)s'?",
-        "room_rejoin_warning": "Esta sala não é pública. Você não poderá voltar sem ser convidada/o.",
+        "room_leave_admin_warning": "Você é o único administrador nesta sala. Se você sair, ninguém poderá alterar as configurações da sala ou realizar outras ações importantes.",
+        "room_leave_mod_warning": "Você é o único moderador nesta sala. Se você sair, ninguém poderá alterar as configurações da sala ou realizar outras ações importantes.",
+        "room_rejoin_warning": "Esta sala não é pública. Você não poderá entrar novamente sem um convite.",
         "space_rejoin_warning": "Este espaço não é público. Você não poderá entrar novamente sem um convite."
     },
     "left_panel": {
@@ -1028,32 +1582,87 @@
     },
     "lightbox": {
         "rotate_left": "Girar para a esquerda",
-        "rotate_right": "Girar para a direita"
+        "rotate_right": "Girar para a direita",
+        "title": "Visualização da imagem"
     },
     "location_sharing": {
+        "MapStyleUrlNotConfigured": "Esse​•​servidor​•​doméstico​•​não​•​está​•​configurado​•​para​•​exibir​•​mapas.",
+        "MapStyleUrlNotReachable": "Esse​•​servidor​•​doméstico​•​não​•​está​•​configurado​•​corretamente​•​para​•​exibir​•​mapas,​•​ou​•​o​•​servidor​•​de​•​mapas​•​configurado​•​pode​•​estar​•​inacessível.",
+        "WebGLNotEnabled": "O​•​WebGL​•​é​•​necessário​•​para​•​exibir​•​mapas,​•​ative-o​•​nas​•​configurações​•​do​•​seu​•​navegador.",
+        "click_drop_pin": "Clique​•​para​•​soltar​•​um​•​alfinete",
+        "click_move_pin": "Clique​•​para​•​mover​•​o​•​alfinete",
+        "close_sidebar": "Fechar​•​barra​•​lateral",
+        "error_fetch_location": "Não​•​foi​•​possível​•​obter​•​a​•​localização",
+        "error_no_perms_description": "Você​•​precisa​•​ter​•​as​•​permissões​•​corretas​•​para​•​compartilhar​•​locais​•​nesta​•​sala.",
+        "error_no_perms_title": "Você​•​não​•​tem​•​permissão​•​para​•​compartilhar​•​locais",
+        "error_send_description": "%(brand)s​•​não​•​pôde​•​enviar​•​a​•​sua​•​localização.​•​Tente​•​novamente​•​mais​•​tarde.",
+        "error_send_title": "Não foi possível enviar sua localização",
+        "error_sharing_live_location": "Ocorreu​•​um​•​erro​•​ao​•​compartilhar​•​sua​•​localização​•​ao​•​vivo",
+        "error_stopping_live_location": "Ocorreu​•​um​•​erro​•​ao​•​interromper​•​sua​•​localização​•​ao​•​vivo",
+        "expand_map": "Expandir​•​mapa",
+        "failed_generic": "Falha​•​ao​•​buscar​•​sua​•​localização.​•​Tente​•​novamente​•​mais​•​tarde.",
+        "failed_load_map": "Não​•​é​•​possível​•​carregar​•​o​•​mapa",
+        "failed_permission": "%(brand)s teve​•​sua​•​permissão​•​negada​•​para​•​buscar​•​sua​•​localização.​•​Permita​•​o​•​acesso​•​à​•​localização​•​nas​•​configurações​•​do​•​seu​•​navegador.",
+        "failed_timeout": "Tempo​•​limite​•​esgotado​•​ao​•​tentar​•​buscar​•​sua​•​localização.​•​Tente​•​novamente​•​mais​•​tarde.",
+        "failed_unknown": "Erro​•​desconhecido​•​ao​•​buscar​•​local.​•​Tente​•​novamente​•​mais​•​tarde.",
         "find_my_location": "Encontrar minha localização",
+        "live_description": "Localização​•​ao​•​vivo​•​de​•​%(displayName)s",
+        "live_enable_description": "Observação: este é um recurso de laboratório que usa uma implementação temporária. Isso significa que você não poderá excluir seu histórico de localização, e usuários avançados poderão ver seu histórico de localização mesmo depois que você parar de compartilhar sua localização ao vivo com esta sala.",
+        "live_enable_heading": "Compartilhamento​•​de​•​localização​•​ao​•​vivo",
+        "live_location_active": "Você está compartilhando sua localização ao vivo",
+        "live_location_enabled": "Localização ao vivo ativada",
+        "live_location_ended": "Localização​•​ao​•​vivo​•​encerrada",
+        "live_location_error": "Erro​•​de​•​localização​•​ao​•​vivo",
+        "live_locations_empty": "Sem​•​locais​•​ao​•​vivo",
+        "live_share_button": "Compartilhe​•​por​•​%(duration)s",
+        "live_toggle_label": "Ativar​•​o​•​compartilhamento​•​de​•​localização​•​ao​•​vivo",
+        "live_until": "Ao​•​vivo​•​até​•​%(expiryTime)s",
+        "live_update_time": "Atualizado​•​%(humanizedUpdateTime)s",
+        "loading_live_location": "Carregando​•​local​•​ao​•​vivo...",
         "location_not_available": "Local não disponível",
-        "share_button": "Compartilhar localização"
+        "map_feedback": "Feedback​•​do​•​mapa",
+        "mapbox_logo": "Logotipo​•​da​•​Mapbox",
+        "reset_bearing": "Redefinir​•​o​•​rumo​•​para​•​o​•​norte",
+        "share_button": "Compartilhar localização",
+        "share_type_live": "Minha​•​localização​•​ao​•​vivo",
+        "share_type_own": "Minha​•​localização​•​atual",
+        "share_type_pin": "Solte​•​um​•​alfinete",
+        "share_type_prompt": "Que​•​tipo​•​de​•​localização​•​você​•​deseja​•​compartilhar?",
+        "toggle_attribution": "Alternar​•​atribuição"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s membro",
+            "other": "%(count)s membros"
+        },
         "filter_placeholder": "Pesquisar participantes da sala",
+        "invite_button_no_perms_tooltip": "Você não tem permissão para convidar usuários",
+        "invited_label": "Convidado",
+        "no_matches": "Sem correspondências",
         "power_label": "%(userName)s (nível de permissão %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Membros da sala",
     "message_edit_dialog_title": "Edições na mensagem",
+    "migrating_crypto": "Aguente firme. Estamos atualizando %(brand)s para tornar a criptografia mais rápida e confiável.",
     "mobile_guide": {
         "toast_accept": "Usar o aplicativo",
+        "toast_description": "%(brand)s é experimental em um navegador da Web móvel. Para uma melhor experiência e os recursos mais recentes, use nosso aplicativo nativo gratuito.",
         "toast_title": "Use o aplicativo para ter uma experiência melhor"
     },
+    "name_and_id": "%(name)s (%(userId)s)",
     "no_more_results": "Não há mais resultados",
     "notif_panel": {
-        "empty_description": "Não há notificações."
+        "empty_description": "Não há notificações.",
+        "empty_heading": "Isso é tudo, pessoal!"
     },
     "notifications": {
         "all_messages": "Todas as mensagens novas",
         "all_messages_description": "Seja notificado para cada mensagem",
+        "class_global": "Global",
         "class_other": "Outros",
         "default": "Padrão",
+        "default_settings": "Corresponder às configurações padrão",
+        "email_pusher_app_display_name": "Notificações por e-mail",
         "enable_prompt_toast_description": "Ativar notificações na área de trabalho",
         "enable_prompt_toast_title": "Notificações",
         "enable_prompt_toast_title_from_message_send": "Não perca uma resposta",
@@ -1061,13 +1670,18 @@
         "keyword": "Palavra-chave",
         "keyword_new": "Nova palavra-chave",
         "level_activity": "Atividade recente",
+        "level_highlight": "Destaque",
+        "level_muted": "Silenciado",
         "level_none": "Nenhuma",
+        "level_notification": "Notificação",
+        "level_unsent": "Não enviado",
         "mark_all_read": "Marcar tudo como lido",
         "mentions_and_keywords": "@menções e palavras-chave",
         "mentions_and_keywords_description": "Receba notificações apenas com menções e palavras-chave conforme definido em suas <a>configurações</a>",
-        "mentions_keywords": "Menções e palavras-chave",
+        "mentions_keywords": "Menções e palavras-chave!",
         "message_didnt_send": "A mensagem não foi enviada. Clique para mais informações.",
-        "mute_description": "Você não receberá nenhuma notificação"
+        "mute_description": "Você não receberá nenhuma notificação",
+        "mute_room": "Silenciar sala"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s está solicitando confirmação"
@@ -1076,16 +1690,41 @@
         "create_room": "Criar um chat de grupo",
         "explore_rooms": "Explorar salas públicas",
         "has_avatar_label": "Ótimo, agora as pessoas identificarão você",
+        "intro_byline": "Seja dono de suas conversas.",
         "intro_welcome": "Boas-vindas ao %(appName)s",
         "no_avatar_label": "Adicione uma foto para as pessoas identificarem você.",
         "send_dm": "Enviar uma mensagem",
         "welcome_detail": "Agora, vamos começar",
         "welcome_user": "Boas-vindas, %(name)s"
     },
+    "pill": {
+        "permalink_other_room": "Mensagem em %(room)s",
+        "permalink_this_room": "Mensagem de %(user)s"
+    },
     "poll": {
+        "create_poll_action": "Criar Enquete",
         "create_poll_title": "Criar enquete",
+        "disclosed_notes": "Os participantes veem os resultados assim que votam",
+        "edit_poll_title": "Editar enquete",
+        "end_description": "Tem certeza de que deseja encerrar esta enquete? Isso mostrará os resultados finais da enquete e impedirá que as pessoas possam votar.",
+        "end_message": "A enquete terminou. Resposta principal: %(topAnswer)s",
+        "end_message_no_votes": "A enquete terminou. Nenhum voto foi dado.",
+        "end_title": "Encerrar Enquete",
+        "error_ending_description": "Desculpe, a enquete não terminou. Por favor, tente novamente.",
+        "error_ending_title": "Falha ao finalizar a enquete",
         "error_voting_description": "Desculpe, seu voto não foi registrado. Por favor, tente novamente.",
         "error_voting_title": "Voto não registrado",
+        "failed_send_poll_description": "Desculpe, a enquete que você tentou criar não foi publicada.",
+        "failed_send_poll_title": "Falha ao postar enquete",
+        "notes": "Os resultados só são revelados quando você encerra a enquete",
+        "options_add_button": "Adicionar opção",
+        "options_heading": "Criar opções",
+        "options_label": "Opção %(number)s",
+        "options_placeholder": "Escrever uma opção",
+        "topic_heading": "Qual é a pergunta ou o tópico da sua enquete?",
+        "topic_label": "Pergunta ou tópico",
+        "topic_placeholder": "Escreva algo…",
+        "total_decryption_errors": "Devido a erros de descriptografia, alguns votos podem não ser contados",
         "total_n_votes": {
             "one": "%(count)s votos expressos. Vote para ver os resultados",
             "other": "%(count)s votos expressos. Vote para ver os resultados"
@@ -1094,7 +1733,13 @@
             "one": "Com base na votação de %(count)s",
             "other": "Com base em %(count)s votos"
         },
-        "total_no_votes": "Sem votos expressos"
+        "total_no_votes": "Sem votos expressos",
+        "total_not_ended": "Os resultados ficarão visíveis quando a enquete terminar",
+        "type_closed": "Enquete encerrada",
+        "type_heading": "Tipo de enquete",
+        "type_open": "Enquete aberta",
+        "unable_edit_description": "Desculpe, você não pode editar uma enquete após os votos terem sido registrados.",
+        "unable_edit_title": "Não é possível editar a enquete"
     },
     "power_level": {
         "admin": "Administrador/a",
@@ -1105,16 +1750,20 @@
         "moderator": "Moderador/a",
         "restricted": "Restrito"
     },
+    "powered_by_matrix": "Desenvolvido por Matrix",
     "powered_by_matrix_with_logo": "Chat descentralizado e encriptado & colaboração, powered by $matrixLogo",
     "presence": {
         "away": "Ausente",
+        "busy": "Ocupado",
         "idle": "Ocioso",
         "idle_for": "Inativo há %(duration)s",
+        "offline": "Offline",
         "offline_for": "Offline há %(duration)s",
-        "online": "Conectada/o",
+        "online": "Disponível",
         "online_for": "Online há %(duration)s",
         "unknown": "Desconhecido",
-        "unknown_for": "Status desconhecido há %(duration)s"
+        "unknown_for": "Status desconhecido há %(duration)s",
+        "unreachable": "Servidor do usuário inacessível"
     },
     "quick_settings": {
         "all_settings": "Todas as configurações",
@@ -1128,85 +1777,175 @@
     },
     "redact": {
         "confirm_button": "Confirmar a remoção",
+        "confirm_description": "Tem certeza de que deseja remover (excluir) este evento?",
+        "confirm_description_state": "Observe que remover alterações na sala desta forma pode desfazer a alteração.",
         "error": "Você não pode apagar esta mensagem. (%(code)s)",
         "ongoing": "Removendo…",
         "reason_label": "Motivo (opcional)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Tem certeza de que deseja recusar o convite?",
-        "failed": "Falha ao tentar recusar o convite",
-        "title": "Recusar o convite"
-    },
     "report_content": {
-        "description": "Reportar esta mensagem enviará o seu 'event ID' único para o/a administrador/a do seu Homeserver. Se as mensagens nesta sala são criptografadas, o/a administrador/a não conseguirá ler o texto da mensagem nem ver nenhuma imagem ou arquivo.",
+        "description": "Denunciar essa mensagem enviará seu “ID de evento” exclusivo ao administrador do seu servidor doméstico. Se as mensagens nesta sala forem criptografadas, o administrador do servidor doméstico não poderá ler o texto da mensagem nem visualizar arquivos ou imagens.",
+        "disagree": "Discordo",
+        "error_create_room_moderation_bot": "Não é possível criar espaço com o bot de moderação",
+        "hide_messages_from_user": "Marque se você deseja ocultar todas as mensagens atuais e futuras desse usuário.",
+        "ignore_user": "Ignorar usuário",
+        "illegal_content": "Conteúdo ilegal",
         "missing_reason": "Por favor, descreva porque você está reportando.",
+        "nature": "Escolha uma natureza e descreva o que torna essa mensagem abusiva.",
+        "nature_disagreement": "O que esse usuário está escrevendo está errado.\nIsso será relatado aos moderadores da sala.",
+        "nature_illegal": "Este usuário está exibindo comportamento ilegal, por exemplo, expondo pessoas (doxxing) ou ameaçando de violência.\nIsso será relatado aos moderadores da sala, que podem encaminhar isso às autoridades legais.",
+        "nature_nonstandard_admin": "Esta sala é dedicada a conteúdo ilegal ou tóxico ou os moderadores não conseguem moderar conteúdo ilegal ou tóxico.\nIsso será relatado aos administradores do %(homeserver)s.",
+        "nature_nonstandard_admin_encrypted": "Esta sala é dedicada a conteúdo ilegal ou tóxico ou os moderadores não conseguem moderar conteúdo ilegal ou tóxico.\nIsso será relatado aos administradores do %(homeserver)s. Os administradores NÃO poderão ler o conteúdo criptografado desta sala.",
+        "nature_other": "Qualquer outro motivo. Descreva o problema.\nIsso será relatado aos moderadores da sala.",
+        "nature_spam": "Esse usuário está enviando spam para a sala com anúncios, links para anúncios ou propaganda.\nIsso será relatado aos moderadores da sala.",
+        "nature_toxic": "Esse usuário está exibindo comportamento tóxico, por exemplo, insultando outros usuários ou compartilhando conteúdo somente para adultos em um sala para menores de idade ou violando as regras desta sala. Isso será relatado aos moderadores da sala.",
         "other_label": "Outros",
-        "report_content_to_homeserver": "Denunciar conteúdo ao administrador do seu servidor principal"
+        "report_content_to_homeserver": "Denunciar conteúdo ao administrador do seu servidor principal",
+        "report_entire_room": "Denunciar a sala inteira",
+        "spam_or_propaganda": "Spam ou propaganda",
+        "toxic_behaviour": "Comportamento tóxico"
+    },
+    "report_room": {
+        "description": "Denuncie esta sala ao administrador do seu servidor. Isso enviará a ID exclusiva da sala, mas se as mensagens forem criptografadas, o administrador não poderá lê-las nem visualizar os arquivos compartilhados.",
+        "reason_label": "Descreva o motivo"
     },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Falha ao descriptografar as sessões de %(failedCount)s!",
         "count_of_successfully_restored_keys": "%(sessionCount)s chaves foram restauradas com sucesso",
-        "enter_key_description": "Acesse o seu histórico de mensagens seguras e configure as mensagens seguras, ao inserir a sua Chave de Segurança.",
-        "enter_key_title": "Digite a Chave de Segurança",
+        "enter_key_description": "Acesse o seu histórico de mensagens seguras e configure as mensagens seguras, ao inserir a sua Chave de Recuperação.",
+        "enter_key_title": "Digite a Chave de Recuperação",
         "enter_phrase_description": "Acesse o seu histórico de mensagens seguras e configure mensagens seguras digitando a sua Frase de Segurança.",
         "enter_phrase_title": "Digite a Frase de Segurança",
         "incorrect_security_phrase_dialog": "O backup não pôde ser descriptografado com esta Frase de Segurança: verifique se você digitou a Frase de Segurança correta.",
         "incorrect_security_phrase_title": "Frase de Segurança incorreta",
         "key_backup_warning": "<b>Atenção</b>: você só deve configurar o backup de chave em um computador de sua confiança.",
-        "key_forgotten_text": "Se você esqueceu a sua Chave de Segurança, você pode <button>definir novas opções de recuperação</button>",
-        "key_is_invalid": "Chave de Segurança inválida",
-        "key_is_valid": "Essa Chave de Segurança é válida!",
+        "key_fetch_in_progress": "Obtendo chaves do servidor…",
+        "key_forgotten_text": "Se você esqueceu a sua Chave de Recuperação, você pode <button>definir novas opções de recuperação</button>",
+        "key_is_invalid": "Chave de Recuperação inválida",
+        "key_is_valid": "Essa Chave de Recuperação é válida!",
         "keys_restored_title": "Chaves restauradas",
         "load_error_content": "Não foi possível carregar o status do backup",
         "load_keys_progress": "%(completed)s de %(total)s chaves restauradas",
         "no_backup_error": "Nenhum backup encontrado!",
-        "phrase_forgotten_text": "Se você esqueceu a sua Frase de Segurança, você pode <button1>usar a sua Chave de Segurança</button1> ou <button2>definir novas opções de recuperação</button2>",
-        "recovery_key_mismatch_description": "Não foi possível descriptografar o backup com esta chave de segurança: verifique se você digitou a chave de segurança correta.",
-        "recovery_key_mismatch_title": "Incompatibilidade da Chave de Segurança",
+        "phrase_forgotten_text": "Se você esqueceu a sua Frase de Segurança, você pode <button1>usar a sua Chave de Recuperação</button1> ou <button2>definir novas opções de recuperação</button2>",
+        "recovery_key_mismatch_description": "O backup não pôde ser descriptografado com essa chave de recuperação: verifique se você inseriu a chave de recuperação correta.",
+        "recovery_key_mismatch_title": "Incompatibilidade da chave de recuperação",
         "restore_failed_error": "Não foi possível restaurar o backup"
     },
     "right_panel": {
-        "add_integrations": "Adicionar widgets, integrações e bots",
+        "add_integrations": "Adicionar extensões",
+        "add_topic": "Adicionar tópico",
+        "extensions_button": "Extensões",
+        "extensions_empty_description": "Selecione “%(addIntegrations)s” para navegar e adicionar extensões a esta sala",
+        "extensions_empty_title": "Aumente a produtividade com mais ferramentas, widgets e bots",
         "files_button": "Arquivos",
         "pinned_messages": {
+            "empty_description": "Selecione uma mensagem e escolha “%(pinAction)s” para incluí-la aqui.",
+            "empty_title": "Fixe mensagens importantes para que elas possam ser facilmente descobertas",
+            "header": {
+                "one": "1 Mensagem fixada",
+                "other": "%(count)s Mensagens fixadas"
+            },
             "limits": {
                 "other": "Você pode fixar até %(count)s widgets"
-            }
+            },
+            "menu": "Abrir menu",
+            "release_announcement": {
+                "close": "OK",
+                "description": "Encontre todas as mensagens fixadas aqui. Passe o mouse sobre qualquer mensagem e selecione “Fixar” para adicioná-la.",
+                "title": "Todas as novas mensagens fixadas"
+            },
+            "reply_thread": "Responder a uma mensagem de <link>discussão</link>",
+            "unpin_all": {
+                "button": "Desfixar todas as mensagens",
+                "content": "Certifique-se de que você realmente deseja remover todas as mensagens fixadas. Essa ação não pode ser desfeita.",
+                "title": "Desfixar todas as mensagens?"
+            },
+            "view": "Ver no histórico"
         },
+        "pinned_messages_button": "Mensagens fixadas",
         "poll": {
+            "active_heading": "Enquetes ativas",
+            "empty_active": "Não há enquetes ativas nesta sala",
+            "empty_active_load_more": "Não há enquetes ativas. Carregue mais enquetes para ver as enquetes dos meses anteriores",
+            "empty_active_load_more_n_days": {
+                "one": "Não há pesquisas ativas no último dia. Carregue mais enquetes para ver as enquetes dos meses anteriores",
+                "other": "Não há pesquisas ativas para os últimos %(count)s dias. Carregue mais enquetes para ver as enquetes dos meses anteriores"
+            },
+            "empty_past": "Não há enquetes anteriores nesta sala",
+            "empty_past_load_more": "Não há enquetes anteriores. Carregue mais enquetes para ver as enquetes dos meses anteriores",
+            "empty_past_load_more_n_days": {
+                "one": "Não há pesquisas anteriores para o dia anterior. Carregue mais enquetes para ver as enquetes dos meses anteriores",
+                "other": "Não há pesquisas anteriores nos últimos %(count)s dias. Carregue mais enquetes para ver as enquetes dos meses anteriores"
+            },
             "final_result": {
                 "one": "Resultado final baseado em %(count)s votos",
                 "other": "Resultado final baseado em %(count)s votos"
-            }
+            },
+            "load_more": "Carregar mais enquetes",
+            "loading": "Carregando enquetes",
+            "past_heading": "Enquetes anteriores",
+            "view_in_timeline": "Exibir enquete no histórico",
+            "view_poll": "Ver enquete"
+        },
+        "polls_button": "Enquetes",
+        "room_summary_card": {
+            "title": "Informação da sala"
+        },
+        "thread_list": {
+            "context_menu_label": "Opções de tópico"
         },
         "video_room_chat": {
             "title": "Bate-papo"
         }
     },
     "room": {
+        "3pid_invite_email_not_found_account": "Este convite foi enviado para %(email)s que não está associado à sua conta",
         "3pid_invite_email_not_found_account_room": "Este convite para %(roomName)s foi enviado para %(email)s, que não está associado à sua conta",
+        "3pid_invite_error_description": "Um erro (%(errcode)s) foi retornado ao tentar validar seu convite. Você pode tentar passar essas informações para a pessoa que o convidou.",
         "3pid_invite_error_invite_action": "Tentar entrar mesmo assim",
         "3pid_invite_error_invite_subtitle": "Você só pode participar com um convite válido.",
+        "3pid_invite_error_public_subtitle": "Você ainda pode entrar aqui.",
+        "3pid_invite_error_title": "Algo deu errado com seu convite.",
         "3pid_invite_error_title_room": "Ocorreu um erro no seu convite para %(roomName)s",
         "3pid_invite_no_is_subtitle": "Use um servidor de identidade em Configurações para receber convites diretamente no %(brand)s.",
+        "banned_by": "Você foi banido por %(memberName)s",
         "banned_from_room_by": "Você foi banido de %(roomName)s por %(memberName)s",
         "context_menu": {
             "copy_link": "Copiar link da sala",
             "favourite": "Favoritar",
             "forget": "Esquecer Sala",
             "low_priority": "Baixa prioridade",
+            "mark_read": "Marcar como lido",
+            "mark_unread": "Marcar como não lido",
+            "notifications_default": "Corresponder à configuração padrão",
+            "notifications_mute": "Silenciar sala",
             "title": "Opções da Sala",
             "unfavourite": "Favoritado"
         },
+        "creating_room_text": "Estamos​•​criando​•​uma​•​sala​•​com​•​%(names)s",
         "dm_invite_action": "Começar a conversa",
         "dm_invite_subtitle": "<userName/> quer conversar",
         "dm_invite_title": "Deseja conversar com %(user)s?",
         "drop_file_prompt": "Arraste um arquivo aqui para enviar",
+        "edit_topic": "Editar tópico",
+        "error_3pid_invite_email_lookup": "Não foi possível encontrar o usuário por e-mail",
+        "error_cancel_knock_title": "Falha ao cancelar",
+        "error_join_403": "Você precisa de um convite para acessar esta sala.",
+        "error_join_404_1": "Você tentou entrar usando um ID de sala sem fornecer uma lista de servidores pelos quais ingressar. Os IDs das salas são identificadores internos e não podem ser usados para entrar em uma sala sem informações adicionais.",
+        "error_join_404_2": "Se você souber o endereço de uma sala, tente entrar por lá.",
         "error_join_404_invite": "A pessoa que o convidou já saiu ou o servidor dela está offline.",
         "error_join_404_invite_same_hs": "A pessoa que o convidou já saiu.",
         "error_join_connection": "Ocorreu um erro ao entrar.",
+        "error_join_incompatible_version_1": "Desculpe, seu servidor doméstico é muito antigo para participar aqui.",
         "error_join_incompatible_version_2": "Por favor, entre em contato com o administrador do seu homeserver.",
         "error_join_title": "Falha ao entrar",
+        "error_jump_to_date": "Servidor​•​retornou%(statusCode)s​•​com​•​código​•​de​•​erro%(errorCode)s",
+        "error_jump_to_date_connection": "Ocorreu um erro de rede ao tentar localizar e ir para a data especificada. Seu servidor doméstico pode estar inativo ou houve apenas um problema temporário com sua conexão com a Internet. Tente novamente. Se isso continuar, entre em contato com o administrador do servidor doméstico.",
+        "error_jump_to_date_details": "Detalhes​•​do​•​erro",
+        "error_jump_to_date_not_found": "Não​•​foi​•​possível​•​encontrar​•​um​•​evento​•​com​•​data​•​anterior​•​a​•​%(dateString)s.​•​Tente​•​escolher​•​uma​•​data​•​anterior.",
+        "error_jump_to_date_send_logs_prompt": "Envie​•​os​•​registros​•​de​•​<debugLogsLink>​•​depuração​•​</debugLogsLink>​•​para​•​nos​•​ajudar​•​a​•​rastrear​•​o​•​problema.",
+        "error_jump_to_date_title": "Não​•​foi​•​possível​•​encontrar​•​o​•​evento​•​nessa​•​data",
         "face_pile_summary": {
             "one": "%(count)s pessoa que você conhece já entrou",
             "other": "%(count)s pessoas que você conhece já entraram"
@@ -1219,17 +1958,30 @@
         "face_pile_tooltip_shortcut_joined": "Incluindo você, %(commaSeparatedMembers)s",
         "failed_reject_invite": "Não foi possível recusar o convite",
         "forget_room": "Esquecer esta sala",
+        "forget_space": "Esqueça este espaço",
         "header": {
+            "n_people_asking_to_join": {
+                "one": "Pedindo para participar",
+                "other": "%(count)s pessoas pedindo para participar"
+            },
             "room_is_public": "Esta sala é pública"
         },
+        "header_avatar_open_settings_label": "Abrir configurações de sala",
+        "header_face_pile_tooltip": "Pessoas",
+        "header_untrusted_label": "Não confiável",
+        "inaccessible": "Esta sala ou espaço não está acessível neste momento.",
         "inaccessible_name": "%(roomName)s não está acessível neste momento.",
+        "inaccessible_subtitle_1": "Tente novamente mais tarde ou peça a um administrador de sala ou espaço para verificar se você tem acesso.",
+        "inaccessible_subtitle_2": "%(errcode)s foi retornado ao tentar acessar a sala ou o espaço. Se você acha que está vendo essa mensagem por engano, <issueLink>envie uma comunicação de bug</issueLink>.",
         "intro": {
             "dm_caption": "Apenas vocês dois estão nesta conversa, a menos que algum de vocês convide mais alguém.",
             "enable_encryption_prompt": "Ative a criptografia nas configurações.",
+            "encrypted_3pid_dm_pending_join": "Depois que todos entrarem, você poderá conversar",
             "no_avatar_label": "Adicione uma imagem para que as pessoas possam identificar facilmente sua sala.",
             "no_topic": "<a>Adicione uma descrição</a> para ajudar as pessoas a saber do que se trata essa conversa.",
             "private_unencrypted_warning": "Suas mensagens privadas normalmente são criptografadas, mas esta sala não é. Isto acontece normalmente por conta de um dispositivo ou método usado sem suporte, como convites via email, por exemplo.",
             "room_invite": "Convidar apenas a esta sala",
+            "send_message_start_dm": "Envie sua primeira mensagem para convidar <displayName/> para conversar",
             "start_of_dm_history": "Este é o início do seu histórico da conversa com <displayName/>.",
             "start_of_room": "Este é o início de <roomName/>.",
             "topic": "Descrição: %(topic)s ",
@@ -1239,35 +1991,84 @@
             "you_created": "Você criou esta sala."
         },
         "invite_email_mismatch_suggestion": "Compartilhe este e-mail em Configurações para receber convites diretamente no %(brand)s.",
-        "invite_reject_ignore": "Recusar e bloquear usuário",
+        "invite_sent_to_email": "Este convite foi enviado para %(email)s",
         "invite_sent_to_email_room": "Este convite para %(roomName)s foi enviado para %(email)s",
-        "invite_subtitle": "<userName/> convidou você",
+        "invite_subtitle": "Convidado por <userName/>",
         "invite_this_room": "Convidar para esta sala",
         "invite_title": "Deseja se juntar a %(roomName)s?",
         "inviter_unknown": "Desconhecido",
+        "invites_you_text": "<inviter/> convida você",
         "join_button_account": "Inscrever-se",
+        "join_failed_needs_invite": "Para ver %(roomName)s, você precisa de um convite",
         "join_the_discussion": "Participar da discussão",
+        "join_title": "Entre na sala para participar",
         "join_title_account": "Participar da conversa com uma conta",
+        "joining": "Juntando-se…",
+        "joining_room": "Entrando na sala…",
+        "joining_space": "Entrando no espaço…",
         "jump_read_marker": "Ir diretamente para a primeira das mensagens não lidas.",
         "jump_to_bottom_button": "Ir para as mensagens recentes",
         "jump_to_date": "Ir para Data",
+        "jump_to_date_beginning": "O​•​começo​•​da​•​sala",
+        "jump_to_date_prompt": "Escolha​•​uma​•​data​•​para​•​ir",
         "kick_reason": "Razão: %(reason)s",
+        "kicked_by": "Você foi removido por %(memberName)s",
+        "kicked_from_room_by": "Você foi removido de %(roomName)s por %(memberName)s",
+        "knock_cancel_action": "Cancelar solicitação",
+        "knock_denied_subtitle": "Como seu acesso foi negado, você não pode voltar a menos que seja convidado pelo administrador ou moderador do grupo.",
+        "knock_denied_title": "Seu acesso foi negado",
+        "knock_message_field_placeholder": "Mensagem (opcional)",
+        "knock_prompt": "Pedir para participar?",
+        "knock_prompt_name": "Pedir para participar de %(roomName)s?",
+        "knock_send_action": "Solicitar acesso",
+        "knock_sent": "Solicitação de adesão enviada",
+        "knock_sent_subtitle": "Sua solicitação de adesão está pendente.",
+        "knock_subtitle": "Você precisa ter acesso a esta sala para visualizar ou participar da conversa. Você pode enviar uma solicitação de adesão abaixo.",
         "leave_error_title": "Erro ao sair da sala",
         "leave_server_notices_description": "Esta sala é usada para mensagens importantes do Homeserver, então você não pode sair dela.",
         "leave_server_notices_title": "Não é possível sair da sala Avisos do Servidor",
         "leave_unexpected_error": "Erro inesperado no servidor, ao tentar sair da sala",
         "link_email_to_receive_3pid_invite": "Vincule esse e-mail à sua conta em Configurações, para receber convites diretamente no %(brand)s.",
+        "loading_preview": "Carregando pré-visualização",
         "no_peek_join_prompt": "%(roomName)s não pode ser visualizado. Deseja participar?",
+        "no_peek_no_name_join_prompt": "Não há pré-visualização, você gostaria de participar?",
+        "not_found_subtitle": "Tem certeza de que está no lugar certo?",
+        "not_found_title": "Esta sala ou espaço não existe.",
         "not_found_title_name": "%(roomName)s não existe.",
         "peek_join_prompt": "Você está visualizando %(roomName)s. Deseja participar?",
+        "pinned_message_badge": "Mensagem fixada",
+        "pinned_message_banner": {
+            "button_close_list": "Fechar lista",
+            "button_view_all": "Ver tudo",
+            "description": "Esta sala tem mensagens fixadas. Clique para visualizá-las.",
+            "go_to_message": "Veja a mensagem fixada no histórico.",
+            "title": "<bold>%(index)s de %(length)s </bold> mensagens fixadas"
+        },
+        "read_topic": "Clique para ler o tópico",
+        "rejecting": "Rejeitando o convite...",
         "rejoin_button": "Entrar novamente",
+        "search": {
+            "all_rooms_button": "Pesquisar todos as salas",
+            "placeholder": "Pesquisar mensagens...",
+            "summary": {
+                "one": "1 resultado encontrado para “<query/>”",
+                "other": "%(count)s resultados encontrados para “<query/>”"
+            },
+            "this_room_button": "Pesquise nesta sala"
+        },
         "status_bar": {
+            "delete_all": "Excluir​•​tudo",
             "exceeded_resource_limit": "Sua mensagem não foi enviada porque este servidor local excedeu o limite de recursos. Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando o serviço.",
+            "homeserver_blocked": "Sua​•​mensagem​•​não​•​foi​•​enviada​•​porque​•​esse​•​servidor​•​doméstico​•​foi​•​bloqueado​•​pelo​•​administrador.​•​<a>Entre​•​em​•​contato​•​com​•​o​•​administrador​•​do​•​serviço​•​</a>​•​para​•​continuar​•​usando​•​o​•​serviço.",
             "monthly_user_limit_reached": "Sua mensagem não foi enviada porque este homeserver atingiu seu Limite de usuário ativo mensal. Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando o serviço.",
             "requires_consent_agreement": "Você não pode enviar nenhuma mensagem até revisar e concordar com <consentLink>nossos termos e condições</consentLink>.",
+            "retry_all": "Repetir​•​tudo",
+            "select_messages_to_retry": "Você​•​pode​•​selecionar​•​todas​•​as​•​mensagens​•​ou​•​mensagens​•​individuais​•​para​•​tentar​•​novamente​•​ou​•​excluir",
             "server_connectivity_lost_description": "Imagens enviadas ficarão armazenadas até que sua conexão seja reestabelecida.",
-            "server_connectivity_lost_title": "A conexão com o servidor foi perdida. Verifique sua conexão de internet."
+            "server_connectivity_lost_title": "A conexão com o servidor foi perdida. Verifique sua conexão de internet.",
+            "some_messages_not_sent": "Algumas​•​de​•​suas​•​mensagens​•​não​•​foram​•​enviadas"
         },
+        "unknown_status_code_for_timeline_jump": "código​•​de​•​status​•​desconhecido",
         "unread_notifications_predecessor": {
             "other": "Você tem %(count)s notificações não lidas em uma versão anterior desta sala.",
             "one": "Você tem %(count)s notificações não lidas em uma versão anterior desta sala."
@@ -1284,31 +2085,92 @@
                 "other": "Enviando o arquivo %(filename)s e %(count)s outros arquivos"
             },
             "uploading_single_file": "Enviando o arquivo %(filename)s"
-        }
+        },
+        "video_room": "Esta sala é uma sala de vídeo",
+        "waiting_for_join_subtitle": "Uma​•​vez​•​que​•​os​•​usuários​•​convidados​•​tenham​•​entrado​•​em​•​%(brand)s,​•​você​•​poderá​•​conversar​•​e​•​a​•​sala​•​será​•​criptografada​•​de​•​ponta​•​a​•​ponta",
+        "waiting_for_join_title": "Aguardando​•​que​•​os​•​usuários​•​se​•​juntem​•​a​•​%(brand)s"
     },
     "room_list": {
         "add_room_label": "Adicionar sala",
         "add_space_label": "Adicionar espaço",
+        "appearance": "Aparência",
         "breadcrumbs_empty": "Nenhuma sala foi visitada recentemente",
         "breadcrumbs_label": "Salas visitadas recentemente",
+        "empty": {
+            "no_chats": "Ainda não há conversas.",
+            "no_chats_description": "Comece enviando uma mensagem para alguém ou criando uma sala",
+            "no_chats_description_no_room_rights": "Comece enviando uma mensagem para alguém",
+            "no_favourites": "Você ainda não tem o bate-papo favorito",
+            "no_favourites_description": "Você pode adicionar um bate-papo aos seus favoritos nas configurações de bate-papo",
+            "no_invites": "Você não tem nenhum convite não lido",
+            "no_mentions": "Você não tem nenhuma menção não lida",
+            "no_people": "Você ainda não tem conversas diretas com ninguém",
+            "no_people_description": "Você pode desmarcar os filtros para ver suas outras conversas",
+            "no_rooms": "Você não está em nenhuma sala ainda",
+            "no_rooms_description": "Você pode desmarcar os filtros para ver suas outras conversas.",
+            "no_unread": "Parabéns! Você não tem nenhuma mensagem não lida",
+            "show_activity": "Ver todas as atividades",
+            "show_chats": "Mostrar todas as conversas"
+        },
         "failed_add_tag": "Falha ao adicionar a tag %(tagName)s para a sala",
         "failed_remove_tag": "Falha ao remover a tag %(tagName)s da sala",
+        "failed_set_dm_tag": "Falha ao definir a marca de mensagem direta",
+        "filters": {
+            "favourite": "Favoritos",
+            "invites": "Convites",
+            "mentions": "Menções",
+            "people": "Pessoas",
+            "rooms": "Salas",
+            "unread": "Não lido"
+        },
         "home_menu_label": "Opções do Início",
         "join_public_room_label": "Entrar na sala pública",
         "joining_rooms_status": {
             "one": "Entrando na %(count)s sala",
             "other": "Entrando atualmente em %(count)s salas"
         },
+        "list_title": "Lista de salas",
+        "more_options": {
+            "copy_link": "Copiar link da sala",
+            "favourited": "Favoritado",
+            "leave_room": "Sair da sala",
+            "low_priority": "Baixa prioridade",
+            "mark_read": "Marcar como lido",
+            "mark_unread": "Marcar como não lido"
+        },
         "notification_options": "Alterar notificações",
+        "open_space_menu": "Abrir menu do espaço",
+        "primary_filters": "Filtros da lista de salas",
+        "redacting_messages_status": {
+            "one": "Atualmente removendo mensagens em %(count)s sala",
+            "other": "Atualmente removendo mensagens em %(count)s salas"
+        },
+        "room": {
+            "more_options": "Mais opções",
+            "open_room": "Abrir sala %(roomName)s"
+        },
+        "room_options": "Opções da Sala",
         "show_less": "Mostrar menos",
+        "show_message_previews": "Mostrar prévias de mensagens",
         "show_n_more": {
             "other": "Mostrar %(count)s a mais",
             "one": "Mostrar %(count)s a mais"
         },
         "show_previews": "Mostrar pré-visualizações de mensagens",
+        "sort": "Ordenar",
         "sort_by": "Classificar por",
         "sort_by_activity": "Atividade recente",
+        "sort_by_alphabet": "De A a Z",
+        "sort_type": {
+            "activity": "Atividade",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Mostrar salas não lidas em primeiro",
+        "space_menu": {
+            "home": "Espaço",
+            "space_settings": "Configurações de espaço"
+        },
+        "space_menu_label": "Menu de %(spaceName)s",
         "sublist_options": "Opções da Lista",
         "suggested_rooms_heading": "Salas sugeridas"
     },
@@ -1322,10 +2184,14 @@
             "error_upgrade_title": "Falha ao atualizar a sala",
             "information_section_room": "Informação da sala",
             "information_section_space": "Informações do espaço",
+            "room_id": "ID interno da sala",
             "room_predecessor": "Ler mensagens antigas em %(roomName)s.",
             "room_upgrade_button": "Atualizar a versão desta sala",
+            "room_upgrade_warning": "<b>Aviso</b> : atualizar um quarto irá<i> não migre automaticamente os membros da sala para a nova versão da sala.</i> Publicaremos um link para a nova sala na versão antiga da sala - os membros da sala terão que clicar neste link para entrar na nova sala.",
             "room_version": "Versão da sala:",
             "room_version_section": "Versão da sala",
+            "space_predecessor": "Exibir a versão mais antiga de %(spaceName)s.",
+            "space_upgrade_button": "Atualize este espaço para a versão de sala recomendada",
             "unfederated": "Esta sala não é acessível para servidores Matrix remotos",
             "upgrade_button": "Atualize essa sala para versão %(version)s",
             "upgrade_dialog_description": "Atualizar esta sala irá fechar a instância atual da sala e, em seu lugar, criar uma sala atualizada com o mesmo nome. Para oferecer a melhor experiência possível aos integrantes da sala, nós iremos:",
@@ -1336,8 +2202,12 @@
             "upgrade_dialog_title": "Atualize a Versão da Sala",
             "upgrade_dwarning_ialog_title_public": "Atualizar a sala pública",
             "upgrade_warning_dialog_description": "Atualizar uma sala é uma ação avançada e geralmente é recomendada quando uma sala está instável devido a erros, recursos ausentes ou vulnerabilidades de segurança.",
+            "upgrade_warning_dialog_explainer": "<b>Por favor, note que a atualização criará uma nova versão do quarto</b> . Todas as mensagens atuais permanecerão nesta sala arquivada.",
             "upgrade_warning_dialog_footer": "Você atualizará esta sala de <oldVersion /> para <newVersion />.",
+            "upgrade_warning_dialog_invite_label": "Convide automaticamente membros desta sala para a nova",
+            "upgrade_warning_dialog_report_bug_prompt": "Isso geralmente afeta apenas como a sala é processada no servidor. Se você tiver problemas com o %(brand)s, <a>informe um erro</a>.",
             "upgrade_warning_dialog_report_bug_prompt_link": "Isso geralmente afeta apenas como a sala é processada no servidor. Se você tiver problemas com o %(brand)s, <a>informe um erro</a>.",
+            "upgrade_warning_dialog_title": "Atualizar a sala",
             "upgrade_warning_dialog_title_private": "Atualizar a sala privada"
         },
         "alias_not_specified": "não especificado",
@@ -1348,9 +2218,13 @@
         },
         "delete_avatar_label": "Remover foto de perfil",
         "general": {
+            "alias_field_has_domain_invalid": "Separador de domínio ausente, por exemplo, (:domain.org)",
+            "alias_field_has_localpart_invalid": "Nome da sala ou separador ausente, por exemplo, (my-room:domain.org)",
+            "alias_field_matches_invalid": "Este endereço não aponta para esta sala",
             "alias_field_placeholder_default": "por exemplo: minha-sala",
             "alias_field_required_invalid": "Por favor, digite um endereço",
             "alias_field_safe_localpart_invalid": "Alguns caracteres não são permitidos",
+            "alias_field_taken_invalid": "Este endereço tem um servidor inválido ou já está em uso",
             "alias_field_taken_invalid_domain": "Este endereço já está em uso",
             "alias_field_taken_valid": "Este endereço está disponível para uso",
             "alias_heading": "Endereço da sala",
@@ -1367,6 +2241,8 @@
             "error_deleting_alias_description": "Ocorreu um erro ao remover esse endereço. Ele pode não mais existir ou houve um problema temporário.",
             "error_deleting_alias_description_forbidden": "Você não tem permissão para excluir este endereço.",
             "error_deleting_alias_title": "Erro ao remover o endereço",
+            "error_publishing": "Não é possível publicar a sala",
+            "error_publishing_detail": "Houve um erro ao publicar esta sala",
             "error_save_space_settings": "Falha ao salvar as configurações desse espaço.",
             "error_updating_alias_description": "Ocorreu um erro ao atualizar o endereço alternativo da sala. Isso pode não ser permitido pelo servidor ou houve um problema temporário.",
             "error_updating_canonical_alias_description": "Ocorreu um erro ao atualizar o endereço principal da sala. Isso pode não ser permitido pelo servidor ou houve um problema temporário.",
@@ -1400,9 +2276,19 @@
             "notification_sound": "Som de notificação",
             "settings_link": "Receba notificações conforme configurado em suas <a>configurações</a>",
             "sounds_section": "Sons",
+            "upload_sound_label": "Carregar som personalizado",
             "uploaded_sound": "Som enviado"
         },
+        "people": {
+            "knock_empty": "Nenhuma solicitação",
+            "knock_section": "Pedir para participar",
+            "see_less": "Ver menos",
+            "see_more": "Ver mais"
+        },
         "permissions": {
+            "add_privileged_user_description": "Dê mais privilégios a um ou mais usuários nesta sala",
+            "add_privileged_user_filter_placeholder": "Pesquisar usuários nesta sala...",
+            "add_privileged_user_heading": "Adicione usuários privilegiados",
             "ban": "Banir usuários",
             "ban_reason": "Razão",
             "banned_by": "Banido por %(displayName)s",
@@ -1415,6 +2301,9 @@
             "events_default": "Enviar mensagens",
             "invite": "Convidar usuários",
             "kick": "Remover usuários",
+            "m.call": "Iniciar %(brand)s chamadas",
+            "m.call.member": "Participe de %(brand)s chamadas",
+            "m.reaction": "Enviar reações",
             "m.room.avatar": "Alterar a foto da sala",
             "m.room.avatar_space": "Alterar avatar do espaço",
             "m.room.canonical_alias": "Alterar o endereço principal da sala",
@@ -1425,6 +2314,7 @@
             "m.room.name_space": "Mudar o nome do espaço",
             "m.room.pinned_events": "Gerenciar eventos fixados",
             "m.room.power_levels": "Alterar permissões",
+            "m.room.redaction": "Remover mensagens enviadas por mim",
             "m.room.server_acl": "Mudar o ACL do servidor",
             "m.room.tombstone": "Atualizar a sala",
             "m.room.topic": "Alterar a descrição",
@@ -1437,7 +2327,7 @@
             "permissions_section": "Permissões",
             "permissions_section_description_room": "Selecione os cargos necessários para alterar várias partes da sala",
             "permissions_section_description_space": "Selecionar os cargos necessários para alterar certas partes do espaço",
-            "privileged_users_section": "Usuárias/os privilegiadas/os",
+            "privileged_users_section": "Usuários privilegiados",
             "redact": "Remover mensagens enviadas por outros",
             "send_event_type": "Enviar eventos de %(eventType)s",
             "state_default": "Alterar configurações",
@@ -1447,16 +2337,18 @@
         "security": {
             "enable_encryption_confirm_description": "Uma vez ativada, a criptografia da sala não poderá ser desativada. Mensagens enviadas em uma sala criptografada não podem ser lidas pelo servidor, apenas pelos participantes da sala. Ativar a criptografia poderá impedir que vários bots e integrações funcionem corretamente. <a>Saiba mais sobre criptografia.</a>",
             "enable_encryption_confirm_title": "Ativar criptografia?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Não é recomendado adicionar criptografia a salas públicas.</b> Qualquer pessoa pode encontrar e ingressar em salas públicas, para que qualquer pessoa possa ler as mensagens nelas contidas. Você não obterá nenhum dos benefícios da criptografia e não poderá desativá-la mais tarde. Criptografar mensagens em uma sala pública tornará o recebimento e o envio de mensagens mais lento.",
             "enable_encryption_public_room_confirm_description_2": "Para evitar esses problemas, crie uma <a>nova sala criptografada</a> para a conversa que você planeja ter.",
             "enable_encryption_public_room_confirm_title": "Tem certeza que deseja adicionar criptografia para esta sala pública?",
             "encrypted_room_public_confirm_description_1": "<b>Não é recomendado adicionar criptografia a salas públicas.</b>Qualqer um pode encontrar e se juntar a salas públicas, então qualquer um pode ler as mensagens nelas. Você não terá nenhum dos benefícios da criptografia, e você não poderá desligá-la depois. Criptografar mensagens em uma sala pública fará com que receber e enviar mensagens fiquem mais lentos do que o normal.",
             "encrypted_room_public_confirm_description_2": "Para evitar esses problemas, crie uma <a>nova sala pública</a> para a conversa que você planeja ter.",
             "encrypted_room_public_confirm_title": "Tem certeza que fazer com que esta sala criptografada seja pública?",
+            "encryption_forced": "Seu servidor exige que a criptografia seja desativada.",
             "encryption_permanent": "Uma vez ativada, a criptografia não poderá ser desativada.",
             "error_join_rule_change_title": "Falha ao atualizar as regras de entrada",
             "error_join_rule_change_unknown": "Falha desconhecida",
             "guest_access_warning": "Pessoas com clientes suportados poderão entrar na sala sem ter uma conta registrada.",
-            "history_visibility_invited": "Apenas participantes (desde que foram convidadas/os)",
+            "history_visibility_invited": "Somente membros (desde que tenham sido convidados)",
             "history_visibility_joined": "Apenas participantes (desde que entraram na sala)",
             "history_visibility_legend": "Quem pode ler o histórico da sala?",
             "history_visibility_shared": "Apenas participantes (a partir do momento em que esta opção for selecionada)",
@@ -1465,12 +2357,23 @@
             "join_rule_description": "Decida quem pode entrar em %(roomName)s.",
             "join_rule_invite": "Privado (convite apenas)",
             "join_rule_invite_description": "Apenas pessoas convidadas podem entrar.",
+            "join_rule_knock": "Pedir para participar",
+            "join_rule_knock_description": "As pessoas não podem participar a menos que o acesso seja concedido.",
             "join_rule_public_description": "Todos podem encontrar e entrar.",
             "join_rule_restricted": "Membros do espaço",
             "join_rule_restricted_description": "Qualquer um em um espaço pode encontrar e se juntar. <a>Edite quais espaços podem ser acessados aqui.</a>",
             "join_rule_restricted_description_active_space": "Qualquer um em <spaceName/> pode encontrar e se juntar. Você pode selecionar outros espaços também.",
             "join_rule_restricted_description_prompt": "Qualquer um em um espaço pode encontrar e se juntar. Você pode selecionar múltiplos espaços.",
             "join_rule_restricted_description_spaces": "Espaço com acesso",
+            "join_rule_restricted_dialog_description": "Decida quais espaços podem acessar essa sala. Se um espaço for selecionado, seus membros poderão encontrar e participar de <RoomName/>.",
+            "join_rule_restricted_dialog_empty_warning": "Você está removendo todos os espaços. O acesso será definido como padrão somente para convidados",
+            "join_rule_restricted_dialog_filter_placeholder": "Pesquisar espaços",
+            "join_rule_restricted_dialog_heading_known": "Outros espaços que você conhece",
+            "join_rule_restricted_dialog_heading_other": "Outros espaços ou salas que você talvez não conheça",
+            "join_rule_restricted_dialog_heading_room": "Espaços que você conhece que contêm esta sala",
+            "join_rule_restricted_dialog_heading_space": "Espaços que você conhece que contêm esse espaço",
+            "join_rule_restricted_dialog_heading_unknown": "Provavelmente são aquelas das quais outros administradores de salas fazem parte.",
+            "join_rule_restricted_dialog_title": "Selecione espaços",
             "join_rule_restricted_n_more": {
                 "other": "e %(count)s mais",
                 "one": "& %(count)s mais"
@@ -1493,7 +2396,9 @@
             },
             "join_rule_upgrade_upgrading_room": "Atualizando sala",
             "public_without_alias_warning": "Para criar um link para esta sala, antes adicione um endereço.",
-            "strict_encryption": "Nunca envie mensagens criptografadas a partir desta sessão para sessões não confirmadas nessa sala",
+            "publish_room": "Torne esta sala visível no diretório de salas públicas.",
+            "publish_space": "Torne este espaço visível no diretório de salas públicas.",
+            "strict_encryption": "Envie mensagens somente para usuários verificados.",
             "title": "Segurança e privacidade"
         },
         "title": "Configurações da sala - %(roomName)s",
@@ -1510,6 +2415,12 @@
             "history_visibility_anyone_space_description": "Permite que pessoas vejam seu espaço antes de entrarem.",
             "history_visibility_anyone_space_recommendation": "Recomendado para espaços públicos.",
             "title": "Visibilidade"
+        },
+        "voip": {
+            "call_type_section": "Tipo de chamada",
+            "enable_element_call_caption": "%(brand)s é criptografado de ponta a ponta, mas atualmente está limitado a um número menor de usuários.",
+            "enable_element_call_label": "Ativar %(brand)s como uma opção de chamada adicional nesta sala",
+            "enable_element_call_no_permissions_tooltip": "Você não tem permissões suficientes para alterar isso."
         }
     },
     "room_summary_card_back_action_label": "Informação da sala",
@@ -1541,8 +2452,16 @@
         "recent_changes_heading": "Alterações recentes que ainda não foram recebidas",
         "title": "O servidor não está respondendo"
     },
+    "service_worker_error": {
+        "description": "%(brand)s requer um operador de serviço para carregar mídia autenticada de repositórios de conteúdo Matrix. Isso não é suportado pelo seu navegador, portanto, pode ocorrer uma falha no carregamento de mídia.",
+        "title": "Falha ao carregar o operador de serviço"
+    },
     "seshat": {
         "error_initialising": "Falha na inicialização da pesquisa por mensagem, confire <a>suas configurações</a> para mais informações",
+        "reset_button": "Redefinir armazenamento de eventos",
+        "reset_description": "Você provavelmente não deseja redefinir seu armazenamento de índice de eventos",
+        "reset_explainer": "Se você fizer isso, observe que nenhuma de suas mensagens será excluída, mas a experiência de pesquisa poderá ser prejudicada por alguns instantes enquanto o índice é recriado",
+        "reset_title": "Redefinir armazenamento de eventos?",
         "warning_kind_files": "Esta versão do %(brand)s não permite visualizar alguns arquivos criptografados",
         "warning_kind_files_app": "Use o <a>app para Computador</a> para ver todos os arquivos criptografados",
         "warning_kind_search": "Esta versão do %(brand)s não permite buscar mensagens criptografadas",
@@ -1553,28 +2472,45 @@
             "access_token_detail": "Seu token de acesso dá acesso total à sua conta. Não o compartilhe com ninguém.",
             "brand_version": "versão do %(brand)s:",
             "clear_cache_reload": "Limpar cache e recarregar",
+            "crypto_version": "Versão Crypto:",
+            "dialog_title": "<strong>Configurações:</strong> Ajuda e Sobre",
             "help_link": "Para obter ajuda com o uso do %(brand)s, clique <a>aqui</a>.",
+            "homeserver": "O servidor doméstico é <code>%(homeserverUrl)s</code>",
+            "identity_server": "O servidor de identidade é <code>%(identityServerUrl)s</code>",
             "title": "Ajuda e sobre",
             "versions": "Versões"
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Configurações:</strong> Conta",
+            "title": "Conta"
+        },
         "all_rooms_home": "Mostrar todas as salas no Início",
         "all_rooms_home_description": "Todas as salas que você estiver presente aparecerão no Início.",
         "always_show_message_timestamps": "Sempre mostrar as datas das mensagens",
         "appearance": {
+            "bundled_emoji_font": "Use a fonte de emoji incluída no pacote",
+            "compact_layout": "Mostrar texto e mensagens compactas",
+            "compact_layout_description": "O layout moderno deve ser selecionado para usar esse recurso.",
             "custom_font": "Usar uma fonte do sistema",
             "custom_font_description": "Defina o nome de uma fonte instalada no seu sistema e o %(brand)s tentará usá-la.",
             "custom_font_name": "Nome da fonte do sistema",
             "custom_font_size": "Usar tamanho personalizado",
-            "custom_theme_error_downloading": "Erro ao baixar as informações do tema.",
+            "custom_theme_add": "Adicionar tema personalizado",
+            "custom_theme_downloading": "Baixando tema personalizado...",
+            "custom_theme_error_downloading": "Erro ao baixar o tema",
+            "custom_theme_help": "Insira o URL de um tema personalizado que você deseja aplicar.",
             "custom_theme_invalid": "Esquema inválido de tema.",
+            "dialog_title": "<strong>Configurações:</strong> Aparência",
             "font_size": "Tamanho da fonte",
+            "font_size_default": "%(fontSize)s (padrão)",
+            "high_contrast": "Alto contraste",
             "image_size_default": "Padrão",
             "image_size_large": "Grande",
             "layout_bubbles": "Balões de mensagem",
             "layout_irc": "IRC (experimental)",
-            "match_system_theme": "Se adaptar ao tema do sistema",
+            "match_system_theme": "Se adaptar ao padrão do sistema",
             "timeline_image_size": "Tamanho da imagem na linha do tempo"
         },
         "automatic_language_detection_syntax_highlight": "Ativar detecção automática de idioma para ressaltar erros de ortografia",
@@ -1583,8 +2519,81 @@
         "big_emoji": "Ativar emojis grandes no bate-papo",
         "code_block_expand_default": "Expandir blocos de código por padrão",
         "code_block_line_numbers": "Mostrar o número da linha em blocos de código",
+        "disable_historical_profile": "Mostrar a foto e o nome do perfil atual dos usuários no histórico de mensagens",
+        "discovery": {
+            "title": "Como encontrar você"
+        },
         "emoji_autocomplete": "Ativar sugestões de emojis ao digitar",
         "enable_markdown": "Habilitar markdown",
+        "enable_markdown_description": "Inicie as mensagens com <code>/plain</code> para enviar sem markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Os detalhes da sua conta, contatos, preferências e lista de bate-papo serão mantidos",
+                "breadcrumb_page": "Redefinir criptografia",
+                "breadcrumb_second_description": "Você perderá qualquer histórico de mensagens armazenado somente no servidor",
+                "breadcrumb_third_description": "Você precisará verificar todos os seus dispositivos e contatos existentes novamente.",
+                "breadcrumb_title": "Tem certeza de que deseja redefinir sua identidade?",
+                "breadcrumb_title_forgot": "Esqueceu sua chave de recuperação? Você precisará redefinir sua identidade.",
+                "breadcrumb_title_sync_failed": "Falha ao sincronizar o armazenamento de chaves. Você precisa redefinir sua identidade.",
+                "breadcrumb_warning": "Faça isso somente se você acreditar que sua conta foi comprometida.",
+                "details_title": "Detalhes da criptografia",
+                "do_not_close_warning": "Não feche essa janela até que a redefinição seja concluída",
+                "export_keys": "Exportar chaves",
+                "import_keys": "Importar chaves",
+                "other_people_device_description": "Por padrão, em salas criptografadas, não envie mensagens criptografadas para ninguém até que você as tenha verificado",
+                "other_people_device_label": "Nunca envie mensagens criptografadas para dispositivos não verificados",
+                "other_people_device_title": "Dispositivos de outras pessoas",
+                "reset_identity": "Redefinir identidade criptográfica",
+                "reset_in_progress": "Redefinição em andamento...",
+                "session_id": "ID da sessão:",
+                "session_key": "Chave da sessão:",
+                "title": "Avançado"
+            },
+            "confirm_key_storage_off": "Tem certeza de que deseja manter o armazenamento de chaves desativado?",
+            "confirm_key_storage_off_description": "Se você sair de todos os seus dispositivos, perderá o histórico de mensagens e precisará verificar todos os seus contatos existentes novamente. <a>Saiba mais</a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Excluir armazenamento de chaves",
+                "confirm": "Excluir armazenamento de chaves",
+                "description": "A exclusão do armazenamento de chaves removerá sua identidade criptográfica e as chaves de mensagem do servidor e desativará os seguintes recursos de segurança:",
+                "list_first": "Você não terá histórico de mensagens criptografadas em novos dispositivos",
+                "list_second": "Você perderá o acesso às suas mensagens criptografadas se estiver desconectado de %(brand)s em todos os lugares",
+                "title": "Tem certeza de que deseja desativar o armazenamento de chaves e excluí-lo?"
+            },
+            "device_not_verified_button": "Verificar este dispositivo",
+            "device_not_verified_description": "Você precisa verificar este dispositivo para visualizar suas configurações de criptografia.",
+            "device_not_verified_title": "Dispositivo não verificado",
+            "dialog_title": "<strong>Configurações: </strong> Criptografia",
+            "key_storage": {
+                "allow_key_storage": "Permitir o armazenamento de chaves",
+                "description": "Armazene sua identidade criptográfica e chaves de mensagem com segurança no servidor. Isso permitirá que você visualize seu histórico de mensagens em quaisquer novos dispositivos.<a> Saber mais</a>",
+                "title": "Armazenamento de chaves"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Confirme a nova chave de recuperação",
+                "change_recovery_confirm_description": "Insira sua nova chave de recuperação abaixo para concluir. Seu antigo não funcionará mais.",
+                "change_recovery_confirm_title": "Insira sua nova chave de recuperação",
+                "change_recovery_key": "Alterar chave de recuperação",
+                "change_recovery_key_description": "Anote esta nova chave de recuperação em algum lugar seguro. Então clique em Continue para confirmar a alteração.",
+                "change_recovery_key_title": "Alterar a chave de recuperação?",
+                "description": "Recupere sua identidade criptográfica e histórico de mensagens com uma chave de recuperação se você tiver perdido todos os seus dispositivos existentes.",
+                "enter_key_error": "A chave de recuperação que você inseriu não está correta.",
+                "enter_recovery_key": "Insira a chave de recuperação",
+                "forgot_recovery_key": "Esqueceu a chave de recuperação?",
+                "key_storage_warning": "Seu armazenamento de chaves está fora de sincronia. Clique em um dos botões abaixo para corrigir o problema.",
+                "save_key_description": "Não compartilhe isso com ninguém!",
+                "save_key_title": "Chave de recuperação",
+                "set_up_recovery": "Configurar a recuperação",
+                "set_up_recovery_confirm_button": "Concluir a configuração",
+                "set_up_recovery_confirm_description": "Digite a chave de recuperação mostrada na tela anterior para concluir a configuração da recuperação.",
+                "set_up_recovery_confirm_title": "Digite sua chave de recuperação para confirmar",
+                "set_up_recovery_description": "Seu armazenamento de chaves é protegido por uma chave de recuperação. Se precisar de uma nova chave de recuperação após a configuração, você pode recriá-la selecionando '%(changeRecoveryKeyButton)s'.",
+                "set_up_recovery_save_key_description": "Anote essa chave de recuperação em algum lugar seguro, como um gerenciador de senhas, uma nota criptografada ou um cofre físico.",
+                "set_up_recovery_save_key_title": "Salve sua chave de recuperação",
+                "set_up_recovery_secondary_description": "Depois de clicar em continuar, geraremos uma chave de recuperação para você.",
+                "title": "Recuperação"
+            },
+            "title": "Criptografia"
+        },
         "general": {
             "account_management_section": "Gerenciamento da Conta",
             "account_section": "Conta",
@@ -1596,16 +2605,38 @@
             "add_msisdn_confirm_sso_button": "Confirme a adição deste número de telefone usando o Login Único para provar sua identidade.",
             "add_msisdn_dialog_title": "Adicionar número de telefone",
             "add_msisdn_instructions": "Digite o código de confirmação enviado por mensagem de texto para +%(msisdn)s.",
+            "add_msisdn_misconfigured": "O fluxo de adição/vinculação com MSISDN está mal configurado",
+            "allow_spellcheck": "Permitir verificação ortográfica",
+            "application_language": "Idioma do aplicativo",
+            "application_language_reload_hint": "O aplicativo será recarregado após selecionar outro idioma",
+            "avatar_remove_progress": "Removendo a imagem...",
+            "avatar_save_progress": "Enviando imagem ...",
+            "avatar_upload_error_text": "O formato do arquivo não é suportado ou a imagem é maior que %(size)s.",
+            "avatar_upload_error_text_generic": "O formato do arquivo pode não ser suportado.",
+            "avatar_upload_error_title": "Não foi possível carregar a imagem de perfil",
             "confirm_adding_email_body": "Clique no botão abaixo para confirmar a adição deste endereço de e-mail.",
             "confirm_adding_email_title": "Confirmar a inclusão de e-mail",
             "deactivate_confirm_body": "Tem certeza de que deseja desativar sua conta? Isso é irreversível.",
             "deactivate_confirm_body_sso": "Prove a sua identidade por meio do seu Acesso único, para confirmar a desativação da sua conta.",
+            "deactivate_confirm_content": "Confirme que você gostaria de desativar sua conta. Se você continuar:",
+            "deactivate_confirm_content_1": "Você não poderá reativar sua conta",
+            "deactivate_confirm_content_2": "Você não poderá mais fazer login",
+            "deactivate_confirm_content_3": "Ninguém poderá reutilizar seu nome de usuário (MXID), incluindo você: esse nome de usuário permanecerá indisponível",
+            "deactivate_confirm_content_4": "Você sairá de todas as salas e DMs em que estiver",
+            "deactivate_confirm_content_5": "Você será removido do servidor de identidade: seus amigos não poderão mais encontrá-lo com seu e-mail ou número de telefone",
+            "deactivate_confirm_content_6": "Suas mensagens antigas ainda estarão visíveis para as pessoas que as receberam, assim como os e-mails que você enviou anteriormente. Você gostaria de ocultar suas mensagens enviadas de pessoas que ingressarão em salas no futuro?",
             "deactivate_confirm_continue": "Confirmar desativação da conta",
+            "deactivate_confirm_erase_label": "Ocultar minhas mensagens de novos participantes",
             "deactivate_section": "Desativar minha conta",
-            "discovery_email_empty": "As opções de descoberta aparecerão assim que você adicione um e-mail acima.",
+            "deactivate_warning": "Desativar sua conta é uma ação permanente — tenha cuidado!",
+            "discovery_email_empty": "As opções de descoberta aparecerão depois que você adicionar um e-mail.",
             "discovery_email_verification_instructions": "Verifique o link na sua caixa de e-mails",
-            "discovery_msisdn_empty": "As opções de descoberta aparecerão assim que você adicione um número de telefone acima.",
+            "discovery_msisdn_empty": "As opções de descoberta aparecerão depois que você adicionar um número de telefone.",
             "discovery_needs_terms": "Concorde com os Termos de Serviço do servidor de identidade (%(serverName)s), para que você possa ser descoberto por endereço de e-mail ou por número de celular.",
+            "discovery_needs_terms_title": "Deixe que as pessoas encontrem você",
+            "display_name": "Nome de exibição",
+            "display_name_error": "Não foi possível definir o nome de exibição",
+            "email_adding_unsupported_by_hs": "Este servidor doméstico não suporta a adição de endereços de e-mail à sua conta.",
             "email_address_in_use": "Este endereço de email já está em uso",
             "email_address_label": "Endereço de e-mail",
             "email_not_verified": "Seu endereço de e-mail ainda não foi confirmado",
@@ -1620,153 +2651,206 @@
             "error_invalid_email_detail": "Este não aparenta ser um endereço de e-mail válido",
             "error_msisdn_verification": "Não foi possível confirmar o número de telefone.",
             "error_password_change_403": "Não foi possível alterar a senha. A sua senha está correta?",
+            "error_password_change_http": "%(errorMessage)s(status HTTP%(httpStatus)s )",
+            "error_password_change_title": "Erro ao alterar a senha",
+            "error_password_change_unknown": "Erro de alteração de senha desconhecido (%(stringifiedError)s )",
             "error_remove_3pid": "Não foi possível remover informação de contato",
             "error_revoke_email_discovery": "Não foi possível revogar o compartilhamento do endereço de e-mail",
             "error_revoke_msisdn_discovery": "Não foi possível revogar o compartilhamento do número de celular",
             "error_share_email_discovery": "Não foi possível compartilhar o endereço de e-mail",
             "error_share_msisdn_discovery": "Não foi possível compartilhar o número de celular",
-            "language_section": "Idioma e região",
+            "identity_server_no_token": "Nenhum token de acesso à identidade foi encontrado",
+            "identity_server_not_set": "Servidor de identidade não definido",
+            "invalid_phone_number": "O número de telefone fornecido não parece ser válido.",
+            "language_section": "Idioma",
+            "msisdn_adding_unsupported_by_hs": "Este servidor doméstico não suporta a adição de números de telefone à sua conta.",
             "msisdn_in_use": "Este número de telefone já está em uso",
             "msisdn_label": "Número de telefone",
             "msisdn_verification_field_label": "Código de confirmação",
             "msisdn_verification_instructions": "Digite o código de confirmação enviado por mensagem de texto.",
             "msisdns_heading": "Números de Telefone",
+            "oidc_manage_button": "Gerenciar conta",
+            "password_change_section": "Defina uma nova senha da conta...",
+            "password_change_success": "Sua senha foi alterada com sucesso.",
+            "personal_info": "Informações pessoais",
+            "profile_subtitle": "É assim que você aparece para outras pessoas no aplicativo.",
+            "profile_subtitle_oidc": "Sua conta é gerenciada separadamente por um provedor de identidade e, portanto, algumas de suas informações pessoais não podem ser alteradas aqui.",
             "remove_email_prompt": "Remover %(email)s?",
-            "remove_msisdn_prompt": "Remover %(phone)s?"
+            "remove_msisdn_prompt": "Remover %(phone)s?",
+            "spell_check_locale_placeholder": "Escolha um local",
+            "unable_to_load_emails": "Não é possível carregar endereços de e-mail",
+            "unable_to_load_msisdns": "Não é possível carregar números de telefone",
+            "username": "Nome de usuário"
         },
-        "image_thumbnails": "Mostrar miniaturas e resumos para imagens",
         "inline_url_previews_default": "Ativar, por padrão, a visualização de resumo de links",
         "inline_url_previews_room": "Ativar, para todos os participantes desta sala, a visualização de links",
         "inline_url_previews_room_account": "Ativar, para esta sala, a visualização de links (só afeta você)",
+        "insert_trailing_colon_mentions": "Insira dois pontos à direita após o usuário mencionar no início de uma mensagem",
         "jump_to_bottom_on_send": "Vá para o final da linha do tempo ao enviar uma mensagem",
         "key_backup": {
             "backup_in_progress": "O backup de suas chaves está sendo feito (o primeiro backup pode demorar alguns minutos).",
+            "backup_starting": "Iniciando o backup...",
             "backup_success": "Pronto!",
             "cannot_create_backup": "Não foi possível criar backup da chave",
             "create_title": "Criar backup de chave",
             "setup_secure_backup": {
+                "backup_setup_success_description": "As suas chaves estão agora a ser copiadas a partir deste dispositivo.",
+                "backup_setup_success_title": "Backup seguro bem-sucedido",
                 "cancel_warning": "Se você cancelar agora, poderá perder mensagens e dados criptografados se você perder acesso aos seus logins atuais.",
                 "confirm_security_phrase": "Confirmar com a sua Frase de Segurança",
                 "description": "Proteja-se contra a perda de acesso a mensagens e dados criptografados fazendo backup das chaves de criptografia no seu servidor.",
+                "download_or_copy": "%(downloadButton)sou %(copyButton)s",
+                "enter_phrase_description": "Insira uma frase de segurança que só você conheça, pois ela é usada para proteger seus dados. Para ficar seguro, você não deve reutilizar a senha da sua conta.",
                 "enter_phrase_title": "Digite uma frase de segurança",
-                "generate_security_key_title": "Gerar uma Chave de Segurança",
+                "enter_phrase_to_confirm": "Digite sua frase de segurança uma segunda vez para confirmá-la.",
+                "generate_security_key_description": "Geraremos uma chave de segurança para você armazenar em algum lugar seguro, como um gerenciador de senhas ou um cofre.",
+                "generate_security_key_title": "Gerar uma chave de recuperação",
                 "pass_phrase_match_failed": "Isto não corresponde.",
                 "pass_phrase_match_success": "Isto corresponde!",
                 "phrase_strong_enough": "Ótimo! Essa frase de segurança parece ser segura o suficiente.",
                 "secret_storage_query_failure": "Não foi possível obter o status do armazenamento secreto",
+                "security_key_safety_reminder": "Armazene sua chave de segurança em um local seguro, como um gerenciador de senhas ou um cofre, pois ela é usada para proteger seus dados criptografados.",
                 "set_phrase_again": "Voltar para configurar novamente.",
                 "settings_reminder": "Você também pode configurar o Backup online & configurar as suas senhas nas Configurações.",
                 "title_confirm_phrase": "Confirme a frase de segurança",
-                "title_save_key": "Salve sua Chave de Segurança",
+                "title_save_key": "Salve sua chave de recuperação",
                 "title_set_phrase": "Defina uma frase de segurança",
                 "unable_to_setup": "Não foi possível definir o armazenamento secreto",
                 "use_different_passphrase": "Usar uma frase secreta diferente?",
-                "use_phrase_only_you_know": "Use uma frase secreta que apenas você conhece, e opcionalmente salve uma Chave de Segurança para acessar o backup."
+                "use_phrase_only_you_know": "Use uma frase secreta que só você conhece e, opcionalmente, salve uma Chave de Recuperação para usar como backup."
             }
         },
         "key_export_import": {
             "confirm_passphrase": "Confirme a senha",
             "enter_passphrase": "Entre com a senha",
             "export_description_1": "Este processo permite que você exporte as chaves para mensagens que você recebeu em salas criptografadas para um arquivo local. Você poderá então importar o arquivo para outro cliente Matrix no futuro, de modo que este cliente também poderá descriptografar suas mensagens.",
+            "export_description_2": "O arquivo exportado permitirá que qualquer pessoa que possa lê-lo descriptografe qualquer mensagem criptografada que você possa ver, portanto, tenha cuidado para mantê-lo seguro. Para ajudar com isso, você deve inserir uma senha exclusiva abaixo, que só será usada para criptografar os dados exportados. Só será possível importar os dados usando a mesma frase secreta.",
             "export_title": "Exportar chaves de sala",
             "file_to_import": "Arquivo para importar",
             "import_description_1": "Este processo faz com que você possa importar as chaves de criptografia que tinha previamente exportado de outro cliente Matrix. Você poderá então descriptografar todas as mensagens que o outro cliente pôde criptografar.",
             "import_description_2": "O arquivo exportado será protegido com uma senha. Você deverá inserir a senha aqui para poder descriptografar o arquivo futuramente.",
             "import_title": "Importar chaves de sala",
             "phrase_cannot_be_empty": "A frase não pode estar em branco",
-            "phrase_must_match": "As senhas têm que ser iguais"
+            "phrase_must_match": "As senhas têm que ser iguais",
+            "phrase_strong_enough": "Ótimo! Esta senha parece forte o suficiente"
         },
         "keyboard": {
+            "dialog_title": "<strong>Configurações:</strong> Teclado",
             "title": "Teclado"
         },
+        "labs": {
+            "dialog_title": "<strong>Configurações:</strong> Labs"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Configurações:</strong> Usuários ignorados"
+        },
+        "media_preview": {
+            "hide_avatars": "Ocultar avatares da sala e do convidador",
+            "hide_media": "Ocultar sempre",
+            "media_preview_description": "Uma mídia oculta sempre pode ser exibida tocando nela",
+            "media_preview_label": "Mostrar mídia na linha do tempo",
+            "show_in_private": "Em salas privadas",
+            "show_media": "Mostrar sempre"
+        },
         "notifications": {
+            "default_setting_description": "Essa configuração será aplicada por padrão a todas as suas salas.",
+            "default_setting_section": "Quero ser notificado sobre (configuração padrão)",
+            "desktop_notification_message_preview": "Mostrar visualização da mensagem na notificação da área de trabalho",
+            "dialog_title": "<strong>Configurações:</strong> Notificações",
+            "email_description": "Receba um resumo por e-mail de notificações perdidas",
+            "email_section": "Resumo por e-mail",
+            "email_select": "Selecione para quais e-mails você deseja enviar resumos. Gerencie seus e-mails em <button>Geral</button>.",
             "enable_audible_notifications_session": "Ativar o som de notificações nesta sessão",
             "enable_desktop_notifications_session": "Ativar notificações na área de trabalho nesta sessão",
             "enable_email_notifications": "Habilita notificação por emails para %(email)s",
+            "enable_notifications_account": "Ativar notificações para esta conta",
+            "enable_notifications_account_detail": "Desligue para desativar notificações em todos os seus dispositivos e sessões",
+            "enable_notifications_device": "Ativar notificações para este dispositivo",
             "error_loading": "Um erro ocorreu ao carregar suas configurações de notificação.",
             "error_permissions_denied": "%(brand)s não tem permissão para lhe enviar notificações - confirme as configurações do seu navegador",
             "error_permissions_missing": "%(brand)s não tem permissão para lhe enviar notificações - tente novamente",
             "error_saving": "Erro ao salvar as preferências de notificações",
             "error_saving_detail": "Um erro ocorreu enquanto suas preferências de notificação eram salvas.",
             "error_title": "Não foi possível ativar as notificações",
+            "error_updating": "Ocorreu um erro ao atualizar suas preferências de notificação. Tente alternar sua opção novamente.",
+            "invites": "Convidado para uma sala",
+            "keywords": "Mostre um selo <badge/> quando as palavras-chave forem usadas em uma sala.",
+            "keywords_prompt": "Insira as palavras-chave aqui ou use para variações ortográficas ou apelidos",
+            "labs_notice_prompt": "<strong>Atualização:</strong> Simplificamos as configurações de notificações para facilitar a localização das opções. Algumas configurações personalizadas que você escolheu anteriormente não são mostradas aqui, mas ainda estão ativas. Se você continuar, algumas de suas configurações poderão mudar.<a> Saber mais</a>",
+            "mentions_keywords": "Menções e Palavras-chave",
+            "mentions_keywords_only": "Apenas Menções e Palavras-chave",
             "messages_containing_keywords": "Mensagens contendo palavras-chave",
             "noisy": "Ativado com som",
+            "notices": "Mensagens enviadas por bots",
+            "notify_at_room": "Notificar quando alguém mencionar usando @room",
+            "notify_keyword": "Notificar quando alguém usar uma palavra-chave",
+            "notify_mention": "Notificar quando alguém mencionar usando @displayname ou %(mxid)s",
+            "other_section": "Outras coisas em que achamos que você pode estar interessado:",
+            "people_mentions_keywords": "Pessoas, Menções e Palavras-chave",
+            "play_sound_for_description": "Aplicado por padrão a todas as salas em todos os dispositivos.",
+            "play_sound_for_section": "Tocar um som para",
             "push_targets": "Aparelhos notificados",
+            "quick_actions_mark_all_read": "Marcar todas as mensagens como lidas",
+            "quick_actions_reset": "Redefinir para as configurações padrão",
+            "quick_actions_section": "Ações rápidas",
+            "room_activity": "Ocorrem novas atividades de sala, atualizações e mensagens de status",
             "rule_call": "Recebendo chamada",
             "rule_contains_display_name": "Mensagens contendo meu nome e sobrenome",
             "rule_contains_user_name": "Mensagens contendo meu nome de usuário",
             "rule_encrypted": "Mensagens criptografadas em salas",
             "rule_encrypted_room_one_to_one": "Mensagens criptografadas em conversas individuais",
-            "rule_invite_for_me": "Quando eu for convidada(o) a uma sala",
+            "rule_invite_for_me": "Quando sou convidado para uma sala",
             "rule_message": "Mensagens em salas",
             "rule_room_one_to_one": "Mensagens em conversas individuais",
             "rule_roomnotif": "Mensagens contendo @room",
             "rule_suppress_notices": "Mensagens enviadas por bots",
             "rule_tombstone": "Quando a versão da sala é atualizada",
-            "show_message_desktop_notification": "Mostrar a mensagem na notificação da área de trabalho"
+            "show_message_desktop_notification": "Mostrar a mensagem na notificação da área de trabalho",
+            "voip": "Chamadas de áudio e vídeo"
         },
         "preferences": {
+            "Electron.enableHardwareAcceleration": "Ativar a aceleração de hardware (reinicie %(appName)s para fazer efeito)",
             "always_show_menu_bar": "Mostrar a barra de menu na janela",
             "autocomplete_delay": "Atraso no preenchimento automático (ms)",
             "code_blocks_heading": "Blocos de código",
             "compact_modern": "Usar um layout \"moderno\" mais compacto",
             "composer_heading": "Campo de texto",
+            "default_timezone": "Navegador padrão (%(timezone)s)",
+            "dialog_title": "<strong>Configurações:</strong> Preferências",
             "enable_hardware_acceleration": "Habilitar aceleração de hardware",
             "enable_tray_icon": "Mostrar o ícone da bandeja e minimizar a janela ao fechar",
             "keyboard_heading": "Teclas de atalho do teclado",
             "keyboard_view_shortcuts_button": "Para ver todos os atalhos do teclado, <a>clique aqui</a>.",
             "media_heading": "Imagens, GIFs e vídeos",
+            "presence_description": "Compartilhe sua atividade e status com outras pessoas.",
+            "publish_timezone": "Publique o fuso horário no perfil público",
             "rm_lifetime": "Duração do marcador de leitura (ms)",
             "rm_lifetime_offscreen": "Vida útil do marcador de leitura fora da tela (ms)",
+            "room_directory_heading": "Diretório de salas",
             "room_list_heading": "Lista de salas",
+            "show_avatars_pills": "Mostrar avatares em menções de usuários, salas e eventos",
+            "show_polls_button": "Mostrar botão de enquetes",
             "surround_text": "Circule o texto selecionado ao digitar caracteres especiais",
-            "time_heading": "Exibindo tempo"
+            "time_heading": "Exibindo tempo",
+            "user_timezone": "Definir fuso horário"
         },
         "prompt_invite": "Avisar antes de enviar convites para IDs da Matrix potencialmente inválidas",
         "replace_plain_emoji": "Substituir automaticamente os emojis em texto",
         "security": {
-            "4s_public_key_in_account_data": "nos dados de conta",
-            "4s_public_key_status": "Chave pública do armazenamento secreto:",
-            "backup_key_cached_status": "Backup da chave em cache:",
-            "backup_key_stored_status": "Backup da chave armazenada:",
-            "backup_key_unexpected_type": "tipo inesperado",
-            "backup_key_well_formed": "bem formado",
-            "backup_keys_description": "Faça backup de suas chaves de criptografia com os dados da sua conta, para se prevenir a perder o acesso às suas sessões. Suas chaves serão protegidas por uma Chave de Segurança exclusiva.",
+            "analytics_description": "Compartilhe dados anônimos para nos ajudar a identificar problemas. Nada pessoal. Sem terceiros.",
             "bulk_options_accept_all_invites": "Aceite todos os convites de %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Recusar todos os %(invitedRooms)s convites",
             "bulk_options_section": "Opções em massa",
-            "cross_signing_cached": "armazenado localmente",
-            "cross_signing_homeserver_support": "Recursos suportados pelo servidor:",
-            "cross_signing_homeserver_support_exists": "existe",
-            "cross_signing_in_4s": "em armazenamento secreto",
-            "cross_signing_in_memory": "na memória",
-            "cross_signing_master_private_Key": "Chave privada principal:",
-            "cross_signing_not_cached": "não encontrado localmente",
-            "cross_signing_not_found": "não encontradas",
-            "cross_signing_not_in_4s": "não encontrado no armazenamento",
-            "cross_signing_not_stored": "não armazenado",
-            "cross_signing_private_keys": "Chaves privadas de autoverificação:",
-            "cross_signing_public_keys": "Chaves públicas de autoverificação:",
-            "cross_signing_self_signing_private_key": "Chave privada auto-assinada:",
-            "cross_signing_user_signing_private_key": "Chave privada de assinatura da(do) usuária(o):",
-            "cryptography_section": "Criptografia",
-            "delete_backup": "Remover backup",
-            "delete_backup_confirm_description": "Tem certeza? Você perderá suas mensagens criptografadas se não tiver feito o backup de suas chaves.",
+            "dehydrated_device_description": "O recurso de dispositivo offline permite que você receba mensagens criptografadas mesmo quando você não está conectado a nenhum dispositivo",
+            "dehydrated_device_enabled": "Dispositivo offline habilitado",
+            "dialog_title": "<strong>Configurações:</strong> Segurança e Privacidade",
             "e2ee_default_disabled_warning": "O administrador do servidor desativou a criptografia de ponta a ponta por padrão em salas privadas e em conversas.",
             "enable_message_search": "Ativar busca de mensagens em salas criptografadas",
             "encryption_section": "Criptografia",
-            "error_loading_key_backup_status": "Não foi possível carregar o status do backup da chave",
-            "export_megolm_keys": "Exportar chaves ponta-a-ponta da sala",
             "ignore_users_empty": "Você não tem usuários ignorados.",
             "ignore_users_section": "Usuários bloqueados",
-            "import_megolm_keys": "Importar chave de criptografia ponta-a-ponta (E2E) da sala",
-            "key_backup_active_version_none": "Nenhuma",
             "key_backup_algorithm": "Algoritmo:",
-            "key_backup_complete": "O backup de todas as chaves foi realizado",
             "key_backup_connect": "Autorize esta sessão a fazer o backup de chaves",
-            "key_backup_connect_prompt": "Autorize esta sessão a fazer o backup de chaves antes de se desconectar, para evitar perder chaves que possam estar apenas nesta sessão.",
-            "key_backup_inactive": "Esta sessão <b>não está fazendo backup de suas chaves</b>, mas você tem um backup existente que pode restaurar para continuar.",
-            "key_backup_inactive_warning": "Suas chaves <b>não estão sendo copiadas desta sessão</b>.",
             "message_search_disable_warning": "Se desativado, as mensagens de salas criptografadas não aparecerão em resultados de buscas.",
             "message_search_disabled": "Armazene mensagens criptografadas de forma segura localmente para que possam aparecer nos resultados das buscas.",
             "message_search_enabled": {
@@ -1785,17 +2869,16 @@
             "message_search_space_used": "Espaço usado:",
             "message_search_unsupported": "%(brand)s precisa de componentes adicionais para pesquisar as mensagens criptografadas armazenadas localmente. Se quiser testar esse recurso, construa uma versão do %(brand)s para Computador com <nativeLink>componentes de busca ativados</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s não consegue pesquisar as mensagens criptografadas armazenadas localmente em um navegador de internet. Use o <desktopLink>%(brand)s para Computador</desktopLink> para que as mensagens criptografadas sejam exibidas nos resultados de buscas.",
-            "restore_key_backup": "Restaurar do backup",
-            "secret_storage_not_ready": "não está pronto",
-            "secret_storage_ready": "pronto",
-            "secret_storage_status": "Armazenamento secreto:",
+            "record_session_details": "Registre o nome, a versão e o URL do cliente para reconhecer as sessões com mais facilidade no gerenciador de sessões",
             "send_analytics": "Enviar dados analíticos",
-            "session_id": "ID da sessão:",
-            "session_key": "Chave da sessão:",
-            "strict_encryption": "Nunca envie mensagens criptografadas a partir desta sessão para sessões não confirmadas"
+            "strict_encryption": "Envie mensagens somente para usuários verificados"
         },
+        "send_read_receipts": "Enviar confirmações de leitura",
+        "send_read_receipts_unsupported": "Seu servidor não suporta a desativação do envio de confirmações de leitura.",
         "send_typing_notifications": "Permitir que saibam quando eu estiver digitando",
         "sessions": {
+            "best_security_note": "Para maior segurança, verifique suas sessões e saia de qualquer sessão que você não reconhece ou não usa mais.",
+            "browser": "Navegador",
             "confirm_sign_out": {
                 "other": "Confirme a saída destes dispositivos",
                 "one": "Confirme a saída deste dispositivo"
@@ -1815,68 +2898,158 @@
             "current_session": "Sessão atual",
             "desktop_session": "Sessão desktop",
             "details_heading": "Detalhes da sessão",
+            "device_unverified_description": "Verifique ou saia desta sessão para obter maior segurança e confiabilidade.",
+            "device_unverified_description_current": "Verifique sua sessão atual para obter mensagens seguras aprimoradas.",
+            "device_verified_description": "Esta sessão está pronta para mensagens seguras.",
+            "device_verified_description_current": "Sua sessão atual está pronta para o envio de mensagens seguras.",
+            "dialog_title": "<strong>Configurações:</strong> Sessões",
+            "error_pusher_state": "Falha ao definir o estado do empurrador",
+            "error_set_name": "Falha ao definir o nome da sessão",
+            "filter_all": "Todos",
+            "filter_inactive": "Inativo",
+            "filter_inactive_description": "Inativo por %(inactiveAgeDays)s dias ou mais",
             "filter_label": "Filtrar dispositivos",
+            "filter_unverified_description": "Não está pronto para mensagens seguras",
+            "filter_verified_description": "Pronto para mensagens seguras",
+            "hide_details": "Ocultar detalhes",
+            "inactive_days": "Inativo por %(inactiveAgeDays)s+ dias",
             "inactive_sessions": "Sessões inativas",
+            "inactive_sessions_explainer_1": "Sessões inativas são sessões que você não usa há algum tempo, mas que continuam a receber chaves de criptografia.",
+            "inactive_sessions_explainer_2": "A remoção de sessões inativas melhora a segurança e o desempenho, além de facilitar a identificação de uma nova sessão suspeita.",
+            "inactive_sessions_list_description": "Considere sair de sessões antigas (%(inactiveAgeDays)sdias ou mais) que você não usa mais.",
             "ip": "Endereço de IP",
             "last_activity": "Última atividade",
+            "manage": "Gerenciar esta sessão",
             "mobile_session": "Sessão móvel",
+            "n_sessions_selected": {
+                "one": "%(count)ssessão selecionada",
+                "other": "%(count)ssessões selecionadas"
+            },
+            "no_inactive_sessions": "Nenhuma sessão inativa foi encontrada.",
+            "no_sessions": "Nenhuma sessão encontrada.",
+            "no_unverified_sessions": "Nenhuma sessão não verificada foi encontrada.",
+            "no_verified_sessions": "Nenhuma sessão verificada foi encontrada.",
             "os": "Sistema operacional",
             "other_sessions_heading": "Outras sessões",
+            "push_heading": "Notificações push",
+            "push_subheading": "Receber notificações push nesta sessão.",
+            "push_toggle": "Ativar as notificações push nesta sessão.",
+            "rename_form_caption": "Esteja ciente de que os nomes das sessões também ficam visíveis para as pessoas com quem você se comunica.",
             "rename_form_heading": "Renomear sessão",
+            "rename_form_learn_more": "Renomeando sessões",
+            "rename_form_learn_more_description_1": "Outros usuários em mensagens diretas e salas das quais você participa podem visualizar uma lista completa de suas sessões.",
+            "rename_form_learn_more_description_2": "Isso dá a eles a certeza de que estão realmente falando com você, mas também significa que eles podem ver o nome da sessão inserido aqui.",
             "security_recommendations": "Recomendações de segurança",
+            "security_recommendations_description": "Melhore a segurança da sua conta seguindo essas recomendações.",
             "session_id": "Identificador de sessão",
+            "show_details": "Mostrar detalhes",
+            "sign_in_with_qr": "Vincular novo dispositivo",
+            "sign_in_with_qr_button": "Mostrar código QR",
+            "sign_in_with_qr_description": "Use um código QR para fazer login em outro dispositivo e configurar mensagens seguras.",
+            "sign_in_with_qr_unsupported": "Não é suportado pelo provedor da sua conta",
             "sign_out": "Sair desta sessão",
+            "sign_out_all_other_sessions": "Sair de todas as outras sessões (%(otherSessionsCount)s)",
+            "sign_out_confirm_description": {
+                "one": "Tem certeza de que deseja sair da %(count)s sessão?",
+                "other": "Tem certeza de que deseja sair das %(count)s sessões?"
+            },
+            "sign_out_n_sessions": {
+                "one": "Sair de%(count)s sessão",
+                "other": "Sair de%(count)s sessões"
+            },
             "title": "Sessões",
             "unknown_session": "Tipo de sessão desconhecido",
             "unverified_session": "Sessão não verificada",
+            "unverified_session_explainer_1": "Esta sessão não oferece suporte à criptografia e, portanto, não pode ser verificada.",
+            "unverified_session_explainer_2": "Você não poderá participar de salas em que a criptografia esteja habilitada ao usar esta sessão.",
+            "unverified_session_explainer_3": "Para maior segurança e privacidade, é recomendável que você use clientes Matrix que ofereçam suporte à criptografia.",
             "unverified_sessions": "Sessões não verificadas",
+            "unverified_sessions_explainer_1": "Sessões não verificadas são sessões que fizeram login com suas credenciais, mas não foram verificadas.",
+            "unverified_sessions_explainer_2": "Você deve se certificar de reconhecer essas sessões, pois elas podem representar um uso não autorizado de sua conta.",
+            "unverified_sessions_list_description": "Verifique suas sessões para obter mensagens seguras aprimoradas ou saia das que você não reconhece ou não usa mais.",
+            "url": "URL",
             "verified_session": "Sessão verificada",
             "verified_sessions": "Sessões verificadas",
+            "verified_sessions_explainer_1": "As sessões verificadas estão em qualquer lugar em que você esteja usando essa conta após inserir sua senha ou confirmar sua identidade com outra sessão verificada.",
+            "verified_sessions_explainer_2": "Isso significa que você tem todas as chaves necessárias para desbloquear suas mensagens criptografadas e confirmar para outros usuários que você confia nesta sessão.",
+            "verified_sessions_list_description": "Para maior segurança, saia de qualquer sessão que você não reconheça ou use mais.",
             "verify_session": "Confirmar sessão",
             "web_session": "Sessão web"
         },
+        "show_avatar_changes": "Mostrar alterações na foto do perfil",
         "show_breadcrumbs": "Mostrar atalhos para salas recentemente visualizadas acima da lista de salas",
         "show_chat_effects": "Mostrar efeitos na conversa (por exemplo: animações ao receber confetes)",
         "show_displayname_changes": "Mostrar alterações de nome e sobrenome",
+        "show_join_leave": "Mostrar mensagens de entrada/saída (convites/remoções/bans não são afetados)",
+        "show_nsfw_content": "Mostrar conteúdo NSFW (\"Não seguro para o trabalho\")",
         "show_read_receipts": "Mostrar confirmações de leitura dos outros usuários",
         "show_redaction_placeholder": "Mostrar um marcador para as mensagens removidas",
         "show_stickers_button": "Mostrar o botão de figurinhas",
         "show_typing_notifications": "Mostrar quando alguém estiver digitando",
+        "showbold": "Mostrar todas as atividades na lista de salas (pontos ou número de mensagens não lidas)",
         "sidebar": {
+            "dialog_title": "<strong>Configurações:</strong> Barra lateral",
+            "metaspaces_favourites_description": "Agrupe todas as suas salas e pessoas favoritas em um só lugar.",
             "metaspaces_home_all_rooms": "Mostrar todas as salas",
             "metaspaces_home_all_rooms_description": "Mostre todas as suas salas no Início, mesmo que elas estejam em um espaço.",
             "metaspaces_home_description": "A página inicial é útil para obter uma visão geral de tudo.",
             "metaspaces_orphans": "Salas fora de um espaço",
+            "metaspaces_orphans_description": "Agrupe todas as suas salas que não fazem parte de um espaço em um só lugar.",
+            "metaspaces_people_description": "Agrupe todas as suas pessoas em um só lugar.",
             "metaspaces_subsection": "Espaços para mostrar",
+            "metaspaces_video_rooms": "Salas de vídeo e conferências",
+            "metaspaces_video_rooms_description": "Agrupar todas as salas de vídeo e conferências privadas.",
+            "metaspaces_video_rooms_description_invite_extension": "Nas conferências, você pode convidar pessoas fora da Matrix.",
+            "spaces_explainer": "Os espaços são formas de agrupar salas e pessoas. Além dos espaços em que você está, você também pode usar alguns pré-construídos.",
             "title": "Barra lateral"
         },
         "start_automatically": "Iniciar automaticamente ao iniciar o sistema",
+        "tac_only_notifications": "Mostrar apenas notificações no centro de atividades do tópico",
         "use_12_hour_format": "Mostrar os horários em formato de 12h (p.ex: 2:30pm)",
         "use_command_enter_send_message": "Usar Command + Enter para enviar uma mensagem",
         "use_command_f_search": "Use Command + F para pesquisar a linha de tempo",
         "use_control_enter_send_message": "Usar Ctrl + Enter para enviar uma mensagem",
         "use_control_f_search": "Use Ctrl + F para pesquisar a linha de tempo",
         "voip": {
+            "allow_p2p": "Permitir Peer-to-Peer para chamadas 1:1",
+            "allow_p2p_description": "Quando ativado, a outra parte talvez poderá ver seu endereço IP",
             "audio_input_empty": "Não foi detectado nenhum microfone",
             "audio_output": "Caixa de som",
             "audio_output_empty": "Nenhuma caixa de som detectada",
+            "auto_gain_control": "Controle de ganho automático",
+            "connection_section": "Conexão",
+            "dialog_title": "<strong>Configurações:</strong> Voz e Vídeo",
+            "echo_cancellation": "Cancelamento de eco",
+            "enable_fallback_ice_server": "Permitir servidor de assistência de chamada de fallback (%(server)s)",
+            "enable_fallback_ice_server_description": "Só se aplica se o seu homeserver não oferecer um. O seu endereço IP seria compartilhado durante uma chamada.",
             "mirror_local_feed": "Espelhar o feed de vídeo local",
             "missing_permissions_prompt": "Permissões de mídia ausentes, clique no botão abaixo para solicitar.",
+            "noise_suppression": "Supressão de ruído",
             "request_permissions": "Solicitar permissões de mídia",
             "title": "Voz e vídeo",
-            "video_input_empty": "Nenhuma câmera detectada"
+            "video_input_empty": "Nenhuma câmera detectada",
+            "video_section": "Configurações de vídeo",
+            "voice_agc": "Ajustar automaticamente o volume do microfone",
+            "voice_processing": "Processamento de voz",
+            "voice_section": "Configurações de voz"
         },
-        "warn_quit": "Avisar antes de sair"
+        "warn_quit": "Avisar antes de sair",
+        "warning": "<w>AVISO:</w><description/>"
     },
     "share": {
+        "link_copied": "Link copiado",
         "permalink_message": "Link da mensagem selecionada",
         "permalink_most_recent": "Link da mensagem mais recente",
+        "share_call": "Link de convite para conferência",
+        "share_call_subtitle": "Link para usuários externos participarem da chamada sem uma conta Matrix:",
+        "title_link": "Compartilhar link",
         "title_message": "Compartilhar Mensagem da Sala",
         "title_room": "Compartilhar sala",
         "title_user": "Compartilhar usuário"
     },
     "slash_command": {
         "addwidget": "Adiciona um widget personalizado na sala por meio de um link",
+        "addwidget_iframe_missing_src": "iframe não tem atributo src",
         "addwidget_invalid_protocol": "Forneça o link de um widget com https:// ou http://",
         "addwidget_missing_url": "Forneça o link de um widget ou de um código de incorporação",
         "addwidget_no_permissions": "Você não pode modificar widgets nesta sala.",
@@ -1890,15 +3063,18 @@
         "command_error": "Erro de comando",
         "converttodm": "Converte a sala para uma conversa",
         "converttoroom": "Converte a conversa para uma sala",
+        "could_not_find_room": "Não foi possível encontrar a sala",
         "deop": "Retira o nível de moderador do usuário com o ID informado",
         "devtools": "Abre a caixa de diálogo Ferramentas do desenvolvedor",
         "discardsession": "Força a atual sessão da comunidade em uma sala criptografada a ser descartada",
         "error_invalid_rendering_type": "Erro de comando: Não é possível manipular o tipo (%(renderingType)s)",
+        "error_invalid_room": "O comando falhou: Não foi possível encontrar a sala (%(roomId)s)",
         "error_invalid_runfn": "Erro de comando: Não é possível manipular o comando de barra.",
+        "error_invalid_user_in_room": "Não foi possível encontrar o usuário na sala",
         "help": "Exibe a lista de comandos com usos e descrições",
         "help_dialog_title": "Ajuda com Comandos",
         "holdcall": "Pausa a chamada na sala atual",
-        "html": "Envia uma mensagem como HTML, sem formatação",
+        "html": "Envia uma mensagem como html, sem interpretá-la como markdown",
         "ignore": "Bloqueia um usuário, escondendo as mensagens dele de você",
         "ignore_dialog_description": "Agora você está bloqueando %(userId)s",
         "ignore_dialog_title": "Usuário bloqueado",
@@ -1906,21 +3082,24 @@
         "invite_3pid_needs_is_error": "Use um servidor de identidade para convidar pessoas por e-mail. Gerencie nas Configurações.",
         "invite_3pid_use_default_is_title": "Usar um servidor de identidade",
         "invite_3pid_use_default_is_title_description": "Use um servidor de identidade para convidar por e-mail. Clique em continuar para usar o servidor de identidade padrão (%(defaultIdentityServerName)s) ou gerencie nas Configurações.",
+        "invite_failed": "O usuário (%(user)s) não foi convidado para %(roomId)s, mas o utilitário de convite não apresentou nenhum erro",
         "join": "Entra em uma sala com o endereço fornecido",
         "jumptodate": "Ir para a data especificada na linha do tempo",
         "jumptodate_invalid_input": "Não foi possível entender a data fornecida (%(inputDate)s). Tente usando o formato AAAA-MM-DD.",
         "lenny": "Adiciona ( ͡° ͜ʖ ͡°) a uma mensagem de texto",
         "me": "Visualizar atividades",
         "msg": "Envia uma mensagem para determinada pessoa",
+        "myavatar": "Altera a foto do seu perfil em todas as salas",
+        "myroomavatar": "Altera sua foto de perfil somente nesta sala atual",
         "myroomnick": "Altera o seu nome e sobrenome apenas nesta sala",
         "nick": "Altera o seu nome e sobrenome",
         "no_active_call": "Nenhuma chamada ativa nesta sala",
         "op": "Define o nível de permissões de um usuário",
         "part_unknown_alias": "Endereço da sala não reconhecido: %(roomAlias)s",
-        "plain": "Envia uma mensagem de texto sem formatação",
+        "plain": "Envia uma mensagem como texto simples, sem interpretá-la como markdown",
         "query": "Abre um chat com determinada pessoa",
         "query_not_found_phone_number": "Não foi possível encontrar o ID Matrix pelo número de telefone",
-        "rageshake": "Envia um relatório de erro",
+        "rageshake": "Enviar um relatório de bug com logs",
         "rainbow": "Envia a mensagem colorida como arco-íris",
         "rainbowme": "Envia o emoji colorido como um arco-íris",
         "remove": "Remove desta sala o usuário com o ID determinado",
@@ -1934,8 +3113,6 @@
         "topic": "Consulta ou altera a descrição da sala",
         "topic_none": "Esta sala não tem descrição.",
         "topic_room_error": "Falha ao obter tópico da sala: Não foi possível encontrar a sala %(roomId)s",
-        "tovirtual": "Muda para a sala virtual desta sala, se houver uma",
-        "tovirtual_not_found": "Nenhuma sala virtual para esta sala",
         "unban": "Remove o banimento do usuário com o ID indicado",
         "unflip": "Adiciona ┬──┬ ノ( ゜-゜ノ) a uma mensagem de texto simples",
         "unholdcall": "Retoma a chamada na sala atual",
@@ -1950,41 +3127,79 @@
         "upgraderoom": "Atualiza a sala para uma nova versão",
         "upgraderoom_permission_error": "Você não tem as permissões necessárias para usar este comando.",
         "usage": "Uso",
+        "view": "Visualizações da sala com o endereço fornecido",
         "whois": "Exibe informação sobre um usuário"
     },
+    "sliding_sync_legacy_no_longer_supported": "A sliding sync antiga não é mais suportada: saia e entre novamente para ativar o novo sinalizador de sliding sync",
     "space": {
         "add_existing_room_space": {
+            "create": "Quer​•​adicionar​•​uma​•​nova​•​sala?",
             "create_prompt": "Criar uma nova sala",
             "dm_heading": "Conversas",
+            "error_heading": "Nem​•​todos​•​os​•​selecionados​•​foram​•​adicionados",
             "progress_text": {
                 "one": "Adicionando sala…",
                 "other": "Adicionando salas… (%(progress)s de %(count)s)"
             },
             "space_dropdown_label": "Seleção de Espaços",
-            "space_dropdown_title": "Adicionar salas existentes"
+            "space_dropdown_title": "Adicionar salas existentes",
+            "subspace_moved_note": "A​•​adição​•​de​•​espaços​•​foi​•​movida."
         },
         "add_existing_subspace": {
             "create_button": "Criar um novo espaço",
+            "create_prompt": "Quer​•​adicionar​•​um​•​novo​•​espaço?",
+            "filter_placeholder": "Procure por espaços",
             "space_dropdown_title": "Adicionar espaço existente"
         },
         "context_menu": {
+            "devtools_open_timeline": "Veja​•​o​•​cronograma​•​da​•​sala​•​(devtools)",
             "explore": "Explorar salas",
+            "home": "Espaço​•​doméstico",
+            "manage_and_explore": "Gerencie​•​e​•​explore​•​salas",
             "options": "Opções do espaço"
         },
+        "failed_load_rooms": "Falha​•​ao​•​carregar​•​a​•​lista​•​de​•​salas.",
+        "failed_remove_rooms": "Falha​•​ao​•​remover​•​algumas​•​salas.​•​Tente​•​novamente​•​mais​•​tarde",
+        "incompatible_server_hierarchy": "Seu​•​servidor​•​não​•​suporta​•​a​•​exibição​•​de​•​hierarquias​•​de​•​espaço.",
         "invite": "Convidar pessoas",
         "invite_description": "Convidar com email ou nome de usuário",
         "invite_link": "Compartilhar link de convite",
+        "joining_space": "Juntando-se",
         "landing_welcome": "Boas-vindas ao <name/>",
         "leave_dialog_action": "Sair do espaço",
+        "leave_dialog_description": "Você​•​está​•​prestes​•​a​•​deixar​•​<spaceName/>.",
+        "leave_dialog_only_admin_room_warning": "Você​•​é​•​o​•​único​•​administrador​•​de​•​algumas​•​das​•​salas​•​ou​•​espaços​•​que​•​deseja​•​deixar.​•​Deixá-los​•​os​•​deixará​•​sem​•​administradores.",
+        "leave_dialog_only_admin_warning": "Você​•​é​•​o​•​único​•​administrador​•​deste​•​espaço.​•​Sair​•​dele​•​significa​•​que​•​ninguém​•​terá​•​controle​•​sobre​•​ele.",
         "leave_dialog_option_all": "Sair de todas as salas",
+        "leave_dialog_option_intro": "Você​•​gostaria​•​de​•​deixar​•​as​•​salas​•​neste​•​espaço?",
         "leave_dialog_option_none": "Não saia de nenhuma sala",
         "leave_dialog_option_specific": "Sair de algumas salas",
+        "leave_dialog_public_rejoin_warning": "Você não poderá entrar novamente a menos que seja convidado novamente.",
+        "leave_dialog_title": "Sair​•de ​%(spaceName)s",
+        "mark_suggested": "Marcar​•​como​•​sugerido",
+        "no_search_result_hint": "Você​•​pode​•​tentar​•​uma​•​pesquisa​•​diferente​•​ou​•​verificar​•​se​•​há​•​erros​•​de​•​digitação.",
+        "preferences": {
+            "sections_section": "Seções​•​para​•​mostrar",
+            "show_people_in_space": "Isso​•​agrupa​•​seus​•​bate-papos​•​com​•​membros​•​desse​•​espaço.​•​Desativar​•​isso​•​ocultará​•​esses​•​bate-papos​•​da​•​sua​•​visão​•​de%(spaceName)s."
+        },
         "room_filter_placeholder": "Buscar salas",
         "search_children": "Pesquisar %(spaceName)s",
-        "share_public": "Compartilhar o seu espaço público"
+        "search_placeholder": "Pesquisar nomes e descrições",
+        "select_room_below": "Selecione​•​uma​•​sala​•​abaixo​•​primeiro",
+        "share_public": "Compartilhar o seu espaço público",
+        "suggested": "Sugerido",
+        "suggested_tooltip": "Esta​•​sala​•​é​•​sugerida​•​como​•​uma​•​boa​•​opção​•​para​•​entrar",
+        "title_when_query_available": "Resultados",
+        "title_when_query_unavailable": "Salas​•​e​•​espaços",
+        "unmark_suggested": "Marcar como não sugerido",
+        "user_lacks_permission": "Você​•​não​•​tem​•​permissão"
+    },
+    "space_settings": {
+        "title": "Configurações - %(spaceName)s"
     },
     "spaces": {
         "error_no_permission_add_room": "Você não tem permissão para adicionar salas neste espaço",
+        "error_no_permission_add_space": "Você não tem permissão para adicionar espaços a este espaço",
         "error_no_permission_create_room": "Você não tem permissão para criar novas salas neste espaço",
         "error_no_permission_invite": "Você não tem permissão para convidar pessoas para este espaço"
     },
@@ -1997,14 +3212,41 @@
             "network_dropdown_available_invalid": "Não foi possível encontrar este servidor ou sua lista de salas",
             "network_dropdown_available_invalid_forbidden": "Você não tem a permissão para ver a lista de salas deste servidor",
             "network_dropdown_available_valid": "Muito bem",
+            "network_dropdown_remove_server_adornment": "Remover servidor “%(roomServer)s”",
             "network_dropdown_required_invalid": "Digite um nome de servidor",
+            "network_dropdown_selected_label": "Mostrar: Salas Matrix",
+            "network_dropdown_selected_label_instance": "Mostrar: %(instance)s salas do servidor (%(server)s )",
             "network_dropdown_your_server_description": "Seu servidor"
         }
     },
     "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Se você não conseguir ver quem está procurando, envie seu link de convite.",
+        "cant_find_room_helpful_hint": "Se você não conseguir encontrar a sala que está procurando, peça um convite ou crie uma nova sala.",
+        "copy_link_text": "Copiar link de convite",
+        "count_of_members": {
+            "one": "%(count)s Membro",
+            "other": "%(count)s Membros"
+        },
         "create_new_room_button": "Criar nova sala",
+        "failed_querying_public_rooms": "Falha ao consultar salas públicas",
+        "failed_querying_public_spaces": "Falha ao consultar espaços públicos",
+        "group_chat_section_title": "Outras opções",
+        "heading_with_query": "Use \"%(query)s\" para pesquisar",
+        "heading_without_query": "Pesquisar por",
+        "join_button_text": "Junte-se a %(roomAddress)s",
+        "keyboard_scroll_hint": "Use <arrows/> para rolar",
+        "messages_label": "Mensagens",
+        "other_rooms_in_space": "Outras salas em %(spaceName)s",
         "public_rooms_label": "Salas públicas",
-        "recently_viewed_section_title": "Visualizado recentemente"
+        "public_spaces_label": "Espaços públicos",
+        "recent_searches_section_title": "Pesquisas recentes",
+        "recently_viewed_section_title": "Visualizado recentemente",
+        "remove_filter": "Remover filtro de pesquisa para %(filter)s",
+        "result_may_be_hidden_privacy_warning": "Alguns resultados podem estar ocultos por questões de privacidade",
+        "result_may_be_hidden_warning": "Alguns resultados podem estar ocultos",
+        "search_dialog": "Diálogo de pesquisa",
+        "spaces_title": "Espaços em que você está",
+        "start_group_chat_button": "Inicie um bate-papo em grupo"
     },
     "stickers": {
         "empty": "No momento, você não tem pacotes de figurinhas ativados",
@@ -2021,22 +3263,38 @@
         "integration_manager": "Use bots, integrações, widgets e pacotes de figurinhas",
         "intro": "Para continuar, você precisa aceitar os termos deste serviço.",
         "summary_identity_server_1": "Encontre outras pessoas por telefone ou e-mail",
-        "summary_identity_server_2": "Seja encontrada/o por número de celular ou por e-mail",
+        "summary_identity_server_2": "Seja encontrado por telefone ou e-mail",
         "tac_button": "Revise os termos e condições",
         "tac_description": "Para continuar usando o servidor local %(homeserverDomain)s, você deve ler e concordar com nossos termos e condições.",
         "tac_title": "Termos e Condições",
         "tos": "Termos de serviço"
     },
     "theme": {
-        "light_high_contrast": "Claro (alto contraste)"
+        "light_high_contrast": "Claro (alto contraste)",
+        "match_system": "Padrão do sistema"
     },
+    "thread_view_back_action_label": "Voltar ao tópico",
     "threads": {
+        "all_threads": "Todos os tópicos",
+        "all_threads_description": "Mostra todos os tópicos da sala atual",
         "count_of_reply": {
             "one": "%(count)s resposta",
             "other": "%(count)s respostas"
         },
+        "empty_description": "Use “%(replyInThread)s” ao passar o mouse sobre uma mensagem.",
+        "empty_title": "Os tópicos ajudam a manter suas conversas dentro do tópico e fáceis de acompanhar.",
+        "error_start_thread_existing_relation": "Não é possível criar um tópico a partir de um evento com uma relação existente",
+        "mark_all_read": "Marcar tudo como lido",
+        "my_threads": "Meus tópicos",
+        "my_threads_description": "Mostra todos os tópicos em que você participou",
+        "open_thread": "Abrir tópico",
         "show_thread_filter": "Exibir:"
     },
+    "threads_activity_centre": {
+        "header": "Atividade de tópicos",
+        "no_rooms_with_threads_notifs": "Você ainda não tem salas com notificações de tópicos.",
+        "no_rooms_with_unread_threads": "Você ainda não tem salas com tópicos não lidos."
+    },
     "time": {
         "about_day_ago": "há aproximadamente um dia",
         "about_hour_ago": "há aproximadamente uma hora",
@@ -2051,21 +3309,48 @@
         "in_n_days": "dentro de %(num)s dias",
         "in_n_hours": "dentro de %(num)s horas",
         "in_n_minutes": "dentro de %(num)s minutos",
+        "left": "%(timeRemaining)s restante(s)",
         "minutes_seconds_left": "%(minutes)sm %(seconds)ss restantes",
         "n_days_ago": "há %(num)s dias",
         "n_hours_ago": "há %(num)s horas",
         "n_minutes_ago": "há %(num)s minutos",
-        "seconds_left": "%(seconds)s restantes"
+        "seconds_left": "%(seconds)s restantes",
+        "short_days": "%(value)sd",
+        "short_days_hours_minutes_seconds": "%(days)sd %(hours)s h %(minutes)s m %(seconds)s s",
+        "short_hours": "%(value)sh",
+        "short_hours_minutes_seconds": "%(hours)sh %(minutes)s m %(seconds)s s",
+        "short_minutes": "%(value)sm",
+        "short_minutes_seconds": "%(minutes)sm %(seconds)s s",
+        "short_seconds": "%(value)ss"
     },
     "timeline": {
         "context_menu": {
+            "collapse_reply_thread": "Recolher tópico de resposta",
             "external_url": "Link do código-fonte",
-            "resent_unsent_reactions": "Reenviar %(unsentCount)s reações"
+            "open_in_osm": "Abrir no OpenStreetMap",
+            "report": "Denunciar",
+            "resent_unsent_reactions": "Reenviar %(unsentCount)s reações",
+            "show_url_preview": "Mostrar pré-visualização",
+            "view_related_event": "Exibir evento relacionado",
+            "view_source": "Ver fonte"
         },
         "creation_summary_dm": "%(creator)s criou esta conversa.",
         "creation_summary_room": "%(creator)s criou e configurou esta sala.",
+        "decryption_failure": {
+            "blocked": "O remetente bloqueou você de receber esta mensagem porque seu dispositivo não foi verificado",
+            "historical_event_no_key_backup": "Mensagens históricas não estão disponíveis neste dispositivo",
+            "historical_event_unverified_device": "Você precisa verificar este dispositivo para acessar as mensagens históricas",
+            "historical_event_user_not_joined": "Você não tem acesso a esta mensagem",
+            "sender_identity_previously_verified": "A identidade verificada do remetente foi alterada",
+            "sender_unsigned_device": "Enviado de um dispositivo inseguro.",
+            "unable_to_decrypt": "Não foi possível descriptografar a mensagem"
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Decriptando",
         "download_action_downloading": "Baixando",
+        "download_failed": "Falha no download",
+        "download_failed_description": "Ocorreu um erro ao baixar esse arquivo",
+        "e2e_state": "Estado da criptografia de ponta a ponta",
         "edits": {
             "tooltip_label": "Editado em %(date)s. Clique para ver edições.",
             "tooltip_sub": "Clicar para ver edições",
@@ -2074,24 +3359,34 @@
         "error_no_renderer": "Este evento não pôde ser exibido",
         "error_rendering_message": "Não foi possível carregar esta mensagem",
         "historical_messages_unavailable": "Você não pode ver as mensagens anteriores",
+        "in_room_name": " em <strong>%(room)s</strong>",
         "io.element.widgets.layout": "%(senderName)s atualizou o layout da sala",
+        "late_event_separator": "Enviado originalmente em %(dateTime)s",
         "load_error": {
             "no_permission": "Não foi possível carregar um trecho específico da conversa desta sala, porque parece que você não tem permissão para ler a mensagem em questão.",
             "title": "Não foi possível carregar um trecho da conversa",
             "unable_to_find": "Não foi possível carregar um trecho específico da conversa desta sala."
         },
         "m.audio": {
+            "error_downloading_audio": "Erro ao baixar o áudio",
             "error_processing_audio": "Erro ao processar a mensagem de áudio",
-            "error_processing_voice_message": "Erro ao processar a mensagem de voz"
+            "error_processing_voice_message": "Erro ao processar a mensagem de voz",
+            "unnamed_audio": "Áudio sem nome"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Ver localização ao vivo"
         },
         "m.call": {
+            "video_call_ended": "Chamada de vídeo encerrada",
             "video_call_started": "Chamada de vídeo iniciada em %(roomName)s.",
+            "video_call_started_text": "%(name)s iniciou uma chamada de vídeo",
             "video_call_started_unsupported": "Chamada de vídeo iniciada em %(roomName)s. (não compatível com este navegador)"
         },
         "m.call.hangup": {
             "dm": "Chamada encerrada"
         },
         "m.call.invite": {
+            "answered_elsewhere": "Respondido em outro lugar",
             "call_back_prompt": "Chamar de volta",
             "declined": "Chamada recusada",
             "failed_connect_media": "Não foi possível conectar-se à mídia",
@@ -2109,10 +3404,12 @@
         },
         "m.file": {
             "error_decrypting": "Erro ao descriptografar o anexo",
-            "error_invalid": "Arquivo inválido %(extra)s"
+            "error_invalid": "Arquivo inválido"
         },
         "m.image": {
+            "error": "Não é possível mostrar a imagem devido a um erro",
             "error_decrypting": "Erro ao descriptografar a imagem",
+            "error_downloading": "Erro ao baixar a imagem",
             "sent": "%(senderDisplayName)s enviou uma imagem.",
             "show_image": "Mostrar imagem"
         },
@@ -2121,7 +3418,9 @@
             "you_started": "Você enviou uma solicitação de confirmação"
         },
         "m.location": {
-            "full": "%(senderName)s compartilhou sua localização"
+            "full": "%(senderName)s compartilhou sua localização",
+            "location": "Compartilhou um local: ",
+            "self_location": "Compartilhou sua localização: "
         },
         "m.poll": {
             "count_of_votes": {
@@ -2130,6 +3429,7 @@
             }
         },
         "m.poll.end": {
+            "ended": "Encerrou uma enquete",
             "sender_ended": "%(senderName)s encerrou uma enquete"
         },
         "m.poll.start": "%(senderName)s começou uma enquete - %(pollQuestion)s",
@@ -2156,11 +3456,16 @@
         },
         "m.room.create": {
             "continuation": "Esta sala é uma continuação de outra conversa.",
-            "see_older_messages": "Clique aqui para ver as mensagens mais antigas."
+            "see_older_messages": "Clique aqui para ver as mensagens mais antigas.",
+            "unknown_predecessor": "Não foi possível encontrar a versão antiga desta sala (ID da sala:%(roomId)s) e não recebemos 'via_servers' para procurá-la.",
+            "unknown_predecessor_guess_server": "Não foi possível encontrar a versão antiga desta sala (ID da sala:%(roomId)s) e não recebemos 'via_servers' para procurá-la. É possível que adivinhar o servidor a partir do ID da sala funcione. Se você quiser tentar, clique neste link:"
         },
         "m.room.encryption": {
             "disable_attempt": "A tentativa de desativar a criptografia foi ignorada",
             "disabled": "Criptografia desativada",
+            "enabled": "As mensagens nesta sala são criptografadas de ponta a ponta. Quando as pessoas ingressam, você pode verificá-las em seus perfis, basta tocar na foto do perfil.",
+            "enabled_dm": "As mensagens aqui são criptografadas de ponta a ponta. Verifique %(displayName)s no perfil deles - toque na foto do perfil.",
+            "enabled_local": "As mensagens neste bate-papo serão criptografadas de ponta a ponta.",
             "parameters_changed": "Alguns parâmetros de criptografia foram modificados.",
             "unsupported": "A criptografia usada nesta sala não é suportada."
         },
@@ -2178,6 +3483,7 @@
         },
         "m.room.join_rules": {
             "invite": "%(senderDisplayName)s tornou a sala disponível apenas por convite.",
+            "knock": "%(senderDisplayName)s alterou a regra de adesão para pedir para ingressar.",
             "public": "%(senderDisplayName)s tornou a sala pública para quem conhece o link.",
             "restricted": "%(senderDisplayName)s modificou quem pode se juntar a esta sala.",
             "restricted_settings": "%(senderDisplayName)s modificou quem pode se juntar a esta sala. <a>Ver configurações</a>.",
@@ -2190,6 +3496,7 @@
             "ban_reason": "%(senderName)s baniu %(targetName)s: %(reason)s",
             "change_avatar": "%(senderName)s mudou sua foto de perfil",
             "change_name": "%(oldDisplayName)s mudou seu nome de exibição para %(displayName)s",
+            "change_name_avatar": "%(oldDisplayName)s alterou o nome de exibição e a foto do perfil",
             "invite": "%(senderName)s convidou %(targetName)s",
             "join": "%(targetName)s entrou na sala",
             "kick": "%(senderName)s removeu %(targetName)s",
@@ -2198,6 +3505,7 @@
             "left_reason": "%(targetName)s saiu da sala: %(reason)s",
             "no_change": "%(senderName)s não fez mudanças",
             "reject_invite": "%(targetName)s rejeitou o convite",
+            "reject_invite_reason": "%(targetName)s rejeitou o convite: %(reason)s",
             "remove_avatar": "%(senderName)s removeu sua foto de perfil",
             "remove_name": "%(senderName)s removeu seu nome de exibição (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s definiu sua foto de perfil",
@@ -2233,10 +3541,14 @@
             "sent": "%(senderName)s enviou um convite para %(targetDisplayName)s entrar na sala."
         },
         "m.room.tombstone": "%(senderDisplayName)s atualizou esta sala.",
-        "m.room.topic": "%(senderDisplayName)s alterou a descrição para \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s alterou a descrição para \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s removeu o tópico."
+        },
         "m.sticker": "%(senderDisplayName)s enviou uma figurinha.",
         "m.video": {
-            "error_decrypting": "Erro ao descriptografar o vídeo"
+            "error_decrypting": "Erro ao descriptografar o vídeo",
+            "show_video": "Mostrar vídeo"
         },
         "m.widget": {
             "added": "O widget %(widgetName)s foi criado por %(senderName)s",
@@ -2249,10 +3561,14 @@
             "removed": "O widget %(widgetName)s foi removido por %(senderName)s"
         },
         "mab": {
+            "collapse_reply_chain": "Recolher citações",
             "copy_link_thread": "Copiar ligação para o tópico",
+            "expand_reply_chain": "Expandir citações",
             "label": "Ações da mensagem",
             "view_in_room": "Ver na sala"
         },
+        "message_timestamp_received_at": "Recebido em: %(dateTime)s",
+        "message_timestamp_sent_at": "Enviado em: %(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s alterou uma regra que bania o que correspondia a %(oldGlob)s para corresponder a %(newGlob)s devido à %(reason)s",
             "changed_rule_rooms": "%(senderName)s alterou uma regra que bania salas que correspondiam a %(oldGlob)s para corresponder a %(newGlob)s devido à %(reason)s",
@@ -2273,14 +3589,21 @@
             "updated_rule_servers": "%(senderName)s atualizou a regra que bane servidores que correspondem a %(glob)s devido à %(reason)s",
             "updated_rule_users": "%(senderName)s atualizou a regra de banimento de usuários correspondendo a %(glob)s devido à %(reason)s"
         },
+        "no_permission_messages_before_invite": "Você não tem permissão para ver as mensagens de antes de ser convidado.",
+        "no_permission_messages_before_join": "Você não tem permissão para ver as mensagens de antes de entrar.",
+        "pending_moderation": "Mensagem pendente de moderação",
+        "pending_moderation_reason": "Mensagem pendente de moderação: %(reason)s",
         "reactions": {
             "add_reaction_prompt": "Adicionar reação",
-            "label": "%(reactors)s reagiram com %(content)s"
+            "custom_reaction_fallback_label": "Reação personalizada",
+            "label": "%(reactors)s reagiram com %(content)s",
+            "tooltip_caption": "Reagiu com %(shortName)s"
         },
         "read_receipt_title": {
             "one": "Visto por %(count)s pessoa",
             "other": "Visto por %(count)s pessoas"
         },
+        "read_receipts_label": "Confirmações de leitura",
         "redacted": {
             "tooltip": "Mensagem apagada em %(date)s"
         },
@@ -2295,7 +3618,9 @@
             "dialog_title": "Adicionar uma integração"
         },
         "self_redaction": "Mensagem apagada",
+        "send_state_encrypting": "Criptografando sua mensagem...",
         "send_state_failed": "Falhou a enviar",
+        "send_state_sending": "Enviando sua mensagem...",
         "send_state_sent": "A sua mensagem foi enviada",
         "summary": {
             "banned": {
@@ -2306,6 +3631,14 @@
                 "other": "foram banidos %(count)s vezes",
                 "one": "foram banidos"
             },
+            "changed_avatar": {
+                "one": "%(oneUser)smudou sua foto de perfil",
+                "other": "%(oneUser)smudou suas fotos de perfil %(count)s vezes"
+            },
+            "changed_avatar_multiple": {
+                "one": "%(severalUsers)s mudou sua foto de perfil",
+                "other": "%(severalUsers)s mudou suas fotos de perfil %(count)s vezes"
+            },
             "changed_name": {
                 "other": "%(oneUser)s alterou o nome e sobrenome %(count)s vezes",
                 "one": "%(oneUser)s alterou o nome e sobrenome"
@@ -2314,6 +3647,15 @@
                 "other": "%(severalUsers)s alteraram o nome e sobrenome %(count)s vezes",
                 "one": "%(severalUsers)s alteraram o nome e sobrenome"
             },
+            "format": "%(nameList)s%(transitionList)s",
+            "hidden_event": {
+                "one": "%(oneUser)s enviou uma mensagem oculta",
+                "other": "%(oneUser)s enviou %(count)s mensagens ocultas"
+            },
+            "hidden_event_multiple": {
+                "one": "%(severalUsers)senviou uma mensagem oculta",
+                "other": "%(severalUsers)senviou %(count)s mensagens ocultas"
+            },
             "invite_withdrawn": {
                 "other": "%(oneUser)s teve os convites removidos %(count)s vezes",
                 "one": "%(oneUser)s teve o convite removido"
@@ -2323,12 +3665,12 @@
                 "one": "%(severalUsers)s tiveram os convites retirados"
             },
             "invited": {
-                "other": "foi convidada/o %(count)s vezes",
-                "one": "foi convidada/o"
+                "one": "foi convidado",
+                "other": "foi convidado %(count)s vezes"
             },
             "invited_multiple": {
-                "other": "foram convidadas/os %(count)s vezes",
-                "one": "foram convidadas/os"
+                "one": "foram convidados",
+                "other": "foram convidados %(count)s vezes"
             },
             "joined": {
                 "other": "%(oneUser)s entrou %(count)s vezes",
@@ -2370,6 +3712,22 @@
                 "other": "%(severalUsers)s não fizeram alterações %(count)s vezes",
                 "one": "%(severalUsers)s não fizeram alterações"
             },
+            "pinned_events": {
+                "one": "%(oneUser)salterado o <a>mensagens fixadas</a> para a sala",
+                "other": "%(oneUser)salterou o <a>mensagens fixadas</a> para a sala %(count)s vezes"
+            },
+            "pinned_events_multiple": {
+                "one": "%(severalUsers)salterado o <a>mensagens fixadas</a> para a sala",
+                "other": "%(severalUsers)salterado o <a>mensagens fixadas</a> para a sala %(count)s vezes"
+            },
+            "redacted": {
+                "one": "%(oneUser)sremoveu uma mensagem",
+                "other": "%(oneUser)sremoveu %(count)s mensagens"
+            },
+            "redacted_multiple": {
+                "one": "%(severalUsers)s removeu uma mensagem",
+                "other": "%(severalUsers)sremoveu %(count)s mensagens"
+            },
             "rejected_invite": {
                 "other": "%(oneUser)s recusou o convite %(count)s vezes",
                 "one": "%(oneUser)s recusou o convite"
@@ -2386,6 +3744,14 @@
                 "other": "%(severalUsers)s saíram e entraram %(count)s vezes",
                 "one": "%(severalUsers)s saíram e entraram"
             },
+            "server_acls": {
+                "one": "%(oneUser)salterou as ACLs do servidor",
+                "other": "%(oneUser)salterou as ACLs do servidor%(count)s vezes"
+            },
+            "server_acls_multiple": {
+                "one": "%(severalUsers)salterou as ACLs do servidor",
+                "other": "%(severalUsers)salterou as ACLs do servidor%(count)s vezes"
+            },
             "unbanned": {
                 "other": "teve o banimento removido %(count)s vezes",
                 "one": "teve o banimento removido"
@@ -2395,6 +3761,7 @@
                 "one": "tiveram o banimento removido"
             }
         },
+        "thread_info_basic": "De um tópico",
         "typing_indicator": {
             "more_users": {
                 "other": "%(names)s e %(count)s outras pessoas estão digitando…",
@@ -2403,6 +3770,7 @@
             "one_user": "%(displayName)s está digitando…",
             "two_users": "%(names)s e %(lastPerson)s estão digitando…"
         },
+        "undecryptable_tooltip": "Esta mensagem não pôde ser descriptografada",
         "url_preview": {
             "close": "Fechar a visualização",
             "show_n_more": {
@@ -2414,9 +3782,17 @@
     "truncated_list_n_more": {
         "other": "E %(count)s mais..."
     },
+    "unsupported_browser": {
+        "description": "Se você continuar, alguns recursos podem parar de funcionar e há o risco de você perder dados no futuro. Atualize seu navegador para continuar usando %(brand)s.",
+        "title": "%(brand)s não suporta este navegador"
+    },
+    "unsupported_server_description": "Este servidor está usando uma versão mais antiga do Matrix. Atualize para o Matrix %(version)s para usar %(brand)s sem erros.",
+    "unsupported_server_title": "Seu servidor não é compatível",
     "update": {
         "changelog": "Registro de alterações",
         "check_action": "Verificar atualizações",
+        "checking": "Verificando se há uma atualização...",
+        "downloading": "Baixando a atualização...",
         "error_encountered": "Erro encontrado (%(errorDetail)s).",
         "error_unable_load_commit": "Não foi possível carregar os detalhes do envio: %(msg)s",
         "new_version_available": "Nova versão disponível. <a>Atualize agora.</a>",
@@ -2427,6 +3803,13 @@
         "toast_title": "Atualizar o %(brand)s",
         "unavailable": "Indisponível"
     },
+    "update_room_access_modal": {
+        "description": "Para criar um link de compartilhamento, torne esta sala <b>pública</b> ou habilite a opção para que os usuários <b>peçam para entrar</b>. Isso permite que os convidados entrem sem serem convidados.",
+        "dont_change_description": "Se não quiser alterar o acesso a essa sala, você pode criar uma nova sala para o link de chamada.",
+        "no_change": "Eu não quero mudar o nível de acesso.",
+        "revert_access_description": "(Isso pode ser revertido para o valor anterior nas configurações da sala: <b>Segurança e privacidade</b> / <b>Acesso</b>)",
+        "title": "Permitir que usuários convidados entrem nesta sala"
+    },
     "upload_failed_generic": "O envio do arquivo '%(fileName)s' falhou.",
     "upload_failed_size": "O arquivo '%(fileName)s' excede o limite de tamanho deste homeserver para uploads",
     "upload_failed_title": "O envio falhou",
@@ -2436,6 +3819,7 @@
         "error_files_too_large": "Esses arquivos são <b>muito grandes</b> para serem enviados. O limite do tamanho de arquivos é %(limit)s.",
         "error_some_files_too_large": "Alguns arquivos são <b>muito grandes</b> para serem enviados. O limite do tamanho de arquivos é %(limit)s.",
         "error_title": "Erro no envio",
+        "not_image": "O arquivo que você escolheu não é um arquivo de imagem válido.",
         "title": "Enviar arquivos",
         "title_progress": "Enviar arquivos (%(current)s de %(total)s)",
         "upload_all_button": "Enviar tudo",
@@ -2446,17 +3830,11 @@
     },
     "user_info": {
         "admin_tools_section": "Ferramentas de administração",
+        "ban_button_room": "Banir da sala",
+        "ban_button_space": "Banir do espaço",
         "ban_room_confirm_title": "Banir de %(roomName)s",
         "ban_space_everything": "Bani-los de tudo que eu faço",
         "ban_space_specific": "Bani-los de coisas específicas que faço",
-        "count_of_sessions": {
-            "other": "%(count)s sessões",
-            "one": "%(count)s sessão"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sessões confirmadas",
-            "one": "1 sessão confirmada"
-        },
         "deactivate_confirm_action": "Desativar usuário",
         "deactivate_confirm_description": "Desativar este usuário irá desconectá-lo e impedi-lo de fazer o login novamente. Além disso, ele sairá de todas as salas em que estiver. Esta ação não pode ser revertida. Tem certeza de que deseja desativar este usuário?",
         "deactivate_confirm_title": "Desativar usuário?",
@@ -2464,17 +3842,25 @@
         "demote_self_confirm_description_space": "Você não poderá desfazer esta mudança, já que está tirando seu próprio cargo, e se você for o último usuário com privilégios neste espaço será impossível ganhá-los novamente.",
         "demote_self_confirm_room": "Você não poderá desfazer essa alteração, já que está rebaixando sua própria permissão. Se você for a última pessoa nesta sala, será impossível recuperar a permissão atual.",
         "demote_self_confirm_title": "Reduzir seu próprio privilégio?",
+        "disinvite_button_room": "Desconvidar da sala",
         "disinvite_button_room_name": "Cancelar convite de %(roomName)s",
-        "edit_own_devices": "Editar dispositivos",
+        "disinvite_button_space": "Desconvidar do espaço",
         "error_ban_user": "Não foi possível banir o usuário",
         "error_deactivate": "Falha ao desativar o usuário",
-        "error_mute_user": "Não foi possível remover notificações da/do usuária/o",
+        "error_kicking_user": "Falha ao remover usuário",
+        "error_mute_user": "Falha ao silenciar o usuário",
         "error_revoke_3pid_invite_description": "Não foi possível revogar o convite. O servidor pode estar com um problema temporário ou você não tem permissões suficientes para revogar o convite.",
         "error_revoke_3pid_invite_title": "Falha ao revogar o convite",
-        "hide_sessions": "Esconder sessões",
-        "hide_verified_sessions": "Esconder sessões confirmadas",
+        "ignore_button": "Ignorar",
+        "ignore_confirm_description": "Todas as mensagens e convites desse usuário serão ocultados. Você tem certeza de que deseja ignorá-lo?",
+        "ignore_confirm_title": "Ignorar %(user)s",
         "invited_by": "Convidado por %(sender)s",
         "jump_to_rr_button": "Ir para a confirmação de leitura",
+        "kick_button_room": "Remover da sala",
+        "kick_button_room_name": "Remover de %(roomName)s",
+        "kick_button_space": "Remover do espaço",
+        "kick_button_space_everything": "Remova-os de tudo que eu puder",
+        "kick_space_specific": "Remover de coisas específicas que eu posso fazer",
         "kick_space_warning": "Eles ainda poderão acessar tudo o que você não for administrador.",
         "promote_warning": "Você não poderá desfazer essa alteração, pois está promovendo o usuário ao mesmo nível de permissão que você.",
         "redact": {
@@ -2482,26 +3868,38 @@
                 "other": "Apagar %(count)s mensagens para todos",
                 "one": "Remover 1 mensagem"
             },
+            "confirm_description_1": {
+                "one": "Você está prestes a remover a %(count)s mensagem de %(user)s. Isso os removerá permanentemente para todos na conversa. Você deseja continuar?",
+                "other": "Você está prestes a remover %(count)s mensagens de %(user)s. Isso os removerá permanentemente para todos na conversa. Você deseja continuar?"
+            },
             "confirm_description_2": "Quando há muitas mensagens, isso pode levar algum tempo. Por favor, não recarregue o seu cliente enquanto isso.",
+            "confirm_keep_state_explainer": "Desmarque se você também deseja remover as mensagens do sistema desse usuário (por exemplo, alteração de associação, alteração de perfil...)",
+            "confirm_keep_state_label": "Preservar as mensagens do sistema",
             "confirm_title": "Apagar mensagens de %(user)s na sala",
             "no_recent_messages_description": "Tente rolar para cima na conversa para ver se há mensagens anteriores.",
             "no_recent_messages_title": "Nenhuma mensagem recente de %(user)s foi encontrada"
         },
-        "redact_button": "Apagar mensagens desta pessoa na sala",
+        "redact_button": "Remover mensagens",
         "revoke_invite": "Revogar o convite",
         "room_encrypted": "As mensagens nesta sala estão criptografadas de ponta a ponta.",
         "room_encrypted_detail": "Suas mensagens são protegidas e somente você e o destinatário têm as chaves exclusivas para desbloqueá-las.",
         "room_unencrypted": "As mensagens nesta sala não estão criptografadas de ponta a ponta.",
         "room_unencrypted_detail": "Em salas criptografadas, suas mensagens estão seguras e apenas você e a pessoa que a recebe têm as chaves únicas que permitem a sua leitura.",
-        "share_button": "Compartilhar este usuário",
+        "send_message": "Enviar mensagem",
+        "share_button": "Compartilhar perfil",
+        "unban_button_room": "Desbanir da sala",
+        "unban_button_space": "Desbanir do espaço",
         "unban_room_confirm_title": "Desbanir de %(roomName)s",
         "unban_space_everything": "Desbani-los de tudo que eu faço",
         "unban_space_specific": "Desbani-los de coisas específicas que eu faço",
         "unban_space_warning": "Eles não poderão acessar o que você não é administrador.",
+        "unignore_button": "Designorar",
+        "verification_unavailable": "Verificação de usuário indisponível",
         "verify_button": "Confirmar usuário",
         "verify_explainer": "Para maior segurança, confirme este usuário comparando um código único em ambos os aparelhos."
     },
     "user_menu": {
+        "link_new_device": "Vincular novo dispositivo",
         "settings": "Todas as configurações",
         "switch_theme_dark": "Alternar para o modo escuro",
         "switch_theme_light": "Alternar para o modo claro"
@@ -2525,22 +3923,41 @@
         "camera_disabled": "Sua câmera está desligada",
         "camera_enabled": "Sua câmera ainda está habilitada",
         "cannot_call_yourself_description": "Você não pode iniciar uma chamada consigo mesmo.",
+        "close_lobby": "Fechar lobby",
         "connecting": "Conectando",
         "connection_lost": "A conectividade com o servidor foi perdida",
         "connection_lost_description": "Você não pode fazer chamadas sem uma conexão com o servidor.",
         "consulting": "Consultar com %(transferTarget)s. Tranferir para <a> %(transferee)s</a>",
         "default_device": "Aparelho padrão",
+        "dial": "Discar",
         "dialpad": "Teclado de discagem",
         "disable_camera": "Desligar câmera",
         "disable_microphone": "Silenciar microfone",
+        "disabled_no_one_here": "Não há ninguém aqui para ligar",
+        "disabled_no_perms_start_video_call": "Você não tem permissão para iniciar chamadas de vídeo",
+        "disabled_no_perms_start_voice_call": "Você não tem permissão para iniciar chamadas de voz",
+        "disabled_ongoing_call": "Chamada em andamento",
+        "element_call": "Element",
         "enable_camera": "Ligar câmera",
         "enable_microphone": "Habilitar microfone",
         "expand": "Retornar para a chamada",
+        "get_call_link": "Compartilhar link de chamada",
         "hangup": "Desligar",
         "hide_sidebar_button": "Esconder a barra lateral",
-        "join_button_tooltip_connecting": "Conectando",
+        "input_devices": "Dispositivos de entrada",
+        "jitsi_call": "Jitsi",
+        "join_button_tooltip_call_full": "Desculpe, esta chamada está lotada no momento",
+        "legacy_call": "Legacy",
+        "maximise": "Preencher tela",
+        "maximise_call": "Maximizar chamada",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Conferências"
+        },
+        "minimise_call": "Minimizar chamada",
         "misconfigured_server": "A chamada falhou por conta de má configuração no servidor",
         "misconfigured_server_description": "Por favor, peça ao administrador do seu servidor (<code>%(homeserverDomain)s</code>) para configurar um servidor TURN, de modo que as chamadas funcionem de maneira estável.",
+        "misconfigured_server_fallback": "Como alternativa, você pode tentar usar o servidor público em<server/>, mas isso não será tão confiável e ele compartilhará seu endereço IP com esse servidor. Você também pode gerenciar isso em Configurações.",
+        "misconfigured_server_fallback_accept": "Tente usar %(server)s",
         "more_button": "Mais",
         "msisdn_lookup_failed": "Não foi possível procurar o número de telefone",
         "msisdn_lookup_failed_description": "Ocorreu um erro ao procurar o número de telefone",
@@ -2556,15 +3973,18 @@
         "no_permission_conference": "Permissão necessária",
         "no_permission_conference_description": "Você não tem permissão para iniciar uma chamada em grupo nesta sala",
         "on_hold": "%(name)s em espera",
+        "output_devices": "Dispositivos de saída",
         "screenshare_monitor": "Compartilhe a tela inteira",
         "screenshare_title": "Compatilhe conteúdo",
         "screenshare_window": "Janela da aplicação",
         "show_sidebar_button": "Exibir a barra lateral",
         "silence": "Silenciar chamado",
+        "silenced": "Notificações silenciadas",
         "start_screenshare": "Começar a compartilhar sua tela",
         "stop_screenshare": "Parar de compartilhar sua tela",
         "too_many_calls": "Muitas chamadas",
         "too_many_calls_description": "Você atingiu o número máximo de chamadas simultâneas.",
+        "transfer_consult_first_label": "Consulte primeiro",
         "transfer_failed": "A Transferência Falhou",
         "transfer_failed_description": "Houve uma falha ao transferir a chamada",
         "unable_to_access_audio_input_description": "Não foi possível acessar seu microfone. Por favor, confira as configurações do seu navegador e tente novamente.",
@@ -2581,6 +4001,7 @@
         "user_is_presenting": "%(sharerName)s está apresentando",
         "video_call": "Chamada de vídeo",
         "video_call_started": "Videochamada iniciada",
+        "video_call_using": "Chamada de vídeo usando:",
         "voice_call": "Chamada de voz",
         "you_are_presenting": "Você está apresentando"
     },
@@ -2667,7 +4088,8 @@
             "move_right": "Mover para a direita",
             "remove": "Remover para todos",
             "revoke": "Revogar permissões",
-            "screenshot": "Tirar uma foto"
+            "screenshot": "Tirar uma foto",
+            "start_audio_stream": "Iniciar transmissão de áudio"
         },
         "cookie_warning": "Este widget pode usar cookies.",
         "error_hangup_description": "Você foi desconectado da chamada. (Erro: %(message)s)",
@@ -2677,7 +4099,9 @@
         "error_need_invite_permission": "Para fazer isso, precisa ter permissão para convidar outras pessoas.",
         "error_need_kick_permission": "Você precisa ter permissão de expulsar usuários para fazer isso.",
         "error_need_to_be_logged_in": "Você precisa estar logado.",
-        "modal_data_warning": "Dados nessa tela são compartilhados com %(widgetDomain)s",
+        "error_unable_start_audio_stream_description": "Não foi possível iniciar a transmissão de áudio.",
+        "error_unable_start_audio_stream_title": "Falha ao iniciar a transmissão ao vivo",
+        "modal_data_warning": "Os dados abaixo são compartilhados com %(widgetDomain)s",
         "modal_title_default": "Popup do widget",
         "no_name": "App desconhecido",
         "open_id_permissions_dialog": {
@@ -2686,7 +4110,10 @@
             "title": "Permitir que este widget verifique a sua identidade"
         },
         "popout": "Widget Popout",
-        "set_room_layout": "Definir a minha aparência da sala para todos",
+        "set_room_layout": "Definir layout para todos",
+        "shared_data_avatar": "URL da sua foto de perfil",
+        "shared_data_device_id": "ID do seu dispositivo",
+        "shared_data_lang": "Seu idioma",
         "shared_data_mxid": "Sua ID de usuário",
         "shared_data_name": "Seu nome e sobrenome",
         "shared_data_room_id": "ID da sala",
@@ -2696,6 +4123,7 @@
         "shared_data_warning_im": "Se você usar esse widget, os dados poderão ser compartilhados <helpIcon /> com %(widgetDomain)s & seu gerenciador de integrações.",
         "shared_data_widget_id": "ID do widget",
         "unencrypted_warning": "Widgets não usam criptografia de mensagens.",
+        "unmaximise": "Desmaximizar",
         "unpin_to_view_right_panel": "Solte este widget para visualizá-lo neste painel"
     },
     "zxcvbn": {
@@ -2708,6 +4136,7 @@
             "l33t": "Substituições previsíveis como '@' em vez de 'a' não ajudam muito",
             "longerKeyboardPattern": "Use um padrão de teclas em diferentes direções e sentido",
             "noNeed": "Não há necessidade de símbolos, dígitos ou letras maiúsculas",
+            "pwned": "Se​•​você​•​usar​•​essa​•​senha​•​em​•​outro​•​lugar,​•​deverá​•​alterá-la.",
             "recentYears": "Evite anos recentes",
             "repeated": "Evite palavras e caracteres repetidos",
             "reverseWords": "Palavras invertidas não são muito mais difíceis de adivinhar",
@@ -2721,6 +4150,7 @@
             "extendedRepeat": "Repetições como \"abcabcabc\" são apenas um pouco mais difíceis de adivinhar que \"abc\"",
             "keyPattern": "Padrões de teclado curtos são fáceis de adivinhar",
             "namesByThemselves": "Nomes e sobrenomes por si só são fáceis de adivinhar",
+            "pwned": "Sua​•​senha​•​foi​•​exposta​•​por​•​uma​•​violação​•​de​•​dados​•​na​•​Internet.",
             "recentYears": "Os últimos anos são fáceis de adivinhar",
             "sequences": "Sequências como abc ou 6543 são fáceis de adivinhar",
             "similarToCommon": "Isto é similar a uma senha muito comum",
@@ -2728,6 +4158,7 @@
             "straightRow": "Linhas retas de teclas são fáceis de adivinhar",
             "topHundred": "Esta é uma das top-100 senhas mais comuns",
             "topTen": "Esta é uma das top-10 senhas mais comuns",
+            "userInputs": "Não deve haver nenhum dado pessoal ou relacionado à página.",
             "wordByItself": "Uma palavra por si só é fácil de adivinhar"
         }
     }
diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json
index d58f87c0045b54fc0ce0b5bfa94363bace7f9dca..108d2e0cd8e111de2df387f271cf942e017e41a7 100644
--- a/src/i18n/strings/ru.json
+++ b/src/i18n/strings/ru.json
@@ -1,6 +1,8 @@
 {
     "a11y": {
+        "emoji_picker": "Выбор эмодзи",
         "jump_first_invite": "Перейти к первому приглашению.",
+        "message_composer": "Автор сообщения",
         "n_unread_messages": {
             "other": "%(count)s непрочитанных сообщения(-й).",
             "one": "1 непрочитанное сообщение."
@@ -10,6 +12,8 @@
             "one": "1 непрочитанное упоминание."
         },
         "room_name": "Комната %(name)s",
+        "room_status_bar": "Строка состояния комнаты",
+        "seek_bar_label": "Панель поиска аудио",
         "unread_messages": "Непрочитанные сообщения.",
         "user_menu": "Меню пользователя"
     },
@@ -59,6 +63,7 @@
         "go": "Вперёд",
         "go_back": "Назад",
         "got_it": "Понятно",
+        "hide": "Скрыть",
         "hide_advanced": "Скрыть дополнительные настройки",
         "hold": "Удерживать",
         "ignore": "Игнорировать",
@@ -75,12 +80,14 @@
         "maximise": "Развернуть",
         "mention": "Упомянуть",
         "minimise": "Свернуть",
+        "new_message": "Новое сообщение",
         "new_room": "Новая комната",
         "new_video_room": "Новая видеокомната",
         "next": "Далее",
         "no": "Нет",
         "ok": "ОК",
         "open": "Открыть",
+        "open_menu": "Открыть меню",
         "pause": "Пауза",
         "pin": "Закрепить",
         "play": "Воспроизведение",
@@ -89,7 +96,6 @@
         "react": "Отреагировать",
         "refresh": "Обновить",
         "register": "Зарегистрироваться",
-        "reject": "Отклонить",
         "reload": "Обновить",
         "remove": "Удалить",
         "rename": "Переименовать",
@@ -105,6 +111,7 @@
         "save": "Сохранить",
         "search": "Поиск",
         "send_report": "Отослать отчёт",
+        "set_avatar": "Установить изображение профиля",
         "share": "Поделиться",
         "show": "Показать",
         "show_advanced": "Показать дополнительные настройки",
@@ -114,7 +121,7 @@
         "skip": "Пропустить",
         "start": "Начать",
         "start_chat": "Отправить личное сообщение",
-        "start_new_chat": "Начать ЛС",
+        "start_new_chat": "Начать новый чат",
         "stop": "Стоп",
         "submit": "Отправить",
         "subscribe": "Подписаться",
@@ -128,6 +135,7 @@
         "update": "Обновить",
         "upgrade": "Обновление",
         "upload": "Загрузить",
+        "upload_file": "Загрузить файл",
         "verify": "Заверить",
         "view": "Просмотр",
         "view_all": "Посмотреть все",
@@ -185,7 +193,7 @@
         "continue_with_sso": "Продолжить с %(ssoButtons)s",
         "country_dropdown": "Выпадающий список стран",
         "create_account_prompt": "Впервые здесь? <a>Создать учётную запись</a>",
-        "create_account_title": "Создать учётную запись",
+        "create_account_title": "Создать учетную запись",
         "email_discovery_text": "Если вы хотите, чтобы другие пользователи могли вас найти, укажите свой адрес электронной почты.",
         "email_field_label": "Электронная почта",
         "email_field_label_invalid": "Не похоже на действительный адрес электронной почты",
@@ -222,6 +230,7 @@
         },
         "misconfigured_body": "Попросите администратора %(brand)s проверить <a>конфигурационный файл</a> на наличие неправильных или повторяющихся записей.",
         "misconfigured_title": "Ваш %(brand)s неправильно настроен",
+        "mobile_create_account_title": "Вы собираетесь создать учетную запись на %(hsName)s",
         "msisdn_field_description": "Другие пользователи могут приглашать вас в комнаты, используя ваши контактные данные",
         "msisdn_field_label": "Телефон",
         "msisdn_field_number_invalid": "Этот номер телефона неправильный, проверьте его и повторите попытку",
@@ -239,11 +248,39 @@
         "phone_label": "Телефон",
         "phone_optional_label": "Телефон (не обязательно)",
         "qr_code_login": {
+            "check_code_explainer": "Это позволит убедиться в безопасности соединения с другим вашим устройством.",
+            "check_code_heading": "Введите номер, отображаемый на другом устройстве",
+            "check_code_input_label": "2-значный код",
+            "check_code_mismatch": "Цифры не совпадают",
             "completing_setup": "Завершение настройки нового устройства",
-            "error_unexpected": "Произошла неожиданная ошибка.",
-            "scan_code_instruction": "Отсканируйте приведенный ниже QR-код на устройстве, которое вышло из системы.",
-            "scan_qr_code": "Сканировать QR-код",
-            "select_qr_code": "Выберите '%(scanQRCode)s'",
+            "error_etag_missing": "Произошла непредвиденная ошибка. Это может быть связано с расширением браузера, прокси-сервером или неправильной настройкой сервера.",
+            "error_expired": "Срок действия входа истек. Пожалуйста, попробуйте еще раз.",
+            "error_expired_title": "Вход в систему не был завершён вовремя",
+            "error_insecure_channel_detected": "Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них.",
+            "error_insecure_channel_detected_instructions_1": "Попробуйте снова войти на другое устройство с помощью QR-кода, если возникла проблема с сетью",
+            "error_insecure_channel_detected_instructions_2": "Если вы столкнулись с аналогичной проблемой, попробуйте переподключиться к сети Wi-Fi или используйте мобильный интернет",
+            "error_insecure_channel_detected_instructions_3": "Если это не помогло, войдите вручную.",
+            "error_insecure_channel_detected_title": "Соединение не защищено",
+            "error_other_device_already_signed_in": "Больше ничего не нужно делать.",
+            "error_other_device_already_signed_in_title": "Вход уже выполнен на другом устройстве",
+            "error_rate_limited": "Слишком много попыток входа за короткое время. Подождите некоторое время, прежде чем повторить попытку.",
+            "error_unexpected": "Произошла непредвиденная ошибка. Запрос на подключение другого вашего устройства был отменен.",
+            "error_unsupported_protocol": "Это устройство не поддерживает вход на другое устройство с помощью QR-кода.",
+            "error_unsupported_protocol_title": "Другое устройство несовместимо",
+            "error_user_cancelled": "Вход был отменен на другом устройстве.",
+            "error_user_cancelled_title": "Запрос на вход отменен",
+            "error_user_declined": "Вы отклонили запрос на вход с другого устройства.",
+            "error_user_declined_title": "Вход отклонен",
+            "follow_remaining_instructions": "Следуйте дальнейшим инструкциям",
+            "open_element_other_device": "Откройте %(brand)s на другом устройстве",
+            "point_the_camera": "Отсканируйте QR-код на экране",
+            "scan_code_instruction": "Отсканируйте QR-код с помощью другого устройства",
+            "scan_qr_code": "Войти с помощью QR-кода",
+            "security_code": "Код безопасности",
+            "security_code_prompt": "Если появится запрос, введите код ниже на другом устройстве.",
+            "select_qr_code": "Выберите \"%(scanQRCode)s\"",
+            "unsupported_explainer": "Поставщик учетной записи не поддерживает вход на новое устройство с помощью QR-кода.",
+            "unsupported_heading": "QR-код не поддерживается",
             "waiting_for_device": "Ожидание входа устройства в систему"
         },
         "register_action": "Создать учётную запись",
@@ -332,6 +369,8 @@
             "email_resend_prompt": "Не получили? <a>Отправить его повторно</a>",
             "email_resent": "Отправлено повторно!",
             "fallback_button": "Начать аутентификацию",
+            "mas_cross_signing_reset_cta": "Перейти к учетной записи",
+            "mas_cross_signing_reset_description": "Сбросьте свои данные через поставщика учетной записи, а затем вернитесь и нажмите «Повторить».",
             "msisdn": "Текстовое сообщение отправлено на %(msisdn)s",
             "msisdn_token_incorrect": "Неверный код проверки",
             "msisdn_token_prompt": "Введите полученный код:",
@@ -366,7 +405,15 @@
         "download_logs": "Скачать журналы",
         "downloading_logs": "Скачивание журналов",
         "error_empty": "Пожалуйста, расскажите нам что пошло не так, либо, ещё лучше, создайте отчёт в GitHub с описанием проблемы.",
-        "failed_send_logs": "Не удалось отправить журналы: ",
+        "failed_download_logs": "Не удалось загрузить журналы отладки: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Ваш отчет об ошибке отклонен. Сервер rageshake не поддерживает это приложение.",
+            "rejected_generic": "Ваш отчет об ошибке отклонен. Сервер rageshake отклонил содержимое отчета из-за политики.",
+            "rejected_recovery_key": "Ваш отчет об ошибке был отклонен по соображениям безопасности, так как в нем содержится ключ восстановления.",
+            "rejected_version": "Ваш отчет об ошибке был отклонен, так как используемая вами версия слишком устарела.",
+            "server_unknown_error": "Сервер rageshake столкнулся с неизвестной ошибкой и не смог обработать сообщение об ошибке.",
+            "unknown_error": "Не удалось отправить журналы отладки."
+        },
         "github_issue": "GitHub вопрос",
         "introduction": "Если вы отправили ошибку через GitHub, журналы отладки могут помочь нам отследить проблему. ",
         "log_request": "Чтобы помочь нам предотвратить это в будущем, пожалуйста, <a>отправьте нам логи</a>.",
@@ -406,7 +453,7 @@
         "access_token": "Токен доступа",
         "accessibility": "Доступность",
         "advanced": "Подробности",
-        "all_rooms": "Все комнаты",
+        "all_chats": "Все чаты",
         "analytics": "Аналитика",
         "and_n_others": {
             "other": "и %(count)s других...",
@@ -421,10 +468,10 @@
         "beta": "Бета",
         "camera": "Камера",
         "cameras": "Камеры",
+        "cancel": "Отменить",
         "capabilities": "Возможности",
         "copied": "Скопировано!",
         "credits": "Благодарности",
-        "cross_signing": "Кросс-подпись",
         "dark": "Темная",
         "description": "Описание",
         "deselect_all": "Отменить выбор",
@@ -462,11 +509,12 @@
         "message_layout": "Макет сообщения",
         "microphone": "Микрофон",
         "model": "Модель",
-        "modern": "Новый",
+        "modern": "Современный",
         "mute": "Приглушить",
         "n_members": {
             "one": "%(count)s участник",
-            "other": "%(count)s участников"
+            "few": "%(count)s участника",
+            "many": "%(count)s участников"
         },
         "n_rooms": {
             "one": "%(count)s комната",
@@ -497,13 +545,15 @@
         "qr_code": "QR-код",
         "random": "Случайный",
         "reactions": "Реакции",
+        "recommended": "Рекомендуемое",
         "report_a_bug": "Сообщить об ошибке",
         "room": "Комната",
         "room_name": "Название комнаты",
         "rooms": "Комнаты",
+        "save": "Сохранить",
+        "saved": "Сохранено",
         "saving": "Сохранение…",
         "secure_backup": "Безопасное резервное копирование",
-        "security": "Безопасность",
         "select_all": "Выбрать все",
         "server": "Сервер",
         "settings": "Настройки",
@@ -512,8 +562,8 @@
         "someone": "Кто-то",
         "space": "Подпространство",
         "spaces": "Пространства",
-        "sticker": "Наклейка",
-        "stickerpack": "Наклейки",
+        "sticker": "Стикер",
+        "stickerpack": "Набор стикеров",
         "success": "Успех",
         "suggestions": "Предложения",
         "support": "Поддержка",
@@ -522,16 +572,16 @@
         "thread": "Обсуждение",
         "threads": "Обсуждения",
         "timeline": "Лента сообщений",
-        "trusted": "Заверенный",
         "unavailable": "недоступен",
         "unencrypted": "Не зашифровано",
         "unmute": "Вернуть право речи",
         "unnamed_room": "Комната без названия",
         "unnamed_space": "Безымянное пространство",
         "unverified": "Не заверено",
+        "updating": "Обновление…",
         "user": "Пользователь",
         "user_avatar": "Аватар",
-        "username": "Псевдоним",
+        "username": "Имя пользователя",
         "verification_cancelled": "Подтверждение отменено",
         "verified": "Заверено",
         "version": "Версия",
@@ -553,7 +603,7 @@
             "user_a11y": "Автозаполнение пользователя",
             "user_description": "Пользователи"
         },
-        "close_sticker_picker": "Скрыть наклейки",
+        "close_sticker_picker": "Скрыть стикеры",
         "edit_composer_label": "Редактировать сообщение",
         "format_bold": "Жирный",
         "format_code_block": "Блок кода",
@@ -644,7 +694,7 @@
         "invite_teammates_by_username": "Пригласить по имени пользователя",
         "invite_teammates_description": "Убедитесь, что правильные люди имеют доступ. Вы можете пригласить больше людей позже.",
         "invite_teammates_heading": "Пригласите своих товарищей по команде",
-        "inviting_users": "Приглашение...",
+        "inviting_users": "Приглашение…",
         "label": "Создать пространство",
         "name_required": "Пожалуйста, введите название пространства",
         "personal_space": "Только я",
@@ -688,6 +738,30 @@
         "category_room": "Комната",
         "caution_colon": "Предупреждение:",
         "client_versions": "Версия клиента",
+        "crypto": {
+            "4s_public_key_in_account_data": "в данных учётной записи",
+            "4s_public_key_not_in_account_data": "не найдено",
+            "backup_key_cached_status": "Кэшированный резервный ключ:",
+            "backup_key_stored_status": "Сохраненный резервный ключ:",
+            "backup_key_well_formed": "корректный",
+            "cross_signing": "Кросс-подпись",
+            "cross_signing_cached": "сохранено локально",
+            "cross_signing_not_ready": "Кросс-подпись не настроена.",
+            "cross_signing_private_keys_in_storage_status": "Приватные ключи для кросс-подписи:",
+            "cross_signing_private_keys_not_in_storage": "не найдено в хранилище",
+            "cross_signing_public_keys_on_device_status": "Публичные ключи для кросс-подписи:",
+            "cross_signing_ready": "Кросс-подпись готова к использованию.",
+            "cross_signing_status": "Статус кросс-подписи:",
+            "cross_signing_untrusted": "У вашей учётной записи есть кросс-подпись в секретное хранилище, но она пока не является доверенной в этом сеансе.",
+            "crypto_not_available": "Криптографический модуль недоступен",
+            "key_backup_active_version": "Активная резервная версия:",
+            "key_backup_active_version_none": "Нет",
+            "key_backup_inactive_warning": "Резервное копирование ваших ключей из этого сеанса не выполняется.",
+            "key_storage": "Хранилище ключей",
+            "master_private_key_cached_status": "Приватный мастер-ключ:",
+            "secret_storage_ready": "готово",
+            "title": "Сквозное шифрование"
+        },
         "developer_mode": "Режим разработчика",
         "developer_tools": "Инструменты разработчика",
         "edit_setting": "Изменить настройки",
@@ -797,52 +871,35 @@
     "empty_room_was_name": "Пустая комната (без %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Введите свою секретную фразу или <button> используйте секретный ключ </button> для продолжения.",
             "key_validation_text": {
-                "invalid_security_key": "Неверный ключ безопасности",
-                "recovery_key_is_correct": "Выглядит неплохо!",
-                "wrong_file_type": "Неправильный тип файла",
-                "wrong_security_key": "Неправильный ключ безопасности"
-            },
-            "reset_title": "Сбросить всё",
-            "reset_warning_1": "Делайте это только в том случае, если у вас нет другого устройства для завершения проверки.",
-            "reset_warning_2": "Если вы сбросите все настройки, вы перезагрузитесь без доверенных сеансов, без доверенных пользователей, и скорее всего не сможете просматривать прошлые сообщения.",
+                "wrong_security_key": "Неправильный ключ восстановления"
+            },
             "restoring": "Восстановление ключей из резервной копии",
-            "security_key_title": "Бумажный ключ",
-            "security_phrase_incorrect_error": "Невозможно получить доступ к секретному хранилищу. Убедитесь, что вы ввели правильную секретную фразу.",
-            "security_phrase_title": "Мнемоническая фраза",
-            "separator": "%(securityKey)s или %(recoveryFile)s",
-            "use_security_key_prompt": "Чтобы продолжить, используйте свой бумажный ключ."
+            "security_key_title": "Ключ восстановления"
         },
         "bootstrap_title": "Настройка ключей",
         "cancel_entering_passphrase_description": "Вы уверены, что хотите отменить ввод кодовой фразы?",
         "cancel_entering_passphrase_title": "Отменить ввод кодовой фразы?",
         "confirm_encryption_setup_body": "Нажмите кнопку ниже, чтобы подтвердить настройку шифрования.",
         "confirm_encryption_setup_title": "Подтвердите настройку шифрования",
-        "cross_signing_not_ready": "Кросс-подпись не настроена.",
-        "cross_signing_ready": "Кросс-подпись готова к использованию.",
-        "cross_signing_ready_no_backup": "Кросс-подпись готова, но ключи не резервируются.",
         "cross_signing_room_normal": "Эта комната зашифрована сквозным шифрованием",
         "cross_signing_room_verified": "Все в этой комнате подтверждены",
         "cross_signing_room_warning": "Кто-то использует неизвестный сеанс",
-        "cross_signing_unsupported": "Ваш домашний сервер не поддерживает кросс-подписи.",
-        "cross_signing_untrusted": "У вашей учётной записи есть кросс-подпись в секретное хранилище, но она пока не является доверенной в этом сеансе.",
         "cross_signing_user_normal": "Вы не подтвердили этого пользователя.",
         "cross_signing_user_verified": "Вы подтвердили этого пользователя. Пользователь подтвердил все свои сеансы.",
         "cross_signing_user_warning": "Этот пользователь не подтвердил все свои сеансы.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Очистить ключи кросс-подписи",
-            "title": "Уничтожить ключи кросс-подписи?",
-            "warning": "Удаление ключей кросс-подписи является мгновенным и необратимым действием. Любой, с кем вы прошли проверку, увидит предупреждения безопасности. Вы почти наверняка не захотите этого делать, если только не потеряете все устройства, с которых можно совершать кросс-подпись."
-        },
+        "enter_recovery_key": "Введите ключ восстановления",
         "event_shield_reason_authenticity_not_guaranteed": "Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве.",
         "event_shield_reason_mismatched_sender_key": "Зашифровано неподтверждённым сеансом",
         "event_shield_reason_unknown_device": "Зашифровано неизвестным или удаленным устройством.",
         "event_shield_reason_unsigned_device": "Зашифровано устройством, не проверенным владельцем.",
         "event_shield_reason_unverified_identity": "Зашифровано неподтвержденным пользователем.",
         "export_unsupported": "Ваш браузер не поддерживает необходимые криптографические расширения",
+        "forgot_recovery_key": "Забыли ключ восстановления?",
         "import_invalid_keyfile": "Недействительный файл ключей %(brand)s",
         "import_invalid_passphrase": "Ошибка аутентификации: возможно, неправильный пароль?",
+        "key_storage_out_of_sync": "Хранилище ключей не синхронизировано.",
+        "key_storage_out_of_sync_description": "Подтвердите ключ восстановления, чтобы сохранить доступ к хранилищу ключей и истории сообщений.",
         "messages_not_secure": {
             "cause_1": "Ваш домашний сервер",
             "cause_2": "Домашний сервер пользователя, которого вы подтверждаете",
@@ -857,7 +914,6 @@
             "title": "Новый метод восстановления",
             "warning": "Если вы не задали новый способ восстановления, злоумышленник может получить доступ к вашей учётной записи. Смените пароль учётной записи и сразу же задайте новый способ восстановления в настройках."
         },
-        "not_supported": "<не поддерживается>",
         "recovery_method_removed": {
             "description_1": "Этот сеанс обнаружил, что ваши секретная фраза и ключ безопасности для защищенных сообщений были удалены.",
             "description_2": "Если вы сделали это по ошибке, вы можете настроить защищённые сообщения в этом сеансе, что снова зашифрует историю сообщений в этом сеансе с помощью нового метода восстановления.",
@@ -865,11 +921,12 @@
             "warning": "Если вы не убрали метод восстановления, злоумышленник может получить доступ к вашей учётной записи. Смените пароль учётной записи и сразу задайте новый способ восстановления в настройках."
         },
         "reset_all_button": "Забыли или потеряли все варианты восстановления? <a>Сбросить всё</a>",
+        "set_up_recovery": "Настроить восстановление",
+        "set_up_recovery_later": "Не сейчас",
         "set_up_toast_description": "Защита от потери доступа к зашифрованным сообщениям и данным",
         "set_up_toast_title": "Настроить безопасное резервное копирование",
         "setup_secure_backup": {
-            "explainer": "Перед выходом сохраните резервную копию ключей шифрования, чтобы не потерять их.",
-            "title": "Настроить"
+            "explainer": "Перед выходом сохраните резервную копию ключей шифрования, чтобы не потерять их."
         },
         "udd": {
             "interactive_verification_button": "Интерактивная сверка по смайлам",
@@ -880,12 +937,10 @@
             "title": "Недоверенное"
         },
         "unable_to_setup_keys_error": "Невозможно настроить ключи",
-        "unsupported": "Этот клиент не поддерживает сквозное шифрование.",
         "verification": {
             "accepting": "Принятие…",
             "after_new_login": {
                 "device_verified": "Сеанс заверен",
-                "reset_confirmation": "Действительно сбросить ключи подтверждения?",
                 "skip_verification": "Пока пропустить проверку",
                 "unable_to_verify": "Невозможно заверить этот сеанс",
                 "verify_this_device": "Заверьте этот сеанс"
@@ -917,9 +972,10 @@
             "qr_or_sas": "%(qrCode)s или %(emojiCompare)s",
             "qr_or_sas_header": "Заверьте этот сеанс, выполнив одно из следующих действий:",
             "qr_prompt": "Отсканируйте этот уникальный код",
-            "qr_reciprocate_same_shield_device": "Почти готово! Ваше другое устройство показывает такой же щит?",
+            "qr_reciprocate_same_shield_device": "Почти готово! На другом устройстве отображается такой же щит?",
             "qr_reciprocate_same_shield_user": "Почти готово! Отображает ли %(displayName)s такой же щит?",
             "request_toast_accept": "Проверка сеанса",
+            "request_toast_accept_user": "Подтвердить пользователя",
             "request_toast_decline_counter": "Игнорировать (%(counter)s)",
             "request_toast_detail": "%(deviceId)s с %(ip)s",
             "reset_proceed_prompt": "Выполнить сброс",
@@ -955,8 +1011,6 @@
             "verify_emoji_prompt": "Подтверждение сравнением уникальных смайлов.",
             "verify_emoji_prompt_qr": "Если вы не можете отсканировать код выше, попробуйте сравнить уникальные смайлы.",
             "verify_later": "Я заверю позже",
-            "verify_reset_warning_1": "Сброс ключей проверки нельзя отменить. После сброса вы не сможете получить доступ к старым зашифрованным сообщениям, а друзья, которые ранее проверили вас, будут видеть предупреждения о безопасности, пока вы не пройдете повторную проверку.",
-            "verify_reset_warning_2": "Продолжайте, только если вы уверены, что потеряли все остальные устройства и ключ безопасности.",
             "verify_using_device": "Сверить с другим сеансом",
             "verify_using_key": "Заверить бумажным ключом",
             "verify_using_key_or_phrase": "Проверка с помощью ключа безопасности или фразы",
@@ -1017,11 +1071,7 @@
             "title": "Не удалось скопировать ссылку на комнату"
         },
         "error_loading_user_profile": "Не удалось загрузить профиль пользователя",
-        "forget_room_failed": "Не удалось забыть комнату: %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Сервер может быть недоступен, перегружен или поиск прекращен по тайм-ауту :(",
-            "title": "Поиск не удался"
-        }
+        "forget_room_failed": "Не удалось забыть комнату: %(errCode)s"
     },
     "error_user_not_logged_in": "Пользователь не вошел в систему",
     "event_preview": {
@@ -1046,7 +1096,10 @@
             "you": "Вы отреагировали %(reaction)s на %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "poll": "Опрос"
+        }
     },
     "export_chat": {
         "cancelled": "Экспорт отменён",
@@ -1169,7 +1222,15 @@
         "other": "В %(spaceName)s и %(count)s других пространствах."
     },
     "incompatible_browser": {
-        "title": "Неподдерживаемый браузер"
+        "detail_no_continue": "Попробуйте обновить этот браузер, если вы используете не последнюю версию, и повторите попытку.",
+        "learn_more": "Подробнее",
+        "linux": "Linux",
+        "macos": "Mac",
+        "title": "Неподдерживаемый браузер",
+        "use_desktop_heading": "Вместо этого используйте %(brand)s Desktop",
+        "use_mobile_heading_after_desktop": "Или воспользуйтесь нашим мобильным приложением",
+        "windows_64bit": "Windows (64-бит)",
+        "windows_arm_64bit": "Windows (ARM 64-бит)"
     },
     "info_tooltip_title": "Информация",
     "integration_manager": {
@@ -1178,6 +1239,7 @@
         "error_connecting_heading": "Не удалось подключиться к менеджеру интеграций",
         "explainer": "Менеджеры по интеграции получают данные конфигурации и могут изменять виджеты, отправлять приглашения в комнаты и устанавливать уровни доступа от вашего имени.",
         "manage_title": "Управление интеграциями",
+        "toggle_label": "Включить менеджер интеграции",
         "use_im": "Используйте менеджер интеграций для управления ботами, виджетами и наклейками.",
         "use_im_default": "Используйте менеджер интеграций <b>%(serverName)s</b> для управления ботами, виджетами и наклейками."
     },
@@ -1357,6 +1419,7 @@
         "group_rooms": "Комнаты",
         "group_spaces": "Пространства",
         "group_themes": "Темы",
+        "group_ui": "Пользовательский интерфейс",
         "group_voip": "Голос и видео",
         "group_widgets": "Виджеты",
         "hidebold": "Скрыть точку уведомления (отображать только значки счетчиков)",
@@ -1372,6 +1435,7 @@
         "location_share_live_description": "Временная реализация. Местоположения сохраняются в истории комнаты.",
         "mjolnir": "Новые способы игнорировать людей",
         "msc3531_hide_messages_pending_moderation": "Позволяет модераторам скрывать сообщения, ожидающие модерации.",
+        "new_room_list": "Включить новый список комнат",
         "notification_settings": "Новые настройки уведомлений",
         "notification_settings_beta_caption": "Представляем вам более простой способ изменения настроек уведомлений. Настройте свои настройки так %(brand)s, как вам удобно.",
         "notification_settings_beta_title": "Настройки уведомлений",
@@ -1492,6 +1556,11 @@
         "toggle_attribution": "Переключить атрибуцию"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s Участник",
+            "few": "%(count)s Участника",
+            "many": "%(count)s Участников"
+        },
         "filter_placeholder": "Поиск по участникам",
         "invite_button_no_perms_tooltip": "У вас нет разрешения приглашать пользователей",
         "power_label": "%(userName)s (уровень прав %(powerLevelNumber)s)"
@@ -1522,6 +1591,10 @@
         "error_change_title": "Изменить настройки уведомлений",
         "keyword": "Ключевое слово",
         "keyword_new": "Новое ключевое слово",
+        "level_activity": "Активность",
+        "level_none": "Пусто",
+        "level_notification": "Уведомление",
+        "level_unsent": "Не отправлено",
         "mark_all_read": "Отметить всё как прочитанное",
         "mentions_and_keywords": "@упоминания и ключевые слова",
         "mentions_and_keywords_description": "Получать уведомления только по упоминаниям и ключевым словам, установленным в ваших <a>настройках</a>",
@@ -1629,11 +1702,6 @@
         "ongoing": "Удаление…",
         "reason_label": "Причина (необязательно)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Уверены, что хотите отклонить приглашение?",
-        "failed": "Не удалось отклонить приглашение",
-        "title": "Отклонить приглашение"
-    },
     "report_content": {
         "description": "Отчет о данном сообщении отправит свой уникальный 'event ID' администратору вашего домашнего сервера. Если сообщения в этой комнате зашифрованы, администратор вашего домашнего сервера не сможет прочитать текст сообщения или просмотреть какие-либо файлы или изображения.",
         "disagree": "Не согласен",
@@ -1659,35 +1727,58 @@
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Не удалось расшифровать сеансы (%(failedCount)s)!",
         "count_of_successfully_restored_keys": "Успешно восстановлены ключи (%(sessionCount)s)",
-        "enter_key_description": "Получите доступ к своей истории защищенных сообщений и настройте безопасный обмен сообщениями, введя ключ безопасности.",
-        "enter_key_title": "Введите ключ безопасности",
+        "enter_key_description": "Получите доступ к защищенной истории сообщений и настройте безопасный обмен сообщениями, введя ключ восстановления.",
+        "enter_key_title": "Введите ключ восстановления",
         "enter_phrase_description": "Получите доступ к своей истории защищенных сообщений и настройте безопасный обмен сообщениями, введя секретную фразу.",
         "enter_phrase_title": "Введите мнемоническую фразу",
         "incorrect_security_phrase_dialog": "Не удалось расшифровать резервную копию с помощью этой секретной фразы: убедитесь, что вы ввели правильную секретную фразу.",
         "incorrect_security_phrase_title": "Неверная секретная фраза",
         "key_backup_warning": "<b>Предупреждение</b>: вам следует настроить резервное копирование ключей только с доверенного компьютера.",
-        "key_fetch_in_progress": "Получение ключей с сервера...",
-        "key_forgotten_text": "Если вы забыли свой ключ безопасности, вы можете <button>настроить новые параметры восстановления</button>",
-        "key_is_invalid": "Неправильный ключ безопасности",
-        "key_is_valid": "Похоже, это правильный ключ безопасности!",
+        "key_fetch_in_progress": "Получение ключей с сервера…",
+        "key_forgotten_text": "Если вы забыли ваш ключ восстановления, вы можете <button> настроить новые параметры восстановления </button>",
+        "key_is_invalid": "Недействительный ключ восстановления",
+        "key_is_valid": "Похоже, это действительный ключ восстановления!",
         "keys_restored_title": "Ключи восстановлены",
         "load_error_content": "Невозможно загрузить статус резервной копии",
         "load_keys_progress": "%(completed)s из %(total)s ключей восстановлено",
         "no_backup_error": "Резервных копий не найдено!",
-        "phrase_forgotten_text": "Если вы забыли секретную фразу, вы можете <button1>использовать ключ безопасности</button1> или <button2>настроить новые параметры восстановления</button2>",
-        "recovery_key_mismatch_description": "Не удалось расшифровать резервную копию с помощью этого ключа безопасности: убедитесь, что вы ввели правильный ключ безопасности.",
-        "recovery_key_mismatch_title": "Ключ безопасности не подходит",
+        "phrase_forgotten_text": "Если вы забыли секретную фразу, вы можете <button1> использовать ключ восстановления </button1> или <button2> настроить новые параметры восстановления. </button2>",
+        "recovery_key_mismatch_description": "Не удалось расшифровать резервную копию с помощью этого ключа восстановления. Убедитесь, что вы ввели правильный ключ восстановления.",
+        "recovery_key_mismatch_title": "Несоответствие ключа восстановления",
         "restore_failed_error": "Невозможно восстановить резервную копию"
     },
     "right_panel": {
-        "add_integrations": "Добавить виджеты, мосты и ботов",
+        "add_integrations": "Добавить расширения",
+        "add_topic": "Добавить тему",
+        "extensions_button": "Расширения",
+        "extensions_empty_description": "Нажмите “%(addIntegrations)s”, чтобы просмотреть и добавить расширения в эту комнату",
+        "extensions_empty_title": "Повысьте производительность с помощью дополнительных инструментов, виджетов и ботов",
         "files_button": "Файлы",
         "pinned_messages": {
+            "empty_description": "Выберите сообщение и нажмите «%(pinAction)s», чтобы включить его сюда.",
+            "empty_title": "Закрепите важные сообщения, чтобы их можно было легко найти",
+            "header": {
+                "one": "1 Закреплённое сообщение",
+                "few": "%(count)s Закреплённых сообщения",
+                "many": "%(count)s Закреплённых сообщений"
+            },
             "limits": {
                 "other": "Вы можете закрепить не более %(count)s виджетов"
-            }
+            },
+            "menu": "Открыть меню",
+            "release_announcement": {
+                "close": "Ок",
+                "description": "Все прикрепленные сообщения можно найти здесь. Наведите курсор на любое сообщение и нажмите «Закрепить», чтобы добавить его.",
+                "title": "Все новые закрепленные сообщения"
+            },
+            "unpin_all": {
+                "button": "Открепить все сообщения",
+                "content": "Убедитесь, что вы действительно хотите удалить все прикреплённые сообщения. Это действие нельзя отменить.",
+                "title": "Открепить все сообщения?"
+            },
+            "view": "Просмотр в хронологии"
         },
-        "pinned_messages_button": "Закреплено",
+        "pinned_messages_button": "Закрепленные сообщения",
         "poll": {
             "active_heading": "Активные опросы",
             "empty_active": "В этой комнате нет активных опросов",
@@ -1743,6 +1834,7 @@
             "forget": "Забыть комнату",
             "low_priority": "Маловажные",
             "mark_read": "Отметить как прочитанное",
+            "mark_unread": "Отметить как непрочитанное",
             "notifications_default": "Соответствует настройке по умолчанию",
             "notifications_mute": "Заглушить комнату",
             "title": "Настройки комнаты",
@@ -1792,6 +1884,8 @@
             },
             "room_is_public": "Это публичная комната"
         },
+        "header_avatar_open_settings_label": "Открыть настройки комнаты",
+        "header_face_pile_tooltip": "Люди",
         "header_untrusted_label": "Ненадёжный",
         "inaccessible": "Эта комната или пространство в данный момент недоступны.",
         "inaccessible_name": "%(roomName)s на данный момент недоступна.",
@@ -1815,7 +1909,6 @@
             "you_created": "Вы создали эту комнату."
         },
         "invite_email_mismatch_suggestion": "Введите адрес эл.почты в Настройках, чтобы получать приглашения прямо в %(brand)s.",
-        "invite_reject_ignore": "Отклонить и заигнорировать пользователя",
         "invite_sent_to_email": "Это приглашение было отправлено на %(email)s",
         "invite_sent_to_email_room": "Это приглашение в %(roomName)s было отправлено на %(email)s",
         "invite_subtitle": "<userName/> пригласил(а) вас",
@@ -1861,11 +1954,22 @@
         "not_found_title": "Такой комнаты или пространства не существует.",
         "not_found_title_name": "%(roomName)s не существует.",
         "peek_join_prompt": "Вы просматриваете %(roomName)s. Хотите присоединиться?",
+        "pinned_message_badge": "Закреплённое сообщение",
+        "pinned_message_banner": {
+            "button_close_list": "Закрыть список",
+            "button_view_all": "Посмотреть все"
+        },
         "read_topic": "Нажмите, чтобы увидеть тему",
         "rejecting": "Отклонение приглашения…",
         "rejoin_button": "Пере-присоединение",
         "search": {
             "all_rooms_button": "Поиск по всем комнатам",
+            "placeholder": "Поиск сообщений...",
+            "summary": {
+                "one": "Найден 1 результат по запросу «<query/>»",
+                "few": "%(count)s результата найдено по запросу “<query/>”",
+                "many": "%(count)s результатов найдено по запросу “<query/>”"
+            },
             "this_room_button": "Поиск в этой комнате"
         },
         "status_bar": {
@@ -1906,20 +2010,48 @@
         "add_space_label": "Добавить пространство",
         "breadcrumbs_empty": "Нет недавно посещенных комнат",
         "breadcrumbs_label": "Недавно посещённые комнаты",
+        "empty": {
+            "no_chats": "Пока нет доступных чатов",
+            "no_chats_description": "Начните с отправки сообщений или создания комнаты",
+            "no_chats_description_no_room_rights": "Начните переписку с отправки сообщения",
+            "no_favourites": "У вас пока нет чатов в Избранное",
+            "no_favourites_description": "Вы можете добавить в Избранное в настройках чата",
+            "no_people": "У вас пока нет личных чатов",
+            "no_rooms": "Вы еще не находитесь ни в одной комнате",
+            "no_unread": "Поздравляю! У вас нет непрочитанных сообщений",
+            "show_chats": "Показать все чаты"
+        },
         "failed_add_tag": "Не удалось добавить тег %(tagName)s в комнату",
         "failed_remove_tag": "Не удалось удалить тег %(tagName)s из комнаты",
         "failed_set_dm_tag": "Не удалось установить метку личного сообщения",
+        "filters": {
+            "favourite": "Избранное",
+            "people": "Люди",
+            "rooms": "Комнаты",
+            "unread": "Непрочитанное"
+        },
         "home_menu_label": "Параметры раздела \"Главная\"",
         "join_public_room_label": "Присоединиться к публичной комнате",
         "joining_rooms_status": {
             "one": "Сейчас вы состоите в %(count)s комнате",
             "other": "Сейчас вы состоите в %(count)s комнатах"
         },
+        "list_title": "Список комнат",
+        "more_options": {
+            "copy_link": "Скопировать ссылку на комнату",
+            "leave_room": "Покинуть комнату",
+            "low_priority": "Низкий приоритет",
+            "mark_read": "Отметить как прочитанное",
+            "mark_unread": "Отметить как непрочитанное"
+        },
         "notification_options": "Настройки уведомлений",
         "redacting_messages_status": {
             "one": "Удаляются сообщения в %(count)s комнате",
             "other": "Удаляются сообщения в %(count)s комнатах"
         },
+        "room": {
+            "more_options": "Дополнительные параметры"
+        },
         "show_less": "Показать меньше",
         "show_n_more": {
             "other": "Показать ещё %(count)s",
@@ -2133,12 +2265,14 @@
             "join_rule_restricted_dialog_heading_unknown": "Это, скорее всего, те, в которых участвуют другие администраторы комнат.",
             "join_rule_restricted_dialog_title": "Выберите места",
             "join_rule_restricted_n_more": {
-                "other": "и %(count)s ещё",
-                "one": "и %(count)s еще"
+                "one": "и еще %(count)s",
+                "few": "и еще %(count)s",
+                "many": "и еще %(count)s"
             },
             "join_rule_restricted_summary": {
-                "other": "В настоящее время %(count)s пространств имеют доступ",
-                "one": "В настоящее время пространство имеет доступ"
+                "one": "В настоящее время пространство имеет доступ",
+                "few": "В настоящее время %(count)s пространства имеют доступ",
+                "many": "В настоящее время %(count)s пространств имеют доступ"
             },
             "join_rule_restricted_upgrade_description": "Это обновление позволит участникам выбранных пространств получить доступ в эту комнату без приглашения.",
             "join_rule_restricted_upgrade_warning": "Эта комната находится в некоторых пространствах, администратором которых вы не являетесь. В этих пространствах старая комната будет по-прежнему отображаться, но людям будет предложено присоединиться к новой.",
@@ -2229,14 +2363,18 @@
             "brand_version": "Версия %(brand)s:",
             "clear_cache_reload": "Очистить кэш и перезагрузить",
             "crypto_version": "Криптоверсия:",
+            "dialog_title": "<strong>Настройки:</strong> Помощь и информация",
             "help_link": "Для получения помощи по использованию %(brand)s, нажмите <a>здесь</a>.",
-            "homeserver": "Homeserver это <code>%(homeserverUrl)s</code>",
-            "identity_server": "Сервер идентификации - это <code>%(identityServerUrl)s</code>",
+            "homeserver": "Домашний сервер: <code>%(homeserverUrl)s</code>",
+            "identity_server": "Сервер идентификации: <code>%(identityServerUrl)s</code>",
             "title": "Помощь и о программе",
             "versions": "Версии"
         }
     },
     "settings": {
+        "account": {
+            "title": "Учетная запись"
+        },
         "all_rooms_home": "Показывать все комнаты на Главной",
         "all_rooms_home_description": "Все комнаты, в которых вы находитесь, будут отображаться на Главной.",
         "always_show_message_timestamps": "Всегда показывать время отправки сообщений",
@@ -2245,9 +2383,13 @@
             "custom_font_description": "Установите имя шрифта, установленного в вашей системе, и %(brand)s попытается его использовать.",
             "custom_font_name": "Название системного шрифта",
             "custom_font_size": "Использовать другой размер",
+            "custom_theme_add": "Добавить пользовательскую тему",
+            "custom_theme_downloading": "Загрузка пользовательской темы…",
             "custom_theme_error_downloading": "Ошибка при загрузке информации темы.",
             "custom_theme_invalid": "Неверная схема темы.",
             "font_size": "Размер шрифта",
+            "font_size_default": "%(fontSize)s (по умолчанию)",
+            "high_contrast": "Высокая контрастность",
             "image_size_default": "По умолчанию",
             "image_size_large": "Большой",
             "layout_bubbles": "Пузыри сообщений",
@@ -2265,6 +2407,55 @@
         "emoji_autocomplete": "Предлагать смайлики при наборе",
         "enable_markdown": "Использовать Markdown",
         "enable_markdown_description": "Начинайте сообщения с <code>/plain</code>, чтобы отправлять их без markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Данные вашей учетной записи, контакты, настройки и список чатов будут сохранены",
+                "breadcrumb_page": "Сбросить шифрование",
+                "breadcrumb_second_description": "Вы потеряете историю сообщений, которая хранится только на сервере",
+                "breadcrumb_third_description": "Вам нужно будет заново подтвердить все существующие устройства и контакты.",
+                "breadcrumb_title": "Вы уверены, что хотите сбросить свою идентификацию?",
+                "breadcrumb_title_forgot": "Забыли ключ восстановления? Вам нужно будет восстановить свою идентификацию.",
+                "breadcrumb_warning": "Делайте это только в том случае, если вы считаете, что ваша учетная запись взломана.",
+                "details_title": "Сведения о шифровании",
+                "do_not_close_warning": "Не закрывайте это окно до тех пор, пока сброс не будет завершен",
+                "export_keys": "Экспортировать ключи",
+                "import_keys": "Импортировать ключи",
+                "other_people_device_description": "Внимание: пользователи, которые явно не подтвердили вашу личность (например, с помощью emoji), не получат ваши зашифрованные сообщения. Кроме того, неверифицированные устройства верифицированных пользователей не будут получать ваши зашифрованные сообщения.",
+                "other_people_device_label": "В зашифрованных комнатах отправляйте сообщения только проверенным пользователям",
+                "other_people_device_title": "Устройства других людей",
+                "reset_identity": "Сбросить криптографическую идентификацию",
+                "reset_in_progress": "Выполняется сброс...",
+                "session_id": "ID сеанса:",
+                "session_key": "Ключ сеанса:",
+                "title": "Дополнительно"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Удалить хранилище ключей",
+                "confirm": "Удалить хранилище ключей",
+                "description": "Удаление хранилища ключей приведёт к удалению вашей идентификации и ключей сообщений с сервера, а также отключению следующих функций безопасности:",
+                "list_first": "Нет зашифрованной истории сообщений на новых устройствах",
+                "title": "Вы уверены, что хотите отключить хранение ключей и удалить их?"
+            },
+            "device_not_verified_button": "Проверить это устройство",
+            "device_not_verified_title": "Устройство не проверено",
+            "dialog_title": "<strong>Настройки:</strong> Шифрование",
+            "key_storage": {
+                "allow_key_storage": "Разрешить хранение ключей",
+                "description": "Безопасно храните свою идентификацию и ключи сообщений на сервере. Это позволит вам просматривать историю сообщений на любых новых устройствах. <a>Узнайте больше</a>",
+                "title": "Хранилище ключей"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Подтвердите новый ключ восстановления",
+                "change_recovery_confirm_title": "Введите новый ключ восстановления",
+                "change_recovery_key": "Изменить ключ восстановления",
+                "change_recovery_key_title": "Изменить ключ восстановления?",
+                "enter_key_error": "Ключ восстановления, который вы ввел, неверный.",
+                "enter_recovery_key": "Введите ключ восстановления",
+                "forgot_recovery_key": "Забыли ключ восстановления?",
+                "save_key_description": "Не сообщайте эту информацию никому!",
+                "save_key_title": "Ключ восстановления"
+            }
+        },
         "general": {
             "account_management_section": "Управление учётной записью",
             "account_section": "Учётная запись",
@@ -2333,7 +2524,6 @@
             "remove_msisdn_prompt": "Удалить %(phone)s?",
             "spell_check_locale_placeholder": "Выберите регион"
         },
-        "image_thumbnails": "Предпросмотр/миниатюры для изображений",
         "inline_url_previews_default": "Предпросмотр ссылок по умолчанию",
         "inline_url_previews_room": "Включить предпросмотр ссылок для участников этой комнаты по умолчанию",
         "inline_url_previews_room_account": "Включить предпросмотр ссылок в этой комнате (влияет только на вас)",
@@ -2469,54 +2659,17 @@
         "prompt_invite": "Подтверждать отправку приглашений на потенциально недействительные matrix ID",
         "replace_plain_emoji": "Автоматически заменять текстовые смайлики на графические",
         "security": {
-            "4s_public_key_in_account_data": "в данных учётной записи",
-            "4s_public_key_status": "Публичный ключ секретного хранилища:",
             "analytics_description": "Делитесь анонимными данными, чтобы помочь нам выявить проблемы. Ничего личного. Никаких третьих лиц.",
-            "backup_key_cached_status": "Резервный ключ кэширован:",
-            "backup_key_stored_status": "Резервный ключ сохранён:",
-            "backup_key_unexpected_type": "непредвиденный тип",
-            "backup_key_well_formed": "корректный",
-            "backup_keys_description": "Сделайте резервную копию ключей шифрования с данными вашей учетной записи на случай, если вы потеряете доступ к своим сеансам. Ваши ключи будут защищены уникальным ключом безопасности.",
             "bulk_options_accept_all_invites": "Принять все приглашения (%(invitedRooms)s)",
             "bulk_options_reject_all_invites": "Отклонить все %(invitedRooms)s приглашения",
             "bulk_options_section": "Основные опции",
-            "cross_signing_cached": "сохранено локально",
-            "cross_signing_homeserver_support": "Поддержка со стороны домашнего сервера:",
-            "cross_signing_homeserver_support_exists": "существует",
-            "cross_signing_in_4s": "в секретном хранилище",
-            "cross_signing_in_memory": "в памяти",
-            "cross_signing_master_private_Key": "Приватный мастер-ключ:",
-            "cross_signing_not_cached": "не найдено локально",
-            "cross_signing_not_found": "не найдено",
-            "cross_signing_not_in_4s": "не найдено в хранилище",
-            "cross_signing_not_stored": "не сохранено",
-            "cross_signing_private_keys": "Приватные ключи для кросс-подписи:",
-            "cross_signing_public_keys": "Публичные ключи для кросс-подписи:",
-            "cross_signing_self_signing_private_key": "Самоподписанный приватный ключ:",
-            "cross_signing_user_signing_private_key": "Приватный ключ подписи пользователей:",
-            "cryptography_section": "Криптография",
-            "delete_backup": "Удалить резервную копию",
-            "delete_backup_confirm_description": "Вы уверены? Зашифрованные сообщения будут безвозвратно утеряны при отсутствии соответствующего резервного копирования ваших ключей.",
             "e2ee_default_disabled_warning": "Администратор вашего сервера отключил сквозное шифрование по умолчанию в приватных комнатах и диалогах.",
             "enable_message_search": "Включить поиск сообщений в зашифрованных комнатах",
             "encryption_section": "Шифрование",
-            "error_loading_key_backup_status": "Не удалось получить статус резервного копирования для ключей шифрования",
-            "export_megolm_keys": "Экспорт ключей шифрования",
             "ignore_users_empty": "У вас нет игнорируемых пользователей.",
             "ignore_users_section": "Игнорируемые пользователи",
-            "import_megolm_keys": "Импорт ключей шифрования",
-            "key_backup_active": "В этом сеансе выполняется резервное копирование ваших ключей.",
-            "key_backup_active_version": "Активная версия резервного копирования:",
-            "key_backup_active_version_none": "Нет",
             "key_backup_algorithm": "Алгоритм:",
-            "key_backup_can_be_restored": "Эту резервную копию можно восстановить в этом сеансе",
-            "key_backup_complete": "Все ключи сохранены",
             "key_backup_connect": "Подключить этот сеанс к резервированию ключей",
-            "key_backup_connect_prompt": "Подключите этот сеанс к резервированию ключей до выхода, чтобы избежать утраты доступных только в этом сеансе ключей.",
-            "key_backup_in_progress": "Резервное копирование %(sessionsRemaining)s ключей…",
-            "key_backup_inactive": "Это сеанс <b>не сохраняет ваши ключи</b>, но у вас есть резервная копия, из которой вы можете их восстановить.",
-            "key_backup_inactive_warning": "Ваши ключи <b>не резервируются с этом сеансе</b>.",
-            "key_backup_latest_version": "Последняя версия резервной копии на сервере:",
             "message_search_disable_warning": "Если этот параметр отключен, сообщения из зашифрованных комнат не будут отображаться в результатах поиска.",
             "message_search_disabled": "Безопасно кэшировать шифрованные сообщения локально, чтобы они появлялись в результатах поиска.",
             "message_search_enabled": {
@@ -2536,13 +2689,7 @@
             "message_search_unsupported": "Отсутствуют некоторые необходимые компоненты для %(brand)s, чтобы безопасно кэшировать шифрованные сообщения локально. Если вы хотите попробовать эту возможность, соберите самостоятельно %(brand)s Desktop с <nativeLink>добавлением поисковых компонентов</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s не может безопасно кэшировать зашифрованные сообщения локально во время работы в веб-браузере. Используйте <desktopLink>%(brand)s Desktop</desktopLink>, чтобы зашифрованные сообщения появились в результатах поиска.",
             "record_session_details": "Записывать название клиента, версию и URL-адрес для более лёгкого распознавания сеансов в менеджере сеансов",
-            "restore_key_backup": "Восстановить из резервной копии",
-            "secret_storage_not_ready": "не готов",
-            "secret_storage_ready": "готов",
-            "secret_storage_status": "Секретное хранилище:",
             "send_analytics": "Отправить данные аналитики",
-            "session_id": "ID сеанса:",
-            "session_key": "Ключ сеанса:",
             "strict_encryption": "Никогда не отправлять неподтверждённым сеансам зашифрованные сообщения через этот сеанс"
         },
         "send_read_receipts": "Уведомлять о прочтении",
@@ -2553,19 +2700,23 @@
             "browser": "Браузер",
             "confirm_sign_out": {
                 "one": "Подтвердите выход из этого устройства",
-                "other": "Подтвердите выход из этих устройств"
+                "few": "Подтвердите выход из этих устройств",
+                "many": "Подтвердите выход из этих устройств"
             },
             "confirm_sign_out_body": {
                 "one": "Нажмите кнопку ниже, чтобы подтвердить выход из этого устройства.",
-                "other": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств."
+                "few": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств.",
+                "many": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств."
             },
             "confirm_sign_out_continue": {
                 "one": "Выйти из устройства",
-                "other": "Выйти из устройств"
+                "few": "Выйти из устройств",
+                "many": "Выйти из устройств"
             },
             "confirm_sign_out_sso": {
                 "one": "Подтвердите выход из этого устройства с помощью единого входа, чтобы подтвердить свою личность.",
-                "other": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность."
+                "few": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность.",
+                "many": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность."
             },
             "current_session": "Текущий сеанс",
             "desktop_session": "Сеанс рабочего стола",
@@ -2593,7 +2744,8 @@
             "mobile_session": "Сеанс мобильного устройства",
             "n_sessions_selected": {
                 "one": "%(count)s сеанс выбран",
-                "other": "Сеансов выбрано: %(count)s"
+                "few": "%(count)s сеанса выбраны",
+                "many": "%(count)s сеансов выбрано"
             },
             "no_inactive_sessions": "Неактивных сеансов не обнаружено.",
             "no_sessions": "Сеансов не найдено.",
@@ -2613,18 +2765,20 @@
             "security_recommendations_description": "Усильте защиту учётной записи, следуя этим рекомендациям.",
             "session_id": "ID сеанса",
             "show_details": "Показать подробности",
-            "sign_in_with_qr": "Войти с QR кодом",
+            "sign_in_with_qr": "Привязать новое устройство",
             "sign_in_with_qr_button": "Показать QR код",
             "sign_in_with_qr_description": "Вы можете использовать это устройство для входа на новом устройство с помощью QR-кода. Вам необходимо отсканировать данный QR-код на новом устройстве.",
             "sign_out": "Выйти из этого сеанса",
             "sign_out_all_other_sessions": "Выйти из всех остальных сеансов (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
                 "one": "Вы уверены, что хотите выйти из %(count)s сеанса?",
-                "other": "Вы уверены, что хотите выйти из %(count)s сеансов?"
+                "few": "Вы уверены, что хотите выйти из %(count)s сеансов?",
+                "many": "Вы уверены, что хотите выйти из %(count)s сеансов?"
             },
             "sign_out_n_sessions": {
                 "one": "Выйти из %(count)s сеанса",
-                "other": "Выйти из сеансов: %(count)s"
+                "few": "Выйти из %(count)s сеансов",
+                "many": "Выйти из %(count)s сеансов"
             },
             "title": "Сеансы",
             "unknown_session": "Неизвестный тип сеанса",
@@ -2771,8 +2925,6 @@
         "topic": "Читает или устанавливает тему комнаты",
         "topic_none": "У этой комнаты нет темы.",
         "topic_room_error": "Не удалось получить тему комнаты: не удалось найти комнату (%(roomId)s",
-        "tovirtual": "Переключается на виртуальную комнату, если ваша комната её имеет",
-        "tovirtual_not_found": "Эта комната не имеет виртуальной комнаты",
         "unban": "Разблокирует пользователя с заданным ID",
         "unflip": "Добавляет ┬──┬ ノ( ゜-゜ノ) в начало сообщения",
         "unholdcall": "Прекратить удержание вызова в текущей комнате",
@@ -2790,6 +2942,7 @@
         "view": "Просмотр комнаты с указанным адресом",
         "whois": "Показать информацию о пользователе"
     },
+    "sliding_sync_legacy_no_longer_supported": "Устаревший протокол Sliding sync больше не поддерживается: выйди из системы и войди снова, чтобы включить новый протокол Sliding sync",
     "space": {
         "add_existing_room_space": {
             "create": "Хотите добавить новую комнату?",
@@ -2895,7 +3048,6 @@
         "heading_without_query": "Поиск",
         "join_button_text": "Присоединиться к %(roomAddress)s",
         "keyboard_scroll_hint": "Используйте <arrows/> для прокрутки",
-        "message_search_section_title": "Другие поиски",
         "other_rooms_in_space": "Прочие комнаты в %(spaceName)s",
         "public_rooms_label": "Публичные комнаты",
         "public_spaces_label": "Публичное пространство",
@@ -2905,7 +3057,6 @@
         "result_may_be_hidden_privacy_warning": "Некоторые результаты могут быть скрыты из-за конфиденциальности",
         "result_may_be_hidden_warning": "Некоторые результаты могут быть скрыты",
         "search_dialog": "Окно поиска",
-        "search_messages_hint": "Для поиска сообщений найдите этот значок <icon/> в верхней части комнаты",
         "spaces_title": "Ваши пространства",
         "start_group_chat_button": "Начать групповой чат"
     },
@@ -2940,7 +3091,8 @@
         "all_threads_description": "Показывает все обсуждения из текущей комнаты",
         "count_of_reply": {
             "one": "%(count)s ответ",
-            "other": "%(count)s ответов"
+            "few": "%(count)s ответа",
+            "many": "%(count)s ответов"
         },
         "error_start_thread_existing_relation": "Невозможно создать обсуждение из события с существующей связью",
         "my_threads": "Мои обсуждения",
@@ -3181,7 +3333,9 @@
             "sent": "%(senderName)s пригласил(а) %(targetDisplayName)s в комнату."
         },
         "m.room.tombstone": "%(senderDisplayName)s обновил(а) эту комнату.",
-        "m.room.topic": "%(senderDisplayName)s изменил(а) тему комнаты на \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s изменил(а) тему комнаты на \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s отправил(а) наклейку.",
         "m.video": {
             "error_decrypting": "Ошибка расшифровки видео"
@@ -3417,7 +3571,9 @@
         }
     },
     "truncated_list_n_more": {
-        "other": "Еще %(count)s…"
+        "one": "%(count)s...",
+        "few": "И еще %(count)s...",
+        "many": "И еще %(count)s..."
     },
     "unsupported_server_description": "На этом сервере используется старая версия Matrix. Перейдите на Matrix%(version)s, чтобы использовать %(brand)s ее без ошибок.",
     "unsupported_server_title": "Ваш сервер не поддерживается",
@@ -3460,14 +3616,6 @@
         "ban_room_confirm_title": "Заблокировать в %(roomName)s",
         "ban_space_everything": "Заблокировать их везде, где я могу это сделать",
         "ban_space_specific": "Заблокировать их в определённых местах, где я могу это сделать",
-        "count_of_sessions": {
-            "other": "Сеансов: %(count)s",
-            "one": "%(count)s сеанс"
-        },
-        "count_of_verified_sessions": {
-            "other": "Заверенных сеансов: %(count)s",
-            "one": "1 заверенный сеанс"
-        },
         "deactivate_confirm_action": "Деактивировать пользователя",
         "deactivate_confirm_description": "Деактивация этого пользователя приведет к его выходу из системы и запрету повторного входа. Кроме того, они оставит все комнаты, в которых он участник. Это действие безповоротно. Вы уверены, что хотите деактивировать этого пользователя?",
         "deactivate_confirm_title": "Деактивировать пользователя?",
@@ -3478,15 +3626,12 @@
         "disinvite_button_room": "Отозвать приглашение в комнату",
         "disinvite_button_room_name": "Отменить приглашение из %(roomName)s",
         "disinvite_button_space": "Отозвать приглашение в пространство",
-        "edit_own_devices": "Редактировать сеансы",
         "error_ban_user": "Не удалось заблокировать пользователя",
         "error_deactivate": "Не удалось деактивировать пользователя",
         "error_kicking_user": "Не удалось удалить пользователя",
         "error_mute_user": "Не удалось заглушить пользователя",
         "error_revoke_3pid_invite_description": "Не удалось отозвать приглашение. Возможно, на сервере возникла вре́менная проблема или у вас недостаточно прав для отзыва приглашения.",
         "error_revoke_3pid_invite_title": "Не удалось отменить приглашение",
-        "hide_sessions": "Свернуть сеансы",
-        "hide_verified_sessions": "Свернуть заверенные сеансы",
         "ignore_confirm_description": "Все сообщения и приглашения от этого пользователя будут скрыты. Вы действительно хотите их игнорировать?",
         "ignore_confirm_title": "Игнорировать %(user)s",
         "invited_by": "Приглашен %(sender)s",
@@ -3531,6 +3676,7 @@
         "verify_explainer": "Для дополнительной безопасности подтвердите этого пользователя, сравнив одноразовый код на ваших устройствах."
     },
     "user_menu": {
+        "link_new_device": "Привязать новое устройство",
         "settings": "Все настройки",
         "switch_theme_dark": "Переключить в тёмный режим",
         "switch_theme_light": "Переключить в светлый режим"
@@ -3574,7 +3720,6 @@
         "hide_sidebar_button": "Скрыть боковую панель",
         "input_devices": "Устройства ввода",
         "join_button_tooltip_call_full": "Извините — этот вызов в настоящее время заполнен",
-        "join_button_tooltip_connecting": "Подключение",
         "maximise": "Заполнить экран",
         "misconfigured_server": "Вызов не состоялся из-за неправильно настроенного сервера",
         "misconfigured_server_description": "Попросите администратора вашего домашнего сервера (<code>%(homeserverDomain)s</code>) настроить сервер TURN для надежной работы звонков.",
diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json
index a5868cf5701643bdb40fa1123ae6f0d2b6141a3e..07e7ffeeb19c557874b6b5a09fb30111eecd1447 100644
--- a/src/i18n/strings/sk.json
+++ b/src/i18n/strings/sk.json
@@ -1,15 +1,34 @@
 {
     "a11y": {
+        "emoji_picker": "Výber emotikonov",
         "jump_first_invite": "Prejsť na prvú pozvánku.",
+        "message_composer": "Editor správ",
         "n_unread_messages": {
-            "other": "%(count)s neprečítaných správ.",
-            "one": "1 neprečítaná správa."
+            "one": "1 neprečítaná správa.",
+            "few": "%(count)s neprečítané správy.",
+            "other": "%(count)s neprečítaných správ."
         },
         "n_unread_messages_mentions": {
             "one": "1 neprečítaná zmienka.",
+            "few": "%(count)s neprečítané správy vrátane zmienok.",
             "other": "%(count)s neprečítaných správ vrátane zmienok."
         },
+        "recent_rooms": "Nedávne miestnosti",
+        "room_messsage_not_sent": "Otvoriť miestnosť %(roomName)s s neodoslanou správou.",
+        "room_n_unread_invite": "Otvoriť pozvánku miestnosti %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Otvoriť miestnosť %(roomName)s s 1 neprečítanou správou.",
+            "few": "Otvoriť miestnosť %(roomName)s s %(count)s neprečítanými správami.",
+            "other": "Otvoriť miestnosť %(roomName)s s %(count)s neprečítanými správami."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Otvoriť miestnosť %(roomName)s s 1 neprečítanou zmienkou.",
+            "few": "Otvoriť miestnosť %(roomName)s s %(count)s neprečítanými správami vrátane zmienok.",
+            "other": "Otvoriť miestnosť %(roomName)s s %(count)s neprečítanými správami vrátane zmienok."
+        },
         "room_name": "Miestnosť %(name)s",
+        "room_status_bar": "Stavový riadok miestnosti",
+        "seek_bar_label": "Panel vyhľadávania zvuku",
         "unread_messages": "Neprečítané správy.",
         "user_menu": "Používateľské menu"
     },
@@ -40,6 +59,8 @@
         "create_a_room": "Vytvoriť miestnosť",
         "create_account": "Vytvoriť účet",
         "decline": "Odmietnuť",
+        "decline_and_block": "Odmietnuť a zablokovať",
+        "decline_invite": "Odmietnuť pozvanie",
         "delete": "Vymazať",
         "deny": "Zamietnuť",
         "disable": "Nepovoliť",
@@ -59,6 +80,7 @@
         "go": "Spustiť",
         "go_back": "Naspäť",
         "got_it": "Rozumiem",
+        "hide": "Skryť",
         "hide_advanced": "Skryť pokročilé možnosti",
         "hold": "Podržať",
         "ignore": "Ignorovať",
@@ -75,12 +97,14 @@
         "maximise": "Maximalizovať",
         "mention": "Zmieniť sa",
         "minimise": "Minimalizovať",
+        "new_message": "Nová správa",
         "new_room": "Nová miestnosť",
         "new_video_room": "Nová video miestnosť",
         "next": "Ďalej",
         "no": "Nie",
         "ok": "OK",
         "open": "Otvoriť",
+        "open_menu": "Otvoriť ponuku",
         "pause": "Pozastaviť",
         "pin": "Špendlík",
         "play": "Prehrať",
@@ -89,13 +113,13 @@
         "react": "Reagovať",
         "refresh": "Obnoviť",
         "register": "Zaregistrovať",
-        "reject": "Odmietnuť",
         "reload": "Znovu načítať",
         "remove": "Odstrániť",
         "rename": "Premenovať",
         "reply": "Odpovedať",
         "reply_in_thread": "Odpovedať vo vlákne",
         "report_content": "Nahlásiť obsah",
+        "report_room": "Nahlásiť miestnosť",
         "resend": "Poslať znovu",
         "reset": "Obnoviť predvolené",
         "resume": "Pokračovať",
@@ -105,6 +129,7 @@
         "save": "Uložiť",
         "search": "Hľadať",
         "send_report": "Odoslať hlásenie",
+        "set_avatar": "Nastaviť profilový obrázok",
         "share": "Zdieľať",
         "show": "Zobraziť",
         "show_advanced": "Ukázať pokročilé možnosti",
@@ -128,6 +153,7 @@
         "update": "Aktualizovať",
         "upgrade": "Aktualizovať",
         "upload": "Nahrať",
+        "upload_file": "Nahrať súbor",
         "verify": "Overiť",
         "view": "Zobraziť",
         "view_all": "Zobraziť všetky",
@@ -135,6 +161,7 @@
         "view_message": "Zobraziť správu",
         "view_source": "Zobraziť zdroj",
         "yes": "Áno",
+        "yes_dismiss": "Áno, zamietnuť",
         "zoom_in": "Priblížiť",
         "zoom_out": "Oddialiť"
     },
@@ -222,6 +249,7 @@
         },
         "misconfigured_body": "Požiadajte správcu vášho %(brand)su, aby skontroloval <a>vašu konfiguráciu</a>. Pravdepodobne obsahuje chyby alebo duplikáty.",
         "misconfigured_title": "Váš %(brand)s nie je nastavený správne",
+        "mobile_create_account_title": "Chystáte sa vytvoriť účet na %(hsName)s",
         "msisdn_field_description": "Ostatní používatelia vás môžu pozývať do miestností pomocou vašich kontaktných údajov",
         "msisdn_field_label": "Telefón",
         "msisdn_field_number_invalid": "Toto telefónne číslo nevyzerá úplne správne, skontrolujte ho a skúste to znova",
@@ -239,11 +267,40 @@
         "phone_label": "Telefón",
         "phone_optional_label": "Telefón (nepovinné)",
         "qr_code_login": {
+            "check_code_explainer": "Týmto sa overí, že pripojenie k vášmu druhému zariadeniu je bezpečné.",
+            "check_code_heading": "Zadajte číslo zobrazené na vašom druhom zariadení",
+            "check_code_input_label": "2-ciferný kód",
+            "check_code_mismatch": "Čísla sa nezhodujú",
             "completing_setup": "Dokončenie nastavenia nového zariadenia",
-            "error_unexpected": "Vyskytla sa neočakávaná chyba.",
-            "scan_code_instruction": "Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené.",
-            "scan_qr_code": "Skenovať QR kód",
+            "error_etag_missing": "Vyskytla sa neočakávaná chyba. Môže to byť spôsobené rozšírením prehliadača, proxy servera alebo nesprávnou konfiguráciou servera.",
+            "error_expired": "Platnosť prihlásenia vypršala. Prosím, skúste to znova.",
+            "error_expired_title": "Prihlásenie nebolo dokončené včas",
+            "error_insecure_channel_detected": "K novému zariadeniu sa nepodarilo vytvoriť zabezpečené pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať.",
+            "error_insecure_channel_detected_instructions": "Čo teraz?",
+            "error_insecure_channel_detected_instructions_1": "Skúste sa znova prihlásiť do druhého zariadenia pomocou QR kódu v prípade, že ide o problém so sieťou",
+            "error_insecure_channel_detected_instructions_2": "Ak narazíte na rovnaký problém, skúste inú sieť Wi-Fi alebo namiesto siete Wi-Fi použite mobilné dáta",
+            "error_insecure_channel_detected_instructions_3": "Ak to nefunguje, prihláste sa manuálne",
+            "error_insecure_channel_detected_title": "Pripojenie nie je zabezpečené",
+            "error_other_device_already_signed_in": "Nemusíte robiť nič iné.",
+            "error_other_device_already_signed_in_title": "Vaše druhé zariadenie je už prihlásené",
+            "error_rate_limited": "Príliš veľa pokusov v krátkom čase. Počkajte chvíľu, kým to skúsite znova.",
+            "error_unexpected": "Vyskytla sa neočakávaná chyba. Požiadavka na pripojenie vášho druhého zariadenia bola zrušená.",
+            "error_unsupported_protocol": "Toto zariadenie nepodporuje prihlásenie do druhého zariadenia pomocou QR kódu.",
+            "error_unsupported_protocol_title": "Druhé zariadenie nie je kompatibilné",
+            "error_user_cancelled": "Prihlásenie bolo zrušené na druhom zariadení.",
+            "error_user_cancelled_title": "Žiadosť o prihlásenie bola zrušená",
+            "error_user_declined": "Odmietli ste žiadosť o prihlásenie zo svojho druhého zariadenia.",
+            "error_user_declined_title": "Prihlásenie bolo odmietnuté",
+            "follow_remaining_instructions": "Postupujte podľa zostávajúcich pokynov",
+            "open_element_other_device": "Otvorte %(brand)s na svojom druhom zariadení",
+            "point_the_camera": "Naskenujte tu zobrazený QR kód",
+            "scan_code_instruction": "Naskenujte QR kód pomocou druhého zariadenia",
+            "scan_qr_code": "Prihláste sa pomocou QR kódu",
+            "security_code": "Bezpečnostný kód",
+            "security_code_prompt": "Ak sa zobrazí výzva, zadajte nižšie uvedený kód na svojom druhom zariadení.",
             "select_qr_code": "Vyberte '%(scanQRCode)s'",
+            "unsupported_explainer": "Poskytovateľ vášho účtu nepodporuje prihlásenie do nového zariadenia pomocou QR kódu.",
+            "unsupported_heading": "QR kód nie je podporovaný",
             "waiting_for_device": "Čaká sa na prihlásenie zariadenia"
         },
         "register_action": "Vytvoriť účet",
@@ -302,7 +359,7 @@
         "set_email_prompt": "Želáte si nastaviť emailovú adresu?",
         "sign_in_description": "Ak chcete pokračovať, použite svoje konto.",
         "sign_in_instead": "Radšej sa prihlásiť",
-        "sign_in_instead_prompt": "Radšej sa prihlásiť",
+        "sign_in_instead_prompt": "Už máte účet? <a>Prihláste sa tu</a>",
         "sign_in_or_register": "Prihlásiť sa alebo vytvoriť nový účet",
         "sign_in_or_register_description": "Použite váš existujúci účet alebo si vytvorte nový, aby ste mohli pokračovať.",
         "sign_in_prompt": "Máte účet? <a>Prihláste sa</a>",
@@ -332,6 +389,9 @@
             "email_resend_prompt": "Nedostali ste ho? <a>Poslať znova</a>",
             "email_resent": "Znova odoslané!",
             "fallback_button": "Spustiť overenie",
+            "mas_cross_signing_reset_cta": "Prejdite do svojho účtu",
+            "mas_cross_signing_reset_description": "Obnovte svoju totožnosť prostredníctvom poskytovateľa účtu a potom sa vráťte späť a kliknite na tlačidlo „Skúsiť znova“.",
+            "mas_cross_signing_reset_title": "Prejdite do svojho účtu a obnovte svoju totožnosť",
             "msisdn": "Na číslo %(msisdn)s bola odoslaná textová správa",
             "msisdn_token_incorrect": "Neplatný token",
             "msisdn_token_prompt": "Prosím, zadajte kód z tejto správy:",
@@ -366,7 +426,15 @@
         "download_logs": "Stiahnuť záznamy",
         "downloading_logs": "Sťahovanie záznamov",
         "error_empty": "Povedzte nám prosím, čo sa pokazilo, alebo radšej vytvorte príspevok v službe GitHub, v ktorom problém popíšete.",
-        "failed_send_logs": "Nepodarilo sa odoslať záznamy: ",
+        "failed_download_logs": "Nepodarilo sa stiahnuť záznamy ladenia: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Vaša správa o chybe bola zamietnutá. Server rageshake túto aplikáciu nepodporuje.",
+            "rejected_generic": "Vaše hlásenie o chybe bolo zamietnuté. Server rageshake odmietol obsah správy kvôli pravidlám.",
+            "rejected_recovery_key": "Vaša správa o chybe bola z bezpečnostných dôvodov zamietnutá, pretože obsahovala kľúč na obnovenie.",
+            "rejected_version": "Vaša správa o chybe bola zamietnutá, pretože verzia, ktorú používate, je príliš stará.",
+            "server_unknown_error": "Server rageshake narazil na neznámu chybu a nedokázal spracovať správu.",
+            "unknown_error": "Nepodarilo sa odoslať protokol."
+        },
         "github_issue": "Správa o probléme na GitHub",
         "introduction": "Ak ste odoslali chybu prostredníctvom služby GitHub, záznamy o ladení nám môžu pomôcť nájsť problém. ",
         "log_request": "Aby ste nám pomohli tomuto v budúcnosti zabrániť, pošlite nám prosím <a>záznamy o chybe</a>.",
@@ -406,11 +474,12 @@
         "access_token": "Prístupový token",
         "accessibility": "Prístupnosť",
         "advanced": "Pokročilé",
-        "all_rooms": "Všetky miestnosti",
+        "all_chats": "Všetky konverzácie",
         "analytics": "Analytické údaje",
         "and_n_others": {
-            "other": "a ďalších %(count)s…",
-            "one": "a jeden ďalší…"
+            "one": "a jeden ďalší…",
+            "few": "a %(count)s ďalší…",
+            "other": "a %(count)s ďalších…"
         },
         "appearance": "Vzhľad",
         "application": "Aplikácia",
@@ -421,10 +490,10 @@
         "beta": "Beta verzia",
         "camera": "Kamera",
         "cameras": "Kamery",
+        "cancel": "Zrušiť",
         "capabilities": "Schopnosti",
         "copied": "Skopírované!",
         "credits": "Poďakovanie",
-        "cross_signing": "Krížové podpisovanie",
         "dark": "Tmavý",
         "description": "Popis",
         "deselect_all": "Zrušiť výber všetkých",
@@ -460,16 +529,20 @@
         "matrix": "Matrix",
         "message": "Správa",
         "message_layout": "Rozloženie správy",
+        "message_timestamp_invalid": "Neplatná časová značka",
         "microphone": "Mikrofón",
         "model": "Model",
+        "moderation_and_safety": "Moderovanie a bezpečnosť",
         "modern": "Moderný",
         "mute": "Umlčať",
         "n_members": {
             "one": "%(count)s člen",
+            "few": "%(count)s členovia",
             "other": "%(count)s členov"
         },
         "n_rooms": {
             "one": "%(count)s miestnosť",
+            "few": "%(count)s miestnosti",
             "other": "%(count)s miestností"
         },
         "name": "Názov",
@@ -497,13 +570,15 @@
         "qr_code": "QR kód",
         "random": "Náhodné",
         "reactions": "Reakcie",
+        "recommended": "Odporúčané",
         "report_a_bug": "Nahlásiť chybu",
         "room": "Miestnosť",
         "room_name": "Názov miestnosti",
         "rooms": "Miestnosti",
+        "save": "Uložiť",
+        "saved": "Uložené",
         "saving": "Ukladanie…",
         "secure_backup": "Bezpečné zálohovanie",
-        "security": "Zabezpečenie",
         "select_all": "Vybrať všetky",
         "server": "Server",
         "settings": "Nastavenia",
@@ -522,13 +597,13 @@
         "thread": "Vlákno",
         "threads": "Vlákna",
         "timeline": "Časová os",
-        "trusted": "Dôveryhodné",
         "unavailable": "nedostupný",
         "unencrypted": "Nešifrované",
         "unmute": "Zrušiť stlmenie",
         "unnamed_room": "Nepomenovaná miestnosť",
         "unnamed_space": "Nepomenovaný priestor",
         "unverified": "Neoverené",
+        "updating": "Aktualizuje sa...",
         "user": "Používateľ",
         "user_avatar": "Obrázok v profile",
         "username": "Meno používateľa",
@@ -681,6 +756,13 @@
         "twemoji": "<twemoji>Twemoji</twemoji> emoji grafika je © <author>Twitter, Inc a ďalší prispievatelia</author> používaná podľa podmienok <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "Písmo <colr>twemoji-colr</colr> je © <author>Mozilla Foundation</author> používané podľa podmienok <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Ste si istý, že chcete odmietnuť pozvanie do miestnosti \"%(roomName)s\"?",
+        "ignore_user_help": "Neuvidíte žiadne správy ani pozvánky do miestností od tohto používateľa.",
+        "reason_description": "Popíšte dôvod nahlásenia miestnosti.",
+        "report_room_description": "Nahlásiť túto miestnosť poskytovateľovi účtu.",
+        "title": "Odmietnuť pozvanie"
+    },
     "desktop_default_device_name": "%(brand)s Stolný počítač: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktívne widgety",
@@ -688,6 +770,44 @@
         "category_room": "Miestnosť",
         "caution_colon": "Upozornenie:",
         "client_versions": "Verzie klienta",
+        "crypto": {
+            "4s_public_key_in_account_data": "v údajoch o účte",
+            "4s_public_key_not_in_account_data": "nenájdené",
+            "4s_public_key_status": "Verejný kľúč tajného úložiska:",
+            "backup_key_cached": "lokálne uložené",
+            "backup_key_cached_status": "Záložný kľúč uložený vo vyrovnávacej pamäti:",
+            "backup_key_not_stored": "nie je uložený",
+            "backup_key_stored": "v tajnom úložisku",
+            "backup_key_stored_status": "Záložný kľúč uložený:",
+            "backup_key_unexpected_type": "neočakávaný typ",
+            "backup_key_well_formed": "správne vytvorený",
+            "cross_signing": "Krížové podpisovanie",
+            "cross_signing_cached": "lokálne uložené",
+            "cross_signing_not_ready": "Krížové podpisovanie nie je nastavené.",
+            "cross_signing_private_keys_in_storage": "v tajnom úložisku",
+            "cross_signing_private_keys_in_storage_status": "Súkromné kľúče krížového podpisovania:",
+            "cross_signing_private_keys_not_in_storage": "neboli nájdené v úložisku",
+            "cross_signing_public_keys_on_device": "v pamäti",
+            "cross_signing_public_keys_on_device_status": "Verejné kľúče krížového podpisovania:",
+            "cross_signing_ready": "Krížové podpisovanie je pripravené na použitie.",
+            "cross_signing_status": "Stav krížového podpisovania:",
+            "cross_signing_untrusted": "Váš účet má identitu s krížovým podpisom v tajnom úložisku, ale táto relácia mu zatiaľ nedôveruje.",
+            "crypto_not_available": "Kryptografický modul nie je k dispozícii",
+            "key_backup_active_version": "Aktívna verzia zálohy:",
+            "key_backup_active_version_none": "Žiadna",
+            "key_backup_inactive_warning": "Vaše kľúče nie sú zálohované z tejto relácie.",
+            "key_backup_latest_version": "Najnovšia záložná verzia na serveri:",
+            "key_storage": "Úložisko kľúčov",
+            "master_private_key_cached_status": "Hlavný súkromný kľúč:",
+            "not_found": "nenájdený",
+            "not_found_locally": "nenájdený lokálne",
+            "secret_storage_not_ready": "nie je pripravený",
+            "secret_storage_ready": "pripravený",
+            "secret_storage_status": "Tajné úložisko:",
+            "self_signing_private_key_cached_status": "Súkromný kľúč s vlastným podpisom:",
+            "title": "Šifrovanie typu end-to-end",
+            "user_signing_private_key_cached_status": "Súkromný kľúč podpisovania používateľa:"
+        },
         "developer_mode": "Režim pre vývojárov",
         "developer_tools": "Vývojárske nástroje",
         "edit_setting": "Upraviť nastavenie",
@@ -744,6 +864,9 @@
         "setting_colon": "Nastavenie:",
         "setting_definition": "Definícia nastavenia:",
         "setting_id": "ID nastavenia",
+        "settings": {
+            "elementCallUrl": "URL adresa Element Call"
+        },
         "settings_explorer": "Prieskumník nastavení",
         "show_hidden_events": "Zobrazovať skryté udalosti v histórii obsahu miestností",
         "spaces": {
@@ -797,52 +920,37 @@
     "empty_room_was_name": "Prázdna miestnosť (bola %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Zadajte svoju bezpečnostnú frázu alebo <button>použite svoj bezpečnostný kľúč</button> pre pokračovanie.",
+            "alternatives": "Ak máte bezpečnostný kľúč alebo bezpečnostnú frázu, bude to fungovať tiež.",
             "key_validation_text": {
-                "invalid_security_key": "Neplatný bezpečnostný kľúč",
-                "recovery_key_is_correct": "Vyzerá to super!",
-                "wrong_file_type": "Nesprávny typ súboru",
-                "wrong_security_key": "Nesprávny bezpečnostný kľúč"
-            },
-            "reset_title": "Obnoviť všetko",
-            "reset_warning_1": "Urobte to len vtedy, ak nemáte iné zariadenie, pomocou ktorého by ste mohli dokončiť overenie.",
-            "reset_warning_2": "Ak všetko obnovíte, reštartujete bez dôveryhodných relácií, bez dôveryhodných používateľov a možno nebudete môcť vidieť predchádzajúce správy.",
+                "wrong_security_key": "Zadaný kľúč na obnovenie nie je správny."
+            },
+            "privacy_warning": "Uistite sa, že túto obrazovku nikto neuvidí!",
             "restoring": "Obnovenie kľúčov zo zálohy",
-            "security_key_title": "Bezpečnostný kľúč",
-            "security_phrase_incorrect_error": "Nie je možné získať prístup k tajnému úložisku. Skontrolujte, či ste zadali správnu bezpečnostnú frázu.",
-            "security_phrase_title": "Bezpečnostná fráza",
-            "separator": "%(securityKey)s alebo %(recoveryFile)s",
-            "use_security_key_prompt": "Ak chcete pokračovať, použite bezpečnostný kľúč."
+            "security_key_title": "Kľúč na obnovenie"
         },
         "bootstrap_title": "Príprava kľúčov",
         "cancel_entering_passphrase_description": "Naozaj chcete zrušiť zadávanie prístupovej frázy?",
         "cancel_entering_passphrase_title": "Zrušiť zadanie prístupovej frázy?",
         "confirm_encryption_setup_body": "Kliknutím na tlačidlo nižšie potvrdíte nastavenie šifrovania.",
         "confirm_encryption_setup_title": "Potvrdiť nastavenie šifrovania",
-        "cross_signing_not_ready": "Krížové podpisovanie nie je nastavené.",
-        "cross_signing_ready": "Krížové podpisovanie je pripravené na použitie.",
-        "cross_signing_ready_no_backup": "Krížové podpisovanie je pripravené, ale kľúče nie sú zálohované.",
         "cross_signing_room_normal": "Táto miestnosť je end-to-end šifrovaná",
         "cross_signing_room_verified": "Všetci v tejto miestnosti sú overení",
         "cross_signing_room_warning": "Niekto používa neznámu reláciu",
-        "cross_signing_unsupported": "Váš domovský server nepodporuje krížové podpisovanie.",
-        "cross_signing_untrusted": "Váš účet má v tajnom úložisku krížovú podpisovú totožnosť, ale táto relácia jej ešte nedôveruje.",
         "cross_signing_user_normal": "Tohto používateľa ste neoverili.",
         "cross_signing_user_verified": "Tohto používateľa ste overili. Tento používateľ si overil všetky svoje relácie.",
         "cross_signing_user_warning": "Tento používateľ si neoveril všetky svoje relácie.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Vyčistiť kľúče na krížové podpisovanie",
-            "title": "Zničiť kľúče na krížové podpisovanie?",
-            "warning": "Zmazanie kľúčov pre krížové podpisovanie je nenávratné. Každý, s kým ste sa overili, bude vidieť bezpečnostné upozornenia. Toto určite nechcete robiť, dokiaľ ste nestratili všetky zariadenia, s ktorými by ste mohli krížovo podpisovať."
-        },
+        "enter_recovery_key": "Zadajte kľúč na obnovenie",
         "event_shield_reason_authenticity_not_guaranteed": "Vierohodnosť tejto zašifrovanej správy nie je možné na tomto zariadení zaručiť.",
         "event_shield_reason_mismatched_sender_key": "Šifrované neoverenou reláciou",
         "event_shield_reason_unknown_device": "Šifrované neznámym alebo odstráneným zariadením.",
         "event_shield_reason_unsigned_device": "Šifrované zariadením, ktoré nie je overené jeho majiteľom.",
         "event_shield_reason_unverified_identity": "Šifrované neovereným používateľom.",
         "export_unsupported": "Váš prehliadač nepodporuje požadované kryptografické rozšírenia",
+        "forgot_recovery_key": "Zabudli ste kľúč na obnovenie?",
         "import_invalid_keyfile": "Toto nie je správny súbor s kľúčmi %(brand)s",
         "import_invalid_passphrase": "Kontrola overenia zlyhala: Nesprávne heslo?",
+        "key_storage_out_of_sync": "Vaše úložisko kľúčov nie je synchronizované.",
+        "key_storage_out_of_sync_description": "Potvrďte svoj kľúč na obnovenie, aby ste zachovali prístup k úložisku kľúčov a histórii správ.",
         "messages_not_secure": {
             "cause_1": "Váš domovský server",
             "cause_2": "Domovský server, ku ktorému je pripojený používateľ, ktorého overujete",
@@ -857,7 +965,8 @@
             "title": "Nový spôsob obnovy",
             "warning": "Ak ste si nenastavili nový spôsob obnovenia, je možné, že útočník sa pokúša dostať k vášmu účtu. Radšej si ihneď zmeňte vaše heslo a nastavte si nový spôsob obnovenia v Nastaveniach."
         },
-        "not_supported": "<nepodporované>",
+        "pinned_identity_changed": "Zdá sa, že identita (<b>%(userId)s</b>) používateľa %(displayName)s bola obnovená. <a>Zistiť viac </a>",
+        "pinned_identity_changed_no_displayname": "Identita používateľa <b>%(userId)s</b> bola obnovená. <a>Zistiť viac</a>",
         "recovery_method_removed": {
             "description_1": "Táto relácia zistila, že vaša bezpečnostná fráza a kľúč pre zabezpečené správy boli odstránené.",
             "description_2": "Ak ste to urobili omylom, môžete v tejto relácii nastaviť Zabezpečené správy, ktoré znovu zašifrujú históriu správ tejto relácie pomocou novej metódy obnovy.",
@@ -865,12 +974,16 @@
             "warning": "Ak ste neodstránili spôsob obnovenia vy, je možné, že útočník sa pokúša dostať k vášmu účtu. Radšej si ihneď zmeňte vaše heslo a nastavte si nový spôsob obnovenia v Nastaveniach."
         },
         "reset_all_button": "Zabudli ste alebo ste stratili všetky metódy obnovy? <a>Resetovať všetko</a>",
+        "set_up_recovery": "Nastaviť obnovenie",
+        "set_up_recovery_later": "Teraz nie",
+        "set_up_recovery_toast_description": "Vytvorte kľúč na obnovenie, ktorý môžete použiť na obnovenie histórie šifrovaných správ v prípade straty prístupu k zariadeniam.",
         "set_up_toast_description": "Zabezpečte sa proti strate šifrovaných správ a údajov",
         "set_up_toast_title": "Nastaviť bezpečné zálohovanie",
         "setup_secure_backup": {
-            "explainer": "Zálohujte si šifrovacie kľúče pred odhlásením, aby ste zabránili ich strate.",
-            "title": "Nastaviť"
+            "explainer": "Zálohujte si šifrovacie kľúče pred odhlásením, aby ste zabránili ich strate."
         },
+        "turn_on_key_storage": "Zapnúť úložisko kľúčov",
+        "turn_on_key_storage_description": "Uložte svoju kryptografickú totožnosť a kľúče správ bezpečne na serveri. To vám umožní zobraziť históriu správ na všetkých nových zariadeniach.",
         "udd": {
             "interactive_verification_button": "Interaktívne overte pomocou emotikonov",
             "other_ask_verify_text": "Poproste tohto používateľa, aby si overil svoju reláciu alebo ju nižšie manuálne overte.",
@@ -880,12 +993,10 @@
             "title": "Nedôveryhodné"
         },
         "unable_to_setup_keys_error": "Nie je možné nastaviť kľúče",
-        "unsupported": "Tento klient nepodporuje end-to-end šifrovanie.",
         "verification": {
             "accepting": "Akceptovanie…",
             "after_new_login": {
                 "device_verified": "Zariadenie overené",
-                "reset_confirmation": "Skutočne vynulovať overovacie kľúče?",
                 "skip_verification": "Vynechať zatiaľ overovanie",
                 "unable_to_verify": "Nie je možné overiť toto zariadenie",
                 "verify_this_device": "Overiť toto zariadenie"
@@ -907,7 +1018,7 @@
             "incoming_sas_dialog_waiting": "Čakanie na potvrdenie od partnera…",
             "incoming_sas_user_dialog_text_1": "Overte tohto používateľa a označte ho ako dôveryhodného. Dôveryhodní používatelia vám poskytujú dodatočný pokoj na duši pri používaní end-to-end šifrovaných správ.",
             "incoming_sas_user_dialog_text_2": "Overenie tohto používateľa označí jeho reláciu ako dôveryhodnú a zároveň označí vašu reláciu ako dôveryhodnú pre neho.",
-            "no_key_or_device": "Vyzerá to, že nemáte bezpečnostný kľúč ani žiadne iné zariadenie, pomocou ktorého by ste to mohli overiť.  Toto zariadenie nebude mať prístup k starým zašifrovaným správam. Ak chcete overiť svoju totožnosť na tomto zariadení, budete musieť obnoviť svoje overovacie kľúče.",
+            "no_key_or_device": "Vyzerá to, že nemáte kľúč na obnovenie ani žiadne iné zariadenie, pomocou ktorého by ste to mohli overiť.  Toto zariadenie nebude mať prístup k starým zašifrovaným správam. Ak chcete overiť svoju totožnosť na tomto zariadení, budete musieť obnoviť svoje overovacie kľúče.",
             "no_support_qr_emoji": "Zariadenie, ktoré sa snažíte overiť, nepodporuje overenie skenovaním QR kódu ani overenie pomocou emotikonov, ktoré podporuje aplikácia %(brand)s. Skúste použiť iného klienta.",
             "other_party_cancelled": "Proti strana zrušila overovanie.",
             "prompt_encrypted": "Overte všetkých používateľov v miestnosti, aby ste sa uistili, že je zabezpečená.",
@@ -920,6 +1031,7 @@
             "qr_reciprocate_same_shield_device": "Už je to takmer hotové! Zobrazuje vaše druhé zariadenie rovnaký štít?",
             "qr_reciprocate_same_shield_user": "Už je to takmer hotové! Zobrazuje sa %(displayName)s rovnaký štít?",
             "request_toast_accept": "Overiť reláciu",
+            "request_toast_accept_user": "Overiť používateľa",
             "request_toast_decline_counter": "Ignorovať (%(counter)s)",
             "request_toast_detail": "%(deviceId)s z %(ip)s",
             "reset_proceed_prompt": "Pokračovať v obnovení",
@@ -945,7 +1057,7 @@
             "unverified_sessions_toast_description": "Skontrolujte, či je vaše konto bezpečné",
             "unverified_sessions_toast_reject": "Neskôr",
             "unverified_sessions_toast_title": "Máte neoverené relácie",
-            "verification_description": "Overte svoju totožnosť, aby ste mali prístup k zašifrovaným správam a potvrdili svoju totožnosť ostatným.",
+            "verification_description": "Overte svoju totožnosť, aby ste mali prístup k zašifrovaným správam a potvrdili svoju totožnosť ostatným. Ak používate aj mobilné zariadenie, otvorte aplikáciu predtým, ako budete pokračovať.",
             "verification_dialog_title_device": "Overenie iného zariadenia",
             "verification_dialog_title_user": "Žiadosť o overenie",
             "verification_skip_warning": "Bez overenia nebudete mať prístup ku všetkým svojim správam a pre ostatných sa môžete javiť ako nedôveryhodní.",
@@ -955,19 +1067,20 @@
             "verify_emoji_prompt": "Overenie porovnaním jedinečnej kombinácie emotikonov.",
             "verify_emoji_prompt_qr": "Ak sa vám nepodarí naskenovať uvedený kód, overte pomocou porovnania jedinečných emotikonov.",
             "verify_later": "Overím to neskôr",
-            "verify_reset_warning_1": "Vynulovanie overovacích kľúčov sa nedá vrátiť späť. Po vynulovaní nebudete mať prístup k starým zašifrovaným správam a všetci priatelia, ktorí vás predtým overili, uvidia bezpečnostné upozornenia, kým sa u nich znovu neoveríte.",
-            "verify_reset_warning_2": "Pokračujte prosím iba vtedy, ak ste si istí, že ste stratili všetky ostatné zariadenia a váš bezpečnostný kľúč.",
             "verify_using_device": "Overiť pomocou iného zariadenia",
-            "verify_using_key": "Overenie bezpečnostným kľúčom",
-            "verify_using_key_or_phrase": "Overiť pomocou bezpečnostného kľúča alebo frázy",
+            "verify_using_key": "Overiť pomocou kľúča na obnovenie",
+            "verify_using_key_or_phrase": "Overiť pomocou kľúča na obnovenie alebo frázy",
             "waiting_for_user_accept": "Čaká sa, kým to %(displayName)s prijme…",
             "waiting_other_device": "Čaká sa na overenie na vašom druhom zariadení…",
             "waiting_other_device_details": "Čaká sa na overenie na vašom druhom zariadení, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Čakám na %(displayName)s, kým nás overí…"
         },
         "verification_requested_toast_title": "Vyžiadané overenie",
+        "verified_identity_changed": "Totožnosť používateľa %(displayName)s's (<b>%(userId)s</b>) bola obnovená. <a>Zistiť viac</a>",
+        "verified_identity_changed_no_displayname": "Totožnosť používateľa <b>%(userId)s</b> bola obnovená. <a>Zistiť viac</a>",
         "verify_toast_description": "Ostatní používatelia jej nemusia dôverovať",
-        "verify_toast_title": "Overiť túto reláciu"
+        "verify_toast_title": "Overiť túto reláciu",
+        "withdraw_verification_action": "Zrušiť overenie"
     },
     "error": {
         "admin_contact": "Prosím, <a>kontaktujte správcu služieb</a> aby ste mohli službu ďalej používať.",
@@ -1008,21 +1121,21 @@
         "unknown_error_code": "neznámy kód chyby",
         "update_power_level": "Nepodarilo sa zmeniť úroveň oprávnenia"
     },
-    "error_app_open_in_another_tab": "Aplikácia %(brand)s bola otvorená na inej karte.",
+    "error_app_open_in_another_tab": "Prepnite na druhú kartu aby ste sa spojili s aplikáciou %(brand)s. Túto kartu môžete teraz zatvoriť.",
     "error_app_open_in_another_tab_title": "%(brand)s je pripojený na inej karte",
     "error_app_opened_in_another_window": "%(brand)s je otvorený v inom okne. Kliknutím na \"%(label)s\" použijete túto aplikáciu %(brand)s a odpojíte druhé okno.",
-    "error_database_closed_title": "Databáza sa neočakávane zatvorila",
+    "error_database_closed_description": {
+        "for_desktop": "Váš disk môže byť plný. Uvoľnite prosím miesto a znova to načítajte.",
+        "for_web": "Ak ste vymazali údaje o prehliadaní, tak táto správa sa očakáva. %(brand)s môže byť otvorený aj na inej karte alebo je váš disk plný. Uvoľnite prosím miesto a znovu ho načítajte"
+    },
+    "error_database_closed_title": "%(brand)s prestal fungovať",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Nie je možné skopírovať odkaz na miestnosť do schránky.",
             "title": "Nie je možné skopírovať odkaz na miestnosť"
         },
         "error_loading_user_profile": "Nie je možné načítať profil používateľa",
-        "forget_room_failed": "Nepodarilo sa zabudnúť miestnosť %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Server môže byť nedostupný, preťažený, alebo vypršal časový limit hľadania :(",
-            "title": "Hľadanie zlyhalo"
-        }
+        "forget_room_failed": "Nepodarilo sa zabudnúť miestnosť %(errCode)s"
     },
     "error_user_not_logged_in": "Používateľ nie je prihlásený",
     "event_preview": {
@@ -1047,7 +1160,15 @@
             "you": "Reagovali ste %(reaction)s na %(message)s"
         },
         "m.sticker": "%(senderName)s: %(stickerName)s",
-        "m.text": "%(senderName)s: %(message)s"
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Zvuk",
+            "file": "Súbor",
+            "image": "Obrázok",
+            "poll": "Anketa",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Export zrušený",
@@ -1142,9 +1263,10 @@
     "identity_server": {
         "change": "Zmeniť server totožností",
         "change_prompt": "Naozaj si želáte odpojiť od servera totožností <current /> a pripojiť sa namiesto toho k serveru <new />?",
-        "change_server_prompt": "Ak nechcete na vyhľadávanie kontaktov a možnosť byť nájdení používať <server />, zadajte adresu servera totožností nižšie.",
+        "change_server_prompt": "Ak nechcete používať <server /> na vyhľadávanie kontaktov a možnosť byť nájdený, zadajte adresu servera totožností nižšie.",
+        "changed": "Váš server totožnosti bol zmenený",
         "checking": "Kontrola servera",
-        "description_connected": "Momentálne na vyhľadávanie kontaktov a na možnosť byť nájdení kontaktmi ktorých poznáte používate <server></server>. Zmeniť server totožností môžete nižšie.",
+        "description_connected": "Momentálne na vyhľadávanie kontaktov a na možnosť byť nájdený kontaktmi, ktoré poznáte používate <server></server>. Zmeniť server totožností môžete nižšie.",
         "description_disconnected": "Momentálne nepoužívate žiaden server totožností. Ak chcete vyhľadávať kontakty a zároveň umožniť ostatným vašim kontaktom, aby mohli nájsť vás, nastavte si server totožností nižšie.",
         "description_optional": "Používanie servera totožností je voliteľné. Ak sa rozhodnete, že nebudete používať server totožností, nebudú vás vaši známi môcť nájsť a ani vy nebudete môcť pozývať používateľov zadaním emailovej adresy alebo telefónneho čísla.",
         "disconnect": "Odpojiť server totožností",
@@ -1171,10 +1293,24 @@
     "in_space1_and_space2": "V priestoroch %(space1Name)s a %(space2Name)s.",
     "in_space_and_n_other_spaces": {
         "one": "V %(spaceName)s a v %(count)s ďalšom priestore.",
+        "few": "V %(spaceName)s a %(count)s ďalších priestoroch.",
         "other": "V %(spaceName)s a %(count)s ďalších priestoroch."
     },
     "incompatible_browser": {
-        "title": "Nepodporovaný prehliadač"
+        "continue": "Napriek tomu pokračovať",
+        "description": "%(brand)s používa niektoré funkcie prehliadača, ktoré nie sú dostupné vo vašom aktuálnom prehliadači. %(detail)s",
+        "detail_can_continue": "Ak budete pokračovať, niektoré funkcie môžu prestať fungovať a existuje riziko, že v budúcnosti stratíte údaje.",
+        "detail_no_continue": "Skúste aktualizovať tento prehliadač, ak nepoužívate najnovšiu verziu, a skúste to znova.",
+        "learn_more": "Zistiť viac",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Pre najlepší zážitok použite prehliadač <Firefox>Firefox</Firefox>, <Chrome>Chrome</Chrome>, <Edge>Edge</Edge> alebo <Safari>Safari</Safari>.",
+        "title": "Nepodporovaný prehliadač",
+        "use_desktop_heading": "Namiesto toho použite aplikáciu %(brand)s Desktop",
+        "use_mobile_heading": "Namiesto toho použite aplikáciu %(brand)s v mobile",
+        "use_mobile_heading_after_desktop": "Alebo použite našu mobilnú aplikáciu",
+        "windows_64bit": "Windows (64-bitový)",
+        "windows_arm_64bit": "Windows (ARM 64-bitový)"
     },
     "info_tooltip_title": "Informácie",
     "integration_manager": {
@@ -1183,6 +1319,7 @@
         "error_connecting_heading": "Nie je možné sa pripojiť k integračnému serveru",
         "explainer": "Správcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení.",
         "manage_title": "Spravovať integrácie",
+        "toggle_label": "Povoliť správcu integrácie",
         "use_im": "Na správu botov, widgetov a balíkov nálepiek použite správcu integrácie.",
         "use_im_default": "Na správu botov, widgetov a balíkov nálepiek použite správcu integrácie <b>(%(serverName)s)</b>."
     },
@@ -1249,12 +1386,14 @@
     },
     "inviting_user1_and_user2": "Pozývanie %(user1)s a %(user2)s",
     "inviting_user_and_n_others": {
-        "one": "Pozývanie %(user)s a 1 ďalšieho",
-        "other": "Pozývanie %(user)s a %(count)s ďalších"
+        "one": "Pozýva sa %(user)s a jeden ďalší",
+        "few": "Pozýva sa %(user)s a %(count)s ďalší",
+        "other": "Pozýva sa %(user)s a %(count)s ďalších"
     },
     "items_and_n_others": {
-        "other": "<Items/> a %(count)s ďalší",
-        "one": "<Items/> a jeden ďalší"
+        "one": "<Items/> a jeden ďalší",
+        "few": "<Items/> a %(count)s ďalšie",
+        "other": "<Items/> a %(count)s ďalší"
     },
     "keyboard": {
         "activate_button": "Aktivovať vybrané tlačidlo",
@@ -1298,12 +1437,14 @@
         "navigate_next_message_edit": "Prejsť na ďalšiu správu na úpravu",
         "navigate_prev_history": "Predchádzajúca nedávno navštívená miestnosť alebo priestor",
         "navigate_prev_message_edit": "Prejsť na predchádzajúcu správu na úpravu",
+        "next_landmark": "Prejdite na ďalší orientačný bod",
         "next_room": "Ďalšia miestnosť alebo správa",
         "next_unread_room": "Ďalšia neprečítaná miestnosť alebo správa",
         "number": "[číslo]",
         "open_user_settings": "Otvoriť používateľské nastavenia",
         "page_down": "Page Down",
         "page_up": "Page Up",
+        "prev_landmark": "Prejsť na predchádzajúci orientačný bod",
         "prev_room": "Predchádzajúca miestnosť alebo správa",
         "prev_unread_room": "Predchádzajúca neprečítaná miestnosť alebo správa",
         "room_list_collapse_section": "Zbaliť sekciu zoznamu miestností",
@@ -1348,8 +1489,11 @@
         "dynamic_room_predecessors": "Predchodcovia dynamickej miestnosti",
         "dynamic_room_predecessors_description": "Zapnúť MSC3946 (na podporu neskorých archívov miestností)",
         "element_call_video_rooms": "Element Call video miestnosti",
+        "exclude_insecure_devices": "Vylúčiť nezabezpečené zariadenia pri odosielaní/prijímaní správ",
+        "exclude_insecure_devices_description": "Ak je tento režim povolený, šifrované správy nebudú zdieľané s neoverenými zariadeniami a správy z neoverených zariadení sa zobrazia ako chyba. Upozorňujeme, že ak povolíte tento režim, možno nebudete môcť komunikovať s používateľmi, ktorí svoje zariadenia neoverili.",
         "experimental_description": "Chcete experimentovať? Vyskúšajte naše najnovšie nápady vo vývojovom štádiu. Tieto funkcie nie sú dokončené; môžu byť nestabilné, môžu sa zmeniť alebo môžu byť úplne zrušené. <a>Zistiť viac</a>.",
         "experimental_section": "Predbežné ukážky",
+        "extended_profiles_msc_support": "Vyžaduje, aby váš server podporoval MSC4133",
         "feature_disable_call_per_sender_encryption": "Zakázať šifrovanie pre jednotlivých odosielateľov pre Element Call",
         "feature_wysiwyg_composer_description": "Použiť rozšírený text namiesto Markdown v správach.",
         "group_calls": "Nový zážitok zo skupinových hovorov",
@@ -1362,6 +1506,8 @@
         "group_rooms": "Miestnosti",
         "group_spaces": "Priestory",
         "group_themes": "Vzhľad",
+        "group_threads": "Vlákna",
+        "group_ui": "Používateľské rozhranie",
         "group_voip": "Zvuk a video",
         "group_widgets": "Widgety",
         "hidebold": "Skryť oznamovaciu bodku (zobrazovať iba odznaky počítadiel)",
@@ -1377,10 +1523,12 @@
         "location_share_live_description": "Dočasná implementácia. Polohy ostávajú v histórii miestnosti.",
         "mjolnir": "Nové spôsoby ignorovania ľudí",
         "msc3531_hide_messages_pending_moderation": "Umožniť moderátorom skryť správy čakajúce na schválenie.",
+        "new_room_list": "Povoliť nový zoznam miestností",
         "notification_settings": "Nové nastavenia oznámení",
         "notification_settings_beta_caption": "Predstavujeme jednoduchší spôsob zmeny nastavení oznámení. Prispôsobte si svoju aplikáciu %(brand)s, presne podľa svojich predstáv.",
         "notification_settings_beta_title": "Nastavenia oznámení",
         "notifications": "Povolenie panelu oznámení v záhlaví miestnosti",
+        "release_announcement": "Oznámenie o vydaní",
         "render_reaction_images": "Vykresľovať vlastné obrázky v reakciách",
         "render_reaction_images_description": "Niekedy sa označujú ako „vlastné emotikony“.",
         "report_to_moderators": "Nahlásiť moderátorom",
@@ -1388,7 +1536,7 @@
         "sliding_sync": "Režim kĺzavej synchronizácie",
         "sliding_sync_description": "V štádiu aktívneho vývoja, nie je možné to vypnúť.",
         "sliding_sync_disabled_notice": "Odhláste sa a znova sa prihláste, aby sa to vyplo",
-        "sliding_sync_server_no_support": "Váš server nemá natívnu podporu",
+        "sliding_sync_server_no_support": "Váš server nemá podporu",
         "under_active_development": "V štádiu aktívneho vývoja.",
         "unrealiable_e2e": "Nespoľahlivé v šifrovaných miestnostiach",
         "video_rooms": "Video miestnosti",
@@ -1440,6 +1588,8 @@
         "last_person_warning": "Ste tu jediný človek. Ak odídete, nikto sa už v budúcnosti nebude môcť pripojiť do tejto miestnosti, vrátane vás.",
         "leave_room_question": "Ste si istí, že chcete opustiť miestnosť '%(roomName)s'?",
         "leave_space_question": "Ste si istí, že chcete opustiť priestor '%(spaceName)s'?",
+        "room_leave_admin_warning": "Ste jediný správca v tejto miestnosti. Ak odídete, nikto nebude môcť zmeniť nastavenia miestnosti alebo podniknúť ďalšie dôležité kroky.",
+        "room_leave_mod_warning": "Ste jediným moderátorom v tejto miestnosti. Ak odídete, nikto nebude môcť zmeniť nastavenia miestnosti alebo podniknúť ďalšie dôležité kroky.",
         "room_rejoin_warning": "Toto nie je verejne dostupná miestnosť. Bez pozvánky nebudete do nej môcť vstúpiť znovu.",
         "space_rejoin_warning": "Tento priestor nie je verejný. Bez pozvánky sa doň nebudete môcť opätovne pripojiť."
     },
@@ -1497,12 +1647,20 @@
         "toggle_attribution": "Prepínanie atribútu"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s člen",
+            "few": "%(count)s členovia",
+            "other": "%(count)s členov"
+        },
         "filter_placeholder": "Filtrovať členov v miestnosti",
         "invite_button_no_perms_tooltip": "Nemáte oprávnenie pozývať používateľov",
+        "invited_label": "Pozvaní",
+        "no_matches": "Žiadne zhody",
         "power_label": "%(userName)s (oprávnenie %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Členovia miestnosti",
     "message_edit_dialog_title": "Úpravy správy",
+    "migrating_crypto": "Ešte to vydržte. Aktualizujeme %(brand)s, aby bolo šifrovanie rýchlejšie a spoľahlivejšie.",
     "mobile_guide": {
         "toast_accept": "Použiť aplikáciu",
         "toast_description": "%(brand)s je v mobilnej verzii experimentálny. Ak chcete získať lepší zážitok a najnovšie funkcie, použite našu bezplatnú natívnu aplikáciu.",
@@ -1520,6 +1678,7 @@
         "class_global": "Celosystémové",
         "class_other": "Ďalšie",
         "default": "Predvolené",
+        "default_settings": "Zhoda s predvolenými nastaveniami",
         "email_pusher_app_display_name": "Emailové oznámenia",
         "enable_prompt_toast_description": "Povoliť oznámenia na ploche",
         "enable_prompt_toast_title": "Oznámenia",
@@ -1527,12 +1686,19 @@
         "error_change_title": "Upraviť nastavenia upozornení",
         "keyword": "Kľúčové slovo",
         "keyword_new": "Nové kľúčové slovo",
+        "level_activity": "Aktivita",
+        "level_highlight": "Zvýrazniť",
+        "level_muted": "Stlmené",
+        "level_none": "Žiadne",
+        "level_notification": "Oznámenie",
+        "level_unsent": "Neodoslané",
         "mark_all_read": "Označiť všetko ako prečítané",
         "mentions_and_keywords": "@zmienky a kľúčové slová",
         "mentions_and_keywords_description": "Dostávajte upozornenia len na zmienky a kľúčové slová nastavené vo vašich <a>nastaveniach</a>",
         "mentions_keywords": "Zmienky a kľúčové slová",
         "message_didnt_send": "Správa sa neodoslala. Kliknite pre informácie.",
-        "mute_description": "Nebudete dostávať žiadne oznámenia"
+        "mute_description": "Nebudete dostávať žiadne oznámenia",
+        "mute_room": "Stlmiť miestnosť"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s žiada o overenie"
@@ -1578,10 +1744,12 @@
         "total_decryption_errors": "Z dôvodu chýb v dešifrovaní sa niektoré hlasy nemusia započítať",
         "total_n_votes": {
             "one": "%(count)s odovzdaný hlas. Hlasujte a pozrite si výsledky",
+            "few": "%(count)s odovzdané hlasy. Hlasujte a pozrite si výsledky",
             "other": "%(count)s odovzdaných hlasov. Hlasujte a pozrite si výsledky"
         },
         "total_n_votes_voted": {
             "one": "Na základe %(count)s hlasu",
+            "few": "Na základe %(count)s hlasov",
             "other": "Na základe %(count)s hlasov"
         },
         "total_no_votes": "Žiadne odovzdané hlasy",
@@ -1634,11 +1802,6 @@
         "ongoing": "Odstraňovanie…",
         "reason_label": "Dôvod (voliteľný)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Ste si istí, že chcete odmietnuť toto pozvanie?",
-        "failed": "Nepodarilo sa odmietnuť pozvanie",
-        "title": "Odmietnuť pozvanie"
-    },
     "report_content": {
         "description": "Nahlásenie tejto správy odošle jej jedinečné \"ID udalosti\" správcovi vášho domovského servera. Ak sú správy v tejto miestnosti zašifrované, správca domovského servera nebude môcť prečítať text správy ani zobraziť žiadne súbory alebo obrázky.",
         "disagree": "Nesúhlasím",
@@ -1661,54 +1824,87 @@
         "spam_or_propaganda": "Spam alebo propaganda",
         "toxic_behaviour": "Nebezpečné správanie"
     },
+    "report_room": {
+        "description": "Nahláste túto miestnosť poskytovateľovi účtu. Ak sú správy zašifrované, váš správca ich nebude môcť prečítať.",
+        "reason_label": "Popíšte dôvod"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Nepodarilo sa dešifrovať %(failedCount)s relácií!",
         "count_of_successfully_restored_keys": "Úspešne obnovených %(sessionCount)s kľúčov",
-        "enter_key_description": "Získajte prístup k histórii zabezpečených správ a nastavte bezpečné zasielanie správ zadaním bezpečnostného kľúča.",
-        "enter_key_title": "Zadajte bezpečnostný kľúč",
+        "enter_key_description": "Získajte prístup k histórii zabezpečených správ a nastavte bezpečné zasielanie správ zadaním kľúča na obnovenie.",
+        "enter_key_title": "Zadajte kľúč na obnovenie",
         "enter_phrase_description": "Získajte prístup k histórii zabezpečených správ a nastavte bezpečné zasielanie správ zadaním bezpečnostnej frázy.",
         "enter_phrase_title": "Zadať bezpečnostnú frázu",
         "incorrect_security_phrase_dialog": "Zálohovanie nebolo možné dešifrovať pomocou tejto bezpečnostnej frázy: overte, či ste zadali správnu bezpečnostnú frázu.",
         "incorrect_security_phrase_title": "Nesprávna bezpečnostná fráza",
         "key_backup_warning": "<b>Varovanie</b>: zálohovanie šifrovacích kľúčov by ste mali nastavovať na dôveryhodnom počítači.",
         "key_fetch_in_progress": "Získavanie kľúčov zo servera…",
-        "key_forgotten_text": "Ak ste zabudli bezpečnostný kľúč, môžete <button>nastaviť nové možnosti obnovenia</button>",
-        "key_is_invalid": "Neplatný bezpečnostný kľúč",
-        "key_is_valid": "Toto vyzerá ako platný bezpečnostný kľúč!",
+        "key_forgotten_text": "Ak ste zabudli kľúč na obnovenie, môžete <button>nastaviť nové možnosti obnovenia</button>",
+        "key_is_invalid": "Neplatný kľúč na obnovenie",
+        "key_is_valid": "Toto vyzerá ako platný kľúč na obnovenie",
         "keys_restored_title": "Kľúče obnovené",
         "load_error_content": "Nie je možné načítať stav zálohy",
         "load_keys_progress": "%(completed)s z %(total)s obnovených kľúčov",
         "no_backup_error": "Nebola nájdená žiadna záloha!",
-        "phrase_forgotten_text": "Ak ste zabudli svoju bezpečnostnú frázu, môžete <button1>použiť bezpečnostný kľúč</button1> alebo <button2>nastaviť nové možnosti obnovenia</button2>",
-        "recovery_key_mismatch_description": "Zálohovanie nebolo možné dešifrovať pomocou tohto bezpečnostného kľúča: overte, či ste zadali správny bezpečnostný kľúč.",
-        "recovery_key_mismatch_title": "Nezhoda bezpečnostných kľúčov",
+        "phrase_forgotten_text": "Ak ste zabudli svoju bezpečnostnú frázu, môžete <button1>použiť kľúč na obnovenie</button1> alebo <button2>nastaviť nové možnosti obnovenia</button2>",
+        "recovery_key_mismatch_description": "Zálohovanie nebolo možné dešifrovať pomocou tohto kľúča na obnovenie: overte, či ste zadali správny kľúč na obnovenie.",
+        "recovery_key_mismatch_title": "Nezhoda kľúča na obnovenie",
         "restore_failed_error": "Nie je možné obnoviť zo zálohy"
     },
     "right_panel": {
-        "add_integrations": "Pridať widgety, premostenia a boty",
+        "add_integrations": "Pridať rozšírenia",
+        "add_topic": "Pridať tému",
+        "extensions_button": "Rozšírenia",
+        "extensions_empty_description": "Vyberte možnosť „%(addIntegrations)s“ pre prehliadanie a pridanie rozšírení do tejto miestnosti",
+        "extensions_empty_title": "Zvýšte produktivitu pomocou ďalších nástrojov, widgetov a botov",
         "files_button": "Súbory",
         "pinned_messages": {
+            "empty_description": "Vyberte správu a vyberte \"%(pinAction)s\", aby ste ju sem pridali.",
+            "empty_title": "Pripnite dôležité správy, aby sa dali ľahko nájsť",
+            "header": {
+                "one": "1 pripnutá správa",
+                "few": "%(count)s pripnuté správy",
+                "other": "%(count)s pripnutých správ"
+            },
             "limits": {
+                "one": "Môžete pripnúť iba %(count)s widget",
+                "few": "Môžete pripnúť iba %(count)s widgety",
                 "other": "Môžete pripnúť iba %(count)s widgetov"
-            }
+            },
+            "menu": "Otvoriť ponuku",
+            "release_announcement": {
+                "close": "Ok",
+                "description": "Všetky pripnuté správy nájdete tu. Prejdite na ľubovoľnú správu a výberom možnosti \"Pripnúť\" ju pridajte.",
+                "title": "Všetky nové pripnuté správy"
+            },
+            "reply_thread": "Odpoveď na <link>vlákno správy</link>",
+            "unpin_all": {
+                "button": "Zrušiť pripnutie všetkých správ",
+                "content": "Uistite sa, že naozaj chcete odstrániť všetky pripnuté správy. Túto akciu nie je možné vrátiť späť.",
+                "title": "Zrušiť pripnutie všetkých správ?"
+            },
+            "view": "Zobraziť na časovej osi"
         },
-        "pinned_messages_button": "Pripnuté",
+        "pinned_messages_button": "Pripnuté správy",
         "poll": {
             "active_heading": "Aktívne ankety",
             "empty_active": "V tejto miestnosti nie sú žiadne aktívne ankety",
             "empty_active_load_more": "Nie sú aktívne žiadne ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
             "empty_active_load_more_n_days": {
                 "one": "Za uplynulý deň nie sú žiadne aktívne ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
+                "few": "Za posledné %(count)s dni nie sú aktívne žiadne ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
                 "other": "Za posledných %(count)s dní nie sú aktívne žiadne ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace"
             },
             "empty_past": "V tejto miestnosti nie sú žiadne predchádzajúce ankety",
             "empty_past_load_more": "Nie sú žiadne predchádzajúce ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
             "empty_past_load_more_n_days": {
                 "one": "Za posledný deň nie sú k dispozícii žiadne minulé ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
+                "few": "Za posledné %(count)s dni nie sú žiadne predchádzajúce ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace",
                 "other": "Za posledných %(count)s dní nie sú žiadne predchádzajúce ankety. Načítaním ďalších ankiet zobrazíte ankety za predchádzajúce mesiace"
             },
             "final_result": {
                 "one": "Konečný výsledok na základe %(count)s hlasu",
+                "few": "Konečný výsledok na základe %(count)s hlasov",
                 "other": "Konečný výsledok na základe %(count)s hlasov"
             },
             "load_more": "Načítať ďalšie ankety",
@@ -1717,7 +1913,7 @@
             "view_in_timeline": "Zobraziť anketu na časovej osi",
             "view_poll": "Zobraziť anketu"
         },
-        "polls_button": "História ankety",
+        "polls_button": "Ankety",
         "room_summary_card": {
             "title": "Informácie o miestnosti"
         },
@@ -1746,6 +1942,7 @@
             "forget": "Zabudnúť miestnosť",
             "low_priority": "Nízka priorita",
             "mark_read": "Označiť ako prečítané",
+            "mark_unread": "Označiť ako neprečítané",
             "notifications_default": "Rovnaké ako predvolené nastavenie",
             "notifications_mute": "Stlmiť miestnosť",
             "title": "Možnosti miestnosti",
@@ -1776,10 +1973,12 @@
         "error_jump_to_date_title": "Nie je možné nájsť udalosť k danému dátumu",
         "face_pile_summary": {
             "one": "%(count)s človek, ktorého poznáte, sa už pripojil",
+            "few": "%(count)s ľudia, ktorých poznáte, sa už pripojili",
             "other": "%(count)s ľudí, ktorých poznáte, sa už pripojilo"
         },
         "face_pile_tooltip_label": {
             "one": "Zobraziť 1 člena",
+            "few": "Zobraziť všetkých %(count)s členov",
             "other": "Zobraziť všetkých %(count)s členov"
         },
         "face_pile_tooltip_shortcut": "Vrátane %(commaSeparatedMembers)s",
@@ -1795,6 +1994,8 @@
             },
             "room_is_public": "Táto miestnosť je verejná"
         },
+        "header_avatar_open_settings_label": "Otvoriť nastavenia miestnosti",
+        "header_face_pile_tooltip": "Ľudia",
         "header_untrusted_label": "Nedôveryhodný",
         "inaccessible": "Táto miestnosť alebo priestor nie je momentálne prístupná.",
         "inaccessible_name": "%(roomName)s nie je momentálne prístupná.",
@@ -1818,7 +2019,6 @@
             "you_created": "Túto miestnosť ste vytvorili vy."
         },
         "invite_email_mismatch_suggestion": "Ak chcete dostávať pozvánky priamo v %(brand)s, zdieľajte tento e-mail v Nastaveniach.",
-        "invite_reject_ignore": "Odmietnuť a ignorovať používateľa",
         "invite_sent_to_email": "Táto pozvánka bola odoslaná na %(email)s",
         "invite_sent_to_email_room": "Táto pozvánka do %(roomName)s bola odoslaná na %(email)s",
         "invite_subtitle": "<userName/> vás pozval/a",
@@ -1864,11 +2064,25 @@
         "not_found_title": "Táto miestnosť alebo priestor neexistuje.",
         "not_found_title_name": "%(roomName)s neexistuje.",
         "peek_join_prompt": "Zobrazujete náhľad %(roomName)s. Chcete sa k nej pripojiť?",
+        "pinned_message_badge": "Pripnutá správa",
+        "pinned_message_banner": {
+            "button_close_list": "Zatvoriť zoznam",
+            "button_view_all": "Zobraziť všetko",
+            "description": "Táto miestnosť má pripnuté správy. Kliknutím ich zobrazíte.",
+            "go_to_message": "Zobraziť pripnuté správy na časovej osi.",
+            "title": "<bold>%(index)s z %(length)s</bold> pripnutých správ"
+        },
         "read_topic": "Kliknutím si prečítate tému",
         "rejecting": "Odmietnutie pozvania …",
         "rejoin_button": "Znovu sa pripojiť",
         "search": {
             "all_rooms_button": "Vyhľadávať vo všetkých miestnostiach",
+            "placeholder": "Hľadať správy…",
+            "summary": {
+                "one": "Našiel sa 1 výsledok pre \"<query/>\"",
+                "few": "Našli sa %(count)s výsledky pre \"<query/>\"",
+                "other": "Našlo sa %(count)s výsledkov pre \"<query/>\""
+            },
             "this_room_button": "Vyhľadávať v tejto miestnosti"
         },
         "status_bar": {
@@ -1886,6 +2100,7 @@
         "unknown_status_code_for_timeline_jump": "neznámy kód stavu",
         "unread_notifications_predecessor": {
             "one": "V predchádzajúcej verzii tejto miestnosti máte %(count)s neprečítané oznámenie.",
+            "few": "V predchádzajúcej verzii tejto miestnosti máte %(count)s neprečítané oznámenia.",
             "other": "V predchádzajúcej verzii tejto miestnosti máte %(count)s neprečítaných oznámení."
         },
         "upgrade_error_description": "Uistite sa, že domovský server podporuje zvolenú verziu miestnosti a skúste znovu.",
@@ -1896,43 +2111,99 @@
         "upgrade_warning_bar_upgraded": "Táto miestnosť už bola aktualizovaná.",
         "upload": {
             "uploading_multiple_file": {
-                "other": "Nahrávanie %(filename)s a %(count)s ďalších súborov",
-                "one": "Nahrávanie %(filename)s a %(count)s ďalší súbor"
+                "one": "Nahrávanie %(filename)s a %(count)s ďalší súbor",
+                "few": "Nahrávanie %(filename)s a %(count)s ďalšie súbory",
+                "other": "Nahrávanie %(filename)s a %(count)s ďalších súborov"
             },
             "uploading_single_file": "Nahrávanie %(filename)s"
         },
+        "video_room": "Táto miestnosť je video miestnosť",
         "waiting_for_join_subtitle": "Keď sa pozvaní používatelia pripoja k aplikácii %(brand)s, budete môcť konverzovať a miestnosť bude end-to-end šifrovaná",
         "waiting_for_join_title": "Čaká sa na používateľov, kým sa pripoja k aplikácii %(brand)s"
     },
     "room_list": {
         "add_room_label": "Pridať miestnosť",
         "add_space_label": "Pridať priestor",
+        "appearance": "Vzhľad",
         "breadcrumbs_empty": "Žiadne nedávno navštívené miestnosti",
         "breadcrumbs_label": "Nedávno navštívené miestnosti",
+        "empty": {
+            "no_chats": "Zatiaľ žiadne konverzácie",
+            "no_chats_description": "Začnite tým, že niekomu napíšete správu alebo vytvoríte miestnosť",
+            "no_chats_description_no_room_rights": "Začnite tým, že niekomu napíšete správu",
+            "no_favourites": "Zatiaľ nemáte obľúbenú konverzáciu",
+            "no_favourites_description": "V nastaveniach konverzácií môžete pridať konverzáciu medzi obľúbené",
+            "no_invites": "Nemáte žiadne neprečítané pozvánky",
+            "no_mentions": "Nemáte žiadne neprečítané zmienky",
+            "no_people": "Zatiaľ s nikým nemáte priame konverzácie",
+            "no_people_description": "Môžete zrušiť výber filtrov, aby ste videli svoje ostatné konverzácie",
+            "no_rooms": "Zatiaľ ešte nie ste v žiadnej miestnosti",
+            "no_rooms_description": "Môžete zrušiť výber filtrov, aby ste videli svoje ostatné konverzácie",
+            "no_unread": "Gratulujeme! Nemáte žiadne neprečítané správy",
+            "show_activity": "Zobraziť všetku aktivitu",
+            "show_chats": "Zobraziť všetky konverzácie"
+        },
         "failed_add_tag": "Miestnosti sa nepodarilo pridať značku %(tagName)s",
         "failed_remove_tag": "Z miestnosti sa nepodarilo odstrániť značku %(tagName)s",
         "failed_set_dm_tag": "Nepodarilo sa nastaviť značku priamej správy",
+        "filters": {
+            "favourite": "Obľúbené",
+            "invites": "Pozvánky",
+            "mentions": "Zmienky",
+            "people": "Ľudia",
+            "rooms": "Miestnosti",
+            "unread": "Neprečítané"
+        },
         "home_menu_label": "Možnosti domovskej obrazovky",
         "join_public_room_label": "Pripojiť sa k verejnej miestnosti",
         "joining_rooms_status": {
-            "other": "Momentálne ste pripojení k %(count)s miestnostiam",
-            "one": "Momentálne ste pripojení k %(count)s miestnosti"
+            "one": "Momentálne ste pripojení k %(count)s miestnosti",
+            "few": "Momentálne ste pripojení k %(count)s miestnostiam",
+            "other": "Momentálne ste pripojení k %(count)s miestnostiam"
+        },
+        "list_title": "Zoznam miestností",
+        "more_options": {
+            "copy_link": "Kopírovať odkaz na miestnosť",
+            "favourited": "Obľúbené",
+            "leave_room": "Opustiť miestnosť",
+            "low_priority": "Nízka priorita",
+            "mark_read": "Označiť ako prečítané",
+            "mark_unread": "Označiť ako neprečítané"
         },
         "notification_options": "Možnosti oznámenia",
+        "open_space_menu": "Otvoriť ponuku priestoru",
+        "primary_filters": "Filtre zoznamu miestností",
         "redacting_messages_status": {
             "one": "V súčasnosti sa odstraňujú správy v %(count)s miestnosti",
+            "few": "V súčasnosti sa odstraňujú správy v %(count)s miestnostiach",
             "other": "V súčasnosti sa odstraňujú správy v %(count)s miestnostiach"
         },
+        "room": {
+            "more_options": "Viac možností",
+            "open_room": "Otvoriť miestnosť %(roomName)s"
+        },
+        "room_options": "Možnosti miestnosti",
         "show_less": "Zobraziť menej",
+        "show_message_previews": "Zobraziť náhľady správ",
         "show_n_more": {
-            "one": "Zobraziť %(count)s viac",
-            "other": "Zobraziť %(count)s viac"
+            "one": "Zobraziť %(count)s ďalšiu",
+            "few": "Zobraziť %(count)s ďalšie",
+            "other": "Zobraziť %(count)s ďalších"
         },
         "show_previews": "Zobraziť náhľady správ",
+        "sort": "Zoradiť",
         "sort_by": "Zoradiť podľa",
         "sort_by_activity": "Aktivity",
         "sort_by_alphabet": "A-Z",
+        "sort_type": {
+            "activity": "Aktivita",
+            "atoz": "A-Z"
+        },
         "sort_unread_first": "Najprv ukázať miestnosti s neprečítanými správami",
+        "space_menu": {
+            "home": "Domov priestoru",
+            "space_settings": "Nastavenia priestoru"
+        },
         "space_menu_label": "%(spaceName)s ponuka",
         "sublist_options": "Možnosti zoznamu",
         "suggested_rooms_heading": "Navrhované miestnosti"
@@ -2004,6 +2275,8 @@
             "error_deleting_alias_description": "Pri odstraňovaní tejto adresy došlo k chybe. Možno už neexistuje alebo došlo k dočasnej chybe.",
             "error_deleting_alias_description_forbidden": "Na vymazanie adresy nemáte povolenie.",
             "error_deleting_alias_title": "Chyba pri odstraňovaní adresy",
+            "error_publishing": "Nepodarilo sa zverejniť miestnosť",
+            "error_publishing_detail": "Vyskytla sa chyba pri zverejnení tejto miestnosti",
             "error_save_space_settings": "Nepodarilo sa uložiť nastavenia priestoru.",
             "error_updating_alias_description": "Pri aktualizácii alternatívnych adries miestnosti došlo k chybe. Nemusí to byť povolené serverom alebo došlo k dočasnému zlyhaniu.",
             "error_updating_canonical_alias_description": "Pri aktualizácii hlavnej adresy miestnosti došlo k chybe. Server to nemusí povoliť alebo došlo k dočasnému zlyhaniu.",
@@ -2141,6 +2414,7 @@
             },
             "join_rule_restricted_summary": {
                 "one": "V súčasnosti má priestor prístup",
+                "few": "V súčasnosti majú prístup %(count)s priestory",
                 "other": "V súčasnosti má prístup %(count)s priestorov"
             },
             "join_rule_restricted_upgrade_description": "Táto aktualizácia umožní členom vybraných priestorov prístup do tejto miestnosti bez pozvánky.",
@@ -2149,17 +2423,19 @@
             "join_rule_upgrade_required": "Vyžaduje sa aktualizácia",
             "join_rule_upgrade_sending_invites": {
                 "one": "Odosielanie pozvánky...",
+                "few": "Odosielanie pozvánok... (%(progress)s z %(count)s)",
                 "other": "Odosielanie pozvánok... (%(progress)s z %(count)s)"
             },
             "join_rule_upgrade_updating_spaces": {
                 "one": "Aktualizácia priestoru...",
+                "few": "Aktualizácia priestorov... (%(progress)s z %(count)s)",
                 "other": "Aktualizácia priestorov... (%(progress)s z %(count)s)"
             },
             "join_rule_upgrade_upgrading_room": "Aktualizácia miestnosti",
             "public_without_alias_warning": "Ak chcete prepojiť túto miestnosť, pridajte prosím adresu.",
             "publish_room": "Zviditeľniť túto miestnosť v adresári verejných miestností.",
             "publish_space": "Zviditeľniť tento priestor v adresári verejných miestností.",
-            "strict_encryption": "Nikdy neposielať šifrované správy neovereným reláciám v tejto miestnosti z tejto relácie",
+            "strict_encryption": "Posielať správy iba overeným používateľom.",
             "title": "Bezpečnosť a súkromie"
         },
         "title": "Nastavenia miestnosti - %(roomName)s",
@@ -2213,6 +2489,10 @@
         "recent_changes_heading": "Nedávne zmeny, ktoré ešte neboli prijaté",
         "title": "Server neodpovedá"
     },
+    "service_worker_error": {
+        "description": "%(brand)s vyžaduje proces služby na načítanie overených médií z úložísk obsahu Matrix. Váš prehliadač to nepodporuje, takže sa môže vyskytnúť zlyhanie načítania médií.",
+        "title": "Nepodarilo sa načítať proces služby"
+    },
     "seshat": {
         "error_initialising": "Inicializácia vyhľadávania správ zlyhala, pre viac informácií skontrolujte <a>svoje nastavenia</a>",
         "reset_button": "Obnoviť úložisko udalostí",
@@ -2230,6 +2510,7 @@
             "brand_version": "Verzia %(brand)s:",
             "clear_cache_reload": "Vymazať vyrovnávaciu pamäť a načítať znovu",
             "crypto_version": "Krypto verzia:",
+            "dialog_title": "<strong>Nastavenia: </strong> Pomocník a o aplikácii",
             "help_link": "Pomoc pri používaní %(brand)s môžete získať kliknutím <a>sem</a>.",
             "homeserver": "Domovský server je <code>%(homeserverUrl)s</code>",
             "identity_server": "Server identity je <code>%(identityServerUrl)s</code>",
@@ -2238,18 +2519,30 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Nastavenia:</strong> Účet",
+            "title": "Účet"
+        },
         "all_rooms_home": "Zobraziť všetky miestnosti v časti Domov",
         "all_rooms_home_description": "Všetky miestnosti, v ktorých sa nachádzate, sa zobrazia na domovskej obrazovke.",
         "always_show_message_timestamps": "Vždy zobrazovať časovú značku správ",
         "appearance": {
             "bundled_emoji_font": "Použiť pribalené písmo emotikonov",
+            "compact_layout": "Zobraziť kompaktný text a správy",
+            "compact_layout_description": "Na použitie tejto funkcie je potrebné zvoliť moderné rozloženie.",
             "custom_font": "Použiť systémové písmo",
             "custom_font_description": "Nastavte názov písma, ktoré máte nainštalované na vašom systéme & %(brand)s sa ho pokúsi použiť.",
-            "custom_font_name": "Meno systémového písma",
+            "custom_font_name": "Názov systémového písma",
             "custom_font_size": "Použiť vlastnú veľkosť",
-            "custom_theme_error_downloading": "Chyba pri stiahnutí informácií o vzhľade.",
+            "custom_theme_add": "Pridať vlastnú tému",
+            "custom_theme_downloading": "Sťahovanie vlastnej témy...",
+            "custom_theme_error_downloading": "Chyba pri sťahovaní témy",
+            "custom_theme_help": "Zadajte adresu URL vlastnej témy, ktorú chcete použiť.",
             "custom_theme_invalid": "Neplatná schéma vzhľadu.",
+            "dialog_title": "<strong>Nastavenia:</strong> Vzhľad",
             "font_size": "Veľkosť písma",
+            "font_size_default": "%(fontSize)s (predvolené)",
+            "high_contrast": "Vysoký kontrast",
             "image_size_default": "Predvolené",
             "image_size_large": "Veľký",
             "layout_bubbles": "Správy v bublinách",
@@ -2264,9 +2557,80 @@
         "code_block_expand_default": "Rozšíriť bloky kódu predvolene",
         "code_block_line_numbers": "Zobrazenie čísel riadkov v blokoch kódu",
         "disable_historical_profile": "Zobraziť aktuálny profilový obrázok a meno používateľov v histórii správ",
+        "discovery": {
+            "title": "Ako vás nájsť"
+        },
         "emoji_autocomplete": "Umožniť automatické návrhy emotikonov počas písania",
         "enable_markdown": "Povoliť funkciu Markdown",
         "enable_markdown_description": "Začnite správy s <code>/plain</code> na odoslanie bez použitia markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Podrobnosti o vašom účte, kontakty, predvoľby a zoznam konverzácií sa zachovajú",
+                "breadcrumb_page": "Obnoviť šifrovanie",
+                "breadcrumb_second_description": "Stratíte históriu správ, ktorá je uložená iba na serveri",
+                "breadcrumb_third_description": "Budete musieť znova overiť všetky vaše existujúce zariadenia a kontakty",
+                "breadcrumb_title": "Naozaj chcete obnoviť svoju identitu?",
+                "breadcrumb_title_forgot": "Zabudli ste kľúč na obnovenie? Budete musieť obnoviť svoju identitu.",
+                "breadcrumb_title_sync_failed": "Synchronizácia úložiska kľúčov zlyhala. Musíte obnoviť svoju identitu.",
+                "breadcrumb_warning": "Urobte to iba vtedy, ak sa domnievate, že váš účet bol ohrozený.",
+                "details_title": "Podrobnosti o šifrovaní",
+                "do_not_close_warning": "Nezatvárajte toto okno, kým sa obnovenie nedokončí",
+                "export_keys": "Exportovať kľúče",
+                "import_keys": "Importovať kľúče",
+                "other_people_device_description": "Upozornenie: používatelia, ktorí sa s vami výslovne neoverili (napr. pomocou emotikonov), nedostanú vaše šifrované správy. Neoverené zariadenia overených používateľov tiež nedostanú vaše šifrované správy.",
+                "other_people_device_label": "V šifrovaných miestnostiach odosielajte správy iba overeným používateľom",
+                "other_people_device_title": "Zariadenia iných ľudí",
+                "reset_identity": "Obnoviť kryptografickú identitu",
+                "reset_in_progress": "Prebieha obnovenie...",
+                "session_id": "ID relácie:",
+                "session_key": "Kľúč relácie:",
+                "title": "Pokročilé"
+            },
+            "confirm_key_storage_off": "Naozaj chcete ponechať úložisko kľúčov vypnuté?",
+            "confirm_key_storage_off_description": "Ak sa zo všetkých zariadení odhlásite, stratíte históriu správ a budete musieť znova overiť všetky existujúce kontakty. <a>Prečítajte si viac </a>",
+            "delete_key_storage": {
+                "breadcrumb_page": "Odstrániť úložisko kľúčov",
+                "confirm": "Odstrániť úložisko kľúčov",
+                "description": "Odstránením úložiska kľúčov sa odstráni vaša kryptografická identita a kľúče správ zo servera a vypnú sa nasledujúce bezpečnostné funkcie:",
+                "list_first": "Na nových zariadeniach nebudete mať zašifrovanú históriu správ",
+                "list_second": "Stratíte prístup k svojim zašifrovaným správam, ak sa odhlásite z aplikácie %(brand)s všade",
+                "title": "Naozaj chcete vypnúť úložisko kľúčov a odstrániť ho?"
+            },
+            "device_not_verified_button": "Overiť toto zariadenie",
+            "device_not_verified_description": "Ak chcete zobraziť nastavenia šifrovania, musíte toto zariadenie overiť.",
+            "device_not_verified_title": "Zariadenie nie je overené",
+            "dialog_title": "<strong>Nastavenia:</strong> Šifrovanie",
+            "key_storage": {
+                "allow_key_storage": "Povoliť úložisko kľúčov",
+                "description": "Uložte svoju kryptografickú totožnosť a kľúče správ bezpečne na serveri. To vám umožní zobraziť históriu správ na všetkých nových zariadeniach. <a>Zistiť viac</a>",
+                "title": "Úložisko kľúčov"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Potvrdiť nový kľúč na obnovenie",
+                "change_recovery_confirm_description": "Dokončite zadaním nového kľúča na obnovenie nižšie. Váš starý už nebude fungovať.",
+                "change_recovery_confirm_title": "Zadajte svoj nový kľúč na obnovenie",
+                "change_recovery_key": "Zmeniť kľúč na obnovenie",
+                "change_recovery_key_description": "Zapíšte si tento nový kľúč na obnovenie niekde na bezpečné miesto. Potom kliknite na tlačidlo Pokračovať a potvrďte zmenu.",
+                "change_recovery_key_title": "Zmeniť kľúč na obnovenie?",
+                "description": "Ak ste stratili všetky existujúce zariadenia, obnovte svoju kryptografickú identitu a históriu správ pomocou kľúča na obnovenie.",
+                "enter_key_error": "Zadaný kľúč na obnovenie nie je správny.",
+                "enter_recovery_key": "Zadajte kľúč na obnovenie",
+                "forgot_recovery_key": "Zabudli ste kľúč na obnovenie?",
+                "key_storage_warning": "Vaše úložisko kľúčov nie je synchronizované. Kliknutím na tlačidlo nižšie vyriešite problém.",
+                "save_key_description": "Nezdieľajte ho s nikým!",
+                "save_key_title": "Kľúč na obnovenie",
+                "set_up_recovery": "Nastaviť obnovenie",
+                "set_up_recovery_confirm_button": "Dokončiť nastavenie",
+                "set_up_recovery_confirm_description": "Zadajte kľúč na obnovenie zobrazený na predchádzajúcej obrazovke a dokončite nastavenie obnovy.",
+                "set_up_recovery_confirm_title": "Na potvrdenie zadajte kľúč na obnovenie",
+                "set_up_recovery_description": "Vaše úložisko kľúčov je chránené kľúčom na obnovenie. Ak po nastavení potrebujete nový kľúč na obnovenie, môžete ho znova vytvoriť výberom „%(changeRecoveryKeyButton)s“.",
+                "set_up_recovery_save_key_description": "Zapíšte si tento kľúč na obnovenie na bezpečné miesto, napríklad do správcu hesiel, zašifrovanej poznámky alebo fyzického trezoru.",
+                "set_up_recovery_save_key_title": "Uložte kľúč na obnovenie na bezpečné miesto",
+                "set_up_recovery_secondary_description": "Po kliknutí na Pokračovať vám vygenerujeme kľúč na obnovenie.",
+                "title": "Obnovenie"
+            },
+            "title": "Šifrovanie"
+        },
         "general": {
             "account_management_section": "Správa účtu",
             "account_section": "Účet",
@@ -2279,6 +2643,14 @@
             "add_msisdn_dialog_title": "Pridať telefónne číslo",
             "add_msisdn_instructions": "SMSka vám bola zaslaná na +%(msisdn)s. Zadajte prosím overovací kód, ktorý obsahuje.",
             "add_msisdn_misconfigured": "Pridanie / prepojenie s MSISDN je nesprávne nakonfigurované",
+            "allow_spellcheck": "Povoliť kontrolu pravopisu",
+            "application_language": "Jazyk aplikácie",
+            "application_language_reload_hint": "Po výbere iného jazyka sa aplikácia znova načíta",
+            "avatar_remove_progress": "Odstraňuje sa obrázok...",
+            "avatar_save_progress": "Nahráva sa obrázok...",
+            "avatar_upload_error_text": "Formát súboru nie je podporovaný alebo obrázok je väčší ako %(size)s.",
+            "avatar_upload_error_text_generic": "Formát súboru nemusí byť podporovaný.",
+            "avatar_upload_error_title": "Profilový obrázok sa nepodarilo nahrať",
             "confirm_adding_email_body": "Kliknutím na tlačidlo nižšie potvrdíte pridanie emailovej adresy.",
             "confirm_adding_email_title": "Potvrdiť pridanie emailu",
             "deactivate_confirm_body": "Ste si istí, že chcete deaktivovať svoje konto? Je to nezvratné.",
@@ -2294,10 +2666,14 @@
             "deactivate_confirm_erase_label": "Skryť moje správy pred novými členmi",
             "deactivate_section": "Deaktivovať účet",
             "deactivate_warning": "Deaktivácia účtu je trvalý úkon — buďte opatrní!",
-            "discovery_email_empty": "Možnosti nastavenia verejného profilu sa objavia po pridaní e-mailovej adresy vyššie.",
+            "discovery_email_empty": "Možnosti vyhľadávania kontaktov sa zobrazia po pridaní e-mailu.",
             "discovery_email_verification_instructions": "Overte odkaz vo vašej emailovej schránke",
-            "discovery_msisdn_empty": "Možnosti nastavenia verejného profilu sa objavia po pridaní telefónneho čísla vyššie.",
-            "discovery_needs_terms": "Súhlaste s podmienkami používania servera totožností (%(serverName)s), aby ste mohli byť nájdení zadaním emailovej adresy alebo telefónneho čísla.",
+            "discovery_msisdn_empty": "Možnosti vyhľadávania kontaktov sa objavia po pridaní telefónneho čísla.",
+            "discovery_needs_terms": "Súhlaste s podmienkami používania servera totožností (%(serverName)s), aby ste mohli byť nájdený zadaním emailovej adresy alebo telefónneho čísla.",
+            "discovery_needs_terms_title": "Umožnite ľuďom, aby vás našli",
+            "display_name": "Zobrazované meno",
+            "display_name_error": "Nedá sa nastaviť zobrazované meno",
+            "email_adding_unsupported_by_hs": "Tento domovský server nepodporuje pridávanie e-mailových adries do vášho účtu.",
             "email_address_in_use": "Táto emailová adresa sa už používa",
             "email_address_label": "Emailová adresa",
             "email_not_verified": "Vaša emailová adresa nebola zatiaľ overená",
@@ -2322,7 +2698,9 @@
             "error_share_msisdn_discovery": "Nepodarilo sa zdieľanie telefónneho čísla",
             "identity_server_no_token": "Nenašiel sa prístupový token totožnosti",
             "identity_server_not_set": "Server totožnosti nie je nastavený",
-            "language_section": "Jazyk a región",
+            "invalid_phone_number": "Poskytnuté telefónne číslo sa nezdá byť platné.",
+            "language_section": "Jazyk",
+            "msisdn_adding_unsupported_by_hs": "Tento domovský server nepodporuje pridávanie telefónnych čísel do vášho účtu.",
             "msisdn_in_use": "Toto telefónne číslo sa už používa",
             "msisdn_label": "Telefónne číslo",
             "msisdn_verification_field_label": "Overovací kód",
@@ -2331,11 +2709,16 @@
             "oidc_manage_button": "Spravovať účet",
             "password_change_section": "Nastaviť nové heslo k účtu…",
             "password_change_success": "Vaše heslo bolo úspešne zmenené.",
+            "personal_info": "Osobné informácie",
+            "profile_subtitle": "Takto sa zobrazujete ostatným v aplikácii.",
+            "profile_subtitle_oidc": "Váš účet spravuje oddelene poskytovateľ identity, takže niektoré z vašich osobných údajov tu nie je možné zmeniť.",
             "remove_email_prompt": "Odstrániť adresu %(email)s?",
             "remove_msisdn_prompt": "Odstrániť číslo %(phone)s?",
-            "spell_check_locale_placeholder": "Vyberte si jazyk"
+            "spell_check_locale_placeholder": "Vyberte si jazyk",
+            "unable_to_load_emails": "Nepodarilo sa načítať e-mailové adresy",
+            "unable_to_load_msisdns": "Nie je možné načítať telefónne čísla",
+            "username": "Používateľské meno"
         },
-        "image_thumbnails": "Zobrazovať ukážky/náhľady obrázkov",
         "inline_url_previews_default": "Predvolene povoliť náhľady URL adries",
         "inline_url_previews_room": "Predvolene povoliť náhľady URL adries pre členov tejto miestnosti",
         "inline_url_previews_room_account": "Povoliť náhľady URL adries pre túto miestnosť (ovplyvňuje len vás)",
@@ -2357,21 +2740,21 @@
                 "enter_phrase_description": "Zadajte bezpečnostnú frázu, ktorú poznáte len vy, keďže sa používa na ochranu vašich údajov. V záujme bezpečnosti by ste nemali heslo k účtu používať opakovane.",
                 "enter_phrase_title": "Zadajte bezpečnostnú frázu",
                 "enter_phrase_to_confirm": "Zadajte svoju bezpečnostnú frázu znova, aby ste ju potvrdili.",
-                "generate_security_key_description": "Vygenerujeme vám bezpečnostný kľúč, ktorý si uložte na bezpečné miesto, napríklad do správcu hesiel alebo do trezoru.",
-                "generate_security_key_title": "Vygenerovať bezpečnostný kľúč",
+                "generate_security_key_description": "Vygenerujeme vám kľúč na obnovenie, ktorý si uložte na bezpečné miesto, napríklad do správcu hesiel alebo do trezoru.",
+                "generate_security_key_title": "Vygenerovať kľúč na obnovenie",
                 "pass_phrase_match_failed": "To sa nezhoduje.",
                 "pass_phrase_match_success": "Zhoda!",
                 "phrase_strong_enough": "Skvelé! Táto bezpečnostná fráza vyzerá dostatočne silná.",
                 "secret_storage_query_failure": "Nie je možné vykonať dopyt na stav tajného úložiska",
-                "security_key_safety_reminder": "Bezpečnostný kľúč uložte na bezpečné miesto, napríklad do správcu hesiel alebo trezora, pretože slúži na ochranu zašifrovaných údajov.",
+                "security_key_safety_reminder": "Kľúč na obnovenie uložte na bezpečné miesto, napríklad do správcu hesiel alebo trezora, pretože slúži na ochranu zašifrovaných údajov.",
                 "set_phrase_again": "Vráťte sa späť a nastavte to znovu.",
                 "settings_reminder": "Bezpečné zálohovanie a správu kľúčov môžete nastaviť aj v Nastaveniach.",
                 "title_confirm_phrase": "Potvrdiť bezpečnostnú frázu",
-                "title_save_key": "Uložte svoj bezpečnostný kľúč",
+                "title_save_key": "Uložte si svoj kľúč na obnovenie",
                 "title_set_phrase": "Nastaviť bezpečnostnú frázu",
                 "unable_to_setup": "Nie je možné nastaviť tajné úložisko",
                 "use_different_passphrase": "Použiť inú prístupovú frázu?",
-                "use_phrase_only_you_know": "Použite tajnú frázu, ktorú poznáte len vy, a prípadne uložte si bezpečnostný kľúč, ktorý môžete použiť na zálohovanie."
+                "use_phrase_only_you_know": "Použite tajnú frázu, ktorú poznáte len vy, a prípadne si uložte kľúč na obnovenie, ktorý môžete použiť na zálohovanie."
             }
         },
         "key_export_import": {
@@ -2389,13 +2772,29 @@
             "phrase_strong_enough": "Skvelé! Táto bezpečnostná fráza vyzerá dostatočne silná"
         },
         "keyboard": {
+            "dialog_title": "<strong>Nastavenia:</strong> Klávesnica",
             "title": "Klávesnica"
         },
+        "labs": {
+            "dialog_title": "<strong>Nastavenia:</strong> Laboratóriá"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Nastavenia:</strong> Ignorovaní používatelia"
+        },
+        "media_preview": {
+            "hide_avatars": "Skryť obrázky miestnosti a pozývateľa",
+            "hide_media": "Vždy skryť",
+            "media_preview_description": "Skryté médium sa dá vždy zobraziť ťuknutím naň",
+            "media_preview_label": "Zobraziť médiá na časovej osi",
+            "show_in_private": "V súkromných miestnostiach",
+            "show_media": "Vždy zobraziť"
+        },
         "notifications": {
             "default_setting_description": "Toto nastavenie sa predvolene použije pre všetky vaše miestnosti.",
-            "default_setting_section": "Chcem byť upozornený na (Predvolené nastavenie)",
+            "default_setting_section": "Chcem byť upozornený na (predvolené nastavenie)",
             "desktop_notification_message_preview": "Zobraziť náhľad správy v oznámení na pracovnej ploche",
-            "email_description": "Prijímajte e-mailový súhrn zmeškaných oznámení",
+            "dialog_title": "<strong>Nastavenia:</strong> Oznámenia",
+            "email_description": "Dostávajte emailový súhrn o zmeškaných oznámeniach",
             "email_section": "Emailový súhrn",
             "email_select": "Vyberte e-maily, na ktoré chcete odosielať súhrny. Spravujte svoje e-maily v <button>Všeobecných nastaveniach</button>.",
             "enable_audible_notifications_session": "Povoliť zvukové oznámenia pre túto reláciu",
@@ -2431,7 +2830,7 @@
             "quick_actions_mark_all_read": "Označiť všetky správy ako prečítané",
             "quick_actions_reset": "Obnoviť predvolené nastavenia",
             "quick_actions_section": "Rýchle akcie",
-            "room_activity": "Objavujú sa nové aktivity v miestnosti, aktualizácie a správy o stave",
+            "room_activity": "Keď sa vyskytnú nové aktivity v miestnosti, aktualizácie a správy o stave",
             "rule_call": "Pozvánka na telefonát",
             "rule_contains_display_name": "Správy obsahujúce moje zobrazované meno",
             "rule_contains_user_name": "Správy obsahujúce moje meno používateľa",
@@ -2453,12 +2852,15 @@
             "code_blocks_heading": "Bloky kódu",
             "compact_modern": "Použiť kompaktnejšie \"moderné\" usporiadanie",
             "composer_heading": "Písanie správ",
+            "default_timezone": "Predvolená (%(timezone)s) prehliadača",
+            "dialog_title": "<strong>Nastavenia:</strong> Predvoľby",
             "enable_hardware_acceleration": "Povoliť hardvérovú akceleráciu",
             "enable_tray_icon": "Zobraziť ikonu na lište a pri zatvorení minimalizovať okno na ňu",
             "keyboard_heading": "Klávesové skratky",
             "keyboard_view_shortcuts_button": "Ak chcete zobraziť všetky klávesové skratky, <a>kliknite sem</a>.",
             "media_heading": "Obrázky, GIF animácie a videá",
             "presence_description": "Zdieľajte svoju aktivitu a stav s ostatnými.",
+            "publish_timezone": "Zverejniť časové pásmo na verejnom profile",
             "rm_lifetime": "Platnosť značky Prečítané (ms)",
             "rm_lifetime_offscreen": "Platnosť značky Prečítané mimo obrazovku (ms)",
             "room_directory_heading": "Adresár miestností",
@@ -2466,59 +2868,26 @@
             "show_avatars_pills": "Zobraziť obrázky profilov v zmienkach o používateľoch, miestnostiach a udalostiach",
             "show_polls_button": "Zobraziť tlačidlo ankiet",
             "surround_text": "Obklopiť vybraný text pri písaní špeciálnych znakov",
-            "time_heading": "Zobrazovanie času"
+            "time_heading": "Zobrazovanie času",
+            "user_timezone": "Nastaviť časové pásmo"
         },
         "prompt_invite": "Upozorniť pred odoslaním pozvánok na potenciálne neplatné Matrix ID",
         "replace_plain_emoji": "Automaticky nahrádzať textové emotikony modernými",
         "security": {
-            "4s_public_key_in_account_data": "v údajoch účtu",
-            "4s_public_key_status": "Verejný kľúč bezpečného úložiska:",
             "analytics_description": "Zdieľajte anonymné údaje, ktoré nám pomôžu identifikovať problémy. Nič osobné. Žiadne tretie strany.",
-            "backup_key_cached_status": "Záložný kľúč v medzipamäti:",
-            "backup_key_stored_status": "Záložný kľúč uložený:",
-            "backup_key_unexpected_type": "neočakávaný typ",
-            "backup_key_well_formed": "správne vytvorené",
-            "backup_keys_description": "Zálohujte si šifrovacie kľúče s údajmi o účte pre prípad, že stratíte prístup k reláciám. Vaše kľúče budú zabezpečené jedinečným bezpečnostným kľúčom.",
             "bulk_options_accept_all_invites": "Prijať všetkých %(invitedRooms)s pozvaní",
             "bulk_options_reject_all_invites": "Odmietnuť všetky %(invitedRooms)s pozvania",
             "bulk_options_section": "Hromadné možnosti",
-            "cross_signing_cached": "uložené do lokálnej vyrovnávacej pamäte",
-            "cross_signing_homeserver_support": "Funkcie podporované domovským serverom:",
-            "cross_signing_homeserver_support_exists": "existuje",
-            "cross_signing_in_4s": "na bezpečnom úložisku",
-            "cross_signing_in_memory": "v pamäti",
-            "cross_signing_master_private_Key": "Hlavný súkromný kľúč:",
-            "cross_signing_not_cached": "nenájdené lokálne",
-            "cross_signing_not_found": "nenájdené",
-            "cross_signing_not_in_4s": "sa nenašiel v úložisku",
-            "cross_signing_not_stored": "neuložené",
-            "cross_signing_private_keys": "Súkromné kľúče krížového podpisovania:",
-            "cross_signing_public_keys": "Verejné kľúče krížového podpisovania:",
-            "cross_signing_self_signing_private_key": "Samopodpisujúci súkromný kľúč:",
-            "cross_signing_user_signing_private_key": "Súkromný podpisový kľúč používateľa:",
-            "cryptography_section": "Kryptografia",
-            "delete_backup": "Vymazať zálohu",
-            "delete_backup_confirm_description": "Ste si istí? Ak nemáte správne zálohované šifrovacie kľúče, prídete o históriu šifrovaných konverzácií.",
+            "dehydrated_device_description": "Funkcia offline zariadenia vám umožňuje prijímať šifrované správy, aj keď nie ste prihlásení do žiadneho zariadenia",
+            "dehydrated_device_enabled": "Funkcia offline zariadenia je povolená",
+            "dialog_title": "<strong>Nastavenia:</strong> Bezpečnosť a súkromie",
             "e2ee_default_disabled_warning": "Správca vášho servera predvolene vypol end-to-end šifrovanie v súkromných miestnostiach a v priamych správach.",
             "enable_message_search": "Povoliť vyhľadávanie správ v šifrovaných miestnostiach",
             "encryption_section": "Šifrovanie",
-            "error_loading_key_backup_status": "Nie je možné načítať stav zálohy kľúčov",
-            "export_megolm_keys": "Exportovať end-to-end šifrovacie kľúče miestnosti",
             "ignore_users_empty": "Nemáte žiadnych ignorovaných používateľov.",
             "ignore_users_section": "Ignorovaní používatelia",
-            "import_megolm_keys": "Importovať end-to-end šifrovacie kľúče miestnosti",
-            "key_backup_active": "Táto relácia zálohuje vaše kľúče.",
-            "key_backup_active_version": "Verzia aktívnej zálohy:",
-            "key_backup_active_version_none": "Žiadne",
             "key_backup_algorithm": "Algoritmus:",
-            "key_backup_can_be_restored": "Túto zálohu je možné obnoviť v tejto relácii",
-            "key_backup_complete": "Všetky kľúče sú zálohované",
             "key_backup_connect": "Pripojiť túto reláciu k Zálohe kľúčov",
-            "key_backup_connect_prompt": "Pred odhlásením pripojte túto reláciu k zálohe kľúčov, aby ste predišli strate kľúčov, ktoré môžu byť len v tejto relácii.",
-            "key_backup_in_progress": "Zálohovanie %(sessionsRemaining)s kľúčov…",
-            "key_backup_inactive": "Táto relácia <b>nezálohuje vaše kľúče</b>, ale máte jednu existujúcu zálohu, ktorú môžete obnoviť a pridať do budúcnosti.",
-            "key_backup_inactive_warning": "Vaše kľúče <b>nie sú zálohované z tejto relácie</b>.",
-            "key_backup_latest_version": "Najnovšia verzia zálohy na serveri:",
             "message_search_disable_warning": "Ak nie je povolené, správy zo zašifrovaných miestností sa nezobrazia vo výsledkoch vyhľadávania.",
             "message_search_disabled": "Bezpečne lokálne ukladať zašifrované správy do vyrovnávacej pamäte, aby sa zobrazovali vo výsledkoch vyhľadávania.",
             "message_search_enabled": {
@@ -2538,14 +2907,8 @@
             "message_search_unsupported": "%(brand)su chýbajú niektoré komponenty potrebné na bezpečné cachovanie šifrovaných správ lokálne. Pokiaľ chcete experimentovať s touto funkciou, spravte si svoj vlastný %(brand)s Desktop s <nativeLink>pridanými vyhľadávacími komponentami</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s nedokáže bezpečne lokálne ukladať do vyrovnávacej pamäte zašifrované správy, keď je spustený vo webovom prehliadači. Na zobrazenie šifrovaných správ vo výsledkoch vyhľadávania použite <desktopLink>%(brand)s Desktop</desktopLink>.",
             "record_session_details": "Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií",
-            "restore_key_backup": "Obnoviť zo zálohy",
-            "secret_storage_not_ready": "nie je pripravené",
-            "secret_storage_ready": "pripravené",
-            "secret_storage_status": "Tajné úložisko:",
             "send_analytics": "Odosielať analytické údaje",
-            "session_id": "ID relácie:",
-            "session_key": "Kľúč relácie:",
-            "strict_encryption": "Nikdy neposielať šifrované správy neovereným reláciám z tejto relácie"
+            "strict_encryption": "Posielať správy iba overeným používateľom"
         },
         "send_read_receipts": "Odosielať potvrdenia o prečítaní",
         "send_read_receipts_unsupported": "Váš server nepodporuje vypnutie odosielania potvrdení o prečítaní.",
@@ -2576,6 +2939,7 @@
             "device_unverified_description_current": "Overte svoju aktuálnu reláciu pre vylepšené bezpečné zasielanie správ.",
             "device_verified_description": "Táto relácia je pripravená na bezpečné zasielanie správ.",
             "device_verified_description_current": "Vaša aktuálna relácia je pripravená na bezpečné zasielanie správ.",
+            "dialog_title": "<strong>Nastavenia:</strong> Relácie",
             "error_pusher_state": "Nepodarilo sa nastaviť stav push oznámení",
             "error_set_name": "Nepodarilo sa nastaviť názov relácie",
             "filter_all": "Všetky",
@@ -2592,9 +2956,11 @@
             "inactive_sessions_list_description": "Zvážte odhlásenie zo starých relácií (%(inactiveAgeDays)s dní alebo starších), ktoré už nepoužívate.",
             "ip": "IP adresa",
             "last_activity": "Posledná aktivita",
+            "manage": "Spravovať túto reláciu",
             "mobile_session": "Relácia na mobile",
             "n_sessions_selected": {
                 "one": "%(count)s vybraná relácia",
+                "few": "%(count)s vybrané relácie",
                 "other": "%(count)s vybraných relácií"
             },
             "no_inactive_sessions": "Nenašli sa žiadne neaktívne relácie.",
@@ -2615,17 +2981,20 @@
             "security_recommendations_description": "Zlepšite zabezpečenie svojho účtu dodržiavaním týchto odporúčaní.",
             "session_id": "ID relácie",
             "show_details": "Zobraziť podrobnosti",
-            "sign_in_with_qr": "Prihlásiť sa pomocou QR kódu",
+            "sign_in_with_qr": "Prepojiť nové zariadenie",
             "sign_in_with_qr_button": "Zobraziť QR kód",
-            "sign_in_with_qr_description": "Toto zariadenie môžete použiť na prihlásenie nového zariadenia pomocou QR kódu. QR kód zobrazený na tomto zariadení musíte naskenovať pomocou zariadenia, ktoré je odhlásené.",
+            "sign_in_with_qr_description": "Pomocou QR kódu sa prihláste do druhého zariadenia a nastavte zabezpečené správy.",
+            "sign_in_with_qr_unsupported": "Nie je podporovaný poskytovateľom vášho účtu",
             "sign_out": "Odhlásiť sa z tejto relácie",
             "sign_out_all_other_sessions": "Odhlásiť sa zo všetkých ostatných relácií (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
                 "one": "Ste si istí, že sa chcete odhlásiť z %(count)s relácie?",
+                "few": "Ste si istí, že sa chcete odhlásiť z %(count)s relácií?",
                 "other": "Ste si istí, že sa chcete odhlásiť z %(count)s relácií?"
             },
             "sign_out_n_sessions": {
                 "one": "Odhlásiť sa z %(count)s relácie",
+                "few": "Odhlásiť sa z %(count)s relácií",
                 "other": "Odhlásiť sa z %(count)s relácií"
             },
             "title": "Relácie",
@@ -2657,7 +3026,9 @@
         "show_redaction_placeholder": "Zobrazovať náhrady za odstránené správy",
         "show_stickers_button": "Zobraziť tlačidlo nálepiek",
         "show_typing_notifications": "Zobrazovať oznámenia, keď ostatní používatelia píšu",
+        "showbold": "Zobraziť všetku aktivitu v zozname miestností (bodky alebo počet neprečítaných správ)",
         "sidebar": {
+            "dialog_title": "<strong>Nastavenia:</strong> Bočný panel",
             "metaspaces_favourites_description": "Zoskupte všetky vaše obľúbené miestnosti a ľudí na jednom mieste.",
             "metaspaces_home_all_rooms": "Zobraziť všetky miestnosti",
             "metaspaces_home_all_rooms_description": "Zobrazte všetky miestnosti v časti Domov, aj keď sú v priestore.",
@@ -2666,10 +3037,14 @@
             "metaspaces_orphans_description": "Zoskupte všetky miestnosti, ktoré nie sú súčasťou priestoru, na jednom mieste.",
             "metaspaces_people_description": "Zoskupte všetkých ľudí na jednom mieste.",
             "metaspaces_subsection": "Priestory na zobrazenie",
+            "metaspaces_video_rooms": "Video miestnosti a konferencie",
+            "metaspaces_video_rooms_description": "Zoskupte všetky súkromné video miestnosti a konferencie.",
+            "metaspaces_video_rooms_description_invite_extension": "Na konferencie môžete pozývať ľudí aj mimo siete matrix.",
             "spaces_explainer": "Priestory sú spôsobom zoskupovania miestností a osôb. Popri priestoroch, v ktorých sa nachádzate, môžete použiť aj niektoré predpripravené.",
             "title": "Bočný panel"
         },
         "start_automatically": "Spustiť automaticky po prihlásení do systému",
+        "tac_only_notifications": "Zobraziť upozornenia iba v centre aktivít vlákna",
         "use_12_hour_format": "Pri zobrazovaní časových značiek používať 12 hodinový formát (napr. 2:30pm)",
         "use_command_enter_send_message": "Použite Command + Enter na odoslanie správy",
         "use_command_f_search": "Na vyhľadávanie na časovej osi použite klávesovú skratku Command + F",
@@ -2683,6 +3058,7 @@
             "audio_output_empty": "Neboli rozpoznané žiadne zariadenia pre výstup zvuku",
             "auto_gain_control": "Automatické riadenie zosilnenia",
             "connection_section": "Pripojenie",
+            "dialog_title": "<strong>Nastavenia:</strong> Hlas a video",
             "echo_cancellation": "Potlačenie ozveny",
             "enable_fallback_ice_server": "Povoliť náhradnú službu hovorov asistenčného servera (%(server)s)",
             "enable_fallback_ice_server_description": "Platí len v prípade, ak váš domovský server takúto možnosť neponúka. Vaša IP adresa bude počas hovoru zdieľaná.",
@@ -2701,8 +3077,12 @@
         "warning": "<w>UPOZORNENIE:</w> <description/>"
     },
     "share": {
+        "link_copied": "Odkaz skopírovaný",
         "permalink_message": "Odkaz na vybratú správu",
         "permalink_most_recent": "Odkaz na najnovšiu správu",
+        "share_call": "Odkaz na pozvánku na konferenciu",
+        "share_call_subtitle": "Odkaz pre externých používateľov na pripojenie k hovoru bez matrix účtu:",
+        "title_link": "Zdieľať odkaz",
         "title_message": "Zdieľať správu z miestnosti",
         "title_room": "Zdieľať miestnosť",
         "title_user": "Zdieľať používateľa"
@@ -2773,8 +3153,6 @@
         "topic": "Zobrazí alebo nastaví tému miestnosti",
         "topic_none": "Táto miestnosť nemá nastavenú tému.",
         "topic_room_error": "Nepodarilo sa získať tému miestnosti: Nepodarilo sa nájsť miestnosť (%(roomId)s",
-        "tovirtual": "Prepne do virtuálnej miestnosti tejto miestnosti, ak ju má",
-        "tovirtual_not_found": "Žiadna virtuálna miestnosť pre túto miestnosť",
         "unban": "Zruší zákaz vstúpiť používateľovi so zadaným ID",
         "unflip": "Pridá znaky ┬──┬ ノ( ゜-゜ノ) pred správy vo formáte obyčajného textu",
         "unholdcall": "Zruší podržanie hovoru v aktuálnej miestnosti",
@@ -2792,6 +3170,7 @@
         "view": "Zobrazí miestnosti s danou adresou",
         "whois": "Zobrazuje informácie o používateľovi"
     },
+    "sliding_sync_legacy_no_longer_supported": "Starší režim kĺzavej synchronizácie už nie je podporovaný: odhláste sa a znova sa prihláste, aby ste povolili nový príznak kĺzavej synchronizácie",
     "space": {
         "add_existing_room_space": {
             "create": "Chcete namiesto toho pridať novú miestnosť?",
@@ -2799,8 +3178,9 @@
             "dm_heading": "Priame správy",
             "error_heading": "Neboli pridané všetky vybrané",
             "progress_text": {
-                "other": "Pridávanie miestností... (%(progress)s z %(count)s)",
-                "one": "Pridávanie miestnosti..."
+                "one": "Pridávanie miestnosti...",
+                "few": "Pridávanie miestností... (%(progress)s z %(count)s)",
+                "other": "Pridávanie miestností... (%(progress)s z %(count)s)"
             },
             "space_dropdown_label": "Výber priestoru",
             "space_dropdown_title": "Pridať existujúce miestnosti",
@@ -2885,8 +3265,9 @@
         "cant_find_room_helpful_hint": "Ak nemôžete nájsť hľadanú miestnosť, požiadajte o pozvánku alebo vytvorte novú miestnosť.",
         "copy_link_text": "Kopírovať odkaz na pozvánku",
         "count_of_members": {
-            "other": "%(count)s členov",
-            "one": "%(count)s člen"
+            "one": "%(count)s člen",
+            "few": "%(count)s členovia",
+            "other": "%(count)s členov"
         },
         "create_new_room_button": "Vytvoriť novú miestnosť",
         "failed_querying_public_rooms": "Nepodarilo sa vyhľadať verejné miestnosti",
@@ -2896,7 +3277,7 @@
         "heading_without_query": "Hľadať",
         "join_button_text": "Pripojiť sa k %(roomAddress)s",
         "keyboard_scroll_hint": "Na posúvanie použite <arrows/>",
-        "message_search_section_title": "Iné vyhľadávania",
+        "messages_label": "Správy",
         "other_rooms_in_space": "Ostatné miestnosti v %(spaceName)s",
         "public_rooms_label": "Verejné miestnosti",
         "public_spaces_label": "Verejné priestory",
@@ -2906,7 +3287,6 @@
         "result_may_be_hidden_privacy_warning": "Niektoré výsledky môžu byť skryté kvôli ochrane súkromia",
         "result_may_be_hidden_warning": "Niektoré výsledky môžu byť skryté",
         "search_dialog": "Vyhľadávacie dialógové okno",
-        "search_messages_hint": "Ak chcete vyhľadávať správy, nájdite túto ikonu v hornej časti miestnosti <icon/>",
         "spaces_title": "Priestory, v ktorých sa nachádzate",
         "start_group_chat_button": "Začať skupinovú konverzáciu"
     },
@@ -2933,7 +3313,7 @@
     },
     "theme": {
         "light_high_contrast": "Ľahký vysoký kontrast",
-        "match_system": "Zhoda so systémom"
+        "match_system": "Prispôsobiť systému"
     },
     "thread_view_back_action_label": "Späť na vlákno",
     "threads": {
@@ -2941,14 +3321,23 @@
         "all_threads_description": "Zobrazí všetky vlákna z aktuálnej miestnosti",
         "count_of_reply": {
             "one": "%(count)s odpoveď",
+            "few": "%(count)s odpovede",
             "other": "%(count)s odpovedí"
         },
+        "empty_description": "Použite „%(replyInThread)s“, keď umiestnite kurzor myši nad správu.",
+        "empty_title": "Vlákna pomáhajú udržiavať vaše konverzácie k téme a ľahko sledovateľné.",
         "error_start_thread_existing_relation": "Nie je možné vytvoriť vlákno z udalosti s existujúcim vzťahom",
+        "mark_all_read": "Označiť všetko ako prečítané",
         "my_threads": "Moje vlákna",
         "my_threads_description": "Zobrazí všetky vlákna, v ktorých ste sa zúčastnili",
         "open_thread": "Otvoriť vlákno",
         "show_thread_filter": "Zobraziť:"
     },
+    "threads_activity_centre": {
+        "header": "Aktivita vlákien",
+        "no_rooms_with_threads_notifs": "Zatiaľ nemáte miestnosti s upozorneniami na vlákna.",
+        "no_rooms_with_unread_threads": "Zatiaľ nemáte miestnosti s neprečítanými vláknami."
+    },
     "time": {
         "about_day_ago": "asi pred jedným dňom",
         "about_hour_ago": "približne pred hodinou",
@@ -2990,9 +3379,21 @@
         },
         "creation_summary_dm": "%(creator)s vytvoril/a túto priamu správu.",
         "creation_summary_room": "%(creator)s vytvoril a nastavil miestnosť.",
+        "decryption_failure": {
+            "blocked": "Odosielateľ vám zablokoval prijímanie tejto správy, pretože vaše zariadenie nie je overené",
+            "historical_event_no_key_backup": "Staré správy nie sú na tomto zariadení k dispozícii",
+            "historical_event_unverified_device": "Musíte overiť toto zariadenie pre prístup k starším správam",
+            "historical_event_user_not_joined": "K tejto správe nemáte prístup",
+            "sender_identity_previously_verified": "Overená totožnosť odosielateľa sa zmenila",
+            "sender_unsigned_device": "Odoslané z nezabezpečeného zariadenia.",
+            "unable_to_decrypt": "Správu sa nepodarilo dešifrovať"
+        },
         "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
         "download_action_decrypting": "Dešifrovanie",
         "download_action_downloading": "Preberanie",
+        "download_failed": "Sťahovanie zlyhalo",
+        "download_failed_description": "Pri sťahovaní tohto súboru sa vyskytla chyba",
+        "e2e_state": "Stav end-to-end šifrovania",
         "edits": {
             "tooltip_label": "Upravené %(date)s. Kliknutím zobrazíte úpravy.",
             "tooltip_sub": "Kliknutím zobrazíte úpravy",
@@ -3046,7 +3447,7 @@
         },
         "m.file": {
             "error_decrypting": "Chyba pri dešifrovaní prílohy",
-            "error_invalid": "Neplatný súbor%(extra)s"
+            "error_invalid": "Neplatný súbor"
         },
         "m.image": {
             "error": "Nie je možné zobraziť obrázok kvôli chybe",
@@ -3067,6 +3468,7 @@
         "m.poll": {
             "count_of_votes": {
                 "one": "%(count)s hlas",
+                "few": "%(count)s hlasy",
                 "other": "%(count)s hlasov"
             }
         },
@@ -3147,6 +3549,7 @@
             "left_reason": "%(targetName)s opustil/a miestnosť: %(reason)s",
             "no_change": "%(senderName)s neurobil žiadne zmeny",
             "reject_invite": "%(targetName)s odmietol/a pozvánku",
+            "reject_invite_reason": "%(targetName)s odmietol/a pozvanie: %(reason)s",
             "remove_avatar": "%(senderName)s odstránil/a svoj profilový obrázok",
             "remove_name": "%(senderName)s odstránil svoje zobrazované meno (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s nastavil/a svoj profilový obrázok",
@@ -3182,10 +3585,14 @@
             "sent": "%(senderName)s pozval %(targetDisplayName)s vstúpiť do miestnosti."
         },
         "m.room.tombstone": "%(senderDisplayName)s aktualizoval túto miestnosť.",
-        "m.room.topic": "%(senderDisplayName)s zmenil tému na \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s zmenil tému na \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s odstránil/a tému."
+        },
         "m.sticker": "%(senderDisplayName)s poslal nálepku.",
         "m.video": {
-            "error_decrypting": "Chyba pri dešifrovaní videa"
+            "error_decrypting": "Chyba pri dešifrovaní videa",
+            "show_video": "Zobraziť video"
         },
         "m.widget": {
             "added": "%(senderName)s pridal widget %(widgetName)s",
@@ -3233,10 +3640,12 @@
         "reactions": {
             "add_reaction_prompt": "Pridať reakciu",
             "custom_reaction_fallback_label": "Vlastná reakcia",
-            "label": "%(reactors)s reagovali %(content)s"
+            "label": "%(reactors)s reagovali %(content)s",
+            "tooltip_caption": "reagoval/a s %(shortName)s"
         },
         "read_receipt_title": {
             "one": "Videl %(count)s človek",
+            "few": "Videli %(count)s ľudia",
             "other": "Videlo %(count)s ľudí"
         },
         "read_receipts_label": "Potvrdenia o prečítaní",
@@ -3292,11 +3701,13 @@
             "format": "%(nameList)s %(transitionList)s",
             "hidden_event": {
                 "one": "%(oneUser)sposlal/a skrytú správu",
-                "other": "%(oneUser)sposlal %(count)s skrytých správ"
+                "few": "%(oneUser)sposlal/a %(count)s skryté správy",
+                "other": "%(oneUser)sposlal/a %(count)s skrytých správ"
             },
             "hidden_event_multiple": {
-                "other": "%(severalUsers)sposlalo %(count)s skrytých správ",
-                "one": "%(severalUsers)sposlalo skrytú správu"
+                "one": "%(severalUsers)sposlalo skrytú správu",
+                "few": "%(severalUsers)sposlali %(count)s skryté správy",
+                "other": "%(severalUsers)sposlalo %(count)s skrytých správ"
             },
             "invite_withdrawn": {
                 "one": "%(oneUser)s ",
@@ -3379,8 +3790,9 @@
                 "other": "%(severalUsers)szmenili <a>pripnuté správy</a> v miestnosti %(count)s-krát"
             },
             "redacted": {
-                "other": "%(oneUser)s odstránil/a %(count)s správ",
-                "one": "%(oneUser)sodstránil správu"
+                "one": "%(oneUser)s odstránil správu",
+                "few": "%(oneUser)s odstránil/a %(count)s správy",
+                "other": "%(oneUser)s odstránil/a %(count)s správ"
             },
             "redacted_multiple": {
                 "one": "%(severalUsers)s odstránili správu",
@@ -3447,6 +3859,10 @@
     "truncated_list_n_more": {
         "other": "A %(count)s ďalších…"
     },
+    "unsupported_browser": {
+        "description": "Ak budete pokračovať, niektoré funkcie môžu prestať fungovať a existuje riziko, že v budúcnosti stratíte údaje. Aktualizujte svoj prehliadač, aby ste mohli pokračovať v používaní aplikácie %(brand)s.",
+        "title": "%(brand)s nepodporuje tento prehliadač"
+    },
     "unsupported_server_description": "Tento server používa staršiu verziu systému Matrix. Ak chcete používať %(brand)s bez chýb, aktualizujte na Matrix %(version)s.",
     "unsupported_server_title": "Váš server nie je podporovaný",
     "update": {
@@ -3464,6 +3880,13 @@
         "toast_title": "Aktualizovať %(brand)s",
         "unavailable": "Nedostupné"
     },
+    "update_room_access_modal": {
+        "description": "Ak chcete vytvoriť odkaz na zdieľanie, nastavte túto miestnosť ako <b> verejnú</b> alebo povolte používateľom možnosť <b>požiadať o pripojenie</b>. To umožní hosťom pripojiť sa bez pozvánky.",
+        "dont_change_description": "Ak nechcete zmeniť prístup do tejto miestnosti, môžete vytvoriť novú miestnosť pre odkaz na hovor.",
+        "no_change": "Nechcem zmeniť úroveň prístupu.",
+        "revert_access_description": "(Túto hodnotu je možné vrátiť na predchádzajúcu hodnotu v nastaveniach miestnosti: <b>Zabezpečenie a súkromie</b> / <b>Prístup</b>)",
+        "title": "Povoliť hosťom pripojiť sa k tejto miestnosti"
+    },
     "upload_failed_generic": "Nepodarilo sa nahrať súbor „%(fileName)s“.",
     "upload_failed_size": "Veľkosť súboru „%(fileName)s“ prekračuje limit veľkosti súboru nahrávania na tento domovský server",
     "upload_failed_title": "Nahrávanie zlyhalo",
@@ -3473,6 +3896,7 @@
         "error_files_too_large": "Tieto súbory sú <b>príliš veľké</b> na nahratie. Limit veľkosti súboru je %(limit)s.",
         "error_some_files_too_large": "Niektoré súbory sú <b>príliš veľké</b> na to, aby sa dali nahrať. Limit veľkosti súboru je %(limit)s.",
         "error_title": "Chyba pri nahrávaní",
+        "not_image": "Súbor, ktorý ste vybrali, nie je platným súborom obrázka.",
         "title": "Nahrať súbory",
         "title_progress": "Nahrať súbory (%(current)s z %(total)s)",
         "upload_all_button": "Nahrať všetko",
@@ -3488,14 +3912,6 @@
         "ban_room_confirm_title": "Zakázať vstup do %(roomName)s",
         "ban_space_everything": "Zakázať im všetko, na čo mám oprávnenie",
         "ban_space_specific": "Zakázať im konkrétne právomoci, na ktoré mám oprávnenie",
-        "count_of_sessions": {
-            "one": "%(count)s relácia",
-            "other": "%(count)s relácie"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 overená relácia",
-            "other": "%(count)s overených relácií"
-        },
         "deactivate_confirm_action": "Deaktivovať používateľa",
         "deactivate_confirm_description": "Deaktivácia tohto používateľa ho odhlási a zabráni mu v opätovnom prihlásení. Okrem toho opustí všetky miestnosti, v ktorých sa nachádza. Túto akciu nie je možné vrátiť späť. Ste si istí, že chcete tohto používateľa deaktivovať?",
         "deactivate_confirm_title": "Deaktivovať používateľa?",
@@ -3506,15 +3922,13 @@
         "disinvite_button_room": "Zrušiť pozvánku z miestnosti",
         "disinvite_button_room_name": "Zrušenie pozvania z %(roomName)s",
         "disinvite_button_space": "Zrušiť pozvánku z priestoru",
-        "edit_own_devices": "Upraviť zariadenia",
         "error_ban_user": "Nepodarilo sa zakázať používateľa",
         "error_deactivate": "Nepodarilo sa deaktivovať používateľa",
         "error_kicking_user": "Nepodarilo sa odstrániť používateľa",
         "error_mute_user": "Nepodarilo sa umlčať používateľa",
         "error_revoke_3pid_invite_description": "Pozvánku nebolo možné odvolať. Na serveri môže byť dočasný problém alebo nemáte dostatočné oprávnenia na odvolanie pozvánky.",
         "error_revoke_3pid_invite_title": "Nepodarilo sa odvolať pozvánku",
-        "hide_sessions": "Skryť relácie",
-        "hide_verified_sessions": "Skryť overené relácie",
+        "ignore_button": "Ignorovať",
         "ignore_confirm_description": "Všetky správy a pozvánky od tohto používateľa budú skryté. Ste si istí, že ich chcete ignorovať?",
         "ignore_confirm_title": "Ignorovať %(user)s",
         "invited_by": "Pozvaný používateľom %(sender)s",
@@ -3528,11 +3942,13 @@
         "promote_warning": "Túto zmenu nebudete môcť vrátiť späť, pretože tomuto používateľovi udeľujete rovnakú úroveň oprávnenia, akú máte vy.",
         "redact": {
             "confirm_button": {
-                "other": "Odstrániť %(count)s správ",
-                "one": "Odstrániť 1 správu"
+                "one": "Odstrániť 1 správu",
+                "few": "Odstrániť %(count)s správy",
+                "other": "Odstrániť %(count)s správ"
             },
             "confirm_description_1": {
                 "one": "Chystáte sa odstrániť %(count)s správu od používateľa %(user)s. Týmto ju natrvalo odstránite pre všetkých účastníkov konverzácie. Chcete pokračovať?",
+                "few": "Chystáte sa odstrániť %(count)s správy od používateľa %(user)s. Týmto ich natrvalo odstránite pre všetkých účastníkov konverzácie. Chcete pokračovať?",
                 "other": "Chystáte sa odstrániť %(count)s správ od používateľa %(user)s. Týmto ich natrvalo odstránite pre všetkých účastníkov konverzácie. Chcete pokračovať?"
             },
             "confirm_description_2": "Pri veľkom množstve správ to môže trvať určitý čas. Medzitým prosím neobnovujte svojho klienta.",
@@ -3542,23 +3958,27 @@
             "no_recent_messages_description": "Skúste sa posunúť nahor na časovej osi a pozrite sa, či tam nie sú nejaké skoršie.",
             "no_recent_messages_title": "Nenašli sa žiadne nedávne správy od používateľa %(user)s"
         },
-        "redact_button": "Odstrániť posledné správy",
+        "redact_button": "Odstrániť správy",
         "revoke_invite": "Odvolať pozvánku",
         "room_encrypted": "Správy v tejto miestnosti sú šifrované od vás až k príjemcovi.",
         "room_encrypted_detail": "Vaše správy sú zabezpečené a jedinečné kľúče na ich odomknutie máte len vy a príjemca.",
         "room_unencrypted": "Správy v tejto miestnosti nie sú šifrované od vás až k príjemcovi.",
         "room_unencrypted_detail": "V šifrovaných miestnostiach sú vaše správy zabezpečené a jedinečné kľúče na ich odomknutie máte len vy a príjemca.",
-        "share_button": "Zdieľať odkaz na používateľa",
+        "send_message": "Odoslať správu",
+        "share_button": "Zdieľať profil",
         "unban_button_room": "Zrušiť zákaz vstupu do miestnosti",
         "unban_button_space": "Zrušiť zákaz vstupu do priestoru",
         "unban_room_confirm_title": "Zrušiť zákaz vstup do %(roomName)s",
         "unban_space_everything": "Zrušiť im zákaz zo všetkého, na čo mám oprávnenie",
         "unban_space_specific": "Zrušiť im zákaz z konkrétnych právomocí, na ktoré mám oprávnenie",
         "unban_space_warning": "Nebudú mať prístup k tomu, čoho nie ste správcom.",
+        "unignore_button": "Prestať ignorovať",
+        "verification_unavailable": "Overenie používateľa nie je k dispozícii",
         "verify_button": "Overiť používateľa",
         "verify_explainer": "V záujme zvýšenia bezpečnosti overte tohto používateľa tak, že na oboch zariadeniach skontrolujete jednorazový kód."
     },
     "user_menu": {
+        "link_new_device": "Prepojiť nové zariadenie",
         "settings": "Všetky nastavenia",
         "switch_theme_dark": "Prepnúť na tmavý režim",
         "switch_theme_light": "Prepnúť na svetlý režim"
@@ -3582,6 +4002,7 @@
         "camera_disabled": "Váš fotoaparát je vypnutý",
         "camera_enabled": "Fotoaparát je stále zapnutý",
         "cannot_call_yourself_description": "Nemôžete zavolať samému sebe.",
+        "close_lobby": "Zatvoriť lobby",
         "connecting": "Pripájanie",
         "connection_lost": "Spojenie so serverom bolo prerušené",
         "connection_lost_description": "Bez pripojenia k serveru nie je možné uskutočňovať hovory.",
@@ -3595,15 +4016,23 @@
         "disabled_no_perms_start_video_call": "Nemáte povolenie na spustenie videohovorov",
         "disabled_no_perms_start_voice_call": "Nemáte povolenie na spustenie hlasových hovorov",
         "disabled_ongoing_call": "Prebiehajúci hovor",
+        "element_call": "Element Call",
         "enable_camera": "Zapnúť kameru",
         "enable_microphone": "Zrušiť stlmenie mikrofónu",
         "expand": "Návrat k hovoru",
+        "get_call_link": "Zdieľať odkaz na hovor",
         "hangup": "Zavesiť",
         "hide_sidebar_button": "Skryť bočný panel",
         "input_devices": "Vstupné zariadenia",
+        "jitsi_call": "Konferencia Jitsi",
         "join_button_tooltip_call_full": "Prepáčte — tento hovor je momentálne obsadený",
-        "join_button_tooltip_connecting": "Pripájanie",
+        "legacy_call": "Zastaraný hovor",
         "maximise": "Vyplniť obrazovku",
+        "maximise_call": "Maximalizovať hovor",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferencie"
+        },
+        "minimise_call": "Minimalizovať hovor",
         "misconfigured_server": "Hovor zlyhal z dôvodu nesprávne nastaveného servera",
         "misconfigured_server_description": "Prosím, požiadajte správcu vášho domovského servera (<code>%(homeserverDomain)s</code>) aby nakonfiguroval Turn server, čo zlepší spoľahlivosť audio / video hovorov.",
         "misconfigured_server_fallback": "Prípadne môžete skúsiť použiť verejný server na adrese <server/>, ale nebude to tak spoľahlivé a vaša IP adresa bude zdieľaná s týmto serverom. Môžete to spravovať aj v nastaveniach.",
@@ -3614,6 +4043,7 @@
         "msisdn_transfer_failed": "Nie je možné presmerovať hovor",
         "n_people_joined": {
             "one": "%(count)s človek sa pripojil",
+            "few": "%(count)s ľudia sa pripojili",
             "other": "%(count)s ľudí sa pripojilo"
         },
         "no_audio_input_description": "Vo vašom zariadení sa nenašiel mikrofón. Skontrolujte svoje nastavenia a skúste to znova.",
@@ -3651,6 +4081,7 @@
         "user_is_presenting": "%(sharerName)s prezentuje",
         "video_call": "Video hovor",
         "video_call_started": "Videohovor bol spustený",
+        "video_call_using": "Videohovor pomocou:",
         "voice_call": "Hlasový hovor",
         "you_are_presenting": "Prezentujete"
     },
@@ -3750,7 +4181,7 @@
         "error_need_to_be_logged_in": "Mali by ste byť prihlásení.",
         "error_unable_start_audio_stream_description": "Nie je možné spustiť streamovanie zvuku.",
         "error_unable_start_audio_stream_title": "Nepodarilo sa spustiť livestream",
-        "modal_data_warning": "Údaje na tejto obrazovke sú zdieľané s %(widgetDomain)s",
+        "modal_data_warning": "Nižšie uvedené údaje sú zdieľané s %(widgetDomain)s",
         "modal_title_default": "Modálny widget",
         "no_name": "Neznáma aplikácia",
         "open_id_permissions_dialog": {
@@ -3759,7 +4190,7 @@
             "title": "Umožniť tomuto widgetu overiť vašu totožnosť"
         },
         "popout": "Otvoriť widget v novom okne",
-        "set_room_layout": "Nastavenie rozmiestnenie v mojej miestnosti pre všetkých",
+        "set_room_layout": "Nastaviť rozmiestnenie pre všetkých",
         "shared_data_avatar": "Vaša URL adresa profilového obrázka",
         "shared_data_device_id": "ID vášho zariadenia",
         "shared_data_lang": "Váš jazyk",
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index 63c72802965ef1e59e692a6edcdca08c7e638891..15ea9a3df88c8e1451dd9029220c78089217b619 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -85,7 +85,6 @@
         "react": "Reagoni",
         "refresh": "Rifreskoje",
         "register": "Regjistrohuni",
-        "reject": "Hidheni tej",
         "reload": "Ringarkoje",
         "remove": "Hiqe",
         "rename": "Riemërtojeni",
@@ -357,7 +356,6 @@
         "download_logs": "Shkarko regjistra",
         "downloading_logs": "Po shkarkohen regjistra",
         "error_empty": "Ju lutemi, na tregoni ç’shkoi keq ose, akoma më mirë, krijoni në GitHub një çështje që përshkruan problemin.",
-        "failed_send_logs": "S’u arrit të dërgoheshin regjistra: ",
         "github_issue": "Çështje në GitHub",
         "introduction": "Nëse keni parashtruar një të metë përmes GitHub-i, regjistrat e diagnostikimit na ndihmojnë të kapim problemin. ",
         "log_request": "Për të na ndihmuar ta parandalojmë këtë në të ardhmen, ju lutemi, <a>dërgonani regjistra</a>.",
@@ -397,7 +395,6 @@
         "access_token": "Token Hyrjesh",
         "accessibility": "Përdorim nga persona me aftësi të kufizuara",
         "advanced": "Të mëtejshme",
-        "all_rooms": "Krejt dhomat",
         "analytics": "Analiza",
         "and_n_others": {
             "other": "dhe %(count)s të tjerë…",
@@ -413,7 +410,6 @@
         "capabilities": "Aftësi",
         "copied": "U kopjua!",
         "credits": "Kredite",
-        "cross_signing": "<em>Cross-signing</em>",
         "dark": "E errët",
         "description": "Përshkrim",
         "deselect_all": "Shpërzgjidhi krejt",
@@ -484,7 +480,6 @@
         "rooms": "Dhoma",
         "saving": "Po ruhet…",
         "secure_backup": "Kopjeruajtje e Sigurt",
-        "security": "Siguri",
         "select_all": "Përzgjidhi krejt",
         "server": "Shërbyes",
         "settings": "Rregullime",
@@ -502,7 +497,6 @@
         "thread": "Rrjedhë",
         "threads": "Rrjedha",
         "timeline": "Rrjedhë Kohore",
-        "trusted": "E besuar",
         "unencrypted": "Të pafshehtëzuara",
         "unmute": "Ktheji zërin",
         "unnamed_room": "Dhomë e Paemërtuar",
@@ -761,44 +755,23 @@
     "empty_room_was_name": "Dhomë e zbrazët (qe %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Që të vazhdohet, jepni Frazën tuaj të Sigurisë, ose <button>përdorni Kyçin tuaj të Sigurisë</button>.",
             "key_validation_text": {
-                "invalid_security_key": "Kyç Sigurie i Pavlefshëm",
-                "recovery_key_is_correct": "Mirë duket!",
-                "wrong_file_type": "Lloj i gabuar kartele",
                 "wrong_security_key": "Kyç Sigurie i Gabuar"
             },
-            "reset_title": "Kthe gjithçka te parazgjedhjet",
-            "reset_warning_1": "Bëjeni këtë vetëm nëse s’keni pajisje tjetër me të cilën të plotësoni verifikimin.",
-            "reset_warning_2": "Nëse riktheni gjithçka te parazgjedhjet, do të rifilloni pa sesione të besuara, pa përdorues të besuar, dhe mund të mos jeni në gjendje të shihni mesazhe të dikurshëm.",
             "restoring": "Po rikthehen kyçesh nga kopjeruajtje",
-            "security_key_title": "Kyç Sigurie",
-            "security_phrase_incorrect_error": "S’arrihet të hyhet në depozitë të fshehtë. Ju lutemi, verifikoni se keni dhënë Frazën e saktë të Sigurisë.",
-            "security_phrase_title": "Frazë Sigurie",
-            "separator": "%(securityKey)s ose %(recoveryFile)s",
-            "use_security_key_prompt": "Që të vazhdohet përdorni Kyçin tuaj të Sigurisë."
+            "security_key_title": "Kyç Sigurie"
         },
         "bootstrap_title": "Ujdisje kyçesh",
         "cancel_entering_passphrase_description": "Jeni i sigurt se doni të anulohet dhënie frazëkalimi?",
         "cancel_entering_passphrase_title": "Të anulohet dhënue frazëkalimi?",
         "confirm_encryption_setup_body": "Klikoni mbi butonin më poshtë që të ripohoni ujdisjen e fshehtëzimit.",
         "confirm_encryption_setup_title": "Ripohoni ujdisje fshehtëzimi",
-        "cross_signing_not_ready": "“Cross-signing” s’është ujdisur.",
-        "cross_signing_ready": "“Cross-signing” është gati për përdorim.",
-        "cross_signing_ready_no_backup": "“Cross-signing” është gati, por kyçet s’janë koperuajtur.",
         "cross_signing_room_normal": "Kjo dhomë është e fshehtëzuar skaj-më-skaj",
         "cross_signing_room_verified": "Gjithkush në këtë dhomë është verifikuar",
         "cross_signing_room_warning": "Dikush po përdor një sesion të panjohur",
-        "cross_signing_unsupported": "Shërbyesi juaj Home nuk mbulon <em>cross-signing</em>.",
-        "cross_signing_untrusted": "Llogaria juaj ka një identitet <em>cross-signing</em> në depozitë të fshehtë, por s’është ende i besuar në këtë sesion.",
         "cross_signing_user_normal": "S’e keni verifikuar këtë përdorues.",
         "cross_signing_user_verified": "E keni verifikuar këtë përdorues. Ky përdorues ka verifikuar krejt sesionet e veta.",
         "cross_signing_user_warning": "Ky përdorues s’ka verifikuar krejt sesionet e tij.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Spastro kyçe <em>cross-signing</em>",
-            "title": "Të shkatërrohen kyçet <em>cross-signing</em>?",
-            "warning": "Fshirja e kyçeve <em>cross-signing</em> është e përhershme. Cilido që keni verifikuar me to, do të shohë një sinjalizim sigurie. Thuajse e sigurt që s’keni pse ta bëni një gjë të tillë, veç në paçi humbur çdo pajisje prej nga mund të bëni <em>cross-sign</em>."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "Mirëfilltësia e këtij mesazhi të fshehtëzuar s’mund të garantohet në këtë pajisje.",
         "event_shield_reason_mismatched_sender_key": "Fshehtëzuar nga një sesion i paverifikuar",
         "export_unsupported": "Shfletuesi juaj nuk mbulon zgjerimet kriptografike të domosdoshme",
@@ -818,7 +791,6 @@
             "title": "Metodë e Re Rimarrjesh",
             "warning": "Nëse metodën e re të rimarrjeve s’e keni caktuar ju, dikush mund të jetë duke u rrekur të hyjë në llogarinë tuaj. Ndryshoni menjëherë fjalëkalimin e llogarisë tuaj, te Rregullimet, dhe caktoni një metodë të re rimarrjesh."
         },
-        "not_supported": "<nuk mbulohet>",
         "recovery_method_removed": {
             "description_1": "Ky sesion ka pikasur se Fraza e Sigurisë dhe kyçi juaj për Mesazhe të Sigurt janë hequr.",
             "description_2": "Nëse këtë e keni bërë pa dashje, mund të ujdisni Mesazhe të Sigurt në këtë sesion, gjë që do të sjellë rifshehtëzimin e historikut të mesazheve të sesionit me një metodë të re rimarrjesh.",
@@ -829,8 +801,7 @@
         "set_up_toast_description": "Mbrohuni nga humbja e hyrjes te mesazhe & të dhëna të fshehtëzuara",
         "set_up_toast_title": "Ujdisni Kopjeruajtje të Sigurt",
         "setup_secure_backup": {
-            "explainer": "Kopjeruajini kyçet tuaj, përpara se të dilni, që të shmangni humbjen e tyre.",
-            "title": "Rregulloje"
+            "explainer": "Kopjeruajini kyçet tuaj, përpara se të dilni, që të shmangni humbjen e tyre."
         },
         "udd": {
             "interactive_verification_button": "Verifikojeni në mënyrë ndërvepruese përmes emoji-sh",
@@ -841,12 +812,10 @@
             "title": "Jo e Besuar"
         },
         "unable_to_setup_keys_error": "S’arrihet të ujdisen kyçe",
-        "unsupported": "Ky klient nuk mbulon fshehtëzim skaj-më-skaj.",
         "verification": {
             "accepting": "Po pranohet…",
             "after_new_login": {
                 "device_verified": "Pajisja u verifikua",
-                "reset_confirmation": "Të kthehen vërtet te parazgjedhjet kyçet e verifikimit?",
                 "skip_verification": "Anashkaloje verifikimin hëpërhë",
                 "unable_to_verify": "S’arrihet të verifikohet kjo pajisje",
                 "verify_this_device": "Verifikoni këtë pajisje"
@@ -916,8 +885,6 @@
             "verify_emoji_prompt": "Verifikoje duke krahasuar emoji unik.",
             "verify_emoji_prompt_qr": "Nëse s’e skanoni dot kodin më sipër, verifikojeni duke krahasuar emoji unik.",
             "verify_later": "Do ta verifikoj më vonë",
-            "verify_reset_warning_1": "Rikthimi te parazgjedhjet i kyçeve tuaj të verifikimit s’mund të zhbëhet. Pas rikthimit te parazgjedhjet, s’do të mund të hyni dot te mesazhe të dikurshëm të fshehtëzuar dhe, cilido shok që ju ka verifikuar më parë, do të shohë një sinjalizim sigurie deri sa të ribëni verifikimin me ta.",
-            "verify_reset_warning_2": "Ju lutemi, ecni më tej vetëm nëse jeni i sigurt se keni humbur krejt pajisjet tuaja të tjera dhe Kyçin tuaj të Sigurisë.",
             "verify_using_device": "Verifikojeni me pajisje tjetër",
             "verify_using_key": "Verifikoje me Kyç Sigurie",
             "verify_using_key_or_phrase": "Verifikojeni me Kyç ose Frazë Sigurie",
@@ -976,11 +943,7 @@
             "title": "S’arrihet të kopjohet lidhja e dhomës"
         },
         "error_loading_user_profile": "S’u ngarkua dot profili i përdoruesit",
-        "forget_room_failed": "S’u arrit të harrohej dhoma %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Shërbyesi mund të jetë i pakapshëm, i mbingarkuar, ose kërkimit i mbaroi koha :(",
-            "title": "Kërkimi shtoi"
-        }
+        "forget_room_failed": "S’u arrit të harrohej dhoma %(errCode)s"
     },
     "event_preview": {
         "m.call.answer": {
@@ -1545,11 +1508,6 @@
         "ongoing": "Po hiqet…",
         "reason_label": "Arsye (opsionale)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Jeni i sigurt se doni të hidhet tej kjo ftesë?",
-        "failed": "S’u arrit të hidhej poshtë ftesa",
-        "title": "Hidheni tej ftesën"
-    },
     "report_content": {
         "description": "Raportimi i këtij mesazhi do të shkaktojë dërgimin e 'ID-së së aktit' unike te përgjegjësi i shërbyesit tuaj Home. Nëse mesazhet në këtë dhomë fshehtëzohen, përgjegjësi i shërbyesit tuaj Home s’do të jetë në gjendje të lexojë tekstin e mesazhit apo të shohë çfarëdo kartelë apo figurë.",
         "disagree": "S’pajtohem",
@@ -1717,7 +1675,6 @@
             "you_created": "Krijuat këtë dhomë."
         },
         "invite_email_mismatch_suggestion": "Që të merrni ftesa drejt e te %(brand)s, ndajeni me të tjerët këtë email, te Rregullimet.",
-        "invite_reject_ignore": "Hidhe poshtë & Shpërfille përdoruesin",
         "invite_sent_to_email": "Kjo ftesë u dërgua për %(email)s",
         "invite_sent_to_email_room": "Kjo ftesë për %(roomName)s u dërgua te %(email)s",
         "invite_subtitle": "Ju ftoi <userName/>",
@@ -2206,7 +2163,6 @@
             "remove_msisdn_prompt": "Të hiqet %(phone)s?",
             "spell_check_locale_placeholder": "Zgjidhni vendore"
         },
-        "image_thumbnails": "Shfaq për figurat paraparje/miniatura",
         "inline_url_previews_default": "Aktivizo, si parazgjedhje, paraparje URL-sh brendazi",
         "inline_url_previews_room": "Aktivizo, si parazgjedhje, paraparje URL-sh për pjesëmarrësit në këtë dhomë",
         "inline_url_previews_room_account": "Aktivizo paraparje URL-sh për këtë dhomë (prek vetëm ju)",
@@ -2314,50 +2270,16 @@
         "prompt_invite": "Pyet, përpara se të dërgohen ftesa te ID Matrix potencialisht të pavlefshme",
         "replace_plain_emoji": "Zëvendëso automatikisht emotikone tekst të thjeshtë me Emoji",
         "security": {
-            "4s_public_key_in_account_data": "në të dhëna llogarie",
-            "4s_public_key_status": "Kyç publik depozite të fshehtë:",
-            "backup_key_cached_status": "Kyç kopjeruajtjesh i ruajtur në fshehtinë:",
-            "backup_key_stored_status": "Kyç kopjeruajtjesh i depozituar:",
-            "backup_key_unexpected_type": "lloj i papritur",
-            "backup_key_well_formed": "e mirëformuar",
-            "backup_keys_description": "Kopjeruani kyçet tuaj të fshehtëzimit me të dhënat e llogarisë tuaj, për ditën kur mund të humbni hyrje në sesionet tuaja. Kyçet tuaj do të jenë të siguruar me një Kyç unik Sigurie.",
             "bulk_options_accept_all_invites": "Prano krejt ftesat prej %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Mos prano asnjë ftesë për në %(invitedRooms)s",
             "bulk_options_section": "Veprime masive",
-            "cross_signing_cached": "ruajtur në fshehtinë lokalisht",
-            "cross_signing_homeserver_support": "Mbulim veçorish nga shërbyesi Home:",
-            "cross_signing_homeserver_support_exists": "ekziston",
-            "cross_signing_in_4s": "në depozitë të fshehtë",
-            "cross_signing_in_memory": "në kujtesë",
-            "cross_signing_master_private_Key": "Kyç privat i përgjithshëm:",
-            "cross_signing_not_cached": "i pagjetur lokalisht",
-            "cross_signing_not_found": "s’u gjet",
-            "cross_signing_not_in_4s": "s’u gjet në depozitë",
-            "cross_signing_not_stored": "e padepozituar",
-            "cross_signing_private_keys": "Kyçe privatë për <em>cross-signing</em>:",
-            "cross_signing_public_keys": "Kyçe publikë për <em>cross-signing</em>:",
-            "cross_signing_self_signing_private_key": "Kyç privat vetënënshkrimi:",
-            "cross_signing_user_signing_private_key": "Kyç privat nënshkrimesh përdoruesi:",
-            "cryptography_section": "Kriptografi",
-            "delete_backup": "Fshije Kopjeruajtjen",
-            "delete_backup_confirm_description": "Jeni i sigurt? Do të humbni mesazhet tuaj të fshehtëzuar, nëse kopjeruajtja për kyçet tuaj nuk bëhet si duhet.",
             "e2ee_default_disabled_warning": "Përgjegjësi i shërbyesit tuaj ka çaktivizuar fshehtëzimin skaj-më-skaj, si parazgjedhje, në dhoma private & Mesazhe të Drejtpërdrejtë.",
             "enable_message_search": "Aktivizo kërkim mesazhesh në dhoma të fshehtëzuara",
             "encryption_section": "Fshehtëzim",
-            "error_loading_key_backup_status": "S’arrihet të ngarkohet gjendje kopjeruajtjeje kyçesh",
-            "export_megolm_keys": "Eksporto kyçe dhome E2E",
             "ignore_users_empty": "S’keni përdorues të shpërfillur.",
             "ignore_users_section": "Përdorues të shpërfillur",
-            "import_megolm_keys": "Importo kyçe E2E dhome",
-            "key_backup_active": "Kjo sesion po bën kopjeruajtje të kyçeve tuaja.",
-            "key_backup_active_version_none": "Asnjë",
             "key_backup_algorithm": "Algoritëm:",
-            "key_backup_complete": "U kopjeruajtën krejt kyçet",
             "key_backup_connect": "Lidhe këtë sesion me Kopjeruajtje Kyçesh",
-            "key_backup_connect_prompt": "Lidheni këtë sesion kopjeruajtje kyçesh, përpara se të dilni, që të shmangni humbje të çfarëdo kyçi që mund të gjendet vetëm në këtë pajisje.",
-            "key_backup_in_progress": "Po kopjeruhen kyçet për %(sessionsRemaining)s…",
-            "key_backup_inactive": "Ky sesion <b>nuk po bën kopjeruajtje të kyçeve tuaja</b>, por keni një kopjeruajtje ekzistuese që mund ta përdorni për rimarrje dhe ta shtoni më tej.",
-            "key_backup_inactive_warning": "Kyçet tuaj <b>nuk po kopjeruhen nga ky sesion</b>.",
             "message_search_disable_warning": "Në u çaktivizoftë, mesazhet prej dhomash të fshehtëzuara s’do të duken në përfundime kërkimi.",
             "message_search_disabled": "Ruaj lokalisht në mënyrë të sigurt në fshehtinë mesazhet që të shfaqen në përfundime kërkimi.",
             "message_search_enabled": {
@@ -2377,13 +2299,7 @@
             "message_search_unsupported": "%(brand)s-it i mungojnë disa përbërës të domosdoshëm për ruajtje lokalisht në mënyrë të sigurt në fshehtinë mesazhe. Nëse do të donit të eksperimentonit me këtë veçori, montoni një Desktop vetjak %(brand)s Desktop me <nativeLink>shtim përbërësish kërkimi</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s s’mund të ruajë lokalisht në fshehtinë në mënyrë të siguruar mesazhe të fshehtëzuar, teksa xhirohet në një shfletues. Që mesazhet e fshehtëzuar të shfaqen te përfundime kërkimi, përdorni <desktopLink>%(brand)s Desktop</desktopLink>.",
             "record_session_details": "Regjistro emrin, versionin dhe URL-në e klientit, për të dalluar më kollaj sesionet te përgjegjës sesionesh",
-            "restore_key_backup": "Riktheje prej Kopjeruajtje",
-            "secret_storage_not_ready": "jo gati",
-            "secret_storage_ready": "gati",
-            "secret_storage_status": "Depozitë e fshehtë:",
             "send_analytics": "Dërgo të dhëna analitike",
-            "session_id": "ID sesioni:",
-            "session_key": "Kyç sesioni:",
             "strict_encryption": "Mos dërgo kurrë prej këtij sesioni mesazhe të fshehtëzuar te sesione të paverifikuar"
         },
         "send_read_receipts": "Dërgo dëftesa leximi",
@@ -2602,8 +2518,6 @@
         "topic": "Merr ose cakton temën e dhomës",
         "topic_none": "Kjo dhomë s’ka temë.",
         "topic_room_error": "S’u arrit të merret tema e dhomës: S’arrihet të gjendet dhomë (%(roomId)s",
-        "tovirtual": "Kalohet te dhoma virtuale e kësaj dhome, në pastë",
-        "tovirtual_not_found": "S’ka dhomë virtuale për këtë dhomë",
         "unban": "I heq dëbimin përdoruesit me ID-në e dhënë",
         "unflip": "E paraprin një mesazh tekst i thjeshtë me ┬──┬ ノ( ゜-゜ノ)",
         "unholdcall": "E heq nga pritja thirrjen në dhomën aktuale",
@@ -2722,7 +2636,6 @@
         "heading_without_query": "Kërkoni për",
         "join_button_text": "Hyni te %(roomAddress)s",
         "keyboard_scroll_hint": "Përdorni <arrows/> për rrëshqitje",
-        "message_search_section_title": "Kërkime të tjera",
         "other_rooms_in_space": "Dhoma të tjera në %(spaceName)s",
         "public_rooms_label": "Dhoma publike",
         "recent_searches_section_title": "Kërkime së fundi",
@@ -2731,7 +2644,6 @@
         "result_may_be_hidden_privacy_warning": "Disa përfundime mund të jenë fshehur, për arsye privatësie",
         "result_may_be_hidden_warning": "Disa përfundime mund të jenë të fshehura",
         "search_dialog": "Dialog Kërkimi",
-        "search_messages_hint": "Që të kërkoni te mesazhet, shihni për këtë ikonë në krye të një dhome <icon/>",
         "spaces_title": "Hapësira ku jeni i pranishëm",
         "start_group_chat_button": "Nisni një fjalosje grupi"
     },
@@ -2995,7 +2907,9 @@
             "sent": "%(senderName)s dërgoi një ftesë për %(targetDisplayName)s që të marrë pjesë në dhomë."
         },
         "m.room.tombstone": "%(senderDisplayName)s e përmirësoi këtë dhomë.",
-        "m.room.topic": "%(senderDisplayName)s ndryshoi temën në \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s ndryshoi temën në \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s dërgoi një ngjitës.",
         "m.video": {
             "error_decrypting": "Gabim në shfshehtëzim videoje"
@@ -3258,14 +3172,6 @@
         "ban_room_confirm_title": "Dëboje prej %(roomName)s",
         "ban_space_everything": "Dëboji prej gjithçkaje ku mundem ta bëj këtë",
         "ban_space_specific": "Dëboji prej gjërash të caktuara ku mundem ta bëj këtë",
-        "count_of_sessions": {
-            "other": "%(count)s sesione",
-            "one": "%(count)s sesion"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s sesione të verifikuar",
-            "one": "1 sesion i verifikuar"
-        },
         "deactivate_confirm_action": "Çaktivizoje përdoruesin",
         "deactivate_confirm_description": "Çaktivizimi i këtij përdoruesi do të sjellë nxjerrjen e tij nga llogaria përkatëse dhe do të pengojë rihyrjen e tij. Veç kësaj, do të braktisë krejt dhomat ku ndodhet. Ky veprim s’mund të prapësohet. Jeni i sigurt se doni të çaktivizohet ky përdorues?",
         "deactivate_confirm_title": "Të çaktivizohet përdoruesi?",
@@ -3276,15 +3182,12 @@
         "disinvite_button_room": "Hiqi ftesën për këtë dhomë",
         "disinvite_button_room_name": "Hiqja ftesën për %(roomName)s",
         "disinvite_button_space": "Hiqi ftesën për këtë hapësirë",
-        "edit_own_devices": "Përpunoni pajisje",
         "error_ban_user": "S’u arrit të dëbohej përdoruesi",
         "error_deactivate": "S’u arrit të çaktivizohet përdorues",
         "error_kicking_user": "S’u arrit të hiqej përdoruesi",
         "error_mute_user": "S’u arrit t’i hiqej zëri përdoruesit",
         "error_revoke_3pid_invite_description": "S’u shfuqizua dot ftesa. Shërbyesi mund të jetë duke kaluar një problem të përkohshëm ose s’keni leje të mjaftueshme për të shfuqizuar ftesën.",
         "error_revoke_3pid_invite_title": "S’u arrit të shfuqizohej ftesa",
-        "hide_sessions": "Fshih sesione",
-        "hide_verified_sessions": "Fshih sesione të verifikuar",
         "ignore_confirm_description": "Krejt mesazhet dhe ftesat prej këtij përdoruesi do të fshihen. Jeni i sigurt se doni të shpërfillet?",
         "ignore_confirm_title": "Shpërfille %(user)s",
         "invited_by": "Ftuar nga %(sender)s",
@@ -3371,7 +3274,6 @@
         "hide_sidebar_button": "Fshihe anështyllën",
         "input_devices": "Pajisje input-i",
         "join_button_tooltip_call_full": "Na ndjeni — aktualisht kjo thirrje është plot",
-        "join_button_tooltip_connecting": "Po lidhet",
         "maximise": "Mbushe ekranin",
         "misconfigured_server": "Thirrja dështoi për shkak shërbyesi të keqformësuar",
         "misconfigured_server_description": "Që thirrjet të funksionojnë pa probleme, ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj Home (<code>%(homeserverDomain)s</code>) të formësojë një shërbyes TURN.",
diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json
index e8df5df5dae86544bdad0c9c6dd338d14bfdcb84..1715f2276fd6c452f442933259f83cc6e726012b 100644
--- a/src/i18n/strings/sv.json
+++ b/src/i18n/strings/sv.json
@@ -12,6 +12,16 @@
             "one": "1 oläst omnämnande."
         },
         "recent_rooms": "Nyliga rum",
+        "room_messsage_not_sent": "Öppna rummet %(roomName)s med ett osänt meddelande.",
+        "room_n_unread_invite": "Öppna inbjudan till rummet %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Öppna rummet %(roomName)s med 1 oläst meddelande.",
+            "other": "Öppna rummet %(roomName)s med %(count)s olästa meddelanden."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Öppna rummet %(roomName)s med 1 oläst omnämnande.",
+            "other": "Öppna rummet %(roomName)s med %(count)s olästa meddelanden inklusive omnämnanden."
+        },
         "room_name": "Rum %(name)s",
         "room_status_bar": "Rumsstatusfält",
         "seek_bar_label": "Förloppsfält för ljud",
@@ -45,6 +55,8 @@
         "create_a_room": "Skapa ett rum",
         "create_account": "Skapa konto",
         "decline": "Avvisa",
+        "decline_and_block": "Avvisa och blockera",
+        "decline_invite": "Avböj inbjudan",
         "delete": "Radera",
         "deny": "Neka",
         "disable": "Inaktivera",
@@ -64,6 +76,7 @@
         "go": "Gå",
         "go_back": "Gå tillbaka",
         "got_it": "Uppfattat",
+        "hide": "Dölj",
         "hide_advanced": "Dölj avancerat",
         "hold": "Parkera",
         "ignore": "Ignorera",
@@ -80,12 +93,14 @@
         "maximise": "Maximera",
         "mention": "Nämn",
         "minimise": "Minimera",
+        "new_message": "Nytt meddelande",
         "new_room": "Nytt rum",
         "new_video_room": "Nytt videorum",
         "next": "Nästa",
         "no": "Nej",
         "ok": "OK",
         "open": "Öppna",
+        "open_menu": "Öppna menyn",
         "pause": "Pausa",
         "pin": "Häftstift",
         "play": "Spela",
@@ -94,13 +109,13 @@
         "react": "Reagera",
         "refresh": "Ladda om",
         "register": "Registrera",
-        "reject": "Avböj",
         "reload": "Ladda om",
         "remove": "Ta bort",
         "rename": "Döp om",
         "reply": "Svara",
         "reply_in_thread": "Svara i tråd",
         "report_content": "Rapportera innehåll",
+        "report_room": "Anmäl rum",
         "resend": "Skicka igen",
         "reset": "Återställ",
         "resume": "Återuppta",
@@ -405,7 +420,15 @@
         "download_logs": "Ladda ner loggar",
         "downloading_logs": "Laddar ner loggar",
         "error_empty": "Berätta vad som gick fel, eller skapa ännu hellre ett GitHub-ärende som beskriver problemet.",
-        "failed_send_logs": "Misslyckades att skicka loggar: ",
+        "failed_download_logs": "Misslyckades att ladda ner felsökningsloggar: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Din felrapport avvisades. Raseriskak-servern stöder inte den här applikationen.",
+            "rejected_generic": "Din felrapport avvisades. Raseriskak-servern avvisade innehållet i rapporten på grund av en policy.",
+            "rejected_recovery_key": "Din felrapport avvisades av säkerhetsskäl, eftersom den innehöll en återställningsnyckel.",
+            "rejected_version": "Din felrapport avvisades eftersom versionen du kör är för gammal.",
+            "server_unknown_error": "Raseriskak-servern stötte på ett okänt fel och kunde inte hantera rapporten.",
+            "unknown_error": "Misslyckades att skicka loggar."
+        },
         "github_issue": "GitHub-ärende",
         "introduction": "Om du har rapporterat en bugg via GitHub så kan felsökningsloggar hjälpa oss att hitta problemet. ",
         "log_request": "För att hjälpa oss att förhindra detta i framtiden, vänligen <a>skicka oss loggar</a>.",
@@ -445,7 +468,7 @@
         "access_token": "Åtkomsttoken",
         "accessibility": "Tillgänglighet",
         "advanced": "Avancerat",
-        "all_rooms": "Alla rum",
+        "all_chats": "Alla chattar",
         "analytics": "Statistik",
         "and_n_others": {
             "other": "och %(count)s andra…",
@@ -464,7 +487,6 @@
         "capabilities": "Förmågor",
         "copied": "Kopierat!",
         "credits": "Medverkande",
-        "cross_signing": "Korssignering",
         "dark": "Mörkt",
         "description": "Beskrivning",
         "deselect_all": "Välj bort alla",
@@ -495,7 +517,6 @@
         "legal": "Juridiskt",
         "light": "Ljust",
         "loading": "Laddar …",
-        "lobby": "Lobby",
         "location": "Plats",
         "low_priority": "Låg prioritet",
         "matrix": "Matrix",
@@ -504,6 +525,7 @@
         "message_timestamp_invalid": "Ogiltig tidsstämpel",
         "microphone": "Mikrofon",
         "model": "Modell",
+        "moderation_and_safety": "Moderering och säkerhet",
         "modern": "Modernt",
         "mute": "Tysta",
         "n_members": {
@@ -539,6 +561,7 @@
         "qr_code": "QR-kod",
         "random": "Slumpmässig",
         "reactions": "Reaktioner",
+        "recommended": "Rekommenderad",
         "report_a_bug": "Rapportera en bugg",
         "room": "Rum",
         "room_name": "Rumsnamn",
@@ -547,7 +570,6 @@
         "saved": "Sparat",
         "saving": "Sparar …",
         "secure_backup": "Säker säkerhetskopiering",
-        "security": "Säkerhet",
         "select_all": "Välj alla",
         "server": "Server",
         "settings": "Inställningar",
@@ -566,7 +588,6 @@
         "thread": "Tråd",
         "threads": "Trådar",
         "timeline": "Tidslinje",
-        "trusted": "Betrodd",
         "unavailable": "otillgänglig",
         "unencrypted": "Okrypterat",
         "unmute": "Avtysta",
@@ -726,6 +747,13 @@
         "twemoji": "Emojigrafiken <twemoji>Twemoji</twemoji> är © <author>Twitter, Inc och andra bidragsgivare</author> och används under villkoren i <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "Teckensnittet <colr>twemoji-colr</colr> är © <author>Mozilla Foundation</author> och används under villkoren för <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Är du säker på att du vill avböja inbjudan att gå med i \"%(roomName)s\"?",
+        "ignore_user_help": "Du kommer inte att se några meddelanden eller rumsinbjudningar från den här användaren.",
+        "reason_description": "Beskriv anledningen att anmäla rummet.",
+        "report_room_description": "Rapportera det här rummet till din kontoleverantör.",
+        "title": "Avböj inbjudan"
+    },
     "desktop_default_device_name": "%(brand)s Skrivbord: %(platformName)s",
     "devtools": {
         "active_widgets": "Aktiva widgets",
@@ -733,6 +761,44 @@
         "category_room": "Rum",
         "caution_colon": "Varning:",
         "client_versions": "Klientversioner",
+        "crypto": {
+            "4s_public_key_in_account_data": "i kontouppgifter",
+            "4s_public_key_not_in_account_data": "Hittades inte",
+            "4s_public_key_status": "Publik nyckel för hemlig lagring:",
+            "backup_key_cached": "cachad lokalt",
+            "backup_key_cached_status": "Säkerhetskopierad nyckel cachad:",
+            "backup_key_not_stored": "inte lagrad",
+            "backup_key_stored": "i hemlig lagring",
+            "backup_key_stored_status": "Säkerhetskopieringsnyckel lagrad:",
+            "backup_key_unexpected_type": "oväntad typ",
+            "backup_key_well_formed": "välformad",
+            "cross_signing": "Korssignering",
+            "cross_signing_cached": "cachad lokalt",
+            "cross_signing_not_ready": "Korssignering är inte konfigurerad.",
+            "cross_signing_private_keys_in_storage": "i hemlig lagring",
+            "cross_signing_private_keys_in_storage_status": "Privata nycklar för korssignering:",
+            "cross_signing_private_keys_not_in_storage": "hittades inte i lagring",
+            "cross_signing_public_keys_on_device": "i minnet",
+            "cross_signing_public_keys_on_device_status": "Korssignerade publika nycklar:",
+            "cross_signing_ready": "Korssignering är klar för användning.",
+            "cross_signing_status": "Korssigneringsstatus:",
+            "cross_signing_untrusted": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men den är ännu inte betrodd av den här sessionen.",
+            "crypto_not_available": "Kryptografisk modul är inte tillgänglig",
+            "key_backup_active_version": "Aktiv säkerhetskopiaversion:",
+            "key_backup_active_version_none": "Ingen",
+            "key_backup_inactive_warning": "Dina nycklar säkerhetskopieras inte från den här sessionen.",
+            "key_backup_latest_version": "Senast version av säkerhetskopia på servern:",
+            "key_storage": "Nyckellagring",
+            "master_private_key_cached_status": "Privat huvudnyckel:",
+            "not_found": "hittades inte",
+            "not_found_locally": "hittades inte lokalt",
+            "secret_storage_not_ready": "inte klar",
+            "secret_storage_ready": "klar",
+            "secret_storage_status": "Hemlig lagring:",
+            "self_signing_private_key_cached_status": "Självsignerande privat nyckel:",
+            "title": "Totalsträckskryptering",
+            "user_signing_private_key_cached_status": "Privat nyckel för användarsignering:"
+        },
         "developer_mode": "Utvecklarläge",
         "developer_tools": "Utvecklarverktyg",
         "edit_setting": "Redigera inställningar",
@@ -788,6 +854,9 @@
         "setting_colon": "Inställning:",
         "setting_definition": "Inställningsdefinition:",
         "setting_id": "Inställnings-ID",
+        "settings": {
+            "elementCallUrl": "Element Call-URL"
+        },
         "settings_explorer": "Inställningar",
         "show_hidden_events": "Visa dolda händelser i tidslinjen",
         "spaces": {
@@ -840,52 +909,35 @@
     "empty_room_was_name": "Tomt rum (var %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Ange din säkerhetsfras eller <button>använd din säkerhetsnyckel</button> för att fortsätta.",
             "key_validation_text": {
-                "invalid_security_key": "Ogiltig säkerhetsnyckel",
-                "recovery_key_is_correct": "Ser bra ut!",
-                "wrong_file_type": "Fel filtyp",
                 "wrong_security_key": "Fel säkerhetsnyckel"
             },
-            "reset_title": "Återställ allt",
-            "reset_warning_1": "Gör detta endast om du inte har någon annan enhet att slutföra verifikationen med.",
-            "reset_warning_2": "Om du återställer allt så kommer du att börja om utan betrodda sessioner eller betrodda användare, och kommer kanske inte kunna se gamla meddelanden.",
             "restoring": "Återställer nycklar från säkerhetskopia",
-            "security_key_title": "Säkerhetsnyckel",
-            "security_phrase_incorrect_error": "Kan inte komma åt hemlig lagring. Vänligen verifiera att du angav rätt säkerhetsfras.",
-            "security_phrase_title": "Säkerhetsfras",
-            "separator": "%(securityKey)s eller %(recoveryFile)s",
-            "use_security_key_prompt": "Använd din säkerhetsnyckel för att fortsätta."
+            "security_key_title": "Säkerhetsnyckel"
         },
         "bootstrap_title": "Sätter upp nycklar",
         "cancel_entering_passphrase_description": "Är du säker på att du vill avbryta inmatning av lösenfrasen?",
         "cancel_entering_passphrase_title": "Avbryta inmatning av lösenfras?",
         "confirm_encryption_setup_body": "Klicka på knappen nedan för att bekräfta inställning av kryptering.",
         "confirm_encryption_setup_title": "Bekräfta krypteringsinställning",
-        "cross_signing_not_ready": "Korssignering är inte inställt.",
-        "cross_signing_ready": "Korssignering är klart att användas.",
-        "cross_signing_ready_no_backup": "Korssignering är klart, men nycklarna är inte säkerhetskopierade än.",
         "cross_signing_room_normal": "Det här rummet är totalsträckskrypterat",
         "cross_signing_room_verified": "Alla i det här rummet är verifierade",
         "cross_signing_room_warning": "Någon använder en okänd session",
-        "cross_signing_unsupported": "Din hemserver stöder inte korssignering.",
-        "cross_signing_untrusted": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men den är inte betrodd av den här sessionen än.",
         "cross_signing_user_normal": "Du har inte verifierat den här användaren.",
         "cross_signing_user_verified": "Du har verifierat den här användaren. Den här användaren har verifierat alla sina sessioner.",
         "cross_signing_user_warning": "Den här användaren har inte verifierat alla sina sessioner.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Rensa korssigneringsnycklar",
-            "title": "Förstöra korssigneringsnycklar?",
-            "warning": "Radering av korssigneringsnycklar är permanent. Alla du har verifierat med kommer att se säkerhetsvarningar. Du vill troligen inte göra detta, såvida du inte har tappat bort alla enheter du kan korssignera från."
-        },
+        "enter_recovery_key": "Ange återställningsnyckel",
         "event_shield_reason_authenticity_not_guaranteed": "Det krypterade meddelandets äkthet kan inte garanteras på den här enheten.",
         "event_shield_reason_mismatched_sender_key": "Krypterat av en overifierad session",
         "event_shield_reason_unknown_device": "Krypterad av en okänd eller raderad enhet.",
         "event_shield_reason_unsigned_device": "Krypterad av en enhet som inte har verifierats av dess ägare.",
         "event_shield_reason_unverified_identity": "Krypterad av en overifierad användare.",
         "export_unsupported": "Din webbläsare stödjer inte nödvändiga kryptografitillägg",
+        "forgot_recovery_key": "Har du glömt återställningsnyckeln?",
         "import_invalid_keyfile": "Inte en giltig %(brand)s-nyckelfil",
         "import_invalid_passphrase": "Autentiseringskontroll misslyckades: felaktigt lösenord?",
+        "key_storage_out_of_sync": "Din nyckellagring är inte synkroniserad.",
+        "key_storage_out_of_sync_description": "Bekräfta din återställningsnyckel för att behålla åtkomsten till din nyckellagring och meddelandehistorik.",
         "messages_not_secure": {
             "cause_1": "Din hemserver",
             "cause_2": "Hemservern användaren du verifierar är ansluten till",
@@ -900,7 +952,6 @@
             "title": "Ny återställningsmetod",
             "warning": "Om du inte har ställt in den nya återställningsmetoden kan en angripare försöka komma åt ditt konto. Byt ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna."
         },
-        "not_supported": "<stöds inte>",
         "pinned_identity_changed": "%(displayName)ss (<b>%(userId)s</b> ) identitet verkar ha ändrats.<a> Läs mer</a>",
         "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>:s identitet verkar ha ändrats. <a>Läs mer</a>",
         "recovery_method_removed": {
@@ -910,11 +961,13 @@
             "warning": "Om du inte tog bort återställningsmetoden kan en angripare försöka komma åt ditt konto. Byt ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna."
         },
         "reset_all_button": "Glömt eller förlorat alla återställningsalternativ? <a>Återställ allt</a>",
+        "set_up_recovery": "Ställ in återställning",
+        "set_up_recovery_later": "Inte nu",
+        "set_up_recovery_toast_description": "Generera en återställningsnyckel som kan användas för att återställa din krypterade meddelandehistorik om du förlorar åtkomst till dina enheter.",
         "set_up_toast_description": "Skydda mot att förlora åtkomst till krypterade meddelanden och data",
         "set_up_toast_title": "Ställ in säker säkerhetskopiering",
         "setup_secure_backup": {
-            "explainer": "Säkerhetskopiera dina nycklar innan du loggar ut för att undvika att du blir av med dem.",
-            "title": "Sätt upp"
+            "explainer": "Säkerhetskopiera dina nycklar innan du loggar ut för att undvika att du blir av med dem."
         },
         "udd": {
             "interactive_verification_button": "Verifiera interaktivt med emoji",
@@ -925,12 +978,10 @@
             "title": "Inte betrodd"
         },
         "unable_to_setup_keys_error": "Kunde inte ställa in nycklar",
-        "unsupported": "Den här klienten stöder inte totalsträckskryptering.",
         "verification": {
             "accepting": "Accepterar…",
             "after_new_login": {
                 "device_verified": "Enhet verifierad",
-                "reset_confirmation": "Återställ verkligen verifieringsnycklar?",
                 "skip_verification": "Hoppa över verifiering för tillfället",
                 "unable_to_verify": "Kunde inte verifiera den här enheten",
                 "verify_this_device": "Verifiera den här enheten"
@@ -1001,8 +1052,6 @@
             "verify_emoji_prompt": "Verifiera genom att jämföra unika emojier.",
             "verify_emoji_prompt_qr": "Om du inte kan skanna koden ovan, verifiera genom att jämföra unika emojier.",
             "verify_later": "Jag verifierar senare",
-            "verify_reset_warning_1": "Återställning av dina verifieringsnycklar kan inte ångras. Efter återställning så kommer du inte att komma åt dina krypterade meddelanden, och alla vänner som tidigare har verifierat dig kommer att se säkerhetsvarningar tills du återverifierar med dem.",
-            "verify_reset_warning_2": "Fortsätt bara om du är säker på att du har förlorat alla dina övriga enheter och din säkerhetsnyckel.",
             "verify_using_device": "Verifiera med annan enhet",
             "verify_using_key": "Verifiera med säkerhetsnyckel",
             "verify_using_key_or_phrase": "Verifiera med säkerhetsnyckel eller -fras",
@@ -1012,8 +1061,11 @@
             "waiting_other_user": "Väntar på att %(displayName)s ska verifiera…"
         },
         "verification_requested_toast_title": "Verifiering begärd",
+        "verified_identity_changed": "Verifierad identitet för %(displayName)s (<b>%(userId)s</b>) har ändrats. <a>Läs mer </a>",
+        "verified_identity_changed_no_displayname": "Verifierad identitet för <b>%(userId)s</b> har ändrats. <a>Läs mer </a>",
         "verify_toast_description": "Andra användare kanske inta litar på den",
-        "verify_toast_title": "Verifiera denna session"
+        "verify_toast_title": "Verifiera denna session",
+        "withdraw_verification_action": "Återkalla verifieringen"
     },
     "error": {
         "admin_contact": "Vänligen <a>kontakta din tjänstadministratör</a> för att fortsätta använda tjänsten.",
@@ -1068,11 +1120,7 @@
             "title": "Kunde inte kopiera rumslänken"
         },
         "error_loading_user_profile": "Kunde inte ladda användarprofil",
-        "forget_room_failed": "Misslyckades att glömma bort rummet %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Servern kan vara otillgänglig eller överbelastad, eller så tog sökningen för lång tid :(",
-            "title": "Sökning misslyckades"
-        }
+        "forget_room_failed": "Misslyckades att glömma bort rummet %(errCode)s"
     },
     "error_user_not_logged_in": "Användaren är inte inloggad",
     "event_preview": {
@@ -1197,6 +1245,7 @@
         "change": "Byt identitetsserver",
         "change_prompt": "Koppla ifrån från identitetsservern <current /> och anslut till <new /> istället?",
         "change_server_prompt": "Om du inte vill använda <server /> för att upptäcka och upptäckas av befintliga kontakter som du känner, ange en annan identitetsserver nedan.",
+        "changed": "Din identitetsserver har ändrats",
         "checking": "Kontrollerar servern",
         "description_connected": "Du använder för närvarande <server></server> för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan byta din identitetsserver nedan.",
         "description_disconnected": "Du använder för närvarande inte en identitetsserver. Lägg till en nedan om du vill upptäcka och bli upptäckbar av befintliga kontakter som du känner.",
@@ -1240,7 +1289,8 @@
         "use_desktop_heading": "Använd %(brand)s skrivbord istället",
         "use_mobile_heading": "Använd %(brand)s på mobilen istället",
         "use_mobile_heading_after_desktop": "Eller använd vår mobilapp",
-        "windows": "Windows (%(bits)s -bit)"
+        "windows_64bit": "Windows (64-bitars)",
+        "windows_arm_64bit": "Windows (ARM 64-bitars)"
     },
     "info_tooltip_title": "Information",
     "integration_manager": {
@@ -1249,6 +1299,7 @@
         "error_connecting_heading": "Kan inte ansluta till integrationshanteraren",
         "explainer": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.",
         "manage_title": "Hantera integrationer",
+        "toggle_label": "Aktivera integrationshanteraren",
         "use_im": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.",
         "use_im_default": "Använd en integrationshanterare <b>(%(serverName)s)</b> för att hantera bottar, widgets och dekalpaket."
     },
@@ -1450,6 +1501,7 @@
         "location_share_live_description": "Temporär implementation. Platser stannar kvar i rumshistoriken.",
         "mjolnir": "Nya sätt att ignorera personer",
         "msc3531_hide_messages_pending_moderation": "Låt moderatorer dölja meddelanden i väntan på moderering.",
+        "new_room_list": "Aktivera ny rumslista",
         "notification_settings": "Nya aviseringsinställningar",
         "notification_settings_beta_caption": "Vi introducerar ett enklare sätt att ändra dina aviseringsinställningar. Anpassa din %(brand)s precis som du vill ha den.",
         "notification_settings_beta_title": "Aviseringsinställningar",
@@ -1573,8 +1625,14 @@
         "toggle_attribution": "Växla tillskrivning"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s medlem",
+            "other": "%(count)s medlemmar"
+        },
         "filter_placeholder": "Filtrera rumsmedlemmar",
         "invite_button_no_perms_tooltip": "Du är inte behörig att bjuda in användare",
+        "invited_label": "Inbjuden",
+        "no_matches": "Inga matchningar",
         "power_label": "%(userName)s (behörighet %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Rumsmedlemmar",
@@ -1597,6 +1655,7 @@
         "class_global": "Globalt",
         "class_other": "Annat",
         "default": "Standard",
+        "default_settings": "Matcha standardinställningar",
         "email_pusher_app_display_name": "E-postaviseringar",
         "enable_prompt_toast_description": "Aktivera skrivbordsaviseringar",
         "enable_prompt_toast_title": "Aviseringar",
@@ -1615,7 +1674,8 @@
         "mentions_and_keywords_description": "Bli endast aviserad om omnämnanden och nyckelord i enlighet med dina <a>inställningar</a>",
         "mentions_keywords": "Omnämnanden & nyckelord",
         "message_didnt_send": "Meddelande skickades inte. Klicka för info.",
-        "mute_description": "Du får inga aviseringar"
+        "mute_description": "Du får inga aviseringar",
+        "mute_room": "Tysta rum"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s begär verifiering"
@@ -1717,11 +1777,6 @@
         "ongoing": "Tar bort…",
         "reason_label": "Orsak (valfritt)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Är du säker på att du vill avböja inbjudan?",
-        "failed": "Misslyckades att avböja inbjudan",
-        "title": "Avböj inbjudan"
-    },
     "report_content": {
         "description": "Att rapportera det här meddelandet kommer att skicka dess unika 'händelse-ID' till administratören för din hemserver. Om meddelanden i det här rummet är krypterade kommer din hemserveradministratör inte att kunna läsa meddelandetexten eller se några filer eller bilder.",
         "disagree": "Håll inte med",
@@ -1744,6 +1799,10 @@
         "spam_or_propaganda": "Spam eller propaganda",
         "toxic_behaviour": "Stötande beteende"
     },
+    "report_room": {
+        "description": "Rapportera det här rummet till din hemserveradministratör. Detta skickar rummets unika ID, men om meddelanden är krypterade kan administratören inte läsa dem eller visa delade filer.",
+        "reason_label": "Beskriv skälet"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Misslyckades att avkryptera %(failedCount)s sessioner!",
         "count_of_successfully_restored_keys": "Återställde framgångsrikt %(sessionCount)s nycklar",
@@ -1926,7 +1985,6 @@
             "you_created": "Du skapade det här rummet."
         },
         "invite_email_mismatch_suggestion": "Dela denna e-postadress i inställningarna för att motta inbjudningar direkt i %(brand)s.",
-        "invite_reject_ignore": "Avvisa och ignorera användare",
         "invite_sent_to_email": "Denna inbjudan skickades till %(email)s",
         "invite_sent_to_email_room": "Denna inbjudan till %(roomName)s skickades till %(email)s",
         "invite_subtitle": "Inbjuden av <userName/>",
@@ -2022,29 +2080,67 @@
             },
             "uploading_single_file": "Laddar upp %(filename)s"
         },
+        "video_room": "Det här rummet är ett videorum",
         "waiting_for_join_subtitle": "När inbjudna användare har gått med i %(brand)s kommer du att kunna chatta och rummet kommer att vara totalsträckskrypterat",
         "waiting_for_join_title": "Väntar på att användare går med i %(brand)s"
     },
     "room_list": {
         "add_room_label": "Lägg till rum",
         "add_space_label": "Lägg till utrymme",
+        "appearance": "Utseende",
         "breadcrumbs_empty": "Inga nyligen besökta rum",
         "breadcrumbs_label": "Nyligen besökta rum",
+        "empty": {
+            "no_chats": "Inga chattar än",
+            "no_chats_description": "Kom igång genom att skicka meddelanden till någon eller genom att skapa ett rum",
+            "no_chats_description_no_room_rights": "Kom igång genom att skicka meddelanden till någon",
+            "no_favourites": "Du har ingen favoritchatt än",
+            "no_favourites_description": "Du kan lägga till en chatt till dina favoriter i chattinställningarna",
+            "no_people": "Du har inte direktchattar med någon ännu",
+            "no_people_description": "Du kan avmarkera filter för att se dina andra chattar",
+            "no_rooms": "Du är inte i något rum än",
+            "no_rooms_description": "Du kan avmarkera filter för att se dina andra chattar",
+            "no_unread": "Grattis! Du har inga olästa meddelanden",
+            "show_chats": "Visa alla chattar"
+        },
         "failed_add_tag": "Misslyckades att lägga till etiketten %(tagName)s till rummet",
         "failed_remove_tag": "Misslyckades att radera etiketten %(tagName)s från rummet",
         "failed_set_dm_tag": "Misslyckades att sätta direktmeddelandetagg",
+        "filters": {
+            "favourite": "Favoriter",
+            "people": "Personer",
+            "rooms": "Rum",
+            "unread": "Olästa"
+        },
         "home_menu_label": "Hemalternativ",
         "join_public_room_label": "Gå med i offentligt rum",
         "joining_rooms_status": {
             "one": "Går just nu med i %(count)s rum",
             "other": "Går just nu med i %(count)s rum"
         },
+        "list_title": "Rumslista",
+        "more_options": {
+            "copy_link": "Kopiera rumslänk",
+            "favourited": "Favoritmarkerad",
+            "leave_room": "Lämna rum",
+            "low_priority": "Låg prioritet",
+            "mark_read": "Markera som läst",
+            "mark_unread": "Markera som oläst"
+        },
         "notification_options": "Aviseringsinställningar",
+        "open_space_menu": "Öppna utrymmesmenyn",
+        "primary_filters": "Filter för rumslista",
         "redacting_messages_status": {
             "one": "Tar just nu bort meddelanden i %(count)s rum",
             "other": "Tar just nu bort meddelanden i %(count)s rum"
         },
+        "room": {
+            "more_options": "Fler alternativ",
+            "open_room": "Öppet rummet %(roomName)s"
+        },
+        "room_options": "Rumsalternativ",
         "show_less": "Visa mindre",
+        "show_message_previews": "Visa förhandsgranskningar av meddelanden",
         "show_n_more": {
             "other": "Visa %(count)s till",
             "one": "Visa %(count)s till"
@@ -2054,6 +2150,10 @@
         "sort_by_activity": "Aktivitet",
         "sort_by_alphabet": "A-Ö",
         "sort_unread_first": "Visa rum med olästa meddelanden först",
+        "space_menu": {
+            "home": "Utrymmeshem",
+            "space_settings": "Utrymmesinställningar"
+        },
         "space_menu_label": "%(spaceName)s-alternativ",
         "sublist_options": "Listalternativ",
         "suggested_rooms_heading": "Föreslagna rum"
@@ -2406,6 +2506,72 @@
         "emoji_autocomplete": "Aktivera emojiförslag medan du skriver",
         "enable_markdown": "Aktivera Markdown",
         "enable_markdown_description": "Börja meddelanden med <code>/plain</code> för att skicka utan markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Dina kontouppgifter, kontakter, inställningar och chattlista sparas",
+                "breadcrumb_page": "Återställ kryptering",
+                "breadcrumb_second_description": "Du kommer att förlora all meddelandehistorik som bara lagras på servern",
+                "breadcrumb_third_description": "Du måste verifiera alla dina befintliga enheter och kontakter igen",
+                "breadcrumb_title": "Är du säker på att du vill återställa din identitet?",
+                "breadcrumb_title_forgot": "Glömt din återställningsnyckel? Du måste återställa din identitet.",
+                "breadcrumb_title_sync_failed": "Misslyckades att synkronisera nyckellagringen. Du måste återställa din identitet.",
+                "breadcrumb_warning": "Gör bara detta om du tror att ditt konto har utsatts för intrång.",
+                "details_title": "Krypteringsdetaljer",
+                "do_not_close_warning": "Stäng inte fönstret förrän återställningen är klar",
+                "export_keys": "Exportera nycklar",
+                "import_keys": "Importera nycklar",
+                "other_people_device_description": "Som standard i krypterade rum, skicka inte krypterade meddelanden till någon förrän du har verifierat dem",
+                "other_people_device_label": "Skicka aldrig krypterade meddelanden till obekräftade enheter",
+                "other_people_device_title": "Andras enheter",
+                "reset_identity": "Återställ kryptografisk identitet",
+                "reset_in_progress": "Återställning pågår …",
+                "session_id": "Sessions-ID:",
+                "session_key": "Sessionsnyckel:",
+                "title": "Avancerad"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Radera nyckellagring",
+                "confirm": "Radera nyckellagring",
+                "description": "Om du raderar nyckelförvaringen tas din kryptografiska identitet och dina meddelandenycklar bort från servern och följande säkerhetsfunktioner stängs av:",
+                "list_first": "Du kommer inte att ha krypterad meddelandehistorik på nya enheter",
+                "list_second": "Du kommer att förlora åtkomst till dina krypterade meddelanden om du loggas ut från %(brand)s överallt",
+                "title": "Är du säker på att du vill stänga av nyckellagring och radera den?"
+            },
+            "device_not_verified_button": "Verifiera den här enheten",
+            "device_not_verified_description": "Du måste verifiera den här enheten för att se dina krypteringsinställningar.",
+            "device_not_verified_title": "Enhet inte verifierad",
+            "dialog_title": "<strong>Inställningar:</strong> Kryptering",
+            "key_storage": {
+                "allow_key_storage": "Tillåt nyckellagring",
+                "description": "Lagra din kryptografiska identitet och dina meddelandenycklar säkert på servern. Detta gör att du kan se din meddelandehistorik på alla nya enheter. <a>Läs mer</a>",
+                "title": "Nyckellagring"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Bekräfta ny återställningsnyckel",
+                "change_recovery_confirm_description": "Ange din nya återställningsnyckel nedan för att slutföra. Din gamla kommer inte att funka längre.",
+                "change_recovery_confirm_title": "Ange din nya återställningsnyckel",
+                "change_recovery_key": "Byt återställningsnyckel",
+                "change_recovery_key_description": "Skriv ner den här nya återställningsnyckeln någonstans säkert. Klicka sedan på Fortsätt för att bekräfta ändringen.",
+                "change_recovery_key_title": "Byt återställningsnyckel?",
+                "description": "Återställ din kryptografiska identitet och meddelandehistorik med en återställningsnyckel om du har tappat bort alla dina befintliga enheter.",
+                "enter_key_error": "Återställningsnyckeln du angav är inte korrekt.",
+                "enter_recovery_key": "Ange återställningsnyckel",
+                "forgot_recovery_key": "Glömt återställningsnyckeln?",
+                "key_storage_warning": "Din nyckellagring är inte synkroniserad. Klicka på knappen nedan för att åtgärda problemet.",
+                "save_key_description": "Dela inte detta med någon!",
+                "save_key_title": "Återställningsnyckel",
+                "set_up_recovery": "Konfigurera återställning",
+                "set_up_recovery_confirm_button": "Slutför inställningen",
+                "set_up_recovery_confirm_description": "Ange återställningsnyckeln som visas på föregående skärm för att slutföra inställningen av återställningen.",
+                "set_up_recovery_confirm_title": "Ange din återställningsnyckel för att bekräfta",
+                "set_up_recovery_description": "Din nyckellagring skyddas av en återställningsnyckel. Om du behöver en ny återställningsnyckel efter installationen kan du återskapa den genom att välja ”%(changeRecoveryKeyButton)s”.",
+                "set_up_recovery_save_key_description": "Skriv ner den här återställningsnyckeln någonstans säkert, som en lösenordshanterare, krypterad anteckning eller ett fysiskt kassaskåp.",
+                "set_up_recovery_save_key_title": "Spara din återställningsnyckel på ett säkert ställe",
+                "set_up_recovery_secondary_description": "När du har klickat på Fortsätt genererar vi en återställningsnyckel åt dig.",
+                "title": "Återställa"
+            },
+            "title": "Kryptering"
+        },
         "general": {
             "account_management_section": "Kontohantering",
             "account_section": "Konto",
@@ -2419,7 +2585,7 @@
             "add_msisdn_instructions": "Ett SMS har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.",
             "add_msisdn_misconfigured": "Flöde för tilläggning/bindning med MSISDN är felkonfigurerat",
             "allow_spellcheck": "Tillåt stavningskontroll",
-            "application_language": "Språk för ansökan",
+            "application_language": "Applikationsspråk",
             "application_language_reload_hint": "Appen laddas om efter att ha valt ett annat språk",
             "avatar_remove_progress": "Tar bort bild...",
             "avatar_save_progress": "Laddar upp bild...",
@@ -2491,7 +2657,6 @@
             "unable_to_load_msisdns": "Det går inte att läsa in telefonnummer",
             "username": "Användarnamn"
         },
-        "image_thumbnails": "Visa förhandsgranskning/miniatyr för bilder",
         "inline_url_previews_default": "Aktivera inbäddad URL-förhandsgranskning som standard",
         "inline_url_previews_room": "Aktivera URL-förhandsgranskning som standard för deltagare i detta rum",
         "inline_url_previews_room_account": "Aktivera URL-förhandsgranskning för detta rum (påverkar bara dig)",
@@ -2554,6 +2719,14 @@
         "labs_mjolnir": {
             "dialog_title": "<strong>Inställningar:</strong> Ignorerade användare"
         },
+        "media_preview": {
+            "hide_avatars": "Dölj avatarer för rum och inbjudare",
+            "hide_media": "Dölj alltid",
+            "media_preview_description": "En dold media kan alltid visas genom att trycka på den",
+            "media_preview_label": "Visa media i tidslinjen",
+            "show_in_private": "I privata rum",
+            "show_media": "Visa alltid"
+        },
         "notifications": {
             "default_setting_description": "Denna inställning kommer att tillämpas som standard för alla dina rum.",
             "default_setting_section": "Jag vill bli meddelad för (Standardinställning)",
@@ -2639,57 +2812,20 @@
         "prompt_invite": "Fråga innan inbjudningar skickas till potentiellt ogiltiga Matrix-ID:n",
         "replace_plain_emoji": "Ersätt automatiskt textemotikoner med emojier",
         "security": {
-            "4s_public_key_in_account_data": "i kontodata",
-            "4s_public_key_status": "Publik nyckel för hemlig lagring:",
             "analytics_description": "Dela anonyma data för att hjälpa oss att identifiera problem. Inget personligt. Inga tredje parter.",
-            "backup_key_cached_status": "Cachad säkerhetskopieringsnyckel:",
-            "backup_key_stored_status": "Lagrad säkerhetskopieringsnyckel:",
-            "backup_key_unexpected_type": "oväntad typ",
-            "backup_key_well_formed": "välformaterad",
-            "backup_keys_description": "Säkerhetskopiera dina krypteringsnycklar med din kontodata ifall du skulle förlora åtkomst till dina sessioner. Dina nycklar kommer att säkras med en unik säkerhetsnyckel.",
             "bulk_options_accept_all_invites": "Acceptera alla %(invitedRooms)s inbjudningar",
             "bulk_options_reject_all_invites": "Avböj alla %(invitedRooms)s inbjudningar",
             "bulk_options_section": "Massalternativ",
-            "cross_signing_cached": "cachad lokalt",
-            "cross_signing_homeserver_support": "Hemserverns funktionsstöd:",
-            "cross_signing_homeserver_support_exists": "existerar",
-            "cross_signing_in_4s": "i hemlig lagring",
-            "cross_signing_in_memory": "i minne",
-            "cross_signing_master_private_Key": "Privat huvudnyckel:",
-            "cross_signing_not_cached": "inte hittad lokalt",
-            "cross_signing_not_found": "hittades inte",
-            "cross_signing_not_in_4s": "hittades inte i lagring",
-            "cross_signing_not_stored": "inte lagrad",
-            "cross_signing_private_keys": "Privata nycklar för korssignering:",
-            "cross_signing_public_keys": "Publika nycklar för korssignering:",
-            "cross_signing_self_signing_private_key": "Privat nyckel för självsignering:",
-            "cross_signing_user_signing_private_key": "Privat nyckel för användarsignering:",
-            "cryptography_section": "Kryptografi",
             "dehydrated_device_description": "Funktionen offlineenhet låter dig ta emot krypterade meddelanden även när du inte är inloggad på någon enhet",
             "dehydrated_device_enabled": "Offlineenhet aktiverad",
-            "delete_backup": "Radera säkerhetskopia",
-            "delete_backup_confirm_description": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.",
             "dialog_title": "<strong>Inställningar:</strong> Säkerhet och integritet",
             "e2ee_default_disabled_warning": "Din serveradministratör har inaktiverat totalsträckskryptering som förval för privata rum och direktmeddelanden.",
             "enable_message_search": "Aktivera meddelandesökning i krypterade rum",
             "encryption_section": "Kryptering",
-            "error_loading_key_backup_status": "Kunde inte ladda status för nyckelsäkerhetskopiering",
-            "export_megolm_keys": "Exportera krypteringsrumsnycklar",
             "ignore_users_empty": "Du har inga ignorerade användare.",
             "ignore_users_section": "Ignorerade användare",
-            "import_megolm_keys": "Importera rumskrypteringsnycklar",
-            "key_backup_active": "Den här sessionen säkerhetskopierar dina nycklar.",
-            "key_backup_active_version": "Aktiv säkerhetskopieringsversion:",
-            "key_backup_active_version_none": "Ingen",
             "key_backup_algorithm": "Algoritm:",
-            "key_backup_can_be_restored": "Säkerhetskopian kan återställas på den här sessionen",
-            "key_backup_complete": "Alla nycklar säkerhetskopierade",
             "key_backup_connect": "Anslut den här sessionen till nyckelsäkerhetskopiering",
-            "key_backup_connect_prompt": "Anslut den här sessionen till nyckelsäkerhetskopiering innan du loggar ut för att undvika att du blir av med nycklar som kanske bara finns på den här sessionen.",
-            "key_backup_in_progress": "Säkerhetskopierar %(sessionsRemaining)s nycklar …",
-            "key_backup_inactive": "Den här servern <b>säkerhetskopierar inte dina nycklar</b>, men du har en existerande säkerhetskopia du kan återställa ifrån och lägga till till i framtiden.",
-            "key_backup_inactive_warning": "Dina nycklar <b>säkerhetskopieras inte från den här sessionen</b>.",
-            "key_backup_latest_version": "Senaste säkerhetskopieringsversionen på servern:",
             "message_search_disable_warning": "Om den är inaktiverad visas inte meddelanden från krypterade rum i sökresultaten.",
             "message_search_disabled": "Cachar krypterade meddelanden säkert lokalt för att de ska visas i sökresultat.",
             "message_search_enabled": {
@@ -2709,13 +2845,7 @@
             "message_search_unsupported": "%(brand)s saknar vissa komponenter som krävs som krävs för att säkert cacha krypterade meddelanden lokalt. Om du vill experimentera med den här funktionen, bygg en anpassad %(brand)s Skrivbord med <nativeLink>sökkomponenter tillagda</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s kan inte säkert cacha krypterade meddelanden lokalt när den kör i en webbläsare. Använd <desktopLink>%(brand)s Skrivbord</desktopLink> för att krypterade meddelanden ska visas i sökresultaten.",
             "record_session_details": "Spara klientens namn, version och URL för att lättare känna igen sessioner i sessionshanteraren",
-            "restore_key_backup": "Återställ från säkerhetskopia",
-            "secret_storage_not_ready": "inte klart",
-            "secret_storage_ready": "klart",
-            "secret_storage_status": "Hemlig lagring:",
             "send_analytics": "Skicka statistik",
-            "session_id": "Sessions-ID:",
-            "session_key": "Sessionsnyckel:",
             "strict_encryption": "Skicka aldrig krypterade meddelanden till overifierade sessioner från den här sessionen"
         },
         "send_read_receipts": "Skicka läskvitton",
@@ -2764,6 +2894,7 @@
             "inactive_sessions_list_description": "Överväg att logga ut ur gamla sessioner (%(inactiveAgeDays)s dagar eller äldre) du inte använder längre.",
             "ip": "IP-adress",
             "last_activity": "Senaste aktiviteten",
+            "manage": "Hantera denna session",
             "mobile_session": "Mobil session",
             "n_sessions_selected": {
                 "one": "%(count)s session vald",
@@ -2957,8 +3088,6 @@
         "topic": "Hämtar eller sätter ämnet för ett rum",
         "topic_none": "Det här rummet har inget ämne.",
         "topic_room_error": "Misslyckades att hitta rumsämne: Kunde inte hitta rummet (%(roomId)s)",
-        "tovirtual": "Byter till det här rummets virtuella rum, om det har ett",
-        "tovirtual_not_found": "Inget virtuellt rum för det här rummet",
         "unban": "Avbannar användaren med givet ID",
         "unflip": "Lägger till ┬──┬ ノ( ゜-゜ノ) till början av ett textmeddelande",
         "unholdcall": "Avslutar parkering av samtalet i det nuvarande samtalet",
@@ -2976,6 +3105,7 @@
         "view": "Visar rum med den angivna adressen",
         "whois": "Visar information om en användare"
     },
+    "sliding_sync_legacy_no_longer_supported": "Äldre sliding sync stöds inte längre: logga ut och in igen för att aktivera den nya sliding sync-flaggan",
     "space": {
         "add_existing_room_space": {
             "create": "Vill du lägga till ett nytt rum istället?",
@@ -3080,7 +3210,6 @@
         "heading_without_query": "Sök efter",
         "join_button_text": "Gå med i %(roomAddress)s",
         "keyboard_scroll_hint": "Använd <arrows/> för att skrolla",
-        "message_search_section_title": "Andra sökningar",
         "other_rooms_in_space": "Andra rum i %(spaceName)s",
         "public_rooms_label": "Offentliga rum",
         "public_spaces_label": "Offentliga utrymmen",
@@ -3090,7 +3219,6 @@
         "result_may_be_hidden_privacy_warning": "Vissa resultat kan vara dolda av sekretesskäl",
         "result_may_be_hidden_warning": "Vissa resultat kanske är dolda",
         "search_dialog": "Sökdialog",
-        "search_messages_hint": "För att söka efter meddelanden, leta efter den här ikonen på toppen av ett rum <icon/>",
         "spaces_title": "Utrymmen du är med i",
         "start_group_chat_button": "Starta en gruppchatt"
     },
@@ -3139,9 +3267,7 @@
     "threads_activity_centre": {
         "header": "Aktivitet för trådar",
         "no_rooms_with_threads_notifs": "Du har inga rum med trådaviseringar ännu.",
-        "no_rooms_with_unread_threads": "Du har inga rum med olästa trådar än.",
-        "release_announcement_description": "Meddelanden om trådar har flyttats, du hittar dem här från och med nu.",
-        "release_announcement_header": "Aktivitetscenter för Trådar"
+        "no_rooms_with_unread_threads": "Du har inga rum med olästa trådar än."
     },
     "time": {
         "about_day_ago": "cirka en dag sedan",
@@ -3353,6 +3479,7 @@
             "left_reason": "%(targetName)s lämnade rummet: %(reason)s",
             "no_change": "%(senderName)s gjorde ingen ändring",
             "reject_invite": "%(targetName)s avböjde inbjudan",
+            "reject_invite_reason": "%(targetName)s avvisade inbjudan: %(reason)s",
             "remove_avatar": "%(senderName)s tog bort sin profilbild",
             "remove_name": "%(senderName)s tog bort sitt visningsnamn %(oldDisplayName)s",
             "set_avatar": "%(senderName)s satte en profilbild",
@@ -3388,10 +3515,14 @@
             "sent": "%(senderName)s bjöd in %(targetDisplayName)s att gå med i rummet."
         },
         "m.room.tombstone": "%(senderDisplayName)s uppgraderade det här rummet.",
-        "m.room.topic": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\".",
+            "removed": "%(senderDisplayName)s tog bort ämnet."
+        },
         "m.sticker": "%(senderDisplayName)s skickade en dekal.",
         "m.video": {
-            "error_decrypting": "Fel vid avkryptering av video"
+            "error_decrypting": "Fel vid avkryptering av video",
+            "show_video": "Visa video"
         },
         "m.widget": {
             "added": "%(widgetName)s-widget har lagts till av %(senderName)s",
@@ -3677,18 +3808,9 @@
         "ban_room_confirm_title": "Banna från %(roomName)s",
         "ban_space_everything": "Banna dem från allt jag kan",
         "ban_space_specific": "Banna dem från specifika saker jag kan",
-        "count_of_sessions": {
-            "other": "%(count)s sessioner",
-            "one": "%(count)s session"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s verifierade sessioner",
-            "one": "1 verifierad session"
-        },
         "deactivate_confirm_action": "Inaktivera användaren",
         "deactivate_confirm_description": "Vid inaktivering av användare loggas den ut och förhindras från att logga in igen. Den kommer dessutom att lämna alla rum den befinner sig i. Den här åtgärden kan inte ångras. Är du säker på att du vill inaktivera den här användaren?",
         "deactivate_confirm_title": "Inaktivera användare?",
-        "dehydrated_device_enabled": "Offlineenhet aktiverad",
         "demote_button": "Degradera",
         "demote_self_confirm_description_space": "Du kommer inte kunna ångra den här ändringen eftersom du degraderar dig själv, och om du är den sista privilegierade användaren i utrymmet så kommer det att vara omöjligt att återfå utrymmet.",
         "demote_self_confirm_room": "Du kommer inte att kunna ångra den här ändringen eftersom du degraderar dig själv. Om du är den sista privilegierade användaren i rummet blir det omöjligt att återfå behörigheter.",
@@ -3696,15 +3818,12 @@
         "disinvite_button_room": "Ta bort från rum",
         "disinvite_button_room_name": "Häv inbjudan från %(roomName)s",
         "disinvite_button_space": "Ta bort inbjudan från utrymme",
-        "edit_own_devices": "Redigera enheter",
         "error_ban_user": "Misslyckades att banna användaren",
         "error_deactivate": "Misslyckades att inaktivera användaren",
         "error_kicking_user": "Misslyckades att ta bort användare",
         "error_mute_user": "Misslyckades att tysta användaren",
         "error_revoke_3pid_invite_description": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckliga behörigheter för att återkalla inbjudan.",
         "error_revoke_3pid_invite_title": "Misslyckades att återkalla inbjudan",
-        "hide_sessions": "Dölj sessioner",
-        "hide_verified_sessions": "Dölj verifierade sessioner",
         "ignore_button": "Ignorera",
         "ignore_confirm_description": "Alla meddelanden och inbjudningar från den här användaren kommer att döljas. Är du säker på att du vill ignorera denne?",
         "ignore_confirm_title": "Ignorera %(user)s",
@@ -3748,6 +3867,7 @@
         "unban_space_specific": "Avbanna dem från specifika saker jag kan",
         "unban_space_warning": "Personen kommer inte kunna komma åt saker du inte är admin för.",
         "unignore_button": "Avignorera",
+        "verification_unavailable": "Användarverifiering inte tillgänglig",
         "verify_button": "Verifiera användare",
         "verify_explainer": "För extra säkerhet, verifiera den här användaren genom att kolla en engångskod på båda era enheter."
     },
@@ -3800,7 +3920,6 @@
         "input_devices": "Ingångsenheter",
         "jitsi_call": "Jitsi-gruppsamtal",
         "join_button_tooltip_call_full": "Tyvärr - det här samtalet är för närvarande fullt",
-        "join_button_tooltip_connecting": "Ansluter",
         "legacy_call": "Standardsamtal",
         "maximise": "Fyll skärmen",
         "maximise_call": "Maximera samtal",
diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json
new file mode 100644
index 0000000000000000000000000000000000000000..20cc904f17730311730431a6700b9a467b84b710
--- /dev/null
+++ b/src/i18n/strings/tr.json
@@ -0,0 +1,4060 @@
+{
+    "a11y": {
+        "emoji_picker": "Emoji seçici",
+        "jump_first_invite": "İlk davete git.",
+        "message_composer": "Mesaj oluşturucu",
+        "n_unread_messages": {
+            "other": "%(count)s okunmamış mesaj.",
+            "one": "1 okunmamış mesaj."
+        },
+        "n_unread_messages_mentions": {
+            "one": "1 okunmamış bahis.",
+            "other": "anmalar dahil okunmayan %(count)s mesaj."
+        },
+        "recent_rooms": "Son odalar",
+        "room_name": "Oda %(name)s",
+        "room_status_bar": "Oda durum çubuğu",
+        "seek_bar_label": "Ses arama çubuğu",
+        "unread_messages": "Okunmamış mesajlar.",
+        "user_menu": "Kullanıcı menüsü"
+    },
+    "a11y_jump_first_unread_room": "İlk okunmamış odaya git.",
+    "action": {
+        "accept": "Kabul Et",
+        "add": "Ekle",
+        "add_existing_room": "Mevcut odayı ekle",
+        "add_people": "Kişi ekleme",
+        "apply": "Uygula",
+        "approve": "Onayla",
+        "ask_to_join": "Katılmak için sor",
+        "back": "Geri",
+        "call": "Ara",
+        "cancel": "İptal",
+        "change": "Değiştir",
+        "clear": "Temizle",
+        "click": "Tıklayın",
+        "click_to_copy": "Kopyalamak için tıklayın",
+        "close": "Kapat",
+        "collapse": "Başarısız",
+        "complete": "Tamamlandı",
+        "confirm": "Doğrula",
+        "continue": "Devam Et",
+        "copy": "Kopyala",
+        "copy_link": "Bağlantıyı kopyala",
+        "create": "Oluştur",
+        "create_a_room": "Bir oda oluştur",
+        "create_account": "Hesap oluştur",
+        "decline": "Reddet",
+        "delete": "Sil",
+        "deny": "Reddet",
+        "disable": "Devre dışı bırak",
+        "disconnect": "Bağlantıyı kes",
+        "dismiss": "Kapat",
+        "done": "Bitti",
+        "download": "İndir",
+        "edit": "Düzenle",
+        "enable": "Etkinleştir",
+        "enter_fullscreen": "Tam ekrana geç",
+        "exit_fullscreeen": "Tam ekrandan çık",
+        "expand": "Genişlet",
+        "explore_public_rooms": "Herkese açık odaları keşfet",
+        "explore_rooms": "Odaları keşfet",
+        "export": "Dışa Aktar",
+        "forward": "İleri",
+        "go": "Git",
+        "go_back": "Geri dön",
+        "got_it": "Anladım",
+        "hide_advanced": "Gelişmişi gizle",
+        "hold": "Beklet",
+        "ignore": "Yoksay",
+        "import": "İçe Aktar",
+        "invite": "Davet et",
+        "invite_to_space": "Alana davet et\n",
+        "invites_list": "Davetler",
+        "join": "Katıl",
+        "learn_more": "Daha fazla bilgi edin",
+        "leave": "Ayrıl",
+        "leave_room": "Odadan ayrıl",
+        "logout": "Çıkış Yap",
+        "manage": "Yönet",
+        "maximise": "Maksimize",
+        "mention": "Sizden bahsediyor",
+        "minimise": "Minimize",
+        "new_message": "Yeni mesaj",
+        "new_room": "Yeni oda",
+        "new_video_room": "Yeni video odası",
+        "next": "İleri",
+        "no": "Hayır",
+        "ok": "Tamam",
+        "open": "Aç",
+        "open_menu": "Menüyü aç",
+        "pause": "Durdur",
+        "pin": "PIN Kodu",
+        "play": "Oynat",
+        "proceed": "İlerle",
+        "quote": "Alıntı",
+        "react": "Tepki ver",
+        "refresh": "Yenile",
+        "register": "Kaydolun",
+        "reload": "Tekrar yükle",
+        "remove": "Kaldır",
+        "rename": "Yeniden adlandır",
+        "reply": "Yanıtla",
+        "reply_in_thread": "Konu içinde yanıtla\n",
+        "report_content": "İçeriği Raporla",
+        "resend": "Yeniden Gönder",
+        "reset": "Sıfırla",
+        "resume": "Devam et",
+        "retry": "Yeniden Dene",
+        "review": "Gözden Geçir",
+        "revoke": "İptal",
+        "save": "Kaydet",
+        "search": "Ara",
+        "send_report": "Rapor gönder",
+        "set_avatar": "Profil resmini ayarla",
+        "share": "Paylaş",
+        "show": "Göster",
+        "show_advanced": "Gelişmiş göster",
+        "show_all": "Hepsini göster",
+        "sign_in": "Giriş Yap",
+        "sign_out": "Çıkış Yap",
+        "skip": "Atla",
+        "start": "Başlat",
+        "start_chat": "Sohbete başla",
+        "start_new_chat": "Yeni sohbeti başlat",
+        "stop": "Dur",
+        "submit": "Gönder",
+        "subscribe": "Abone ol",
+        "transfer": "Aktar",
+        "trust": "Güven",
+        "try_again": "Yeniden deneyin",
+        "unban": "Yasağı Kaldır",
+        "unignore": "Yoksayma",
+        "unpin": "Sabitlemeyi kaldır",
+        "unsubscribe": "Abonelikten Çık",
+        "update": "Güncelleştirme",
+        "upgrade": "Yükselt",
+        "upload": "Yükle",
+        "upload_file": "Dosya Yükle",
+        "verify": "Doğrula",
+        "view": "Görüntüle",
+        "view_all": "Hepsini göster",
+        "view_list": "Listeyi görüntüle",
+        "view_message": "Mesajı görüntüle",
+        "view_source": "Kaynağı Görüntüle",
+        "yes": "Evet",
+        "zoom_in": "Yakınlaştır",
+        "zoom_out": "Uzaklaştır"
+    },
+    "analytics": {
+        "accept_button": "Bu iyi",
+        "bullet_1": "Hesap verilerinizi <Bold>kaydetmez</Bold> veya analiz etmeyiz",
+        "bullet_2": "Bilgilerinizi üçüncü taraflarla <Bold>paylaşmıyoruz</Bold>",
+        "consent_migration": "Daha önce anonim kullanım verilerini bizimle paylaşmayı kabul ettiniz. Bunun nasıl çalıştığını güncelliyoruz.",
+        "disable_prompt": "Bunu istediğiniz zaman ayarlardan kapatabilirsiniz",
+        "enable_prompt": "%(analyticsOwner)s geliştirilmesine yardımcı olun",
+        "learn_more": "Sorunları belirlememize yardımcı olması için anonim verileri paylaşın. Kişisel değil. Üçüncü taraflar yok. <LearnMoreLink>Daha Fazla Bilgi</LearnMoreLink>",
+        "privacy_policy": "Tüm şartlarımızı <PrivacyPolicyUrl>buradan</PrivacyPolicyUrl> okuyabilirsiniz.",
+        "pseudonymous_usage_data": "Anonim kullanım verilerini paylaşarak sorunların tespit edilmesine ve %(analyticsOwner)s iyileştirilmesine yardımcı olun. İnsanların birden fazla cihazı nasıl kullandığını anlamak için cihazlarınız arasında paylaşılan rastgele bir tanımlayıcı oluşturacağız.",
+        "shared_data_heading": "Aşağıdaki verilerden herhangi biri paylaşılabilir:"
+    },
+    "auth": {
+        "3pid_in_use": "Bu e-posta adresi veya telefon numarası zaten kullanılıyor.",
+        "account_clash": "Yeni hesabınız (%(newAccountId)s) kaydedildi, ancak zaten farklı bir hesapta (%(loggedInUserId)s) oturum açmış durumdasınız.",
+        "account_clash_previous_account": "Önceki hesapla devam et",
+        "account_deactivated": "Hesap devre dışı bırakıldı.",
+        "autodiscovery_generic_failure": "Sunucudan otomatik bulma yapılandırması alınamadı",
+        "autodiscovery_hs_incompatible": "Ana sunucunuz çok eski ve gereken minimum API sürümünü desteklemiyor. Lütfen sunucu sahibinizle iletişime geçin veya sunucunuzu yükseltin.",
+        "autodiscovery_invalid": "Geçersiz anasunucu keşif yanıtı",
+        "autodiscovery_invalid_hs": "Anasunucu URL'si geçerli bir Matrix anasuunucusu olarak görünmüyor",
+        "autodiscovery_invalid_hs_base_url": "m.anasunucu için geçersiz base_url",
+        "autodiscovery_invalid_is": "Kimlik sunucu adresi geçerli bir kimlik sunucu adresi gibi gözükmüyor",
+        "autodiscovery_invalid_is_base_url": "m.kimlik_sunucu için geçersiz base_url",
+        "autodiscovery_invalid_is_response": "Geçersiz kimlik sunucu keşfi yanıtı",
+        "autodiscovery_invalid_json": "Hatalı JSON",
+        "autodiscovery_no_well_known": ".well-known JSON dosyası bulunamadı\n",
+        "autodiscovery_unexpected_error_hs": "Ana sunucu yapılandırması çözümlenirken beklenmeyen hata",
+        "autodiscovery_unexpected_error_is": "Kimlik sunucu yapılandırması çözümlenirken beklenmeyen hata",
+        "captcha_description": "Bu ana sunucu sizin bir robot olup olmadığınızdan emin olmak istiyor.",
+        "change_password_action": "Şifre Değiştir",
+        "change_password_confirm_invalid": "Şifreler uyuşmuyor",
+        "change_password_confirm_label": "Şifreyi Onayla",
+        "change_password_current_label": "Mevcut Şifre",
+        "change_password_empty": "Şifreler boş olamaz",
+        "change_password_error": "Şifre değiştirilirken hata oluştu: %(error)s",
+        "change_password_mismatch": "Yeni şifreler uyuşmuyor",
+        "change_password_new_label": "Yeni Şifre",
+        "check_email_explainer": "<b>%(email)s</b> adresine gönderilen talimatları izleyin.",
+        "check_email_resend_prompt": "Almadınız mı?",
+        "check_email_resend_tooltip": "Doğrulama bağlantısı e-postası yeniden gönderildi!",
+        "check_email_wrong_email_button": "E-posta adresini tekrar girin",
+        "check_email_wrong_email_prompt": "Yanlış e-posta adresi?",
+        "continue_with_idp": "%(provider)s ile devam edin",
+        "continue_with_sso": "%(ssoButtons)s ile devam et",
+        "country_dropdown": "Ülke Listesi",
+        "create_account_prompt": "Burada yeni misiniz? <a>Bir hesap oluşturun</a>",
+        "create_account_title": "Yeni hesap",
+        "email_discovery_text": "İsteğe bağlı olarak mevcut kişiler tarafından bulunabilir olmak için e-posta ekleyin.",
+        "email_field_label": "E-posta",
+        "email_field_label_invalid": "Geçerli bir e-posta adresine benzemiyor",
+        "email_field_label_required": "E-posta adresi gir",
+        "email_help_text": "Şifrenizi sıfırlayabilmek için bir e-posta ekleyin.",
+        "email_phone_discovery_text": "İsteğe bağlı olarak mevcut kişiler tarafından keşfedilebilir olmak için e-posta veya telefon ekleyin.",
+        "enter_email_explainer": "<b>%(homeserver)s</b>, şifrenizi sıfırlamanız için size bir doğrulama bağlantısı gönderecek.",
+        "enter_email_heading": "Şifrenizi sıfırlamak için e-posta adresinizi girin",
+        "failed_connect_identity_server": "Kimlik sunucusu erişilemiyor",
+        "failed_connect_identity_server_other": "Oturum açabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.",
+        "failed_connect_identity_server_register": "Kayıt olabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.",
+        "failed_connect_identity_server_reset_password": "Parolanızı sıfırlayabilirsiniz, fakat kimlik sunucunuz çevrimiçi olana kadar bazı özellikler mevcut olmayacak. Bu uyarıyı sürekli görüyorsanız, yapılandırmanızı kontrol edin veya sunucu yöneticinizle iletişime geçin.",
+        "failed_homeserver_discovery": "Anasunucu keşif işlemi başarısız",
+        "failed_query_registration_methods": "Desteklenen kayıt yöntemleri için sorgulama yapılamıyor.",
+        "failed_soft_logout_auth": "Yeniden kimlik doğrulama başarısız",
+        "failed_soft_logout_homeserver": "Anasunucu problemi yüzünden yeniden kimlik doğrulama başarısız",
+        "forgot_password_email_invalid": "E-posta adresi geçerli görünmüyor.",
+        "forgot_password_email_required": "Hesabınıza bağlı e-posta adresi girilmelidir.",
+        "forgot_password_prompt": "Parolanızı mı unuttunuz?",
+        "forgot_password_send_email": "E-posta gönder",
+        "identifier_label": "Şununla oturum açın:",
+        "incorrect_credentials": "Yanlış kullanıcı adı ve / veya şifre.",
+        "incorrect_credentials_detail": "Lütfen %(hs)s sunucusuna oturum açtığınızın farkında olun. Bu sunucu matrix.org değil.",
+        "incorrect_password": "Yanlış Şifre",
+        "log_in_new_account": "Yeni hesabınızla <a>Oturum aç</a>ın.",
+        "logout_dialog": {
+            "description": "Oturumdan çıkmak istediğinize emin misiniz?",
+            "megolm_export": "Elle dışa aktarılmış anahtarlar",
+            "setup_key_backup_title": "Şifrelenmiş mesajlarınıza erişiminizi kaybedeceksiniz",
+            "setup_secure_backup_description_1": "Şifreli mesajlar uçtan uca şifreleme ile korunur. Bu mesajları yalnızca siz ve alıcı(lar) okuyabilir.",
+            "setup_secure_backup_description_2": "Oturumu kapattığınızda, bu anahtarlar bu cihazdan silinecek. Bu, diğer cihazlarınızda bu anahtarlara sahip olmadığınız veya anahtarları sunucuya yedeklemediğiniz sürece şifreli mesajları okuyamayacağınız anlamına gelir.",
+            "skip_key_backup": "Şifrelenmiş mesajlarımı istemiyorum",
+            "use_key_backup": "Anahtar Yedekleme kullanmaya başla"
+        },
+        "misconfigured_body": "%(brand)s yöneticinize <a>yapılandırmanızın</a> hatalı ve mükerrer girdilerini kontrol etmesi için talepte bulunun.",
+        "misconfigured_title": "%(brand)s hatalı ayarlanmış",
+        "mobile_create_account_title": "%(hsName)s üzerinde bir hesap oluşturmak üzeresiniz",
+        "msisdn_field_description": "Diğer kullanıcılar, iletişim bilgilerinizi kullanarak sizi odalara davet edebilir",
+        "msisdn_field_label": "Telefon",
+        "msisdn_field_number_invalid": "Bu telefon numarası tam olarak doğru görünmüyor, lütfen kontrol edip tekrar deneyin",
+        "msisdn_field_required_invalid": "Telefon numarası gir",
+        "no_hs_url_provided": "Ana sunucu adresi belirtilmemiş",
+        "oidc": {
+            "error_title": "Sizin girişinizi yapamadık",
+            "generic_auth_error": "Kimlik doğrulama sırasında bir şeyler ters gitti. Oturum açma sayfasına gidin ve tekrar deneyin.",
+            "missing_or_invalid_stored_state": "Tarayıcınıza bağlandığınız ana sunucuyu anımsamasını söyledik ama ne yazık ki tarayıcınız bunu unutmuş. Lütfen giriş sayfasına gidip tekrar deneyin."
+        },
+        "password_field_keep_going_prompt": "Devam et...",
+        "password_field_label": "Şifre gir",
+        "password_field_strong_label": "Güzel, güçlü şifre!",
+        "password_field_weak_label": "Şifreye izin var, fakat kullanmak güvenli değil",
+        "phone_label": "Telefon",
+        "phone_optional_label": "Telefon (isteğe bağlı)",
+        "qr_code_login": {
+            "check_code_explainer": "Bu, diğer cihazınızla olan bağlantının güvenli olduğunu doğrulayacaktır.",
+            "check_code_heading": "Diğer cihazınızda gösterilen numarayı girin",
+            "check_code_input_label": "2 haneli kod",
+            "check_code_mismatch": "Sayılar uyuşmuyor",
+            "completing_setup": "Yeni cihazınızın kurulumunu tamamlayın",
+            "error_etag_missing": "Beklenmeyen bir hata oluştu. Bunun nedeni bir tarayıcı uzantısı, proxy sunucusu veya sunucunun yanlış yapılandırılması olabilir.",
+            "error_expired": "Oturum açma süresi doldu. Lütfen tekrar deneyin.",
+            "error_expired_title": "Oturum açma işlemi zamanında tamamlanmadı",
+            "error_insecure_channel_detected": "Yeni cihaza güvenli bir bağlantı kurulamadı. Mevcut cihazlarınız hala güvende ve onlar için endişelenmenize gerek yok.",
+            "error_insecure_channel_detected_instructions": "Şimdi ne olacak?",
+            "error_insecure_channel_detected_instructions_1": "Bunun bir ağ sorunu olması ihtimaline karşı diğer cihazda QR koduyla tekrar oturum açmayı deneyin",
+            "error_insecure_channel_detected_instructions_2": "Aynı sorunla karşılaşırsanız, farklı bir wifi ağı deneyin veya wifi yerine mobil verinizi kullanın",
+            "error_insecure_channel_detected_instructions_3": "Bu işe yaramazsa, manuel olarak oturum açın",
+            "error_insecure_channel_detected_title": "Bağlantı güvenli değil",
+            "error_other_device_already_signed_in": "Başka bir şey yapmanıza gerek yok.",
+            "error_other_device_already_signed_in_title": "Diğer cihazınızda zaten oturum açıldı",
+            "error_rate_limited": "Kısa sürede çok fazla deneme. Tekrar denemeden önce bir süre bekleyin.",
+            "error_unexpected": "Beklenmeyen bir hata oluştu. Diğer cihazınızı bağlama isteği iptal edildi.",
+            "error_unsupported_protocol": "Bu cihaz, diğer cihazda QR koduyla oturum açmayı desteklemiyor.",
+            "error_unsupported_protocol_title": "Diğer cihaz uyumlu değil",
+            "error_user_cancelled": "Oturum açma işlemi diğer cihazda iptal edildi.",
+            "error_user_cancelled_title": "Oturum açma isteği iptal edildi",
+            "error_user_declined": "Siz veya hesap sağlayıcısı oturum açma isteğini reddetti.",
+            "error_user_declined_title": "Oturum açma reddedildi",
+            "follow_remaining_instructions": "Kalan talimatları izleyin",
+            "open_element_other_device": "Diğer cihazınızda %(brand)s açın",
+            "point_the_camera": "Burada gösterilen QR kodunu tarayın",
+            "scan_code_instruction": "QR kodunu başka bir cihazla tarayın",
+            "scan_qr_code": "QR kod ile oturum aç",
+            "security_code": "Güvenlik kodu",
+            "security_code_prompt": "İstenirse, diğer cihazınızda aşağıdaki kodu girin.",
+            "select_qr_code": "Seç \"%(scanQRCode)s”",
+            "unsupported_explainer": "Hesap sağlayıcınız, QR koduyla yeni bir cihazda oturum açmayı desteklemiyor.",
+            "unsupported_heading": "QR kodu desteklenmiyor",
+            "waiting_for_device": "Cihazın oturum açması bekleniyor"
+        },
+        "register_action": "Hesap Oluştur",
+        "registration": {
+            "continue_without_email_description": "Sadece bir uyarı, e-posta eklemezseniz ve şifrenizi unutursanız, hesabınıza erişimi <b> kalıcı olarak kaybedebilirsiniz</b>.",
+            "continue_without_email_field_label": "E-posta (isteğe bağlı)",
+            "continue_without_email_title": "E-posta olmadan devam et"
+        },
+        "registration_disabled": "Bu ana sunucuda kayıt devre dışı bırakıldı.",
+        "registration_msisdn_field_required_invalid": "Telefon numarası gir ( bu ana sunucuda gerekli)",
+        "registration_successful": "Kayıt Başarılı",
+        "registration_username_in_use": "Birisi zaten bu kullanıcı adına sahip. Başka bir tane deneyin veya sizseniz, aşağıdan oturum açın.",
+        "registration_username_unable_check": "Kullanıcı adının alınıp alınmadığı kontrol edilemiyor. Daha sonra tekrar deneyin.",
+        "registration_username_validation": "Sadece küçük harfler, numara, tire ve alt tire kullanın",
+        "reset_password": {
+            "confirm_new_password": "Yeni şifreyi onayla",
+            "devices_logout_success": "Tüm cihazlardan çıkış yaptınız ve artık anlık bildirimler almayacaksınız. Bildirimleri yeniden etkinleştirmek için her cihazda tekrar oturum açın.",
+            "other_devices_logout_warning_1": "Cihazlarınızdan çıkış yaptığınızda, cihazlarda saklanan mesaj şifreleme anahtarları silinir ve şifrelenmiş sohbet geçmişi okunamaz hale gelir.",
+            "other_devices_logout_warning_2": "Şifreli odalarda sohbet geçmişinize erişimi korumak istiyorsanız, devam etmeden önce Anahtar Yedekleme'yi ayarlayın veya mesaj anahtarlarınızı diğer cihazlarınızdan dışa aktarın.",
+            "password_not_entered": "Yeni bir şifre girilmelidir.",
+            "passwords_mismatch": "Yeni şifreler birbirleriyle eşleşmelidir.",
+            "rate_limit_error": "Kısa sürede çok fazla deneme. Tekrar denemeden önce biraz bekleyin.",
+            "rate_limit_error_with_time": "Kısa sürede çok fazla deneme. %(timeout)s sonra yeniden deneyin.",
+            "reset_successful": "Parolanız sıfırlandı.",
+            "return_to_login": "Giriş ekranına dön",
+            "sign_out_other_devices": "Tüm cihazlardan çıkış yapın"
+        },
+        "reset_password_action": "Şifreyi sıfırla",
+        "reset_password_button": "Şifrenizi mi unuttunuz?",
+        "reset_password_email_field_description": "Hesabınızı kurtarmak için bir e-posta adresi kullanın",
+        "reset_password_email_field_required_invalid": "E-posta adresi gir ( bu ana sunucuda gerekli)",
+        "reset_password_email_not_associated": "E-posta adresiniz bu ana sunucudaki bir Matrix Kimliği ile ilişkili görünmüyor.",
+        "reset_password_email_not_found_title": "Bu e-posta adresi bulunamadı",
+        "reset_password_title": "Şifrenizi sıfırlayın",
+        "server_picker_custom": "Diğer ana sunucu",
+        "server_picker_description": "Farklı bir ana sunucu URL'si belirterek diğer Matrix sunucularında oturum açmak için özel sunucu seçeneklerini kullanabilirsiniz. Bu, farklı bir ana sunucudan varolan Matrix hesabınızla %(brand)s kullanmanıza olanak sağlar.",
+        "server_picker_description_matrix.org": "En büyük açık sunucu üzerindeki milyonlara ücretsiz ulaşmak için katılın",
+        "server_picker_dialog_title": "Hesabınızın nerede barındırılacağına karar verin",
+        "server_picker_explainer": "Varsa tercih ettiğiniz Matrix ana sunucusunu kullanın veya kendi sunucunuzu barındırın.",
+        "server_picker_failed_validate_homeserver": "Ana sunucu doğrulanamıyor",
+        "server_picker_intro": "Hesabınızı barındırabileceğiniz yerlere 'ana sunucular' diyoruz.",
+        "server_picker_invalid_url": "Geçersiz URL",
+        "server_picker_learn_more": "Ana sunucular hakkında",
+        "server_picker_matrix.org": "Matrix.org dünyadaki en büyük halka açık ev sunucusudur, bu nedenle çoğu için iyi bir seçim.",
+        "server_picker_required": "Bir ana sunucu belirtin",
+        "server_picker_title": "Ana sunucunuzda oturum açın",
+        "server_picker_title_default": "Sunucu Seçenekleri",
+        "server_picker_title_registration": "Ana bilgisayar hesabı açık",
+        "session_logged_out_description": "Güvenlik için , bu oturuma çıkış yapıldı . Lütfen tekrar oturum açın.",
+        "session_logged_out_title": "Oturum Kapatıldı",
+        "set_email": {
+            "description": "Bu, şifrenizi sıfırlamanıza ve bildirimler almanıza olanak tanır.",
+            "verification_pending_description": "Lütfen e-postanızı kontrol edin ve içerdiği bağlantıya tıklayın. Bunu yaptıktan sonra devam'a tıklayın.",
+            "verification_pending_title": "Doğrulama Bekleniyor"
+        },
+        "set_email_prompt": "E-posta adresi belirlemek ister misiniz?",
+        "sign_in_description": "Devam etmek için hesabınızı kullanın.",
+        "sign_in_instead": "Bunun yerine oturum aç",
+        "sign_in_instead_prompt": "Zaten bir hesabınız var mı? <a>Buradan oturum açın</a>",
+        "sign_in_or_register": "Giriş Yap veya Hesap Oluştur",
+        "sign_in_or_register_description": "Devam etmek için hesabınızı kullanın veya yeni bir hesap oluşturun.",
+        "sign_in_prompt": "Hesabınız var mı? <a>Oturum aç</a>",
+        "sign_in_with_sso": "Tek oturum açma ile oturum açın",
+        "signing_in": "Oturum açılıyor…",
+        "soft_logout": {
+            "clear_data_button": "Tüm verileri temizle",
+            "clear_data_description": "Bu oturumdaki tüm verilerin silinmesi kalıcıdır. Şifrelenmiş mesajlar, anahtarları yedeklenmemiş ise kaybolacaktır.",
+            "clear_data_title": "Bu oturumdaki tüm verileri temizle?"
+        },
+        "soft_logout_heading": "Oturumunuz kapatıldı",
+        "soft_logout_intro_password": "Oturum açmak için şifreni gir ve hesabına yeniden erişimi sağla.",
+        "soft_logout_intro_sso": "Oturum açın ve yeniden hesabınıza ulaşın.",
+        "soft_logout_intro_unsupported_auth": "Hesabınıza giriş yapamazsınız. Lütfen daha fazla bilgi için ana sunucu yöneticiniz ile bağlantıya geçiniz.",
+        "soft_logout_subheading": "Kişisel veri temizle",
+        "soft_logout_warning": "Uyarı: kişisel verileriniz (şifreleme anahtarları dahil) bu oturumda saklanmaya devam eder. Bu oturumu kullanmayı bitirdiyseniz veya başka bir hesapta oturum açmak istiyorsanız verileri temizleyin.",
+        "sso": "Tek seferlik oturum aç",
+        "sso_complete_in_browser_dialog_title": "Oturum Açma işlemini tamamlamak için tarayıcınıza gidin",
+        "sso_failed_missing_storage": "Tarayıcınıza bağlandığınız ana sunucuyu anımsamasını söyledik ama ne yazık ki tarayıcınız bunu unutmuş. Lütfen giriş sayfasına gidip tekrar deneyin.",
+        "sso_or_username_password": "%(ssoButtons)s Veya %(usernamePassword)s",
+        "sync_footer_subtitle": "Çok sayıda odaya katıldıysanız, bu biraz zaman alabilir",
+        "syncing": "Senkronize ediliyor…",
+        "uia": {
+            "code": "Kod",
+            "email": "Hesabınızı oluşturmak için, az önce %(emailAddress)s adresine gönderdiğimiz e-postadaki bağlantıyı açın.",
+            "email_auth_header": "Devam etmek için e-postanızı kontrol edin",
+            "email_resend_prompt": "E-posta size ulaşmadı mı? <a>Tekrar gönder</a>",
+            "email_resent": "Yeniden Gönder",
+            "fallback_button": "Kimlik Doğrulamayı başlatın",
+            "mas_cross_signing_reset_cta": "Hesabınıza gidin",
+            "mas_cross_signing_reset_description": "Hesap sağlayıcınız aracılığıyla kimliğinizi sıfırlayın ve ardından geri gelip \"Yeniden Dene \"ye tıklayın.",
+            "msisdn": "%(msisdn)s ye bir metin mesajı gönderildi",
+            "msisdn_token_incorrect": "Belirteç(Token) hatalı",
+            "msisdn_token_prompt": "Lütfen içerdiği kodu girin:",
+            "password_prompt": "Hesabınızın şifresini aşağıya girerek kimliğinizi teyit edin.",
+            "recaptcha_missing_params": "Ana sunucu yapılandırmasında eksik captcha genel anahtarı. Lütfen bunu ana sunucu yöneticinize bildirin.",
+            "registration_token_label": "Kayıt anahtarı",
+            "registration_token_prompt": "Ana sunucu yöneticisi tarafından sağlanan bir kayıt belirteci girin.",
+            "sso_body": "Kimliğinizi doğrulamak için Tek Seferlik Oturum Açma özelliğini kullanarak bu e-posta adresini eklemeyi onaylayın.",
+            "sso_failed": "Kimliğinizi doğrularken bir şeyler ters gitti. İptal edin ve tekrar deneyin.",
+            "sso_postauth_body": "Kimliğinizi doğrulamak için aşağıdaki düğmeye tıklayın.",
+            "sso_postauth_title": "Devam etmek için onaylayın",
+            "sso_preauth_body": "Devam etmek için, kimliğinizi kanıtlamak üzere Tek Oturum Açma'yı kullanın.",
+            "sso_title": "Devam etmek için tek seferlik oturum açın",
+            "terms": "Lütfen bu ana sunucunun politikalarını inceleyin ve kabul edin:",
+            "terms_invalid": "Lütfen ana sunucunun tüm politikalarını inceleyin ve kabul edin"
+        },
+        "unsupported_auth": "Bu ana sunucu, bu istemci tarafından desteklenen herhangi bir oturum açma akışı sunmuyor.",
+        "unsupported_auth_email": "Bu ana sunucu e-posta adresiyle oturum açmayı desteklemiyor.",
+        "unsupported_auth_msisdn": "Bu sunucu bir telefon numarası ile kimlik doğrulamayı desteklemez.",
+        "username_field_required_invalid": "Kullanıcı adını girin",
+        "username_in_use": "Birisi zaten bu kullanıcı adına sahip, lütfen başka bir tane deneyin.",
+        "verify_email_explainer": "Şifrenizi sıfırlamadan önce kimliğinizi doğrulamamız gerek. <b>%(email)s</b> adresine gönderdiğimiz bağlantıya tıklayın.",
+        "verify_email_heading": "Devam etmek için e-postanızı doğrulayın"
+    },
+    "bug_reporting": {
+        "additional_context": "O sırada ne yaptığınız, oda kimlikleri, kullanıcı kimlikleri vb.gibi sorunu analiz etmede yardımcı olacak ek öğe varsa lütfen buraya ekleyin.",
+        "before_submitting": "Logları göndermeden önce, probleminizi betimleyen <a>bir GitHub talebi oluşturun</a>.",
+        "collecting_information": "Uygulama sürümü bilgileri toplanıyor",
+        "collecting_logs": "Kayıtlar toplanıyor",
+        "create_new_issue": "Lütfen GitHub’da <newIssueLink>Yeni bir talep</newIssueLink> oluşturun ki bu hatayı inceleyebilelim.",
+        "description": "Hata ayıklama günlükleri, kullanıcı adınız, ziyaret ettiğiniz odaların kimlikleri veya takma adları, en son hangi arayüz öğeleriyle etkileşimde bulunduğunuz ve diğer kullanıcıların kullanıcı adları dahil olmak üzere uygulama kullanım verilerini içerir. Mesaj içermezler.",
+        "download_logs": "Günlükleri indir",
+        "downloading_logs": "Günlükler indiriliyor",
+        "error_empty": "Lütfen neyin yanlış gittiğini bize bildirin ya da en güzeli problemi tanımlayan bir GitHub talebi oluşturun.",
+        "failed_download_logs": "Hata ayıklama günlükleri indirilemedi: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Hata raporunuz reddedildi. rageshake sunucusu bu uygulamayı desteklemiyor.",
+            "rejected_generic": "Hata raporunuz reddedildi. Rageshake sunucusu, bir politika nedeniyle raporun içeriğini reddetti.",
+            "rejected_recovery_key": "Hata raporunuz bir kurtarma anahtarı içerdiği için güvenlik nedeniyle reddedildi.",
+            "rejected_version": "Çalıştırdığınız sürüm çok eski olduğu için hata raporunuz reddedildi.",
+            "server_unknown_error": "Rageshake sunucusu bilinmeyen bir hatayla karşılaştı ve raporu işleyemedi.",
+            "unknown_error": "Günlükler gönderilemedi."
+        },
+        "github_issue": "GitHub sorunu",
+        "introduction": "GitHub aracılığıyla bir hata bildirdiyseniz, hata ayıklama günlükleri sorunu bulmamızda yardımcı olabilir. ",
+        "log_request": "Bunun gelecekte de olmasının önüne geçmek için lütfen <a>günceleri bize gönderin</a>.",
+        "logs_sent": "Loglar gönderiliyor",
+        "matrix_security_issue": "Matrix ile ilgili bir güvenlik sorununu bildirmek için lütfen Matrix.org <a>Güvenlik Bildirim Politikası</a> bölümünü okuyun.",
+        "preparing_download": "Günlükleri indirmeye hazırlanıyor",
+        "preparing_logs": "Günlükleri göndermeye hazırlanıyor",
+        "send_logs": "Günlükleri gönder",
+        "submit_debug_logs": "Hata ayıklama kayıtlarını gönder",
+        "textarea_label": "Notlar",
+        "thank_you": "Teşekkürler!",
+        "title": "Hata raporlama",
+        "unsupported_browser": "Hatırlatma:Tarayıcınız desteklenmiyor, deneyiminiz öngörülemiyor.",
+        "uploading_logs": "Günlükler yükleniyor",
+        "waiting_for_server": "Sunucudan yanıt bekleniyor"
+    },
+    "cannot_invite_without_identity_server": "Bir kimlik sunucusu olmadan bir kullanıcıyı e-posta ile davet edemezsiniz. \"Ayarlar\" altından bir kimlik sunucusuna bağlanabilirsiniz.",
+    "cannot_reach_homeserver": "Ana sunucuya erişilemiyor",
+    "cannot_reach_homeserver_detail": "Kararlı bir internet bağlantısına sahip olduğunuzdan emin olun yada sunucu yöneticisi ile iletişime geçin",
+    "cant_load_page": "Sayfa yüklenemiyor",
+    "chat_card_back_action_label": "Sohbete geri dön",
+    "chat_effects": {
+        "confetti_description": "Mesajı konfeti ile gönderir",
+        "confetti_message": "konfeti gönderir",
+        "fireworks_description": "Mesajı havai fişeklerle gönderir",
+        "fireworks_message": "Havai fişek gönderir",
+        "hearts_description": "Verilen mesajı kalplerle gönderir",
+        "hearts_message": "kalpler gönderir",
+        "rainfall_description": "Verilen mesajı yağmurla gönderir",
+        "rainfall_message": "yağmur yağdırır",
+        "snowfall_description": "Mesajı kartopu efekti ile gönderir",
+        "snowfall_message": "Kartopu gönderir",
+        "spaceinvaders_description": "Verilen mesajı uzay temalı bir efektle gönderir",
+        "spaceinvaders_message": "uzay istilacılarını gönderir"
+    },
+    "common": {
+        "access_token": "Erişim Jetonu",
+        "accessibility": "Erişilebilirlik",
+        "advanced": "Gelişmiş",
+        "analytics": "Analizler",
+        "and_n_others": {
+            "one": "ve bir diğeri...",
+            "other": "ve %(count)s diğerleri..."
+        },
+        "appearance": "Görünüm",
+        "application": "Uygulama",
+        "are_you_sure": "Emin misiniz?",
+        "attachment": "Ek dosya",
+        "authentication": "Doğrulama",
+        "avatar": "Avatar",
+        "beta": "Beta",
+        "camera": "Kamera",
+        "cameras": "Kameralar",
+        "cancel": "İptal",
+        "capabilities": "Yetenekler",
+        "copied": "Kopyalandı!",
+        "credits": "Katkıda Bulunanlar",
+        "dark": "Karanlık",
+        "description": "Açıklama",
+        "deselect_all": "Tüm seçimleri kaldır",
+        "device": "Cihaz",
+        "edited": "düzenlendi",
+        "email_address": "E-posta Adresi",
+        "emoji": "Emoji",
+        "encrypted": "Şifrelenmiş",
+        "encryption_enabled": "Şifreleme etkin",
+        "error": "Hata",
+        "faq": "SSS",
+        "favourites": "Favoriler",
+        "feedback": "Geri bildirim",
+        "filter_results": "Sonuçları filtrele",
+        "forward_message": "Mesajı ilet",
+        "general": "Genel",
+        "go_to_settings": "Ayarlara Git",
+        "guest": "Misafir",
+        "help": "Yardım",
+        "historical": "Geçmiş",
+        "home": "Ev",
+        "homeserver": "Ana sunucu",
+        "identity_server": "Kimlik sunucusu",
+        "image": "Resim",
+        "integration_manager": "İlişikilendirme yöneticisi",
+        "joined": "Katıldı",
+        "labs": "Deneysel Özellikler",
+        "legal": "Yasal",
+        "light": "Aydınlık",
+        "loading": "Yükleniyor...",
+        "location": "Konum",
+        "low_priority": "Düşük öncelik",
+        "matrix": "Matrix",
+        "message": "Message",
+        "message_layout": "Mesaj düzeni",
+        "message_timestamp_invalid": "Geçersiz zaman damgası",
+        "microphone": "Mikrofon",
+        "model": "Model",
+        "modern": "Modern",
+        "mute": "Sessiz",
+        "n_members": {
+            "one": "%(count)s üye",
+            "other": "%(count)s üye"
+        },
+        "n_rooms": {
+            "one": "%(count)s oda",
+            "other": "%(count)s oda"
+        },
+        "name": "İsim",
+        "no_results": "Sonuç bulunamadı",
+        "no_results_found": "Hiçbir sonuç bulunamadı",
+        "not_trusted": "Güvenilir değil",
+        "off": "Kapalı",
+        "offline": "Çevrimdışı",
+        "on": "Açık",
+        "options": "Ayarlar",
+        "orphan_rooms": "Diğer odalar",
+        "password": "Şifre",
+        "people": "Kişiler",
+        "preferences": "Tercihler",
+        "presence": "Mevcudiyet",
+        "preview_message": "Hey sen,evet sen! En iyisisin.",
+        "privacy": "Gizlilik",
+        "private": "Özel",
+        "private_room": "Özel oda",
+        "private_space": "Özel alan",
+        "profile": "Profil",
+        "public": "Topluluk",
+        "public_room": "Herkese açık oda",
+        "public_space": "Herkese açık alan",
+        "qr_code": "QR kod",
+        "random": "Rastgele",
+        "reactions": "İfadeler",
+        "recommended": "Önerilen",
+        "report_a_bug": "Bir hatayı bildirin",
+        "room": "Oda",
+        "room_name": "Oda adı",
+        "rooms": "Odalar",
+        "save": "Kaydet",
+        "saved": "Kaydedildi",
+        "saving": "Kaydediliyor...",
+        "secure_backup": "Korumalı yedekleme",
+        "select_all": "Tümünü seç",
+        "server": "Sunucu",
+        "settings": "Ayarlar",
+        "setup_secure_messages": "Korumalı Mesajları Ayarla",
+        "show_more": "Daha fazla göster",
+        "someone": "Herhangi biri",
+        "space": "Alan",
+        "spaces": "Alanlar",
+        "sticker": "Çıkartma",
+        "stickerpack": "Çıkartma paketi",
+        "success": "Başarılı",
+        "suggestions": "Öneriler",
+        "support": "Destek",
+        "system_alerts": "Sistem Uyarıları",
+        "theme": "Tema",
+        "thread": "Konu",
+        "threads": "Konular",
+        "timeline": "Zaman Akışı",
+        "unavailable": "kullanılamıyor",
+        "unencrypted": "Şifrelenmemiş",
+        "unmute": "Sesi aç",
+        "unnamed_room": "İsimsiz Oda",
+        "unnamed_space": "İsimsiz Boşluk",
+        "unverified": "Doğrulanmamış",
+        "updating": "Güncelleniyor...",
+        "user": "Kullanıcı",
+        "user_avatar": "Profil resmi",
+        "username": "Kullanıcı Adı",
+        "verification_cancelled": "Doğrulama iptal edildi",
+        "verified": "Doğrulanmış",
+        "version": "Versiyon",
+        "video": "Video",
+        "video_room": "Görüntülü sohbet odası",
+        "view_message": "Mesajı görüntüle",
+        "warning": "Uyarı"
+    },
+    "composer": {
+        "autocomplete": {
+            "@room_description": "Tüm odayı bilgilendir",
+            "command_a11y": "Otomatik Düzeltme Komutu",
+            "command_description": "Komutlar",
+            "emoji_a11y": "Emoji Otomaik Düzeltme",
+            "notification_a11y": "Otomatik Bildirim Tamamlama",
+            "notification_description": "Oda Bildirimi",
+            "room_a11y": "Otomatik Oda Tamamlama",
+            "space_a11y": "Otomatik Alan Tamamlama",
+            "user_a11y": "Kullanıcı Otomatik Tamamlama",
+            "user_description": "Kullanıcılar"
+        },
+        "close_sticker_picker": "Çıkartmaları gizle",
+        "edit_composer_label": "Mesajı düzenle",
+        "format_bold": "Kalın",
+        "format_code_block": "Kod bloğu",
+        "format_decrease_indent": "Girintiyi azalt",
+        "format_increase_indent": "Girintiyi arttır",
+        "format_inline_code": "Kod",
+        "format_insert_link": "Bağlantı ekle",
+        "format_italic": "İtalik",
+        "format_italics": "İtalik(Eğik)",
+        "format_link": "Bağlantı",
+        "format_ordered_list": "Numaralı liste",
+        "format_strikethrough": "Üstü çizgili",
+        "format_underline": "Altı çizili",
+        "format_unordered_list": "Madde işaretli liste",
+        "formatting_toolbar_label": "Biçimlendirme",
+        "link_modal": {
+            "link_field_label": "Bağlantı",
+            "text_field_label": "Metin",
+            "title_create": "Bağlantı Oluştur",
+            "title_edit": "Bağlantıyı Düzenle"
+        },
+        "mode_plain": "Biçimlendirmeyi gizle",
+        "mode_rich_text": "Biçimlendirmeyi göster",
+        "no_perms_notice": "Bu gönderiyi gönderme izniniz bulunmamaktadır",
+        "placeholder": "Bir mesaj gönder…",
+        "placeholder_encrypted": "Şifrelenmiş bir mesaj gönder…",
+        "placeholder_reply": "Bir cevap gönder…",
+        "placeholder_reply_encrypted": "Şifrelenmiş bir cevap gönder…",
+        "placeholder_thread": "İleti dizisine cevap ver...",
+        "placeholder_thread_encrypted": "Şifrelenmiş ileti dizisini yanıtla...",
+        "poll_button": "Oylama",
+        "poll_button_no_perms_description": "Bu odada oylama başlatma izniniz yok.",
+        "poll_button_no_perms_title": "İzin Gerekli",
+        "replying_title": "Cevap yazıyor",
+        "room_upgraded_link": "Sohbet buradan devam ediyor.",
+        "room_upgraded_notice": "Bu oda değiştirildi ve artık aktif değil.",
+        "send_button_title": "Mesajı gönder",
+        "send_button_voice_message": "Sesli mesaj gönder",
+        "send_voice_message": "Sesli mesaj gönder",
+        "stop_voice_message": "Kaydı durdur",
+        "voice_message_button": "Sesli Mesaj"
+    },
+    "console_dev_note": "Ne yaptığınızı biliyorsanız, Element açık kaynaklıdır, GitHub'ımıza (https://github.com/vector-im/element-web/) göz atmayı ve katkıda bulunmayı unutmayın!",
+    "console_scam_warning": "Birisi size buraya bir şey kopyalayıp yapıştırmanızı söylediyse, dolandırılıyor olma olasılığınız yüksektir!",
+    "console_wait": "Bekle!",
+    "create_room": {
+        "action_create_room": "Oda oluştur",
+        "action_create_video_room": "Video odası oluştur",
+        "encrypted_video_room_warning": "Bunu daha sonra devre dışı bırakamazsınız. Oda şifrelenecek ancak gömülü arama şifrelenmeyecek.",
+        "encrypted_warning": "Bunu daha sonra devre dışı bırakamazsınız. Köprüler ve çoğu bot henüz çalışmayacaktır.",
+        "encryption_forced": "Sunucunuz özel odalarda şifrelemenin etkinleştirilmesini gerektiriyor.",
+        "encryption_label": "Uçtan uca şifrelemeyi etkinleştir",
+        "error_title": "Oda oluşturırken bir sorunla karşılaştık",
+        "generic_error": "Sunucu mevcut olmayabilir, aşırı yüklenmiş veya bir hata ile karşılaşmış olabilirsiniz.",
+        "join_rule_change_notice": "Bunu istediğiniz zaman oda ayarlarından değiştirebilirsiniz.",
+        "join_rule_invite": "Özel oda (yalnızca davetliler)",
+        "join_rule_invite_label": "Sadece davet edilen kişiler bu odayı bulabilir ve katılabilir.",
+        "join_rule_knock_label": "Herkes katılma isteğinde bulunabilir ancak yöneticilerin veya moderatörlerin erişim izni vermesi gerekir. Bunu daha sonra değiştirebilirsiniz.",
+        "join_rule_public_label": "Herkes bu odayı bulabilir ve katılabilir.",
+        "join_rule_public_parent_space_label": "Bu odayı sadece <SpaceName/> üyeleri değil, herkes bulabilir ve bu odaya katılabilir.",
+        "join_rule_restricted": "Alan üyeleri tarafından görülebilir",
+        "join_rule_restricted_label": "<SpaceName/> içindeki herkes bu odayı bulabilir ve bu odaya katılabilir.",
+        "name_validation_required": "Lütfen oda için bir ad girin",
+        "room_visibility_label": "Oda görünürlüğü",
+        "title_private_room": "Özel bir oda oluştur",
+        "title_public_room": "Herkese açık bir oda oluşturun",
+        "title_video_room": "Bir video odası oluşturun",
+        "topic_label": "Konu (isteğe bağlı)",
+        "unfederated": "%(serverName)s üyesi olmayanların bu odaya katılmasını engelleyin.",
+        "unfederated_label_default_off": "Oda yalnızca ana sunucunuzdaki ekiplerle işbirliği yapmak için kullanılacaksa bu ayarı etkinleştirebilirsiniz. Bu daha sonra değiştirilemez.",
+        "unfederated_label_default_on": "Oda ana sunucunuzun dışındaki ekiplerle işbirliği yapmak için kullanılacaksa bu ayarı devre dışı bırakabilirsiniz. Bu daha sonra değiştirilemez.",
+        "unsupported_version": "Belirtilen oda sürümünü sunucu desteklemiyor."
+    },
+    "create_space": {
+        "add_details_prompt": "İnsanların tanımasına yardımcı olacak bazı ayrıntılar ekleyin.",
+        "add_details_prompt_2": "Bunları istediğiniz zaman değiştirebilirsiniz.",
+        "add_existing_rooms_description": "Eklemek için odaları veya konuşmaları seçin. Bu sadece sizin için bir alan, kimse bilgilendirilmeyecek. Daha sonra daha fazlasını ekleyebilirsiniz.",
+        "add_existing_rooms_heading": "Neyi düzenlemek istiyorsunuz?",
+        "address_label": "Adres",
+        "address_placeholder": "ör. benim alanım",
+        "creating": "Oluşturuluyor…",
+        "creating_rooms": "Odalar oluşturuluyor…",
+        "done_action": "Alanıma git",
+        "done_action_first_room": "İlk odama git",
+        "explainer": "Alanlar, odaları ve insanları gruplandırmanın yeni bir yoludur. Ne tür bir Alan yaratmak istiyorsunuz? Bunu daha sonra değiştirebilirsiniz.",
+        "failed_create_initial_rooms": "İlk alan odaları oluşturulamadı",
+        "failed_invite_users": "Aşağıdaki kullanıcılar alanınıza davet edilemedi: %(csvUsers)s",
+        "invite_teammates_by_username": "Kullanıcı adıyla davet et",
+        "invite_teammates_description": "Doğru kişilerin erişimi olduğundan emin olun. Daha sonra daha fazlasını davet edebilirsiniz.",
+        "invite_teammates_heading": "Takım arkadaşlarınızı davet edin",
+        "inviting_users": "Davet ediliyor...",
+        "label": "Alan Oluştur",
+        "name_required": "Lütfen alan için bir ad girin",
+        "personal_space": "Sadece ben",
+        "personal_space_description": "Odalarınızı düzenlemek için özel bir alan",
+        "private_description": "Sadece davetliler, kendiniz veya ekipler için en iyisi",
+        "private_heading": "Özel alanınız",
+        "private_personal_description": "Doğru kişilerin %(name)s erişimi olduğundan emin olun",
+        "private_personal_heading": "Kiminle çalışıyorsunuz?",
+        "private_space": "Ben ve takım arkadaşlarım",
+        "private_space_description": "Siz ve takım arkadaşlarınız için özel bir alan",
+        "public_description": "Herkes için açık alan, topluluklar için en iyisi",
+        "public_heading": "Herkese açık alanınız",
+        "search_public_button": "Herkese açık alanları arayın",
+        "setup_rooms_community_description": "Her biri için bir oda oluşturalım.",
+        "setup_rooms_community_heading": "%(spaceName)s alanında tartışmak istediğiniz bazı şeyler nelerdir?",
+        "setup_rooms_description": "Daha sonra, halihazırda var olanlar da dahil olmak üzere, daha fazlasını ekleyebilirsiniz.",
+        "setup_rooms_private_description": "Her biri için oda oluşturacağız.",
+        "setup_rooms_private_heading": "Ekibiniz hangi projeler üzerinde çalışıyor?",
+        "share_description": "Şimdilik sadece sen varsın, başkalarıyla daha da iyi olacak.",
+        "share_heading": "Paylaş %(name)s",
+        "skip_action": "Şimdilik atla",
+        "subspace_adding": "Ekleniyor…",
+        "subspace_beta_notice": "Yönettiğiniz bir alana bir alan ekleyin.",
+        "subspace_dropdown_title": "Bir alan oluştur",
+        "subspace_existing_space_prompt": "Bunun yerine mevcut bir alanı mı eklemek istiyorsunuz?",
+        "subspace_join_rule_invite_description": "Sadece davet edilen kişiler bu alanı bulabilecek ve bu alana katılabilecektir.",
+        "subspace_join_rule_invite_only": "Özel alan (sadece davetliler)",
+        "subspace_join_rule_label": "Alan görünürlüğü",
+        "subspace_join_rule_public_description": "Bu alanı sadece <SpaceName/> üyeleri değil, herkes bulabilecek ve katılabilecektir.",
+        "subspace_join_rule_restricted_description": "<SpaceName/> içindeki herkes bulabilecek ve katılabilecektir."
+    },
+    "credits": {
+        "default_cover_photo": "<photo>Varsayılan kapak fotoğrafı</photo>, <author>Jesús Roncero</author>'ya ait olup, <terms>CC-BY-SA 4.0</terms> lisansı kapsamında kullanılmaktadır.",
+        "twemoji": "<twemoji>Twemoji</twemoji> emoji resmi, <author>Twitter, Inc. ve diğer katkıda bulunanlara</author> ait olup, <terms>CC-BY 4.0</terms> lisansı kapsamında kullanılmaktadır.",
+        "twemoji_colr": "<colr>twemoji-colr</colr> yazı tipi, <author>Mozilla foundation</author>'a ait olup, <terms>Apache 2.0</terms> lisansı kapsamında kullanılmaktadır."
+    },
+    "desktop_default_device_name": "%(brand)s Masaüstü: %(platformName)s",
+    "devtools": {
+        "active_widgets": "Aktif Widget'lar",
+        "category_other": "Diğer",
+        "category_room": "Oda",
+        "caution_colon": "Dikkat:",
+        "client_versions": "İstemci Sürümleri",
+        "crypto": {
+            "4s_public_key_in_account_data": "hesap verisinde",
+            "4s_public_key_not_in_account_data": "bulunamadı",
+            "4s_public_key_status": "Gizli depolama açık anahtarı:",
+            "backup_key_cached": "yerel olarak önbelleğe alındı",
+            "backup_key_cached_status": "Yedekleme anahtarı önbelleğe alındı:",
+            "backup_key_not_stored": "depolanmadı",
+            "backup_key_stored": "gizli depolamada",
+            "backup_key_stored_status": "Yedekleme anahtarı depolandı:",
+            "backup_key_unexpected_type": "beklenmedik tür",
+            "backup_key_well_formed": "uygun biçimlendirilmiş",
+            "cross_signing": "Çapraz imzalama",
+            "cross_signing_cached": "yerel olarak önbelleğe alındı",
+            "cross_signing_not_ready": "Çapraz imzalama ayarlanmamış.",
+            "cross_signing_private_keys_in_storage": "gizli depolamada",
+            "cross_signing_private_keys_in_storage_status": "Gizli anahtarları çapraz imzalama:",
+            "cross_signing_private_keys_not_in_storage": "cihazda bulunamadı",
+            "cross_signing_public_keys_on_device": "hafızada",
+            "cross_signing_public_keys_on_device_status": "Açık anahtarları çapraz imzalama:",
+            "cross_signing_ready": "Çapraz imzalama kullanıma hazır:",
+            "cross_signing_status": "Çapraz imzalama durumu:",
+            "cross_signing_untrusted": "Hesabınız gizli depolama alanında çapraz imzalama kimliği barındırıyor ancak bu oturum henüz güvenilir değil.",
+            "crypto_not_available": "Şifreleme modülü mevcut değil.",
+            "key_backup_active_version": "Aktif yedekleme sürümü:",
+            "key_backup_active_version_none": "Yok",
+            "key_backup_inactive_warning": "Bu oturumdaki anahtarlarınız yedeklenmiyor.",
+            "key_backup_latest_version": "Sunucudaki en son yedekleme sürümü:",
+            "key_storage": "Anahtar Depolama",
+            "master_private_key_cached_status": "Ana gizli anahtar:",
+            "not_found": "bulunamadı",
+            "not_found_locally": "yerel olarak bulunamadı",
+            "secret_storage_not_ready": "hazır değil",
+            "secret_storage_ready": "hazır",
+            "secret_storage_status": "Gizli depolama:",
+            "self_signing_private_key_cached_status": "Kendinden imzalı özel anahtar:",
+            "title": "Uçtan uca şifreleme",
+            "user_signing_private_key_cached_status": "Kullanıcı imzalı özel anahtar:"
+        },
+        "developer_mode": "Geliştirici modu",
+        "developer_tools": "Geliştirici Araçları",
+        "edit_setting": "Ayarı düzenle",
+        "edit_values": "Değerleri düzenle",
+        "empty_string": "<empty string>",
+        "event_content": "Etkinlik İçeriği",
+        "event_id": "Etkinlik Kimliği: %(eventId)s",
+        "event_sent": "Etkinlik gönderildi!",
+        "event_type": "Etkinlik Türü",
+        "explore_account_data": "Hesap verilerini keşfet",
+        "explore_room_account_data": "Oda hesap verilerini keşfet",
+        "explore_room_state": "Oda durumunu keşfet",
+        "failed_to_find_widget": "Bu widget'ı bulurken bir hata oluştu.",
+        "failed_to_load": "Yükleme başarısız",
+        "failed_to_save": "Ayarlar kaydedilemedi.",
+        "failed_to_send": "Etkinlik gönderilemedi!",
+        "id": "Kimlik: ",
+        "invalid_json": "Geçerli JSON gibi görünmüyor.",
+        "level": "Seviye",
+        "low_bandwidth_mode": "Düşük bant genişliği modu",
+        "low_bandwidth_mode_description": "Uyumlu ana sunucu gerektirir.",
+        "main_timeline": "Ana zaman çizelgesi",
+        "no_receipt_found": "Okuma bilgisi bulunamadı",
+        "notification_state": "Bildirim durumu <strong> %(notificationState)s </strong>",
+        "notifications_debug": "Bildirimlerde hata ayıklama",
+        "number_of_users": "Kullanıcı sayısı",
+        "original_event_source": "Orijinal olay kaynağı",
+        "room_encrypted": "Oda <strong>şifreli ✅</strong>",
+        "room_id": "Oda Kimliği: %(roomId)s",
+        "room_not_encrypted": "Oda <strong>şifreli değil 🚨 </strong>",
+        "room_notifications_dot": "Nokta: ",
+        "room_notifications_highlight": "Vurgula: ",
+        "room_notifications_last_event": "Son olay:",
+        "room_notifications_sender": "Gönderen: ",
+        "room_notifications_thread_id": "Dizi Mesaj Kimliği: ",
+        "room_notifications_total": "Toplam: ",
+        "room_notifications_type": "Tür: ",
+        "room_status": "Oda durumu",
+        "room_unread_status_count": {
+            "one": "Oda okunmamış durumu: <strong> %(status)s</strong>, sayım: <strong> %(count)s </strong>",
+            "other": "Oda okunmamış durumu: <strong> %(status)s</strong>, sayım: <strong> %(count)s </strong>"
+        },
+        "save_setting_values": "Ayar değerlerini kaydet",
+        "see_history": "Geçmişi gör",
+        "send_custom_account_data_event": "Özel hesap verisi etkinliğini gönder",
+        "send_custom_room_account_data_event": "Özel oda hesabı veri etkinliğini gönder",
+        "send_custom_state_event": "Özel durum etkinliği gönder",
+        "send_custom_timeline_event": "Özel zaman çizelgesi etkinliği gönder",
+        "server_info": "Sunucu bilgisi",
+        "server_versions": "Sunucu Sürümleri",
+        "settable_global": "Genel olarak ayarlanabilir",
+        "settable_room": "Oda özelinde ayarlanabilir",
+        "setting_colon": "Ayar:",
+        "setting_definition": "Ayar tanımı:",
+        "setting_id": "Ayar Kimliği",
+        "settings_explorer": "Ayarlar gezgini",
+        "show_hidden_events": "Zaman çizelgesinde gizli olayları göster",
+        "spaces": {
+            "one": "<space>",
+            "other": "<%(count)s boşluk>"
+        },
+        "state_key": "Durum Anahtarı",
+        "thread_root_id": "Konu Kök Kimliği: %(threadRootId)s",
+        "threads_timeline": "Dizi mesajlar zaman çizelgesi",
+        "title": "Geliştirici araçları",
+        "toggle_event": "etkinliği değiştir",
+        "toolbox": "Araç Kutusu",
+        "use_at_own_risk": "Bu kullanıcı arayüzü, değerlerin türlerini kontrol ETMEZ. Kendi sorumluluğunuzda kullanın.",
+        "user_read_up_to": "Kullanıcı buraya kadar okudu: ",
+        "user_read_up_to_ignore_synthetic": "Kullanıcı buraya kadar okudu (ignoreSynthetic): ",
+        "user_read_up_to_private": "Kullanıcı buraya kadar okudu (m.read.private): ",
+        "user_read_up_to_private_ignore_synthetic": "Kullanıcı buraya kadar okudu (m.read.private;ignoreSynthetic): ",
+        "value": "Değer",
+        "value_colon": "Değer:",
+        "value_in_this_room": "Bu odadaki değer",
+        "value_this_room_colon": "Bu odadaki değer:",
+        "values_explicit": "Belli düzeylerdeki değerler",
+        "values_explicit_colon": "Belli düzeylerdeki değerler:",
+        "values_explicit_room": "Bu odadaki belirli seviyelerdeki değerler",
+        "values_explicit_this_room_colon": "Bu odadaki belirli seviyelerdeki değerler:",
+        "view_servers_in_room": "Odadaki sunucuları görüntüle",
+        "view_source_decrypted_event_source": "Şifresi çözülen etkinlik kaynağı",
+        "view_source_decrypted_event_source_unavailable": "Şifresi çözülmüş kaynak kullanılamıyor",
+        "widget_screenshots": "Desteklenen görsel bileşenlerde anlık görüntüleri aç"
+    },
+    "dialog_close_label": "Kutucuğu kapat",
+    "download_completed": "İndirme Tamamlandı",
+    "emoji": {
+        "categories": "Kategoriler",
+        "category_activities": "Aktiviteler",
+        "category_animals_nature": "Hayvanlar & Doğa",
+        "category_flags": "Bayraklar",
+        "category_food_drink": "Yiyecek & İçecek",
+        "category_frequently_used": "Sıklıkla Kullanılan",
+        "category_objects": "Nesneler",
+        "category_smileys_people": "İfadeler ve İnsanlar",
+        "category_symbols": "Semboller",
+        "category_travel_places": "Seyahat & Yerler",
+        "quick_reactions": "Hızlı Tepkiler"
+    },
+    "emoji_picker": {
+        "cancel_search_label": "Aramayı iptal et"
+    },
+    "empty_room": "Boş oda",
+    "empty_room_was_name": "Boş oda (%(oldName)s idi)",
+    "encryption": {
+        "access_secret_storage_dialog": {
+            "key_validation_text": {
+                "wrong_security_key": "Yanlış Güvenlik Anahtarı"
+            },
+            "restoring": "Anahtarları yedekten geri yükle",
+            "security_key_title": "Güvenlik anahtarı"
+        },
+        "bootstrap_title": "Anahtarları ayarla",
+        "cancel_entering_passphrase_description": "Parola girmeyi iptal etmek istediğinizden emin misiniz?",
+        "cancel_entering_passphrase_title": "Parola girişini iptal et?",
+        "confirm_encryption_setup_body": "Şifreleme kurulumunu onaylamak için aşağıdaki düğmeye tıklayın.",
+        "confirm_encryption_setup_title": "Şifreleme kurulumunu onayla",
+        "cross_signing_room_normal": "Bu oda uçtan uça şifreli",
+        "cross_signing_room_verified": "Bu odadaki herkes doğrulanmış",
+        "cross_signing_room_warning": "Birisi bilinmeyen bir oturum kullanıyor",
+        "cross_signing_user_normal": "Bu kullanıcıyı doğrulamadınız.",
+        "cross_signing_user_verified": "Bu kullanıcıyı doğruladınız. Bu kullanıcı tüm oturumlarını doğruladı.",
+        "cross_signing_user_warning": "Bu kullanıcı bütün oturumlarında doğrulanmamış.",
+        "enter_recovery_key": "Kurtarma anahtarını girin",
+        "event_shield_reason_authenticity_not_guaranteed": "Bu şifrelenmiş mesajın güvenilirliği bu cihazda garanti edilemez.",
+        "event_shield_reason_mismatched_sender_key": "Doğrulanmamış bir oturum tarafından şifrelenmiş",
+        "event_shield_reason_unknown_device": "Bilinmeyen veya silinmiş bir cihaz tarafından şifrelenmiştir.",
+        "event_shield_reason_unsigned_device": "Sahibi tarafından doğrulanmamış bir cihaz tarafından şifrelenmiştir.",
+        "event_shield_reason_unverified_identity": "Doğrulanmamış bir kullanıcı tarafından şifrelenmiştir.",
+        "export_unsupported": "Tarayıcınız gerekli şifreleme uzantılarını desteklemiyor",
+        "forgot_recovery_key": "Kurtarma anahtarınızı mı unuttunuz?",
+        "import_invalid_keyfile": "Geçersiz bir %(brand)s anahtar dosyası",
+        "import_invalid_passphrase": "Kimlik doğrulama denetimi başarısız oldu : yanlış şifre ?",
+        "key_storage_out_of_sync": "Anahtar depolama alanınız senkronize değil.",
+        "key_storage_out_of_sync_description": "Anahtar depolama alanınıza ve mesaj geçmişinize erişimi sürdürmek için kurtarma anahtarınızı onaylayın.",
+        "messages_not_secure": {
+            "cause_1": "Ana sunucunuz",
+            "cause_2": "Doğruladığınız kullanıcının bağlı olduğu ana sunucu",
+            "cause_3": "Sizin veya diğer kullanıcıların internet bağlantısı",
+            "cause_4": "Sizin veya diğer kullanıcıların oturumu",
+            "heading": "Aşağıdakilerden biri tehlikeye girmiş olabilir:",
+            "title": "Mesajlarınız korunmuyor"
+        },
+        "new_recovery_method_detected": {
+            "description_1": "Güvenli Mesajlar için yeni bir Güvenlik İfadesi ve anahtarı tespit edildi.",
+            "description_2": "Bu oturumda yeni kurtarma yöntemi kullanılarak geçmiş şifreleniyor.",
+            "title": "Yeni Kurtarma Yöntemi",
+            "warning": "Yeni kurtarma yöntemini ayarlamadıysanız, bir saldırgan hesabınıza erişmeye çalışıyor olabilir. Hesap parolanızı değiştirin ve Ayarlar'da hemen yeni bir kurtarma yöntemi ayarlayın."
+        },
+        "pinned_identity_changed": "%(displayName)s'ın (<b>%(userId)s</b>) kimliği değişmiş gibi görünüyor. <a>Daha fazla bilgi </a>",
+        "pinned_identity_changed_no_displayname": "<b>%(userId)s</b>'ın kimliği değişmiş gibi görünüyor. <a>Daha fazla bilgi </a>",
+        "recovery_method_removed": {
+            "description_1": "Bu oturum, Güvenli Mesajlar için Güvenlik İfadenizin ve anahtarınızın kaldırıldığını tespit etti.",
+            "description_2": "Bunu yanlışlıkla yaptıysanız, bu oturumun mesaj geçmişini yeni bir kurtarma yöntemiyle yeniden şifreleyecek olan Güvenli Mesajları bu oturumda ayarlayabilirsiniz.",
+            "title": "Kurtarma Yöntemi Kaldırıldı",
+            "warning": "Kurtarma yöntemini kaldırmadıysanız, bir saldırgan hesabınıza erişmeye çalışıyor olabilir. Hesap parolanızı değiştirin ve Ayarlar'da hemen yeni bir kurtarma yöntemi belirleyin."
+        },
+        "reset_all_button": "Tüm kurtarma yöntemlerini unuttunuz veya kaybettiniz mi? <a>Tümünü sıfırla</a>",
+        "set_up_recovery": "Kurtarmayı ayarlayın",
+        "set_up_recovery_later": "Şimdi değil",
+        "set_up_recovery_toast_description": "Cihazlarınıza erişiminizi kaybetmeniz durumunda şifrelenmiş mesaj geçmişinizi geri yüklemek için kullanılabilecek bir kurtarma anahtarı oluşturun.",
+        "set_up_toast_description": "Şifrelenmiş mesajlara ve verilere erişimi kaybetmemek için koruma sağlayın",
+        "set_up_toast_title": "Güvenli Yedekleme kur",
+        "setup_secure_backup": {
+            "explainer": "Anahtarlarını kaybetmemek için, çıkış yapmadan önce önleri yedekle."
+        },
+        "udd": {
+            "interactive_verification_button": "Emoji ile etkileşimli olarak doğrula",
+            "other_ask_verify_text": "Kullanıcıya oturumunu doğrulamasını söyle, ya da aşağıdan doğrula.",
+            "other_new_session_text": "%(name)s (%(userId)s) yeni oturuma doğrulamadan giriş yaptı:",
+            "own_ask_verify_text": "Diğer oturumunuzu aşağıdaki seçeneklerden birini kullanarak doğrulayın.",
+            "own_new_session_text": "Yeni bir oturuma, doğrulamadan oturum açtınız:",
+            "title": "Güvenilmiyor"
+        },
+        "unable_to_setup_keys_error": "Anahtarlar ayarlanamıyor",
+        "verification": {
+            "accepting": "Kabul ediyorum…",
+            "after_new_login": {
+                "device_verified": "Cihaz doğrulandı",
+                "skip_verification": "Şimdilik doğrulamayı atla",
+                "unable_to_verify": "Bu cihaz doğrulanamıyor",
+                "verify_this_device": "Bu cihazı doğrulayın"
+            },
+            "cancelled": "Doğrulamayı iptal ettiniz.",
+            "cancelled_self": "Diğer cihazınızda doğrulamayı iptal ettiniz.",
+            "cancelled_user": "%(displayName)s doğrulamayı iptal etti.",
+            "cancelling": "İptal ediliyor…",
+            "complete_action": "Anlaşıldı",
+            "complete_description": "Bu kullanıcıyı başarılı şekilde doğruladınız.",
+            "complete_title": "Doğrulandı!",
+            "error_starting_description": "Diğer kullanıcıyla sohbet başlatamadık.",
+            "error_starting_title": "Doğrulama başlatılırken hata oluştu",
+            "explainer": "Bu kullanıcıyla olan güvenli mesajlar uçtan uca şifrelidir ve 3 taraflar tarafından okunamaz.",
+            "in_person": "Güvende olmak için, bunu şahsen yapın veya güvenilir bir iletişim yöntemi kullanın.",
+            "incoming_sas_device_dialog_text_1": "Bu cihazı güvenilir olarak işaretlemek için doğrulayın. Bu cihaza güvenmek, uçtan uca şifrelenmiş mesajlar kullanırken size ve diğer kullanıcılara ekstra gönül rahatlığı sağlar.",
+            "incoming_sas_device_dialog_text_2": "Bu cihaz doğrulanmak, cihazı güvenilir olarak işaretleyecek ve sizinle doğrulayan kullanıcılar bu cihaza güvenecektir.",
+            "incoming_sas_dialog_title": "Gelen Doğrulama İsteği",
+            "incoming_sas_dialog_waiting": "Ortağın onaylaması bekleniyor…",
+            "incoming_sas_user_dialog_text_1": "Güvenilir olarak işaretlemek için bu kullanıcıyı doğrulayın. Kullanıcılara güvenmek, uçtan uca şifrelenmiş mesajları kullanırken içinizin daha rahat olmasını sağlar.",
+            "incoming_sas_user_dialog_text_2": "Bu kullanıcıyı doğrulamak, oturumlarını güvenilir olarak işaretleyecek ve ayrıca oturumunuzu onlar için güvenilir olarak işaretleyecektir.",
+            "no_key_or_device": "Görünüşe göre bir Güvenlik Anahtarınız veya doğrulama yapabileceğiniz başka bir cihazınız yok. Bu cihaz eski şifrelenmiş mesajlara erişemeyecek. Bu cihazda kimliğinizi doğrulamak için doğrulama anahtarlarınızı sıfırlamanız gerekir.",
+            "no_support_qr_emoji": "Doğrulamaya çalıştığınız cihaz, %(brand)s tarafından desteklenen bir QR kodu taramayı veya emoji doğrulamasını desteklemiyor. Farklı bir istemciyle deneyin.",
+            "other_party_cancelled": "Diğer taraf onaylamayı reddetti.",
+            "prompt_encrypted": "Güvenli olduğuna emin olmak için odadaki tüm kullanıcıları onaylayın.",
+            "prompt_self": "Doğrulamayı bildirimden tekrar başlatın.",
+            "prompt_unencrypted": "Şifrelenmiş odalarda, güvenli olduğundan emin olmak için tüm kullanıcıları doğrulayın.",
+            "prompt_user": "Doğrulamayı profillerinden tekrar başlatın.",
+            "qr_or_sas": "%(qrCode)s veya %(emojiCompare)s",
+            "qr_or_sas_header": "Aşağıdakilerden birini tamamlayarak bu cihazı doğrulayın:",
+            "qr_prompt": "Bu benzersiz kodu tarayın",
+            "qr_reciprocate_same_shield_device": "Az kaldı! Diğer cihazınız da aynı kalkanı gösteriyor mu?",
+            "qr_reciprocate_same_shield_user": "Az kaldı! %(displayName)s aynı kalkanı mı gösteriyor?",
+            "request_toast_accept": "Oturumu Doğrula",
+            "request_toast_accept_user": "Kullanıcıyı Doğrula",
+            "request_toast_decline_counter": "Yoksay (%(counter)s)",
+            "request_toast_detail": "%(ip)s 'den %(deviceId)s",
+            "reset_proceed_prompt": "Sıfırlama işlemine devam et",
+            "sas_caption_self": "Ekranda aşağıdaki numaranın göründüğünü onaylayarak bu cihazı doğrulayın.",
+            "sas_caption_user": "Aşağıdaki numaranın ekranlarında göründüğünü onaylayarak bu kullanıcıyı doğrulayın.",
+            "sas_description": "Her iki cihazda da kamera yoksa benzersiz bir emoji setini karşılaştırın",
+            "sas_emoji_caption_self": "Aşağıdaki emojilerin her iki cihazda da aynı sırada görüntülendiğini onaylayın:",
+            "sas_emoji_caption_user": "Aşağıdaki emojinin ekranlarında göründüğünü onaylayarak bu kullanıcıyı doğrulayın.",
+            "sas_match": "Eşleşiyorlar",
+            "sas_no_match": "Eşleşmiyorlar",
+            "sas_prompt": "Benzersiz emoji karşılaştır",
+            "scan_qr": "Taramayla doğrula",
+            "scan_qr_explainer": "%(displayName)s den kodunuzu taramasını isteyin:",
+            "self_verification_hint": "Devam etmek için lütfen diğer cihazınızda doğrulama isteğini kabul edin.",
+            "start_button": "Doğrulamayı Başlat",
+            "successful_device": "%(deviceName)s (%(deviceId)s) başarıyla doğruladınız!",
+            "successful_own_device": "Cihazınızı başarıyla doğruladınız!",
+            "successful_user": "%(displayName)s başarıyla doğruladınız!",
+            "timed_out": "Doğrulama zaman aşımına uğradı.",
+            "unsupported_method": "Desteklenen doğrulama yöntemi bulunamadı.",
+            "unverified_session_toast_accept": "Evet, bendim.",
+            "unverified_session_toast_title": "Yeni giriş. Bu siz miydiniz?",
+            "unverified_sessions_toast_description": "Hesabınızın güvende olduğundan emin olmak için inceleyin",
+            "unverified_sessions_toast_reject": "Sonra",
+            "unverified_sessions_toast_title": "Doğrulanmamış oturumlarınız var",
+            "verification_description": "Şifreli mesajlara erişmek ve kimliğinizi başkalarına kanıtlamak için kimliğinizi doğrulayın. Ayrıca bir mobil cihaz kullanıyorsanız, devam etmeden önce lütfen mobilden uygulamayı açın.",
+            "verification_dialog_title_device": "Diğer cihazı doğrulayın",
+            "verification_dialog_title_user": "Doğrulama Talebi",
+            "verification_skip_warning": "Doğrulama yapmazsanız, tüm mesajlarınıza erişemezsiniz ve başkalarına güvenilmez görünebilirsiniz.",
+            "verification_success_with_backup": "Yeni cihazınız artık doğrulanmıştır. Şifrelenmiş mesajlarınıza erişebilir ve diğer kullanıcılar onu güvenilir olarak görür.",
+            "verification_success_without_backup": "Yeni cihazınız artık doğrulandı. Diğer kullanıcılar bunu güvenilir olarak görecek.",
+            "verify_emoji": "Emojiyle doğrula",
+            "verify_emoji_prompt": "Eşsiz emoji eşleştirme ile doğrulama.",
+            "verify_emoji_prompt_qr": "Yukarıdaki kodu tarayamıyorsanız benzersiz emojiyi karşılaştırarak doğrulayın.",
+            "verify_later": "Daha sonra doğrulayacağım",
+            "verify_using_device": "Başka bir cihazla doğrula",
+            "verify_using_key": "Güvenlik Anahtarı ile Doğrula",
+            "verify_using_key_or_phrase": "Güvenlik Anahtarı veya İfadesiyle Doğrula",
+            "waiting_for_user_accept": "%(displayName)s kullanıcısın onaylaması için bekleniliyor…",
+            "waiting_other_device": "Diğer cihazınızda doğrulamanız bekleniyor...",
+            "waiting_other_device_details": "Diğer cihazınızdan doğrulama beklenior, %(deviceName)s (%(deviceId)s)...",
+            "waiting_other_user": "%(displayName)s ın doğrulaması için bekleniyor…"
+        },
+        "verification_requested_toast_title": "Doğrulama talep edildi",
+        "verified_identity_changed": "%(displayName)s'in (<b>%(userId)s</b>) doğrulanmış kimliği değişti. <a>Daha fazla bilgi</a>",
+        "verified_identity_changed_no_displayname": "(<b>%(userId)s</b>)'in doğrulanmış kimliği değişti. <a>Daha fazla bilgi</a>",
+        "verify_toast_description": "Diğer kullanıcılar güvenmeyebilirler",
+        "verify_toast_title": "Bu oturumu doğrula",
+        "withdraw_verification_action": "Doğrulamayı iptal et"
+    },
+    "error": {
+        "admin_contact": "Bu servisi kullanmaya devam etmek için lütfen <a>servis yöneticinizle bağlantı kurun</a>.",
+        "admin_contact_short": "<a>Sunucu yöneticinize</a> başvurun.",
+        "app_launch_unexpected_error": "Uygulama hazırlanırken beklenmeyen bir hata oluştu. Ayrıntılar için konsola bakın.",
+        "cannot_load_config": "Yapılandırma dosyası yüklenemiyor: tekrar denemek için lütfen sayfayı yenileyin.",
+        "connection": "Ana sunucuyla iletişimde bir sorun oluştu, lütfen daha sonra tekrar deneyin.",
+        "dialog_description_default": "Bir hata oluştu.",
+        "download_media": "Kaynak medya indirilemedi, kaynak url bulunamadı",
+        "edit_history_unsupported": "Ana sunucunuz bu özelliği desteklemiyor gözüküyor.",
+        "failed_copy": "Kopyalama başarısız",
+        "hs_blocked": "Bu sunucu yöneticisi tarafından bloke edildi.",
+        "invalid_configuration_mixed_server": "Geçersiz yapılandırma: default_server_name veya default_server_config ile birlikte bir default_hs_url belirtilemiyor",
+        "invalid_configuration_no_server": "Geçersiz yapılandırma: varsayılan sunucu belirtilmedi.",
+        "invalid_json": "Element yapılandırmanız geçersiz JSON içeriyor. Lütfen sorunu düzeltin ve sayfayı yeniden yükleyin.",
+        "invalid_json_detail": "Ayrıştırıcıdan gelen mesaj: %(message)s",
+        "invalid_json_generic": "Hatalı JSON",
+        "mau": "Bu ana sunucu Aylık Aktif Kullanıcı limitine ulaştı.",
+        "misconfigured": "Elementiniz yanlış yapılandırılmış",
+        "mixed_content": "Tarayıcı çubuğunuzda bir HTTPS URL'si olduğunda Ana Sunusuna HTTP üzerinden bağlanılamıyor . Ya HTTPS kullanın veya <a> güvensiz komut dosyalarını</a> etkinleştirin.",
+        "non_urgent_echo_failure_toast": "Sunucunuz bası <a>istekler'e</a> onay vermiyor.",
+        "resource_limits": "Bu anasunucu kaynak limitlerinden birini aştı.",
+        "session_restore": {
+            "clear_storage_button": "Depolamayı temizle ve Oturumu Kapat",
+            "clear_storage_description": "Oturumu kapat ve şifreleme anahtarlarını sil?",
+            "description_1": "Önceki oturumunuzu geri yüklerken bir hatayla karşılaştık.",
+            "description_2": "Eğer daha önce %(brand)s'un daha yeni bir versiyonunu kullandıysanız , oturumunuz bu sürümle uyumsuz olabilir . Bu pencereyi kapatın ve daha yeni sürüme geri dönün.",
+            "description_3": "Tarayıcınızın depolama alanını temizlemek sorunu çözebilir, ancak bu işlem oturumunuzu kapatacak ve şifrelenmiş sohbet geçmişinizi okunamaz hale getirecektir.",
+            "title": "Oturum geri yüklenemiyor"
+        },
+        "something_went_wrong": "Bir şeyler yanlış gitti!",
+        "storage_evicted_description_1": "Şifrelenmiş mesaj anahtarları da dahil olmak üzere bazı oturum verileri eksik. Bunu düzeltmek için oturumu kapatıp tekrar açın ve anahtarları yedekten geri yükleyin.",
+        "storage_evicted_description_2": "Tarayıcınız muhtemelen disk alanı azaldığında bu verileri silmiş.",
+        "storage_evicted_title": "Eksik oturum verileri",
+        "sync": "Ana sunucuya bağlanamıyor. Yeniden deneniyor…",
+        "tls": "Ana Sunucu'ya bağlanılamıyor - lütfen bağlantınızı kontrol edin ,<a> Ana Sunucu SSL sertifikanızın </a> güvenilir olduğundan ve bir tarayıcı uzantısının istekleri engellemiyor olduğundan emin olun.",
+        "unknown": "Bilinmeyen Hata",
+        "unknown_error_code": "bilinmeyen hata kodu",
+        "update_power_level": "Güç seviyesi değiştirilemedi"
+    },
+    "error_app_open_in_another_tab": "%(brand)s bağlanmak için diğer sekmeye geçin. Bu sekme artık kapatılabilir.",
+    "error_app_open_in_another_tab_title": "%(brand)s başka bir sekmede bağlı",
+    "error_app_opened_in_another_window": "%(brand)s başka bir pencerede açık. %(brand)s burada kullanmak ve diğer pencerenin bağlantısını kesmek için \"%(label)s\" düğmesine tıklayın.",
+    "error_database_closed_description": {
+        "for_desktop": "Diskiniz dolu olabilir. Lütfen biraz yer açın ve yeniden yükleyin.",
+        "for_web": "Eğer tarama verilerinizi temizlediyseniz bu mesajın gelmesi normaldir.%(brand)s başka bir sekmede de açık olabilir veya diskiniz dolu olabilir. Lütfen biraz yer açın ve yeniden yükleyin"
+    },
+    "error_database_closed_title": "%(brand)s çalışmayı durdurdu",
+    "error_dialog": {
+        "copy_room_link_failed": {
+            "description": "Oda bağlantısı panoya kopyalanamıyor.",
+            "title": "Oda bağlantısı kopyalanamıyor"
+        },
+        "error_loading_user_profile": "Kullanıcı profili yüklenemedi",
+        "forget_room_failed": "Oda unutulması başarısız oldu %(errCode)s"
+    },
+    "error_user_not_logged_in": "Kullanıcı oturum açmadı",
+    "event_preview": {
+        "m.call.answer": {
+            "dm": "Çağrı devam ediyor",
+            "user": "%(senderName)s çağrıya katıldı",
+            "you": "Çağrıya katıldınız"
+        },
+        "m.call.hangup": {
+            "user": "%(senderName)s aramayı sonlandırdı",
+            "you": "Aramayı sonlandırdınız"
+        },
+        "m.call.invite": {
+            "dm_receive": "%(senderName)s arıyor",
+            "dm_send": "Yanıt bekleniyor",
+            "user": "%(senderName)s bir çağrı başlattı",
+            "you": "Bir çağrı başlattınız"
+        },
+        "m.emote": "%(senderName)s%(emote)s",
+        "m.reaction": {
+            "user": "%(sender)s, %(message)s mesajına %(reaction)s tepkisi verdi",
+            "you": "%(message)s mesajına %(reaction)s tepkisi verdiniz"
+        },
+        "m.sticker": "%(senderName)s%(stickerName)s",
+        "m.text": "%(senderName)s%(message)s",
+        "prefix": {
+            "audio": "Ses",
+            "file": "Dosya",
+            "image": "Resim",
+            "poll": "Anket",
+            "video": "Video"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
+    },
+    "export_chat": {
+        "cancelled": "Dışa aktarma İptal Edildi",
+        "cancelled_detail": "Dışa aktarma başarıyla iptal edildi",
+        "confirm_stop": "Verilerinizi dışa aktarmayı durdurmak istediğinizden emin misiniz? Bunu yaparsanız, baştan başlamanız gerekir.",
+        "creating_html": "HTML oluşturuluyor…",
+        "creating_output": "Çıkış oluşturuluyor…",
+        "creator_summary": "%(creatorName)s bu odayı oluşturdu.",
+        "current_timeline": "Geçerli Zaman Çizelgesi",
+        "enter_number_between_min_max": "%(min)s ile %(max)s arasında bir sayı girin",
+        "error_fetching_file": "Dosya getirilirken hata oluştu",
+        "export_info": "Bu, <roomName/> odasının dışa aktarım başlangıcıdır. %(exportDate)s tarihinde <exporterDetails/> tarafından dışa aktarılmıştır.",
+        "export_successful": "Dışa aktarım başarılı!",
+        "exported_n_events_in_time": {
+            "one": "%(seconds)s saniyede %(count)s olay dışa aktarıldı",
+            "other": "%(seconds)s saniyede %(count)s olay dışa aktarıldı"
+        },
+        "exporting_your_data": "Verilerinizi dışa aktarma",
+        "fetched_n_events": {
+            "one": "Şimdiye kadar %(count)s olay getirildi",
+            "other": "Şimdiye kadar %(count)s olay getirildi"
+        },
+        "fetched_n_events_in_time": {
+            "one": "%(seconds)s saniyede %(count)s olay getirildi",
+            "other": "%(seconds)s saniyede %(count)s olay getirildi"
+        },
+        "fetched_n_events_with_total": {
+            "one": "%(total)s olaydan %(count)s tanesi getirildi",
+            "other": "%(total)s olaydan %(count)s tanesi getirildi"
+        },
+        "fetching_events": "Etkinlikler getiriliyor…",
+        "file_attached": "Dosya Eklendi",
+        "format": "Biçim",
+        "from_the_beginning": "Baştan",
+        "generating_zip": "ZIP dosyası oluşturuluyor",
+        "html": "HTML",
+        "html_title": "Dışa Aktarılan Veriler",
+        "include_attachments": "Ekleri Dahil Et",
+        "json": "JSON",
+        "media_omitted": "Medya atlandı",
+        "media_omitted_file_size": "Medya atlandı - dosya boyutu sınırı aşıldı",
+        "messages": "Mesajlar",
+        "next_page": "Sonraki mesaj grubu",
+        "num_messages": "Mesaj sayısı",
+        "num_messages_min_max": "Mesaj sayısı yalnızca %(min)s ile %(max)s arasında bir sayı olabilir",
+        "number_of_messages": "Mesaj sayısını belirtin",
+        "previous_page": "Önceki mesaj grubu",
+        "processing": "İşleniyor…",
+        "processing_event_n": "%(total)s olaydan %(number)s olay işleniyor",
+        "select_option": "Sohbetleri zaman akışınızdan dışa aktarmak için aşağıdaki seçeneklerden birini seçin",
+        "size_limit": "Boyut Sınırı",
+        "size_limit_min_max": "Boyut yalnızca %(min)s MB ile %(max)s MB arasında olabilir",
+        "size_limit_postfix": "MB",
+        "starting_export": "Dışa aktarım başlatılıyor…",
+        "successful": "Dışa Aktarma Başarılı",
+        "successful_detail": "Dışa aktarma işlem başarılı. İndirilenler klasörünüzde bulabilirsiniz.",
+        "text": "Düz Metin",
+        "title": "Sohbeti Dışa Aktar",
+        "topic": "Konu: %(topic)s",
+        "unload_confirm": "Dışa aktarırken çıkmak istediğinizden emin misiniz?"
+    },
+    "failed_load_async_component": "Yüklenemedi! Ağ bağlantınızı kontrol edin ve tekrar deneyin.",
+    "feedback": {
+        "can_contact_label": "Başka sorularınız varsa benimle iletişime geçebilirsiniz",
+        "comment_label": "Yorum",
+        "existing_issue_link": "Lütfen önce <existingIssuesLink>Github'daki mevcut hataları görüntüleyin </existingIssuesLink>. Eşleşme yok mu? <newIssueLink>Yeni bir tane başlat</newIssueLink>.",
+        "may_contact_label": "Yeni fikirleri takip etmek veya test etmek için benimle iletişime geçebilirsiniz",
+        "platform_username": "Platformunuz ve kullanıcı adınız, geri bildirimlerinizi en iyi şekilde kullanmamıza yardımcı olmak için not edilecektir.",
+        "pro_type": "İPUCU: Bir hatayla karşılaşırsanız, sorunu bulmamıza yardımcı olması için lütfen <debugLogsLink>hata ayıklama günlüklerini</debugLogsLink> gönderin.",
+        "send_feedback_action": "Geri bildirim gönder",
+        "sent": "Geri bildirim gönderildi! Teşekkürler!"
+    },
+    "file_panel": {
+        "empty_description": "Sohbetten dosya ekleyin veya odada herhangi bir yere sürükleyip bırakın.",
+        "empty_heading": "Bu odada hiçbir dosya görünmüyor",
+        "guest_note": "Bu işlevi kullanmak için <a> Kayıt Olun </a>",
+        "peek_note": "Dosyalarını görmek için odaya katılmanız gerekir"
+    },
+    "forward": {
+        "filter_placeholder": "Oda veya kişi ara",
+        "message_preview_heading": "Mesaj önizlemesi",
+        "no_perms_title": "Bunu yapmak için izniniz yok",
+        "open_room": "Oda aç",
+        "send_label": "Gönder",
+        "sending": "Gönderiliyor…",
+        "sent": "Gönderildi"
+    },
+    "identity_server": {
+        "change": "Kimlik sunucusunu değiştir",
+        "change_prompt": "<current /> kimlik sunucusundan bağlantı kesilip <new /> kimlik sunucusuna bağlanılsın mı?",
+        "change_server_prompt": "Eğer <server /> kimlik sunucusunu kullanarak başkalarını bulmak ve başkalarını tarafından bulunabilmek istemiyorsanız aşağıya bir başka kimlik sunucusu giriniz.",
+        "changed": "Kimlik sunucunuz değiştirildi",
+        "checking": "Sunucu kontrol ediliyor",
+        "description_connected": "Şu anda <server></server> kimlik sunucusunu kullanarak başkalarını buluyorsunuz ve başkalarını tarafından bulunabiliyorsunuz. Aşağıdan kimlik sunucunuzu değiştirebilirsiniz.",
+        "description_disconnected": "Şu anda herhangi bir kimlik sunucusu kullanmıyorsunuz. Başkalarını bulmak ve başkaları tarafından bulunabilmek için aşağıya bir kimlik sunucusu ekleyin.",
+        "description_optional": "Bir kimlik sunucusu kullanmak isteğe bağlıdır. Eğer bir tane kullanmak istemezseniz başkaları tarafından bulunamayabilir ve başkalarını e-posta adresi ya da telefon numarası ile davet edemeyebilirsiniz.",
+        "disconnect": "Kimlik sunucu bağlantısını kes",
+        "disconnect_anyway": "Yinede bağlantıyı kes",
+        "disconnect_offline_warning": "Bağlantınızı kesmeden önce <b>kişisel verilerinizi <idserver /> kimlik sunucusundan silmelisiniz</b>. Ne yazık ki <idserver /> kimlik sunucusu şu anda çevrim dışı ya da bir nedenden ötürü erişilemiyor.",
+        "disconnect_personal_data_warning_1": "Kimlik sunucusu üzerinde hala <b>kişisel veri paylaşımı</b> yapıyorsunuz <idserver />.",
+        "disconnect_personal_data_warning_2": "Kimlik sunucusundan bağlantıyı kesmeden önce telefon numaranızı ve e-posta adreslerinizi silmenizi tavsiye ederiz.",
+        "disconnect_server": "<idserver /> kimlik sunucusundan bağlantıyı kes?",
+        "disconnect_warning": "Kimlik sunucunuz ile bağlantıyı keserseniz başkaları tarafından bulunamayabilir ve başkalarını e-posta adresi ya da telefon numarası ile davet edemeyebilirsiniz.",
+        "do_not_use": "Bir kimlik sunucu kullanma",
+        "error_connection": "Kimlik Sunucusuna bağlanılamadı",
+        "error_invalid": "Geçerli bir Kimlik Sunucu değil ( durum kodu %(code)s )",
+        "error_invalid_or_terms": "Hizmet şartları kabuk edilmedi yada kimlik sunucu geçersiz.",
+        "no_terms": "Seçtiğiniz kimlik sunucu herhangi bir hizmet şartları sözleşmesine sahip değil.",
+        "suggestions": "Şunu yapmalısınız:",
+        "suggestions_1": "kimlik sunucunuza erişimi engelleyen herhangi bir eklenti (Privacy Badger gibi) için tarayıcınızı kontrol ediniz",
+        "suggestions_2": "kimlik sunucusunun yöneticileriyle iletişime geçin <idserver />",
+        "suggestions_3": "bekleyip daha sonra tekrar deneyin",
+        "url": "(%(server)s) Kimlik Sunucusu",
+        "url_field_label": "Yeni bir kimlik sunucusu girin",
+        "url_not_https": "Kimlik sunucusu URL'si HTTPS olmalıdır"
+    },
+    "in_space": "%(spaceName)s alanında.",
+    "in_space1_and_space2": "%(space1Name)s ve %(space2Name)s alanlarında.",
+    "in_space_and_n_other_spaces": {
+        "one": "%(spaceName)s ve bir başka alanda.",
+        "other": "%(spaceName)s ve %(count)s diğer alanda."
+    },
+    "incompatible_browser": {
+        "continue": "Yine de devam et",
+        "description": "%(brand)s mevcut tarayıcınızda bulunmayan bazı tarayıcı özelliklerini kullanır. %(detail)s",
+        "detail_can_continue": "Devam etmeniz durumunda bazı özellikler çalışmayı durdurabilir ve gelecekte verilerinizi kaybetme riskiniz olabilir.",
+        "detail_no_continue": "En son sürümü kullanmıyorsanız tarayıcınızı güncelleyip tekrar deneyin.",
+        "learn_more": "Daha fazla bilgi",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "En iyi deneyim için <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge> veya <Safari>Safari</Safari>. kullanın.",
+        "title": "%(brand)s bu tarayıcıyı desteklemiyor",
+        "use_desktop_heading": "Bunun yerine %(brand)s Masaüstü kullanın",
+        "use_mobile_heading": "Bunun yerine mobil cihazlarda %(brand)s kullanın",
+        "use_mobile_heading_after_desktop": "Veya mobil uygulamamızı kullanın",
+        "windows_64bit": "Windows (64 bit)",
+        "windows_arm_64bit": "Windows (ARM 64-bit)"
+    },
+    "info_tooltip_title": "Bilgi",
+    "integration_manager": {
+        "connecting": "Bütünleştirme yöneticisine bağlanıyor",
+        "error_connecting": "Entegrasyon yöneticisi çevrim dışı veya anasunucunuza erişemiyor.",
+        "error_connecting_heading": "Entegrasyon yöneticisine bağlanılamadı",
+        "explainer": "Bütünleştirme yöneticileri yapılandırma verilerini alır ve sizin adınıza widget'ları değiştirebilir, oda davetleri gönderebilir ve güç düzeylerini ayarlayabilir.",
+        "manage_title": "Entegrasyonları yönet",
+        "toggle_label": "Entegrasyon yöneticisini etkinleştirin",
+        "use_im": "Botları, görsel bileşenleri ve çıkartma paketlerini yönetmek için bir entegrasyon yöneticisi kullanın.",
+        "use_im_default": "Botları, widget'ları ve çıkartma paketlerini yönetmek için bir bütünleştirme yöneticisi <b>(%(serverName)s)</b> kullanın."
+    },
+    "integrations": {
+        "disabled_dialog_description": "Bunu yapmak için Ayarlar'da %(manageIntegrations)s '' etkinleştirin.",
+        "disabled_dialog_title": "Entegrasyonlar devre dışı bırakıldı",
+        "impossible_dialog_description": "%(brand)s bunu yapmak için bir bütünleştirme yöneticisi kullanmanıza izin vermiyor. Lütfen bir yönetici ile iletişime geçin.",
+        "impossible_dialog_title": "Entegrasyonlara izin verilmiyor"
+    },
+    "invite": {
+        "ask_anyway_description": "Aşağıda listelenen Matrix Kimlikleri için profil bulunamadı - yine de bir DM başlatmak ister misiniz?",
+        "ask_anyway_label": "Yine de DM'ye başla",
+        "ask_anyway_never_warn_label": "Yine de DM'yi başlat ve beni bir daha uyarma",
+        "email_caption": "E-posta ile davet et",
+        "email_limit_one": "E-posta yoluyla davetler yalnızca birer birer gönderilebilir",
+        "email_use_default_is": "E-posta ile davet etmek için bir kimlik sunucusu kullan. <default> Varsayılanı kullan (%(defaultIdentityServerName)s</default> ya da <settings>Ayarlar</settings> kullanarak yönetin.",
+        "email_use_is": "E-postayla davet etmek için bir kimlik sunucusu kullanın. <settings>Ayarlar</settings>'da yönetin.",
+        "error_already_invited_room": "Kullanıcı zaten odaya davet edildi",
+        "error_already_invited_space": "Kullanıcı alana zaten davet edildi",
+        "error_already_joined_room": "Kullanıcı zaten odada",
+        "error_already_joined_space": "Kullanıcı zaten alanda",
+        "error_bad_state": "Kullanıcının davet edilebilmesi için öncesinde yasağının kaldırılması gereklidir.",
+        "error_dm": "DM'nizi oluşturamadık.",
+        "error_find_room": "Kullanıcıların davet edilmesinde bir şeyler yanlış gitti.",
+        "error_find_user_description": "Aşağıdaki kullanıcılar mevcut olmayabilir veya geçersiz olabilir ve davet edilemez: %(csvNames)s",
+        "error_find_user_title": "Aşağıdaki kullanıcılar bulunamadı",
+        "error_invite": "Bu kullanıcıları davet edemedik. Lütfen davet etmek istediğiniz kullanıcıları kontrol edin ve tekrar deneyin.",
+        "error_permissions_room": "Bu odaya kişi davet etme izniniz yok.",
+        "error_permissions_space": "Kullanıcıları bu alana davet etme izniniz yok.",
+        "error_profile_undisclosed": "Kullanıcı mevcut olabilir veya olmayabilir",
+        "error_transfer_multiple_target": "Bir çağrı yalnızca tek bir kullanıcıya aktarılabilir.",
+        "error_unfederated_room": "Bu oda federasyon dışıdır. Harici sunuculardan insanları davet edemezsiniz.",
+        "error_unfederated_space": "Bu alan federasyon dışıdır. Harici sunuculardan kişileri davet edemezsiniz.",
+        "error_unknown": "Bilinmeyen sunucu hatası",
+        "error_user_not_found": "Kullanıcı mevcut değil",
+        "error_version_unsupported_room": "Kullanıcının ana sunucusu odanın sürümünü desteklemiyor.",
+        "error_version_unsupported_space": "Kullanıcının ana sunucusu bu alanın sürümünü desteklemiyor.",
+        "failed_generic": "Operasyon başarısız oldu",
+        "failed_title": "Davet edilemedi",
+        "invalid_address": "Tanınmayan adres",
+        "name_email_mxid_share_room": "Birini adını, e-posta adresini, kullanıcı adını ( <userId/> gibi) veya <a>bu odayı paylaş</a> kullanarak davet edin.",
+        "name_email_mxid_share_space": "Birini adını, e-posta adresini, kullanıcı adını ( <userId/> gibi) veya <a>bu alanı paylaş</a> kullanarak davet edin.",
+        "name_mxid_share_room": "Birini adını, kullanıcı adını ( <userId/> gibi) veya <a>bu odayı paylaş</a> kullanarak davet edin.",
+        "name_mxid_share_space": "Birini adını, kullanıcı adını ( <userId/> gibi) veya <a>bu alanı paylaş</a> kullanarak davet edin.",
+        "recents_section": "Güncel Sohbetler",
+        "room_failed_partial": "Başkalarına davetler iletilmekle beraber, aşağıdakiler <RoomName/> odasına davet edilemedi",
+        "room_failed_partial_title": "Bazı davetler gönderilemiyor",
+        "room_failed_title": "Kullanıcılar %(roomName)s adresine davet edilemedi",
+        "send_link_prompt": "Veya davet bağlantısı gönder",
+        "start_conversation_name_email_mxid_prompt": "Biriyle adını, e-posta adresini veya kullanıcı adını ( <userId/> gibi) kullanarak sohbet başlatın.",
+        "start_conversation_name_mxid_prompt": "Biriyle adını veya kullanıcı adını kullanarak sohbet başlatın ( <userId/> gibi).",
+        "suggestions_disclaimer": "Bazı öneriler gizlilik için gizlenmiş olabilir.",
+        "suggestions_disclaimer_prompt": "Aradığınız kişiyi göremiyorsanız, aşağıdaki davet bağlantınızı gönderin.",
+        "suggestions_section": "Yakın Zamanda Doğrudan Mesajlaşıldı",
+        "to_room": "%(roomName)s davet et",
+        "to_space": "%(spaceName)s davet et",
+        "transfer_dial_pad_tab": "Arama tuşları",
+        "transfer_user_directory_tab": "Kullanıcı Dizini",
+        "unable_find_profiles_description_default": "Altta belirtilen Matrix ID li profiller bulunamıyor - Onları yinede davet etmek ister misiniz?",
+        "unable_find_profiles_invite_label_default": "Yinede davet et",
+        "unable_find_profiles_invite_never_warn_label_default": "Yinede davet et ve asla beni uyarma",
+        "unable_find_profiles_title": "Belirtilen kullanıcılar mevcut olmayabilir",
+        "unban_first_title": "Kullanıcı, yasağı kaldırılana kadar davet edilemez"
+    },
+    "inviting_user1_and_user2": "%(user1)s ve %(user2)s davet ediliyor",
+    "inviting_user_and_n_others": {
+        "one": "%(user)s ve diğer bir kişi davet ediliyor",
+        "other": "%(user)s ve %(count)s kişi davet ediliyor"
+    },
+    "items_and_n_others": {
+        "other": "<Items/> ve diğer %(count)s",
+        "one": "<Items/> ve bir diğeri"
+    },
+    "keyboard": {
+        "activate_button": "Seçili düğmeyi etkinleştir",
+        "alt": "Alt",
+        "autocomplete_cancel": "Otomatik tamamlamayı iptal et",
+        "autocomplete_force": "Tamamlamaya zorla",
+        "autocomplete_navigate_next": "Sonraki otomatik tamamlama önerisi",
+        "autocomplete_navigate_prev": "Önceki otomatik tamamlama önerisi",
+        "backspace": "Geri tuşu",
+        "cancel_reply": "Mesajı yanıtlamayı iptal et",
+        "category_autocomplete": "Otomatik Tamamlama",
+        "category_calls": "Aramalar",
+        "category_navigation": "Navigasyon",
+        "category_room_list": "Oda listesi",
+        "close_dialog_menu": "İletişim kutusunu veya bağlam menüsünü kapat",
+        "composer_jump_end": "Oluşturucunun sonuna atla",
+        "composer_jump_start": "Oluşturucunun başına atla",
+        "composer_navigate_next_history": "Oluşturucu geçmişindeki sonraki mesaja git",
+        "composer_navigate_prev_history": "Oluşturucu geçmişindeki önceki mesaja git",
+        "composer_new_line": "Yeni satır",
+        "composer_redo": "Düzenlemeyi yeniden yap",
+        "composer_toggle_bold": "Kalınlığı Aç/Kapat",
+        "composer_toggle_code_block": "Kod Bloğunu Aç/Kapat",
+        "composer_toggle_italics": "İtalik Aç/Kapa",
+        "composer_toggle_link": "Bağlantıyı Aç/Kapat",
+        "composer_toggle_quote": "Alıntıyı Aç/Kapat",
+        "composer_undo": "Düzenlemeyi geri al",
+        "control": "Ctrl",
+        "dismiss_read_marker_and_jump_bottom": "Okuma işaretini kaldır ve en alta atla",
+        "end": "End",
+        "enter": "Giriş",
+        "escape": "Esc",
+        "go_home_view": "Ana Sayfa Görünümüne Git",
+        "home": "Ana Sayfa",
+        "jump_first_message": "İlk mesaja atla",
+        "jump_last_message": "Son mesaja atla",
+        "jump_room_search": "Oda aramasına atla",
+        "jump_to_read_marker": "Okunmamış en eski mesaja atla",
+        "keyboard_shortcuts_tab": "Bu ayarlar sekmesini aç",
+        "navigate_next_history": "Yakın zamanda ziyaret edilen sonraki oda veya alan",
+        "navigate_next_message_edit": "Düzenlemek için bir sonraki mesaja git",
+        "navigate_prev_history": "Yakın zamanda ziyaret edilen önceki oda veya alan",
+        "navigate_prev_message_edit": "Düzenlemek için önceki mesaja git",
+        "next_landmark": "Sonraki dönüm noktasına git",
+        "next_room": "Sonraki oda veya DM",
+        "next_unread_room": "Sonraki okunmamış oda veya DM",
+        "number": "[sayı]",
+        "open_user_settings": "Kullanıcı ayarlarını aç",
+        "page_down": "Sayfa aşağı",
+        "page_up": "Sayfa yukarı",
+        "prev_landmark": "Önceki dönüm noktasına git",
+        "prev_room": "Önceki oda veya DM",
+        "prev_unread_room": "Önceki okunmamış oda veya DM",
+        "room_list_collapse_section": "Oda listesi bölümünü daralt",
+        "room_list_expand_section": "Oda listesi bölümünü genişlet",
+        "room_list_navigate_down": "Oda listesinde aşağı git",
+        "room_list_navigate_up": "Oda listesinde yukarı git",
+        "room_list_select_room": "Oda listesinden oda seçin",
+        "scroll_down_timeline": "Zaman çizelgesinde aşağı kaydır",
+        "scroll_up_timeline": "Zaman çizelgesinde yukarı kaydır",
+        "search": "Arama (etkinleştirilmeli)",
+        "send_sticker": "Çıkartma gönder",
+        "shift": "Üst karakter",
+        "space": "Boşluk",
+        "switch_to_space": "Sayıya göre alana geç",
+        "toggle_hidden_events": "Gizli etkinlik görünürlüğünü aç/kapat",
+        "toggle_microphone_mute": "Mikrofonu Aç/Kapa",
+        "toggle_right_panel": "Sağ paneli aç/kapat",
+        "toggle_space_panel": "Alan panelini aç/kapat",
+        "toggle_top_left_menu": "Sol üst menüyü aç/kapat",
+        "toggle_webcam_mute": "Kamerayı Aç/Kapa",
+        "upload_file": "Dosya yükle"
+    },
+    "labs": {
+        "allow_screen_share_only_mode": "Yalnızca ekran paylaşımına izin ver modu",
+        "ask_to_join": "Katılma isteğini etkinleştir",
+        "automatic_debug_logs": "Herhangi bir hatada otomatik olarak hata ayıklama günlükleri gönder",
+        "automatic_debug_logs_decryption": "Şifre çözme hatalarında otomatik olarak hata ayıklama günlükleri gönder",
+        "automatic_debug_logs_key_backup": "Anahtar yedekleme çalışmadığında hata ayıklama günlüklerini otomatik olarak gönder",
+        "beta_description": "%(brand)s için sırada ne var? Labsr, bir şeyleri erkenden elde etmenin, yeni özellikleri test etmenin ve piyasaya sürülmeden önce şekillendirmeye yardımcı olmanın en iyi yoludur.",
+        "beta_feature": "Bu bir beta özelliğidir",
+        "beta_feedback_leave_button": "Beta sürümünden çıkmak için ayarlarınızı ziyaret edin.",
+        "beta_feedback_title": "%(featureName)s Beta geri bildirimi",
+        "beta_section": "Gelecek özellikler",
+        "bridge_state": "Oda ayarlarındaki köprülerin bilgilerini göster",
+        "bridge_state_channel": "Kanal: <channelLink/>",
+        "bridge_state_creator": "Bu köprü <user /> tarafından sağlandı.",
+        "bridge_state_manager": "Bu köprü <user /> tarafından yönetiliyor.",
+        "bridge_state_workspace": "Çalışma alanı: <networkLink/>",
+        "click_for_info": "Daha fazla bilgi için tıklayın",
+        "currently_experimental": "Şu anda deneysel.",
+        "custom_themes": "Özel temaların eklenmesini destekleyin",
+        "dynamic_room_predecessors": "Dinamik oda öncülleri",
+        "dynamic_room_predecessors_description": "MSC3946 etkinleştir (geç gelen oda arşivlerini desteklemek için)",
+        "element_call_video_rooms": "Element Çağrısı video odaları",
+        "exclude_insecure_devices": "Mesaj gönderirken/alırken güvenli olmayan cihazları hariç tut",
+        "exclude_insecure_devices_description": "Bu mod etkinleştirildiğinde, şifrelenmiş mesajlar doğrulanmamış cihazlarla paylaşılmaz ve doğrulanmamış cihazlardan gelen mesajlar bir hata olarak gösterilir. Bu modu etkinleştirirseniz, cihazlarını doğrulamamış kullanıcılarla iletişim kuramayabileceğinizi unutmayın.",
+        "experimental_description": "Deneysel mi hissediyorsunuz? Geliştirme aşamasındaki en son fikirlerimizi deneyin. Bu özellikler nihai değildir; kararsız olabilirler, değişebilirler veya tamamen kaldırılabilirler. <a>Daha fazla bilgi için</a>.",
+        "experimental_section": "Erken önizlemeler",
+        "extended_profiles_msc_support": "Sunucunuzun MSC4133 desteklemesini gerektirir",
+        "feature_disable_call_per_sender_encryption": "Element Call için gönderen başına şifrelemeyi devre dışı bırakın",
+        "feature_wysiwyg_composer_description": "Mesaj oluşturucu olarak Markdown yerine zengin metin kullanın.",
+        "group_calls": "Yeni grup görüşmesi deneyimi",
+        "group_developer": "Geliştirici",
+        "group_encryption": "Şifreleme",
+        "group_experimental": "Deneysel",
+        "group_messaging": "Mesajlaşma",
+        "group_moderation": "Moderasyon",
+        "group_profile": "Profil",
+        "group_rooms": "Odalar",
+        "group_spaces": "Alanlar",
+        "group_themes": "Temalar",
+        "group_threads": "Mesaj dizileri",
+        "group_ui": "Kullanıcı arayüzü",
+        "group_voip": "Ses & Video",
+        "group_widgets": "Widget'lar",
+        "hidebold": "Bildirim noktasını gizle (yalnızca sayaç rozetlerini göster)",
+        "html_topic": "Oda konularının HTML gösterimini göster",
+        "join_beta": "Beta’ya katıl",
+        "join_beta_reload": "Betaya katılmak, %(brand)s yeniden yükleyecektir.",
+        "jump_to_date": "Tarihe atla (/jumptodate ve tarihe atla başlıklarını ekler)",
+        "jump_to_date_msc_support": "Sunucunuzun MSC3030 desteklemesi gerekir",
+        "latex_maths": "Mesajlarda LaTeX matematiğini göster",
+        "leave_beta": "Betadan ayrıl",
+        "leave_beta_reload": "Betadan çıkıldığında %(brand)s yeniden yüklenir.",
+        "location_share_live": "Canlı Konum Paylaşımı",
+        "location_share_live_description": "Geçici uygulama. Konumlar oda geçmişinde kalır.",
+        "mjolnir": "İnsanları görmezden gelmenin yeni yolları",
+        "msc3531_hide_messages_pending_moderation": "Moderatörlerin onay bekleyen mesajları gizlemesine izin verin.",
+        "new_room_list": "Yeni oda listesini etkinleştir",
+        "notification_settings": "Yeni Bildirim Ayarları",
+        "notification_settings_beta_caption": "Bildirim ayarlarınızı değiştirmenin daha basit bir yolunu sunuyoruz. %(brand)s istediğiniz gibi özelleştirin.",
+        "notification_settings_beta_title": "Bildirim Ayarları",
+        "notifications": "Oda başlığındaki bildirim panelini etkinleştir",
+        "release_announcement": "Yayın duyurusu",
+        "render_reaction_images": "Tepkilerde özel görüntüler oluştur",
+        "render_reaction_images_description": "Bazen \"özel emojiler\" olarak da adlandırılır.",
+        "report_to_moderators": "Moderatörlere bildir",
+        "report_to_moderators_description": "Moderasyonu destekleyen odalarda, \"Bildir\" düğmesi, kötüye kullanımı oda moderatörlerine bildirmenize olanak tanır.",
+        "sliding_sync": "Sliding Sync modu",
+        "sliding_sync_description": "Aktif geliştirme aşamasında, devre dışı bırakılamaz.",
+        "sliding_sync_disabled_notice": "Devre dışı bırakmak için oturumu kapatıp tekrar açın",
+        "sliding_sync_server_no_support": "Sunucunuz desteklemiyor",
+        "under_active_development": "Aktif geliştirme aşamasında.",
+        "unrealiable_e2e": "Şifreli odalarda güvenilir değil",
+        "video_rooms": "Video odaları",
+        "video_rooms_a_new_way_to_chat": "%(brand)s ile sesli ve görüntülü sohbet etmenin yeni bir yolu.",
+        "video_rooms_always_on_voip_channels": "Video odaları, %(brand)s içinde bir odaya yerleştirilmiş her zaman açık VoIP kanallarıdır.",
+        "video_rooms_beta": "Video odaları bir beta özelliğidir",
+        "video_rooms_faq1_answer": "Sol panelin oda bölümündeki \"+\" düğmesini kullanın.",
+        "video_rooms_faq1_question": "Nasıl görüntülü arama odası oluşturabilirim?",
+        "video_rooms_faq2_answer": "Evet, sohbet zaman çizelgesi videonun yanında görüntülenir.",
+        "video_rooms_faq2_question": "Görüntülü görüşmede yazılı sohbeti kullanabilir miyim?",
+        "video_rooms_feedbackSubheading": "Betayı denediğiniz için teşekkür ederiz, lütfen geliştirebilmemiz için olabildiğince ayrıntılı bilgi verin.",
+        "wysiwyg_composer": "Zengin metin düzenleyici"
+    },
+    "labs_mjolnir": {
+        "advanced_warning": "⚠ Bu ayarlar ileri düzey kullanıcılar içindir.",
+        "ban_reason": "Yoksayılan/Bloklanan",
+        "error_adding_ignore": "Yoksayılan kullanıcı/sunucu eklenirken hata",
+        "error_adding_list_description": "Lütfen oda kimliğini ya da adresini doğrulayıp yeniden deneyin.",
+        "error_adding_list_title": "Listeye abone olunurken hata",
+        "error_removing_ignore": "Yoksayılan kullanıcı/sunucu silinirken hata",
+        "error_removing_list_description": "Lütfen yeniden deneyin veya ipuçları için konsolunuza bakın.",
+        "error_removing_list_title": "Listeden abonelikten çıkılırken hata",
+        "explainer_1": "Görmezden gelmek istediğiniz kullanıcıları ya da sunucuları buraya ekleyin. %(brand)s uygulamasının herhangi bir karakteri eşleştirmesini istiyorsanız yıldız imi kullanın. Örneğin <code>@fobar:*</code>, \"foobar\" adlı kullanıcıların hepsini bütün sunucularda görmezden gelir.",
+        "explainer_2": "Kullanıcıları engelleme, hangi kullanıcıları engelleyeceğini belirleyen kurallar bulunduran bir engelleme listesi kullanılarak gerçekleşir. Bir engelleme listesine abone olmak, o listeden engellenen kullanıcıların veya sunucuların sizden gizlenmesi demektir.",
+        "lists": "Halizhazırdaki abonelikleriniz:",
+        "lists_description_1": "Bir yasak listesine abonelik ona katılmanıza yol açar!",
+        "lists_description_2": "Eğer istediğiniz bu değilse, kullanıcıları yoksaymak için lütfen farklı bir araç kullanın.",
+        "lists_heading": "Abone olunan listeler",
+        "lists_new_label": "Oda ID'si veya yasaklı listesinin adresi",
+        "no_lists": "Herhangi bir listeye aboneliğiniz bulunmuyor",
+        "personal_description": "Kişisel yasaklama listeniz, kişisel olarak mesajlarını görmek istemediğiniz tüm kullanıcıları/sunucuları içerir. İlk kullanıcınızı/sunucunuzu görmezden geldikten sonra, oda listenizde '%(myBanList)s' adında yeni bir oda belirecektir - yasaklama listesini etkin tutmak için bu odadan ayrılmayın.",
+        "personal_empty": "Kimseyi yok saymamışsınız.",
+        "personal_heading": "Kişisel yasak listesi",
+        "personal_new_label": "Yoksaymak için sunucu veya kullanıcı ID",
+        "personal_new_placeholder": "örn: @bot:* veya example.org",
+        "personal_section": "Halihazırda yoksaydıklarınız:",
+        "room_name": "Yasaklı Listem",
+        "room_topic": "Bu sizin engellediğiniz kullanıcılar/sunucular listeniz - odadan ayrılmayın!",
+        "rules_empty": "Yok",
+        "rules_server": "Sunucu kuralları",
+        "rules_title": "Yasak Liste Kuralları - %(roomName)s",
+        "rules_user": "Kullanıcı kuralları",
+        "something_went_wrong": "Bir şeyler hatalı gitti. Lütfen yeniden deneyin veya ipuçları için konsolunuza bakın.",
+        "title": "Yoksayılan kullanıcılar",
+        "view_rules": "Kuralları görüntüle"
+    },
+    "language_dropdown_label": "Dil Açılır Listesi",
+    "leave_room_dialog": {
+        "last_person_warning": "Buradaki tek kişi sensin. Ayrılırsanız, gelecekte siz de dahil olmak üzere hiç kimse katılamaz.",
+        "leave_room_question": "'%(roomName)s' odasından ayrılmak istediğinize emin misiniz ?",
+        "leave_space_question": "'%(spaceName)s' alanından ayrılmak istediğinizden emin misiniz?",
+        "room_leave_admin_warning": "Bu odadaki tek yönetici sizsiniz. Ayrılırsanız, hiç kimse oda ayarlarını değiştiremez veya diğer önemli işlemleri yapamaz.",
+        "room_leave_mod_warning": "Bu odadaki tek moderatör sizsiniz. Ayrılırsanız, hiç kimse oda ayarlarını değiştiremez veya diğer önemli işlemleri yapamaz.",
+        "room_rejoin_warning": "Bu oda herkese açık değildir. Davetiye olmadan tekrar katılamazsınız.",
+        "space_rejoin_warning": "Bu alan herkese açık değildir. Davet olmadan yeniden katılamazsınız."
+    },
+    "left_panel": {
+        "open_dial_pad": "Arama tuşlarını aç"
+    },
+    "lightbox": {
+        "rotate_left": "Sola Döndür",
+        "rotate_right": "Sağa Döndür",
+        "title": "Resim görünümü"
+    },
+    "location_sharing": {
+        "MapStyleUrlNotConfigured": "Bu ana sunucu haritaları görüntülemek için yapılandırılmamıştır.",
+        "MapStyleUrlNotReachable": "Bu ana sunucu, haritaları görüntülemek için doğru yapılandırılmamış veya yapılandırılan harita sunucusuna ulaşılamıyor olabilir.",
+        "WebGLNotEnabled": "Haritaları görüntülemek için WebGL gereklidir, lütfen tarayıcı ayarlarınızdan etkinleştirin.",
+        "click_drop_pin": "İğne bırakmak için tıkla",
+        "click_move_pin": "İğneyi taşımak için tıkla",
+        "close_sidebar": "Kenar çubuğunu kapat",
+        "error_fetch_location": "Konum alınamadı",
+        "error_no_perms_description": "Bu odada konum paylaşmak için doğru izinlere sahip olmanız gerekir.",
+        "error_no_perms_title": "Konum paylaşma izniniz yok",
+        "error_send_description": "%(brand)s konumunuzu gönderemedi. Lütfen daha sonra tekrar deneyin.",
+        "error_send_title": "Konumunuzu gönderemedik",
+        "error_sharing_live_location": "Canlı konumunuzu paylaşırken bir hata oluştu",
+        "error_stopping_live_location": "Canlı konumunuzu durdururken bir hata oluştu",
+        "expand_map": "Haritayı genişlet",
+        "failed_generic": "Konumunuz alınamadı. Lütfen daha sonra tekrar deneyin.",
+        "failed_load_map": "Harita yüklenemiyor",
+        "failed_permission": "%(brand)s konumunuzu almak için izin verilmedi. Lütfen tarayıcı ayarlarınızdan konum erişimine izin verin.",
+        "failed_timeout": "Konumunuzu almaya çalışırken zaman aşımı oluştu. Lütfen daha sonra tekrar deneyin.",
+        "failed_unknown": "Konum alınırken bilinmeyen hata. Lütfen daha sonra tekrar deneyin.",
+        "find_my_location": "Konumumu bul",
+        "live_description": "%(displayName)s canlı konumu",
+        "live_enable_description": "Lütfen dikkat: Bu, geçici bir uygulama kullanan bir labs özelliğidir. Bu, konum geçmişinizi silemeyeceğiniz ve ileri düzey kullanıcıların, canlı konumunuzu bu odayla paylaşmayı bıraktıktan sonra bile konum geçmişinizi görebileceği anlamına gelir.",
+        "live_enable_heading": "Canlı Konum Paylaşımı",
+        "live_location_active": "Canlı konumunuzu paylaşıyorsunuz",
+        "live_location_enabled": "Canlı konum etkin",
+        "live_location_ended": "Canlı konum sona erdi",
+        "live_location_error": "Canlı konum hatası",
+        "live_locations_empty": "Canlı konum yok",
+        "live_share_button": "%(duration)s paylaş",
+        "live_toggle_label": "Canlı konum paylaşımını etkinleştir",
+        "live_until": "%(expiryTime)s kadar canlı",
+        "live_update_time": "Güncellendi %(humanizedUpdateTime)s",
+        "loading_live_location": "Canlı konum yükleniyor...",
+        "location_not_available": "Konum kullanılamıyor.",
+        "map_feedback": "Harita geri bildirimi",
+        "mapbox_logo": "Mapbox logosu",
+        "reset_bearing": "Kerterizi kuzeye sıfırlayın",
+        "share_button": "Konum paylaş",
+        "share_type_live": "Canlı konumum",
+        "share_type_own": "Güncel Konumum",
+        "share_type_pin": "Bir İğne Bırak",
+        "share_type_prompt": "Hangi konum türünü paylaşmak istiyorsunuz?",
+        "toggle_attribution": "İlişkilendirmeyi aç/kapat"
+    },
+    "member_list": {
+        "count": {
+            "one": "%(count)s Üye",
+            "other": "%(count)s Üye"
+        },
+        "filter_placeholder": "Oda üyelerini Filtrele",
+        "invite_button_no_perms_tooltip": "Kullanıcıları davet etme izniniz yok",
+        "invited_label": "Davet Edildi",
+        "no_matches": "Eşleşme yok",
+        "power_label": "%(userName)s (güç %(powerLevelNumber)s)"
+    },
+    "member_list_back_action_label": "Oda üyeleri",
+    "message_edit_dialog_title": "Mesajları düzenle",
+    "migrating_crypto": "Sıkı durun. Şifrelemeyi daha hızlı ve daha güvenilir hale getirmek için %(brand)s güncelliyoruz.",
+    "mobile_guide": {
+        "toast_accept": "Uygulamayı kullan",
+        "toast_description": "%(brand)s mobil web tarayıcısında deneyseldir. Daha iyi bir deneyim ve en yeni özellikler için ücretsiz yerel uygulamamızı kullanın.",
+        "toast_title": "Daha iyi bir deneyim için uygulamayı kullanın"
+    },
+    "name_and_id": "%(name)s (%(userId)s)",
+    "no_more_results": "Başka sonuç yok",
+    "notif_panel": {
+        "empty_description": "Görünür bir bildiriminiz yok.",
+        "empty_heading": "Hepsini tamamladınız!"
+    },
+    "notifications": {
+        "all_messages": "Tüm mesajlar",
+        "all_messages_description": "Her mesaj için bildirim alın",
+        "class_global": "Genel",
+        "class_other": "Diğer",
+        "default": "Varsayılan",
+        "email_pusher_app_display_name": "E-posta Bildirimleri",
+        "enable_prompt_toast_description": "Masaüstü bildirimlerini etkinleştir",
+        "enable_prompt_toast_title": "Bildirimler",
+        "enable_prompt_toast_title_from_message_send": "Yanıtları kaçırma",
+        "error_change_title": "Bildirim ayarlarını değiştir",
+        "keyword": "Anahtar Kelime",
+        "keyword_new": "Yeni anahtar kelime",
+        "level_activity": "Etkinlik",
+        "level_highlight": "Vurgula",
+        "level_muted": "Sessiz",
+        "level_none": "Hiçbiri",
+        "level_notification": "Bildirim",
+        "level_unsent": "Gönderilmemiş",
+        "mark_all_read": "Tümünü okunmuş olarak işaretle",
+        "mentions_and_keywords": "@bahsetmeler ve anahtar kelimeler",
+        "mentions_and_keywords_description": "Yalnızca <a>settings</a> ayarlarınızda ayarlanan bahsetmeler ve anahtar kelimelerle bildirim alın",
+        "mentions_keywords": "Bahsetmeler ve anahtar kelimeler",
+        "message_didnt_send": "Mesaj gönderilmedi. Bilgi için tıklayınız.",
+        "mute_description": "Hiçbir bildirim almayacaksınız"
+    },
+    "notifier": {
+        "m.key.verification.request": "%(name)s doğrulama istiyor"
+    },
+    "onboarding": {
+        "create_room": "Grup sohbeti başlat",
+        "explore_rooms": "Herkese açık odaları keşfet",
+        "has_avatar_label": "Harika, bu insanların sen olduğunu anlamalarına yardımcı olur.",
+        "intro_byline": "Konuşmalarınıza sahip çıkın.",
+        "intro_welcome": "%(appName)s' e Hoş geldiniz",
+        "no_avatar_label": "İnsanların kimliğinizi anlaması için bir fotoğraf ekleyin.",
+        "send_dm": "Doğrudan Mesaj Gönder",
+        "welcome_detail": "Şimdi, başlamanıza yardım edelim",
+        "welcome_user": "Hoş Geldin %(name)s"
+    },
+    "pill": {
+        "permalink_other_room": "%(room)s içindeki mesaj",
+        "permalink_this_room": "%(user)s kullanıcısından mesaj"
+    },
+    "poll": {
+        "create_poll_action": "Anket oluştur",
+        "create_poll_title": "Anket oluştur",
+        "disclosed_notes": "Seçmenler oy kullandıkları anda sonuçları görür",
+        "edit_poll_title": "Anketi düzenle",
+        "end_description": "Bu anketi bitirmek istediğinizden emin misiniz? Bu, anketin sonuçlarını gösterecek ve kullanıcıların oy kullanmasını engelleyecektir.",
+        "end_message": "Anket sona erdi. Sonuç: %(topAnswer)s",
+        "end_message_no_votes": "Anket sona erdi. Oy kullanılmadı.",
+        "end_title": "Anketi bitir",
+        "error_ending_description": "Üzgünüz, anket bitmedi. Lütfen tekrar deneyin.",
+        "error_ending_title": "Anket sonlandırılamadı",
+        "error_voting_description": "Üzgünüz, oyunuz kaydedilmedi. Lütfen tekrar deneyin.",
+        "error_voting_title": "Oy kaydedilmedi",
+        "failed_send_poll_description": "Üzgünüz, oluşturmaya çalıştığınız anket gönderilmedi.",
+        "failed_send_poll_title": "Anket gönderilemedi",
+        "notes": "Sonuçlar yalnızca anketi sonlandırdığınızda gösterilir",
+        "options_add_button": "Seçenek ekle",
+        "options_heading": "Seçenekler oluştur",
+        "options_label": "Seçenek %(number)s",
+        "options_placeholder": "Bir seçenek yaz",
+        "topic_heading": "Anket sorunuz veya konunuz nedir?",
+        "topic_label": "Soru veya konu",
+        "topic_placeholder": "Bir şeyler yaz.",
+        "total_decryption_errors": "Şifre çözme hataları nedeniyle bazı oylar sayılmayabilir",
+        "total_n_votes": {
+            "one": "%(count)s oy kullanıldı. Sonuçları görmek için oy verin",
+            "other": "%(count)s oy kullanıldı. Sonuçları görmek için oy verin"
+        },
+        "total_n_votes_voted": {
+            "one": "%(count)s oyla",
+            "other": "%(count)s oyla"
+        },
+        "total_no_votes": "Oy kullanılmadı",
+        "total_not_ended": "Anket sona erdiğinde sonuçlar görünür olacak",
+        "type_closed": "Kapalı anket",
+        "type_heading": "Anket türü",
+        "type_open": "Açık anket",
+        "unable_edit_description": "Üzgünüz, oylar kullanıldıktan sonra anketi düzenleyemezsiniz.",
+        "unable_edit_title": "Anket düzenlenemiyor"
+    },
+    "power_level": {
+        "admin": "Yönetici",
+        "custom": "Özel (%(level)s)",
+        "custom_level": "Özel seviye",
+        "default": "Varsayılan",
+        "label": "Güç düzeyi",
+        "moderator": "Moderatör",
+        "restricted": "Sınırlı"
+    },
+    "powered_by_matrix": "Matrix tarafından desteklenmektedir",
+    "powered_by_matrix_with_logo": "Merkezi olmayan, şifreli sohbet ve işbirliği $matrixLogo tarafından desteklenmektedir.",
+    "presence": {
+        "away": "Uzakta",
+        "busy": "Meşgul",
+        "idle": "Boş",
+        "idle_for": "%(duration)s süresince boşta",
+        "offline": "Çevrimdışı",
+        "offline_for": "%(duration)s süresince çevrimdışı",
+        "online": "Çevrimiçi",
+        "online_for": "%(duration)s süresince çevrimiçi",
+        "unknown": "Bilinmeyen",
+        "unknown_for": "%(duration)s süresince bilinmiyor",
+        "unreachable": "Kullanıcının sunucusuna ulaşılamıyor"
+    },
+    "quick_settings": {
+        "all_settings": "Tüm ayarlar",
+        "metaspace_section": "Kenar çubuğuna sabitle",
+        "sidebar_settings": "Daha fazla seçenek",
+        "title": "Hızlı ayarlar"
+    },
+    "quit_warning": {
+        "call_in_progress": "Bir çağrıda gözüküyorsunuz , çıkmak istediğinizden emin misiniz ?",
+        "file_upload_in_progress": "Dosya yüklüyorsunuz gibi görünüyor , çıkmak istediğinizden emin misiniz ?"
+    },
+    "redact": {
+        "confirm_button": "Kaldırma İşlemini Onayla",
+        "confirm_description": "Bu etkinliği kaldırmak (silmek) istediğinizden emin misiniz?",
+        "confirm_description_state": "Bu şekilde oda değişikliklerini kaldırmanın, değişikliği geri alabileceğini unutmayın.",
+        "error": "Bu mesajı silemezsiniz (%(code)s)",
+        "ongoing": "Siliniyor…",
+        "reason_label": "Neden (İsteğe bağlı)"
+    },
+    "report_content": {
+        "description": "Bu mesajı bildirdiğinizde, benzersiz 'etkinlik kimliği' ev sunucunuzun yöneticisine gönderilir. Bu odadaki iletiler şifrelenmişse, ev sunucusu yöneticiniz ileti metnini okuyamaz veya herhangi bir dosya ya da resmi görüntüleyemez.",
+        "disagree": "Katılmıyorum",
+        "error_create_room_moderation_bot": "Moderasyon botu ile oda oluşturulamıyor",
+        "hide_messages_from_user": "Bu kullanıcıdan gelen tüm mevcut ve gelecekteki mesajları gizlemek istiyorsanız işaretleyin.",
+        "ignore_user": "Kullanıcıyı görmezden gel",
+        "illegal_content": "Yasadışı İçerik",
+        "missing_reason": "Lütfen neden raporlama yaptığınızı belirtin.",
+        "nature": "Lütfen bir nitelik seçin ve bu mesajın neden taciz edici olduğunu açıklayın.",
+        "nature_disagreement": "Bu kullanıcının yazdıkları yanlıştır.\nBu durum oda moderatörlerine bildirilecektir.",
+        "nature_illegal": "Bu kullanıcı yasadışı davranışlar sergiliyor, örneğin insanlara iftira atıyor veya şiddet tehdidinde bulunuyor.\nBu durum oda moderatörlerine bildirilecek ve onlar da konuyu yasal mercilere taşıyabileceklerdir.",
+        "nature_nonstandard_admin": "Bu oda yasadışı veya zehirli içeriğe adanmıştır veya moderatörler yasadışı veya zehirli içeriği denetlemede başarısız olmuşlardır.\nBu durum %(homeserver)s yöneticilerine bildirilecektir.",
+        "nature_nonstandard_admin_encrypted": "Bu oda yasadışı veya zehirli içeriğe adanmıştır veya moderatörler yasadışı veya zehirli içeriği denetlemekte başarısız olmaktadır.\nBu durum %(homeserver)s yöneticilerine bildirilecektir. Yöneticiler bu odanın şifrelenmiş içeriğini okuyamayacaktır.",
+        "nature_other": "Başka herhangi bir sebep. Lütfen sorunu açıklayın.\nBu durum oda moderatörlerine bildirilecektir.",
+        "nature_spam": "Bu kullanıcı, odaya reklamlar, reklam bağlantıları veya propaganda ile spam gönderiyor.\nBu durum oda moderatörlerine bildirilecektir.",
+        "nature_toxic": "Bu kullanıcı, örneğin diğer kullanıcılara hakaret ederek veya aile dostu bir odada yalnızca yetişkinlere yönelik içerik paylaşarak veya bu odanın kurallarını başka bir şekilde ihlal ederek zehirli davranışlar sergiliyor.\nBu durum oda moderatörlerine bildirilecektir.",
+        "other_label": "Diğer",
+        "report_content_to_homeserver": "Ana Sunucu Yöneticinize İçeriği Raporlayın",
+        "report_entire_room": "Tüm odayı bildir",
+        "spam_or_propaganda": "Spam veya propaganda",
+        "toxic_behaviour": "Zehirli Davranış"
+    },
+    "restore_key_backup_dialog": {
+        "count_of_decryption_failures": "%(failedCount)s adet oturum çözümlenemedi!",
+        "count_of_successfully_restored_keys": "%(sessionCount)s anahtarları başarıyla geri yüklendi",
+        "enter_key_description": "Güvenlik Anahtarınızı girerek güvenli mesaj geçmişinize erişin ve güvenli mesajlaşmayı ayarlayın.",
+        "enter_key_title": "Güvenlik Anahtarını Girin",
+        "enter_phrase_description": "Güvenli mesaj geçmişinize erişin ve Güvenlik İfadenizi girerek güvenli mesajlaşmayı ayarlayın.",
+        "enter_phrase_title": "Güvenlik İfadesini Girin",
+        "incorrect_security_phrase_dialog": "Bu Güvenlik İfadesi ile yedeklemenin şifresi çözülemedi: lütfen doğru Güvenlik İfadesini girdiğinizden emin olun.",
+        "incorrect_security_phrase_title": "Yanlış Güvenlik İfadesi",
+        "key_backup_warning": "<b>Uyarı</b> : Anahtar yedeklemesini yalnızca güvenilir bir bilgisayardan ayarlamalısınız.",
+        "key_fetch_in_progress": "Anahtarlar sunucudan alınıyor...",
+        "key_forgotten_text": "Güvenlik Anahtarınızı unuttuysanız şunları yapabilirsiniz: <button>yeni kurtarma seçenekleri ayarlayın</button>",
+        "key_is_invalid": "Geçerli bir Güvenlik Anahtarı değil",
+        "key_is_valid": "Bu geçerli bir Güvenlik Anahtarı gibi görünüyor!",
+        "keys_restored_title": "Anahtarlar geri yüklendi",
+        "load_error_content": "Yedek durumu yüklenemiyor",
+        "load_keys_progress": "%(completed)s / %(total)s anahtarlar geri yüklendi",
+        "no_backup_error": "Yedek bulunamadı!",
+        "phrase_forgotten_text": "Güvenlik İfadenizi unuttuysanız, <button1>Güvenlik Anahtarınızı kullanabilirsiniz</button1> veya <button2>yeni kurtarma seçeneklerini ayarlayın</button2>",
+        "recovery_key_mismatch_description": "Bu Güvenlik Anahtarı ile yedeklemenin şifresi çözülemedi: lütfen doğru Güvenlik Anahtarı'nı girdiğinizden emin olun.",
+        "recovery_key_mismatch_title": "Güvenlik Anahtarı uyuşmazlığı",
+        "restore_failed_error": "Yedekleme geri yüklenemiyor"
+    },
+    "right_panel": {
+        "add_integrations": "Uzantı ekle",
+        "add_topic": "Konu ekle",
+        "extensions_button": "Uzantılar",
+        "extensions_empty_description": "Bu odaya göz atmak ve uzantı eklemek için \"%(addIntegrations)s\" öğesini seçin",
+        "extensions_empty_title": "Daha fazla araç, widget ve bot ile üretkenliği artırın",
+        "files_button": "Dosyalar",
+        "pinned_messages": {
+            "empty_description": "Bir mesaj seçin ve buraya eklemek için \"%(pinAction)s\" seçeneğini seçin.",
+            "empty_title": "Önemli mesajları kolayca keşfedilebilmeleri için sabitleyin",
+            "header": {
+                "one": "1 Sabitlenmiş mesaj",
+                "other": "%(count)s Sabitlenmiş mesaj"
+            },
+            "limits": {
+                "one": "",
+                "other": "En fazla %(count)s widget'ı sabitleyebilirsiniz"
+            },
+            "menu": "Menüyü aç",
+            "release_announcement": {
+                "close": "Tamam",
+                "description": "Sabitlenmiş tüm mesajları burada bulabilirsiniz. Herhangi bir mesajın üzerine gelin ve eklemek için \"Sabitle\"yi seçin.",
+                "title": "Tüm yeni sabitlenmiş mesajlar"
+            },
+            "reply_thread": "<link>Mesaj dizisinde</link> yanıtla",
+            "unpin_all": {
+                "button": "Tüm mesajların sabitlemesini kaldır",
+                "content": "Sabitlenmiş tüm mesajları gerçekten kaldırmak istediğinizden emin olun. Bu eylem geri alınamaz.",
+                "title": "Tüm mesajların sabitlemesini kaldırmak istiyor musunuz?"
+            },
+            "view": "Zaman çizelgesinde görüntüle"
+        },
+        "pinned_messages_button": "Sabitlenmiş mesajlar",
+        "poll": {
+            "active_heading": "Etkin anketler",
+            "empty_active": "Bu odada aktif anket yok",
+            "empty_active_load_more": "Aktif anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin",
+            "empty_active_load_more_n_days": {
+                "one": "Geçen gün için aktif anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin",
+                "other": "Geçtiğimiz %(count)s günde aktif anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin"
+            },
+            "empty_past": "Bu odada geçmiş anket yok",
+            "empty_past_load_more": "Geçmiş anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin",
+            "empty_past_load_more_n_days": {
+                "one": "Geçen gün için geçmiş anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin",
+                "other": "Geçtiğimiz %(count)s gün için geçmiş anket yok. Önceki aylara ait anketleri görüntülemek için daha fazla anket yükleyin"
+            },
+            "final_result": {
+                "one": "%(count)s oya dayalı sonuç",
+                "other": "%(count)s oya dayalı sonuç"
+            },
+            "load_more": "Daha fazla anket yükle",
+            "loading": "Anketler yükleniyor",
+            "past_heading": "Geçmiş anketler",
+            "view_in_timeline": "Anketi zaman çizelgesinde görüntüle",
+            "view_poll": "Anketi görüntüle"
+        },
+        "polls_button": "Anketler",
+        "room_summary_card": {
+            "title": "Oda bilgisi"
+        },
+        "thread_list": {
+            "context_menu_label": "Mesaj dizisi seçenekleri"
+        },
+        "video_room_chat": {
+            "title": "Sohbet"
+        }
+    },
+    "room": {
+        "3pid_invite_email_not_found_account": "Bu davet, hesabınızla ilişkili olmayan %(email)s adresine gönderildi",
+        "3pid_invite_email_not_found_account_room": "Bu davet, %(roomName)s odasına %(email)s e-posta adresi üzerinden yollanmıştır ve sizinle ilgili değildir",
+        "3pid_invite_error_description": "Davetinizi doğrulamaya çalışırken bir hata (%(errcode)s) gerçekleşti. Bu bilgiyi sizi davet eden kişiye iletmeyi deneyebilirsiniz.",
+        "3pid_invite_error_invite_action": "Yine de katılmayı deneyin",
+        "3pid_invite_error_invite_subtitle": "Sadece çalışan bir davetle katılabilirsiniz.",
+        "3pid_invite_error_public_subtitle": "Hala buraya katılabilirsiniz.",
+        "3pid_invite_error_title": "Davetinizle ilgili bir sorun oluştu.",
+        "3pid_invite_error_title_room": "%(roomName)s odasına davet işleminizde birşeyler yanlış gitti",
+        "3pid_invite_no_is_subtitle": "Doğrudan %(brand)s uygulamasından davet isteği almak için Ayarlardan bir kimlik sunucusu belirleyin.",
+        "banned_by": "%(memberName)s tarafından yasaklandınız",
+        "banned_from_room_by": "%(memberName)s tarafından %(roomName)s odası size yasaklandı",
+        "context_menu": {
+            "copy_link": "Oda bağlantısını kopyala",
+            "favourite": "Favori",
+            "forget": "Odayı unut",
+            "low_priority": "Düşük Öncelikli",
+            "mark_read": "Okundu olarak işaretle",
+            "mark_unread": "Okunmamış olarak işaretle",
+            "notifications_default": "Varsayılan ayarlarla eşleştir",
+            "notifications_mute": "Odayı sessize al",
+            "title": "Oda ayarları",
+            "unfavourite": "Favorilere eklendi"
+        },
+        "creating_room_text": "%(names)s ile bir oda oluşturuyoruz",
+        "dm_invite_action": "Sohbet başlat",
+        "dm_invite_subtitle": "<userName/> sohbet etmek istiyor",
+        "dm_invite_title": "%(user)s ile sohbet etmek ister misin?",
+        "drop_file_prompt": "Yüklemek için dosyaları buraya bırakın",
+        "edit_topic": "Başlığı Düzenle",
+        "error_3pid_invite_email_lookup": "Kullanıcı e-posta ile bulunamıyor",
+        "error_cancel_knock_title": "İptal edilemedi",
+        "error_join_403": "Bu odaya erişmek için bir davetiyeye ihtiyacınız var.",
+        "error_join_404_1": "Sunucu listesi sağlamadan bir oda kimliği kullanarak katılmaya çalıştınız. Oda kimlikleri dahili tanımlayıcılardır ve ek bilgi olmadan bir odaya katılmak için kullanılamazlar.",
+        "error_join_404_2": "Eğer bir oda adresi biliyorsanız, onun üzerinden katılmayı deneyin.",
+        "error_join_404_invite": "Sizi davet eden kişi çoktan ayrıldı veya sunucusu çevrimdışı.",
+        "error_join_404_invite_same_hs": "Sizi davet eden kişi çoktan ayrıldı.",
+        "error_join_connection": "Katılım sırasında bir hata oluştu.",
+        "error_join_incompatible_version_1": "Üzgünüz, ana sunucunuz buraya katılmak için çok eski.",
+        "error_join_incompatible_version_2": "Lütfen anasunucu yöneticiniz ile bağlantıya geçin.",
+        "error_join_title": "Katılamadı",
+        "error_jump_to_date": "Sunucu, %(errorCode)s hata koduyla %(statusCode)s döndü",
+        "error_jump_to_date_connection": "Verilen tarihi bulmaya ve atlamaya çalışırken bir ağ hatası oluştu. Ana sunucunuz kapalı olabilir veya internet bağlantınızla ilgili geçici bir sorun olabilir. Lütfen tekrar deneyin. Bu devam ederse, lütfen ana sunucusu yöneticinizle iletişime geçin.",
+        "error_jump_to_date_details": "Hata ayrıntıları",
+        "error_jump_to_date_not_found": "İleriye dönük bir etkinlik bulamadık. %(dateString)s Daha erken bir tarih seçmeyi deneyin.",
+        "error_jump_to_date_send_logs_prompt": "Sorunu bulmamıza yardımcı olması için lütfen <debugLogsLink>hata ayıklama günlüklerini</debugLogsLink> gönderin.",
+        "error_jump_to_date_title": "O tarihte etkinlik bulunamıyor",
+        "face_pile_summary": {
+            "one": "tanıdığınız %(count)s kişi zaten katıldı",
+            "other": "tanıdığınız %(count)s kişi zaten katıldı"
+        },
+        "face_pile_tooltip_label": {
+            "one": "1 üyeyi görüntüle",
+            "other": "Tüm %(count)s üyeyi görüntüle"
+        },
+        "face_pile_tooltip_shortcut": "%(commaSeparatedMembers)s dahil",
+        "face_pile_tooltip_shortcut_joined": "Siz de dahil olmak üzere, %(commaSeparatedMembers)s",
+        "failed_reject_invite": "Daveti reddetme başarısız oldu",
+        "forget_room": "Bu odayı unut",
+        "forget_space": "Bu alanı unut",
+        "header": {
+            "n_people_asking_to_join": {
+                "one": "Katılmak istiyor",
+                "other": "%(count)s kişi katılmak istiyor"
+            },
+            "room_is_public": "Bu oda herkese açık"
+        },
+        "header_avatar_open_settings_label": "Oda ayarlarını aç",
+        "header_face_pile_tooltip": "Kişiler",
+        "header_untrusted_label": "Güvenilir değil",
+        "inaccessible": "Bu oda veya alan şu anda erişilebilir değil.",
+        "inaccessible_name": "%(roomName)s şu anda erişilebilir değil.",
+        "inaccessible_subtitle_1": "Daha sonra tekrar deneyin veya oda ya da alan yöneticisinden erişiminiz olup olmadığını kontrol etmesini isteyin.",
+        "inaccessible_subtitle_2": "Odaya veya alana erişmeye çalışırken %(errcode)s gerçekleşti. Bu mesajı yanlışlıkla gördüğünüzü düşünüyorsanız, lütfen <issueLink>bir hata raporu gönderin</issueLink>.",
+        "intro": {
+            "dm_caption": "Biriniz bir başkasını davet etmediğiniz sürece bu görüşmede sadece ikiniz varsınız.",
+            "enable_encryption_prompt": "Ayarlarda şifrelemeyi etkinleştirin.",
+            "encrypted_3pid_dm_pending_join": "Herkes katıldıktan sonra sohbet edebileceksiniz",
+            "no_avatar_label": "İnsanların odanı kolayca tanıması için bir fotoğraf ekle.",
+            "no_topic": "İnsanların ne hakkında olduğunu bilmelerine yardımcı olmak için <a>Konu ekle</a>.",
+            "private_unencrypted_warning": "Özel mesajlarınız normalde şifrelenir, ancak bu oda şifrelenmemiş. Genellikle bunun nedeni desteklenmeyen bir cihaz veya e-posta davetleri gibi kullanılan bir yöntemdir.",
+            "room_invite": "Sadece bu odaya davet et",
+            "send_message_start_dm": "<displayName/> adlı kişiyi sohbete davet etmek için ilk mesajınızı gönderin",
+            "start_of_dm_history": "Bu <displayName/> ile olan direkt mesaj geçmişinizin başlangıcıdır.",
+            "start_of_room": "Bu <roomName/> odasının başlangıcıdır.",
+            "topic": "Konu: %(topic)s ",
+            "topic_edit": "Konu: %(topic)s (<a>düzenle</a>)",
+            "unencrypted_warning": "Uçtan uca şifreleme etkin değil",
+            "user_created": "%(displayName)s bu odayı oluşturdu.",
+            "you_created": "Bu odayı oluşturdunuz."
+        },
+        "invite_email_mismatch_suggestion": "Davetiyeleri doğrudan %(brand)s adresinden almak için bu e-postayı Ayarlar'da paylaşın.",
+        "invite_sent_to_email": "Bu davet %(email)s adresine gönderildi",
+        "invite_sent_to_email_room": "%(roomName)s odası daveti %(email)s adresine gönderildi",
+        "invite_subtitle": "<userName/> davet etti",
+        "invite_this_room": "Bu odaya davet et",
+        "invite_title": "%(roomName)s odasına katılmak ister misin?",
+        "inviter_unknown": "Bilinmeyen",
+        "invites_you_text": "<inviter/> sizi davet ediyor",
+        "join_button_account": "Kayıt Ol",
+        "join_failed_needs_invite": "%(roomName)s görüntülemek için bir davetiyeye ihtiyacınız var",
+        "join_the_discussion": "Tartışmaya katılın",
+        "join_title": "Sohbete başlamak için odaya katılın",
+        "join_title_account": "Konuşmaya bir hesapla katıl",
+        "joining": "Katılınıyor…",
+        "joining_room": "Odaya katılıyor…",
+        "joining_space": "Alana katılıyor…",
+        "jump_read_marker": "İlk okunmamış iletiye atla.",
+        "jump_to_bottom_button": "En son mesajlara git",
+        "jump_to_date": "Tarihe atla",
+        "jump_to_date_beginning": "Odanın başlangıcı",
+        "jump_to_date_prompt": "Atlamak için bir tarih seçin",
+        "kick_reason": "Sebep: %(reason)s",
+        "kicked_by": "%(memberName)s tarafından kovuldunuz.",
+        "kicked_from_room_by": "%(roomName)s odasından %(memberName)s tarafından kovuldunuz",
+        "knock_cancel_action": "İsteği iptal et",
+        "knock_denied_subtitle": "Erişiminiz reddedildiğinden, grubun yöneticisi veya moderatörü tarafından davet edilmediğiniz sürece yeniden katılamazsınız.",
+        "knock_denied_title": "Erişiminiz reddedildi",
+        "knock_message_field_placeholder": "Mesaj (isteğe bağlı)",
+        "knock_prompt": "Katılma isteği gönder?",
+        "knock_prompt_name": "%(roomName)s katılma isteği gönder?",
+        "knock_send_action": "Erişim iste",
+        "knock_sent": "Katılma isteği gönderildi",
+        "knock_sent_subtitle": "Katılma isteğiniz beklemede.",
+        "knock_subtitle": "Sohbeti görüntülemek veya sohbete katılmak için bu odaya erişim izni almanız gerekir. Aşağıdan katılmak için bir istek gönderebilirsiniz.",
+        "leave_error_title": "Odadan ayrılırken hata",
+        "leave_server_notices_description": "Bu oda, Ana Sunucudan gelen önemli mesajlar için kullanılır, bu yüzden ayrılamazsınız.",
+        "leave_server_notices_title": "Sunucu Bildirimleri odasından ayrılınamıyor",
+        "leave_unexpected_error": "Odadan ayrılmaya çalışırken beklenmeyen sunucu hatası",
+        "link_email_to_receive_3pid_invite": "Doğrudan %(brand)s uygulamasından davet isteği almak için bu e-posta adresini Ayarlardan kendi hesabınıza bağlayın.",
+        "loading_preview": "Önizleme yükleniyor",
+        "no_peek_join_prompt": "%(roomName)s odasında önizleme yapılamaz. Katılmak ister misin?",
+        "no_peek_no_name_join_prompt": "Ön izleme yok, katılmak ister misiniz?",
+        "not_found_subtitle": "Doğru yerde olduğunuza emin misiniz?",
+        "not_found_title": "Bu oda veya alan mevcut değil.",
+        "not_found_title_name": "%(roomName)s mevcut değil.",
+        "peek_join_prompt": "%(roomName)s odasını inceliyorsunuz. Katılmak ister misiniz?",
+        "pinned_message_badge": "Sabitlenmiş mesaj",
+        "pinned_message_banner": {
+            "button_close_list": "Listeyi kapat",
+            "button_view_all": "Tümünü görüntüle",
+            "description": "Bu odada sabitlenmiş mesajlar var. Görüntülemek için tıklayın.",
+            "go_to_message": "Sabitlenmiş mesajı zaman çizelgesinde görüntüle.",
+            "title": "<bold>%(index)s / %(length)s</bold> Sabitlenmiş mesajlar"
+        },
+        "read_topic": "Başlığı okumak için tıklayın",
+        "rejecting": "Davet reddediliyor…",
+        "rejoin_button": "Yeniden katıl",
+        "search": {
+            "all_rooms_button": "Tüm odaları ara",
+            "placeholder": "Mesajları ara…",
+            "summary": {
+                "one": "\"<query/>\" için 1 sonuç bulundu",
+                "other": "\"<query/>\" için %(count)s sonuç bulundu"
+            },
+            "this_room_button": "Bu odayı ara"
+        },
+        "status_bar": {
+            "delete_all": "Tümünü sil",
+            "exceeded_resource_limit": "Bu ana sunucu bir kaynak sınırını aştığı için mesajınız gönderilemedi. Hizmeti kullanmaya devam etmek için lütfen <a>hizmet yöneticinizle</a> iletişime geçin.",
+            "homeserver_blocked": "Bu ana sunucu yöneticisi tarafından engellendiği için mesajınız gönderilemedi. Hizmeti kullanmaya devam etmek için lütfen <a>hizmet yöneticinizle</a> iletişime geçin.",
+            "monthly_user_limit_reached": "Bu ana sunucu Aylık Aktif Kullanıcı Limitine ulaştığı için mesajınız gönderilmedi. Hizmeti kullanmaya devam etmek için lütfen <a>hizmet yöneticinizle</a> iletişime geçin.",
+            "requires_consent_agreement": "<consentLink>Şartlar ve koşullarımızı </consentLink> inceleyip kabul edene kadar herhangi bir mesaj gönderemezsiniz.",
+            "retry_all": "Tümünü yeniden dene",
+            "select_messages_to_retry": "Yeniden denemek veya silmek için mesajların hepsini seçebilir veya tek tek seçebilirsiniz",
+            "server_connectivity_lost_description": "Gönderilen iletiler bağlantınız geri gelene kadar saklanacak.",
+            "server_connectivity_lost_title": "Sunucuyla olan bağlantı kesildi.",
+            "some_messages_not_sent": "Bazı mesajlarınız gönderilmedi"
+        },
+        "unknown_status_code_for_timeline_jump": "bilinmeyen durum kodu",
+        "unread_notifications_predecessor": {
+            "one": "Bu odanın önceki bir sürümünde %(count)s okunmamış bildiriminiz var.",
+            "other": "Bu odanın önceki bir sürümünde %(count)s okunmamış bildiriminiz var."
+        },
+        "upgrade_error_description": "Seçtiğiniz oda sürümünün sunucunuz tarafından desteklenip desteklenmediğini iki kez kontrol edin ve yeniden deneyin.",
+        "upgrade_error_title": "Oda güncellenirken hata",
+        "upgrade_warning_bar": "Bu odayı güncellerseniz bu oda kapanacak ve yerine aynı adlı, güncellenmiş bir oda geçecek.",
+        "upgrade_warning_bar_admins": "Bu uyarıyı sadece oda yöneticileri görür",
+        "upgrade_warning_bar_unstable": "Bu oda, <roomVersion /> oda sürümünü kullanmaktadır ve ana sunucunuz tarafından <i>tutarsız</i> olarak işaretlenmiştir.",
+        "upgrade_warning_bar_upgraded": "Bu oda daha önceden yükseltilmiş.",
+        "upload": {
+            "uploading_multiple_file": {
+                "one": "%(filename)s ve %(count)s kadarı yükleniyor",
+                "other": "%(filename)s ve %(count)s kadarları yükleniyor"
+            },
+            "uploading_single_file": "%(filename)s yükleniyor"
+        },
+        "waiting_for_join_subtitle": "Davet edilen kullanıcılar %(brand)s'a katıldıktan sonra sohbet edebileceksiniz ve oda uçtan uca şifrelenecek",
+        "waiting_for_join_title": "Kullanıcıların katılması bekleniyor %(brand)s"
+    },
+    "room_list": {
+        "add_room_label": "Oda ekle",
+        "add_space_label": "Alan ekle",
+        "breadcrumbs_empty": "Yakında ziyaret edilen oda yok",
+        "breadcrumbs_label": "En son ziyaret edilmiş odalar",
+        "failed_add_tag": "%(tagName)s etiketi odaya eklenemedi",
+        "failed_remove_tag": "Odadan %(tagName)s etiketi kaldırılamadı",
+        "failed_set_dm_tag": "Doğrudan mesaj etiketi ayarlanamadı",
+        "filters": {
+            "favourite": "Favoriler",
+            "people": "Kişiler",
+            "rooms": "Odalar",
+            "unread": "Okunmamış"
+        },
+        "home_menu_label": "Ana sayfa seçenekleri",
+        "join_public_room_label": "Herkese açık odaya katıl",
+        "joining_rooms_status": {
+            "one": "Şu anda %(count)s odaya katılıyor",
+            "other": "Şu anda %(count)s odaya katılıyor"
+        },
+        "list_title": "Oda listesi",
+        "notification_options": "Bildirim ayarları",
+        "open_space_menu": "Açık alan menüsü",
+        "primary_filters": "Oda listesi filtreleri",
+        "redacting_messages_status": {
+            "one": "Şu anda %(count)s odadaki mesajlar kaldırılıyor",
+            "other": "Şu anda %(count)s odadaki mesajlar kaldırılıyor"
+        },
+        "room": {
+            "open_room": "Açık oda %(roomName)s"
+        },
+        "show_less": "Daha az göster",
+        "show_n_more": {
+            "one": "%(count)s adet daha fazla göster",
+            "other": "%(count)s adet daha fazla göster"
+        },
+        "show_previews": "Mesajların ön izlemelerini göster",
+        "sort_by": "Göre sırala",
+        "sort_by_activity": "Aktivite",
+        "sort_by_alphabet": "A-Z",
+        "sort_unread_first": "Önce okunmamış mesajları olan odaları göster",
+        "space_menu": {
+            "home": "Alan ana sayfa",
+            "space_settings": "Alan Ayarları"
+        },
+        "space_menu_label": "%(spaceName)s menü",
+        "sublist_options": "Liste seçenekleri",
+        "suggested_rooms_heading": "Önerilen Odalar"
+    },
+    "room_settings": {
+        "access": {
+            "description_space": "Kimlerin görüntüleyebileceğine ve katılabileceğine karar verin %(spaceName)s.",
+            "title": "Erişim"
+        },
+        "advanced": {
+            "error_upgrade_description": "Oda güncelleme tamamlanamadı",
+            "error_upgrade_title": "Oda güncelleme başarısız",
+            "information_section_room": "Oda bilgisi",
+            "information_section_space": "Alan bilgisi",
+            "room_id": "Dahili oda kimliği",
+            "room_predecessor": "%(roomName)s'deki eski mesajları görüntüle",
+            "room_upgrade_button": "Bu odayı önerilen oda sürümüne yükselt",
+            "room_upgrade_warning": "<b>Uyarı</b>: bir odayı yükseltmek <i>oda üyelerini odanın yeni sürümüne otomatik olarak taşımayacaktır.</i> Odanın eski sürümünde yeni odaya bir bağlantı yayınlayacağız - oda üyeleri yeni odaya katılmak için bu bağlantıya tıklamak zorunda kalacaklar.",
+            "room_version": "Oda versiyonu:",
+            "room_version_section": "Oda sürümü",
+            "space_predecessor": "%(spaceName)s alanının eski sürümünü görüntüleyin.",
+            "space_upgrade_button": "Bu alanı önerilen oda sürümüne yükselt",
+            "unfederated": "Bu oda uzak Matrix Sunucuları tarafından erişilebilir değil",
+            "upgrade_button": "Bu odayı %(version)s sürümüne yükseltin",
+            "upgrade_dialog_description": "Bu odayı yükseltmek, odanın mevcut örneğini kapatmayı ve yerine yeni bir oda oluşturmayı gerektirir. Oda üyelerine mümkün olan en iyi deneyimi sunmak için şunları yapacağız:",
+            "upgrade_dialog_description_1": "Aynı ad, açıklama ve avatar ile yeni bir oda oluştur",
+            "upgrade_dialog_description_2": "Tüm yerel oda takma adlarını yeni odayı gösterecek şekilde güncelleyin",
+            "upgrade_dialog_description_3": "Kullanıcıların odanın eski sürümünde konuşmasını durdurun ve kullanıcılara yeni odaya geçmelerini öneren bir mesaj gönderin",
+            "upgrade_dialog_description_4": "Kullanıcıların eski mesajları görebilmesi için yeni odanın başına eski odaya bir bağlantı koyun",
+            "upgrade_dialog_title": "Oda Sürümünü Yükselt",
+            "upgrade_dwarning_ialog_title_public": "Genel odayı yükseltin",
+            "upgrade_warning_dialog_description": "Bir odayı yükseltmek ileri seviye bir eylemdir ve genellikle bir oda hatalar, eksik özellikler veya güvenlik açıkları nedeniyle kararsız olduğunda önerilir.",
+            "upgrade_warning_dialog_explainer": "<b>Yükseltmenin odanın yeni bir sürümünü oluşturacağını lütfen unutmayın</b>. Tüm güncel mesajlar bu arşivlenmiş odada kalacaktır.",
+            "upgrade_warning_dialog_footer": "Bu odayı <oldVersion /> versiyonundan <newVersion /> versiyonuna güncelleyeceksiniz.",
+            "upgrade_warning_dialog_invite_label": "Bu odadaki üyeleri otomatik olarak yeni odaya davet et",
+            "upgrade_warning_dialog_report_bug_prompt": "Bu genellikle yalnızca odanın sunucuda nasıl işlendiğini etkiler. %(brand)s ile ilgili sorun yaşıyorsanız, lütfen bir hata bildirin.",
+            "upgrade_warning_dialog_report_bug_prompt_link": "Bu genellikle yalnızca odanın sunucuda nasıl işlendiğini etkiler. %(brand)s ile ilgili sorun yaşıyorsanız, lütfen <a>hata bildir</a>.",
+            "upgrade_warning_dialog_title": "Odayı yükselt",
+            "upgrade_warning_dialog_title_private": "Özel oda güncelle"
+        },
+        "alias_not_specified": "Belirtilmemiş",
+        "bridges": {
+            "description": "Bu oda, iletileri sözü edilen platformlara köprülüyor. <a>Daha fazla bilgi için.</a>",
+            "empty": "Bu oda, mesajları herhangi bir platforma köprülemiyor. <a>Daha fazla bilgi edinin.</a>",
+            "title": "Köprüler"
+        },
+        "delete_avatar_label": "Avatarı sil",
+        "general": {
+            "alias_field_has_domain_invalid": "Eksik alan adı ayırıcısı, örneğin (:domain.org)",
+            "alias_field_has_localpart_invalid": "Eksik oda adı veya ayırıcı (ör. (my-room:domain.org)",
+            "alias_field_matches_invalid": "Bu adres bu odayı göstermiyor",
+            "alias_field_placeholder_default": "örn. odam",
+            "alias_field_required_invalid": "Lütfen bir adres belirtin",
+            "alias_field_safe_localpart_invalid": "Bazı karakterlere izin verilmiyor",
+            "alias_field_taken_invalid": "Bu adres geçersiz sunucuya sahip veya zaten kullanımda",
+            "alias_field_taken_invalid_domain": "Bu adres zaten kullanımda",
+            "alias_field_taken_valid": "Bu adres kullanılabilir",
+            "alias_heading": "Oda adresi",
+            "aliases_items_label": "Diğer yayınlanmış adresler:",
+            "aliases_no_items_label": "Henüz yayınlanmış başka adres yok, aşağıdan bir tane ekle",
+            "aliases_section": "Oda Adresleri",
+            "avatar_field_label": "Oda avatarı",
+            "canonical_alias_field_label": "Ana adres",
+            "default_url_previews_off": "URL ön izlemeleri, bu odadaki kullanıcılar için varsayılan olarak devre dışı bıraktırılmıştır.",
+            "default_url_previews_on": "URL önizlemeleri, bu odadaki katılımcılar için varsayılan olarak etkin.",
+            "description_space": "Alanınızla ilgili ayarları düzenleyin.",
+            "error_creating_alias_description": "Adres oluşturulurken hata ile karşılaşıldı. Sunucu tarafından izin verilmemiş yada geçici bir hata olabilir.",
+            "error_creating_alias_title": "Adres oluşturulurken hata",
+            "error_deleting_alias_description": "Adres kaldırılırken bir hata ile karşılaşıldı. Artık mevcut olmayabilir yada geçici bir oluştu.",
+            "error_deleting_alias_description_forbidden": "Bu adresi silmeye yetkiniz yok.",
+            "error_deleting_alias_title": "Adres kaldırılırken hata",
+            "error_publishing": "Oda yayınlanamıyor",
+            "error_publishing_detail": "Bu oda yayınlanırken bir hata oluştu",
+            "error_save_space_settings": "Alan ayarları kaydedilemedi.",
+            "error_updating_alias_description": "Adanın alternatif adresini güncellerken bir hata oluştu. Bu eylem, sunucu tarafından izin verilmemiş olabilir ya da geçici bir sorun oluşmuş olabilir.",
+            "error_updating_canonical_alias_description": "Odanın ana adresini güncellerken bir sorun oluştu. Bu eylem, sunucu tarafından izin verilmemiş olabilir ya da geçici bir sorun oluşmuş olabilir.",
+            "error_updating_canonical_alias_title": "Ana adresi güncellemede hata",
+            "leave_space": "Alandan ayrıl",
+            "local_alias_field_label": "Yerel adres",
+            "local_aliases_explainer_room": "Bu oda için adresleri ayarlayın, böylece kullanıcılar bu odayı ana sunucunuz aracılığıyla bulabilir (%(localDomain)s)",
+            "local_aliases_explainer_space": "Bu alan için adresleri ayarlayın, böylece kullanıcılar bu alanı ana sunucunuz aracılığıyla bulabilir (%(localDomain)s)",
+            "local_aliases_section": "Yerel Adresler",
+            "name_field_label": "Oda Adı",
+            "new_alias_placeholder": "Yeni yayınlanmış adresler (e.g. #alias:server)",
+            "no_aliases_room": "Bu oda hiçbir yerel adrese sahip değil",
+            "no_aliases_space": "Bu alanın yerel adresi yok",
+            "other_section": "Diğer",
+            "publish_toggle": "Bu odayı %(domain)s oda dizininde herkese açık olarak yayınlayın?",
+            "published_aliases_description": "Bir adres yayınlamak için önce yerel adres olarak ayarlanması gerekir.",
+            "published_aliases_explainer_room": "Yayınlanan adresler, herhangi bir sunucudaki herkes tarafından odanıza katılmak için kullanılabilir.",
+            "published_aliases_explainer_space": "Yayınlanan adresler, herhangi bir sunucudaki herkes tarafından alanınıza katılmak için kullanılabilir.",
+            "published_aliases_section": "Yayınlanmış adresler",
+            "save": "Değişiklikleri Kaydet",
+            "topic_field_label": "Oda Başlığı",
+            "url_preview_encryption_warning": "Bunun gibi şifreli odalarda, ana sunucunuzun (önizlemelerin oluşturulduğu yer) bu odada gördüğünüz bağlantılar hakkında bilgi toplayamamasını sağlamak için URL önizlemeleri varsayılan olarak devre dışı bırakılır.",
+            "url_preview_explainer": "Birisi mesajına bir URL eklediğinde, bu bağlantı hakkında başlık, açıklama ve web sitesinden bir görüntü gibi daha fazla bilgi vermek için bir URL önizlemesi gösterilebilir.",
+            "url_previews_section": "URL önizlemeleri",
+            "user_url_previews_default_off": "URL önizlemelerini varsayılan olarak <a> devre dışı </a> bıraktınız.",
+            "user_url_previews_default_on": "URL önizlemelerini varsayılan olarak <a>etkinleştirdiniz</a>."
+        },
+        "notifications": {
+            "browse_button": "Gözat",
+            "custom_sound_prompt": "Özel bir ses ayarla",
+            "notification_sound": "Bildirim sesi",
+            "settings_link": "<a>Ayarlarınızda</a> ayarlandığı gibi bildirimleri alın",
+            "sounds_section": "Sesler",
+            "upload_sound_label": "Özel ses yükle",
+            "uploaded_sound": "Yüklenen ses"
+        },
+        "people": {
+            "knock_empty": "İstek yok",
+            "knock_section": "Katılma isteği gönderiliyor",
+            "see_less": "Daha az göster",
+            "see_more": "Daha fazla göster"
+        },
+        "permissions": {
+            "add_privileged_user_description": "Bu odadaki bir veya birden fazla kullanıcıya daha fazla ayrıcalık verin",
+            "add_privileged_user_filter_placeholder": "Bu odadaki kullanıcıları ara...",
+            "add_privileged_user_heading": "Ayrıcalıklı kullanıcılar ekle",
+            "ban": "Kullanıcıları yasakla",
+            "ban_reason": "Sebep",
+            "banned_by": "%(displayName)s tarafından yasaklandı",
+            "banned_users_section": "Yasaklanan(Banlanan) Kullanıcılar",
+            "error_changing_pl_description": "Kullanıcının güç düzeyini değiştirirken bir hata oluştu. Yeterli izinlere sahip olduğunuza emin olun ve yeniden deneyin.",
+            "error_changing_pl_reqs_description": "Odanın güç düzeyi gereksinimlerini değiştirirken bir hata ile karşılaşıldı. Yeterince yetkiniz olduğunuzdan emin olup yeniden deyin.",
+            "error_changing_pl_reqs_title": "Güç düzey gereksinimi değiştirmede hata",
+            "error_changing_pl_title": "Güç düzeyi değiştirme hatası",
+            "error_unbanning": "Yasağı kaldırmak başarısız oldu",
+            "events_default": "Mesajları gönder",
+            "invite": "Kullanıcıları davet et",
+            "kick": "Kullanıcıları kaldır",
+            "m.call": "%(brand)s çağrıları başlat",
+            "m.call.member": "%(brand)s çağrılarına katıl",
+            "m.reaction": "Tepki gönder",
+            "m.room.avatar": "Oda resmini değiştir",
+            "m.room.avatar_space": "Alan resmini değiştir",
+            "m.room.canonical_alias": "Oda için ana adresi değiştir",
+            "m.room.canonical_alias_space": "Alanın ana adresini değiştir",
+            "m.room.encryption": "Oda şifrelemeyi aç",
+            "m.room.history_visibility": "Geçmiş görünürlüğünü değiştir",
+            "m.room.name": "Oda adını değiştir",
+            "m.room.name_space": "Alan adını değiştir",
+            "m.room.pinned_events": "Sabitlenmiş etkinlikleri yönet",
+            "m.room.power_levels": "İzinleri değiştir",
+            "m.room.redaction": "Gönderdiğim mesajları sil",
+            "m.room.server_acl": "Sunucu ACL'lerini değiştir",
+            "m.room.tombstone": "Odayı güncelle",
+            "m.room.topic": "Başlığı değiştir",
+            "m.room.topic_space": "Açıklamayı değiştir",
+            "m.space.child": "Bu alandaki odaları yönetin",
+            "m.widget": "Görsel bileşenleri düzenle",
+            "muted_users_section": "Sessizdeki Kullanıcılar",
+            "no_privileged_users": "Bu odada hiçbir kullanıcının belirli ayrıcalıkları yoktur",
+            "notifications.room": "Herkesi bilgilendir",
+            "permissions_section": "İzinler",
+            "permissions_section_description_room": "Odanın çeşitli bölümlerini değişmek için gerekli rolleri seçiniz",
+            "permissions_section_description_space": "Alanın çeşitli bölümlerini değiştirmek için gereken rolleri seçin",
+            "privileged_users_section": "Ayrıcalıklı Kullanıcılar",
+            "redact": "Diğerleri tarafından gönderilen iletileri kaldır",
+            "send_event_type": "%(eventType)s olaylarını gönder",
+            "state_default": "Ayarları değiştir",
+            "title": "Roller & İzinler",
+            "users_default": "Varsayılan rol"
+        },
+        "security": {
+            "enable_encryption_confirm_description": "Bir oda için şifreleme bir kez etkinleştirildiğinde geri alınamaz. Şifrelenmiş bir odada gönderilen iletiler yalnızca ve yalnızca odadaki kullanıcılar tarafından görülebilir. Şifrelemeyi etkinleştirmek bir çok bot'un ve köprülemenin doğru çalışmasını etkileyebilir. <a>Şifrelemeyle ilgili daha fazla bilgi edinmek için.</a>",
+            "enable_encryption_confirm_title": "Şifrelemeyi aç?",
+            "enable_encryption_public_room_confirm_description_1": "<b>Herkese açık odalara şifreleme eklemeniz önerilmez. </b> Herkes ortak odaları bulabilir ve bu odalara katılabilir, böylece herkes bu odalardaki mesajları okuyabilir. Şifrelemenin hiçbir avantajından yararlanamazsınız ve daha sonra kapatamazsınız. Herkese açık bir odadaki mesajları şifrelemek, mesaj almayı ve göndermeyi yavaşlatır.",
+            "enable_encryption_public_room_confirm_description_2": "Bu sorunları önlemek için, yapmayı planladığınız görüşme için <a>yeni bir şifreli oda</a> oluşturun.",
+            "enable_encryption_public_room_confirm_title": "Bu herkese açık odaya şifreleme eklemek istediğinizden emin misiniz?",
+            "encrypted_room_public_confirm_description_1": "<b>Şifrelenmiş odaların herkese açık hale getirilmesi önerilmez. </b> Bu, herkesin odayı bulup katılabileceği anlamına gelir, böylece herkes mesajları okuyabilir. Şifrelemenin hiçbir avantajından yararlanamazsınız. Herkese açık bir odadaki mesajları şifrelemek, mesaj almayı ve göndermeyi yavaşlatır.",
+            "encrypted_room_public_confirm_description_2": "Bu sorunlardan kaçınmak için, yapmayı planladığınız konuşma için <a>yeni bir herkese açık oda</a> oluşturun.",
+            "encrypted_room_public_confirm_title": "Bu şifreli odayı herkese açık hale getirmek istediğinizden emin misiniz?",
+            "encryption_forced": "Sunucunuz şifrelemenin devre dışı bırakılmasını gerektiriyor.",
+            "encryption_permanent": "Açıldıktan donra şifreleme kapatılamaz.",
+            "error_join_rule_change_title": "Katılma kuralları güncellenemedi",
+            "error_join_rule_change_unknown": "Bilinmeyen hata",
+            "guest_access_warning": "Desteklenen istemciye sahip kişiler, kayıtlı bir hesaba sahip olmadan odaya katılabilecekler.",
+            "history_visibility_invited": "Sadece üyeler (davet edildiklerinden beri)",
+            "history_visibility_joined": "Sadece üyeler (katıldıklarından beri)",
+            "history_visibility_legend": "Geçmişi kimler okuyabilir ?",
+            "history_visibility_shared": "Sadece üyeler ( bu seçeneği seçtiğinizden itibaren)",
+            "history_visibility_warning": "Geçmişi kimin okuyabileceğini değiştirmek yalnızca odadaki yeni iletileri etkiler. Var olan geçmiş değişmeden kalacaktır.",
+            "history_visibility_world_readable": "Herhangi biri",
+            "join_rule_description": "%(roomName)s kimlerin katılabileceğine karar ver.",
+            "join_rule_invite": "Özel (yalnızca davet)",
+            "join_rule_invite_description": "Yalnızca davet edilen kişiler katılabilir.",
+            "join_rule_knock": "Katılma isteği gönder",
+            "join_rule_knock_description": "Erişim izni verilmedikçe kişiler katılamaz.",
+            "join_rule_public_description": "Herkes bulabilir ve katılabilir.",
+            "join_rule_restricted": "Alan üyeleri",
+            "join_rule_restricted_description": "Alandaki herkes alanı bulabilir ve katılabilir. <a>Hangi alanların erişebileceğini buradan düzenleyin. </a>",
+            "join_rule_restricted_description_active_space": "<spaceName/> alanındaki herkes bulabilir ve katılabilir. Diğer alanları da seçebilirsiniz.",
+            "join_rule_restricted_description_prompt": "Alandaki herkes alanı bulabilir ve katılabilir. Birden fazla alan seçebilirsiniz.",
+            "join_rule_restricted_description_spaces": "Erişimi olan alanlar",
+            "join_rule_restricted_dialog_description": "Bu odaya hangi alanların erişebileceğine karar verin. Bir alan seçilirse, üyeleri <RoomName/> odasını bulabilir ve katılabilir.",
+            "join_rule_restricted_dialog_empty_warning": "Tüm alanları kaldırıyorsunuz. Erişim varsayılan olarak yalnızca davetlilere açık olacak",
+            "join_rule_restricted_dialog_filter_placeholder": "Alanları ara",
+            "join_rule_restricted_dialog_heading_known": "Bildiğiniz diğer alanlar",
+            "join_rule_restricted_dialog_heading_other": "Bilmediğiniz diğer alanlar veya odalar",
+            "join_rule_restricted_dialog_heading_room": "Bu odayı içeren bildiğiniz alanlar",
+            "join_rule_restricted_dialog_heading_space": "Bu alanı içeren bildiğiniz alanlar",
+            "join_rule_restricted_dialog_heading_unknown": "Bunlar büyük olasılıkla diğer oda yöneticilerinin de bir parçası olduğu şeyerdir.",
+            "join_rule_restricted_dialog_title": "Alanları seç",
+            "join_rule_restricted_n_more": {
+                "one": "& %(count)s daha fazla",
+                "other": "& %(count)s daha fazla"
+            },
+            "join_rule_restricted_summary": {
+                "one": "Şu anda bir alanın erişimi var",
+                "other": "Şu anda %(count)s alanın erişimi var"
+            },
+            "join_rule_restricted_upgrade_description": "Bu yükseltme, seçilen alanların üyelerinin davet olmadan bu odaya erişmesine izin verecektir.",
+            "join_rule_restricted_upgrade_warning": "Bu oda, yöneticisi olmadığınız bazı alanlarda bulunuyor. Bu alanlarda eski oda gösterilmeye devam eder, ancak kullanıcılardan yeni odaya katılmaları istenecektir.",
+            "join_rule_upgrade_awaiting_room": "Yeni oda yükleniyor",
+            "join_rule_upgrade_required": "Yükseltme Gerekli",
+            "join_rule_upgrade_sending_invites": {
+                "one": "Davet gönderiliyor...",
+                "other": "Davet gönderiliyor... (%(progress)s / %(count)s)"
+            },
+            "join_rule_upgrade_updating_spaces": {
+                "one": "Alan güncelleniyor...",
+                "other": "Alanlar güncelleniyor... (%(progress)s / %(count)s)"
+            },
+            "join_rule_upgrade_upgrading_room": "Odayı yükselt",
+            "public_without_alias_warning": "Bu odaya bağlamak için lütfen bir adres ekleyin.",
+            "publish_room": "Bu odayı herkese açık oda dizininde görünür yap.",
+            "publish_space": "Bu alanı herkese açık oda dizininde görünür hale getirin.",
+            "strict_encryption": "Şifreli mesajları asla oturumdaki bu odadaki doğrulanmamış oturumlara iletme",
+            "title": "Güvenlik & Gizlilik"
+        },
+        "title": "Oda Ayarları - %(roomName)s",
+        "upload_avatar_label": "Avatar yükle",
+        "visibility": {
+            "alias_section": "Adres",
+            "error_failed_save": "Bu alanın görünürlüğü güncellenemedi",
+            "error_update_guest_access": "Bu alanın misafir erişimi güncellenemedi",
+            "error_update_history_visibility": "Bu alanın geçmiş görünürlüğü güncellenemedi",
+            "guest_access_explainer": "Misafirler bir hesap oluşturmadan bir alana katılabilirler.",
+            "guest_access_explainer_public_space": "Bu, herkese açık alanlar için yararlı olabilir.",
+            "guest_access_label": "Misafir erişimini etkinleştir",
+            "history_visibility_anyone_space": "Alanı önizle",
+            "history_visibility_anyone_space_description": "İnsanların katılmadan önce alanınızı önizlemesine izin verin.",
+            "history_visibility_anyone_space_recommendation": "Herkese açık alanlar için önerilir.",
+            "title": "Görünürlük"
+        },
+        "voip": {
+            "call_type_section": "Çağrı türü",
+            "enable_element_call_caption": "%(brand)s uçtan uca şifrelenmiştir, ancak şu anda daha az sayıda kullanıcıyla sınırlıdır.",
+            "enable_element_call_label": "Bu odada ek bir arama seçeneği olarak %(brand)s seçeneğini etkinleştirin",
+            "enable_element_call_no_permissions_tooltip": "Bunu değiştirmek için yeterli izne sahip değilsiniz."
+        }
+    },
+    "room_summary_card_back_action_label": "Oda bilgisi",
+    "scalar": {
+        "error_create": "Görsel bileşen oluşturulamıyor.",
+        "error_membership": "Bu odada değilsin.",
+        "error_missing_room_id": "roomId eksik.",
+        "error_missing_room_id_request": "İstekte eksik room_id",
+        "error_missing_user_id_request": "İstekte user_id eksik",
+        "error_permission": "Bu odada bunu yapma yetkiniz yok.",
+        "error_power_level_invalid": "Güç seviyesi pozitif tamsayı olmalıdır.",
+        "error_room_not_visible": "%(roomId)s odası görünür değil",
+        "error_room_unknown": "Bu oda tanınmıyor.",
+        "error_send_request": "İstek gönderimi başarısız oldu.",
+        "failed_read_event": "Etkinlikler okunamadı",
+        "failed_send_event": "Etkinlik gönderilemedi"
+    },
+    "server_offline": {
+        "description": "Sunucunuz bazı isteklerinize yanıt vermiyor. Aşağıda en olası nedenlerden bazıları verilmiştir.",
+        "description_1": "Sunucunun (%(serverName)s) yanıt vermesi çok uzun sürdü.",
+        "description_2": "Güvenlik duvarınız veya anti-virüs programınız isteği engelliyor.",
+        "description_3": "Bir tarayıcı uzantısı isteği engelliyor.",
+        "description_4": "Sunucu çevrimdışı.",
+        "description_5": "Sunucu isteğinizi reddetti.",
+        "description_6": "Bölgenizde internete bağlanma sorunları yaşanıyor.",
+        "description_7": "Sunucuyla bağlantı kurmaya çalışırken bir bağlantı hatası oluştu.",
+        "description_8": "Sunucu, sorunun ne olduğunu belirtecek şekilde yapılandırılmamıştır (CORS).",
+        "empty_timeline": "Her şey tamam.",
+        "recent_changes_heading": "Henüz alınmamış son değişiklikler",
+        "title": "Sunucu yanıt vermiyor"
+    },
+    "seshat": {
+        "error_initialising": "Mesaj arama başlatılamadı, daha fazla bilgi için <a>ayarlarınızı</a> kontrol edin",
+        "reset_button": "Etkinlik mağazasını sıfırla",
+        "reset_description": "Çoğunlukla etkinlik dizini mağazanızı sıfırlamak istemezsiniz",
+        "reset_explainer": "Bunu yaparsanız, lütfen mesajlarınızın hiçbirinin silinmeyeceğini, ancak dizin yeniden oluşturulurken arama deneyiminin birkaç dakikalığına bozulabileceğini unutmayın",
+        "reset_title": "Etkinlik mağazasını sıfırla?",
+        "warning_kind_files": "%(brand)s'ın bu sürümü, bazı şifrelenmiş dosyaların görüntülenmesini desteklemez",
+        "warning_kind_files_app": "Tüm şifrelenmiş dosyaları görmek için <a>Masaüstü uygulamasını</a> kullanın",
+        "warning_kind_search": "%(brand)s'ın bu sürümü şifreli mesajların aranmasını desteklemez",
+        "warning_kind_search_app": "Şifrelenmiş mesajları aramak için <a>Masaüstü uygulamasını</a> kullanın"
+    },
+    "setting": {
+        "help_about": {
+            "access_token_detail": "Erişim anahtarınız, hesabınıza tam erişim sağlar. Kimseyle paylaşmayın.",
+            "brand_version": "%(brand)s versiyon:",
+            "clear_cache_reload": "Belleği temizle ve yeniden yükle",
+            "crypto_version": "Kripto versiyonu:",
+            "dialog_title": "<strong>Ayarlar:</strong> Yardım ve Hakkında",
+            "help_link": "%(brand)s kullanarak yardım etmek için, <a>buraya</a> tıklayın.",
+            "homeserver": "Ana sunucu <code>%(homeserverUrl)s</code>",
+            "identity_server": "Kimlik sunucusu <code>%(identityServerUrl)s</code>",
+            "title": "Yardım & Hakkında",
+            "versions": "Sürümler"
+        }
+    },
+    "settings": {
+        "account": {
+            "dialog_title": "<strong>Ayarlar:</strong> Hesap",
+            "title": "Hesap"
+        },
+        "all_rooms_home": "Ana Sayfa'da tüm odaları göster",
+        "all_rooms_home_description": "Bulunduğunuz tüm odalar Ana Sayfa'da görünecek.",
+        "always_show_message_timestamps": "Her zaman mesaj zaman dalgalarını (timestamps) gösterin",
+        "appearance": {
+            "bundled_emoji_font": "Paketlenmiş emoji yazı tipini kullan",
+            "compact_layout": "Kompakt metin ve mesajları göster",
+            "compact_layout_description": "Bu özelliği kullanmak için modern düzen seçilmelidir.",
+            "custom_font": "Bir sistem yazı tipi kullanın",
+            "custom_font_description": "Sisteminizde yüklü bir yazı tipinin adını ayarlayın &%(brand)s onu kullanmaya çalışacaktır.",
+            "custom_font_name": "Sistem yazı tipi adı",
+            "custom_font_size": "Özel boyut kullan",
+            "custom_theme_add": "Özel tema ekle",
+            "custom_theme_downloading": "Özel tema indiriliyor…",
+            "custom_theme_error_downloading": "Tema indirilirken hata oluştu",
+            "custom_theme_help": "Uygulamak istediğiniz özel temanın URL'sini girin.",
+            "custom_theme_invalid": "Geçersiz tema taslağı.",
+            "dialog_title": "<strong>Ayarlar:</strong> Görünüm",
+            "font_size": "Yazı boyutu",
+            "font_size_default": "%(fontSize)s (varsayılan)",
+            "high_contrast": "Yüksek kontrast",
+            "image_size_default": "Varsayılan",
+            "image_size_large": "Büyük",
+            "layout_bubbles": "Mesaj baloncukları",
+            "layout_irc": "IRC (Deneysel)",
+            "match_system_theme": "Sistem temasıyla eşle",
+            "timeline_image_size": "Zaman çizelgesindeki görüntü boyutu"
+        },
+        "automatic_language_detection_syntax_highlight": "Sözdizimi vurgulama için otomatik dil algılamayı etkinleştir",
+        "autoplay_gifs": "GIF'leri otomatik oynat",
+        "autoplay_videos": "Videoları otomatik oynat",
+        "big_emoji": "Sohbette büyük emojileri aç",
+        "code_block_expand_default": "Varsayılan olarak kod bloklarını genişlet",
+        "code_block_line_numbers": "Kod bloklarında satır sayısını göster",
+        "disable_historical_profile": "Mesaj geçmişindeki kullanıcılar için güncel profil resmini ve adını göster",
+        "discovery": {
+            "title": "Sizi nasıl bulabilirim"
+        },
+        "emoji_autocomplete": "Yazarken Emoji önerilerini aç",
+        "enable_markdown": "İşaretlemeyi Etkinleştir",
+        "enable_markdown_description": "Mesajları işaretleme olmadan göndermek için başına <code>/plain</code> ekleyin.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Hesap bilgileriniz, kişileriniz, tercihleriniz ve sohbet listeniz saklanacaktır",
+                "breadcrumb_page": "Şifrelemeyi sıfırla",
+                "breadcrumb_second_description": "Yalnızca sunucuda depolanan mesaj geçmişlerini kaybedeceksiniz",
+                "breadcrumb_third_description": "Mevcut tüm cihazlarınızı ve kişilerinizi tekrar doğrulamanız gerekecek",
+                "breadcrumb_title": "Kimliğinizi sıfırlamak istediğinizden emin misiniz?",
+                "breadcrumb_title_forgot": "Kurtarma anahtarınızı mı unuttunuz? Kimliğinizi sıfırlamanız gerekecek.",
+                "breadcrumb_warning": "Bunu yalnızca hesabınızın tehlikeye girdiğini düşünüyorsanız yapın.",
+                "details_title": "Şifreleme detayları",
+                "do_not_close_warning": "Sıfırlama işlemi bitene kadar bu pencereyi kapatmayın",
+                "export_keys": "Anahtarları dışa aktar",
+                "import_keys": "Anahtarları içe aktar",
+                "other_people_device_description": "Şifrelenmiş odalarda varsayılan olarak, doğrulayana kadar kimseye şifrelenmiş mesajlar göndermeyin",
+                "other_people_device_label": "Doğrulanmamış cihazlara asla şifrelenmiş mesajlar göndermeyin",
+                "other_people_device_title": "Diğer kişilerin cihazları",
+                "reset_identity": "Şifreleme kimliğini sıfırla",
+                "reset_in_progress": "Sıfırlama devam ediyor...",
+                "session_id": "Oturum Kimliği:",
+                "session_key": "Oturum anahtarı:",
+                "title": "Gelişmiş"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Anahtar depolamasını sil",
+                "confirm": "Anahtar depolamasını sil",
+                "description": "Anahtar depolamanın silinmesi kriptografik kimliğinizi ve mesaj anahtarlarınızı sunucudan kaldıracak ve aşağıdaki güvenlik özelliklerini kapatacaktır:",
+                "list_first": "Yeni cihazlarda şifrelenmiş mesaj geçmişine sahip olmayacaksınız",
+                "list_second": "%(brand)s adresinden her yerde oturumunuzu kapatırsanız şifrelenmiş mesajlarınıza erişiminizi kaybedersiniz"
+            },
+            "device_not_verified_button": "Bu cihazı doğrulayın",
+            "device_not_verified_description": "Şifreleme ayarlarınızı görüntülemek için bu cihazı doğrulamanız gerekiyor.",
+            "device_not_verified_title": "Cihaz doğrulanmamış",
+            "dialog_title": "<strong>Ayarlar:</strong> Şifreleme",
+            "recovery": {
+                "change_recovery_confirm_button": "Yeni kurtarma anahtarını onaylayın",
+                "change_recovery_confirm_description": "İşlemi tamamlamak için aşağıya yeni kurtarma anahtarınızı girin. Eski anahtarınız artık çalışmayacak.",
+                "change_recovery_confirm_title": "Yeni kurtarma anahtarınızı girin",
+                "change_recovery_key": "Kurtarma anahtarını değiştir",
+                "change_recovery_key_description": "Bu yeni kurtarma anahtarını güvenli bir yere kaydedin. Ardından değişikliği onaylamak için Devam'a tıklayın.",
+                "change_recovery_key_title": "Kurtarma anahtarını değiştir?",
+                "description": "Mevcut tüm cihazlarınızı kaybettiyseniz, kurtarma anahtarıyla şifreleme kimliğinizi ve mesaj geçmişinizi kurtarın.",
+                "enter_key_error": "Girdiğiniz kurtarma anahtarı doğru değil.",
+                "enter_recovery_key": "Kurtarma anahtarını girin",
+                "forgot_recovery_key": "Kurtarma anahtarınızı mı unuttunuz?",
+                "key_storage_warning": "Anahtar depolama alanınız senkronize değil. Sorunu düzeltmek için aşağıdaki düğmeyi tıklayın.",
+                "save_key_description": "Bunu kimseyle paylaşmayın!",
+                "save_key_title": "Kurtarma anahtarı",
+                "set_up_recovery": "Kurtarmayı ayarlayın",
+                "set_up_recovery_confirm_button": "Kurulumu tamamla",
+                "set_up_recovery_confirm_description": "Kurtarma işlemini tamamlamak için önceki ekranda gösterilen kurtarma anahtarını girin.",
+                "set_up_recovery_confirm_title": "Onaylamak için kurtarma anahtarınızı girin",
+                "set_up_recovery_description": "Anahtar depolama alanınız bir kurtarma anahtarı ile korunur. Kurulumdan sonra yeni bir kurtarma anahtarına ihtiyacınız varsa, '%(changeRecoveryKeyButton)s' seçeneğini seçerek yeniden oluşturabilirsiniz.",
+                "set_up_recovery_save_key_description": "Bu kurtarma anahtarını şifre yöneticisi, şifreli not veya fiziksel kasa gibi güvenli bir yere kaydedin.",
+                "set_up_recovery_save_key_title": "Kurtarma anahtarınızı güvenli bir yere kaydedin",
+                "set_up_recovery_secondary_description": "Devam'a tıkladıktan sonra, sizin için bir kurtarma anahtarı oluşturacağız.",
+                "title": "Kurtarma"
+            },
+            "title": "Şifreleme"
+        },
+        "general": {
+            "account_management_section": "Hesap yönetimi",
+            "account_section": "Hesap",
+            "add_email_dialog_title": "Eposta Adresi Ekle",
+            "add_email_failed_verification": "E-posta adresi doğrulanamadı: E-postadaki bağlantıya tıkladığınızdan emin olun",
+            "add_email_instructions": "Onaylamanız için size e-posta gönderdik. Lütfen yönergeleri takip edin ve sonra aşağıdaki butona tıklayın.",
+            "add_msisdn_confirm_body": "Telefon numarasını eklemeyi kabul etmek için aşağıdaki tuşa tıklayın.",
+            "add_msisdn_confirm_button": "Telefon numarası eklemeyi onayla",
+            "add_msisdn_confirm_sso_button": "Kimliğinizi doğrulamak için Tek Seferlik Oturum Açma özelliğini kullanarak bu telefon numarasını eklemeyi onaylayın.",
+            "add_msisdn_dialog_title": "Telefon Numarası Ekle",
+            "add_msisdn_instructions": "Bir metin mesajı gönderildi: +%(msisdn)s. Lütfen içerdiği doğrulama kodunu girin.",
+            "add_msisdn_misconfigured": "MSISDN akışı ile ekle/bağla yanlış yapılandırılmış",
+            "allow_spellcheck": "Yazım denetimine izin ver",
+            "application_language": "Uygulama dili",
+            "application_language_reload_hint": "Uygulama, başka bir dil seçtikten sonra yeniden yüklenecektir",
+            "avatar_remove_progress": "Görüntü kaldırılıyor...",
+            "avatar_save_progress": "Resim yükleniyor...",
+            "avatar_upload_error_text": "Dosya biçimi desteklenmiyor veya görüntü %(size)s'den büyük.",
+            "avatar_upload_error_text_generic": "Dosya biçimi desteklenmiyor olabilir.",
+            "avatar_upload_error_title": "Avatar resmi yüklenemedi",
+            "confirm_adding_email_body": "E-posta adresini eklemeyi kabul etmek için aşağıdaki tuşa tıklayın.",
+            "confirm_adding_email_title": "E-posta adresini eklemeyi onayla",
+            "deactivate_confirm_body": "Hesabınızı devre dışı bırakmak istediğinizden emin misiniz? Bu geri döndürülemez.",
+            "deactivate_confirm_body_sso": "Kimliğinizi doğrulamak için Tek Oturum Açma'yı kullanarak hesabınızın devre dışı bırakıldığını doğrulayın.",
+            "deactivate_confirm_content": "Hesabınızı devre dışı bırakmak istediğinizi onaylayın. Devam ederseniz:",
+            "deactivate_confirm_content_1": "Hesabınızı yeniden etkinleştiremezsiniz",
+            "deactivate_confirm_content_2": "Artık giriş yapamayacaksınız",
+            "deactivate_confirm_content_3": "Siz de dahil olmak üzere hiç kimse kullanıcı adınızı (MXID) yeniden kullanamayacaktır: bu kullanıcı adı kullanılamaz durumda kalacaktır",
+            "deactivate_confirm_content_4": "Bulunduğunuz tüm odalardan ve DM'lerden ayrılacaksınız",
+            "deactivate_confirm_content_5": "Kimlik sunucusundan kaldırılacaksınız: arkadaşlarınız artık sizi e-posta veya telefon numaranızla bulamayacak",
+            "deactivate_confirm_content_6": "Eski mesajlarınız, geçmişte gönderdiğiniz e-postalar gibi, onları alan kişiler tarafından görülmeye devam eder. Gönderdiğiniz mesajları gelecekte odalara katılacak kişilerden gizlemek ister misiniz?",
+            "deactivate_confirm_continue": "Hesap devre dışı bırakmayı onayla",
+            "deactivate_confirm_erase_label": "Mesajlarımı yeni katılımcılardan gizle",
+            "deactivate_section": "Hesabı Devre Dışı Bırakma",
+            "deactivate_warning": "Hesabınızı devre dışı bırakmak kalıcı bir işlemdir - dikkatli olun!",
+            "discovery_email_empty": "Bulunulabilirlik seçenekleri, yukarıya bir e-posta adresi ekleyince ortaya çıkacaktır.",
+            "discovery_email_verification_instructions": "Gelen kutunuzdaki linki doğrulayın",
+            "discovery_msisdn_empty": "Bulunulabilirlik seçenekleri, yukarıya bir telefon numarası ekleyince ortaya çıkacaktır.",
+            "discovery_needs_terms": "Başkaları tarafından e-posta adresi ya da telefon numarası ile bulunabilmek için %(serverName)s kimlik sunucusunun Kullanım Koşullarını kabul edin.",
+            "discovery_needs_terms_title": "İnsanların sizi bulmasına izin verin",
+            "display_name": "Görünen ad",
+            "display_name_error": "Görünen ad ayarlanamıyor",
+            "email_address_in_use": "Bu e-posta adresi zaten kullanımda",
+            "email_address_label": "E-posta Adresi",
+            "email_not_verified": "E-posta adresiniz henüz doğrulanmadı",
+            "email_verification_instructions": "Aldığınız e-postaki bağlantıyı tıklayarak doğrulayın ve sonra tekrar tıklayarak devam edin.",
+            "emails_heading": "E-posta adresleri",
+            "error_add_email": "E-posta adresi eklenemiyor",
+            "error_deactivate_communication": "Sunucu ile iletişimde bir sorun oluştu. Lütfen tekrar deneyin.",
+            "error_deactivate_invalid_auth": "Sunucu geçerli kimlik doğrulama bilgisi döndürmedi.",
+            "error_deactivate_no_auth": "Sunucu herhangi bir kimlik doğrulaması gerektirmedi",
+            "error_email_verification": "E-posta adresi doğrulanamıyor.",
+            "error_invalid_email": "Geçersiz E-posta Adresi",
+            "error_invalid_email_detail": "Bu geçerli bir e-posta adresi olarak gözükmüyor",
+            "error_msisdn_verification": "Telefon numarası doğrulanamıyor.",
+            "error_password_change_403": "Parola değiştirilemedi . Şifreniz doğru mu ?",
+            "error_password_change_http": "%(errorMessage)s (HTTP durumu %(httpStatus)s)",
+            "error_password_change_title": "Şifre değiştirirken hata oluştu",
+            "error_password_change_unknown": "Bilinmeyen şifre değiştirme hatası (%(stringifiedError)s )",
+            "error_remove_3pid": "Kişi bilgileri kaldırılamıyor",
+            "error_revoke_email_discovery": "E-posta adresi paylaşımı kaldırılamadı",
+            "error_revoke_msisdn_discovery": "Telefon numarası paylaşımı kaldırılamıyor",
+            "error_share_email_discovery": "E-posta adresi paylaşılamıyor",
+            "error_share_msisdn_discovery": "Telefon numarası paylaşılamıyor",
+            "identity_server_no_token": "Kimlik erişim belirteci bulunamadı",
+            "identity_server_not_set": "Kimlik sunucusu ayarlanmadı",
+            "language_section": "Dil ve bölge",
+            "msisdn_in_use": "Bu telefon numarası zaten kullanımda",
+            "msisdn_label": "Telefon Numarası",
+            "msisdn_verification_field_label": "Doğrulama kodu",
+            "msisdn_verification_instructions": "Lütfen mesajla gönderilen doğrulama kodunu girin.",
+            "msisdns_heading": "Telefon numaraları",
+            "oidc_manage_button": "Hesabı Yönet",
+            "password_change_section": "Yeni bir hesap şifresi belirleyin…",
+            "password_change_success": "Şifreniz başarıyla değiştirildi.",
+            "personal_info": "Kişisel Bilgiler",
+            "profile_subtitle": "Uygulamadaki diğer kişilere bu şekilde görünürsünüz.",
+            "profile_subtitle_oidc": "Hesabınız bir kimlik sağlayıcı tarafından ayrı olarak yönetilir ve bu nedenle bazı kişisel bilgileriniz burada değiştirilemez.",
+            "remove_email_prompt": "%(email)s sil?",
+            "remove_msisdn_prompt": "%(phone)s sil?",
+            "spell_check_locale_placeholder": "Bir dil seçin",
+            "unable_to_load_emails": "E-posta adresleri yüklenemiyor",
+            "unable_to_load_msisdns": "Telefon numaraları yüklenemiyor",
+            "username": "Kullanıcı Adı"
+        },
+        "inline_url_previews_default": "Varsayılan olarak satır içi URL önizlemeleri aç",
+        "inline_url_previews_room": "Bu odadaki katılımcılar için URL önizlemeyi varsayılan olarak açık hale getir",
+        "inline_url_previews_room_account": "Bu oda için URL önizlemeyi aç (sadece sizi etkiler)",
+        "insert_trailing_colon_mentions": "Mesajın başında kullanıcı etiketlerinden sonra iki nokta üst üste ekle",
+        "jump_to_bottom_on_send": "Mesaj gönderdiğinizde zaman çizelgesinin en sonuna atla",
+        "key_backup": {
+            "backup_in_progress": "Anahtarlarınız yedekleniyor (ilk yedekleme birkaç dakika sürebilir).",
+            "backup_starting": "Yedekleme başlatılıyor...",
+            "backup_success": "Başarılı!",
+            "cannot_create_backup": "Anahtar yedeklemesi oluşturulamıyor",
+            "create_title": "Anahtar yedeklemesi oluştur",
+            "setup_secure_backup": {
+                "backup_setup_success_description": "Anahtarlarınız artık bu cihazdan yedekleniyor.",
+                "backup_setup_success_title": "Güvenli Yedekleme başarılı",
+                "cancel_warning": "Şimdi iptal ederseniz, oturum açma bilgilerinize erişiminizi kaybetmeniz durumunda şifrelenmiş mesajlarınızı ve verilerinizi kaybedebilirsiniz.",
+                "confirm_security_phrase": "Güvenlik İfadenizi Onaylayın",
+                "description": "Şifrelenmiş mesajlarınıza ve verilerinize erişiminizi kaybetmemek için şifreleme anahtarlarınızı sunucunuzda yedekleyin.",
+                "download_or_copy": "%(downloadButton)s ya da %(copyButton)s",
+                "enter_phrase_description": "Verilerinizi korumak için kullanıldığından yalnızca sizin bildiğiniz bir Güvenlik İfadesi girin. Güvende olmak için hesap şifrenizi tekrar kullanmamalısınız.",
+                "enter_phrase_title": "Bir Güvenlik İfadesi Girin",
+                "enter_phrase_to_confirm": "Onaylamak için Güvenlik İfadenizi ikinci kez girin.",
+                "generate_security_key_description": "Şifre yöneticisi veya kasa gibi güvenli bir yerde saklamanız için bir Güvenlik Anahtarı oluşturacağız.",
+                "generate_security_key_title": "Güvenlik Anahtarı Oluştur",
+                "pass_phrase_match_failed": "Eşleşmiyor.",
+                "pass_phrase_match_success": "Eşleşti!",
+                "phrase_strong_enough": "Harika! Bu Güvenlik İfadesi yeterince güçlü görünüyor.",
+                "secret_storage_query_failure": "Gizli depolama durumu sorgulanamıyor",
+                "security_key_safety_reminder": "Şifrelenmiş verilerinizi korumak için kullanıldığı için Güvenlik Anahtarınızı bir şifre yöneticisi veya kasa gibi güvenli bir yerde saklayın.",
+                "set_phrase_again": "Tekrar ayarlamak için geri dönün.",
+                "settings_reminder": "Ayrıca Ayarlar'da Güvenli Yedekleme'yi ayarlayabilir ve anahtarlarınızı yönetebilirsiniz.",
+                "title_confirm_phrase": "Güvenlik İfadesini Onayla",
+                "title_save_key": "Güvenlik Anahtarını Kaydet",
+                "title_set_phrase": "Güvenlik İfadesi Ayarla",
+                "unable_to_setup": "Gizli depolama ayarlanamıyor",
+                "use_different_passphrase": "Farklı bir şifre kullan?",
+                "use_phrase_only_you_know": "Yalnızca sizin bildiğiniz gizli bir ifade kullanın ve isteğe bağlı olarak yedekleme için kullanmak üzere bir Güvenlik Anahtarı kaydedin."
+            }
+        },
+        "key_export_import": {
+            "confirm_passphrase": "Şifreyi onayla",
+            "enter_passphrase": "Parolayı girin",
+            "export_description_1": "Bu işlem şifreli odalarda aldığınız iletilerin anahtarlarını yerel dosyaya vermenizi sağlar . Bundan sonra dosyayı ileride başka bir Matrix istemcisine de aktarabilirsiniz , böylece istemci bu mesajların şifresini çözebilir (decryption).",
+            "export_description_2": "Dışa aktarılan dosya, onu okuyabilen herkesin görebileceğiniz şifreli mesajların şifresini çözmesine izin verir, bu nedenle onu güvende tutmak için dikkatli olmalısınız. Buna yardımcı olmak için, aşağıya yalnızca dışa aktarılan verileri şifrelemek için kullanılacak benzersiz bir şifre girmelisiniz. Verileri yalnızca aynı şifreyi kullanarak içe aktarmak mümkün olacaktır.",
+            "export_title": "Oda anahtarlarını dışa aktar",
+            "file_to_import": "İçe aktarılacak dosya",
+            "import_description_1": "Bu işlem , geçmişte başka Matrix istemcisinden dışa aktardığınız şifreleme anahtarlarınızı içe aktarmanızı sağlar . Böylece diğer istemcinin çözebileceği tüm iletilerin şifresini çözebilirsiniz.",
+            "import_description_2": "Dışa aktarma dosyası bir şifre ile korunacaktır . Dosyanın şifresini çözmek için buraya şifre girmelisiniz.",
+            "import_title": "Oda anahtarlarını içe aktar",
+            "phrase_cannot_be_empty": "Şifrenin boş olmaması gerekir",
+            "phrase_must_match": "Şifrenin eşleşmesi gerekir",
+            "phrase_strong_enough": "Harika! Bu şifre yeterince güçlü görünüyor"
+        },
+        "keyboard": {
+            "dialog_title": "<strong>Ayarlar:</strong> Klavye",
+            "title": "Klavye"
+        },
+        "labs": {
+            "dialog_title": "<strong>Ayarlar:</strong> Labs"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Ayarlar:</strong> Yoksayılan Kullanıcılar"
+        },
+        "notifications": {
+            "default_setting_description": "Bu ayar varsayılan olarak tüm odalarınıza uygulanacaktır.",
+            "default_setting_section": "Bildirim almak istiyorum (Varsayılan Ayar)",
+            "desktop_notification_message_preview": "Masaüstü bildiriminde mesaj önizlemesini gösterme",
+            "dialog_title": "<strong>Ayarlar:</strong> Bildirimler",
+            "email_description": "Kaçırılan bildirimlerin e-posta özetini alın",
+            "email_section": "E-posta özeti",
+            "email_select": "Hangi e-postalara özet göndermek istediğinizi seçin. E-postalarınızı <button> Genel olarak yönetin</button>.",
+            "enable_audible_notifications_session": "Bu oturum için sesli bildirimleri etkinleştir",
+            "enable_desktop_notifications_session": "Bu oturum için masaüstü bildirimlerini etkinleştir",
+            "enable_email_notifications": "%(email)s için e-posta bildirimlerini etkinleştirin",
+            "enable_notifications_account": "Bu hesap için bildirimleri etkinleştir",
+            "enable_notifications_account_detail": "Tüm cihazlarınızda ve oturumlarınızda bildirimleri devre dışı bırakmak için kapatın",
+            "enable_notifications_device": "Bu cihaz için bildirimleri etkinleştir",
+            "error_loading": "Bildirim ayarlarınız yüklenirken bir hata oluştu.",
+            "error_permissions_denied": "%(brand)s size bildirim gönderme yetkisine sahip değil - lütfen tarayıcı ayarlarınızı kontrol edin",
+            "error_permissions_missing": "%(brand)s'a bildirim gönderme izni verilmedi - lütfen tekrar deneyin",
+            "error_saving": "Bildirim tercihleri kaydedilirken hata oluştu",
+            "error_saving_detail": "Bildirim tercihlerinizi kaydederken bir hata oluştu.",
+            "error_title": "Bildirimler etkinleştirilemiyor",
+            "error_updating": "Bildirim tercihleriniz güncellenirken bir hata oluştu. Lütfen seçeneği tekrar değiştirmeyi deneyin.",
+            "invites": "Bir odaya davet edildi",
+            "keywords": "Bir odada anahtar kelimeler kullanıldığında bir rozet <badge/> gösterin.",
+            "keywords_prompt": "Anahtar kelimeleri buraya girin veya farklı yazımlar ya da takma adlar için kullanın",
+            "labs_notice_prompt": "<strong>Güncelleme:</strong> Seçeneklerin bulunmasını kolaylaştırmak için Bildirim Ayarlarını basitleştirdik. Geçmişte seçtiğiniz bazı özel ayarlar burada gösterilmese de hâlâ etkindir. Devam ederseniz bazı ayarlarınız değişebilir.<a> Daha fazla bilgi edin</a>",
+            "mentions_keywords": "Bahsetmeler ve Anahtar Kelimeler",
+            "mentions_keywords_only": "Yalnızca Bahsetmeler ve Anahtar Kelimeler",
+            "messages_containing_keywords": "Anahtar kelimeler içeren mesajlar",
+            "noisy": "Gürültülü",
+            "notices": "Botlar tarafından gönderilen mesajlar",
+            "notify_at_room": "Biri @room kullanarak bahsettiğinde bildir",
+            "notify_keyword": "Biri bir anahtar kelime kullandığında bildir",
+            "notify_mention": "Biri @displayname veya %(mxid)s kullanarak bahsettiğinde bildir",
+            "other_section": "İlginizi çekebileceğini düşündüğümüz diğer şeyler:",
+            "people_mentions_keywords": "Kişiler, Bahsedilenler ve Anahtar Kelimeler",
+            "play_sound_for_description": "Varsayılan olarak tüm cihazlardaki tüm odalara uygulanır.",
+            "play_sound_for_section": "Şunun için bir ses çalın",
+            "push_targets": "Bildirim hedefleri",
+            "quick_actions_mark_all_read": "Tüm mesajları okundu olarak işaretle",
+            "quick_actions_reset": "Varsayılan ayarlara sıfırla",
+            "quick_actions_section": "Hızlı Eylemler",
+            "room_activity": "Yeni oda etkinliği, yükseltmeler ve durum mesajları gerçekleşir",
+            "rule_call": "Çağrı daveti",
+            "rule_contains_display_name": "İsmimi içeren mesajlar",
+            "rule_contains_user_name": "Kullanıcı adımı içeren mesajlar",
+            "rule_encrypted": "Grup sohbetlerdeki şifrelenmiş mesajlar",
+            "rule_encrypted_room_one_to_one": "Birebir sohbetlerdeki şifrelenmiş mesajlar",
+            "rule_invite_for_me": "Bir odaya davet edildiğimde",
+            "rule_message": "Grup sohbetlerindeki mesajlar",
+            "rule_room_one_to_one": "Bire bir sohbetlerdeki mesajlar",
+            "rule_roomnotif": "@room odasındaki mesajlar",
+            "rule_suppress_notices": "Bot tarafından gönderilen mesajlar",
+            "rule_tombstone": "Odalar güncellendiğinde",
+            "show_message_desktop_notification": "Masaüstü bildiriminde mesaj göster",
+            "voip": "Sesli ve Görüntülü aramalar"
+        },
+        "preferences": {
+            "Electron.enableHardwareAcceleration": "Donanım hızlandırmayı etkinleştirin (etkili olması için %(appName)s uygulamasını yeniden başlatın)",
+            "always_show_menu_bar": "Pencerenin menü çubuğunu her zaman göster",
+            "autocomplete_delay": "Oto tamamlama gecikmesi (ms)",
+            "code_blocks_heading": "Kod blokları",
+            "compact_modern": "Daha derli toplu 'Modern' düzeni kullan",
+            "composer_heading": "Yazan",
+            "default_timezone": "Tarayıcı varsayılanı (%(timezone)s)",
+            "dialog_title": "<strong>Ayarlar:</strong> Tercihler",
+            "enable_hardware_acceleration": "Donanım hızlandırmayı etkinleştir",
+            "enable_tray_icon": "Tepsi simgesini göster ve kapatıldığında pencereyi simge durumuna küçült",
+            "keyboard_heading": "Klavye kısayolları",
+            "keyboard_view_shortcuts_button": "Tüm klavye kısayollarını görmek için <a>buraya tıklayın</a>.",
+            "media_heading": "Görseller, GIF'ler ve videolar",
+            "presence_description": "Etkinliğinizi ve durumunuzu başkalarıyla paylaşın.",
+            "publish_timezone": "Herkese açık profilde zaman dilimini yayınla",
+            "rm_lifetime": "Okuma İşaretleyici ömrü (ms)",
+            "rm_lifetime_offscreen": "İşaretleyicinin ekran dışı kullanım ömrü (ms)",
+            "room_directory_heading": "Oda dizini",
+            "room_list_heading": "Oda listesi",
+            "show_avatars_pills": "Kullanıcı, oda ve etkinlik bahsetmelerinde avatarları göster",
+            "show_polls_button": "Anketler düğmesini göster",
+            "surround_text": "Özel karakterler yazarken seçili metni çevrele",
+            "time_heading": "Zamanı görüntüle",
+            "user_timezone": "Zaman dilimini ayarla"
+        },
+        "prompt_invite": "Potansiyel olarak geçersiz matrix kimliği olanlara davet gönderirken uyarı ver",
+        "replace_plain_emoji": "Düz metini otomatik olarak emoji ile değiştir",
+        "security": {
+            "analytics_description": "Sorunları belirlememize yardımcı olmak için anonim verileri paylaşın. Kişisel hiçbir şey yok. Üçüncü taraflar yok.",
+            "bulk_options_accept_all_invites": "Bütün %(invitedRooms)s davetlerini kabul et",
+            "bulk_options_reject_all_invites": "Tüm %(invitedRooms)s davetlerini reddet",
+            "bulk_options_section": "Toplu işlem seçenekleri",
+            "dehydrated_device_description": "Çevrimdışı cihaz özelliği, herhangi bir cihazda oturum açmamış olsanız bile şifreli mesajlar almanızı sağlar",
+            "dehydrated_device_enabled": "Çevrimdışı cihaz etkin",
+            "dialog_title": "<strong>Ayarlar:</strong> Güvenlik ve Gizlilik",
+            "e2ee_default_disabled_warning": "Sunucu yönetinciniz varsayılan olarak odalarda ve doğrudandan iletilerde uçtan uca şifrelemeyi kapadı.",
+            "enable_message_search": "Şifrelenmiş odalardaki mesaj aramayı aktifleştir",
+            "encryption_section": "Şifreleme",
+            "ignore_users_empty": "Yok saydığınız kullanıcı yok.",
+            "ignore_users_section": "Yoksayılan kullanıcılar",
+            "key_backup_algorithm": "Algoritma:",
+            "key_backup_connect": "Anahtar Yedekleme için bu oturuma bağlanın",
+            "message_search_disable_warning": "Devre dışı bırakılırsa, şifreli odalardaki mesajlar arama sonuçlarında görünmez.",
+            "message_search_disabled": "Arama sonuçlarında gozükmeleri için iletileri güvenli bir şekilde yerel olarak önbelleğe al.",
+            "message_search_enabled": {
+                "one": "İletilerin arama sonuçlarında gözükmeleri için %(rooms)s odasından %(size)s  yardımıyla depolayarak, şifrelenmiş iletileri güvenli bir şekilde yerel olarak önbelleğe al.",
+                "other": "İletilerin arama sonuçlarında gözükmeleri için %(rooms)s odalardan %(size)s yardımıyla depolayarak, şifrelenmiş iletileri güvenli bir şekilde yerel olarak önbelleğe al."
+            },
+            "message_search_failed": "Mesaj arama başlatılamadı",
+            "message_search_indexed_messages": "İndekslenmiş mesajlar:",
+            "message_search_indexed_rooms": "İndekslenmiş odalar:",
+            "message_search_indexing": "Şu anda dizinleniyor: %(currentRoom)s",
+            "message_search_indexing_idle": "Şu an hiç bir odada mesaj indeksleme yapılmıyor.",
+            "message_search_intro": "%(brand)s, şifreli mesajları arama sonuçlarında görünebilmesi için yerel olarak güvenli bir şekilde önbelleğe alıyor.",
+            "message_search_room_progress": "%(totalRooms)s odadan %(doneRooms)s tamamlandı",
+            "message_search_section": "Mesaj arama",
+            "message_search_sleep_time": "Mesajlar ne kadar hızlı indirilmeli.",
+            "message_search_space_used": "Kullanılan alan:",
+            "message_search_unsupported": "%(brand)s, şifrelenmiş iletileri yerel olarak güvenli bir şekilde önbelleğe almak için gereken bazı bileşenlerden yoksun. Bu özelliği denemek istiyorsanız, <nativeLink> arama bileşenlerinin eklendiği</nativeLink> özel bir masaüstü oluşturun.",
+            "message_search_unsupported_web": "%(brand)s internet tarayıcısında çalışıyorken şifrelenmiş mesajları güvenli bir şekilde önbelleğe alamaz. Şifrelenmiş mesajların arama sonucunda görünmesi için <desktopLink>%(brand)s Masaüstü</desktopLink> kullanın.",
+            "record_session_details": "Oturum yöneticisinde oturumları daha kolay tanımak için istemci adı, sürümü ve URL'yi kaydet",
+            "send_analytics": "Analiz verilerini gönder",
+            "strict_encryption": "Bu oturumdan doğrulanmamış oturumlara asla şifreli mesaj göndermeyin"
+        },
+        "send_read_receipts": "Okundu bilgisi gönder",
+        "send_read_receipts_unsupported": "Sunucunuz okundu bilgisi göndermeyi devre dışı bırakmayı desteklemiyor.",
+        "send_typing_notifications": "Yazma bildirimlerini gönder",
+        "sessions": {
+            "best_security_note": "Güvenlik için oturumlarınızı doğrulayın ve tanımadığınız veya artık kullanmadığınız oturumlardan çıkış yapın.",
+            "browser": "Tarayıcı",
+            "confirm_sign_out": {
+                "one": "Bu cihazın oturumunu kapatmayı onayla",
+                "other": "Şu cihazlardan oturumu kapatmayı onayla"
+            },
+            "confirm_sign_out_body": {
+                "one": "Bu cihazın oturumunu kapatmak için aşağıdaki butona tıkla.",
+                "other": "Bu cihazların oturumunu kapatmak için aşağıdaki butona tıkla."
+            },
+            "confirm_sign_out_continue": {
+                "one": "Cihazın oturumunu kapat",
+                "other": "Cihazların oturumunu kapat"
+            },
+            "confirm_sign_out_sso": {
+                "one": "Kimliğinizi doğrulamak için Tek Oturum Açma (SSO) özelliğini kullanarak bu cihazdan çıkış yapmayı onaylayın.",
+                "other": "Kimliğinizi doğrulamak için Tek Oturum Açma (SSO) özelliğini kullanarak bu cihazlardan çıkış yapmayı onaylayın."
+            },
+            "current_session": "Şimdiki oturum",
+            "desktop_session": "Masaüstü oturumu",
+            "details_heading": "Oturum detayları",
+            "device_unverified_description": "En iyi güvenlik ve güvenilirlik için bu oturumu doğrulayın veya oturumu kapatın.",
+            "device_unverified_description_current": "Gelişmiş güvenli mesajlaşma için mevcut oturumunuzu doğrulayın.",
+            "device_verified_description": "Bu oturum güvenli mesajlaşma için hazır.",
+            "device_verified_description_current": "Mevcut oturumunuz güvenli mesajlaşmaya hazır.",
+            "dialog_title": "<strong>Ayarlar:</strong> Oturumlar",
+            "error_pusher_state": "İtici durumu ayarlanamadı",
+            "error_set_name": "Oturum adı ayarlanamadı",
+            "filter_all": "Tümü",
+            "filter_inactive": "Aktif değil",
+            "filter_inactive_description": "%(inactiveAgeDays)s gün veya daha uzun süredir aktif değil",
+            "filter_label": "Cihazları filtrele",
+            "filter_unverified_description": "Güvenli mesajlaşma için hazır değil",
+            "filter_verified_description": "Güvenli mesajlaşma için hazır",
+            "hide_details": "Ayrıntıları gizle",
+            "inactive_days": "%(inactiveAgeDays)s+ gündür etkin değil",
+            "inactive_sessions": "Aktif olmayan oturumlar",
+            "inactive_sessions_explainer_1": "Etkin olmayan oturumlar, bir süredir kullanmadığınız, ancak şifreleme anahtarlarını almaya devam eden oturumlardır.",
+            "inactive_sessions_explainer_2": "Etkin olmayan oturumları kaldırmak güvenliği ve performansı artırır ve yeni bir oturumun şüpheli olup olmadığını belirlemenizi kolaylaştırır.",
+            "inactive_sessions_list_description": "Artık kullanmadığınız eski oturumlardan (%(inactiveAgeDays)s gün veya daha eski) oturumu kapatmayı düşünün.",
+            "ip": "IP adresi",
+            "last_activity": "Son etkinlik",
+            "manage": "Bu oturumu yönet",
+            "mobile_session": "Mobil cihaz oturumu",
+            "n_sessions_selected": {
+                "one": "%(count)s oturum seçildi",
+                "other": "%(count)s oturum seçildi"
+            },
+            "no_inactive_sessions": "Aktif olmayan oturum bulunamadı.",
+            "no_sessions": "Oturum bulunamadı.",
+            "no_unverified_sessions": "Doğrulanmamış oturum bulunamadı.",
+            "no_verified_sessions": "Doğrulanmış oturum bulunamadı.",
+            "os": "İşletim Sistemi",
+            "other_sessions_heading": "Diğer oturumlar",
+            "push_heading": "Anlık bildirimler",
+            "push_subheading": "Bu oturumda anlık bildirimler alın.",
+            "push_toggle": "Bu oturumda anlık bildirimleri aç/kapat.",
+            "rename_form_caption": "Oturum adlarının, iletişim kurduğunuz kişiler tarafından da görülebileceğini lütfen unutmayın.",
+            "rename_form_heading": "Oturumu yeniden adlandır",
+            "rename_form_learn_more": "Oturumları yeniden adlandırma",
+            "rename_form_learn_more_description_1": "Doğrudan mesajlardaki ve katıldığınız odalardaki diğer kullanıcılar, oturumlarınızın tam listesini görebilir.",
+            "rename_form_learn_more_description_2": "Bu, onlara gerçekten sizinle konuştuklarına dair güvence sağlar, ancak aynı zamanda buraya girdiğiniz oturum adını görebilecekleri anlamına gelir.",
+            "security_recommendations": "Güvenlik önerileri",
+            "security_recommendations_description": "Bu önerileri uygulayarak hesap güvenliğinizi artırın.",
+            "session_id": "Oturum ID",
+            "show_details": "Ayrıntıları göster",
+            "sign_in_with_qr": "Yeni cihaz bağla",
+            "sign_in_with_qr_button": "QR Kodunu Göster",
+            "sign_in_with_qr_description": "Başka bir cihazda oturum açmak ve güvenli mesajlaşma ayarlamak için bir QR kodu kullanın.",
+            "sign_in_with_qr_unsupported": "Hesap sağlayıcınız tarafından desteklenmiyor",
+            "sign_out": "Bu oturumdan çıkış yap",
+            "sign_out_all_other_sessions": "Diğer tüm oturumlardan çıkış yap (%(otherSessionsCount)s)",
+            "sign_out_confirm_description": {
+                "one": "%(count)s oturumdan çıkış yapmak istediğinizden emin misiniz?",
+                "other": "%(count)s oturumdan çıkış yapmak istediğinizden emin misiniz?"
+            },
+            "sign_out_n_sessions": {
+                "one": "%(count)s oturumdan çıkış yap",
+                "other": "%(count)s oturumdan çıkış yap"
+            },
+            "title": "Oturumlar",
+            "unknown_session": "Bilinmeyen oturum türü",
+            "unverified_session": "Doğrulanmamış oturum",
+            "unverified_session_explainer_1": "Bu oturum şifrelemeyi desteklemiyor ve bu nedenle doğrulanamıyor.",
+            "unverified_session_explainer_2": "Bu oturumuda şifrelemenin etkin olduğu odalara katılamazsınız.",
+            "unverified_session_explainer_3": "En iyi güvenlik ve gizlilik için, şifrelemeyi destekleyen Matrix istemcilerinin kullanılması önerilir.",
+            "unverified_sessions": "Doğrulanmamış oturumlar",
+            "unverified_sessions_explainer_1": "Doğrulanmamış oturumlar, kimlik bilgilerinizle giriş yapmış ancak çapraz doğrulaması yapılmamış oturumlardır.",
+            "unverified_sessions_explainer_2": "Hesabınızın yetkisiz kullanımı anlamına gelebileceğinden, bu oturumları tanıdığınızdan özellikle emin olmalısınız.",
+            "unverified_sessions_list_description": "Gelişmiş güvenli mesajlaşma için oturumlarınızı doğrulayın. Artık tanımadığınız ya da kullanmadığınız oturumlardan çıkış yapın.",
+            "url": "URL",
+            "verified_session": "Doğrulanmış oturum",
+            "verified_sessions": "Doğrulanmış oturumlar",
+            "verified_sessions_explainer_1": "Doğrulanmış oturumlar, şifrenizi girdikten veya kimliğinizi başka bir doğrulanmış oturumla onayladıktan sonra bu hesabı kullandığınız her yerdir.",
+            "verified_sessions_explainer_2": "Bu, şifrelenmiş mesajlarınızın kilidini açmak ve diğer kullanıcılara bu oturuma güvendiğinizi onaylamak için gereken tüm anahtarlara sahip olduğunuz anlamına gelir.",
+            "verified_sessions_list_description": "En iyi güvenlik için tanımadığınız veya artık kullanmadığınız tüm oturumlardan çıkış yapın.",
+            "verify_session": "Oturumu doğrula",
+            "web_session": "Ağ üzerinden oturum"
+        },
+        "show_avatar_changes": "Profil resmi değişikliklerini göster",
+        "show_breadcrumbs": "Oda listesinin üzerinde en son kullanılan odaları göster",
+        "show_chat_effects": "Sohbet efektlerini göster (ör. konfeti animasyonları)",
+        "show_displayname_changes": "Görüntü adı değişikliklerini göster",
+        "show_join_leave": "Katılma/ayrılma mesajlarını göster (davetler/çıkarmalar/engellemeler etkilenmez)",
+        "show_nsfw_content": "NSFW içeriği göster",
+        "show_read_receipts": "Diğer kullanıcılar tarafından gönderilen okundu bilgisini göster",
+        "show_redaction_placeholder": "Silinen mesajlar için bir yer tutucu göster",
+        "show_stickers_button": "Çıkartma tuşunu göster",
+        "show_typing_notifications": "Yazma bildirimlerini göster",
+        "showbold": "Oda listesindeki tüm etkinlikleri göster (noktalar veya okunmamış mesaj sayısı)",
+        "sidebar": {
+            "dialog_title": "<strong>Ayarlar:</strong> Kenar çubuğu",
+            "metaspaces_favourites_description": "Tüm favori odalarınızı ve kişilerinizi tek bir yerde toplayın.",
+            "metaspaces_home_all_rooms": "Tüm odaları göster",
+            "metaspaces_home_all_rooms_description": "Bir alana dahil olsalar bile tüm odaları Ana sayfada göster.",
+            "metaspaces_home_description": "Ana sayfa, genel bir bakış sağlamak için faydalıdır.",
+            "metaspaces_orphans": "Bir alana ait olmayan odalar",
+            "metaspaces_orphans_description": "Bir alana ait olmayan tüm odalarınızı tek bir yerde toplayın.",
+            "metaspaces_people_description": "Tüm kişilerinizi tek bir yerde toplayın.",
+            "metaspaces_subsection": "Gösterilecek alanlar",
+            "metaspaces_video_rooms": "Video odaları ve konferanslar",
+            "metaspaces_video_rooms_description": "Tüm özel video odalarını ve konferansları gruplandır.",
+            "metaspaces_video_rooms_description_invite_extension": "Konferanslarda matrix dışındaki kişileri davet edebilirsiniz.",
+            "spaces_explainer": "Alanlar, odaları ve kişileri gruplamanın bir yoludur. İçinde bulunduğunuz alanların yanı sıra, önceden oluşturulmuş bazı alanları da kullanabilirsiniz.",
+            "title": "Kenar çubuğu"
+        },
+        "start_automatically": "Sisteme giriş yaptıktan sonra otomatik başlat",
+        "tac_only_notifications": "Bildirimleri yalnızca mesaj dizisi etkinlik merkezinde göster",
+        "use_12_hour_format": "Zaman damgalarını 12 biçiminde göster (örn. 2:30 pm)",
+        "use_command_enter_send_message": "Mesaj göndermek için Command + Enter tuşlarını kullanın",
+        "use_command_f_search": "Zaman çizelgesinde arama yapmak için Command + F tuşlarını kullanın",
+        "use_control_enter_send_message": "Mesaj göndermek için Ctrl + Enter tuşlarını kullanın",
+        "use_control_f_search": "Zaman çizelgesinde arama yapmak için Ctrl + F tuşlarını kullanın",
+        "voip": {
+            "allow_p2p": "Bire bir aramalarda doğrudan bağlantıya izin ver",
+            "allow_p2p_description": "Etkinleştirildiğinde, karşı taraf IP adresinizi görebilir",
+            "audio_input_empty": "Hiçbir Mikrofon bulunamadı",
+            "audio_output": "Ses Çıkışı",
+            "audio_output_empty": "Ses çıkışları tespit edilemedi",
+            "auto_gain_control": "Otomatik kazanç kontrolü",
+            "connection_section": "Bağlantı",
+            "dialog_title": "<strong>Ayarlar:</strong> Ses ve Görüntü",
+            "echo_cancellation": "Yankı giderme",
+            "enable_fallback_ice_server": "Yedek arama destek sunucusuna (%(server)s) izin ver",
+            "enable_fallback_ice_server_description": "Yalnızca sunucunuz bunu sağlamıyorsa geçerlidir. Arama sırasında IP adresiniz paylaşılır.",
+            "mirror_local_feed": "Yerel video akışını yansıt",
+            "missing_permissions_prompt": "Medya izinleriniz eksikse, talep etmek için aşağıdaki butona tıklayın.",
+            "noise_suppression": "Gürültü bastırma",
+            "request_permissions": "Medya izinlerini isteyin",
+            "title": "Ses & Video",
+            "video_input_empty": "Hiçbir Web Kamerası algılanmadı",
+            "video_section": "Video ayarları",
+            "voice_agc": "Mikrofon sesini otomatik olarak ayarla",
+            "voice_processing": "Ses işleme",
+            "voice_section": "Ses ayarları"
+        },
+        "warn_quit": "Çıkmadan önce uyar",
+        "warning": "<w>UYARI:</w> <description/>"
+    },
+    "share": {
+        "link_copied": "Bağlantı kopyalandı",
+        "permalink_message": "Seçili mesaja bağlantı",
+        "permalink_most_recent": "En son mesaja bağlantı",
+        "share_call": "Konferans davet bağlantısı",
+        "share_call_subtitle": "Harici kullanıcıların Matrix hesabı olmadan çağrıya katılması için bağlantı:",
+        "title_link": "Bağlantıyı paylaş",
+        "title_message": "Oda Mesajını Paylaş",
+        "title_room": "Odayı Paylaş",
+        "title_user": "Kullanıcıyı Paylaş"
+    },
+    "slash_command": {
+        "addwidget": "Odaya URL ile özel bir widget ekler",
+        "addwidget_iframe_missing_src": "iframe'in src özniteliği yok",
+        "addwidget_invalid_protocol": "Lütfen bir https:// ya da http:// olarak bir görsel bileşen URL i belirtin",
+        "addwidget_missing_url": "Lütfen bir widget URL'si veya yerleşik kod girin",
+        "addwidget_no_permissions": "Bu odadaki görsel bileşenleri değiştiremezsiniz.",
+        "ban": "Yasaklanan(Banlanan) Kullanıcılar , ID'leri ile birlikte",
+        "category_actions": "Eylemler",
+        "category_admin": "Yönetici",
+        "category_advanced": "Gelişmiş",
+        "category_effects": "Efektler",
+        "category_messages": "Mesajlar",
+        "category_other": "Diğer",
+        "command_error": "Komut Hatası",
+        "converttodm": "Odayı bir DM'ye dönüştürür",
+        "converttoroom": "DM'yi bir odaya dönüştürür",
+        "could_not_find_room": "Oda bulunamadı",
+        "deop": "ID'leriyle birlikte , düşürülmüş kullanıcılar",
+        "devtools": "Geliştirici Araçları kutucuğunu açar",
+        "discardsession": "Şifrelenmiş bir odadaki geçerli giden grup oturumunun atılmasını zorlar",
+        "error_invalid_rendering_type": "Komut hatası: İşleme türü bulunamıyor (%(renderingType)s)",
+        "error_invalid_room": "Komut başarısız oldu: Oda bulunamıyor (%(roomId)s)",
+        "error_invalid_runfn": "Komut hatası: Eğik çizgi komutu işlenemiyor.",
+        "error_invalid_user_in_room": "Odada kullanıcı bulunamadı",
+        "help": "Komutların listesini kullanımı ve tanımlarıyla gösterir",
+        "help_dialog_title": "Komut Yardımı",
+        "holdcall": "Mevcut odadaki aramayı beklemeye alır",
+        "html": "İletiyi MarkDown olarak göndermek yerine HTML olarak gönderir",
+        "ignore": "Mesajlarını senden gizleyerek, bir kullanıcıyı yok sayar",
+        "ignore_dialog_description": "Şimdi %(userId)s yı yoksayıyorsunuz",
+        "ignore_dialog_title": "Yoksayılan kullanıcı",
+        "invite": "Mevcut odaya verilen kimliği olan kullanıcıyı davet eder",
+        "invite_3pid_needs_is_error": "E-posta ile davet etmek için bir kimlik sunucusu kullan. Ayarlardan Yönet.",
+        "invite_3pid_use_default_is_title": "Bir kimlik sunucusu kullan",
+        "invite_3pid_use_default_is_title_description": "E-posta ile davet etmek için kimlik sunucusu kullan. Varsayılan kimlik sunucusunu (%(defaultIdentityServerName)s) kullanmak için devam edin ya da ayarlardan değiştirin.",
+        "invite_failed": "Kullanıcı (%(user)s) %(roomId)s 'a davet edilemedi ancak davet eden yardımcı programdan herhangi bir hata verilmedi",
+        "join": "Belirtilen adres ile odaya katılır",
+        "jumptodate": "Zaman çizelgesinde belirtilen tarihe atla",
+        "jumptodate_invalid_input": "Verilen tarihi anlayamadık (%(inputDate)s). YYYY-AA-GG formatını kullanmayı deneyin.",
+        "lenny": "Düz metin mesajının başına (͡ ° ͜ʖ ͡ °) ekler",
+        "me": "Eylemi görüntüler",
+        "msg": "Belirtilen kullanıcıya ileti gönderir",
+        "myavatar": "Tüm odalarda profil resminizi değiştirir",
+        "myroomavatar": "Yalnızca bu odadaki profil resminizi değiştirir",
+        "myroomnick": "sadece mevcut odada görüntülenen lakabınızı değiştirir",
+        "nick": "Görünen takma adınızı değiştirir",
+        "no_active_call": "Bu odada aktif arama yok",
+        "op": "Bir kullanıcının güç düzeyini tanımla",
+        "part_unknown_alias": "Tanınmayan oda adresi: %(roomAlias)s",
+        "plain": "Mesajı markdown kullanmadan basit metin olarak iletir",
+        "query": "Belirtilen kullanıcı ile sohbet başlatır",
+        "query_not_found_phone_number": "Telefon numarası için Matrix kimliği bulunamıyor",
+        "rageshake": "Günlükler (log) ile hata raporu gönderin",
+        "rainbow": "Verilen mesajı gökkuşağı renklerinde gönderir",
+        "rainbowme": "Verilen ifadeyi bir gökkuşağı gibi renklendirilmiş olarak gönderin",
+        "remove": "Belirtilen kimliğe sahip kullanıcıyı bu odadan çıkartır",
+        "roomavatar": "Mevcut odadaki avatarınızı değiştirir",
+        "roomname": "Oda adını düzenler",
+        "server_error": "Sunucu Hatası",
+        "server_error_detail": "Sunucu kullanılamıyor , aşırı yüklenmiş veya başka bir şey ters gitmiş olabilir.",
+        "shrug": "Düz-metin mesajına ¯\\_(ツ)_/¯ ifadesi ekler",
+        "spoiler": "Mesajı sürprizbozan olarak gönder",
+        "tableflip": "Düz metin mesajının başına (╯°□°)╯︵ ┻━┻ ekler",
+        "topic": "Oda başlığını getirir yada ayarlar",
+        "topic_none": "Bu odanın başlığı yok.",
+        "topic_room_error": "Oda konusu alınamadı: Oda bulunamadı (%(roomId)s",
+        "unban": "Verilen ID ile kullanıcı yasağını kaldırır",
+        "unflip": "Düz metin mesajının başına ┬──┬ ノ (゜ - ゜ ノ) ekler",
+        "unholdcall": "Mevcut odadaki aramayı beklemeden çıkarır",
+        "unignore": "Sonraki mesajlarını göstererek, bir kullanıcıyı yoksaymaktan vazgeç",
+        "unignore_dialog_description": "%(userId)s artık yoksayılmıyor",
+        "unignore_dialog_title": "Reddedilmemiş kullanıcı",
+        "unknown_command": "Bilinmeyen Komut",
+        "unknown_command_button": "Mesaj olarak gönder",
+        "unknown_command_detail": "Tanınmayan komut: %(commandText)s",
+        "unknown_command_help": "<code>/help</code> yazarak var olan komutları listeleyebilirsiniz. Yoksa bunu bir ileti olarak mı göndermek istemiştiniz?",
+        "unknown_command_hint": "İpucu: İletilerinizi eğik çizgi ile başlatmak için <code>//</code> ile başlayın.",
+        "upgraderoom": "Bir odayı yeni bir versiyona yükseltir",
+        "upgraderoom_permission_error": "Bu komutu kullanmak için gerekli izinlere sahip değilsin.",
+        "usage": "Kullanım",
+        "view": "Verilen adresli odayı görüntüle",
+        "whois": "Bir kullanıcı hakkındaki bilgileri görüntüler"
+    },
+    "space": {
+        "add_existing_room_space": {
+            "create": "Bunun yerine yeni bir oda eklemek ister misiniz?",
+            "create_prompt": "Yeni bir oda oluştur",
+            "dm_heading": "Doğrudan Mesajlar",
+            "error_heading": "Seçilenlerin tümü eklenmedi",
+            "progress_text": {
+                "one": "Oda ekleniyor...",
+                "other": "Odalar ekleniyor... %(progress)s / %(count)s"
+            },
+            "space_dropdown_label": "Alan seçimi",
+            "space_dropdown_title": "Mevcut odaları ekle",
+            "subspace_moved_note": "Alan ekleme taşındı."
+        },
+        "add_existing_subspace": {
+            "create_button": "Yeni bir alan oluştur",
+            "create_prompt": "Bunun yerine yeni bir alan eklemek ister misiniz?",
+            "filter_placeholder": "Alanları ara",
+            "space_dropdown_title": "Var olan alanı ekle"
+        },
+        "context_menu": {
+            "devtools_open_timeline": "Oda zaman çizelgesine bak (devtools)",
+            "explore": "Odaları keşfet",
+            "home": "Alan ana sayfa",
+            "manage_and_explore": "Odaları yönetin ve keşfedin",
+            "options": "Alan seçenekleri"
+        },
+        "failed_load_rooms": "Oda listesi yüklenemedi.",
+        "failed_remove_rooms": "Bazı odalar kaldırılamadı. Daha sonra tekrar deneyin",
+        "incompatible_server_hierarchy": "Sunucunuz alan hiyerarşilerini göstermeyi desteklemiyor.",
+        "invite": "İnsanları davet et",
+        "invite_description": "E-posta veya kullanıcı adı ile davet et",
+        "invite_link": "Davet bağlantısını paylaş",
+        "joining_space": "Katılınıyor",
+        "landing_welcome": "Hoş geldiniz <name/>",
+        "leave_dialog_action": "Alandan ayrıl",
+        "leave_dialog_description": "<spaceName/> alanından ayrılmak üzeresiniz.",
+        "leave_dialog_only_admin_room_warning": "Ayrılmak istediğiniz bazı odaların veya alanların tek yöneticisi sizsiniz. Onları terk ettiğinizde hiçbir yönetici kalmayacaktır.",
+        "leave_dialog_only_admin_warning": "Bu alanın tek yöneticisi sizsiniz. Burayı terk etmeniz, kimsenin burayı kontrol edemeyeceği anlamına gelir.",
+        "leave_dialog_option_all": "Tüm odalardan çık",
+        "leave_dialog_option_intro": "Bu alandaki odalardan ayılmak ister misiniz?",
+        "leave_dialog_option_none": "Hiçbir odadan ayrılma",
+        "leave_dialog_option_specific": "Bazı odaları terk et",
+        "leave_dialog_public_rejoin_warning": "Yeniden davet edilmediğiniz sürece yeniden katılamazsınız.",
+        "leave_dialog_title": "Ayrıl %(spaceName)s",
+        "mark_suggested": "Önerilen olarak işaretle",
+        "no_search_result_hint": "Farklı bir arama yapmayı veya yazım hatalarını kontrol etmeyi deneyebilirsiniz.",
+        "preferences": {
+            "sections_section": "Gösterilecek bölümler",
+            "show_people_in_space": "Bu, sohbetlerinizi bu alanın üyeleriyle gruplandırır. Bunu kapatmak, bu sohbetleri %(spaceName)s görünümünüzden gizleyecektir."
+        },
+        "room_filter_placeholder": "Oda ara",
+        "search_children": "Ara %(spaceName)s",
+        "search_placeholder": "Adları ve açıklamaları ara",
+        "select_room_below": "Önce aşağıdan bir oda seçin",
+        "share_public": "Herkese açık alanınızı paylaşın",
+        "suggested": "Önerilen",
+        "suggested_tooltip": "Bu oda katılmak için öneriliyor",
+        "title_when_query_available": "Sonuçlar",
+        "title_when_query_unavailable": "Odalar ve alanlar",
+        "unmark_suggested": "Önerilmeyen olarak işaretle",
+        "user_lacks_permission": "İzniniz yok"
+    },
+    "space_settings": {
+        "title": "Ayarlar - %(spaceName)s"
+    },
+    "spaces": {
+        "error_no_permission_add_room": "Bu alana oda ekleme izniniz yok",
+        "error_no_permission_add_space": "Bu alana alan ekleme izniniz yok",
+        "error_no_permission_create_room": "Bu alanda yeni oda oluşturma izniniz yok",
+        "error_no_permission_invite": "Kullanıcıları bu alana davet etme izniniz yok"
+    },
+    "spotlight": {
+        "public_rooms": {
+            "network_dropdown_add_dialog_description": "Keşfetmek istediğiniz sunucunun adını girin.",
+            "network_dropdown_add_dialog_placeholder": "Sunucu adı",
+            "network_dropdown_add_dialog_title": "Yeni sunucu ekle",
+            "network_dropdown_add_server_option": "Yeni sunucu ekle...",
+            "network_dropdown_available_invalid": "Sunucuda veya oda listesinde bulunamıyor",
+            "network_dropdown_available_invalid_forbidden": "Bu sunucunun oda listesini görüntülemenize izin verilmiyor",
+            "network_dropdown_available_valid": "İyi görünüyor",
+            "network_dropdown_remove_server_adornment": "Sunucuyu kaldır \"%(roomServer)s\"",
+            "network_dropdown_required_invalid": "Sunucu adı girin",
+            "network_dropdown_selected_label": "Gösteri: Matrix odaları",
+            "network_dropdown_selected_label_instance": "Göster: %(instance)s oda (%(server)s)",
+            "network_dropdown_your_server_description": "Sunucunuz"
+        }
+    },
+    "spotlight_dialog": {
+        "cant_find_person_helpful_hint": "Aradığınız kişiyi göremiyorsanız, davet bağlantınızı gönderin.",
+        "cant_find_room_helpful_hint": "Aradığınız odayı bulamıyorsanız, davet isteyin veya yeni bir oda oluşturun.",
+        "copy_link_text": "Davet bağlantısını kopyala",
+        "count_of_members": {
+            "one": "%(count)s Üye",
+            "other": "%(count)s Üye"
+        },
+        "create_new_room_button": "Yeni Oda Oluştur",
+        "failed_querying_public_rooms": "Herkese açık odalar sorgulanamadı",
+        "failed_querying_public_spaces": "Herkese açık alanlar sorgulanamadı",
+        "group_chat_section_title": "Diğer seçenekler",
+        "heading_with_query": "Arama yapmak için \"%(query)s\" kullanın",
+        "heading_without_query": "Ara",
+        "join_button_text": "Katıl %(roomAddress)s",
+        "keyboard_scroll_hint": "Kaydırmak için <arrows/> kullanın",
+        "other_rooms_in_space": "%(spaceName)s alanındaki diğer odalar",
+        "public_rooms_label": "Herkese açık odalar",
+        "public_spaces_label": "Herkese açık alanlar",
+        "recent_searches_section_title": "Son Aramalar",
+        "recently_viewed_section_title": "Son görüntülenenler",
+        "remove_filter": "%(filter)s için arama filtresini kaldır",
+        "result_may_be_hidden_privacy_warning": "Bazı sonuçlar gizlilik için gizlenmiş olabilir",
+        "result_may_be_hidden_warning": "Bazı sonuçlar gizlenmiş olabilir",
+        "search_dialog": "Arama İletişim Kutusu",
+        "spaces_title": "Bulunduğunuz alanlar",
+        "start_group_chat_button": "Grup sohbeti başlat"
+    },
+    "stickers": {
+        "empty": "Açılmış herhangi bir çıkartma paketine sahip değilsiniz",
+        "empty_add_prompt": "Şimdi biraz ekle"
+    },
+    "terms": {
+        "column_document": "Belge",
+        "column_service": "Hizmet",
+        "column_summary": "Özet",
+        "identity_server_no_terms_description_1": "Bu eylem, bir e-posta adresini veya telefon numarasını doğrulamak için varsayılan kimlik sunucusuna <server /> erişilmesini gerektirir, ancak sunucunun herhangi bir hizmet şartı yoktur.",
+        "identity_server_no_terms_description_2": "Sadece sunucunun sahibine güveniyorsanız devam edin.",
+        "identity_server_no_terms_title": "Kimlik sunucusu hizmet kurallarına sahip değil",
+        "inline_intro_text": "Devam etmek için <policyLink /> i kabul ediniz:",
+        "integration_manager": "Botları, köprüleri, görsel bileşenleri ve çıkartma paketlerini kullan",
+        "intro": "Devam etmek için bu servisi kullanma şartlarını kabul etmeniz gerekiyor.",
+        "summary_identity_server_1": "Kişileri telefon yada e-posta ile bul",
+        "summary_identity_server_2": "Telefon veya e-posta ile bulunun",
+        "tac_button": "Hükümler ve koşulları incele",
+        "tac_description": "%(homeserverDomain)s ana sunucusunu kullanmaya devam etmek için hüküm ve koşulları incelemeli ve kabul etmelisiniz.",
+        "tac_title": "Hükümler ve koşullar",
+        "tos": "Hizmet Şartları"
+    },
+    "theme": {
+        "light_high_contrast": "Yüksek ışık kontrastı",
+        "match_system": "Eşleşme sistemi"
+    },
+    "thread_view_back_action_label": "Konuya geri dön",
+    "threads": {
+        "all_threads": "Tüm mesaj dizileri",
+        "all_threads_description": "Bu odadaki tüm mesaj dizilerini göster",
+        "count_of_reply": {
+            "one": "%(count)s yanıt",
+            "other": "%(count)s yanıt"
+        },
+        "empty_description": "Bir mesajın üzerine geldiğinizde \"%(replyInThread)s\" kullan.",
+        "empty_title": "Mesaj dizileri, konuşmalarınızın konuyla ilgili ve takip edilmesi kolay kalmasına yardımcı olur.",
+        "error_start_thread_existing_relation": "Varolan bir ilişkiye sahip mesaj dizisinden dolayı, mesaj dizisi oluşturulamıyor",
+        "mark_all_read": "Tümünü okundu olarak işaretle",
+        "my_threads": "Mesaj dizilerim",
+        "my_threads_description": "Katıldığınız tüm mesaj dizilerini gösterir",
+        "open_thread": "Mesaj dizisi aç",
+        "show_thread_filter": "Göster:"
+    },
+    "threads_activity_centre": {
+        "header": "Mesaj dizileri etkinliği",
+        "no_rooms_with_threads_notifs": "Henüz mesaj dizisi bildirimleri olan odalarınız yok.",
+        "no_rooms_with_unread_threads": "Henüz okunmamış mesaj dizilerinin bulunduğu odalarınız yok."
+    },
+    "time": {
+        "about_day_ago": "yaklaşık bir gün önce",
+        "about_hour_ago": "yaklaşık bir saat önce",
+        "about_minute_ago": "yaklaşık bir dakika önce",
+        "date_at_time": "%(date)s e %(time)s",
+        "few_seconds_ago": "bir kaç saniye önce",
+        "hours_minutes_seconds_left": "%(hours)sh %(minutes)sm %(seconds)ss kaldı",
+        "in_about_day": "şu andan itibaren yaklaşık bir gün",
+        "in_about_hour": "şu andan itibaren yaklaşık bir saat",
+        "in_about_minute": "şu andan itibaren yaklaşık bir dakika",
+        "in_few_seconds": "şu andan itibaren bir kaç saniye",
+        "in_n_days": "şu andan itibaren %(num)s gün",
+        "in_n_hours": "şu andan itibaren %(num)s saat",
+        "in_n_minutes": "şu andan itibaren %(num)s dakika",
+        "left": "%(timeRemaining)s kaldı",
+        "minutes_seconds_left": "%(minutes)sm %(seconds)ss kaldı",
+        "n_days_ago": "%(num)s gün önce",
+        "n_hours_ago": "%(num)s saat önce",
+        "n_minutes_ago": "%(num)s dakika önce",
+        "seconds_left": "%(seconds)s saniye kaldı",
+        "short_days": "%(value)sd",
+        "short_days_hours_minutes_seconds": "%(days)sd %(hours)sh %(minutes)sm %(seconds)ss",
+        "short_hours": "%(value)sh",
+        "short_hours_minutes_seconds": "%(hours)sh %(minutes)sm %(seconds)ss",
+        "short_minutes": "%(value)sm",
+        "short_minutes_seconds": "%(minutes)sm %(seconds)ss",
+        "short_seconds": "%(value)ss"
+    },
+    "timeline": {
+        "context_menu": {
+            "collapse_reply_thread": "Yanıt dizisini daralt",
+            "external_url": "Kaynak URL",
+            "open_in_osm": "OpenStreetMap'te aç",
+            "report": "Şikayet et",
+            "resent_unsent_reactions": "%(unsentCount)s tepki(ler)i yeniden gönder",
+            "show_url_preview": "Önizlemeyi göster",
+            "view_related_event": "İlgili etkinliği görüntüle",
+            "view_source": "Kaynağı görüntüle"
+        },
+        "creation_summary_dm": "%(creator)s bu DM'yi oluşturdu.",
+        "creation_summary_room": "%(creator)s odayı oluşturdu ve yapılandırdı.",
+        "decryption_failure": {
+            "blocked": "Cihazınız doğrulanmamış olduğu için gönderen kişi bu mesajı almanızı engelledi",
+            "historical_event_no_key_backup": "Geçmiş mesajlar bu cihazda kullanılamıyor",
+            "historical_event_unverified_device": "Geçmiş mesajlara erişim için bu cihazı doğrulamanız gerekir",
+            "historical_event_user_not_joined": "Bu mesaja erişiminiz yok",
+            "sender_identity_previously_verified": "Gönderenin doğrulanmış kimliği değişti",
+            "sender_unsigned_device": "Güvenli olmayan bir cihazdan gönderildi",
+            "unable_to_decrypt": "Mesaj şifresi çözülemedi"
+        },
+        "disambiguated_profile": "%(displayName)s (%(matrixId)s)",
+        "download_action_decrypting": "Şifre çözülüyor",
+        "download_action_downloading": "İndiriliyor",
+        "download_failed": "İndirme başarısız oldu",
+        "download_failed_description": "Bu dosya indirilirken bir hata oluştu",
+        "e2e_state": "Uçtan uca şifrelemenin durumu",
+        "edits": {
+            "tooltip_label": "%(date)s tarihinde düzenlendi. Düzenlemeleri görmek için tıkla.",
+            "tooltip_sub": "Düzenlemeleri görmek için tıkla",
+            "tooltip_title": "%(date)s tarihinde düzenlendi"
+        },
+        "error_no_renderer": "Bu olay görüntülenemedi",
+        "error_rendering_message": "Bu mesaj yüklenemiyor",
+        "historical_messages_unavailable": "Önceki mesajları göremezsiniz",
+        "in_room_name": " <strong>%(room)s</strong>",
+        "io.element.widgets.layout": "%(senderName)s oda düzenini güncelledi",
+        "late_event_separator": "%(dateTime)s gönderildi",
+        "load_error": {
+            "no_permission": "Bu odanın zaman çizelgesinde belirli bir nokta yüklemeye çalışıldı , ama geçerli mesajı görüntülemeye izniniz yok.",
+            "title": "Zaman çizelgesi konumu yüklenemedi",
+            "unable_to_find": "Bu odanın akışında belirli bir noktaya yüklemeye çalışıldı , ancak bulunamadı."
+        },
+        "m.audio": {
+            "error_downloading_audio": "Ses dosyası indirilirken hata oluştu",
+            "error_processing_audio": "Sesli mesaj işlenirken hata oluştu",
+            "error_processing_voice_message": "Sesli mesaj işlenirken hata oluştu",
+            "unnamed_audio": "İsimsiz ses"
+        },
+        "m.beacon_info": {
+            "view_live_location": "Canlı konumu görüntüle"
+        },
+        "m.call": {
+            "video_call_ended": "Görüntülü arama sona erdi",
+            "video_call_started": "%(roomName)s odasında görüntülü görüşme başladı.",
+            "video_call_started_text": "%(name)s görüntülü arama başlattı",
+            "video_call_started_unsupported": "%(roomName)s'de görüntülü görüşme başladı. (bu tarayıcı tarafından desteklenmiyor)"
+        },
+        "m.call.hangup": {
+            "dm": "Çağrı sonlandı"
+        },
+        "m.call.invite": {
+            "answered_elsewhere": "Başka bir yerde yanıtlandı",
+            "call_back_prompt": "Geri ara",
+            "declined": "Çağrı reddedildi",
+            "failed_connect_media": "Medya bağlanamadı",
+            "failed_connection": "Bağlantı başarısız",
+            "failed_opponent_media": "Cihazları kamerayı veya mikrofonu başlatamadı",
+            "missed_call": "Cevapsız çağrı",
+            "no_answer": "Cevap yok",
+            "unknown_error": "Bilinmeyen bir hata oluştu.",
+            "unknown_failure": "Bilinmeyen hata: %(reason)s",
+            "unknown_state": "Çağrı bilinmeyen bir durumda!",
+            "video_call": "%(senderName)s bir görüntülü çağrı yaptı.",
+            "video_call_unsupported": "%(senderName)s bir görüntülü çağrı yaptı. (bu tarayıcı tarafından desteklenmiyor)",
+            "voice_call": "%(senderName)s bir çağrı yaptı.",
+            "voice_call_unsupported": "%(senderName)s bir çağrı başlattı. (Bu tarayıcı tarafından desteklenmiyor)"
+        },
+        "m.file": {
+            "error_decrypting": "Ek şifresini çözme hatası",
+            "error_invalid": "Geçersiz dosya"
+        },
+        "m.image": {
+            "error": "Hata nedeniyle görüntü gösterilemiyor",
+            "error_decrypting": "Resim şifre çözme hatası",
+            "error_downloading": "Görüntü indirilirken hata oluştu",
+            "sent": "%(senderDisplayName)s bir resim gönderdi.",
+            "show_image": "Resim göster"
+        },
+        "m.key.verification.request": {
+            "user_wants_to_verify": "%(name)s doğrulamak istiyor",
+            "you_started": "Doğrulama isteği gönderdiniz"
+        },
+        "m.location": {
+            "full": "%(senderName)s konumunu paylaştı",
+            "location": "Konum paylaştı: ",
+            "self_location": "Konumunu paylaştı: "
+        },
+        "m.poll": {
+            "count_of_votes": {
+                "one": "%(count)s oy",
+                "other": "%(count)s oy"
+            }
+        },
+        "m.poll.end": {
+            "ended": "Anketi sonlandırdı",
+            "sender_ended": "%(senderName)s anketi sonlandırdı"
+        },
+        "m.poll.start": "%(senderName)s bir anket başlattı - %(pollQuestion)s",
+        "m.room.avatar": {
+            "changed": "%(senderDisplayName)s odanın avatarını değiştirdi.",
+            "changed_img": "%(senderDisplayName)s odanın avatarını <img/> olarak çevirdi",
+            "lightbox_title": "%(senderDisplayName)s %(roomName)s için avatarı değiştirdi",
+            "removed": "%(senderDisplayName)s odanın avatarını kaldırdı."
+        },
+        "m.room.canonical_alias": {
+            "alt_added": {
+                "other": "%(senderName)s bu odaya alternatif olarak %(addresses)s adreslerini ekledi.",
+                "one": "%(senderName)s bu oda için alternatif adres %(addresses)s ekledi."
+            },
+            "alt_removed": {
+                "other": "%(senderName)s bu oda için alternatif adresleri %(addresses)s sildi.",
+                "one": "%(senderName)s bu oda için alternatif adresi %(addresses)s sildi."
+            },
+            "changed": "Bu oda adresleri %(senderName)s tarafından değiştirildi.",
+            "changed_alternative": "Bu oda için alternatif adresler %(senderName)s tarafından değiştirildi.",
+            "changed_main_and_alternative": "Bu oda için ana ve alternatif adresler %(senderName)s tarafından değiştirildi.",
+            "removed": "Bu oda için ana adresi silen %(senderName)s.",
+            "set": "%(senderName)s bu odanın ana adresini %(address)s olarak ayarladı."
+        },
+        "m.room.create": {
+            "continuation": "Bu oda başka bir görüşmenin devamıdır.",
+            "see_older_messages": "Daha eski mesajları görmek için buraya tıklayın.",
+            "unknown_predecessor": "Bu odanın eski sürümünü bulamıyoruz (oda kimliği: %(roomId)s) ve aramak için bize 'via_servers' sağlanmadı.",
+            "unknown_predecessor_guess_server": "Bu odanın eski sürümünü bulamıyoruz (oda kimliği: %(roomId)s) ve bu sürümü aramamız için bize 'via_servers' sağlanmadı. Oda kimliğinden sunucuyu tahmin etmenin işe yaraması mümkün. Denemek isterseniz, bu bağlantıya tıklayın:"
+        },
+        "m.room.encryption": {
+            "disable_attempt": "Şifrelemeyi devre dışı bırakma denemesi yok sayıldı",
+            "disabled": "Şifreleme etkin değil",
+            "enabled": "Bu odadaki mesajlar uçtan uca şifrelenmiştir. Kullanıcılar katıldığında, profillerinde onları doğrulayabilirsiniz, profil resimlerine dokunmanız yeterlidir.",
+            "enabled_dm": "Buradaki mesajlar uçtan uca şifrelenir. Profilinden %(displayName)s'i doğrulayın - profil resmine dokunun.",
+            "enabled_local": "Bu sohbetteki mesajlar uçtan uca şifrelenecektir.",
+            "parameters_changed": "Bazı şifreleme parametreleri değiştirildi.",
+            "unsupported": "Bu odada kullanılan şifreleme desteklenmiyor."
+        },
+        "m.room.guest_access": {
+            "can_join": "%(senderDisplayName)s misafirlerin odaya katılmasına izin verdi.",
+            "forbidden": "Odaya misafirlerin girişini engelleyen %(senderDisplayName)s.",
+            "unknown": "%(senderDisplayName)s misafir erişim kuralını %(rule)s şeklinde değiştirdi"
+        },
+        "m.room.history_visibility": {
+            "invited": "%(senderName)s gelecekte oda geçmişini görünür yaptı Tüm oda üyeleri , davet edildiği noktadan.",
+            "joined": "%(senderName)s gelecekte oda geçmişini görünür yaptı Tüm oda üyeleri , katıldıkları noktalardan.",
+            "shared": "%(senderName)s gelecekte oda geçmişini görünür yaptı Tüm oda üyeleri.",
+            "unknown": "%(senderName)s gelecekte oda geçmişini görünür yaptı bilinmeyen (%(visibility)s).",
+            "world_readable": "%(senderName)s gelecekte oda geçmişini görünür yaptı herhangi biri."
+        },
+        "m.room.join_rules": {
+            "invite": "Odayı sadece davetle yapan %(senderDisplayName)s.",
+            "knock": "%(senderDisplayName)s katılmayı istemek için katılma kuralını değiştirdi.",
+            "public": "%(senderDisplayName)s odayı adresi bilen herkesin girebileceği şekilde halka açık hale getirdi.",
+            "restricted": "%(senderDisplayName)s Bu odaya kimlerin katılabileceği değiştirdi",
+            "restricted_settings": "%(senderDisplayName)s Bu odaya kimlerin katılabileceği değiştirdi.<a>Ayarları görüntüle</a> .",
+            "unknown": "%(senderDisplayName)s katılma kuralını %(rule)s şeklinde değiştirdi"
+        },
+        "m.room.member": {
+            "accepted_3pid_invite": "%(targetName)s, %(displayName)s kişisinin davetini kabul etti",
+            "accepted_invite": "%(targetName)s daveti kabul etti",
+            "ban": "%(senderName)s %(targetName)s kullanıcısını yasakladı: %(reason)s",
+            "ban_reason": "%(senderName)s %(targetName) kullanıcısını yasakladı: %(reason)s",
+            "change_avatar": "%(senderName)s profil resmini değiştirdi",
+            "change_name": "%(oldDisplayName)s görünür adını %(displayName)s yaptı",
+            "change_name_avatar": "%(oldDisplayName)s görüntülenen adını ve profil resmini değiştirdi",
+            "invite": "%(targetName)s kullanıcılarını %(senderName)s davet etti",
+            "join": "%(targetName)s odaya katıldı",
+            "kick": "%(senderName)s kaldırıldı %(targetName)s",
+            "kick_reason": "%(senderName)s kaldırıldı %(targetName)s :%(reason)s",
+            "left": "%(targetName)s odadan çıktı",
+            "left_reason": "%(targetName)s odadan çıktı: %(reason)s",
+            "no_change": "%(senderName)s hiçbir değişiklik yapmadı",
+            "reject_invite": "%(targetName)s daveti geri çevirdi",
+            "reject_invite_reason": "%(targetName)s daveti reddetti: %(reason)s",
+            "remove_avatar": "%(senderName)s profil resmini kaldırdı",
+            "remove_name": "%(senderName)s, %(oldDisplayName)s görünür adını kaldırdı",
+            "set_avatar": "%(senderName)s profil resmi belirledi",
+            "set_name": "%(senderName)s görünür adını %(displayName)s yaptı",
+            "unban": "%(targetName) tarafından %(senderName)s yasakları kaldırıldı",
+            "withdrew_invite": "%(senderName)s, %(targetName)s kullanıcısının davetini geri çekti",
+            "withdrew_invite_reason": "%(senderName)s,%(targetName)s kullanıcısının davetini geri çekti: %(reason)s"
+        },
+        "m.room.name": {
+            "change": "%(senderDisplayName)s oda ismini %(oldRoomName)s bununla değiştirdi %(newRoomName)s.",
+            "remove": "%(senderDisplayName)s oda adını kaldırdı.",
+            "set": "%(senderDisplayName)s odanın ismini %(roomName)s olarak değiştirdi."
+        },
+        "m.room.pinned_events": {
+            "changed": "Oda için sabitlenmiş mesajları %(senderName)s değiştirdi.",
+            "changed_link": "%(senderName)s odadaki <a>ileti sabitlemelerini</a> değiştirdi.",
+            "pinned": "%(senderName)s bu odaya bir mesaj sabitledi, Bütün sabitlenmiş mesajları görün.",
+            "pinned_link": "%(senderName)s bu odaya<a> sabitlenmiş bir mesaj</a>gönderildi. Hepsini gör<b> sabitlenmiş mesajlar</b> .",
+            "unpinned": "%(senderName)s Bu odadan bir mesajın sabitlemesini kaldırdı. Bütün sabitlenmiş mesajları görün.",
+            "unpinned_link": "%(senderName)s bu odadan bir <a>mesajın</a> sabitlenmesini kaldırdı. Tüm <b>sabitlenmiş mesajları gör</b>."
+        },
+        "m.room.power_levels": {
+            "changed": "%(senderName)s %(powerLevelDiffText)s'nin güç düzeyini değiştirdi.",
+            "user_from_to": "%(userId)s %(fromPowerLevel)s den %(toPowerLevel)s ' ye"
+        },
+        "m.room.server_acl": {
+            "all_servers_banned": "🎉 Tüm sunucuların katılımı yasaklanmıştır! Bu oda artık kullanılamaz.",
+            "changed": "%(senderDisplayName)s bu oda için sunucu ACL'lerini değiştirdi.",
+            "set": "%(senderDisplayName)s bu oda için sunucu ACL'lerini ayarladı."
+        },
+        "m.room.third_party_invite": {
+            "revoked": "%(senderName)s, %(targetDisplayName)s'nin odaya katılması için daveti iptal etti.",
+            "sent": "%(senderName)s %(targetDisplayName)s' a odaya katılması için bir davet gönderdi."
+        },
+        "m.room.tombstone": "Odayı güncelleyen %(senderDisplayName)s.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s konuyu \"%(topic)s\" olarak değiştirdi.",
+            "removed": "%(senderDisplayName)s konuyu kaldırdı."
+        },
+        "m.sticker": "%(senderDisplayName)s bir çıkartma gönderdi.",
+        "m.video": {
+            "error_decrypting": "Video şifre çözme hatası"
+        },
+        "m.widget": {
+            "added": "%(widgetName)s görsel bileşeni %(senderName)s tarafından eklendi",
+            "jitsi_ended": "Video konferans %(senderName)s tarafından sonlandırıldı",
+            "jitsi_join_right_prompt": "Sağdaki oda bilgi kartından konferansa katılın",
+            "jitsi_join_top_prompt": "Bu odanın tepesindeki konferansa katılın",
+            "jitsi_started": "Video konferans %(senderName)s tarafından başlatıldı",
+            "jitsi_updated": "Video konferans %(senderName)s tarafından güncellendi",
+            "modified": "%(widgetName)s görsel bileşeni %(senderName)s tarafından düzenlendi",
+            "removed": "%(widgetName)s görsel bileşeni %(senderName)s tarafından silindi"
+        },
+        "mab": {
+            "collapse_reply_chain": "Alıntıları daralt",
+            "copy_link_thread": "Bağlantıyı konuya kopyala",
+            "expand_reply_chain": "Alıntıları genişlet",
+            "label": "Mesaj Eylemleri",
+            "view_in_room": "Odada görüntüle"
+        },
+        "message_timestamp_received_at": "%(dateTime)s alındı",
+        "message_timestamp_sent_at": "Gönderildi: %(dateTime)s",
+        "mjolnir": {
+            "changed_rule_glob": "%(senderName)s %(oldGlob)s ile eşleşen banlama kuralını %(newGlob)s ile eşleşen olarak değiştirdi sebebi %(reason)s",
+            "changed_rule_rooms": "%(senderName)s %(oldGlob)s ile eşleşen odaları banlama kuralını %(newGlob)s ile eşleşen olarak değiştirdi sebebi %(reason)s",
+            "changed_rule_servers": "%(senderName)s %(oldGlob)s ile eşleşen sunucuları banlama kuralını %(newGlob)s ile eşleşen olarak değiştirdi sebebi %(reason)s",
+            "changed_rule_users": "%(senderName)s %(oldGlob)s ile eşleşen kullanıcıları banlama kuralını %(newGlob)s ile eşleşen olarak değiştirdi sebebi %(reason)s",
+            "created_rule": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir yasak kuralı oluşturdu",
+            "created_rule_rooms": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir oda yasaklama kuralı oluşturdu",
+            "created_rule_servers": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen bir sunucular yasaklama kuralı oluşturdu",
+            "created_rule_users": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen kullanıcıları yasaklama kuralı oluşturdu",
+            "message_hidden": "Bu kullanıcıyı yok saydınız, bu yüzden mesajları gizlidir. <a>Yine de göster.</a>",
+            "removed_rule": "%(senderName)s %(glob)s ile eşleşen banlama kuralını kaldırdı",
+            "removed_rule_rooms": "%(senderName)s %(glob)s ile eşleşen odaları banlama kuralını kaldırdı",
+            "removed_rule_servers": "%(senderName)s %(glob)s ile eşleşen sunucuları banlama kuralını kaldırdı",
+            "removed_rule_users": "%(senderName)s %(glob)s ile eşleşen kullanıcıları banlama kuralını kaldırdı",
+            "updated_invalid_rule": "%(senderName)s bir geçersiz yasaklama kuralını güncelledi",
+            "updated_rule": "%(senderName)s, %(reason)s nedeniyle %(glob)s ile eşleşen yasaklama kuralını güncelledi",
+            "updated_rule_rooms": "%(senderName)s %(glob)s ile eşleşen odaları banlama kuralını bu sebepten dolayı güncelledi %(reason)s",
+            "updated_rule_servers": "%(senderName)s %(glob)s ile eşleşen sunucuları banlama kuralını bu sebepten dolayı güncelledi %(reason)s",
+            "updated_rule_users": "%(senderName)s %(glob)s ile eşleşen kullanıcıları banlama kuralını bu sebepten dolayı güncelledi %(reason)s"
+        },
+        "no_permission_messages_before_invite": "Davet edilmeden önceki mesajları görüntüleme izniniz yok.",
+        "no_permission_messages_before_join": "Katılmadan önceki mesajları görüntüleme izniniz yok.",
+        "pending_moderation": "Moderasyon bekleyen mesaj",
+        "pending_moderation_reason": "Moderasyon bekleyen mesaj: %(reason)s",
+        "reactions": {
+            "add_reaction_prompt": "Tepki Ekle",
+            "custom_reaction_fallback_label": "Özel tepki",
+            "label": "%(reactors)s, %(content)s ile tepki verdi",
+            "tooltip_caption": "%(shortName)s tepki verdi"
+        },
+        "read_receipt_title": {
+            "one": "%(count)s kişi tarafından görüldü",
+            "other": "%(count)s kişi tarafından görüldü"
+        },
+        "read_receipts_label": "Okundu bilgisi",
+        "redacted": {
+            "tooltip": "Mesaj %(date)s tarihinde silindi"
+        },
+        "redaction": "Mesaj %(name)s tarafından silindi",
+        "reply": {
+            "error_loading": "Yanıtlanan etkinlik yüklenemedi, etkinlik mevcut değil veya görüntüleme yetkiniz yok.",
+            "in_reply_to": "<a>Cevap olarak</a> <pill>",
+            "in_reply_to_for_export": "<a>bu mesaja</a> yanıt olarak"
+        },
+        "scalar_starter_link": {
+            "dialog_description": "Hesabınızı %(integrationsUrl)s ile kullanmak üzere doğrulayabilmeniz için üçüncü taraf bir siteye götürülmek üzeresiniz. Devam etmek istiyor musunuz ?",
+            "dialog_title": "Entegrasyon ekleyin"
+        },
+        "self_redaction": "Mesaj silindi",
+        "send_state_encrypting": "Mesajınız şifreleniyor...",
+        "send_state_failed": "Gönderilemedi",
+        "send_state_sending": "Mesajınız gönderiliyor...",
+        "send_state_sent": "Mesajınız gönderildi",
+        "summary": {
+            "banned": {
+                "other": "%(count)s kez yasaklandı",
+                "one": "yasaklandı"
+            },
+            "banned_multiple": {
+                "other": "%(count)s kez yasaklandı",
+                "one": "yasaklandı"
+            },
+            "changed_avatar": {
+                "one": "%(oneUser)s profil resmini değiştirdi",
+                "other": "%(oneUser)s profil resmini %(count)s kez değiştirdi"
+            },
+            "changed_avatar_multiple": {
+                "one": "%(severalUsers)s profil resmini değiştirdi",
+                "other": "%(severalUsers)s profil resmini %(count)s kez değiştirdi"
+            },
+            "changed_name": {
+                "one": "%(oneUser)s ismini değiştirdi",
+                "other": "%(oneUser)s ismini %(count)s kez değiştirdi"
+            },
+            "changed_name_multiple": {
+                "one": "%(severalUsers)s isimlerini değiştrtiler",
+                "other": "%(severalUsers)s kullanıcıları isimlerini %(count)s kez değiştirdiler"
+            },
+            "format": "%(nameList)s%(transitionList)s",
+            "hidden_event": {
+                "one": "%(oneUser)s gizli bir mesaj gönderdi",
+                "other": "%(oneUser)s, %(count)s gizli mesaj gönderdi"
+            },
+            "hidden_event_multiple": {
+                "one": "%(severalUsers)s gizli bir mesaj gönderdi",
+                "other": "%(severalUsers)s, %(count)s gizli mesaj gönderdi"
+            },
+            "invite_withdrawn": {
+                "other": "%(oneUser)s davetini %(count)s kez geri çekti",
+                "one": "%(oneUser)s davetini geri çekti"
+            },
+            "invite_withdrawn_multiple": {
+                "one": "%(severalUsers)s davetlerini geri çekti"
+            },
+            "invited": {
+                "other": "%(count)s kez davet edildi",
+                "one": "davet edildi"
+            },
+            "invited_multiple": {
+                "other": "%(count)s kez davet edildi",
+                "one": "davet edildi"
+            },
+            "joined": {
+                "other": "%(oneUser)s %(count)s kez katıldı",
+                "one": "%(oneUser)s katıldı"
+            },
+            "joined_and_left": {
+                "one": "%(oneUser)s katıldı ve ayrıldı",
+                "other": "%(oneUser)s %(count)s kez katıldı ve ayrıldı"
+            },
+            "joined_and_left_multiple": {
+                "one": "%(severalUsers)s katıldı ve ayrıldı",
+                "other": "%(severalUsers)s %(count)s kez katılıp ve ayrıldı"
+            },
+            "joined_multiple": {
+                "other": "%(severalUsers)s %(count)s kez katıldı",
+                "one": "%(severalUsers)s katıldı"
+            },
+            "kicked": {
+                "one": "atıldı",
+                "other": "%(count)s kez atıldı"
+            },
+            "kicked_multiple": {
+                "one": "atıldı",
+                "other": "%(count)s kez atıldı"
+            },
+            "left": {
+                "one": "%(oneUser)s ayrıldı",
+                "other": "%(oneUser)s %(count)s kez ayrıldı"
+            },
+            "left_multiple": {
+                "one": "%(severalUsers)s kullanıcı ayrıldı",
+                "other": "%(severalUsers)s, %(count)s kez ayrıldı"
+            },
+            "no_change": {
+                "other": "%(oneUser)s %(count)s kez değişiklik yapmadı",
+                "one": "%(oneUser)s değişiklik yapmadı"
+            },
+            "no_change_multiple": {
+                "one": "%(severalUsers)s değişiklik yapmadı",
+                "other": "%(severalUsers)s %(count)s kez hiç bir değişiklik yapmadı"
+            },
+            "pinned_events": {
+                "one": "%(oneUser)s odanın <a>sabitlenmiş mesajlarını</a> değiştirdi",
+                "other": "%(oneUser)s odanın <a>sabitlenmiş mesajlarını</a> %(count)s kez değiştirdi"
+            },
+            "pinned_events_multiple": {
+                "one": "%(severalUsers)s odanın <a>sabitlenmiş mesajlarını</a> değiştirdi",
+                "other": "%(severalUsers)s odanın <a>sabitlenmiş mesajlarını</a> %(count)s kez değiştirdi"
+            },
+            "redacted": {
+                "one": "%(oneUser)s bir mesajı sildi",
+                "other": "%(oneUser)s, %(count)s mesaj sildi"
+            },
+            "redacted_multiple": {
+                "one": "%(severalUsers)s bir mesajı sildi",
+                "other": "%(severalUsers)s, %(count)s mesaj sildi"
+            },
+            "rejected_invite": {
+                "one": "%(oneUser)s davetlerini reddetti"
+            },
+            "rejected_invite_multiple": {
+                "other": "%(severalUsers)s %(count)s kez davetlerini reddetti",
+                "one": "%(severalUsers)s davetlerini reddetti"
+            },
+            "rejoined": {
+                "one": "%(oneUser)s ayrıldı ve yeniden katıldı"
+            },
+            "rejoined_multiple": {
+                "one": "%(severalUsers)s ayrıldı ve yeniden katıldı"
+            },
+            "server_acls": {
+                "one": "%(oneUser)s sunucu ACL'lerini değiştirdi",
+                "other": "%(oneUser)s sunucu ACL'lerini %(count)s kez değiştirdi"
+            },
+            "server_acls_multiple": {
+                "one": "%(severalUsers)s sunucu ACL'lerini değiştirdi",
+                "other": "%(severalUsers)s sunucu ACL'lerini %(count)s kez değiştirdi"
+            },
+            "unbanned": {
+                "other": "%(count)s kez yasak kaldırıldı",
+                "one": "yasak kaldırıldı"
+            },
+            "unbanned_multiple": {
+                "other": "%(count)s kez yasak kaldırıldı",
+                "one": "yasak kaldırıldı"
+            }
+        },
+        "thread_info_basic": "Bir konudan",
+        "typing_indicator": {
+            "more_users": {
+                "one": "%(names)s ve bir diğeri yazıyor…",
+                "other": "%(names)s ve diğer %(count)s kişi yazıyor…"
+            },
+            "one_user": "%(displayName)s yazıyor…",
+            "two_users": "%(names)s ve %(lastPerson)s yazıyor…"
+        },
+        "undecryptable_tooltip": "Bu mesajın şifresi çözülemedi",
+        "url_preview": {
+            "close": "Önizlemeyi kapat",
+            "show_n_more": {
+                "one": "%(count)s diğer önizlemeyi göster",
+                "other": "%(count)s diğer önizlemeyi göster"
+            }
+        }
+    },
+    "truncated_list_n_more": {
+        "other": "ve %(count)s kez daha..."
+    },
+    "unsupported_browser": {
+        "description": "Devam ederseniz, bazı özellikler çalışmayı durdurabilir ve gelecekte veri kaybetme riskiniz vardır. %(brand)s kullanmaya devam etmek için tarayıcınızı güncelleyin.",
+        "title": "%(brand)s bu tarayıcıyı desteklemiyor"
+    },
+    "unsupported_server_description": "Bu sunucu Matrix'in eski bir sürümünü kullanıyor. %(brand)s hatasız kullanmak için Matrix'i %(version)s yükseltin.",
+    "unsupported_server_title": "Sunucunuz desteklenmiyor",
+    "update": {
+        "changelog": "Değişiklikler",
+        "check_action": "Güncelleme kontrolü",
+        "checking": "Güncelleme kontrol ediliyor...",
+        "downloading": "Güncelleme indiriliyor...",
+        "error_encountered": "Hata oluştu (%(errorDetail)s).",
+        "error_unable_load_commit": "İşleme ayrıntısı yüklenemedi: %(msg)s",
+        "new_version_available": "Yeni sürüm mevcut: <a> Şimdi güncelle.</a>",
+        "no_update": "Güncelleme yok.",
+        "release_notes_toast_title": "Yenilikler",
+        "see_changes_button": "Neler yeni?",
+        "toast_description": "%(brand)s 'in yeni versiyonu hazır",
+        "toast_title": "%(brand)s 'i güncelle",
+        "unavailable": "Kullanım dışı"
+    },
+    "update_room_access_modal": {
+        "description": "Paylaşım bağlantısı oluşturmak için, misafirlerin bu odaya katılmasına izin vermeniz gerekir. Bu, odayı daha az güvenli hale getirebilir. Aramayı bitirdiğinizde, odayı tekrar özel yapabilirsiniz.",
+        "dont_change_description": "Alternatif olarak görüşmeyi ayrı bir odada da yapabilirsiniz.",
+        "no_change": "Erişim düzeyini değiştirmek istemiyorum.",
+        "title": "Oda erişim seviyesini değiştir"
+    },
+    "upload_failed_generic": "%(fileName)s dosyası için yükleme başarısız.",
+    "upload_failed_size": "%(fileName)s dosyası anasunucunun yükleme boyutu limitini aşıyor",
+    "upload_failed_title": "Yükleme Başarısız",
+    "upload_file": {
+        "cancel_all_button": "Tümünü iptal et",
+        "error_file_too_large": "Bu dosya yüklemek için <b>çok büyük</b>. Dosya boyutu sınırı %(limit)s ancak bu dosya %(sizeOfThisFile)s.",
+        "error_files_too_large": "Bu dosyalar yükleme için <b>çok büyük</b>. Dosya boyut limiti %(limit)s.",
+        "error_some_files_too_large": "Bazı dosyalar yükleme için <b>çok büyük</b>. Dosya boyutu limiti %(limit)s.",
+        "error_title": "Yükleme Hatası",
+        "not_image": "Seçtiğiniz dosya geçerli bir resim dosyası değil.",
+        "title": "Dosyaları yükle",
+        "title_progress": "Dosyaları yükle (%(current)s / %(total)s)",
+        "upload_all_button": "Hepsini yükle",
+        "upload_n_others_button": {
+            "other": "%(count)s diğer dosyaları yükle",
+            "one": "%(count)s dosyayı sağla"
+        }
+    },
+    "user_info": {
+        "admin_tools_section": "Admin Araçları",
+        "ban_button_room": "Odadan yasakla",
+        "ban_button_space": "Alandan yasakla",
+        "ban_room_confirm_title": "%(roomName)s odasından yasakla",
+        "ban_space_everything": "Yasaklayabileceğim her yerden yasakla",
+        "ban_space_specific": "Yasaklayabileceğim her yerden yasakla",
+        "deactivate_confirm_action": "Kullanıcıyı pasifleştir",
+        "deactivate_confirm_description": "Bu kullanıcı etkisizleştirmek onu bir daha oturum açmasını engeller.Ek olarak da bulundukları bütün odalardan atılırlar. Bu eylem geri dönüştürülebilir. Bu kullanıcıyı etkisizleştirmek istediğinize emin misiniz?",
+        "deactivate_confirm_title": "Kullanıcı devre dışı bırakılsın mı?",
+        "demote_button": "Rütbe Düşür",
+        "demote_self_confirm_description_space": "Rütbenizi düşürdüğünüz için bu değişikliği geri alamazsınız, eğer alandaki son ayrıcalıklı kullanıcı sizseniz ayrıcalıkları yeniden kazanmanız mümkün olmayacaktır.",
+        "demote_self_confirm_room": "Rütbenizi düşürdüğünüz için bu değişikliği geri alamazsınız, eğer odadaki son ayrıcalıklı kullanıcı sizseniz ayrıcalıkları yeniden kazanmanız mümkün olmayacaktır.",
+        "demote_self_confirm_title": "Rütbeni düşür?",
+        "disinvite_button_room": "Odaya daveti iptal et",
+        "disinvite_button_room_name": "%(roomName)s odasına daveti iptal et",
+        "disinvite_button_space": "Alan davetini iptal et",
+        "error_ban_user": "Kullanıcı yasaklanamadı",
+        "error_deactivate": "Kullanıcı devre dışı bırakılamadı",
+        "error_kicking_user": "Kullanıcı çıkarılamadı",
+        "error_mute_user": "Kullanıcı sessize alınamadı",
+        "error_revoke_3pid_invite_description": "Davet geri çekilemiyor. Sunucu geçici bir problem yaşıyor olabilir yada daveti geri çekmek için gerekli izinlere sahip değilsin.",
+        "error_revoke_3pid_invite_title": "Daveti iptal etme başarısız oldu",
+        "ignore_button": "Görmezden gel",
+        "ignore_confirm_description": "Bu kullanıcıdan gelen tüm mesajlar ve davetler gizlenecek. Bunları görmezden gelmek istediğinizden emin misiniz?",
+        "ignore_confirm_title": "Görmezden gel %(user)s",
+        "invited_by": "%(sender)s tarafından davet edildi",
+        "jump_to_rr_button": "Okundu bilgisine atla",
+        "kick_button_room": "Odadan çıkar",
+        "kick_button_room_name": "%(roomName)s odasından çıkar",
+        "kick_button_space": "Alandan kaldır",
+        "kick_button_space_everything": "Çıkarabileceğim her yerden çıkar",
+        "kick_space_specific": "Çıkarabileceğim belirli yerlerden çıkar",
+        "kick_space_warning": "Yöneticisi olmadığınız her şeye yine erişebilecekler.",
+        "promote_warning": "Kullanıcıyı sizinle aynı güç seviyesine yükseltirken , bu değişikliği geri alamazsınız.",
+        "redact": {
+            "confirm_button": {
+                "other": "%(count)s mesajı sil",
+                "one": "1 mesajı sil"
+            },
+            "confirm_description_1": {
+                "one": "%(user)s kullanıcısının %(count)s mesajını kaldırmak üzeresiniz. Bu, konuşmadaki herkes için mesajları kaldıracaktır. Devam etmek istiyor musunuz?",
+                "other": "%(user)s kullanıcısının %(count)s mesajını kaldırmak üzeresiniz. Bu, konuşmadaki herkes için mesajları kaldıracaktır. Devam etmek istiyor musunuz?"
+            },
+            "confirm_description_2": "Çok sayıda ileti için bu biraz sürebilir. Lütfen bu sürede kullandığınız istemciyi yenilemeyin.",
+            "confirm_keep_state_explainer": "Bu kullanıcıyla ilgili sistem mesajlarını da kaldırmak istiyorsanız işareti kaldırın (örn. üyelik değişikliği, profil değişikliği...)",
+            "confirm_keep_state_label": "Sistem mesajlarını koru",
+            "confirm_title": "%(user)s kullanıcısından en son iletileri kaldır",
+            "no_recent_messages_description": "Daha önceden kalma iletilerin var olup olmadığını kontrol etmek için zaman çizelgesinde yukarı doğru kaydırın.",
+            "no_recent_messages_title": "%(user)s kullanıcısın hiç yeni ileti yok"
+        },
+        "redact_button": "Mesajları kaldır",
+        "revoke_invite": "Davet geri çekildi",
+        "room_encrypted": "Bu odadaki iletiler uçtan uca şifrelenmiştir.",
+        "room_encrypted_detail": "İletileriniz şifreledir ve yalnızca sizde ve gönderdiğiniz kullanıcılarda iletileri açmak için anahtarlar vardır.",
+        "room_unencrypted": "Bu odadaki iletiler uçtan uca şifreli değildir.",
+        "room_unencrypted_detail": "Şifrelenmiş odalarda iletileriniz şifreledir ve yalnızca sizde ve gönderdiğiniz kullanıcılarda iletileri açmak için anahtarlar vardır.",
+        "send_message": "Mesaj gönder",
+        "share_button": "Profili paylaş",
+        "unban_button_room": "Odadan yasağı kaldır",
+        "unban_button_space": "Alandan yasağı kaldır",
+        "unban_room_confirm_title": "%(roomName)s yasağını kaldır",
+        "unban_space_everything": "Yasaklarını kaldırabileceğim her yerden yasaklarını kaldır",
+        "unban_space_specific": "Yasaklarını kaldırabileceğim belirli yerlerden yasaklarını kaldır",
+        "unban_space_warning": "Yönetici olmadığınız hiçbir şeye erişemezler.",
+        "unignore_button": "Görmezden Gelmeyi Kaldır",
+        "verification_unavailable": "Kullanıcı doğrulaması kullanılamıyor",
+        "verify_button": "Kullanıcıyı Doğrula",
+        "verify_explainer": "Ekstra güvenlik için, her iki cihazınızda da tek seferlik bir kodu kontrol ederek bu kullanıcıyı doğrulayın."
+    },
+    "user_menu": {
+        "link_new_device": "Yeni cihaz bağla",
+        "settings": "Tüm ayarlar",
+        "switch_theme_dark": "Karanlık Moda Geç",
+        "switch_theme_light": "Aydınlık Moda Geç"
+    },
+    "voip": {
+        "already_in_call": "Bu kişi zaten çağrıda",
+        "already_in_call_person": "Bu kişi ile halihazırda çağrıdasınız.",
+        "answered_elsewhere": "Arama başka bir yerde yanıtlandı",
+        "answered_elsewhere_description": "Arama başka bir cihazda cevaplandı.",
+        "call_failed": "Arama Başarısız",
+        "call_failed_description": "Arama yapılamadı",
+        "call_failed_media": "Kameraya yada mikrofona erişilemediği için arama yapılamadı. Şunu kontrol edin:",
+        "call_failed_media_applications": "Kamerayı başka bir uygulama kullanmıyor",
+        "call_failed_media_connected": "Mikrofon ve kamera takılımı ve doğru şekilde ayarlanmış mı",
+        "call_failed_media_permissions": "Kamerayı kullanmak için izin gerekiyor",
+        "call_failed_microphone": "Mikrofona erişilemediği için arama yapılamadı. Mikrofonun takılı ve doğru şekilde ayarlandığından emin olun.",
+        "call_held": "%(peerName)s aramayı duraklattı",
+        "call_held_resume": "Aramayı beklettiniz <a>Devam Ettir</a>",
+        "call_held_switch": "Aramayı beklettiniz <a>Değiştir</a>",
+        "call_toast_unknown_room": "Bilinmeyen oda",
+        "camera_disabled": "Kameranız kapalı",
+        "camera_enabled": "Kameranız hâlâ etkin",
+        "cannot_call_yourself_description": "Kendinizle görüşme yapamazsınız .",
+        "close_lobby": "Lobiyi kapat",
+        "connecting": "Bağlanıyor",
+        "connection_lost": "Sunucuyla bağlantı kesildi",
+        "connection_lost_description": "Sunucuyla bağlantınız olmadan arama yapamazsınız.",
+        "consulting": "%(transferTarget)s ile görüşülüyor. <a>Şuraya aktar %(transferee)s</a>",
+        "default_device": "Varsayılan Cihaz",
+        "dial": "Ara",
+        "dialpad": "Tuş takımı",
+        "disable_camera": "Kamerayı kapat",
+        "disable_microphone": "Mikrofonu sessize al",
+        "disabled_no_one_here": "Burada arayacak kimse yok.",
+        "disabled_no_perms_start_video_call": "Görüntülü arama başlatmak için izniniz yok",
+        "disabled_no_perms_start_voice_call": "Sesli arama başlatmak için izniniz yok",
+        "disabled_ongoing_call": "Devam eden çağrı",
+        "element_call": "Element Çağrısı",
+        "enable_camera": "Kamerayı aç",
+        "enable_microphone": "Mikrofonun sesini aç",
+        "expand": "Çağrıya geri dön",
+        "get_call_link": "Arama bağlantısını paylaş",
+        "hangup": "Kapatma",
+        "hide_sidebar_button": "Kenar çubuğunu gizle",
+        "input_devices": "Giriş aygıtları",
+        "jitsi_call": "Jitsi Konferansı",
+        "join_button_tooltip_call_full": "Üzgünüz - bu çağrı şu anda dolu",
+        "legacy_call": "Eski Tip Çağrı",
+        "maximise": "Ekranı kapla",
+        "maximise_call": "Arama ekranı kapla",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Konferanslar"
+        },
+        "minimise_call": "Arama simge durumuna",
+        "misconfigured_server": "Hatalı yapılandırılmış sunucu nedeniyle arama başarısız",
+        "misconfigured_server_description": "Çağrıların sağlıklı bir şekide yapılabilmesi için lütfen anasunucunuzun (<code>%(homeserverDomain)s</code>) yöneticisinden bir TURN sunucusu yapılandırmasını isteyin.",
+        "misconfigured_server_fallback": "Alternatif olarak, <server/> adresindeki genel sunucuyu kullanmayı deneyebilirsiniz, ancak bu o kadar güvenilir olmayacak ve IP adresinizi o sunucuyla paylaşacaktır. Bunu Ayarlar'dan da yönetebilirsiniz.",
+        "misconfigured_server_fallback_accept": "%(server)s kullanmayı deneyin",
+        "more_button": "Daha fazla",
+        "msisdn_lookup_failed": "Telefon numarasına bakılamadı",
+        "msisdn_lookup_failed_description": "Telefon numarasına bakarken bir hata oluştu",
+        "msisdn_transfer_failed": "Arama Karşıdaki kişiye aktarılamıyor",
+        "n_people_joined": {
+            "one": "%(count)s kişi katıldı",
+            "other": "%(count)s kişi katıldı"
+        },
+        "no_audio_input_description": "Cihazınızda bir mikrofon bulamadık. Lütfen ayarlarınızı kontrol edin ve tekrar deneyin.",
+        "no_audio_input_title": "Mikrofon bulunamadı",
+        "no_media_perms_description": "%(brand)s'un mikrofonunuza / web kameranıza el le erişmesine izin vermeniz gerekebilir",
+        "no_media_perms_title": "Medya izinleri yok",
+        "no_permission_conference": "İzin Gerekli",
+        "no_permission_conference_description": "Bu odada bir konferans başlatmak için izniniz yok",
+        "on_hold": "%(name)s beklemede",
+        "output_devices": "Çıkış aygıtları",
+        "screenshare_monitor": "Tüm ekranı paylaş",
+        "screenshare_title": "İçerik paylaş",
+        "screenshare_window": "Uygulama penceresi",
+        "show_sidebar_button": "Kenar çubuğunu göster",
+        "silence": "Sessiz çağrı",
+        "silenced": "Bildirimler susturuldu",
+        "start_screenshare": "Ekranınızı paylaşmaya başlayın",
+        "stop_screenshare": "Ekran paylaşmayı durdur",
+        "too_many_calls": "Çok fazla arama",
+        "too_many_calls_description": "Maksimum eşzamanlı arama sayısına ulaştınız.",
+        "transfer_consult_first_label": "Önce danış",
+        "transfer_failed": "Aktarma Başarısız",
+        "transfer_failed_description": "Arama aktarılırken hata oluştu",
+        "unable_to_access_audio_input_description": "Mikrofonunuza erişemedik. Lütfen tarayıcı ayarlarınızı kontrol edin ve tekrar deneyin.",
+        "unable_to_access_audio_input_title": "Mikrofonunuza erişilemiyor",
+        "unable_to_access_media": "Kameraya / mikrofona erişilemedi",
+        "unable_to_access_microphone": "Mikrofona erişilemiyor",
+        "unknown_caller": "Bilinmeyen arayan",
+        "unknown_person": "bilinmeyen kişi",
+        "unsilence": "Ses açık",
+        "unsupported": "Aramalar desteklenmiyor",
+        "unsupported_browser": "Bu tarayıcıda arama yapamazsınız.",
+        "user_busy": "Kullanıcı Meşgul",
+        "user_busy_description": "Aradığınız kullanıcı meşgul.",
+        "user_is_presenting": "%(sharerName)s sunum yapıyor",
+        "video_call": "Görüntülü arama",
+        "video_call_started": "Görüntülü arama başlatıldı",
+        "video_call_using": "Görüntülü arama kullanılıyor:",
+        "voice_call": "Sesli arama",
+        "you_are_presenting": "Sunum yapıyorsunuz"
+    },
+    "web_default_device_name": "%(appName)s: %(browserName)s %(osName)s",
+    "welcome_to_element": "Element'e Hoş Geldiniz",
+    "widget": {
+        "added_by": "Widget ekleyen",
+        "capabilities_dialog": {
+            "content_starting_text": "Bu widget şunları istiyor:",
+            "decline_all_permission": "Tümünü Reddet",
+            "remember_Selection": "Bu görsel bileşen işin seçimimi hatırla",
+            "title": "Widget izinlerini onayla"
+        },
+        "capability": {
+            "always_on_screen_generic": "Uygulama çalışırken lütfen başka uygulamaya geçmeyin",
+            "always_on_screen_viewing_another_room": "Başka bir odayı görüntülerken, çalışırken ekranınızda kalın",
+            "any_room": "Yukarıdakiler, ancak katıldığınız veya davet edildiğiniz odalarda da",
+            "byline_empty_state_key": "boş durum anahtarı ile",
+            "byline_state_key": "%(stateKey)s durum anahtarı ile",
+            "capability": "<b>%(capability)s</b> kabiliyet",
+            "change_avatar_active_room": "Aktif odanın avatarını değiştir",
+            "change_avatar_this_room": "Bu odanın avatarını değiştir",
+            "change_name_active_room": "Aktif odanızın ismini değiştirin",
+            "change_name_this_room": "Bu odanın ismini değiştirin",
+            "change_topic_active_room": "Aktif odanızın konusunu değiştirin",
+            "change_topic_this_room": "Bu odanın konusunu değiştirin",
+            "receive_membership_active_room": "İnsanların odanıza ne zaman katıldığını, ayrıldığını veya davet edildiğini görün",
+            "receive_membership_this_room": "İnsanların bu odaya ne zaman katıldığını, ayrıldığını veya davet edildiğini görün",
+            "remove_ban_invite_leave_active_room": "Kullanıcıları şuan ki odadan çıkarın, yasaklayın veya davet edin ve ayrılın",
+            "remove_ban_invite_leave_this_room": "Kullanıcıları bu odadan çıkarın, yasaklayın veya davet edin ve ayrılın",
+            "see_avatar_change_active_room": "Aktif odanızdaki profil fotoğrafı değişikliklerini görün",
+            "see_avatar_change_this_room": "Bu odadaki avatar değişikliklerini görün",
+            "see_event_type_sent_active_room": "Aktif odanıza gönderilen <b>%(eventType)s</b> etkinlikleri gör",
+            "see_event_type_sent_this_room": "Bu odaya gönderilen <b>%(eventType)s</b> türü etkinlikleri gör",
+            "see_images_sent_active_room": "Aktif odanıza gönderilen fotoğrafları görün",
+            "see_images_sent_this_room": "Bu odaya gönderilen resimleri gör",
+            "see_messages_sent_active_room": "Aktif odanıza gönderilen mesajları görün",
+            "see_messages_sent_this_room": "Bu odaya gönderilen mesajları görün",
+            "see_msgtype_sent_active_room": "Aktif odanıza gönderilen <b>%(msgtype)s</b> mesajları görün",
+            "see_msgtype_sent_this_room": "Bu odada gönderilen <b>%(msgtype)s</b> mesajlara bak",
+            "see_name_change_active_room": "Aktif odanızdaki isim değişikliklerini görün",
+            "see_name_change_this_room": "Bu odadaki isim değişikliklerini görün",
+            "see_sent_emotes_active_room": "Aktif odanıza gönderilen ifadeleri görün",
+            "see_sent_emotes_this_room": "Bu odaya gönderilen ifadeleri görün",
+            "see_sent_files_active_room": "Aktif odanıza gönderilen genel dosyaları görün",
+            "see_sent_files_this_room": "Bu odaya gönderilen genel dosyaları gör",
+            "see_sticker_posted_active_room": "Aktif odanızda birisi çıkartma paylaştığında görün",
+            "see_sticker_posted_this_room": "Bu odada çıkartma paylaşıldığında görün",
+            "see_text_messages_sent_active_room": "Aktif odanıza gönderilen metin mesajlarını görün",
+            "see_text_messages_sent_this_room": "Bu odaya gönderilen metin mesajlarını gör",
+            "see_topic_change_active_room": "Bu odada konu başlığı değişince değişiklikleri görün",
+            "see_topic_change_this_room": "Bu odada konu başlığı değişince değişiklikleri görün",
+            "see_videos_sent_active_room": "Aktif odana gönderilen videoları gör",
+            "see_videos_sent_this_room": "Bu odaya gönderilen videoları gör",
+            "send_emotes_active_room": "Bu araç sizin adınıza bu odaya ileti gönderir",
+            "send_emotes_this_room": "Bu araç odaya sizin adınıza ifade gönderir",
+            "send_event_type_active_room": "Bu araç odanıza sizin adınıza <b>%(eventType)s</b> türü etkinlik gönderir",
+            "send_event_type_this_room": "Bu araç odaya sizin adınıza <b>%(eventType)s</b> türü etkinlik gönderir",
+            "send_files_active_room": "Widget aktif odanıza sizin adınıza genel dosyalar göndersin",
+            "send_files_this_room": "Widget sizin adınıza bu odaya genel dosyalar göndersin",
+            "send_images_active_room": "Widget aktif odanıza sizin adınıza resim göndersin",
+            "send_images_this_room": "Bu araç odaya sizin adınıza resim gönderir",
+            "send_messages_active_room": "Bu araç odanıza sizin adınıza ileti gönderir",
+            "send_messages_this_room": "Bu Araç sizin adınıza mesaj gönderir",
+            "send_msgtype_active_room": "Widget sizin adınıza <b>%(msgtype)s</b> mesajlar göndersin",
+            "send_msgtype_this_room": "Bu odadayken <b>%(msgtype)s</b> mesajlar gönder",
+            "send_stickers_active_room": "Aktif odanıza çıkartma gönderin",
+            "send_stickers_active_room_as_you": "Widget aktif odanıza sizin adınıza çıkartma göndersin",
+            "send_stickers_this_room": "Bu odaya çıkartma gönderin",
+            "send_stickers_this_room_as_you": "Widget bu odaya sizin adınıza çıkartma göndersin",
+            "send_text_messages_active_room": "Bu araç sizin adınıza bu odaya mesaj gönderir",
+            "send_text_messages_this_room": "Bu araç odaya sizin adınıza metin iletisi gönderir",
+            "send_videos_active_room": "Bu araç odaya sizin adınıza video gönderir",
+            "send_videos_this_room": "Bu araç odaya sizin adınıza video gönderir",
+            "specific_room": "Yukarıdakiler, ama <Room />'da da",
+            "switch_room": "Görüntülediğiniz odayı değiştirin",
+            "switch_room_message_user": "Görüntülediğiniz odayı, mesajı veya kullanıcıyı değiştirin"
+        },
+        "close_to_view_right_panel": "Bu panelde görüntülemek için bu widget'ı kapatın",
+        "context_menu": {
+            "delete": "Görsel bileşen sil",
+            "delete_warning": "Bir widget silindiğinde, widget bu odadaki tüm kullanıcılar için kaldırılır. Bu widget'ı silmek istediğinizden emin misiniz?",
+            "move_left": "Sola taşı",
+            "move_right": "Sağa taşı",
+            "remove": "Herkes için sil",
+            "revoke": "İzinleri iptal et",
+            "screenshot": "Resim çek",
+            "start_audio_stream": "Ses akışını başlat"
+        },
+        "cookie_warning": "Bu widget çerezleri kullanabilir.",
+        "error_hangup_description": "Aramayla bağlantınız kesildi. (Hata: %(message)s)",
+        "error_hangup_title": "Bağlantı Kesildi",
+        "error_loading": "Widget yüklenirken hata oluştu",
+        "error_mixed_content": "Hata - Karışık içerik",
+        "error_need_invite_permission": "Bunu yapmak için kullanıcıları davet etmeye ihtiyacınız var.",
+        "error_need_kick_permission": "Bunu yapmak için kullanıcıları atabilmeniz gerekir.",
+        "error_need_to_be_logged_in": "Oturum açmanız gerekiyor.",
+        "error_unable_start_audio_stream_description": "Ses akışı başlatılamıyor.",
+        "error_unable_start_audio_stream_title": "Canlı yayın başlatılamadı",
+        "modal_data_warning": "Aşağıdaki veriler şu kişilerle paylaşılmaktadır:%(widgetDomain)s",
+        "modal_title_default": "Modal Widget",
+        "no_name": "Bilinmeyen uygulama",
+        "open_id_permissions_dialog": {
+            "remember_selection": "Hatırla",
+            "starting_text": "Widget kullanıcı kimliğinizi doğrulayacak, ancak sizin için işlem yapamayacaktır:",
+            "title": "Bu widget'ın kimliğinizi doğrulamasına izin verin"
+        },
+        "popout": "Görsel bileşeni göster",
+        "set_room_layout": "Oda düzenimi herkes için ayarla",
+        "shared_data_avatar": "Profil resminizin URL'si",
+        "shared_data_device_id": "Cihaz kimliği",
+        "shared_data_lang": "Diliniz",
+        "shared_data_mxid": "Kullanıcı ID",
+        "shared_data_name": "Ekran adınız",
+        "shared_data_room_id": "Oda ID",
+        "shared_data_theme": "Temanız",
+        "shared_data_url": "%(brand)s Linki",
+        "shared_data_warning": "Bu widget'ı kullanmak <helpIcon /> verilerini %(widgetDomain)s ile paylaşabilir.",
+        "shared_data_warning_im": "Bu widget'ı kullanmak <helpIcon /> verilerini %(widgetDomain)s ve entegrasyon yöneticinizle paylaşabilir.",
+        "shared_data_widget_id": "Görsel Bileşen ID si",
+        "unencrypted_warning": "Widget'lar mesaj şifreleme kullanmaz.",
+        "unmaximise": "Büyütmeyi kaldır",
+        "unpin_to_view_right_panel": "Bu panelde görüntülemek için bu widget'ın sabitlemesini kaldırın"
+    },
+    "zxcvbn": {
+        "suggestions": {
+            "allUppercase": "Bütün harflerin büyük olmasıyla tümünün küçük olması tahmin edilmesi bakımından hemen hemen aynı kolaylıktadır",
+            "anotherWord": "Bir iki kelime daha ekleyin. Yaygın olmayan kelimeler daha iyi olur.",
+            "associatedYears": "Sizle ilişkili yıllardan kaçının",
+            "capitalization": "Baş harfi büyük yapmak size pek yardımcı olmaz",
+            "dates": "Sizle ilişkili tarihler ve yıllardan kaçının",
+            "l33t": "Tahmin edilebilir harf değişimleri örneğin 'a' yerine '@' pek yardımcı olmuyor",
+            "longerKeyboardPattern": "Daha karmaşık ve uzun bir klavye deseni kullan",
+            "noNeed": "Semboller, sayılar yada büyük harflere gerek yok",
+            "pwned": "Bu şifreyi başka bir yerde kullanıyorsanız değiştirmelisiniz.",
+            "recentYears": "Son yıllardan kaçının",
+            "repeated": "Tekrarlanan kelimeler ve karakterlerden kaçının",
+            "reverseWords": "Ters kelimeler tahmin için çok zor değil",
+            "sequences": "Sekanslardan kaçının",
+            "useWords": "Bir kaç kelime kullanın ve genel ifadelerden kaçının"
+        },
+        "warnings": {
+            "common": "Bu oldukça yaygın parola",
+            "commonNames": "Yaygın isimleri ve soyisimleri tahmin etmek oldukça kolay",
+            "dates": "Tarihler sıklıkla tahmin için daha kolaydır",
+            "extendedRepeat": "“abcabcabc” gibi tekrarlar “abc” yi tahmin etmekten çok az daha zor olur",
+            "keyPattern": "Kısa klavye desenleri kolay tahmin edilir",
+            "namesByThemselves": "Adlar ve soyadlar kendi kendilerine tahmin için kolaydır",
+            "pwned": "Şifreniz internetteki bir veri ihlali sonucu ifşa oldu.",
+            "recentYears": "Güncel yılların tahmini kolaydır",
+            "sequences": "abc veya 6543 gibi diziler tahmin için oldukça kolaydır",
+            "similarToCommon": "Bu yaygınca kullanılan bir parolaya benziyor",
+            "simpleRepeat": "“aaa” gibi tekrarlar tahmin için oldukça kolay",
+            "straightRow": "Aynı klavye satırındaki ardışık tuşlar kolay tahmin edilir",
+            "topHundred": "Bu bir top-100 yaygın parola",
+            "topTen": "Bu bir top-10 yaygın parola",
+            "userInputs": "Hiçbir kişisel veya sayfayla ilgili veri bulunmamalıdır.",
+            "wordByItself": "Kelime zaten kolay tahmin edilir"
+        }
+    }
+}
diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json
index 7522d01e55932127b0f9a6ab86d617fb692ed6cd..614cfca2991655a55fb140e5087d059a33d518b5 100644
--- a/src/i18n/strings/uk.json
+++ b/src/i18n/strings/uk.json
@@ -1,6 +1,8 @@
 {
     "a11y": {
+        "emoji_picker": "Панель емоджі",
         "jump_first_invite": "Перейти до першого запрошення.",
+        "message_composer": "Редактор повідомлень",
         "n_unread_messages": {
             "other": "%(count)s непрочитаних повідомлень.",
             "one": "1 непрочитане повідомлення."
@@ -9,7 +11,22 @@
             "other": "%(count)s непрочитаних повідомлень включно зі згадками.",
             "one": "1 непрочитана згадка."
         },
+        "recent_rooms": "Недавні кімнати",
+        "room_messsage_not_sent": "Відкрити кімнату %(roomName)s з не надісланим повідомленням.",
+        "room_n_unread_invite": "Відкрити запрошення кімнати %(roomName)s.",
+        "room_n_unread_messages": {
+            "one": "Відкрити кімнату %(roomName)s з 1 непрочитаним повідомленням.",
+            "few": "Відкрити кімнату %(roomName)s з %(count)s непрочитаними повідомленнями.",
+            "many": "Відкрити кімнату %(roomName)s з %(count)s непрочитаними повідомленнями."
+        },
+        "room_n_unread_messages_mentions": {
+            "one": "Відкрити кімнату %(roomName)s з 1 непрочитаною згадкою.",
+            "few": "Відкрити кімнату %(roomName)s з %(count)s непрочитаними згадками.",
+            "many": "Відкрити кімнату %(roomName)s з %(count)s непрочитаними згадками."
+        },
         "room_name": "Кімната %(name)s",
+        "room_status_bar": "Панель стану кімнати",
+        "seek_bar_label": "Панель гортання аудіо",
         "unread_messages": "Непрочитані повідомлення.",
         "user_menu": "Користувацьке меню"
     },
@@ -40,6 +57,8 @@
         "create_a_room": "Створити кімнату",
         "create_account": "Створити обліковий запис",
         "decline": "Відхилити",
+        "decline_and_block": "Відхилити та заблокувати",
+        "decline_invite": "Відхилити запрошення",
         "delete": "Видалити",
         "deny": "Відхилити",
         "disable": "Вимкнути",
@@ -59,6 +78,7 @@
         "go": "Уперед",
         "go_back": "Назад",
         "got_it": "Зрозуміло",
+        "hide": "Сховати",
         "hide_advanced": "Сховати розширені",
         "hold": "Утримувати",
         "ignore": "Ігнорувати",
@@ -75,12 +95,14 @@
         "maximise": "Розгорнути",
         "mention": "Згадати",
         "minimise": "Згорнути",
+        "new_message": "Нове повідомлення",
         "new_room": "Нова кімната",
         "new_video_room": "Нова відеокімната",
         "next": "Далі",
         "no": "НІ",
         "ok": "Гаразд",
         "open": "Відкрити",
+        "open_menu": "Відкрити меню",
         "pause": "Призупинити",
         "pin": "Кнопка",
         "play": "Відтворити",
@@ -89,13 +111,13 @@
         "react": "Відреагувати",
         "refresh": "Оновити",
         "register": "Зареєструватися",
-        "reject": "Відмовитись",
         "reload": "Перезавантажити",
         "remove": "Прибрати",
         "rename": "Перейменувати",
         "reply": "Відповісти",
         "reply_in_thread": "Відповісти у гілці",
         "report_content": "Поскаржитись на вміст",
+        "report_room": "Поскаржитися на кімнату",
         "resend": "Перенадіслати",
         "reset": "Скинути",
         "resume": "Продовжити",
@@ -105,6 +127,7 @@
         "save": "Зберегти",
         "search": "Пошук",
         "send_report": "Надіслати звіт",
+        "set_avatar": "Установити зображення профілю",
         "share": "Поділитись",
         "show": "Показати",
         "show_advanced": "Показати розширені",
@@ -113,7 +136,7 @@
         "sign_out": "Вийти",
         "skip": "Пропустити",
         "start": "Почати",
-        "start_chat": "Почати розмову",
+        "start_chat": "Почати бесіду",
         "start_new_chat": "Почати бесіду",
         "stop": "Припинити",
         "submit": "Надіслати",
@@ -128,6 +151,7 @@
         "update": "Оновити",
         "upgrade": "Поліпшити",
         "upload": "Вивантажити",
+        "upload_file": "Вивантажити файл",
         "verify": "Звірити",
         "view": "Перегляд",
         "view_all": "Переглянути всі",
@@ -164,6 +188,7 @@
         "autodiscovery_invalid_is_base_url": "Хибний base_url у m.identity_server",
         "autodiscovery_invalid_is_response": "Хибна відповідь самоналаштування сервера ідентифікації",
         "autodiscovery_invalid_json": "Хибний JSON",
+        "autodiscovery_no_well_known": "Файл .well-known JSON не знайдено",
         "autodiscovery_unexpected_error_hs": "Неочікувана помилка в налаштуваннях домашнього серверу",
         "autodiscovery_unexpected_error_is": "Незрозуміла помилка при розборі параметру сервера ідентифікації",
         "captcha_description": "Домашній сервер бажає впевнитися, що ви не робот.",
@@ -221,6 +246,7 @@
         },
         "misconfigured_body": "Попросіть адміністратора %(brand)s перевірити <a>конфігураційний файл</a> на наявність неправильних або повторюваних записів.",
         "misconfigured_title": "Ваш %(brand)s налаштовано неправильно",
+        "mobile_create_account_title": "Ви збираєтеся створити обліковий запис %(hsName)s",
         "msisdn_field_description": "Інші користувачі можуть запрошувати вас до кімнат за вашими контактними даними",
         "msisdn_field_label": "Телефон",
         "msisdn_field_number_invalid": "Цей номер телефону не правильний. Перевірте та повторіть спробу",
@@ -228,6 +254,7 @@
         "no_hs_url_provided": "URL адресу домашнього сервера не вказано",
         "oidc": {
             "error_title": "Нам не вдалося виконати вхід",
+            "generic_auth_error": "Щось пішло не так під час автентифікації. Перейдіть на сторінку входу та повторіть спробу.",
             "missing_or_invalid_stored_state": "Ми попросили браузер запам’ятати, який домашній сервер ви використовуєте, щоб дозволити вам увійти, але, на жаль, ваш браузер забув його. Перейдіть на сторінку входу та повторіть спробу."
         },
         "password_field_keep_going_prompt": "Продовжуйте…",
@@ -237,12 +264,40 @@
         "phone_label": "Телефон",
         "phone_optional_label": "Телефон (не обов'язково)",
         "qr_code_login": {
+            "check_code_explainer": "Це підтвердить, що з'єднання з іншим пристроєм захищене.",
+            "check_code_heading": "Введіть номер, показаний на іншому пристрої",
+            "check_code_input_label": "2-значний код",
+            "check_code_mismatch": "Цифри не збігаються",
             "completing_setup": "Завершення налаштування нового пристрою",
+            "error_etag_missing": "Сталася неочікувана помилка. Це може бути пов’язано з розширенням браузера, проксі-сервером або неправильною конфігурацією сервера.",
+            "error_expired": "Час входу минув. Повторіть спробу.",
+            "error_expired_title": "Вхід не було завершено вчасно",
+            "error_insecure_channel_detected": "Не вдалося встановити безпечне з'єднання з новим пристроєм. Ваші наявні пристрої все ще в безпеці, і вам не потрібно про них турбуватися.",
+            "error_insecure_channel_detected_instructions": "Що тепер?",
+            "error_insecure_channel_detected_instructions_1": "Спробуйте знову ввійти на іншому пристрої за допомогою QR-коду, якщо це проблема мережі",
+            "error_insecure_channel_detected_instructions_2": "Якщо ви зіткнулися з такою ж проблемою, спробуйте іншу мережу Wi-Fi або використовуйте мобільні дані замість Wi-Fi",
+            "error_insecure_channel_detected_instructions_3": "Якщо це не працює, увійдіть вручну",
+            "error_insecure_channel_detected_title": "З'єднання не захищене",
+            "error_other_device_already_signed_in": "Більше нічого робити не потрібно.",
+            "error_other_device_already_signed_in_title": "На вашому іншому пристрої вже виконано вхід",
             "error_rate_limited": "Забагато спроб за короткий час. Зачекайте трохи, перш ніж повторити спробу.",
-            "error_unexpected": "Виникла непередбачувана помилка.",
-            "scan_code_instruction": "Скануйте QR-код знизу своїм пристроєм, на якому ви вийшли.",
-            "scan_qr_code": "Скануйте QR-код",
+            "error_unexpected": "Сталася неочікувана помилка. Запит на підключення іншого пристрою скасовано.",
+            "error_unsupported_protocol": "Цей пристрій не підтримує вхід на інший пристрій за допомогою QR-коду.",
+            "error_unsupported_protocol_title": "Інший пристрій несумісний",
+            "error_user_cancelled": "Вхід було скасовано на іншому пристрої.",
+            "error_user_cancelled_title": "Запит на вхід скасовано",
+            "error_user_declined": "Ви або постачальник облікового запису відхилили запит на вхід.",
+            "error_user_declined_title": "Вхід відхилено",
+            "follow_remaining_instructions": "Дотримуйтесь подальших інструкцій",
+            "open_element_other_device": "Відкрийте %(brand)s на своєму іншому пристрої",
+            "point_the_camera": "Скануйте QR-код, показаний тут",
+            "scan_code_instruction": "Відскануйте QR-код іншим пристроєм",
+            "scan_qr_code": "Увійдіть за допомогою QR-коду",
+            "security_code": "Захисний код",
+            "security_code_prompt": "Якщо з'явиться запит, введіть наведений нижче код на іншому пристрої.",
             "select_qr_code": "Виберіть «%(scanQRCode)s»",
+            "unsupported_explainer": "Ваш постачальник облікового запису не підтримує вхід на новий пристрій за допомогою QR-коду.",
+            "unsupported_heading": "QR-код не підтримується",
             "waiting_for_device": "Очікування входу з пристрою"
         },
         "register_action": "Створити обліковий запис",
@@ -271,6 +326,7 @@
             "sign_out_other_devices": "Вийти на всіх пристроях"
         },
         "reset_password_action": "Скинути пароль",
+        "reset_password_button": "Забули пароль?",
         "reset_password_email_field_description": "Введіть е-пошту, щоб могти відновити обліковий запис",
         "reset_password_email_field_required_invalid": "Введіть е-пошту (обов'язково на цьому домашньому сервері)",
         "reset_password_email_not_associated": "Ваша електронна адреса не пов'язана з Matrix ID на цьому домашньому сервері.",
@@ -330,6 +386,9 @@
             "email_resend_prompt": "Не отримали листа? <a>Надіслати повторно</a>",
             "email_resent": "Надіслано повторно!",
             "fallback_button": "Почати автентифікацію",
+            "mas_cross_signing_reset_cta": "Перейти до свого облікового запису",
+            "mas_cross_signing_reset_description": "Скиньте свою особистість через постачальника облікового запису, а потім поверніться та натисніть «Повторити».",
+            "mas_cross_signing_reset_title": "Перейдіть до свого облікового запису, щоб скинути свою ідентичність",
             "msisdn": "Текстове повідомлення надіслано на %(msisdn)s",
             "msisdn_token_incorrect": "Хибний токен",
             "msisdn_token_prompt": "Введіть отриманий код:",
@@ -364,7 +423,15 @@
         "download_logs": "Завантажити журнали",
         "downloading_logs": "Завантаження журналів",
         "error_empty": "Будь ласка, повідомте нам, що пішло не так; а ще краще створіть обговорення на GitHub із описом проблеми.",
-        "failed_send_logs": "Не вдалося надіслати журнали: ",
+        "failed_download_logs": "Не вдалося завантажити журнали налагодження: ",
+        "failed_send_logs_causes": {
+            "disallowed_app": "Ваш звіт про помилку було відхилено. Сервер rageshake не підтримує цей застосунок.",
+            "rejected_generic": "Ваш звіт про помилку було відхилено. Сервер rageshake відхилив вміст звіту через політику.",
+            "rejected_recovery_key": "Ваш звіт про помилку було відхилено з міркувань безпеки, оскільки він містив ключ відновлення.",
+            "rejected_version": "Ваш звіт про помилку було відхилено, оскільки версія, яку ви використовуєте, надто стара.",
+            "server_unknown_error": "Сервер rageshake зіткнувся з невідомою помилкою і не зміг обробити звіт.",
+            "unknown_error": "Не вдалося надіслати журнали."
+        },
         "github_issue": "Обговорення на GitHub",
         "introduction": "Якщо ви надіслали звіт про ваду на GitHub, журнали зневадження можуть допомогти нам визначити проблему. ",
         "log_request": "Щоб уникнути цього в майбутньому просимо <a>надіслати нам журнал</a>.",
@@ -404,7 +471,7 @@
         "access_token": "Токен доступу",
         "accessibility": "Доступність",
         "advanced": "Подробиці",
-        "all_rooms": "Усі кімнати",
+        "all_chats": "Усі бесіди",
         "analytics": "Аналітика",
         "and_n_others": {
             "one": "і інше...",
@@ -419,10 +486,10 @@
         "beta": "Бета",
         "camera": "Камера",
         "cameras": "Камери",
+        "cancel": "Скасувати",
         "capabilities": "Можливості",
         "copied": "Скопійовано!",
         "credits": "Подяки",
-        "cross_signing": "Перехресне підписування",
         "dark": "Темна",
         "description": "Опис",
         "deselect_all": "Скасувати вибір",
@@ -455,10 +522,13 @@
         "loading": "Завантаження…",
         "location": "Місцеперебування",
         "low_priority": "Неважливі",
+        "matrix": "Matrix",
         "message": "Повідомлення",
         "message_layout": "Макет повідомлення",
+        "message_timestamp_invalid": "Недійсна позначка часу",
         "microphone": "Мікрофон",
         "model": "Модель",
+        "moderation_and_safety": "Модерування й безпека",
         "modern": "Сучасний",
         "mute": "Стишити",
         "n_members": {
@@ -494,13 +564,15 @@
         "qr_code": "QR-код",
         "random": "Випадковий",
         "reactions": "Реакції",
+        "recommended": "Рекомендовано",
         "report_a_bug": "Повідомити про ваду",
         "room": "Кімната",
         "room_name": "Назва кімнати",
         "rooms": "Кімнати",
+        "save": "Зберегти",
+        "saved": "Збережено",
         "saving": "Збереження…",
         "secure_backup": "Безпечне резервне копіювання",
-        "security": "Безпека",
         "select_all": "Вибрати всі",
         "server": "Сервер",
         "settings": "Налаштування",
@@ -519,13 +591,13 @@
         "thread": "Гілка",
         "threads": "Гілки",
         "timeline": "Стрічка",
-        "trusted": "Довірений",
         "unavailable": "недоступний",
         "unencrypted": "Не зашифроване",
         "unmute": "Розтишити",
         "unnamed_room": "Кімната без назви",
         "unnamed_space": "Простір без назви",
         "unverified": "Не звірений",
+        "updating": "Оновлення...",
         "user": "Користувач",
         "user_avatar": "Зображення профілю",
         "username": "Ім'я користувача",
@@ -654,6 +726,7 @@
         "private_space_description": "Приватний простір для вас та учасників вашої команди",
         "public_description": "Відкритий простір для будь-кого, найкраще для спільнот",
         "public_heading": "Ваш загальнодоступний простір",
+        "search_public_button": "Пошук загальнодоступних просторів",
         "setup_rooms_community_description": "Створімо по кімнаті для кожної.",
         "setup_rooms_community_heading": "Які речі ви бажаєте обговорювати в %(spaceName)s?",
         "setup_rooms_description": "Згодом ви зможете додати більше, зокрема вже наявні.",
@@ -677,6 +750,13 @@
         "twemoji": "Стиль емоджі <twemoji>Twemoji</twemoji> від © <author>Twitter, Inc та інших учасників</author> використовується на умовах <terms>CC-BY 4.0</terms>.",
         "twemoji_colr": "Шрифт <colr>wemoji-colr</colr> від © <author>Mozilla Foundation</author> використовується на умовах <terms>Apache 2.0</terms>."
     },
+    "decline_invitation_dialog": {
+        "confirm": "Ви впевнені, що хочете відхилити запрошення приєднатися до \"%(roomName)s\"?",
+        "ignore_user_help": "Ви не бачитимете повідомлень або запрошень до кімнат від цього користувача.",
+        "reason_description": "Опишіть причину скарги на room.",
+        "report_room_description": "Поскаржитися на цю кімнату постачальнику облікового запису.",
+        "title": "Відхилити запрошення"
+    },
     "desktop_default_device_name": "%(brand)s для комп'ютера: %(platformName)s",
     "devtools": {
         "active_widgets": "Активні віджети",
@@ -684,6 +764,44 @@
         "category_room": "Кімната",
         "caution_colon": "Попередження:",
         "client_versions": "Версії клієнта",
+        "crypto": {
+            "4s_public_key_in_account_data": "у даних облікового запису",
+            "4s_public_key_not_in_account_data": "не знайдено",
+            "4s_public_key_status": "Відкритий ключ таємного сховища:",
+            "backup_key_cached": "кешовано локально",
+            "backup_key_cached_status": "Резервний ключ кешовано:",
+            "backup_key_not_stored": "не зберігається",
+            "backup_key_stored": "у таємному сховищі",
+            "backup_key_stored_status": "Резервний ключ розміщено:",
+            "backup_key_unexpected_type": "несподіваний тип",
+            "backup_key_well_formed": "добре сформований",
+            "cross_signing": "Перехресне підписування",
+            "cross_signing_cached": "кешовано локально",
+            "cross_signing_not_ready": "Перехресне підписування не налаштовано.",
+            "cross_signing_private_keys_in_storage": "у таємному сховищі",
+            "cross_signing_private_keys_in_storage_status": "Приватні ключі перехресного підписування:",
+            "cross_signing_private_keys_not_in_storage": "не знайдено у сховищі",
+            "cross_signing_public_keys_on_device": "у пам'яті",
+            "cross_signing_public_keys_on_device_status": "Відкриті ключі перехресного підписування:",
+            "cross_signing_ready": "Перехресне підписування готове до користування.",
+            "cross_signing_status": "Стан перехресного підписування:",
+            "cross_signing_untrusted": "Ваш обліковий запис має ідентичність із перехресним підписом у таємному сховищі, але цей сеанс ще не довіряє їй.",
+            "crypto_not_available": "Криптографічний модуль недоступний",
+            "key_backup_active_version": "Активна версія резервної копії:",
+            "key_backup_active_version_none": "Немає",
+            "key_backup_inactive_warning": "Резервне копіювання ваших ключів із цього сеансу не виконується.",
+            "key_backup_latest_version": "Остання версія резервної копії на сервері:",
+            "key_storage": "Сховище ключів",
+            "master_private_key_cached_status": "Головний приватний ключ:",
+            "not_found": "не знайдено",
+            "not_found_locally": "не знайдено локально",
+            "secret_storage_not_ready": "не готовий",
+            "secret_storage_ready": "готовий",
+            "secret_storage_status": "Таємне сховище:",
+            "self_signing_private_key_cached_status": "Самопідписаний приватний ключ:",
+            "title": "Наскрізне шифрування",
+            "user_signing_private_key_cached_status": "Приватний ключ підпису користувача:"
+        },
         "developer_mode": "Режим розробника",
         "developer_tools": "Інструменти розробника",
         "edit_setting": "Змінити налаштування",
@@ -700,6 +818,7 @@
         "failed_to_load": "Не вдалося завантажити.",
         "failed_to_save": "Не вдалося зберегти налаштування.",
         "failed_to_send": "Не вдалося надіслати подію!",
+        "id": "Ідентифікатор: ",
         "invalid_json": "Не схоже на чинний JSON.",
         "level": "Рівень",
         "low_bandwidth_mode": "Режим низької пропускної спроможності",
@@ -722,7 +841,9 @@
         "room_notifications_type": "Тип: ",
         "room_status": "Статус кімнати",
         "room_unread_status_count": {
-            "other": "Стан непрочитаного в кімнаті: <strong>%(status)s</strong>, кількість: <strong>%(count)s</strong>"
+            "one": "Статус непрочитаного кімнати: <strong> %(status)s</strong>, кількість: <strong> %(count)s </strong>",
+            "few": "Статус непрочитаного кімнати: <strong> %(status)s</strong>, кількість: <strong> %(count)s </strong>",
+            "many": "Статус непрочитаного кімнати: <strong> %(status)s</strong>, кількість: <strong> %(count)s </strong>"
         },
         "save_setting_values": "Зберегти значення налаштування",
         "see_history": "Переглянути історію",
@@ -737,6 +858,9 @@
         "setting_colon": "Налаштування:",
         "setting_definition": "Означення налаштування:",
         "setting_id": "ID налаштувань",
+        "settings": {
+            "elementCallUrl": "URL Element Call"
+        },
         "settings_explorer": "Налаштування оглядача",
         "show_hidden_events": "Показувати приховані події у часоряді",
         "spaces": {
@@ -789,49 +913,35 @@
     "empty_room_was_name": "Порожня кімната (були %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Введіть свою фразу безпеки чи <button>скористайтесь ключем безпеки</button> для продовження.",
             "key_validation_text": {
-                "invalid_security_key": "Хибний ключ безпеки",
-                "recovery_key_is_correct": "Виглядає файно!",
-                "wrong_file_type": "Тип файлу не підтримується",
-                "wrong_security_key": "Ключ безпеки не збігається"
-            },
-            "reset_title": "Скинути все",
-            "reset_warning_1": "Робіть це лише якщо у вас немає іншого пристрою для виконання перевірки.",
-            "reset_warning_2": "Якщо ви скинете все, то почнете заново без довірених сеансів, користувачів і доступу до минулих повідомлень.",
+                "wrong_security_key": "Неправильний ключ відновлення"
+            },
             "restoring": "Відновлення ключів із резервної копії",
-            "security_key_title": "Ключ безпеки",
-            "security_phrase_incorrect_error": "Не вдалося зайти до таємного сховища. Переконайтеся, що ввели правильну фразу безпеки.",
-            "security_phrase_title": "Фраза безпеки",
-            "separator": "%(securityKey)s або %(recoveryFile)s",
-            "use_security_key_prompt": "Скористайтеся ключем безпеки для продовження."
+            "security_key_title": "Ключ відновлення"
         },
         "bootstrap_title": "Налаштовування ключів",
         "cancel_entering_passphrase_description": "Ви точно хочете скасувати введення парольної фрази?",
         "cancel_entering_passphrase_title": "Скасувати введення парольної фрази?",
         "confirm_encryption_setup_body": "Клацніть на кнопку внизу, щоб підтвердити налаштування шифрування.",
         "confirm_encryption_setup_title": "Підтвердити налаштування шифрування",
-        "cross_signing_not_ready": "Перехресне підписування не налаштовано.",
-        "cross_signing_ready": "Перехресне підписування готове до користування.",
-        "cross_signing_ready_no_backup": "Перехресне підписування готове, але резервна копія ключів не створюється.",
         "cross_signing_room_normal": "Ця кімната є наскрізно зашифрованою",
         "cross_signing_room_verified": "Усі в цій кімнаті звірені",
         "cross_signing_room_warning": "Хтось користується невідомим сеансом",
-        "cross_signing_unsupported": "Ваш домашній сервер не підтримує перехресного підписування.",
-        "cross_signing_untrusted": "Ваш обліковий запис має перехресне підписування особи у таємному сховищі, але цей сеанс йому ще не довіряє.",
         "cross_signing_user_normal": "Ви не звіряли цього користувача.",
         "cross_signing_user_verified": "Ви звірили цього користувача. Цей користувач звірив усі свої сеанси.",
         "cross_signing_user_warning": "Цей користувач звірив не всі свої сеанси.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Очистити ключі перехресного підписування",
-            "title": "Знищити ключі перехресного підписування?",
-            "warning": "Видалення ключів перехресного підписування безповоротне. Усі, з ким ви звірили сеанси, бачитимуть сповіщення системи безпеки. Ви майже напевно не захочете цього робити, якщо тільки ви не втратили всі пристрої, з яких можна виконувати перехресне підписування."
-        },
+        "enter_recovery_key": "Введіть ключ відновлення",
         "event_shield_reason_authenticity_not_guaranteed": "Справжність цього зашифрованого повідомлення не може бути гарантованою на цьому пристрої.",
         "event_shield_reason_mismatched_sender_key": "Зашифроване незвіреним сеансом",
+        "event_shield_reason_unknown_device": "Зашифровано невідомим або видаленим пристроєм.",
+        "event_shield_reason_unsigned_device": "Зашифровано пристроєм, не перевіреним його власником.",
+        "event_shield_reason_unverified_identity": "Зашифровано неперевіреним користувачем.",
         "export_unsupported": "Ваш браузер не підтримує необхідних криптографічних функцій",
+        "forgot_recovery_key": "Забули ключ відновлення?",
         "import_invalid_keyfile": "Файл ключа %(brand)s некоректний",
         "import_invalid_passphrase": "Помилка автентифікації: неправильний пароль?",
+        "key_storage_out_of_sync": "Ваше сховище ключів не синхронізується.",
+        "key_storage_out_of_sync_description": "Підтвердьте свій ключ відновлення, щоб мати доступ до сховища ключів та історії повідомлень.",
         "messages_not_secure": {
             "cause_1": "Ваш домашній сервер",
             "cause_2": "Домашній сервер користувача, якого ви підтверджуєте",
@@ -846,7 +956,8 @@
             "title": "Новий метод відновлення",
             "warning": "Якщо ви не встановлювали нового способу відновлення, ймовірно хтось намагається зламати ваш обліковий запис. Негайно змініть пароль до свого облікового запису й встановіть новий спосіб відновлення в налаштуваннях."
         },
-        "not_supported": "<не підтримується>",
+        "pinned_identity_changed": "Ідентичність %(displayName)s (<b>%(userId)s</b>) скинуто. <a>Докладніше</a>",
+        "pinned_identity_changed_no_displayname": "Ідентичність <b>%(userId)s</b> скинуто. <a>Докладніше</a>",
         "recovery_method_removed": {
             "description_1": "Цей сеанс виявив, що ваша фраза безпеки й ключ до захищених повідомлень були видалені.",
             "description_2": "Якщо це ненароком зробили ви, налаштуйте захищені повідомлення для цього сеансу, щоб повторно зашифрувати історію листування цього сеансу з новим способом відновлення.",
@@ -854,11 +965,13 @@
             "warning": "Якщо ви не видаляли способу відновлення, ймовірно хтось намагається зламати ваш обліковий запис. Негайно змініть пароль до свого облікового запису й встановіть новий спосіб відновлення в налаштуваннях."
         },
         "reset_all_button": "Забули чи втратили всі способи відновлення? <a>Скинути все</a>",
+        "set_up_recovery": "Налаштування відновлення",
+        "set_up_recovery_later": "Не зараз",
+        "set_up_recovery_toast_description": "Згенеруйте ключ відновлення, який можна використовувати для відновлення історії зашифрованих повідомлень у разі втрати доступу до своїх пристроїв.",
         "set_up_toast_description": "Захистіться від втрати доступу до зашифрованих повідомлень і даних",
         "set_up_toast_title": "Налаштувати захищене резервне копіювання",
         "setup_secure_backup": {
-            "explainer": "Створіть резервну копію ключів перед виходом, щоб не втратити їх.",
-            "title": "Налаштувати"
+            "explainer": "Створіть резервну копію ключів перед виходом, щоб не втратити їх."
         },
         "udd": {
             "interactive_verification_button": "Звірити інтерактивно за допомогою емоджі",
@@ -869,12 +982,10 @@
             "title": "Не довірений"
         },
         "unable_to_setup_keys_error": "Не вдалося налаштувати ключі",
-        "unsupported": "Цей клієнт не підтримує наскрізного шифрування.",
         "verification": {
             "accepting": "Прийняття…",
             "after_new_login": {
                 "device_verified": "Пристрій звірено",
-                "reset_confirmation": "Точно скинути ключі звірки?",
                 "skip_verification": "На разі пропустити звірку",
                 "unable_to_verify": "Не вдалося звірити цей пристрій",
                 "verify_this_device": "Звірити цей пристрій"
@@ -896,7 +1007,7 @@
             "incoming_sas_dialog_waiting": "Очікування підтвердження партнером…",
             "incoming_sas_user_dialog_text_1": "Звірте цього користувача щоб позначити його довіреним. Довіряння користувачам додає спокою якщо ви користуєтесь наскрізно зашифрованими повідомленнями.",
             "incoming_sas_user_dialog_text_2": "Звірка цього користувача позначить його сеанс довіреним вам, а ваш йому.",
-            "no_key_or_device": "Схоже, у вас немає ключа безпеки або будь-яких інших пристроїв, які ви можете підтвердити. Цей пристрій не зможе отримати доступ до старих зашифрованих повідомлень. Щоб підтвердити свою справжність на цьому пристрої, вам потрібно буде скинути ключі перевірки.",
+            "no_key_or_device": "Схоже, у вас немає ключа відновлення або будь-яких інших пристроїв, за допомогою яких ви можете виконати верифікацію. Цей пристрій не зможе отримати доступ до старих зашифрованих повідомлень. Щоб підтвердити свою ідентичність на цьому пристрої, вам потрібно буде скинути ключі верифікації.",
             "no_support_qr_emoji": "Пристрій, який ви намагаєтесь звірити, не підтримує сканування QR-коду або звірення за допомогою емоджі, що є підтримувані %(brand)s. Спробуйте використати інший клієнт.",
             "other_party_cancelled": "Друга сторона скасувала звірення.",
             "prompt_encrypted": "Звірте всіх користувачів у кімнаті, щоб забезпечити її захищеність.",
@@ -909,6 +1020,7 @@
             "qr_reciprocate_same_shield_device": "Майже готово! Чи показує інший пристрій такий самий щит?",
             "qr_reciprocate_same_shield_user": "Майже готово! Ваш %(displayName)s показує той самий щит?",
             "request_toast_accept": "Звірити сеанс",
+            "request_toast_accept_user": "Підтвердити користувача",
             "request_toast_decline_counter": "Ігнорувати (%(counter)s)",
             "request_toast_detail": "%(deviceId)s з %(ip)s",
             "reset_proceed_prompt": "Продовжити скидання",
@@ -934,7 +1046,7 @@
             "unverified_sessions_toast_description": "Перевірте, щоб переконатися, що ваш обліковий запис у безпеці",
             "unverified_sessions_toast_reject": "Пізніше",
             "unverified_sessions_toast_title": "У вас є неперевірені сеанси",
-            "verification_description": "Підтвердьте свою особу, щоб отримати доступ до зашифрованих повідомлень і довести свою справжність іншим.",
+            "verification_description": "Верифікуйте свою особистість, щоб отримати доступ до зашифрованих повідомлень і довести свою справжність іншим. Якщо ви також використовуєте мобільний пристрій, відкрийте застосунок на ньому, перш ніж продовжити.",
             "verification_dialog_title_device": "Звірити інший пристрій",
             "verification_dialog_title_user": "Запит підтвердження",
             "verification_skip_warning": "До звірки ви матимете доступ не до всіх своїх повідомлень, а в інших ви можете позначатися недовіреними.",
@@ -944,19 +1056,20 @@
             "verify_emoji_prompt": "Звірити порівнянням унікальних емодзі.",
             "verify_emoji_prompt_qr": "Якщо ви не можете сканувати зазначений код, звірте порівнянням унікальних емодзі.",
             "verify_later": "Звірю пізніше",
-            "verify_reset_warning_1": "Скидання ключів звірки неможливо скасувати. Після скидання, ви втратите доступ до старих зашифрованих повідомлень, а всі друзі, які раніше вас звіряли, бачитимуть застереження безпеки, поки ви не проведете звірку з ними знову.",
-            "verify_reset_warning_2": "Будь ласка, продовжуйте лише в разі втрати всіх своїх інших пристроїв та ключа безпеки.",
             "verify_using_device": "Звірити за допомогою іншого пристрою",
-            "verify_using_key": "Підтвердити ключем безпеки",
-            "verify_using_key_or_phrase": "Підтвердити ключем чи фразою безпеки",
+            "verify_using_key": "Верифікувати за допомогою ключа відновлення",
+            "verify_using_key_or_phrase": "Верифікувати за допомогою фрази або ключа відновлення",
             "waiting_for_user_accept": "Очікування згоди %(displayName)s…",
             "waiting_other_device": "Очікування вашої звірки на іншому пристрої…",
             "waiting_other_device_details": "Очікування вашої звірки на іншому пристрої, %(deviceName)s (%(deviceId)s)…",
             "waiting_other_user": "Очікування звірки %(displayName)s…"
         },
         "verification_requested_toast_title": "Запит перевірки",
+        "verified_identity_changed": "Ідентичність %(displayName)s (<b>%(userId)s</b>) скинуто. <a>Докладніше</a>",
+        "verified_identity_changed_no_displayname": "Ідентичність <b>%(userId)s</b> скинуто. <a>Докладніше</a> ",
         "verify_toast_description": "Інші користувачі можуть не довіряти цьому",
-        "verify_toast_title": "Звірити цей сеанс"
+        "verify_toast_title": "Звірити цей сеанс",
+        "withdraw_verification_action": "Відкликати верифікацію"
     },
     "error": {
         "admin_contact": "<a>Зв'яжіться з адміністратором сервісу,</a> щоб продовжити використання.",
@@ -997,18 +1110,21 @@
         "unknown_error_code": "невідомий код помилки",
         "update_power_level": "Не вдалося змінити рівень повноважень"
     },
-    "error_database_closed_title": "База даних несподівано закрилася",
+    "error_app_open_in_another_tab": "Перейдіть на іншу вкладку, щоб під'єднатися до %(brand)s. Цю вкладку можна закрити.",
+    "error_app_open_in_another_tab_title": "%(brand)s під'єднано в іншій вкладці",
+    "error_app_opened_in_another_window": "%(brand)s відкрито в іншому вікні. Натисніть \"%(label)s\", щоб використовувати %(brand)s тут і від'єднати інше вікно.",
+    "error_database_closed_description": {
+        "for_desktop": "Можливо ваш диск переповнений. Звільніть місце і перезавантажте.",
+        "for_web": "Якщо ви очистили дані перегляду, це повідомлення очікується. %(brand)s також може бути відкрито в іншій вкладці, або ваш диск заповнений. Звільніть простір і перезавантажте"
+    },
+    "error_database_closed_title": "%(brand)s перестав працювати",
     "error_dialog": {
         "copy_room_link_failed": {
             "description": "Не вдалося скопіювати посилання на цю кімнату до буфера обміну.",
             "title": "Не вдалося скопіювати посилання на кімнату"
         },
         "error_loading_user_profile": "Не вдалося звантажити профіль користувача",
-        "forget_room_failed": "Не вдалось видалити кімнату %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Сервер може бути недосяжним, перевантаженим або запит на пошук застарів :(",
-            "title": "Пошук не вдався"
-        }
+        "forget_room_failed": "Не вдалось видалити кімнату %(errCode)s"
     },
     "error_user_not_logged_in": "Користувач не увійшов",
     "event_preview": {
@@ -1031,7 +1147,17 @@
         "m.reaction": {
             "user": "%(sender)s реагує з %(reaction)s на %(message)s",
             "you": "Ви зреагували %(reaction)s на %(message)s"
-        }
+        },
+        "m.sticker": "%(senderName)s: %(stickerName)s",
+        "m.text": "%(senderName)s: %(message)s",
+        "prefix": {
+            "audio": "Аудіо",
+            "file": "Файл",
+            "image": "Зображення",
+            "poll": "Опитування",
+            "video": "Відео"
+        },
+        "preview": "<bold>%(prefix)s:</bold> %(preview)s"
     },
     "export_chat": {
         "cancelled": "Експорт скасовано",
@@ -1067,8 +1193,10 @@
         "format": "Формат",
         "from_the_beginning": "З самого початку",
         "generating_zip": "Генерування ZIP-файлу",
+        "html": "HTML",
         "html_title": "Експортовані дані",
         "include_attachments": "Включити вкладення",
+        "json": "JSON",
         "media_omitted": "Медіа пропущено",
         "media_omitted_file_size": "Медіа пропущено — перевищує обмеження розміру файлу",
         "messages": "Повідомлення",
@@ -1121,6 +1249,7 @@
         "change": "Змінити сервер ідентифікації",
         "change_prompt": "Від'єднатися від сервера ідентифікації <current /> й натомість під'єднатися до <new />?",
         "change_server_prompt": "Якщо ви не бажаєте використовувати <server />, щоб знаходити наявні контакти й щоб вони вас знаходили, введіть інший сервер ідентифікації нижче.",
+        "changed": "Ваш сервер ідентифікації змінено",
         "checking": "Перевірка сервера",
         "description_connected": "Зараз <server></server> дозволяє вам знаходити контакти, а контактам вас. Можете змінити сервер ідентифікації нижче.",
         "description_disconnected": "Зараз ви не використовуєте сервер ідентифікації. Щоб знайти наявні контакти й вони могли знайти вас, додайте його нижче.",
@@ -1152,7 +1281,20 @@
         "other": "У %(spaceName)s та %(count)s інших пристроях."
     },
     "incompatible_browser": {
-        "title": "Непідтримуваний браузер"
+        "continue": "Усе одно продовжити",
+        "description": "%(brand)s використовує деякі функції браузера, які недоступні у вашому поточному браузері. %(detail)s",
+        "detail_can_continue": "Якщо ви продовжите, деякі функції можуть перестати працювати, і існує ризик втрати даних у майбутньому.",
+        "detail_no_continue": "Спробуйте оновити цей браузер, якщо ви не використовуєте найновішу його версію, і повторіть спробу.",
+        "learn_more": "Докладніше",
+        "linux": "Linux",
+        "macos": "Mac",
+        "supported_browsers": "Для найкращої роботи використовуйте <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge> або <Safari>Safari</Safari>.",
+        "title": "Непідтримуваний браузер",
+        "use_desktop_heading": "Натомість використовуйте %(brand)s для комп'ютерів",
+        "use_mobile_heading": "Натомість використовуйте %(brand)s для мобільних",
+        "use_mobile_heading_after_desktop": "Або скористайтеся нашим мобільним застосунком",
+        "windows_64bit": "Windows (64-розрядна)",
+        "windows_arm_64bit": "Windows (64-розрядна версія ARM)"
     },
     "info_tooltip_title": "Відомості",
     "integration_manager": {
@@ -1161,6 +1303,7 @@
         "error_connecting_heading": "Не вдалося з'єднатися з менеджером інтеграцій",
         "explainer": "Менеджери інтеграцій отримують дані конфігурації та можуть змінювати віджети, надсилати запрошення у кімнати й установлювати рівні повноважень від вашого імені.",
         "manage_title": "Керування інтеграціями",
+        "toggle_label": "Увімкнути менеджер інтеграції",
         "use_im": "Використовувати менеджер інтеграцій для керування ботами, віджетами й пакунками наліпок.",
         "use_im_default": "Використовувати менеджер інтеграцій <b>%(serverName)s</b> для керування ботами, віджетами й пакунками наліпок."
     },
@@ -1192,6 +1335,8 @@
         "error_permissions_space": "Ви не маєте дозволу запрошувати людей у цей простір.",
         "error_profile_undisclosed": "Користувач може або не може існувати",
         "error_transfer_multiple_target": "Виклик можна переадресувати лише на одного користувача.",
+        "error_unfederated_room": "Ця кімната не федеративна. Ви не можете запрошувати людей із зовнішніх серверів.",
+        "error_unfederated_space": "Цей простір не федеративний. Ви не можете запрошувати людей із зовнішніх серверів.",
         "error_unknown": "Невідома помилка з боку сервера",
         "error_user_not_found": "Користувача не існує",
         "error_version_unsupported_room": "Домашній сервер користувача не підтримує версію кімнати.",
@@ -1234,6 +1379,7 @@
     },
     "keyboard": {
         "activate_button": "Натиснути обрану кнопку",
+        "alt": "Alt",
         "autocomplete_cancel": "Скасувати самодоповнення",
         "autocomplete_force": "Примусово завершити",
         "autocomplete_navigate_next": "Наступна пропозиція автодоповнення",
@@ -1257,7 +1403,11 @@
         "composer_toggle_link": "Перемкнути посилання",
         "composer_toggle_quote": "Перемкнути цитування",
         "composer_undo": "Скасувати зміни",
+        "control": "Ctrl",
         "dismiss_read_marker_and_jump_bottom": "Відхилити маркер прочитання й перейти донизу",
+        "end": "End",
+        "enter": "Enter",
+        "escape": "Esc",
         "go_home_view": "Перейти до домівки",
         "home": "Домівка",
         "jump_first_message": "Перейти до першого повідомлення",
@@ -1269,10 +1419,14 @@
         "navigate_next_message_edit": "Перейти до наступного повідомлення для редагування",
         "navigate_prev_history": "Попередня недавно відвідана кімната або простір",
         "navigate_prev_message_edit": "Перейти до попереднього повідомлення для редагування",
+        "next_landmark": "Перейти до наступного пункту",
         "next_room": "Наступна кімната або особисте повідомлення",
         "next_unread_room": "Наступна непрочитана кімната або особисте повідомлення",
         "number": "[цифра]",
         "open_user_settings": "Відкрити користувацькі налаштування",
+        "page_down": "Page Down",
+        "page_up": "Page Up",
+        "prev_landmark": "Перейти до попереднього пункту",
         "prev_room": "Попередня кімната або особисте повідомлення",
         "prev_unread_room": "Попередня непрочитана кімната або особисте повідомлення",
         "room_list_collapse_section": "Згорнути розділ з переліком кімнат",
@@ -1284,6 +1438,7 @@
         "scroll_up_timeline": "Гортати стрічку вгору",
         "search": "Пошук (повинен бути увімкненим)",
         "send_sticker": "Надіслати наліпку",
+        "shift": "Shift",
         "space": "Простір",
         "switch_to_space": "Перейти до простору за номером",
         "toggle_hidden_events": "Показати/сховати подію",
@@ -1316,8 +1471,12 @@
         "dynamic_room_predecessors": "Попередники динамічної кімнати",
         "dynamic_room_predecessors_description": "Увімкнути MSC3946 (для підтримки архівів пізніх кімнат)",
         "element_call_video_rooms": "Відео кімнати Element Call",
+        "exclude_insecure_devices": "Виключати незахищені пристрої під час надсилання/отримання повідомлень",
+        "exclude_insecure_devices_description": "Якщо цей режим увімкнено, зашифровані повідомлення не надходитимуть на неверифіковані пристрої, а повідомлення з неверифікованих пристроїв будуть показані як помилка. Зауважте, що якщо ви увімкнете цей режим, ви не зможете спілкуватися з користувачами, які не верифікували свої пристрої.",
         "experimental_description": "Відчуваєте себе експериментатором? Спробуйте наші новітні задуми в розробці. Ці функції не остаточні; вони можуть бути нестабільними, можуть змінюватися або взагалі можуть бути відкинуті. <a>Докладніше</a>.",
         "experimental_section": "Ранній огляд",
+        "extended_profiles_msc_support": "Вимагає, щоб ваш сервер підтримував MSC4133",
+        "feature_disable_call_per_sender_encryption": "Вимкнути шифрування для кожного відправника для Element Call",
         "feature_wysiwyg_composer_description": "Використовувати розширений текст замість розмітки в редакторі повідомлень.",
         "group_calls": "Нові можливості групових викликів",
         "group_developer": "Розробка",
@@ -1330,6 +1489,7 @@
         "group_spaces": "Простори",
         "group_themes": "Теми",
         "group_threads": "Гілки",
+        "group_ui": "Інтерфейс користувача",
         "group_voip": "Голос і відео",
         "group_widgets": "Віджети",
         "hidebold": "Сховати крапку сповіщення ( показувати тільки значки лічильників)",
@@ -1345,15 +1505,22 @@
         "location_share_live_description": "Тимчасова реалізація. Місце перебування зберігається в історії кімнати.",
         "mjolnir": "Нові способи нехтувати людей",
         "msc3531_hide_messages_pending_moderation": "Дозволити модераторам ховати повідомлення у черзі модерування.",
+        "new_room_list": "Увімкнути новий список кімнат",
         "notification_settings": "Нові налаштування сповіщень",
+        "notification_settings_beta_caption": "Представляємо простіший спосіб змінювати налаштування сповіщень. Налаштуйте %(brand)s, саме так, як вам подобається.",
         "notification_settings_beta_title": "Налаштування сповіщень",
+        "notifications": "Увімкнути панель сповіщень у шапці кімнати",
+        "release_announcement": "Оголошення про випуск",
+        "render_reaction_images": "Відтворення кастомних зображень у реакціях",
+        "render_reaction_images_description": "Іноді їх називають \"користувацькими емодзі\".",
         "report_to_moderators": "Поскаржитись модераторам",
         "report_to_moderators_description": "У кімнатах, які підтримують модерацію, кнопка «Поскаржитися» дає змогу повідомити про зловживання модераторам кімнати.",
         "sliding_sync": "Режим ковзної синхронізації",
         "sliding_sync_description": "На стадії активної розробки, вимкнути не можна.",
         "sliding_sync_disabled_notice": "Вийдіть і знову увійдіть, щоб вимкнути",
-        "sliding_sync_server_no_support": "На вашому сервері немає вбудованої підтримки",
+        "sliding_sync_server_no_support": "На вашому сервері відсутня підтримка",
         "under_active_development": "У стадії активної розробки.",
+        "unrealiable_e2e": "Ненадійно в зашифрованих кімнатах",
         "video_rooms": "Відеокімнати",
         "video_rooms_a_new_way_to_chat": "Новий спосіб спілкування за допомогою голосового та відеозв’язку в %(brand)s.",
         "video_rooms_always_on_voip_channels": "Відеокімнати — це завжди в VOIP-канали, вбудовані в кімнату в %(brand)s.",
@@ -1362,6 +1529,7 @@
         "video_rooms_faq1_question": "Як створити відеокімнату?",
         "video_rooms_faq2_answer": "Так, стрічка бесіди показана поряд із відео.",
         "video_rooms_faq2_question": "Чи можу я писати текстові повідомлення під час відеовиклику?",
+        "video_rooms_feedbackSubheading": "Дякую за те що спробували бета-версію. Будь ласка опишіть ваш досвід насільки детально наскільки ви можете, щоб ми могли покращити систему.",
         "wysiwyg_composer": "Розширений текстовий редактор"
     },
     "labs_mjolnir": {
@@ -1402,6 +1570,8 @@
         "last_person_warning": "Тут лише ви. Якщо ви вийдете, ніхто більше не зможе приєднатися, навіть ви самі.",
         "leave_room_question": "Ви впевнені, що хочете вийти з «%(roomName)s»?",
         "leave_space_question": "Точно вийти з простору «%(spaceName)s»?",
+        "room_leave_admin_warning": "Ви єдиний адміністратор у цій кімнаті. Якщо ви вийдете, ніхто не зможе змінити налаштування кімнати або зробити інші важливі дії.",
+        "room_leave_mod_warning": "Ви єдиний модератор у цій кімнаті. Якщо ви вийдете, ніхто не зможе змінити налаштування кімнати або зробити інші важливі дії.",
         "room_rejoin_warning": "Ця кімната не загальнодоступна. Ви не зможете повторно приєднатися без запрошення.",
         "space_rejoin_warning": "Цей простір не загальнодоступний. Ви не зможете приєднатися знову без запрошення."
     },
@@ -1459,17 +1629,26 @@
         "toggle_attribution": "Перемкнути атрибуцію"
     },
     "member_list": {
+        "count": {
+            "one": "%(count)s учасник",
+            "few": "%(count)s учасники",
+            "many": "%(count)s учасників"
+        },
         "filter_placeholder": "Відфільтрувати учасників кімнати",
         "invite_button_no_perms_tooltip": "У вас немає дозволу запрошувати користувачів",
+        "invited_label": "Запрошено",
+        "no_matches": "Немає збігів",
         "power_label": "%(userName)s (повноваження %(powerLevelNumber)s)"
     },
     "member_list_back_action_label": "Учасники кімнати",
     "message_edit_dialog_title": "Редагування повідомлення",
+    "migrating_crypto": "Тримайтеся міцно. Ми оновлюємо %(brand)s, щоб зробити шифрування швидшим і надійнішим.",
     "mobile_guide": {
         "toast_accept": "Використовувати застосунок",
         "toast_description": "%(brand)s у мобільних браузерах ще випробовується. Поки що кращі враження й новіші функції — у нашому вільному мобільному застосунку.",
         "toast_title": "Використовуйте застосунок для зручності"
     },
+    "name_and_id": "%(name)s(%(userId)s)",
     "no_more_results": "Інших результатів нема",
     "notif_panel": {
         "empty_description": "У вас нема видимих сповіщень.",
@@ -1481,6 +1660,7 @@
         "class_global": "Глобально",
         "class_other": "Інше",
         "default": "Типовий",
+        "default_settings": "Згідно з усталеними налаштуваннями",
         "email_pusher_app_display_name": "Сповіщення е-поштою",
         "enable_prompt_toast_description": "Увімкнути сповіщення стільниці",
         "enable_prompt_toast_title": "Сповіщення",
@@ -1489,14 +1669,18 @@
         "keyword": "Ключове слово",
         "keyword_new": "Нове ключове слово",
         "level_activity": "Активністю",
+        "level_highlight": "Виділити",
+        "level_muted": "Звук вимкнено",
         "level_none": "Вимкнено",
+        "level_notification": "Сповіщення",
         "level_unsent": "Не надіслано",
         "mark_all_read": "Позначити все прочитаним",
         "mentions_and_keywords": "@згадки й ключові слова",
         "mentions_and_keywords_description": "Отримувати лише вказані у ваших <a>налаштуваннях</a> згадки й ключові слова",
         "mentions_keywords": "Згадки та ключові слова",
         "message_didnt_send": "Повідомлення не надіслане. Натисніть, щоб дізнатись більше.",
-        "mute_description": "Ви не отримуватимете жодних сповіщень"
+        "mute_description": "Ви не отримуватимете жодних сповіщень",
+        "mute_room": "Вимкнути сповіщення кімнати"
     },
     "notifier": {
         "m.key.verification.request": "%(name)s робить запит на звірення"
@@ -1577,7 +1761,8 @@
         "online": "У мережі",
         "online_for": "У мережі %(duration)s",
         "unknown": "Невідомо",
-        "unknown_for": "Невідомо %(duration)s"
+        "unknown_for": "Невідомо %(duration)s",
+        "unreachable": "Сервер користувача недоступний"
     },
     "quick_settings": {
         "all_settings": "Усі налаштування",
@@ -1597,21 +1782,19 @@
         "ongoing": "Вилучення…",
         "reason_label": "Причина (не обов'язково)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Ви впевнені, що хочете відхилити запрошення?",
-        "failed": "Не вдалось відхилити запрошення",
-        "title": "Відхилити запрошення"
-    },
     "report_content": {
         "description": "Зі скаргою на це повідомлення буде надіслано його унікальний «ID події» адміністраторові вашого домашнього сервера. Якщо повідомлення у цій кімнаті зашифровані, то адміністратор не зможе побачити ані тексту повідомлень, ані жодних файлів чи зображень.",
         "disagree": "Відхилити",
+        "error_create_room_moderation_bot": "Не вдається створити кімнату за допомогою бота-модератора",
         "hide_messages_from_user": "Виберіть, чи хочете ви сховати всі поточні та майбутні повідомлення від цього користувача.",
         "ignore_user": "Нехтувати користувача",
         "illegal_content": "Протиправний вміст",
-        "missing_reason": "Будь ласка, вкажіть, чому ви скаржитеся.",
+        "missing_reason": "Будь ласка, вкажіть причину скарги.",
         "nature": "Оберіть природу й опишіть, що образливого в цьому повідомленні.",
         "nature_disagreement": "Те, що пише цей користувач, неправильно.\nПро це буде повідомлено модераторам кімнати.",
         "nature_illegal": "Користувач порушує закон, наприклад зливає особисті дані людей чи погрожує насиллям.\nМодератори кімнати отримають скаргу на це й зможуть передати її правоохоронцям.",
+        "nature_nonstandard_admin": "Ця кімната присвячена незаконному чи токсичному вмісту, або модератори не модерують незаконний чи токсичний вміст.\nПро це буде повідомлено адміністраторів %(homeserver)s.",
+        "nature_nonstandard_admin_encrypted": "Ця кімната присвячена незаконному чи токсичному вмісту, або модератори не модерують незаконний чи токсичний вміст.\nПро це буде повідомлено адміністраторів %(homeserver)s. Адміністратори НЕ зможуть прочитати зашифрований вміст цієї кімнати.",
         "nature_other": "Будь-яка інша причина. Будь ласка, опишіть проблему.\nМодератори кімнати отримають вашу скаргу.",
         "nature_spam": "Користувач засмічує кімнату рекламою, посиланнями на рекламу чи пропагандою.\nМодератори кімнати отримають скаргу на це.",
         "nature_toxic": "Користувач поводиться токсично, наприклад ображає інших користувачів, поширює дорослий вміст у сімейній кімнаті чи ще якось порушує правила цієї кімнати.\nМодератори кімнати отримають скаргу на це.",
@@ -1621,38 +1804,66 @@
         "spam_or_propaganda": "Спам чи пропаганда",
         "toxic_behaviour": "Токсична поведінка"
     },
+    "report_room": {
+        "description": "Поскаржитися на цю кімнату постачальнику облікового запису. Якщо повідомлення зашифровані, адміністратор не зможе прочитати їх.",
+        "reason_label": "Опишіть причину"
+    },
     "restore_key_backup_dialog": {
         "count_of_decryption_failures": "Не вдалося розшифрувати %(failedCount)s сеансів!",
         "count_of_successfully_restored_keys": "Успішно відновлено %(sessionCount)s ключів",
-        "enter_key_description": "Отримайте історію своїх зашифрованих повідомлень і налаштуйте безпечне листування, ввівши свій ключ безпеки.",
-        "enter_key_title": "Введіть ключ безпеки",
+        "enter_key_description": "Отримайте історію своїх зашифрованих повідомлень і налаштуйте безпечний обмін повідомленнями, ввівши свій ключ відновлення.",
+        "enter_key_title": "Введіть ключ відновлення",
         "enter_phrase_description": "Отримайте історію своїх зашифрованих повідомлень і налаштуйте безпечне листування, ввівши свою фразу безпеки.",
         "enter_phrase_title": "Введіть фразу безпеки",
         "incorrect_security_phrase_dialog": "Не вдалося розшифрувати резервну копію цією фразою безпеки: переконайтеся, що вводите правильну фразу безпеки.",
         "incorrect_security_phrase_title": "Хибна фраза безпеки",
         "key_backup_warning": "<b>Застереження</b>: налаштовуйте резервне копіювання ключів лише на довіреному комп'ютері.",
         "key_fetch_in_progress": "Отримання ключів із сервера…",
-        "key_forgotten_text": "Якщо ви забули ключ безпеки, <button>налаштуйте нові параметри відновлення</button>",
-        "key_is_invalid": "Хибний ключ безпеки",
-        "key_is_valid": "Це схоже на дійсний ключ безпеки!",
+        "key_forgotten_text": "Якщо ви забули ключ відновлення, <button>налаштуйте нові параметри відновлення</button>",
+        "key_is_invalid": "Недійсний ключ відновлення",
+        "key_is_valid": "Це схоже на дійсний ключ відновлення!",
         "keys_restored_title": "Ключ відновлено",
         "load_error_content": "Не вдалося отримати стан резервного копіювання",
         "load_keys_progress": "%(completed)s із %(total)s ключів відновлено",
         "no_backup_error": "Резервних копій не знайдено!",
-        "phrase_forgotten_text": "Якщо ви забули фразу безпеки, <button1>скористайтеся ключем безпеки</button1> чи <button2>налаштуйте нові параметри відновлення</button2>",
-        "recovery_key_mismatch_description": "Не вдалося розшифрувати резервну копію цим ключем безпеки: переконайтеся, що вводите правильний ключ безпеки.",
-        "recovery_key_mismatch_title": "Хибний ключ безпеки",
+        "phrase_forgotten_text": "Якщо ви забули фразу безпеки, <button1>скористайтеся ключем відновлення</button1> або <button2>налаштуйте нові параметри відновлення</button2>",
+        "recovery_key_mismatch_description": "Не вдалося розшифрувати резервну копію цим ключем відновлення: переконайтеся, що вводите правильний ключ відновлення.",
+        "recovery_key_mismatch_title": "Невідповідність ключа відновлення",
         "restore_failed_error": "Не вдалося відновити резервну копію"
     },
     "right_panel": {
-        "add_integrations": "Додати віджети, мости та ботів",
+        "add_integrations": "Додати розширення",
+        "add_topic": "Додати тему",
+        "extensions_button": "Розширення",
+        "extensions_empty_description": "Виберіть “%(addIntegrations)s”, щоб переглянути та додати розширення до цієї кімнати",
+        "extensions_empty_title": "Підвищуйте продуктивність завдяки більшій кількості інструментів, віджетів і ботів",
         "files_button": "Файли",
         "pinned_messages": {
+            "empty_description": "Виберіть повідомлення та оберіть “%(pinAction)s”, щоб включити його сюди.",
+            "empty_title": "Закріпіть важливі повідомлення, щоб їх було легко знайти",
+            "header": {
+                "one": "1 закріплене повідомлення",
+                "few": "%(count)s закріплені повідомлення",
+                "many": "%(count)s закріплених повідомлень"
+            },
             "limits": {
                 "other": "Закріпити можна до %(count)s віджетів"
-            }
+            },
+            "menu": "Відкрити меню",
+            "release_announcement": {
+                "close": "Ok",
+                "description": "Усі закріплені повідомлення можна знайти тут. Наведіть курсор на будь-яке повідомлення і виберіть «Закріпити», щоб додати його.",
+                "title": "Усі нові закріплені повідомлення"
+            },
+            "reply_thread": "Відповісти в <link>гілці повідомлення</link>",
+            "unpin_all": {
+                "button": "Відкріпити всі повідомлення",
+                "content": "Переконайтеся, що ви дійсно хочете вилучити всі закріплені повідомлення. Це незворотна дія.",
+                "title": "Відкріпити всі повідомлення?"
+            },
+            "view": "Переглянути у стрічці"
         },
-        "pinned_messages_button": "Закріплені",
+        "pinned_messages_button": "Закріплені повідомлення",
         "poll": {
             "active_heading": "Активні опитування",
             "empty_active": "У цій кімнаті немає активних опитувань",
@@ -1677,7 +1888,7 @@
             "view_in_timeline": "Переглянути опитування у стрічці",
             "view_poll": "Переглянути опитування"
         },
-        "polls_button": "Історія опитувань",
+        "polls_button": "Опитування",
         "room_summary_card": {
             "title": "Відомості про кімнату"
         },
@@ -1706,6 +1917,7 @@
             "forget": "Забути кімнату",
             "low_priority": "Неважливі",
             "mark_read": "Позначити прочитаним",
+            "mark_unread": "Позначити непрочитаним",
             "notifications_default": "Збігається з типовим налаштуванням",
             "notifications_mute": "Вимкнути сповіщення кімнати",
             "title": "Параметри кімнати",
@@ -1748,8 +1960,16 @@
         "forget_room": "Забути цю кімнату",
         "forget_space": "Забути цей простір",
         "header": {
+            "n_people_asking_to_join": {
+                "one": "Просить доєднатись",
+                "few": "%(count)s користувачі просять доєднатись",
+                "many": "%(count)s користувачів просять доєднатись"
+            },
             "room_is_public": "Ця кімната загальнодоступна"
         },
+        "header_avatar_open_settings_label": "Відкрити налаштування кімнати",
+        "header_face_pile_tooltip": "Люди",
+        "header_untrusted_label": "Недовірений",
         "inaccessible": "Ця кімната або простір на разі не доступні.",
         "inaccessible_name": "%(roomName)s зараз офлайн.",
         "inaccessible_subtitle_1": "Повторіть спробу пізніше, або запитайте у кімнати або простору перевірку, чи маєте ви доступ.",
@@ -1772,10 +1992,9 @@
             "you_created": "Ви створили кімнату."
         },
         "invite_email_mismatch_suggestion": "Поширте цю адресу е-пошти у налаштуваннях, щоб отримувати запрошення безпосередньо в %(brand)s.",
-        "invite_reject_ignore": "Відхилити й нехтувати користувачем",
         "invite_sent_to_email": "Це запрошення було надіслано на %(email)s",
         "invite_sent_to_email_room": "Це запрошення до %(roomName)s було надіслане на %(email)s",
-        "invite_subtitle": "<userName/> запрошує вас",
+        "invite_subtitle": "Запрошено <userName/>",
         "invite_this_room": "Запросити до цієї кімнати",
         "invite_title": "Бажаєте приєднатися до %(roomName)s?",
         "inviter_unknown": "Невідомо",
@@ -1797,6 +2016,8 @@
         "kicked_by": "Вас вилучено користувачем %(memberName)s",
         "kicked_from_room_by": "%(memberName)s вилучає вас із %(roomName)s",
         "knock_cancel_action": "Скасувати запит",
+        "knock_denied_subtitle": "Так як вам було відмовлено у доступі, ви не можете доєднатись знову доки вас не запросить адміністратор чи модератор групи.",
+        "knock_denied_title": "Вам було відмовлено у доступі",
         "knock_message_field_placeholder": "Повідомлення (необов'язково)",
         "knock_prompt": "Надіслати запит на приєднання?",
         "knock_prompt_name": "Надіслати запит на приєднання до %(roomName)s?",
@@ -1816,11 +2037,25 @@
         "not_found_title": "Такої кімнати або простору не існує.",
         "not_found_title_name": "%(roomName)s не існує.",
         "peek_join_prompt": "Ви попередньо переглядаєте %(roomName)s. Бажаєте приєднатися?",
+        "pinned_message_badge": "Закріплене повідомлення",
+        "pinned_message_banner": {
+            "button_close_list": "Закрити список",
+            "button_view_all": "Подивитись все",
+            "description": "У цій кімнаті є закріплені повідомлення. Натисніть, щоб переглянути їх.",
+            "go_to_message": "Переглянути закріплене повідомлення у стрічці часу.",
+            "title": "<bold>%(index)s з %(length)s</bold> закріплених повідомлень"
+        },
         "read_topic": "Натисніть, щоб побачити тему",
         "rejecting": "Відхилення запрошення…",
         "rejoin_button": "Перепід'єднатись",
         "search": {
             "all_rooms_button": "Вибрати всі кімнати",
+            "placeholder": "Пошук повідомлень…",
+            "summary": {
+                "one": "Знайдено 1 результат для \"<query/>\"",
+                "few": "Знайдено %(count)s результати для \"<query/>\"",
+                "many": "Знайдено %(count)s результатів для \"<query/>\""
+            },
             "this_room_button": "Шукати цю кімнату"
         },
         "status_bar": {
@@ -1853,38 +2088,85 @@
             },
             "uploading_single_file": "Вивантаження %(filename)s"
         },
+        "video_room": "Ця кімната є відеокімнатою",
         "waiting_for_join_subtitle": "Після того, як запрошені користувачі приєднаються до %(brand)s, ви зможете спілкуватися в бесіді, а кімната буде наскрізно зашифрована",
         "waiting_for_join_title": "Очікування приєднання користувача до %(brand)s"
     },
     "room_list": {
         "add_room_label": "Додати кімнату",
         "add_space_label": "Додати простір",
+        "appearance": "Вигляд",
         "breadcrumbs_empty": "Немає недавно відвіданих кімнат",
         "breadcrumbs_label": "Недавно відвідані кімнати",
+        "empty": {
+            "no_chats": "Ще немає бесід",
+            "no_chats_description": "Почніть користування, надіславши комусь повідомлення або створивши кімнату",
+            "no_chats_description_no_room_rights": "Розпочніть користування, написавши комусь повідомлення",
+            "no_favourites": "У вас ще немає обраних бесід",
+            "no_favourites_description": "Ви можете додати бесіду до обраних у її налаштуваннях",
+            "no_people": "У вас ще немає особистих бесід",
+            "no_people_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди",
+            "no_rooms": "Ви ще не входили до кімнат",
+            "no_rooms_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди",
+            "no_unread": "Вітаємо! У вас немає непрочитаних повідомлень",
+            "show_chats": "Показати всі бесіди"
+        },
         "failed_add_tag": "Не вдалось додати до кімнати мітку %(tagName)s",
         "failed_remove_tag": "Не вдалося прибрати з кімнати мітку %(tagName)s",
         "failed_set_dm_tag": "Не вдалося встановити мітку особистого повідомлення",
+        "filters": {
+            "favourite": "Обрані",
+            "people": "Люди",
+            "rooms": "Кімнати",
+            "unread": "Непрочитані"
+        },
         "home_menu_label": "Параметри домівки",
         "join_public_room_label": "Приєднатись до загальнодоступної кімнати",
         "joining_rooms_status": {
             "one": "Приєднання до %(count)s кімнати",
             "other": "Приєднання до %(count)s кімнат"
         },
+        "list_title": "Список кімнат",
+        "more_options": {
+            "copy_link": "Копіювати посилання на кімнату",
+            "favourited": "Обране",
+            "leave_room": "Вийти з кімнати",
+            "low_priority": "Неважливі",
+            "mark_read": "Позначити прочитаним",
+            "mark_unread": "Позначити непрочитаним"
+        },
         "notification_options": "Параметри сповіщень",
+        "open_space_menu": "Відкрити меню простору",
+        "primary_filters": "Фільтри списку кімнат",
         "redacting_messages_status": {
             "one": "Триває видалення повідомлень в %(count)s кімнаті",
             "other": "Триває видалення повідомлень у %(count)s кімнатах"
         },
+        "room": {
+            "more_options": "Інші опції",
+            "open_room": "Відкрити кімнату %(roomName)s"
+        },
+        "room_options": "Параметри кімнати",
         "show_less": "Згорнути",
+        "show_message_previews": "Показати попередній перегляд повідомлень",
         "show_n_more": {
             "other": "Показати ще %(count)s",
             "one": "Показати ще %(count)s"
         },
         "show_previews": "Показувати попередній перегляд повідомлень",
+        "sort": "Сортувати",
         "sort_by": "Упорядкувати за",
         "sort_by_activity": "Активністю",
         "sort_by_alphabet": "А-Я",
+        "sort_type": {
+            "activity": "Діяльність",
+            "atoz": "А-Я"
+        },
         "sort_unread_first": "Спочатку показувати кімнати з непрочитаними повідомленнями",
+        "space_menu": {
+            "home": "Домівка простору",
+            "space_settings": "Налаштування простору"
+        },
         "space_menu_label": "%(spaceName)s — меню",
         "sublist_options": "Параметри переліку",
         "suggested_rooms_heading": "Пропоновані кімнати"
@@ -1956,6 +2238,8 @@
             "error_deleting_alias_description": "Помилка видалення такої адреси. Можливо, вона не існує або стався тимчасовий збій.",
             "error_deleting_alias_description_forbidden": "У вас немає дозволу видаляти адресу.",
             "error_deleting_alias_title": "Помилка видалення адреси",
+            "error_publishing": "Неможливо опублікувати кімнату",
+            "error_publishing_detail": "Виникла помилка під час публікації цієї кімнати",
             "error_save_space_settings": "Не вдалося зберегти налаштування простору.",
             "error_updating_alias_description": "Помилка оновлення запасних адрес кімнати. Можливо, сервер цього не дозволяє або стався тимчасовий збій.",
             "error_updating_canonical_alias_description": "Помилка оновлення головної адреси кімнати. Можливо, сервер цього не дозволяє або стався тимчасовий збій.",
@@ -1992,6 +2276,12 @@
             "upload_sound_label": "Вивантажити власний звук",
             "uploaded_sound": "Вивантажені звуки"
         },
+        "people": {
+            "knock_empty": "Запитів немає",
+            "knock_section": "Запит на приєднання",
+            "see_less": "Показати менше",
+            "see_more": "Показати більше"
+        },
         "permissions": {
             "add_privileged_user_description": "Надайте одному або кільком користувачам у цій кімнаті більше повноважень",
             "add_privileged_user_filter_placeholder": "Пошук користувачів у цій кімнаті…",
@@ -2103,7 +2393,9 @@
             },
             "join_rule_upgrade_upgrading_room": "Поліпшення кімнати",
             "public_without_alias_warning": "Щоб посилатись на цю кімнату, додайте їй адресу.",
-            "strict_encryption": "Ніколи не надсилати зашифровані повідомлення до незвірених сеансів у цій кімнаті з цього сеансу",
+            "publish_room": "Зробити цю кімнату видимою в каталозі загальнодоступних кімнат.",
+            "publish_space": "Зробити цей простір видимим у каталозі загальнодоступних кімнат.",
+            "strict_encryption": "Надсилати повідомлення лише верифікованим користувачам.",
             "title": "Безпека й приватність"
         },
         "title": "Налаштування кімнати - %(roomName)s",
@@ -2173,6 +2465,8 @@
             "access_token_detail": "Токен доступу надає повний доступ до вашого облікового запису. Не передавайте його нікому.",
             "brand_version": "Версія %(brand)s:",
             "clear_cache_reload": "Очистити кеш та перезавантажити",
+            "crypto_version": "Криптоверсія:",
+            "dialog_title": "<strong>Налаштування:</strong> Довідка та Про нас",
             "help_link": "Якщо необхідна допомога у користуванні %(brand)s'ом, клацніть <a>тут</a>.",
             "homeserver": "Домашній сервер <code>%(homeserverUrl)s</code>",
             "identity_server": "Сервер ідентифікації <code>%(identityServerUrl)s</code>",
@@ -2181,21 +2475,34 @@
         }
     },
     "settings": {
+        "account": {
+            "dialog_title": "<strong>Налаштування:</strong> Обліковий запис",
+            "title": "Обліковий запис"
+        },
         "all_rooms_home": "Показувати всі кімнати в Домівці",
         "all_rooms_home_description": "Всі кімнати, до яких ви приєднались, з'являться в домівці.",
         "always_show_message_timestamps": "Завжди показувати часові позначки повідомлень",
         "appearance": {
+            "bundled_emoji_font": "Використовувати вбудований шрифт емодзі",
+            "compact_layout": "Показувати компактні текст і повідомлення",
+            "compact_layout_description": "Щоб користуватися цією функцією потрібно вибрати сучасний макет.",
             "custom_font": "Використовувати системний шрифт",
             "custom_font_description": "Вкажіть назву шрифту, встановленого у вашій системі, й %(brand)s спробує його використати.",
             "custom_font_name": "Ім’я системного шрифту",
             "custom_font_size": "Використовувати нетиповий розмір",
-            "custom_theme_error_downloading": "Помилка завантаження відомостей теми.",
+            "custom_theme_add": "Додати власну тему",
+            "custom_theme_downloading": "Завантаження користувацької теми…",
+            "custom_theme_error_downloading": "Помилка завантаження теми",
+            "custom_theme_help": "Введіть URL-адресу користувацької теми, яку хочете застосувати.",
             "custom_theme_invalid": "Хибна схема теми.",
+            "dialog_title": "<strong>Налаштування:</strong> Вигляд",
             "font_size": "Розмір шрифту",
+            "font_size_default": "%(fontSize)s (усталено)",
+            "high_contrast": "Висока контрастність",
             "image_size_default": "Типовий",
             "image_size_large": "Великі",
             "layout_bubbles": "Бульбашки повідомлень",
-            "layout_irc": "IRC (Експериментально)",
+            "layout_irc": "IRC (експериментальний)",
             "match_system_theme": "Тема системи",
             "timeline_image_size": "Розмір зображень у стрічці"
         },
@@ -2206,9 +2513,78 @@
         "code_block_expand_default": "Розгортати блоки коду одразу",
         "code_block_line_numbers": "Нумерувати рядки блоків коду",
         "disable_historical_profile": "Показувати поточне зображення профілю та ім'я для користувачів у історії повідомлень",
+        "discovery": {
+            "title": "Як вас знайти"
+        },
         "emoji_autocomplete": "Увімкнути пропонування емодзі при друкуванні",
         "enable_markdown": "Увімкнути Markdown",
         "enable_markdown_description": "Розпочинати повідомлення з <code>/plain</code>, щоб надіслати без markdown.",
+        "encryption": {
+            "advanced": {
+                "breadcrumb_first_description": "Дані вашого облікового запису, контакти, налаштування й бесіди будуть збережені",
+                "breadcrumb_page": "Скинути шифрування",
+                "breadcrumb_second_description": "Ви втратите історію повідомлень, яка зберігається лише на сервері",
+                "breadcrumb_third_description": "Вам потрібно буде верифікувати всі наявні пристрої та контакти ще раз",
+                "breadcrumb_title": "Ви впевнені, що хочете скинути свою особистість?",
+                "breadcrumb_title_forgot": "Забули ключ відновлення? Вам потрібно скинути свою особистість.",
+                "breadcrumb_title_sync_failed": "Не вдалося синхронізувати сховище ключів. Вам потрібно скинути свою ідентичність.",
+                "breadcrumb_warning": "Робіть це, лише якщо ви впевнені, що ваш обліковий запис скомпрометований.",
+                "details_title": "Подробиці шифрування",
+                "do_not_close_warning": "Не закривайте це вікно, поки не завершиться скидання",
+                "export_keys": "Експорт ключів",
+                "import_keys": "Імпорт ключів",
+                "other_people_device_description": "Попередження: користувачі, які не пройшли явну верифікацію з вами (наприклад, за допомогою емодзі), не отримають ваші зашифровані повідомлення. Також неверифіковані пристрої верифікованих користувачів не отримуватимуть ваші зашифровані повідомлення.",
+                "other_people_device_label": "У кімнатах з увімкненим шифруванням надсилати повідомлення лише верифікованим користувачам",
+                "other_people_device_title": "Пристрої інших людей",
+                "reset_identity": "Скинути криптографічну ідентичність",
+                "reset_in_progress": "Виконується скидання...",
+                "session_id": "ID сеансу:",
+                "session_key": "Ключ сеансу:",
+                "title": "Додатково"
+            },
+            "delete_key_storage": {
+                "breadcrumb_page": "Видалити сховище ключів",
+                "confirm": "Видалити сховище ключів",
+                "description": "Видалення сховища ключів вилучить вашу криптографічну ідентичність і ключі повідомлень з сервера, а також вимкне такі функції безпеки:",
+                "list_first": "Ви не матимете історії зашифрованих повідомлень на нових пристроях",
+                "list_second": "Ви втратите доступ до своїх зашифрованих повідомлень, якщо ви вийдете з %(brand)s на всіх пристроях",
+                "title": "Ви впевнені, що хочете вимкнути зберігання ключів і видалити їх?"
+            },
+            "device_not_verified_button": "Верифікувати цей пристрій",
+            "device_not_verified_description": "Верифікуйте цей пристрій, щоб переглянути налаштування шифрування.",
+            "device_not_verified_title": "Пристрій не верифіковано",
+            "dialog_title": "<strong>Налаштування:</strong> Шифрування",
+            "key_storage": {
+                "allow_key_storage": "Дозволити зберігання ключів",
+                "description": "Надійно зберігайте свою криптографічну ідентичність і ключі повідомлень на сервері. Це дозволить вам переглядати історію повідомлень на будь-яких нових пристроях. <a>Докладніше</a>",
+                "title": "Сховище ключів"
+            },
+            "recovery": {
+                "change_recovery_confirm_button": "Підтвердити новий ключ відновлення",
+                "change_recovery_confirm_description": "Введіть новий ключ відновлення нижче, щоб завершити. Ваш старий ключ більше не працюватиме.",
+                "change_recovery_confirm_title": "Введіть новий ключ відновлення",
+                "change_recovery_key": "Змінити ключ відновлення",
+                "change_recovery_key_description": "Запишіть цей новий ключ відновлення в безпечне місце. Потім натисніть Продовжити, щоб підтвердити зміну.",
+                "change_recovery_key_title": "Змінити ключ відновлення?",
+                "description": "Відновіть свою криптографічну особистість та історію повідомлень за допомогою ключа відновлення, якщо ви втратили всі наявні пристрої.",
+                "enter_key_error": "Ви ввели некоректний ключ відновлення.",
+                "enter_recovery_key": "Введіть ключ відновлення",
+                "forgot_recovery_key": "Забули ключ відновлення?",
+                "key_storage_warning": "Ваше сховище ключів не синхронізовано. Натисніть кнопку нижче, щоб усунути проблему.",
+                "save_key_description": "Не діліться цим ні з ким!",
+                "save_key_title": "Ключ відновлення",
+                "set_up_recovery": "Налаштувати відновлення",
+                "set_up_recovery_confirm_button": "Завершити налаштування",
+                "set_up_recovery_confirm_description": "Введіть ключ відновлення, показаний на попередньому екрані, щоб завершити налаштування відновлення.",
+                "set_up_recovery_confirm_title": "Введіть ключ відновлення для підтвердження",
+                "set_up_recovery_description": "Сховище ключів захищено ключем відновлення. Якщо вам потрібен новий ключ відновлення після налаштування, ви можете створити його повторно, вибравши ‘%(changeRecoveryKeyButton)s’.",
+                "set_up_recovery_save_key_description": "Запишіть цей ключ відновлення в безпечне місце, наприклад, у менеджері паролів, зашифровану примітку або фізично у безпечне місце.",
+                "set_up_recovery_save_key_title": "Збережіть ключ відновлення в надійному місці",
+                "set_up_recovery_secondary_description": "Після натискання кнопки Продовжити ми згенеруємо для вас ключ відновлення.",
+                "title": "Відновлення"
+            },
+            "title": "Шифрування"
+        },
         "general": {
             "account_management_section": "Керування обліковим записом",
             "account_section": "Обліковий запис",
@@ -2221,6 +2597,14 @@
             "add_msisdn_dialog_title": "Додати номер телефону",
             "add_msisdn_instructions": "Текстове повідомлення надіслано на номер +%(msisdn)s. Введіть код перевірки з нього.",
             "add_msisdn_misconfigured": "Неправильно налаштовано додавання / зв'язування з потоком MSISDN",
+            "allow_spellcheck": "Дозволити перевірку правопису",
+            "application_language": "Мова застосунку",
+            "application_language_reload_hint": "Застосунок перезапуститься після вибору іншої мови",
+            "avatar_remove_progress": "Вилучення зображення...",
+            "avatar_save_progress": "Вивантаження зображення...",
+            "avatar_upload_error_text": "Формат файлу не підтримується або зображення більше ніж %(size)s.",
+            "avatar_upload_error_text_generic": "Формат файлу може не підтримуватися.",
+            "avatar_upload_error_title": "Не вдалося завантажити зображення аватара",
             "confirm_adding_email_body": "Клацніть на кнопку внизу, щоб підтвердити додавання цієї адреси е-пошти.",
             "confirm_adding_email_title": "Підтвердити додавання е-пошти",
             "deactivate_confirm_body": "Ви впевнені, що бажаєте деактивувати обліковий запис? Ця дія безповоротна.",
@@ -2236,10 +2620,14 @@
             "deactivate_confirm_erase_label": "Сховати мої повідомлення від нових учасників",
             "deactivate_section": "Деактивувати обліковий запис",
             "deactivate_warning": "Деактивація вашого облікового запису — це незворотна дія, будьте обережні!",
-            "discovery_email_empty": "Опції знаходження з'являться тут, коли ви додасте е-пошту вгорі.",
+            "discovery_email_empty": "Параметри виявлення з’являться, коли ви додасте електронну адресу.",
             "discovery_email_verification_instructions": "Перевірте посилання у теці «Вхідні»",
-            "discovery_msisdn_empty": "Опції знаходження з'являться тут, коли ви додасте номер телефону вгорі.",
+            "discovery_msisdn_empty": "Параметри виявлення з’являться, коли ви додасте номер телефону.",
             "discovery_needs_terms": "Погодьтесь з Умовами надання послуг сервера ідентифікації (%(serverName)s), щоб дозволити знаходити вас за адресою електронної пошти або за номером телефону.",
+            "discovery_needs_terms_title": "Дайте змогу людям знаходити вас",
+            "display_name": "Показуване ім'я",
+            "display_name_error": "Не вдалося встановити показуване ім'я",
+            "email_adding_unsupported_by_hs": "Цей домашній сервер не підтримує додавання адрес електронної пошти до вашого облікового запису.",
             "email_address_in_use": "Ця е-пошта вже використовується",
             "email_address_label": "Адреса е-пошти",
             "email_not_verified": "Ваша адреса е-пошти ще не підтверджена",
@@ -2264,7 +2652,9 @@
             "error_share_msisdn_discovery": "Не вдалося надіслати телефонний номер",
             "identity_server_no_token": "Токен доступу до ідентифікації не знайдено",
             "identity_server_not_set": "Сервер ідентифікації не налаштовано",
-            "language_section": "Мова та регіон",
+            "invalid_phone_number": "Вказано недійсний номер телефону.",
+            "language_section": "Мова",
+            "msisdn_adding_unsupported_by_hs": "Цей домашній сервер не підтримує додавання телефонних номерів до вашого облікового запису.",
             "msisdn_in_use": "Цей телефонний номер вже використовується",
             "msisdn_label": "Телефонний номер",
             "msisdn_verification_field_label": "Код перевірки",
@@ -2273,11 +2663,16 @@
             "oidc_manage_button": "Керувати обліковим записом",
             "password_change_section": "Встановити новий пароль облікового запису…",
             "password_change_success": "Ваш пароль успішно змінено.",
+            "personal_info": "Особиста інформація",
+            "profile_subtitle": "Ось як вас бачать інші в застосунку.",
+            "profile_subtitle_oidc": "Вашим обліковим записом окремо керує постачальник даних особистості, тому деякі ваші особисті дані не можуть бути змінені тут.",
             "remove_email_prompt": "Вилучити %(email)s?",
             "remove_msisdn_prompt": "Вилучити %(phone)s?",
-            "spell_check_locale_placeholder": "Вибрати локаль"
+            "spell_check_locale_placeholder": "Вибрати локаль",
+            "unable_to_load_emails": "Не вдалося завантажити адреси електронної пошти",
+            "unable_to_load_msisdns": "Не вдалося завантажити номери телефонів",
+            "username": "Ім'я користувача"
         },
-        "image_thumbnails": "Показувати попередній перегляд зображень",
         "inline_url_previews_default": "Увімкнути вбудований перегляд гіперпосилань за умовчанням",
         "inline_url_previews_room": "Увімкнути попередній перегляд гіперпосилань за умовчанням для учасників цієї кімнати",
         "inline_url_previews_room_account": "Увімкнути попередній перегляд гіперпосилань в цій кімнаті (стосується тільки вас)",
@@ -2299,21 +2694,21 @@
                 "enter_phrase_description": "Введіть фразу безпеки, відому лише вам, бо вона оберігатиме ваші дані. Задля безпеки, використайте щось інше ніж пароль вашого облікового запису.",
                 "enter_phrase_title": "Ввести фразу безпеки",
                 "enter_phrase_to_confirm": "Введіть свою фразу безпеки ще раз для підтвердження.",
-                "generate_security_key_description": "Ми створимо ключ безпеки. Зберігайте його в надійному місці, скажімо в менеджері паролів чи сейфі.",
-                "generate_security_key_title": "Згенерувати ключ безпеки",
+                "generate_security_key_description": "Ми згенеруємо ключ відновлення. Зберігайте його в надійному місці, скажімо в менеджері паролів чи сейфі.",
+                "generate_security_key_title": "Згенерувати ключ відновлення",
                 "pass_phrase_match_failed": "Не збігається.",
                 "pass_phrase_match_success": "Збіг!",
                 "phrase_strong_enough": "Чудово! Фраза безпеки досить надійна.",
                 "secret_storage_query_failure": "Не вдалося дізнатися стан таємного сховища",
-                "security_key_safety_reminder": "Зберігайте ключ безпеки в надійному місці, скажімо в менеджері паролів чи сейфі, бо ключ оберігає ваші зашифровані дані.",
+                "security_key_safety_reminder": "Розмістіть ключ відновлення в надійному місці, скажімо в менеджері паролів або сейфі, оскільки він захищає ваші зашифровані дані.",
                 "set_phrase_again": "Поверніться, щоб налаштувати заново.",
                 "settings_reminder": "Ввімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях.",
                 "title_confirm_phrase": "Підвердьте фразу безпеки",
-                "title_save_key": "Збережіть свій ключ безпеки",
+                "title_save_key": "Збережіть ключ відновлення",
                 "title_set_phrase": "Вкажіть фразу безпеки",
                 "unable_to_setup": "Не вдалося налаштувати таємне сховище",
                 "use_different_passphrase": "Використати іншу парольну фразу?",
-                "use_phrase_only_you_know": "Захистіть резервну копію відомою лише вам таємною фразою. Можете також зберегти ключ безпеки."
+                "use_phrase_only_you_know": "Використовуйте секретну фразу, відому лише вам, і за бажанням збережіть ключ відновлення для резервного копіювання."
             }
         },
         "key_export_import": {
@@ -2331,12 +2726,28 @@
             "phrase_strong_enough": "Чудово! Цю парольна фраза видається достатньо надійною"
         },
         "keyboard": {
+            "dialog_title": "<strong>Налаштування:</strong> Клавіатура",
             "title": "Клавіатура"
         },
+        "labs": {
+            "dialog_title": "<strong>Налаштування:</strong> Лабораторії"
+        },
+        "labs_mjolnir": {
+            "dialog_title": "<strong>Налаштування:</strong> Ігноровані користувачі"
+        },
+        "media_preview": {
+            "hide_avatars": "Сховати аватари кімнати та запрошувача",
+            "hide_media": "Завжди ховати",
+            "media_preview_description": "Сховане медіа завжди можна переглянути, натиснувши на нього",
+            "media_preview_label": "Показувати медіа у стрічці",
+            "show_in_private": "У приватних кімнатах",
+            "show_media": "Завжди показувати"
+        },
         "notifications": {
             "default_setting_description": "Цей параметр буде застосовано усталеним до всіх ваших кімнат.",
             "default_setting_section": "Я хочу отримувати сповіщення про (типове налаштування)",
             "desktop_notification_message_preview": "Показувати попередній перегляд повідомлень у сповіщеннях комп'ютера",
+            "dialog_title": "<strong>Налаштування:</strong> Сповіщення",
             "email_description": "Отримуйте зведення пропущених сповіщень на електронну пошту",
             "email_section": "Зведення електронною поштою",
             "email_select": "Виберіть, на які адреси ви хочете отримувати зведення. Керуйте адресами в <button>Загальних</button> налаштуваннях.",
@@ -2395,12 +2806,15 @@
             "code_blocks_heading": "Блоки коду",
             "compact_modern": "Використовувати компактний вигляд «Модерн»",
             "composer_heading": "Редактор",
+            "default_timezone": "Типовий браузера (%(timezone)s)",
+            "dialog_title": "<strong>Налаштування:</strong> Параметри",
             "enable_hardware_acceleration": "Увімкнути апаратне прискорення",
             "enable_tray_icon": "Згортати вікно до піктограми в лотку при закритті",
             "keyboard_heading": "Комбінації клавіш",
             "keyboard_view_shortcuts_button": "Щоб переглянути всі комбінації клавіш, <a>натисніть сюди</a>.",
             "media_heading": "Зображення, GIF та відео",
             "presence_description": "Діліться своєю активністю та станом з іншими.",
+            "publish_timezone": "Публікувати часовий пояс у загальнодоступному профілі",
             "rm_lifetime": "Тривалість маркеру прочитання (мс)",
             "rm_lifetime_offscreen": "Тривалість маркеру прочитання поза екраном (мс)",
             "room_directory_heading": "Каталог кімнат",
@@ -2408,59 +2822,26 @@
             "show_avatars_pills": "Показувати аватари у згадках користувачів, кімнат і подій",
             "show_polls_button": "Показувати кнопку опитування",
             "surround_text": "Обгортати виділений текст при введенні спеціальних символів",
-            "time_heading": "Формат часу"
+            "time_heading": "Формат часу",
+            "user_timezone": "Установити часовий пояс"
         },
         "prompt_invite": "Запитувати перед надсиланням запрошень на потенційно недійсні matrix ID",
         "replace_plain_emoji": "Автоматично замінювати простотекстові емодзі",
         "security": {
-            "4s_public_key_in_account_data": "у даних облікового запису",
-            "4s_public_key_status": "Таємне сховище відкритого ключа:",
             "analytics_description": "Поділіться анонімними даними, щоб допомагати нам виявляти проблеми. Нічого особистого. Ніяких третіх осіб.",
-            "backup_key_cached_status": "Резервну копію ключа кешовано:",
-            "backup_key_stored_status": "Резервну копію ключа розміщено:",
-            "backup_key_unexpected_type": "несподіваний тип",
-            "backup_key_well_formed": "добре сформований",
-            "backup_keys_description": "Резервне копіювання ключів шифрування з даними вашого облікового запису на випадок втрати доступу до сеансів. Ваші ключі будуть захищені унікальним ключем безпеки.",
             "bulk_options_accept_all_invites": "Прийняти всі %(invitedRooms)s запрошення",
             "bulk_options_reject_all_invites": "Відхилити запрошення до усіх %(invitedRooms)s",
             "bulk_options_section": "Масові дії",
-            "cross_signing_cached": "кешовано локально",
-            "cross_signing_homeserver_support": "Підтримка функції домашнім сервером:",
-            "cross_signing_homeserver_support_exists": "існує",
-            "cross_signing_in_4s": "у таємному сховищі",
-            "cross_signing_in_memory": "у пам'яті",
-            "cross_signing_master_private_Key": "Головний приватний ключ:",
-            "cross_signing_not_cached": "не знайдено локально",
-            "cross_signing_not_found": "не знайдено",
-            "cross_signing_not_in_4s": "не знайдено у сховищі",
-            "cross_signing_not_stored": "не збережено",
-            "cross_signing_private_keys": "Приватні ключі перехресного підписування:",
-            "cross_signing_public_keys": "Відкриті ключі перехресного підписування:",
-            "cross_signing_self_signing_private_key": "Самопідписаний приватний ключ:",
-            "cross_signing_user_signing_private_key": "Приватний ключ підпису користувача:",
-            "cryptography_section": "Криптографія",
-            "delete_backup": "Видалити резервну копію",
-            "delete_backup_confirm_description": "Ви впевнені? Ви втратите ваші зашифровані повідомлення якщо копія ключів не була створена коректно.",
+            "dehydrated_device_description": "Функція офлайн-пристрою дозволяє отримувати зашифровані повідомлення, навіть якщо ви не ввійшли в обліковий запис на жодному пристрої",
+            "dehydrated_device_enabled": "Офлайн-пристрій увімкнено",
+            "dialog_title": "<strong>Налаштування:</strong> Безпека й приватність",
             "e2ee_default_disabled_warning": "Адміністратором вашого сервера було вимкнено автоматичне наскрізне шифрування у приватних кімнатах і особистих повідомленнях.",
             "enable_message_search": "Увімкнути шукання повідомлень у зашифрованих кімнатах",
             "encryption_section": "Шифрування",
-            "error_loading_key_backup_status": "Не вдалося завантажити стан резервного копіювання ключа",
-            "export_megolm_keys": "Експортувати ключі наскрізного шифрування кімнат",
             "ignore_users_empty": "Ви не маєте нехтуваних користувачів.",
             "ignore_users_section": "Нехтувані користувачі",
-            "import_megolm_keys": "Імпортувати ключі кімнат наскрізного шифрування",
-            "key_backup_active": "Під час цього сеансу створюється резервна копія ваших ключів.",
-            "key_backup_active_version": "Активна версія резервної копії:",
-            "key_backup_active_version_none": "Вимкнено",
             "key_backup_algorithm": "Алгоритм:",
-            "key_backup_can_be_restored": "Ця резервна копія може бути відновлена в цьому сеансі",
-            "key_backup_complete": "Усі ключі збережено",
             "key_backup_connect": "Налаштувати цьому сеансу резервне копіювання ключів",
-            "key_backup_connect_prompt": "Налаштуйте цьому сеансу резервне копіювання, інакше при виході втратите ключі, доступні лише в цьому сеансі.",
-            "key_backup_in_progress": "Резервне копіювання %(sessionsRemaining)s ключів…",
-            "key_backup_inactive": "Цей сеанс <b>не створює резервну копію ваших ключів</b>, але у вас є резервна копія, з якої ви можете їх відновити.",
-            "key_backup_inactive_warning": "<b>Резервна копія ваших ключів не створюється з цього сеансу</b>.",
-            "key_backup_latest_version": "Остання версія резервної копії на сервері:",
             "message_search_disable_warning": "Якщо вимкнути, пошук не показуватиме повідомлень зашифрованих кімнат.",
             "message_search_disabled": "Безпечно локально кешувати зашифровані повідомлення щоб вони з'являлись у результатах пошуку.",
             "message_search_enabled": {
@@ -2480,19 +2861,14 @@
             "message_search_unsupported": "%(brand)s'ові бракує деяких складників, необхідних для безпечного локального кешування зашифрованих повідомлень. Якщо ви хочете поекспериментувати з цією властивістю, зберіть спеціальну збірку %(brand)s Desktop із <nativeLink>доданням пошукових складників</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s не може безпечно локально кешувати зашифровані повідомлення під час роботи у браузері. Користуйтесь <desktopLink>%(brand)s для комп'ютерів</desktopLink>, в якому зашифровані повідомлення з'являються у результатах пошуку.",
             "record_session_details": "Записуйте назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів",
-            "restore_key_backup": "Відновити з резервної копії",
-            "secret_storage_not_ready": "не готове",
-            "secret_storage_ready": "готове",
-            "secret_storage_status": "Таємне сховище:",
             "send_analytics": "Надсилати дані аналітики",
-            "session_id": "ID сеансу:",
-            "session_key": "Ключ сеансу:",
-            "strict_encryption": "Ніколи не надсилати зашифровані повідомлення до незвірених сеансів з цього сеансу"
+            "strict_encryption": "Надсилати повідомлення лише верифікованим користувачам"
         },
         "send_read_receipts": "Надсилати підтвердження прочитання",
         "send_read_receipts_unsupported": "Ваш сервер не підтримує вимкнення надсилання сповіщень про прочитання.",
         "send_typing_notifications": "Надсилати сповіщення про набирання тексту",
         "sessions": {
+            "best_security_note": "Для кращої безпеки перевірте свої сеанси та вийдіть із усіх сеансів, які ви більше не впізнаєте або не використовуєте.",
             "browser": "Браузер",
             "confirm_sign_out": {
                 "one": "Підтвердьте вихід із цього пристрою",
@@ -2517,6 +2893,7 @@
             "device_unverified_description_current": "Звірте свій поточний сеанс для посилення безпеки обміну повідомленнями.",
             "device_verified_description": "Цей сеанс готовий для безпечного обміну повідомленнями.",
             "device_verified_description_current": "Ваш поточний сеанс готовий до захищеного обміну повідомленнями.",
+            "dialog_title": "<strong>Налаштування:</strong> Сеанси",
             "error_pusher_state": "Не вдалося встановити стан push-служби",
             "error_set_name": "Не вдалося встановити назву сеансу",
             "filter_all": "Усі",
@@ -2533,6 +2910,7 @@
             "inactive_sessions_list_description": "Обміркуйте можливість виходу зі старих сеансів (%(inactiveAgeDays)s днів або більше), якими ви більше не користуєтесь.",
             "ip": "IP-адреса",
             "last_activity": "Остання активність",
+            "manage": "Керувати цим сеансом",
             "mobile_session": "Сеанс на мобільному",
             "n_sessions_selected": {
                 "one": "%(count)s сеанс вибрано",
@@ -2556,9 +2934,10 @@
             "security_recommendations_description": "Удоскональте безпеку свого облікового запису, дотримуючись цих порад.",
             "session_id": "ID сеансу",
             "show_details": "Показати подробиці",
-            "sign_in_with_qr": "Увійти за допомогою QR-коду",
+            "sign_in_with_qr": "Підключити новий пристрій",
             "sign_in_with_qr_button": "Показати QR-код",
-            "sign_in_with_qr_description": "Ви можете використовувати цей пристрій для входу на новому пристрої за допомогою QR-коду. Вам потрібно буде сканувати QR-код, показаний на цьому пристрої, своїм пристроєм, на якому ви вийшли.",
+            "sign_in_with_qr_description": "Використовуйте QR-код, щоб увійти на інший пристрій і налаштувати безпечний обмін повідомленнями.",
+            "sign_in_with_qr_unsupported": "Не підтримується постачальником облікового запису",
             "sign_out": "Вийти з цього сеансу",
             "sign_out_all_other_sessions": "Вийти з усіх інших сеансів (%(otherSessionsCount)s)",
             "sign_out_confirm_description": {
@@ -2598,7 +2977,9 @@
         "show_redaction_placeholder": "Показувати замісну позначку замість видалених повідомлень",
         "show_stickers_button": "Показати кнопку наліпок",
         "show_typing_notifications": "Сповіщати про друкування",
+        "showbold": "Показати всю діяльність у списку кімнат (крапки або кількість непрочитаних повідомлень)",
         "sidebar": {
+            "dialog_title": "<strong>Налаштування:</strong> Бічна панель",
             "metaspaces_favourites_description": "Групуйте всі свої улюблені кімнати та людей в одному місці.",
             "metaspaces_home_all_rooms": "Показати всі кімнати",
             "metaspaces_home_all_rooms_description": "Показати всі кімнати в домівці, навіть ті, що належать до просторів.",
@@ -2607,10 +2988,14 @@
             "metaspaces_orphans_description": "Групуйте всі свої кімнати, не приєднані до простору, в одному місці.",
             "metaspaces_people_description": "Групуйте всіх своїх людей в одному місці.",
             "metaspaces_subsection": "Показувати такі простори",
+            "metaspaces_video_rooms": "Відеокімнати та конференції",
+            "metaspaces_video_rooms_description": "Згрупувати всі приватні відеокімнати та конференції.",
+            "metaspaces_video_rooms_description_invite_extension": "На конференції можна запрошувати людей не з matrix.",
             "spaces_explainer": "Простір це спосіб групування кімнат та людей. Окрім просторів, які ви створюєте, ви також можете використовувати вже готові.",
             "title": "Бічна панель"
         },
         "start_automatically": "Автозапуск при вході в систему",
+        "tac_only_notifications": "Показувати сповіщення лише в центрі діяльності в гілках",
         "use_12_hour_format": "Показувати час у 12-годинному форматі (напр. 2:30 пп)",
         "use_command_enter_send_message": "Натисніть Command + Enter, щоб надіслати повідомлення",
         "use_command_f_search": "Command + F для пошуку в стрічці",
@@ -2624,6 +3009,7 @@
             "audio_output_empty": "Звуковий вивід не виявлено",
             "auto_gain_control": "Авторегулювання підсилення",
             "connection_section": "З'єднання",
+            "dialog_title": "<strong>Налаштування:</strong> Голос і відео",
             "echo_cancellation": "Пригнічення відлуння",
             "enable_fallback_ice_server": "Дозволити резервний сервер підтримки викликів (%(server)s)",
             "enable_fallback_ice_server_description": "Застосовується лише в тому випадку, якщо ваш домашній сервер не пропонує його. Ваша IP-адреса передаватиметься під час виклику.",
@@ -2642,8 +3028,12 @@
         "warning": "<w>ПОПЕРЕДЖЕННЯ:</w> <description/>"
     },
     "share": {
+        "link_copied": "Посилання скопійовано",
         "permalink_message": "Посилання на вибране повідомлення",
         "permalink_most_recent": "Посилання на останнє повідомлення",
+        "share_call": "Посилання для запрошення на конференцію",
+        "share_call_subtitle": "Посилання для зовнішніх користувачів для приєднання до виклику без облікового запису matrix:",
+        "title_link": "Поділитися посиланням",
         "title_message": "Поділитися повідомленням кімнати",
         "title_room": "Поділитись кімнатою",
         "title_user": "Поділитися користувачем"
@@ -2669,7 +3059,9 @@
         "devtools": "Відкриває вікно інструментів розробника",
         "discardsession": "Примусово відкидає поточний вихідний груповий сеанс у зашифрованій кімнаті",
         "error_invalid_rendering_type": "Помилка команди: неможливо знайти тип рендерингу (%(renderingType)s)",
+        "error_invalid_room": "Помилка команди: не вдається знайти кімнату (%(roomId)s)",
         "error_invalid_runfn": "Помилка команди: Неможливо виконати slash-команду.",
+        "error_invalid_user_in_room": "Не вдалося знайти користувача в кімнаті",
         "help": "Відбиває перелік команд із прикладами вжитку та описом",
         "help_dialog_title": "Допомога команди",
         "holdcall": "Переводить виклик у поточній кімнаті на утримання",
@@ -2712,8 +3104,6 @@
         "topic": "Показує чи встановлює тему кімнати",
         "topic_none": "Ця кімната не має теми.",
         "topic_room_error": "Не вдалося отримати тему кімнати: не вдалося знайти кімнату (%(roomId)s",
-        "tovirtual": "Переходить до віртуальної кімнати, якщо ваша кімната її має",
-        "tovirtual_not_found": "Ця кімната не має віртуальної кімнати",
         "unban": "Розблоковує користувача з указаним ID",
         "unflip": "Додає ┬──┬ ノ( ゜-゜ノ) на початку текстового повідомлення",
         "unholdcall": "Знімає виклик у поточній кімнаті з утримання",
@@ -2731,6 +3121,7 @@
         "view": "Перегляд кімнати з вказаною адресою",
         "whois": "Показує відомості про користувача"
     },
+    "sliding_sync_legacy_no_longer_supported": "Застаріла ковзна синхронізація більше не підтримується: вийдіть і ввійдіть знову, щоб увімкнути новий прапорець ковзної синхронізації",
     "space": {
         "add_existing_room_space": {
             "create": "Хочете додати нову кімнату натомість?",
@@ -2829,21 +3220,22 @@
         },
         "create_new_room_button": "Створити нову кімнату",
         "failed_querying_public_rooms": "Не вдалося зробити запит до загальнодоступних кімнат",
+        "failed_querying_public_spaces": "Не вдалося здійснити запит на загальноодступні простори",
         "group_chat_section_title": "Інші опції",
         "heading_with_query": "У якому контексті шукати \"%(query)s\"",
         "heading_without_query": "Пошук",
         "join_button_text": "Приєднатися до %(roomAddress)s",
         "keyboard_scroll_hint": "Використовуйте <arrows/>, щоб прокручувати",
-        "message_search_section_title": "Інші пошуки",
+        "messages_label": "Повідомлення",
         "other_rooms_in_space": "Інші кімнати в %(spaceName)s",
         "public_rooms_label": "Загальнодоступні кімнати",
+        "public_spaces_label": "Загальнодоступні простори",
         "recent_searches_section_title": "Недавні пошуки",
         "recently_viewed_section_title": "Недавно переглянуті",
         "remove_filter": "Вилучити фільтр пошуку для %(filter)s",
         "result_may_be_hidden_privacy_warning": "Деякі результати можуть бути приховані через приватність",
         "result_may_be_hidden_warning": "Деякі результати можуть бути приховані",
         "search_dialog": "Вікно пошуку",
-        "search_messages_hint": "Шукайте повідомлення за допомогою піктограми <icon/> вгорі кімнати",
         "spaces_title": "Ваші простори",
         "start_group_chat_button": "Розпочати нову бесіду"
     },
@@ -2880,12 +3272,20 @@
             "one": "%(count)s відповідь",
             "other": "%(count)s відповідей"
         },
+        "empty_description": "Використовувати “%(replyInThread)s” під час наведення курсора на повідомлення.",
+        "empty_title": "Гілки допомагають підтримувати тему розмов і їх легко відстежувати.",
         "error_start_thread_existing_relation": "Неможливо створити гілку з події з наявним відношенням",
+        "mark_all_read": "Позначити все прочитаним",
         "my_threads": "Мої гілки",
         "my_threads_description": "Показує всі гілки, де ви брали участь",
         "open_thread": "Відкрити гілку",
         "show_thread_filter": "Показати:"
     },
+    "threads_activity_centre": {
+        "header": "Діяльність у гілках",
+        "no_rooms_with_threads_notifs": "У вас ще немає кімнат зі сповіщеннями в гілках.",
+        "no_rooms_with_unread_threads": "У вас ще немає кімнат з непрочитаними гілками."
+    },
     "time": {
         "about_day_ago": "близько доби тому",
         "about_hour_ago": "близько години тому",
@@ -2927,8 +3327,21 @@
         },
         "creation_summary_dm": "%(creator)s створює цю приватну розмову.",
         "creation_summary_room": "%(creator)s створює й налаштовує кімнату.",
+        "decryption_failure": {
+            "blocked": "Відправник заблокував вам отримання цього повідомлення, оскільки ваш пристрій не верифіковано",
+            "historical_event_no_key_backup": "Історичні повідомлення недоступні на цьому пристрої",
+            "historical_event_unverified_device": "Щоб отримати доступ до історичних повідомлень, потрібно верифікувати цей пристрій",
+            "historical_event_user_not_joined": "Ви не маєте доступу до цього повідомлення",
+            "sender_identity_previously_verified": "Верифіковану ідентичність відправника скинуто",
+            "sender_unsigned_device": "Зашифровано пристроєм, який не верифіковано його власником.",
+            "unable_to_decrypt": "Не вдалося розшифрувати повідомлення"
+        },
+        "disambiguated_profile": "%(displayName)s(%(matrixId)s)",
         "download_action_decrypting": "Розшифрування",
         "download_action_downloading": "Завантаження",
+        "download_failed": "Не вдалося завантажити",
+        "download_failed_description": "Під час завантаження цього файлу сталася помилка",
+        "e2e_state": "Стан наскрізного шифрування",
         "edits": {
             "tooltip_label": "Змінено %(date)s. Натисніть, щоб переглянути зміни.",
             "tooltip_sub": "Натисніть, щоб переглянути зміни",
@@ -2939,6 +3352,7 @@
         "historical_messages_unavailable": "Ви не можете переглядати давніші повідомлення",
         "in_room_name": " в <strong>%(room)s</strong>",
         "io.element.widgets.layout": "%(senderName)s оновлює макет кімнати",
+        "late_event_separator": "Початково надіслано %(dateTime)s",
         "load_error": {
             "no_permission": "У вас нема дозволу на перегляд повідомлення за вказаною позицією в стрічці цієї кімнати.",
             "title": "Не вдалося завантажити позицію стрічки",
@@ -2981,7 +3395,7 @@
         },
         "m.file": {
             "error_decrypting": "Помилка розшифрування вкладення",
-            "error_invalid": "Пошкоджений файл%(extra)s"
+            "error_invalid": "Недійсний файл"
         },
         "m.image": {
             "error": "Не вдалося показати зображення через помилку",
@@ -3082,6 +3496,7 @@
             "left_reason": "%(targetName)s виходить з кімнати: %(reason)s",
             "no_change": "%(senderName)s нічого не змінює",
             "reject_invite": "%(targetName)s відхиляє запрошення",
+            "reject_invite_reason": "%(targetName)s відхиляє запрошення: %(reason)s",
             "remove_avatar": "%(senderName)s вилучає своє зображення профілю",
             "remove_name": "%(senderName)s вилучає свій псевдонім (%(oldDisplayName)s)",
             "set_avatar": "%(senderName)s встановлює зображення профілю",
@@ -3117,10 +3532,14 @@
             "sent": "%(senderName)s надсилає запрошення %(targetDisplayName)s приєднатися до кімнати."
         },
         "m.room.tombstone": "%(senderDisplayName)s поліпшує цю кімнату.",
-        "m.room.topic": "%(senderDisplayName)s змінює тему на %(topic)s.",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s змінює тему на %(topic)s.",
+            "removed": "%(senderDisplayName)s вилучає тему."
+        },
         "m.sticker": "%(senderDisplayName)s надсилає наліпку.",
         "m.video": {
-            "error_decrypting": "Помилка розшифрування відео"
+            "error_decrypting": "Помилка розшифрування відео",
+            "show_video": "Показати відео"
         },
         "m.widget": {
             "added": "%(senderName)s додає віджет %(widgetName)s",
@@ -3139,6 +3558,8 @@
             "label": "Дії з повідомленням",
             "view_in_room": "Дивитися в кімнаті"
         },
+        "message_timestamp_received_at": "Отримано: %(dateTime)s",
+        "message_timestamp_sent_at": "Надіслано: %(dateTime)s",
         "mjolnir": {
             "changed_rule_glob": "%(senderName)s змінює правило блокування зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
             "changed_rule_rooms": "%(senderName)s змінює правило блокування кімнат зі збігу з %(oldGlob)s на збіг з %(newGlob)s через %(reason)s",
@@ -3165,7 +3586,9 @@
         "pending_moderation_reason": "Повідомлення очікує модерування: %(reason)s",
         "reactions": {
             "add_reaction_prompt": "Додати реакцію",
-            "label": "%(reactors)s додає реакцію %(content)s"
+            "custom_reaction_fallback_label": "Спеціальна реакція",
+            "label": "%(reactors)s додає реакцію %(content)s",
+            "tooltip_caption": "реагує з %(shortName)s"
         },
         "read_receipt_title": {
             "one": "Переглянули %(count)s осіб",
@@ -3215,6 +3638,7 @@
                 "one": "%(severalUsers)sзмінили свої імена",
                 "other": "%(severalUsers)sзмінили свої імена %(count)s разів"
             },
+            "format": "%(nameList)s%(transitionList)s",
             "hidden_event": {
                 "one": "%(oneUser)sнадсилає приховане повідомлення",
                 "other": "%(oneUser)sнадсилає %(count)s прихованих повідомлень"
@@ -3349,6 +3773,10 @@
     "truncated_list_n_more": {
         "other": "І ще %(count)s..."
     },
+    "unsupported_browser": {
+        "description": "Якщо ви продовжите, деякі функції можуть перестати працювати, і існує ризик втрати даних у майбутньому. Оновіть браузер, щоб продовжити користуватись %(brand)s.",
+        "title": "%(brand)s не підтримує цей браузер"
+    },
     "unsupported_server_description": "Цей сервер використовує стару версію Matrix. Оновіть Matrix до %(version)s, щоб використовувати %(brand)s без помилок.",
     "unsupported_server_title": "Ваш сервер не підтримується",
     "update": {
@@ -3366,6 +3794,13 @@
         "toast_title": "Оновити %(brand)s",
         "unavailable": "Недоступний"
     },
+    "update_room_access_modal": {
+        "description": "Щоб створити посилання для поширення, зробіть кімнату <b>загальнодоступною</b> або дозвольте користувачам надсилати <b>запит приєднатися</b>. Це дозволить гостям приєднуватися без запрошення.",
+        "dont_change_description": "Якщо ви не хочете змінювати доступ до цієї кімнати, ви можете створити нову кімнату для посилання на виклик.",
+        "no_change": "Я не хочу змінювати рівень доступу.",
+        "revert_access_description": "(Це значення можна повернути до попереднього в налаштуваннях кімнати: <b>Безпека та приватність</b> /<b> Доступ</b>)",
+        "title": "Дозволити гостьовим користувачам приєднуватися до цієї кімнати"
+    },
     "upload_failed_generic": "Не вдалося вивантажити файл '%(fileName)s'.",
     "upload_failed_size": "Файл '%(fileName)s' перевищує ліміт розміру для відвантажень домашнього сервера",
     "upload_failed_title": "Помилка відвантаження",
@@ -3375,6 +3810,7 @@
         "error_files_too_large": "Ці файли є <b>надто великими</b> для відвантаження. Допустимий розмір файлів — %(limit)s.",
         "error_some_files_too_large": "Деякі файли є <b>надто великими</b> для відвантаження. Допустимий розмір файлів — %(limit)s.",
         "error_title": "Помилка вивантаження",
+        "not_image": "Обраний файл не є дійсним файлом зображення.",
         "title": "Вивантажити файли",
         "title_progress": "Вивантажити файли (%(current)s з %(total)s)",
         "upload_all_button": "Вивантажити всі",
@@ -3390,14 +3826,6 @@
         "ban_room_confirm_title": "Заблокувати в %(roomName)s",
         "ban_space_everything": "Заблокувати скрізь, де маю доступ",
         "ban_space_specific": "Заблокувати в частині того, куди маю доступ",
-        "count_of_sessions": {
-            "one": "%(count)s сеанс",
-            "other": "Сеансів: %(count)s"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 звірений сеанс",
-            "other": "Довірених сеансів: %(count)s"
-        },
         "deactivate_confirm_action": "Деактивувати користувача",
         "deactivate_confirm_description": "Деактивація цього користувача виведе їх з системи й унеможливить вхід у майбутньому. До того ж вони вийдуть з усіх кімнат, у яких перебувають. Ця дія безповоротна. Ви впевнені, що хочете деактивувати цього користувача?",
         "deactivate_confirm_title": "Деактивувати користувача?",
@@ -3408,15 +3836,13 @@
         "disinvite_button_room": "Відкликати запрошення до кімнати",
         "disinvite_button_room_name": "Скасувати запрошення до %(roomName)s",
         "disinvite_button_space": "Відкликати запрошення до простору",
-        "edit_own_devices": "Керувати пристроями",
         "error_ban_user": "Не вдалося заблокувати користувача",
         "error_deactivate": "Не вдалося деактивувати користувача",
         "error_kicking_user": "Не вдалося вилучити користувача",
         "error_mute_user": "Не вдалося заглушити користувача",
         "error_revoke_3pid_invite_description": "Не вдалось відкликати запрошення. Сервер може мати тимчасові збої або у вас немає достатніх дозволів щоб відкликати запрошення.",
         "error_revoke_3pid_invite_title": "Не вдалось відкликати запрошення",
-        "hide_sessions": "Сховати сеанси",
-        "hide_verified_sessions": "Сховати звірені сеанси",
+        "ignore_button": "Ігнорувати",
         "ignore_confirm_description": "Усі повідомлення та запрошення від цього користувача будуть приховані. Ви впевнені, що хочете їх нехтувати?",
         "ignore_confirm_title": "Нехтувати %(user)s",
         "invited_by": "Запрошення від %(sender)s",
@@ -3444,23 +3870,27 @@
             "no_recent_messages_description": "Гортайте стрічку вище, щоб побачити, чи були такі раніше.",
             "no_recent_messages_title": "Не знайдено недавніх повідомлень %(user)s"
         },
-        "redact_button": "Видалити останні повідомлення",
+        "redact_button": "Вилучити повідомлення",
         "revoke_invite": "Відкликати запрошення",
         "room_encrypted": "Повідомлення у цій кімнаті захищено наскрізним шифруванням.",
         "room_encrypted_detail": "Ваші повідомлення захищені. Лише ви з отримувачем маєте унікальні ключі їхнього розшифрування.",
         "room_unencrypted": "Повідомлення у цій кімнаті не захищено наскрізним шифруванням.",
         "room_unencrypted_detail": "У зашифрованих кімнатах ваші повідомлення є захищеними, тож тільки ви та отримувач маєте ключі для їх розблокування.",
-        "share_button": "Поділитися посиланням на користувача",
+        "send_message": "Надіслати повідомлення",
+        "share_button": "Поділитися профілем",
         "unban_button_room": "Розблокувати в кімнаті",
         "unban_button_space": "Розблокувати у просторі",
         "unban_room_confirm_title": "Розблокувати в %(roomName)s",
         "unban_space_everything": "Розблокувати скрізь, де маю доступ",
         "unban_space_specific": "Розблокувати в частині того, куди маю доступ",
         "unban_space_warning": "Вони не матимуть доступу ні до чого, де ви не є адміністратором.",
+        "unignore_button": "Не ігнорувати",
+        "verification_unavailable": "Верифікація користувача недоступна",
         "verify_button": "Звірити користувача",
         "verify_explainer": "Для додаткової безпеки перевірте цього користувача, звіривши одноразовий код на обох своїх пристроях."
     },
     "user_menu": {
+        "link_new_device": "Під'єднати новий пристрій",
         "settings": "Усі налаштування",
         "switch_theme_dark": "Темна тема",
         "switch_theme_light": "Світла тема"
@@ -3484,6 +3914,7 @@
         "camera_disabled": "Вашу камеру вимкнено",
         "camera_enabled": "Ваша камера досі увімкнена",
         "cannot_call_yourself_description": "Ви не можете подзвонити самим собі.",
+        "close_lobby": "Закрити зону очікування",
         "connecting": "З'єднання",
         "connection_lost": "Втрачено зʼєднання з сервером",
         "connection_lost_description": "Неможливо здійснювати виклики без з'єднання з сервером.",
@@ -3497,15 +3928,23 @@
         "disabled_no_perms_start_video_call": "У вас немає дозволу розпочинати відеовиклики",
         "disabled_no_perms_start_voice_call": "У вас немає дозволу розпочинати голосові виклики",
         "disabled_ongoing_call": "Поточний виклик",
+        "element_call": "Element Call",
         "enable_camera": "Увімкнути камеру",
         "enable_microphone": "Увімкнути мікрофон",
         "expand": "Повернутися до виклику",
+        "get_call_link": "Поділитися посиланням на виклик",
         "hangup": "Покласти слухавку",
         "hide_sidebar_button": "Сховати бічну панель",
         "input_devices": "Пристрої вводу",
+        "jitsi_call": "Конференція Jitsi",
         "join_button_tooltip_call_full": "Перепрошуємо, цей виклик заповнено",
-        "join_button_tooltip_connecting": "З'єднання",
+        "legacy_call": "Застарілий спосіб виклику",
         "maximise": "Заповнити екран",
+        "maximise_call": "Розгорнути виклик",
+        "metaspace_video_rooms": {
+            "conference_room_section": "Конференції"
+        },
+        "minimise_call": "Згорнути виклик",
         "misconfigured_server": "Виклик не вдався через неправильне налаштування сервера",
         "misconfigured_server_description": "Запропонуйте адміністратору вашого домашнього серверу (<code>%(homeserverDomain)s</code>) налаштувати сервер TURN для надійної роботи викликів.",
         "misconfigured_server_fallback": "Як альтернативу, ви можете спробувати публічний сервер на <server/>, але він не буде надто надійним, а також поширюватиме вашу IP-адресу на тому сервері. Ви також можете керувати цим у налаштуваннях.",
@@ -3553,6 +3992,7 @@
         "user_is_presenting": "%(sharerName)s показує",
         "video_call": "Відеовиклик",
         "video_call_started": "Відеовиклик розпочато",
+        "video_call_using": "Відеодзвінок за допомогою:",
         "voice_call": "Голосовий виклик",
         "you_are_presenting": "Ви показуєте"
     },
@@ -3652,7 +4092,7 @@
         "error_need_to_be_logged_in": "Вам потрібно увійти.",
         "error_unable_start_audio_stream_description": "Не вдалося почати аудіотрансляцію.",
         "error_unable_start_audio_stream_title": "Не вдалося почати живу трансляцію",
-        "modal_data_warning": "Дані на цьому екрані надсилаються до %(widgetDomain)s",
+        "modal_data_warning": "Наведені далі дані надсилаються на %(widgetDomain)s",
         "modal_title_default": "Модальний віджет",
         "no_name": "Невідомий додаток",
         "open_id_permissions_dialog": {
@@ -3661,7 +4101,7 @@
             "title": "Дозволити цьому віджету перевіряти вашу особу"
         },
         "popout": "Спливний віджет",
-        "set_room_layout": "Встановити мій вигляд кімнати всім",
+        "set_room_layout": "Встановіть макет для всіх",
         "shared_data_avatar": "URL-адреса зображення вашого профілю",
         "shared_data_device_id": "ID вашого пристрою",
         "shared_data_lang": "Ваша мова",
@@ -3687,6 +4127,7 @@
             "l33t": "Передбачувані заміни типу «@» замість «a» не особливо допомагають",
             "longerKeyboardPattern": "Використовуйте довшу комбінацію клавіш з більшою кількістю поворотів",
             "noNeed": "Цифри або великі букви не вимагаються",
+            "pwned": "Якщо ви використовуєте цей пароль десь іще, вам слід змінити його.",
             "recentYears": "Уникайте останніх років",
             "repeated": "Уникайте повторюваних слів та символів",
             "reverseWords": "Вгадувати перевернуті слова не сильно важче",
@@ -3700,13 +4141,15 @@
             "extendedRepeat": "Повтори типу «abcabcabc», лише трішки складніше вгадати, ніж «abc»",
             "keyPattern": "Короткі клавіатурні шаблони легко вгадувані",
             "namesByThemselves": "Імена та прізвища легко вгадувані",
+            "pwned": "Ваш пароль було розкрито в результаті витоку даних в інтернеті.",
             "recentYears": "Останні роки легко вгадувані",
             "sequences": "Послідовності типу abc або 6543 легко вгадувані",
             "similarToCommon": "Це занадто простий пароль",
             "simpleRepeat": "Повтори типу \"ааа\" легко вгадувані",
-            "straightRow": "Прямі ради клавіш легко вгадувані",
-            "topHundred": "Це топ-100 легких та розповсюджених паролів",
+            "straightRow": "Прямі ряди клавіш легко вгадувані",
+            "topHundred": "Це 100 найлегших та найпоширеніших паролів",
             "topTen": "Це топ-10 легких та звичайних паролів",
+            "userInputs": "Не повинно бути жодних особистих даних або даних, пов'язаних зі сторінкою.",
             "wordByItself": "Загальновживані слова легко вгадувані"
         }
     }
diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json
index 1769b5896c229393efa4bce8fe311d5f0a07e4fc..a0c91663ff31b0ec149267b835d8dfcb9105f572 100644
--- a/src/i18n/strings/vi.json
+++ b/src/i18n/strings/vi.json
@@ -79,6 +79,7 @@
         "new_video_room": "Tạo phòng truyền hình",
         "next": "Tiếp",
         "no": "Không",
+        "ok": "Ok",
         "open": "Mở",
         "pause": "Tạm dừng",
         "pin": "Ghim",
@@ -88,7 +89,6 @@
         "react": "Phản ứng",
         "refresh": "Làm tươi",
         "register": "Đăng ký",
-        "reject": "Từ chối",
         "reload": "Tải lại",
         "remove": "Xoá",
         "rename": "Đặt lại tên",
@@ -332,7 +332,6 @@
         "download_logs": "Tải xuống nhật ký",
         "downloading_logs": "Đang tải nhật ký xuống",
         "error_empty": "Vui lòng cho chúng tôi biết điều gì đã xảy ra hoặc tốt hơn là tạo sự cố trên GitHub để mô tả vấn đề.",
-        "failed_send_logs": "Không gửi được nhật ký: ",
         "github_issue": "Sự cố GitHub",
         "introduction": "Nếu bạn đã báo cáo lỗi qua GitHub, nhật ký gỡ lỗi có thể giúp chúng tôi theo dõi vấn đề. ",
         "log_request": "Để giúp chúng tôi ngăn chặn điều này trong tương lai, vui lòng gửi nhật ký cho chúng tôi <a>send us logs</a>.",
@@ -370,8 +369,8 @@
     },
     "common": {
         "access_token": "Token truy cập",
+        "accessibility": "Trợ năng",
         "advanced": "Nâng cao",
-        "all_rooms": "Tất cả các phòng",
         "analytics": "Về dữ liệu phân tích",
         "and_n_others": {
             "one": "và một cái khác…",
@@ -387,7 +386,6 @@
         "cameras": "Máy quay",
         "copied": "Đã sao chép!",
         "credits": "Ghi công",
-        "cross_signing": "Xác thực chéo",
         "dark": "Tối",
         "description": "Sự mô tả",
         "deselect_all": "Bỏ chọn tất cả",
@@ -466,7 +464,6 @@
         "rooms": "Phòng",
         "saving": "Đang lưu…",
         "secure_backup": "Sao lưu bảo mật",
-        "security": "Bảo mật",
         "select_all": "Chọn tất cả",
         "server": "Máy chủ",
         "settings": "Cài đặt",
@@ -485,7 +482,6 @@
         "thread": "Chủ đề",
         "threads": "Chủ đề",
         "timeline": "Dòng thời gian",
-        "trusted": "Tin cậy",
         "unavailable": "không có sẵn",
         "unencrypted": "Không được mã hóa",
         "unmute": "Bật tiếng",
@@ -520,6 +516,8 @@
         "edit_composer_label": "Chỉnh sửa tin nhắn",
         "format_bold": "In đậm",
         "format_code_block": "Khối mã",
+        "format_decrease_indent": "Giảm thụt lề",
+        "format_increase_indent": "Tăng thụt lề",
         "format_inline_code": "Mã",
         "format_insert_link": "Chèn liên kết",
         "format_italic": "Nghiêng",
@@ -629,7 +627,9 @@
         "subspace_join_rule_restricted_description": "Bất kỳ ai trong <SpaceName/> đều có thể tìm và tham gia."
     },
     "credits": {
-        "default_cover_photo": "Các <photo>ảnh bìa mặc định</photo> Là © <author>Chúa Jesus Roncero</author> Được sử dụng theo các điều khoản của <terms>CC-BY-SA 4.0</terms>."
+        "default_cover_photo": "Các <photo>ảnh bìa mặc định</photo> Là © <author>Chúa Jesus Roncero</author> Được sử dụng theo các điều khoản của <terms>CC-BY-SA 4.0</terms>.",
+        "twemoji": "Bộ <twemoji>Twemoji</twemoji> được © <author>Twitter, Inc and other contributors</author> sử dụng dưới các điều khoản trong giấy phép <terms>CC-BY 4.0</terms>.",
+        "twemoji_colr": "<colr>twemoji-colr</colr> được Quỹ ©<author>Mozilla Foundation</author> sử dụng dựa trên các điều khoản trong giấy phép <terms>Apache 2.0</terms>."
     },
     "desktop_default_device_name": "%(brand)s máy tính: %(platformName)s",
     "devtools": {
@@ -716,44 +716,23 @@
     "empty_room_was_name": "Phòng trống (trước kia là %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "Nhập Chuỗi bảo mật hoặc <button>sử dụng Khóa bảo mật</button> của bạn để tiếp tục.",
             "key_validation_text": {
-                "invalid_security_key": "Khóa bảo mật không hợp lệ",
-                "recovery_key_is_correct": "Có vẻ tốt!",
-                "wrong_file_type": "Loại tệp sai",
                 "wrong_security_key": "Khóa bảo mật sai"
             },
-            "reset_title": "Đặt lại mọi thứ",
-            "reset_warning_1": "Chỉ thực hiện việc này nếu bạn không có thiết bị nào khác để hoàn tất quá trình xác thực.",
-            "reset_warning_2": "Nếu bạn đặt lại mọi thứ, bạn sẽ khởi động lại mà không có phiên nào đáng tin cậy, không có người dùng đáng tin cậy và có thể không xem được các tin nhắn trước đây.",
             "restoring": "Khôi phục khóa từ sao lưu",
-            "security_key_title": "Chìa khóa bảo mật",
-            "security_phrase_incorrect_error": "Không thể truy cập bộ nhớ bí mật. Vui lòng xác minh rằng bạn đã nhập đúng Cụm từ bảo mật.",
-            "security_phrase_title": "Cụm từ Bảo mật",
-            "separator": "%(securityKey)s hay %(recoveryFile)s",
-            "use_security_key_prompt": "Sử dụng Khóa bảo mật của bạn để tiếp tục."
+            "security_key_title": "Chìa khóa bảo mật"
         },
         "bootstrap_title": "Đang thiết lập khóa bảo mật",
         "cancel_entering_passphrase_description": "Bạn có chắc chắn muốn hủy nhập cụm mật khẩu không?",
         "cancel_entering_passphrase_title": "Hủy nhập cụm mật khẩu?",
         "confirm_encryption_setup_body": "Nhấp vào nút bên dưới để xác nhận thiết lập mã hóa.",
         "confirm_encryption_setup_title": "Xác nhận thiết lập mã hóa",
-        "cross_signing_not_ready": "Tính năng xác thực chéo chưa được thiết lập.",
-        "cross_signing_ready": "Xác thực chéo đã sẵn sàng để sử dụng.",
-        "cross_signing_ready_no_backup": "Xác thực chéo đã sẵn sàng nhưng các khóa chưa được sao lưu.",
         "cross_signing_room_normal": "Phòng này được mã hóa end-to-end",
         "cross_signing_room_verified": "Mọi người trong phòng này đã được xác thực",
         "cross_signing_room_warning": "Ai đó đang sử dụng một phiên không xác định",
-        "cross_signing_unsupported": "Máy chủ của bạn không hỗ trợ xác thực chéo.",
-        "cross_signing_untrusted": "Tài khoản của bạn có danh tính xác thực chéo trong vùng lưu trữ bí mật, nhưng chưa được phiên này tin cậy.",
         "cross_signing_user_normal": "Bạn chưa xác thực người dùng này.",
         "cross_signing_user_verified": "Bạn đã xác thực người dùng này. Người dùng này đã xác thực tất cả các phiên của họ.",
         "cross_signing_user_warning": "Người dùng này chưa xác thực tất cả các phiên của họ.",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "Xóa các khóa ký chéo",
-            "title": "Hủy khóa xác thực chéo?",
-            "warning": "Xóa khóa ký chéo là vĩnh viễn. Bất kỳ ai mà bạn đã xác thực đều sẽ thấy cảnh báo bảo mật. Bạn gần như chắc chắn không muốn làm điều này, trừ khi bạn bị mất mọi thiết bị mà bạn có thể đăng nhập chéo."
-        },
         "event_shield_reason_authenticity_not_guaranteed": "Tính xác thực của tin nhắn được mã hóa này không thể được đảm bảo trên thiết bị này.",
         "event_shield_reason_mismatched_sender_key": "Được mã hóa bởi một phiên chưa được xác thực",
         "export_unsupported": "Trình duyệt của bạn không hỗ trợ chức năng mã hóa",
@@ -773,7 +752,6 @@
             "title": "Phương pháp Khôi phục mới",
             "warning": "Nếu bạn không đặt phương pháp khôi phục mới, kẻ tấn công có thể đang cố truy cập vào tài khoản của bạn. Thay đổi mật khẩu tài khoản của bạn và đặt phương pháp khôi phục mới ngay lập tức trong Cài đặt."
         },
-        "not_supported": "<không được hỗ trợ>",
         "recovery_method_removed": {
             "description_1": "Phiên này đã phát hiện rằng Cụm từ bảo mật và khóa cho Tin nhắn an toàn của bạn đã bị xóa.",
             "description_2": "Nếu bạn vô tình làm điều này, bạn có thể Cài đặt Tin nhắn được bảo toàn trên phiên này. Tính năng này sẽ mã hóa lại lịch sử tin nhắn của phiên này bằng một phương pháp khôi phục mới.",
@@ -784,8 +762,7 @@
         "set_up_toast_description": "Bảo vệ chống mất quyền truy cập vào tin nhắn và dữ liệu được mã hóa",
         "set_up_toast_title": "Thiết lập Sao lưu Bảo mật",
         "setup_secure_backup": {
-            "explainer": "Sao lưu chìa khóa của bạn trước khi đăng xuất để tránh mất chúng.",
-            "title": "Cài đặt"
+            "explainer": "Sao lưu chìa khóa của bạn trước khi đăng xuất để tránh mất chúng."
         },
         "udd": {
             "interactive_verification_button": "Xác thực có tương tác bằng biểu tượng cảm xúc",
@@ -796,12 +773,10 @@
             "title": "Không tin cậy"
         },
         "unable_to_setup_keys_error": "Không thể thiết lập khóa",
-        "unsupported": "Ứng dụng khách này không hỗ trợ mã hóa đầu cuối.",
         "verification": {
             "accepting": "Đang chấp nhận…",
             "after_new_login": {
                 "device_verified": "Thiết bị được xác thực",
-                "reset_confirmation": "Thực sự đặt lại các khóa xác minh?",
                 "skip_verification": "Bỏ qua xác thực ngay bây giờ",
                 "unable_to_verify": "Không thể xác thực thiết bị này",
                 "verify_this_device": "Xác thực thiết bị này"
@@ -871,8 +846,6 @@
             "verify_emoji_prompt": "Xác thực bằng cách so sánh biểu tượng cảm xúc độc đáo.",
             "verify_emoji_prompt_qr": "Nếu bạn không thể quét mã ở trên, hãy xác thực bằng cách so sánh biểu tượng cảm xúc duy nhất.",
             "verify_later": "Tôi sẽ xác thực sau",
-            "verify_reset_warning_1": "Sẽ không thể hoàn tác lại việc đặt lại các khóa xác thực của bạn. Sau khi đặt lại, bạn sẽ không có quyền truy cập vào các tin nhắn đã được mã hóa cũ, và bạn bè đã được xác thực trước đó bạn sẽ thấy các cảnh báo bảo mật cho đến khi bạn xác thực lại với họ.",
-            "verify_reset_warning_2": "Chỉ tiếp tục nếu bạn chắc chắn là mình đã mất mọi thiết bị khác và khóa bảo mật.",
             "verify_using_device": "Xác thực bằng thiết bị khác",
             "verify_using_key": "Xác thực bằng Khóa Bảo mật",
             "verify_using_key_or_phrase": "Xác thực bằng Khóa hoặc Chuỗi Bảo mật",
@@ -931,11 +904,7 @@
             "title": "Không thể sao chép liên kết phòng"
         },
         "error_loading_user_profile": "Không thể tải hồ sơ người dùng",
-        "forget_room_failed": "Không thể quên phòng %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "Máy chủ có thể không khả dụng, quá tải hoặc hết thời gian tìm kiếm :(",
-            "title": "Tìm kiếm không thành công"
-        }
+        "forget_room_failed": "Không thể quên phòng %(errCode)s"
     },
     "error_user_not_logged_in": "Người dùng đang không đăng nhập",
     "event_preview": {
@@ -995,6 +964,7 @@
         "generating_zip": "Tạo ZIP",
         "html_title": "Dữ liệu được trích xuất",
         "include_attachments": "Bao gồm các đính kèm",
+        "json": "JSON",
         "media_omitted": "Phương tiện bị bỏ qua",
         "media_omitted_file_size": "Phương tiện bị bỏ qua - kích thước tệp vượt quá giới hạn",
         "messages": "Tin nhắn",
@@ -1159,6 +1129,7 @@
     },
     "keyboard": {
         "activate_button": "Kích hoạt nút đã chọn",
+        "alt": "Alt",
         "autocomplete_cancel": "Hủy tự động hoàn thành",
         "backspace": "Phím lùi",
         "cancel_reply": "Hủy trả lời tin nhắn",
@@ -1179,6 +1150,7 @@
         "composer_toggle_link": "Chuyển đổi liên kết",
         "composer_toggle_quote": "Chuyển sang Trích dẫn",
         "composer_undo": "Hoàn tác chỉnh sửa",
+        "control": "Phím Ctrl",
         "dismiss_read_marker_and_jump_bottom": "Bỏ qua điểm đánh dấu đã đọc và chuyển xuống cuối",
         "end": "Kết thúc",
         "enter": "Vào",
@@ -1200,6 +1172,7 @@
         "scroll_up_timeline": "Cuộn lên trong dòng thời gian",
         "search": "Tìm kiếm (phải được bật)",
         "send_sticker": "Gửi nhãn dán",
+        "shift": "Phím Shift",
         "space": "space",
         "toggle_microphone_mute": "Chuyển đổi chế độ tắt tiếng micrô",
         "toggle_right_panel": "Chuyển đổi bảng điều khiển bên phải",
@@ -1473,11 +1446,6 @@
         "ongoing": "Đang xóa…",
         "reason_label": "Lý do (không bắt buộc)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "Bạn có chắc chắn muốn từ chối lời mời không?",
-        "failed": "Không thể từ chối lời mời",
-        "title": "Từ chối lời mời"
-    },
     "report_content": {
         "description": "Báo cáo thông báo này sẽ gửi 'ID sự kiện' duy nhất của nó đến quản trị viên của máy chủ của bạn. Nếu tin nhắn trong phòng này được mã hóa, quản trị viên máy chủ của bạn sẽ không thể đọc nội dung tin nhắn hoặc xem bất kỳ tệp hoặc hình ảnh nào.",
         "disagree": "Không đồng ý",
@@ -1620,7 +1588,6 @@
             "you_created": "Bạn đã tạo phòng này."
         },
         "invite_email_mismatch_suggestion": "Chia sẻ địa chỉ thư điện tử này trong Cài đặt để nhận lời mời trực tiếp trong %(brand)s.",
-        "invite_reject_ignore": "Từ chối & Bỏ qua người dùng",
         "invite_sent_to_email": "Lời mời này đã được gửi tới %(email)s",
         "invite_sent_to_email_room": "Lời mời đến %(roomName)s này đã được gửi tới %(email)s",
         "invite_subtitle": "<userName/> đã mời bạn",
@@ -2106,7 +2073,6 @@
             "remove_msisdn_prompt": "Xóa %(phone)s?",
             "spell_check_locale_placeholder": "Chọn vùng miền"
         },
-        "image_thumbnails": "Hiển thị bản xem trước / hình thu nhỏ cho hình ảnh",
         "inline_url_previews_default": "Bật xem trước nội dung liên kết theo mặc định",
         "inline_url_previews_room": "Bật xem trước nội dung liên kết cho mọi người trong phòng này",
         "inline_url_previews_room_account": "Bật xem trước nội dung liên kết trong phòng này (chỉ với bạn)",
@@ -2218,50 +2184,16 @@
         "prompt_invite": "Nhắc trước khi gửi lời mời đến các ID Matrix có khả năng không hợp lệ",
         "replace_plain_emoji": "Tự động thay thế hình biểu tượng",
         "security": {
-            "4s_public_key_in_account_data": "trong dữ liệu tài khoản",
-            "4s_public_key_status": "Khóa công khai lưu trữ bí mật:",
-            "backup_key_cached_status": "Đệm các khóa được sao lưu:",
-            "backup_key_stored_status": "Lưu trữ hóa được sao lưu:",
-            "backup_key_unexpected_type": "loại bất ngờ",
-            "backup_key_well_formed": "được hình thành một cách hoàn hảo",
-            "backup_keys_description": "Sao lưu các khóa mã hóa với dữ liệu tài khoản của bạn trong trường hợp bạn mất quyền truy cập vào các phiên của mình. Các khóa của bạn sẽ được bảo mật bằng Khóa bảo mật duy nhất.",
             "bulk_options_accept_all_invites": "Chấp nhận tất cả các lời mời từ %(invitedRooms)s",
             "bulk_options_reject_all_invites": "Từ chối tất cả lời mời từ %(invitedRooms)s",
             "bulk_options_section": "Tùy chọn hàng loạt",
-            "cross_signing_cached": "được lưu trữ cục bộ",
-            "cross_signing_homeserver_support": "Tính năng được hỗ trợ bởi máy chủ:",
-            "cross_signing_homeserver_support_exists": "tồn tại",
-            "cross_signing_in_4s": "trong vùng lưu trữ bí mật",
-            "cross_signing_in_memory": "trong bộ nhớ",
-            "cross_signing_master_private_Key": "Khóa cá nhân chính:",
-            "cross_signing_not_cached": "không tìm thấy ở địa phương",
-            "cross_signing_not_found": "không tìm thấy",
-            "cross_signing_not_in_4s": "không tìm thấy trong bộ nhớ",
-            "cross_signing_not_stored": "không được lưu trữ",
-            "cross_signing_private_keys": "Khóa cá nhân xác thực chéo:",
-            "cross_signing_public_keys": "Khóa công khai xác thực chéo:",
-            "cross_signing_self_signing_private_key": "Khóa cá nhân tự ký:",
-            "cross_signing_user_signing_private_key": "Người dùng ký khóa cá nhân:",
-            "cryptography_section": "Mã hóa bảo mật",
-            "delete_backup": "Xóa Sao lưu",
-            "delete_backup_confirm_description": "Bạn có chắc không? Bạn sẽ mất các tin nhắn được mã hóa nếu các khóa của bạn không được sao lưu đúng cách.",
             "e2ee_default_disabled_warning": "Người quản trị máy chủ của bạn đã vô hiệu hóa mã hóa đầu cuối theo mặc định trong phòng riêng và Tin nhắn trực tiếp.",
             "enable_message_search": "Bật tính năng tìm kiếm tin nhắn trong các phòng được mã hóa",
             "encryption_section": "Mã hóa",
-            "error_loading_key_backup_status": "Không thể tải trạng thái sao lưu khóa",
-            "export_megolm_keys": "Xuất các mã khoá phòng E2E",
             "ignore_users_empty": "Bạn không có người dùng bị bỏ qua.",
             "ignore_users_section": "Người dùng bị bỏ qua",
-            "import_megolm_keys": "Nhập các mã khoá phòng E2E",
-            "key_backup_active": "Phiên này đang sao lưu các khóa.",
-            "key_backup_active_version_none": "Không có",
             "key_backup_algorithm": "Thuật toán:",
-            "key_backup_complete": "Tất cả các khóa được sao lưu",
             "key_backup_connect": "Kết nối phiên này với Khóa Sao lưu",
-            "key_backup_connect_prompt": "Kết nối phiên này với máy chủ sao lưu khóa trước khi đăng xuất để tránh mất bất kỳ khóa nào có thể chỉ có trong phiên này.",
-            "key_backup_in_progress": "Đang sao lưu %(sessionsRemaining)s khóa…",
-            "key_backup_inactive": "Phiên này đang <b>không sao lưu các khóa</b>, nhưng bạn có một bản sao lưu hiện có, bạn có thể khôi phục và thêm vào để về sau.",
-            "key_backup_inactive_warning": "Các khóa của bạn <b>not being backed up from this session</b>.",
             "message_search_disable_warning": "Nếu bị tắt, tin nhắn từ các phòng được mã hóa sẽ không xuất hiện trong kết quả tìm kiếm.",
             "message_search_disabled": "Bộ nhớ cache an toàn các tin nhắn được mã hóa cục bộ để chúng xuất hiện trong kết quả tìm kiếm.",
             "message_search_enabled": {
@@ -2281,13 +2213,7 @@
             "message_search_unsupported": "%(brand)s thiếu một số thành phần thiết yếu để lưu trữ cục bộ an toàn các tin nhắn được mã hóa. Nếu bạn muốn thử nghiệm với tính năng này, hãy dựng một bản %(brand)s tùy chỉnh cho máy tính có thêm <nativeLink>các thành phần để tìm kiếm</nativeLink>.",
             "message_search_unsupported_web": "%(brand)s không thể lưu trữ cục bộ an toàn các tin nhắn được mã hóa khi đang chạy trong trình duyệt web. Sử dụng <desktopLink>%(brand)s cho máy tính</desktopLink> để các tin nhắn được mã hóa xuất hiện trong kết quả tìm kiếm.",
             "record_session_details": "Ghi lại tên phần mềm máy khách, phiên bản, và đường dẫn để nhận diện các phiên dễ dàng hơn trong trình quản lý phiên",
-            "restore_key_backup": "Khôi phục từ Sao lưu",
-            "secret_storage_not_ready": "chưa sẵn sàng",
-            "secret_storage_ready": "Sẵn sàng",
-            "secret_storage_status": "Lưu trữ bí mật:",
             "send_analytics": "Gửi dữ liệu phân tích",
-            "session_id": "Định danh (ID) phiên:",
-            "session_key": "Khóa phiên:",
             "strict_encryption": "Không bao giờ gửi tin nhắn được mã hóa đến các phiên chưa được xác thực từ phiên này"
         },
         "send_read_receipts": "Gửi thông báo đã đọc",
@@ -2509,8 +2435,6 @@
         "topic": "Lấy hoặc đặt chủ đề phòng",
         "topic_none": "Phòng này chưa có chủ đề.",
         "topic_room_error": "Không thể lấy chủ đề phòng: Không tìm thấy phòng (%(roomId)s)",
-        "tovirtual": "Chuyển sang phòng ảo của phòng này, nếu nó có",
-        "tovirtual_not_found": "Không có phòng ảo của phòng này",
         "unban": "Gỡ cấm thành viên có ID chỉ định",
         "unflip": "Thêm ┬──┬ ノ( ゜-゜ノ) vào tin nhắn văn bản thuần túy",
         "unholdcall": "Nối lại cuộc gọi trong phòng hiện tại",
@@ -2625,14 +2549,12 @@
         "heading_without_query": "Tìm",
         "join_button_text": "Tham gia %(roomAddress)s",
         "keyboard_scroll_hint": "Dùng <arrows/> để cuộn",
-        "message_search_section_title": "Các tìm kiếm khác",
         "other_rooms_in_space": "Các phòng khác trong %(spaceName)s",
         "public_rooms_label": "Các phòng công cộng",
         "recent_searches_section_title": "Các tìm kiếm gần đây",
         "recently_viewed_section_title": "Được xem gần đây",
         "result_may_be_hidden_privacy_warning": "Một số kết quả có thể bị ẩn để đảm bảo quyền riêng tư",
         "result_may_be_hidden_warning": "Một số kết quả có thể bị ẩn",
-        "search_messages_hint": "Để tìm các tin nhắn, hãy tìm biểu tượng này ở đầu phòng <icon/>",
         "spaces_title": "Các Space bạn đang trong đó",
         "start_group_chat_button": "Bắt đầu cuộc trò chuyện nhóm"
     },
@@ -2889,7 +2811,9 @@
             "sent": "%(senderName)s đã mời %(targetDisplayName)s tham gia phòng."
         },
         "m.room.tombstone": "%(senderDisplayName)s đã nâng cấp phòng này.",
-        "m.room.topic": "%(senderDisplayName)s đổi chủ đề thành \"%(topic)s\".",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s đổi chủ đề thành \"%(topic)s\"."
+        },
         "m.sticker": "%(senderDisplayName)s đã gửi một sticker.",
         "m.video": {
             "error_decrypting": "Lỗi khi giải mã video"
@@ -3141,14 +3065,6 @@
         "ban_room_confirm_title": "Cấm từ %(roomName)s",
         "ban_space_everything": "Cấm họ khỏi mọi thứ mà tôi có thể",
         "ban_space_specific": "Cấm họ khỏi những thứ cụ thể mà tôi có thể",
-        "count_of_sessions": {
-            "one": "%(count)s phiên",
-            "other": "%(count)s phiên"
-        },
-        "count_of_verified_sessions": {
-            "one": "1 phiên đã xác thực",
-            "other": "%(count)s phiên đã xác thực"
-        },
         "deactivate_confirm_action": "Hủy kích hoạt người dùng",
         "deactivate_confirm_description": "Việc hủy kích hoạt người dùng này sẽ đăng xuất họ và ngăn họ đăng nhập lại. Ngoài ra, họ sẽ rời khỏi tất cả các phòng mà họ đang ở. Không thể hoàn tác hành động này. Bạn có chắc chắn muốn hủy kích hoạt người dùng này không?",
         "deactivate_confirm_title": "Hủy kích hoạt người dùng?",
@@ -3159,15 +3075,12 @@
         "disinvite_button_room": "Không mời vào phòng nữa",
         "disinvite_button_room_name": "Hủy mời từ %(roomName)s",
         "disinvite_button_space": "Hủy lời mời vào space",
-        "edit_own_devices": "Chỉnh sửa thiết bị",
         "error_ban_user": "Đã có lỗi khi chặn người dùng",
         "error_deactivate": "Không thể hủy kích hoạt người dùng",
         "error_kicking_user": "Không thể loại bỏ người dùng",
         "error_mute_user": "Không thể tắt tiếng người dùng",
         "error_revoke_3pid_invite_description": "Không thể thu hồi lời mời. Máy chủ có thể đang gặp sự cố tạm thời hoặc bạn không có đủ quyền để thu hồi lời mời.",
         "error_revoke_3pid_invite_title": "Không thể thu hồi lời mời",
-        "hide_sessions": "Ẩn các phiên",
-        "hide_verified_sessions": "Ẩn các phiên đã xác thực",
         "ignore_confirm_description": "Toàn bộ tin nhắn và lời mời từ người dùng này sẽ bị ẩn. Bạn có muốn tảng lờ người dùng?",
         "ignore_confirm_title": "Tảng lờ %(user)s",
         "invited_by": "Được %(sender)s mời",
@@ -3249,7 +3162,6 @@
         "hide_sidebar_button": "Ẩn thanh bên",
         "input_devices": "Thiết bị đầu vào",
         "join_button_tooltip_call_full": "Xin lỗi — cuộc gọi này đang đầy",
-        "join_button_tooltip_connecting": "Đang kết nối",
         "maximise": "Vừa màn hình",
         "misconfigured_server": "Thực hiện cuộc gọi thất bại do thiết lập máy chủ sai",
         "misconfigured_server_description": "Vui lòng yêu cầu quản trị viên máy chủ của bạn (<code>%(homeserverDomain)s</code>) thiết lập máy chủ TURN để cuộc gọi hoạt động ổn định.",
diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json
index 0ef859e5484d25576d2d4c7717a0c7b6e7f8a430..43e1ac008ca81ba3c490f1c2df7104d08bd3d84c 100644
--- a/src/i18n/strings/zh_Hans.json
+++ b/src/i18n/strings/zh_Hans.json
@@ -89,7 +89,6 @@
         "react": "回应",
         "refresh": "刷新",
         "register": "注册",
-        "reject": "拒绝",
         "reload": "重加载",
         "remove": "移除",
         "rename": "重命名",
@@ -349,7 +348,6 @@
         "download_logs": "下载日志",
         "downloading_logs": "正在下载日志",
         "error_empty": "请告诉我们哪里出错了,或最好创建一个 GitHub issue 来描述此问题。",
-        "failed_send_logs": "无法发送日志: ",
         "github_issue": "GitHub 上的 issue",
         "introduction": "若你通过GitHub提交bug,则调试日志能帮助我们追踪问题。 ",
         "log_request": "要帮助我们防止其以后发生,请<a>给我们发送日志</a>。",
@@ -389,7 +387,6 @@
         "access_token": "访问令牌",
         "accessibility": "无障碍功能",
         "advanced": "高级",
-        "all_rooms": "所有房间",
         "analytics": "统计分析服务",
         "and_n_others": {
             "other": "和其他%(count)s个人……",
@@ -407,7 +404,6 @@
         "capabilities": "功能",
         "copied": "已复制!",
         "credits": "感谢",
-        "cross_signing": "交叉签名",
         "dark": "深色",
         "description": "描述",
         "deselect_all": "取消全选",
@@ -485,7 +481,6 @@
         "room_name": "房间名称",
         "rooms": "房间",
         "secure_backup": "安全备份",
-        "security": "安全",
         "select_all": "全选",
         "server": "服务器",
         "settings": "设置",
@@ -504,7 +499,6 @@
         "thread": "消息列",
         "threads": "消息列",
         "timeline": "时间线",
-        "trusted": "受信任的",
         "unencrypted": "未加密",
         "unmute": "取消静音",
         "unnamed_room": "未命名的房间",
@@ -732,44 +726,23 @@
     "empty_room_was_name": "空房间(曾是%(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "输入安全短语或<button>使用安全密钥</button>以继续。",
             "key_validation_text": {
-                "invalid_security_key": "安全密钥无效",
-                "recovery_key_is_correct": "看着不错!",
-                "wrong_file_type": "错误文件类型",
                 "wrong_security_key": "安全密钥错误"
             },
-            "reset_title": "全部重置",
-            "reset_warning_1": "当你没有其他设备可以用于完成验证时,方可执行此操作。",
-            "reset_warning_2": "如果你全部重置,你将会在没有受信任的会话重新开始、没有受信任的用户,且可能会看不到过去的消息。",
             "restoring": "从备份恢复密钥",
-            "security_key_title": "安全密钥",
-            "security_phrase_incorrect_error": "无法访问秘密存储。请确认你输入了正确的安全短语。",
-            "security_phrase_title": "安全短语",
-            "separator": "%(securityKey)s或%(recoveryFile)s",
-            "use_security_key_prompt": "使用你的安全密钥以继续。"
+            "security_key_title": "安全密钥"
         },
         "bootstrap_title": "设置密钥",
         "cancel_entering_passphrase_description": "你确定要取消输入口令词组吗?",
         "cancel_entering_passphrase_title": "取消输入口令词组?",
         "confirm_encryption_setup_body": "点击下方按钮以确认设置加密。",
         "confirm_encryption_setup_title": "确认加密设置",
-        "cross_signing_not_ready": "未设置交叉签名。",
-        "cross_signing_ready": "交叉签名已可用。",
-        "cross_signing_ready_no_backup": "交叉签名已就绪,但尚未备份密钥。",
         "cross_signing_room_normal": "此房间是端到端加密的",
         "cross_signing_room_verified": "房间中所有人都已被验证",
         "cross_signing_room_warning": "有人在使用未知会话",
-        "cross_signing_unsupported": "你的家服务器不支持交叉签名。",
-        "cross_signing_untrusted": "你的账户在秘密存储中有交叉签名身份,但并没有被此会话信任。",
         "cross_signing_user_normal": "你没有验证此用户。",
         "cross_signing_user_verified": "你验证了此用户。此用户已验证了其全部会话。",
         "cross_signing_user_warning": "此用户没有验证其全部会话。",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "清楚交叉签名密钥",
-            "title": "销毁交叉签名密钥?",
-            "warning": "删除交叉签名密钥是永久的。所有你验证过的人都会看到安全警报。除非你丢失了所有可以交叉签名的设备,否则几乎可以确定你不想这么做。"
-        },
         "event_shield_reason_authenticity_not_guaranteed": "此加密消息的真实性无法在此设备上保证。",
         "event_shield_reason_mismatched_sender_key": "由未验证的会话加密",
         "export_unsupported": "你的浏览器不支持所需的密码学扩展",
@@ -789,7 +762,6 @@
             "title": "新恢复方式",
             "warning": "如果你没有设置新恢复方式,可能有攻击者正试图侵入你的账户。请立即更改你的账户密码并在设置中设定一个新恢复方式。"
         },
-        "not_supported": "<不支持>",
         "recovery_method_removed": {
             "description_1": "此会话已检测到你的安全短语和安全消息密钥被移除。",
             "description_2": "如果你出于意外这样做了,你可以在此会话上设置安全消息,以使用新的加密方式重新加密此会话的消息历史。",
@@ -800,8 +772,7 @@
         "set_up_toast_description": "防止丢失加密消息和数据的访问权",
         "set_up_toast_title": "设置安全备份",
         "setup_secure_backup": {
-            "explainer": "在登出之前请备份密钥以免丢失。",
-            "title": "设置"
+            "explainer": "在登出之前请备份密钥以免丢失。"
         },
         "udd": {
             "interactive_verification_button": "用表情符号交互式验证",
@@ -812,12 +783,10 @@
             "title": "不可信任"
         },
         "unable_to_setup_keys_error": "无法设置密钥",
-        "unsupported": "此客户端不支持端到端加密。",
         "verification": {
             "accepting": "正在接受……",
             "after_new_login": {
                 "device_verified": "设备已验证",
-                "reset_confirmation": "确实要重置验证密钥?",
                 "skip_verification": "暂时跳过验证",
                 "unable_to_verify": "无法验证此设备",
                 "verify_this_device": "验证此设备"
@@ -881,7 +850,6 @@
             "verify_emoji_prompt": "通过比较唯一的表情符号来验证。",
             "verify_emoji_prompt_qr": "如果你不能扫描以上代码,请通过比较唯一的表情符号来验证。",
             "verify_later": "我稍后进行验证",
-            "verify_reset_warning_1": "无法撤消重置验证密钥的操作。重置后,你将无法访问旧的加密消息,任何之前验证过你的朋友将看到安全警告,直到你再次和他们进行验证。",
             "verify_using_device": "使用其他设备进行验证",
             "verify_using_key": "使用安全密钥进行验证",
             "verify_using_key_or_phrase": "使用安全密钥或短语进行验证",
@@ -940,11 +908,7 @@
             "title": "无法复制房间链接"
         },
         "error_loading_user_profile": "无法加载用户资料",
-        "forget_room_failed": "忘记房间失败,错误代码: %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "服务器可能不可用、超载,或者搜索超时 :(",
-            "title": "搜索失败"
-        }
+        "forget_room_failed": "忘记房间失败,错误代码: %(errCode)s"
     },
     "error_user_not_logged_in": "用户未登录",
     "event_preview": {
@@ -1503,11 +1467,6 @@
         "ongoing": "正在移除…",
         "reason_label": "理由(可选)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "你确定要拒绝邀请吗?",
-        "failed": "拒绝邀请失败",
-        "title": "拒绝邀请"
-    },
     "report_content": {
         "description": "举报此消息会将其唯一的“事件ID”发送给你的家服务器的管理员。如果此房间中的消息是加密的,则你的家服务器管理员将无法阅读消息文本,也无法查看任何文件或图片。",
         "disagree": "不同意",
@@ -1646,7 +1605,6 @@
             "you_created": "你创建了此房间。"
         },
         "invite_email_mismatch_suggestion": "要在 %(brand)s 中直接接收邀请,请在设置中共享此邮箱。",
-        "invite_reject_ignore": "拒绝并忽略用户",
         "invite_sent_to_email": "邀请已被发送到 %(email)s",
         "invite_sent_to_email_room": "这个到 %(roomName)s 的邀请是发送给 %(email)s 的",
         "invite_subtitle": "<userName/> 邀请了你",
@@ -2117,7 +2075,6 @@
             "remove_msisdn_prompt": "删除 %(phone)s 吗?",
             "spell_check_locale_placeholder": "选择区域设置"
         },
-        "image_thumbnails": "显示图片的预览图",
         "inline_url_previews_default": "默认启用行内URL预览",
         "inline_url_previews_room": "对此房间的所有参与者默认启用URL预览",
         "inline_url_previews_room_account": "对此房间启用URL预览(仅影响你)",
@@ -2218,49 +2175,16 @@
         "prompt_invite": "在发送邀请之前提示可能无效的 Matrix ID",
         "replace_plain_emoji": "自动取代纯文本为表情符号",
         "security": {
-            "4s_public_key_in_account_data": "在账户数据中",
-            "4s_public_key_status": "秘密存储公钥:",
-            "backup_key_cached_status": "备份密钥已缓存:",
-            "backup_key_stored_status": "备份密钥已保存:",
-            "backup_key_unexpected_type": "未预期的类型",
-            "backup_key_well_formed": "格式正确",
-            "backup_keys_description": "请使用你的账户数据备份加密密钥,以免你无法访问你的会话。密钥会由一个唯一安全密钥保护。",
             "bulk_options_accept_all_invites": "接受所有 %(invitedRooms)s 邀请",
             "bulk_options_reject_all_invites": "拒绝所有 %(invitedRooms)s 的邀请",
             "bulk_options_section": "批量选择",
-            "cross_signing_cached": "本地缓存",
-            "cross_signing_homeserver_support": "家服务器功能支持:",
-            "cross_signing_homeserver_support_exists": "存在",
-            "cross_signing_in_4s": "在秘密存储中",
-            "cross_signing_in_memory": "在内存中",
-            "cross_signing_master_private_Key": "主私钥:",
-            "cross_signing_not_cached": "本地未找到",
-            "cross_signing_not_found": "未找到",
-            "cross_signing_not_in_4s": "未在存储中找到",
-            "cross_signing_not_stored": "未存储",
-            "cross_signing_private_keys": "交叉签名私钥:",
-            "cross_signing_public_keys": "交叉签名公钥:",
-            "cross_signing_self_signing_private_key": "自签名私钥:",
-            "cross_signing_user_signing_private_key": "用户签名私钥:",
-            "cryptography_section": "加密",
-            "delete_backup": "删除备份",
-            "delete_backup_confirm_description": "你确定吗?如果密钥没有正确地备份你将失去你的加密消息。",
             "e2ee_default_disabled_warning": "你的服务器管理员默认关闭了私人房间和私聊中的端到端加密。",
             "enable_message_search": "在加密房间中启用消息搜索",
             "encryption_section": "加密",
-            "error_loading_key_backup_status": "无法载入密钥备份状态",
-            "export_megolm_keys": "导出房间的端到端加密密钥",
             "ignore_users_empty": "你没有设置忽略用户。",
             "ignore_users_section": "已忽略的用户",
-            "import_megolm_keys": "导入房间端到端加密密钥",
-            "key_backup_active": "此会话正在备份你的密钥。",
-            "key_backup_active_version_none": "无",
             "key_backup_algorithm": "算法:",
-            "key_backup_complete": "所有密钥都已备份",
             "key_backup_connect": "将此会话连接到密钥备份",
-            "key_backup_connect_prompt": "在登出前连接此会话到密钥备份以避免丢失可能仅在此会话上的密钥。",
-            "key_backup_inactive": "此会话<b>未备份你的密钥</b>,但如果你已有现存备份,你可以继续并从中恢复和向其添加。",
-            "key_backup_inactive_warning": "你的密钥<b>没有被此会话备份</b>。",
             "message_search_disable_warning": "如果被禁用,加密房间内的消息不会显示在搜索结果中。",
             "message_search_disabled": "在本地安全地缓存加密消息以使其出现在搜索结果中。",
             "message_search_enabled": {
@@ -2280,13 +2204,7 @@
             "message_search_unsupported": "%(brand)s缺少安全地在本地缓存加密信息所必须的部件。如果你想实验此功能,请构建一个自定义的<nativeLink>带有搜索部件的</nativeLink>%(brand)s桌面版。",
             "message_search_unsupported_web": "%(brand)s 在浏览器中运行时不能安全地在本地缓存加密信息。请使用<desktopLink>%(brand)s 桌面版</desktopLink>以使加密信息出现在搜索结果中。",
             "record_session_details": "记录客户端名称、版本和url以便在会话管理器里更易识别",
-            "restore_key_backup": "从备份恢复",
-            "secret_storage_not_ready": "尚未就绪",
-            "secret_storage_ready": "就绪",
-            "secret_storage_status": "秘密存储:",
             "send_analytics": "发送统计数据",
-            "session_id": "会话 ID:",
-            "session_key": "会话密钥:",
             "strict_encryption": "永不从本会话向未验证的会话发送加密消息"
         },
         "send_read_receipts": "发送已读回执",
@@ -2468,8 +2386,6 @@
         "topic": "获取或设置房间话题",
         "topic_none": "此房间没有话题。",
         "topic_room_error": "获取房间话题失败:无法找到房间(%(roomId)s)",
-        "tovirtual": "切换到此房间的虚拟房间(如有)",
-        "tovirtual_not_found": "此房间未有虚拟房间",
         "unban": "按照 ID 解封用户",
         "unflip": "在纯文本消息开头添加 ┬──┬ ノ( ゜-゜ノ)",
         "unholdcall": "解除挂起当前房间的通话",
@@ -2589,7 +2505,6 @@
         "heading_without_query": "搜索",
         "join_button_text": "加入%(roomAddress)s",
         "keyboard_scroll_hint": "用<arrows/>来滚动",
-        "message_search_section_title": "其他搜索",
         "other_rooms_in_space": "%(spaceName)s 中的其他房间",
         "public_rooms_label": "公共房间",
         "public_spaces_label": "公共空间",
@@ -2599,7 +2514,6 @@
         "result_may_be_hidden_privacy_warning": "为保护隐私,一些结果可能被隐藏",
         "result_may_be_hidden_warning": "一些结果可能被隐藏",
         "search_dialog": "搜索对话",
-        "search_messages_hint": "要搜索消息,请在房间顶部查找此图标<icon/>",
         "spaces_title": "你所在的空间",
         "start_group_chat_button": "发起群聊天"
     },
@@ -2863,7 +2777,9 @@
             "sent": "%(senderName)s 向 %(targetDisplayName)s 发了加入房间的邀请。"
         },
         "m.room.tombstone": "%(senderDisplayName)s 升级了此房间。",
-        "m.room.topic": "%(senderDisplayName)s 将话题修改为 “%(topic)s”。",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s 将话题修改为 “%(topic)s”。"
+        },
         "m.sticker": "%(senderDisplayName)s 发送了一张贴纸。",
         "m.video": {
             "error_decrypting": "解密视频时出错"
@@ -3121,14 +3037,6 @@
         "ban_room_confirm_title": "禁止进入 %(roomName)s",
         "ban_space_everything": "禁止这些人做任何我有权决定的事",
         "ban_space_specific": "禁止这些人做某些我有权决定的事",
-        "count_of_sessions": {
-            "other": "%(count)s 个会话",
-            "one": "%(count)s 个会话"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s 个已验证的会话",
-            "one": "1 个已验证的会话"
-        },
         "deactivate_confirm_action": "停用用户",
         "deactivate_confirm_description": "停用此用户将会使其登出并阻止其再次登入。而且此用户也会离开其所在的所有房间。此操作不可逆。你确定要停用此用户吗?",
         "deactivate_confirm_title": "停用用户吗?",
@@ -3139,15 +3047,12 @@
         "disinvite_button_room": "从房间取消邀请",
         "disinvite_button_room_name": "取消邀请加入 %(roomName)s",
         "disinvite_button_space": "从空间取消邀请",
-        "edit_own_devices": "编辑设备",
         "error_ban_user": "封禁失败",
         "error_deactivate": "停用用户失败",
         "error_kicking_user": "移除用户失败",
         "error_mute_user": "禁言用户失败",
         "error_revoke_3pid_invite_description": "无法撤销邀请。此服务器可能出现了临时错误,或者你没有足够的权限来撤销邀请。",
         "error_revoke_3pid_invite_title": "撤销邀请失败",
-        "hide_sessions": "隐藏会话",
-        "hide_verified_sessions": "隐藏已验证的会话",
         "invited_by": "被 %(sender)s 邀请",
         "jump_to_rr_button": "跳到阅读回执",
         "kick_button_room": "从房间移除",
@@ -3227,7 +3132,6 @@
         "hide_sidebar_button": "隐藏侧边栏",
         "input_devices": "输入设备",
         "join_button_tooltip_call_full": "抱歉——目前线路拥挤",
-        "join_button_tooltip_connecting": "连接中",
         "maximise": "填满屏幕",
         "misconfigured_server": "服务器配置错误导致通话失败",
         "misconfigured_server_description": "请联系你的家服务器(<code>%(homeserverDomain)s</code>)的管理员配置 TURN 服务器,以确保通话过程稳定。",
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index bb63ce99e8f49883b8209a44d056004524259431..18cc169d9725c2964b7c9be9062483a5d930f819 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -89,7 +89,6 @@
         "react": "反應",
         "refresh": "重新整理",
         "register": "註冊",
-        "reject": "拒絕",
         "reload": "重新載入",
         "remove": "移除",
         "rename": "重新命名",
@@ -364,7 +363,6 @@
         "download_logs": "下載紀錄檔",
         "downloading_logs": "正在下載紀錄檔",
         "error_empty": "請告訴我們發生了什麼錯誤,或更好的是,在 GitHub 上建立描述問題的議題。",
-        "failed_send_logs": "無法傳送除錯訊息: ",
         "github_issue": "GitHub 議題",
         "introduction": "若您透過 GitHub 遞交錯誤,除錯紀錄檔可以協助我們追蹤問題。 ",
         "log_request": "要協助我們讓這個問題不再發生,請<a>將紀錄檔傳送給我們</a>。",
@@ -404,7 +402,6 @@
         "access_token": "存取權杖",
         "accessibility": "可近用性",
         "advanced": "進階",
-        "all_rooms": "所有聊天室",
         "analytics": "分析",
         "and_n_others": {
             "other": "與另 %(count)s 個人…",
@@ -416,12 +413,12 @@
         "attachment": "附件",
         "authentication": "授權",
         "avatar": "大頭照",
+        "beta": "試用版",
         "camera": "相機",
         "cameras": "相機",
         "capabilities": "功能",
         "copied": "已複製!",
         "credits": "感謝",
-        "cross_signing": "交叉簽署",
         "dark": "暗色",
         "description": "描述",
         "deselect_all": "取消選取",
@@ -499,7 +496,6 @@
         "rooms": "聊天室",
         "saving": "正在儲存…",
         "secure_backup": "安全備份",
-        "security": "安全性",
         "select_all": "全部選取",
         "server": "伺服器",
         "settings": "設定",
@@ -518,7 +514,6 @@
         "thread": "討論串",
         "threads": "討論串",
         "timeline": "時間軸",
-        "trusted": "受信任",
         "unavailable": "無法使用",
         "unencrypted": "未加密",
         "unmute": "解除靜音",
@@ -789,44 +784,23 @@
     "empty_room_was_name": "空的聊天室(曾為 %(oldName)s)",
     "encryption": {
         "access_secret_storage_dialog": {
-            "enter_phrase_or_key_prompt": "輸入您的安全密語或<button>使用您的安全金鑰</button>以繼續。",
             "key_validation_text": {
-                "invalid_security_key": "無效的安全金鑰",
-                "recovery_key_is_correct": "看起來真棒!",
-                "wrong_file_type": "錯誤的檔案類型",
                 "wrong_security_key": "錯誤的安全金鑰"
             },
-            "reset_title": "重設所有東西",
-            "reset_warning_1": "當您沒有其他裝置可以完成驗證時,才執行此動作。",
-            "reset_warning_2": "如果您重設所有東西,您將會在沒有受信任的工作階段、沒有受信任的使用者的情況下重新啟動,且可能會看不到過去的訊息。",
             "restoring": "從備份還原金鑰",
-            "security_key_title": "安全金鑰",
-            "security_phrase_incorrect_error": "無法存取祕密儲存空間。請確認您輸入了正確的安全密語。",
-            "security_phrase_title": "安全密語",
-            "separator": "%(securityKey)s 或 %(recoveryFile)s",
-            "use_security_key_prompt": "使用您的安全金鑰以繼續。"
+            "security_key_title": "安全金鑰"
         },
         "bootstrap_title": "正在產生金鑰",
         "cancel_entering_passphrase_description": "您確定要取消輸入安全密語嗎?",
         "cancel_entering_passphrase_title": "取消輸入安全密語?",
         "confirm_encryption_setup_body": "點擊下方按鈕以確認設定加密。",
         "confirm_encryption_setup_title": "確認加密設定",
-        "cross_signing_not_ready": "尚未設定交叉簽署。",
-        "cross_signing_ready": "交叉簽署已可使用。",
-        "cross_signing_ready_no_backup": "已準備好交叉簽署但金鑰未備份。",
         "cross_signing_room_normal": "此聊天室已端對端加密",
         "cross_signing_room_verified": "此聊天室中每個人都已驗證",
         "cross_signing_room_warning": "某人正在使用未知的工作階段",
-        "cross_signing_unsupported": "您的家伺服器不支援交叉簽署。",
-        "cross_signing_untrusted": "您的帳號在秘密儲存空間中有交叉簽署的身分,但尚未被此工作階段信任。",
         "cross_signing_user_normal": "您尚未驗證此使用者。",
         "cross_signing_user_verified": "您已驗證此使用者。此使用者已驗證他們所有的工作階段。",
         "cross_signing_user_warning": "此使用者尚未驗證他們的所有工作階段。",
-        "destroy_cross_signing_dialog": {
-            "primary_button_text": "清除交叉簽署金鑰",
-            "title": "摧毀交叉簽署金鑰?",
-            "warning": "永久刪除交叉簽署金鑰。任何您已驗證過的人都會看到安全性警告。除非您遺失了所有可以進行交叉簽署的裝置,否則您不會想要這樣做。"
-        },
         "event_shield_reason_authenticity_not_guaranteed": "無法在此裝置上保證加密訊息的真實性。",
         "event_shield_reason_mismatched_sender_key": "已由未驗證的工作階段加密",
         "export_unsupported": "您的瀏覽器不支援需要的加密擴充",
@@ -846,7 +820,6 @@
             "title": "新復原方法",
             "warning": "如果您沒有設定新的復原方法,攻擊者可能會嘗試存取您的帳號。在設定中立刻變更您的密碼並設定新的復原方法。"
         },
-        "not_supported": "<不支援>",
         "recovery_method_removed": {
             "description_1": "此工作階段偵測到您的安全密語以及安全訊息金鑰已被移除。",
             "description_2": "如果您不小心這樣做了,您可以在此工作階段上設定安全訊息,這將會以新的復原方法重新加密此工作階段的訊息歷史。",
@@ -857,8 +830,7 @@
         "set_up_toast_description": "避免失去對加密訊息與資料的存取權",
         "set_up_toast_title": "設定安全備份",
         "setup_secure_backup": {
-            "explainer": "請在登出前備份您的金鑰,以免遺失。",
-            "title": "設定"
+            "explainer": "請在登出前備份您的金鑰,以免遺失。"
         },
         "udd": {
             "interactive_verification_button": "透過表情符號互動來驗證",
@@ -869,12 +841,10 @@
             "title": "未受信任"
         },
         "unable_to_setup_keys_error": "無法設定金鑰",
-        "unsupported": "此客戶端不支援端對端加密。",
         "verification": {
             "accepting": "正在接受…",
             "after_new_login": {
                 "device_verified": "裝置已驗證",
-                "reset_confirmation": "真的要重設驗證金鑰?",
                 "skip_verification": "暫時略過驗證",
                 "unable_to_verify": "無法驗證此裝置",
                 "verify_this_device": "驗證此裝置"
@@ -944,8 +914,6 @@
             "verify_emoji_prompt": "透過比對獨特的表情符號來進行驗證。",
             "verify_emoji_prompt_qr": "如果您無法掃描上面的條碼,請透過比較獨特的表情符號驗證。",
             "verify_later": "我稍後驗證",
-            "verify_reset_warning_1": "重設您的驗證金鑰將無法復原。重設後,您將無法存取舊的加密訊息,之前任何驗證過您的朋友也會看到安全警告,直到您重新驗證。",
-            "verify_reset_warning_2": "請僅在您確定遺失了您其他所有裝置與安全金鑰時才繼續。",
             "verify_using_device": "用另一台裝置驗證",
             "verify_using_key": "使用安全金鑰進行驗證",
             "verify_using_key_or_phrase": "使用安全金鑰或密語進行驗證",
@@ -1004,11 +972,7 @@
             "title": "無法複製聊天室連結"
         },
         "error_loading_user_profile": "無法載入使用者簡介",
-        "forget_room_failed": "無法忘記聊天室 %(errCode)s",
-        "search_failed": {
-            "server_unavailable": "伺服器可能無法使用、超載,或搜尋時間過長 :(",
-            "title": "無法搜尋"
-        }
+        "forget_room_failed": "無法忘記聊天室 %(errCode)s"
     },
     "error_user_not_logged_in": "使用者未登入",
     "event_preview": {
@@ -1068,8 +1032,10 @@
         "format": "格式",
         "from_the_beginning": "從一開始",
         "generating_zip": "產生 ZIP",
+        "html": "HTML",
         "html_title": "已匯出的資料",
         "include_attachments": "包含附件",
+        "json": "JSON",
         "media_omitted": "媒體省略",
         "media_omitted_file_size": "媒體省略 - 超過檔案大小限制",
         "messages": "訊息",
@@ -1234,6 +1200,7 @@
     },
     "keyboard": {
         "activate_button": "啟動已選取按鈕",
+        "alt": "Alt",
         "autocomplete_cancel": "取消自動完成",
         "autocomplete_force": "強制完成",
         "autocomplete_navigate_next": "下一個自動完成建議",
@@ -1259,6 +1226,8 @@
         "composer_undo": "復原編輯",
         "dismiss_read_marker_and_jump_bottom": "清除讀取標記並跳至底部",
         "end": "",
+        "enter": "你好",
+        "escape": "Esc",
         "go_home_view": "前往主畫面",
         "home": "首頁",
         "jump_first_message": "跳至第一則訊息",
@@ -1599,11 +1568,6 @@
         "ongoing": "正在刪除…",
         "reason_label": "理由(選擇性)"
     },
-    "reject_invitation_dialog": {
-        "confirmation": "您確認要拒絕邀請嗎?",
-        "failed": "無法拒絕邀請",
-        "title": "拒絕邀請"
-    },
     "report_content": {
         "description": "回報此訊息將會傳送其獨特的「活動 ID」給您家伺服器的管理員。如果此聊天室中的訊息已加密,您的家伺服器管理員將無法閱讀訊息文字或檢視任何檔案或圖片。",
         "disagree": "不同意",
@@ -1774,7 +1738,6 @@
             "you_created": "您建立了此聊天室。"
         },
         "invite_email_mismatch_suggestion": "在設定中分享此電子郵件以直接在 %(brand)s 中接收邀請。",
-        "invite_reject_ignore": "拒絕並忽略使用者",
         "invite_sent_to_email": "此邀請已傳送至 %(email)s",
         "invite_sent_to_email_room": "此 %(roomName)s 的邀請已傳送給 %(email)s",
         "invite_subtitle": "<userName/> 已邀請您",
@@ -2278,7 +2241,6 @@
             "remove_msisdn_prompt": "移除 %(phone)s?",
             "spell_check_locale_placeholder": "選擇語系"
         },
-        "image_thumbnails": "顯示圖片的預覽/縮圖",
         "inline_url_previews_default": "預設啟用行內網址預覽",
         "inline_url_previews_room": "對此聊天室中的參與者預設啟用網址預覽",
         "inline_url_previews_room_account": "對此聊天室啟用網址預覽(僅影響您)",
@@ -2390,6 +2352,7 @@
             "voip": "音訊與視訊通話"
         },
         "preferences": {
+            "Electron.enableHardwareAcceleration": "啟用硬體加速 (重新啟動%(appName)s以生效)",
             "always_show_menu_bar": "總是顯示視窗選單列",
             "autocomplete_delay": "自動完成延遲(毫秒)",
             "code_blocks_heading": "程式碼區塊",
@@ -2413,50 +2376,17 @@
         "prompt_invite": "在傳送邀請給潛在的無效 Matrix ID 前提示",
         "replace_plain_emoji": "自動取代純文字為表情符號",
         "security": {
-            "4s_public_key_in_account_data": "在帳號資料中",
-            "4s_public_key_status": "秘密儲存空間公鑰:",
-            "backup_key_cached_status": "是否快取備份金鑰:",
-            "backup_key_stored_status": "是否已儲存備份金鑰:",
-            "backup_key_unexpected_type": "預料之外的類型",
-            "backup_key_well_formed": "組成良好",
-            "backup_keys_description": "請備份您帳號的加密金鑰,以防無法使用您的工作階段。您的金鑰會被特殊的安全金鑰保護。",
+            "analytics_description": "共享匿名資料以幫助我們識別問題。沒有個人資料。沒有第三方。",
             "bulk_options_accept_all_invites": "接受所有 %(invitedRooms)s 邀請",
             "bulk_options_reject_all_invites": "拒絕所有 %(invitedRooms)s 邀請",
             "bulk_options_section": "大量選項",
-            "cross_signing_cached": "已快取至本機",
-            "cross_signing_homeserver_support": "家伺服器功能支援:",
-            "cross_signing_homeserver_support_exists": "存在",
-            "cross_signing_in_4s": "在秘密儲存空間中",
-            "cross_signing_in_memory": "在記憶體中",
-            "cross_signing_master_private_Key": "主控私鑰:",
-            "cross_signing_not_cached": "在本機找不到",
-            "cross_signing_not_found": "找不到",
-            "cross_signing_not_in_4s": "在儲存空間中找不到",
-            "cross_signing_not_stored": "未儲存",
-            "cross_signing_private_keys": "交叉簽署的私密金鑰:",
-            "cross_signing_public_keys": "交叉簽署的公開金鑰:",
-            "cross_signing_self_signing_private_key": "自行簽署私鑰:",
-            "cross_signing_user_signing_private_key": "使用者簽署私鑰:",
-            "cryptography_section": "加密",
-            "delete_backup": "刪除備份",
-            "delete_backup_confirm_description": "您確定嗎?如果沒有正確備份金鑰的話,將會遺失所有加密訊息。",
             "e2ee_default_disabled_warning": "您的伺服器管理員已停用在私密聊天室與私人訊息中預設的端對端加密。",
             "enable_message_search": "在已加密的聊天室中啟用訊息搜尋",
             "encryption_section": "加密",
-            "error_loading_key_backup_status": "無法載入金鑰備份狀態",
-            "export_megolm_keys": "匯出聊天室的端對端加密金鑰",
             "ignore_users_empty": "您沒有忽略的使用者。",
             "ignore_users_section": "忽略使用者",
-            "import_megolm_keys": "匯入聊天室端對端加密金鑰",
-            "key_backup_active": "此工作階段正在備份您的金鑰。",
-            "key_backup_active_version_none": "無",
             "key_backup_algorithm": "演算法:",
-            "key_backup_complete": "所有金鑰都已備份",
             "key_backup_connect": "將此工作階段連結至金鑰備份",
-            "key_backup_connect_prompt": "在登出前,請先將此工作階段連線到金鑰備份以避免遺失任何可能僅存在此工作階段中的金鑰。",
-            "key_backup_in_progress": "正在備份 %(sessionsRemaining)s 把金鑰…",
-            "key_backup_inactive": "此工作階段<b>並未備份您的金鑰</b>,您可還原先前的備份後再繼續新增金鑰到備份內容中。",
-            "key_backup_inactive_warning": "您<b>並未備份此裝置的金鑰</b>。",
             "message_search_disable_warning": "若停用,從加密聊天室傳來的訊息將不會出現在搜尋結果中。",
             "message_search_disabled": "將加密的訊息安全地在本機快取以出現在顯示結果中。",
             "message_search_enabled": {
@@ -2476,13 +2406,7 @@
             "message_search_unsupported": "%(brand)s 缺少某些在本機快取已加密訊息所需的元件。如果您想要實驗此功能,請加入<nativeLink>搜尋元件</nativeLink>來自行建構 %(brand)s 桌面版。",
             "message_search_unsupported_web": "%(brand)s 無法在網頁瀏覽器中執行時,安全地在本機快取加密訊息。若需搜尋加密訊息,請使用 <desktopLink>%(brand)s 桌面版</desktopLink>。",
             "record_session_details": "記錄客戶端名稱、版本與網址,以便在工作階段管理員當中能更輕鬆找出工作階段",
-            "restore_key_backup": "從備份還原",
-            "secret_storage_not_ready": "尚未準備好",
-            "secret_storage_ready": "已準備好",
-            "secret_storage_status": "秘密儲存空間:",
             "send_analytics": "傳送分析資料",
-            "session_id": "工作階段 ID:",
-            "session_key": "工作階段金鑰:",
             "strict_encryption": "不要從此工作階段傳送加密訊息到未驗證的工作階段"
         },
         "send_read_receipts": "傳送讀取回條",
@@ -2602,6 +2526,7 @@
             "metaspaces_orphans_description": "將所有不屬於某個聊天空間的聊天室集中在同一個地方。",
             "metaspaces_people_description": "將您所有的夥伴集中在同一個地方。",
             "metaspaces_subsection": "要顯示的聊天空間",
+            "spaces_explainer": "空間是將房間和人群分組的方式。除了您所在的空間之外,您也可以使用一些預先構建的空間。",
             "title": "側邊欄"
         },
         "start_automatically": "在系統登入後自動開始",
@@ -2706,8 +2631,6 @@
         "topic": "取得或設定聊天室主題",
         "topic_none": "此聊天室沒有主題。",
         "topic_room_error": "無法取得聊天室主題:找不到聊天室(%(roomId)s)",
-        "tovirtual": "切換到此聊天室的虛擬聊天室(若有)",
-        "tovirtual_not_found": "此聊天室沒有虛擬聊天室",
         "unban": "取消封鎖特定 ID 的使用者",
         "unflip": "在純文字訊息前加入 ┬──┬ ノ( ゜-゜ノ)",
         "unholdcall": "取消目前聊天室通話等候接聽狀態",
@@ -2828,7 +2751,6 @@
         "heading_without_query": "搜尋",
         "join_button_text": "加入 %(roomAddress)s",
         "keyboard_scroll_hint": "使用 <arrows/> 捲動",
-        "message_search_section_title": "其他搜尋",
         "other_rooms_in_space": "其他在 %(spaceName)s 中的聊天室",
         "public_rooms_label": "公開聊天室",
         "recent_searches_section_title": "近期搜尋",
@@ -2837,7 +2759,6 @@
         "result_may_be_hidden_privacy_warning": "出於隱私考量,可能會隱藏一些結果",
         "result_may_be_hidden_warning": "某些結果可能會被隱藏",
         "search_dialog": "搜尋對話方塊",
-        "search_messages_hint": "要搜尋訊息,請在聊天室頂部尋找此圖示 <icon/>",
         "spaces_title": "您所在的聊天空間",
         "start_group_chat_button": "開始群組聊天"
     },
@@ -2900,9 +2821,13 @@
         "n_hours_ago": "%(num)s 小時前",
         "n_minutes_ago": "%(num)s 分鐘前",
         "seconds_left": "剩 %(seconds)s 秒",
+        "short_days": "%(value)sd",
         "short_days_hours_minutes_seconds": "%(days)s 天 %(hours)s 小時 %(minutes)s 分鐘 %(seconds)s 秒",
+        "short_hours": "%(value)sh",
         "short_hours_minutes_seconds": "%(hours)s 小時 %(minutes)s 分鐘 %(seconds)s 秒",
-        "short_minutes_seconds": "%(minutes)s 分鐘 %(seconds)s 秒"
+        "short_minutes": "%(value)sm",
+        "short_minutes_seconds": "%(minutes)s 分鐘 %(seconds)s 秒",
+        "short_seconds": "%(value)sS"
     },
     "timeline": {
         "context_menu": {
@@ -3107,7 +3032,9 @@
             "sent": "%(senderName)s 向 %(targetDisplayName)s 傳送了加入聊天室的邀請。"
         },
         "m.room.tombstone": "%(senderDisplayName)s 升級了此聊天室。",
-        "m.room.topic": "%(senderDisplayName)s 將主題變更為「%(topic)s」。",
+        "m.room.topic": {
+            "changed": "%(senderDisplayName)s 將主題變更為「%(topic)s」。"
+        },
         "m.sticker": "%(senderDisplayName)s 傳送了貼圖。",
         "m.video": {
             "error_decrypting": "解密影片出錯"
@@ -3380,14 +3307,6 @@
         "ban_room_confirm_title": "從 %(roomName)s 封鎖",
         "ban_space_everything": "從我有權限的所有地方封鎖他們",
         "ban_space_specific": "從我有權限的特定地方封鎖他們",
-        "count_of_sessions": {
-            "other": "%(count)s 個工作階段",
-            "one": "%(count)s 個工作階段"
-        },
-        "count_of_verified_sessions": {
-            "other": "%(count)s 個已驗證的工作階段",
-            "one": "1 個已驗證的工作階段"
-        },
         "deactivate_confirm_action": "停用使用者",
         "deactivate_confirm_description": "停用此使用者將會把他們登出並防止他們再次登入。另外,他們也將會離開所有加入的聊天室。此動作不可逆。您確定您想要停用此使用者嗎?",
         "deactivate_confirm_title": "停用使用者?",
@@ -3398,15 +3317,12 @@
         "disinvite_button_room": "從聊天室取消邀請",
         "disinvite_button_room_name": "拒絕來自 %(roomName)s 的邀請",
         "disinvite_button_space": "從聊天空間取消邀請",
-        "edit_own_devices": "編輯裝置",
         "error_ban_user": "無法封鎖使用者",
         "error_deactivate": "無法停用使用者",
         "error_kicking_user": "無法移除使用者",
         "error_mute_user": "無法將使用者設為靜音",
         "error_revoke_3pid_invite_description": "無法撤銷邀請。伺服器可能暫時發生問題,或您沒有足夠的權限來撤銷邀請。",
         "error_revoke_3pid_invite_title": "無法撤銷邀請",
-        "hide_sessions": "隱藏工作階段",
-        "hide_verified_sessions": "隱藏已驗證的工作階段",
         "ignore_confirm_description": "來自該使用者的所有訊息與邀請都將被隱藏。您確定要忽略它們嗎?",
         "ignore_confirm_title": "忽略 %(user)s",
         "invited_by": "由 %(sender)s 邀請",
@@ -3494,7 +3410,6 @@
         "hide_sidebar_button": "隱藏側邊欄",
         "input_devices": "輸入裝置",
         "join_button_tooltip_call_full": "抱歉 — 此通話目前已滿",
-        "join_button_tooltip_connecting": "連線中",
         "maximise": "填滿螢幕",
         "misconfigured_server": "由於伺服器設定錯誤,無法通話",
         "misconfigured_server_description": "請聯繫您家伺服器(<code>%(homeserverDomain)s</code>)的管理員建立一套 TURN 伺服器,使通話能更穩定運作。",
diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts
index 0088a796832dbee744639e44bb86c2ff2b8ff92d..7ec65a5f8481d19883af1115b6a5ac71ef5742a2 100644
--- a/src/indexing/BaseEventIndexManager.ts
+++ b/src/indexing/BaseEventIndexManager.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    IMatrixProfile,
-    IEventWithRoomId as IMatrixEvent,
-    IResultRoomEvents,
-    Direction,
+    type IMatrixProfile,
+    type IEventWithRoomId as IMatrixEvent,
+    type IResultRoomEvents,
+    type Direction,
 } from "matrix-js-sdk/src/matrix";
 
 // The following interfaces take their names and member names from seshat and the spec
diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts
index 7a126b2ffb602be9dfba78b0189f1697a392b537..235d68fffdb7855de4f3ede3ca3000e6e1830901 100644
--- a/src/indexing/EventIndex.ts
+++ b/src/indexing/EventIndex.ts
@@ -9,26 +9,26 @@ Please see LICENSE files in the repository root for full details.
 import { EventEmitter } from "events";
 import {
     RoomMember,
-    Room,
+    type Room,
     RoomEvent,
-    RoomState,
+    type RoomState,
     RoomStateEvent,
-    MatrixEvent,
+    type MatrixEvent,
     Direction,
     EventTimeline,
-    EventTimelineSet,
-    IRoomTimelineData,
+    type EventTimelineSet,
+    type IRoomTimelineData,
     EventType,
     ClientEvent,
-    MatrixClient,
+    type MatrixClient,
     HTTPError,
-    IEventWithRoomId,
-    IMatrixProfile,
-    IResultRoomEvents,
-    SyncStateData,
-    SyncState,
-    TimelineIndex,
-    TimelineWindow,
+    type IEventWithRoomId,
+    type IMatrixProfile,
+    type IResultRoomEvents,
+    type SyncStateData,
+    type SyncState,
+    type TimelineIndex,
+    type TimelineWindow,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { sleep } from "matrix-js-sdk/src/utils";
@@ -38,7 +38,13 @@ import PlatformPeg from "../PlatformPeg";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import SettingsStore from "../settings/SettingsStore";
 import { SettingLevel } from "../settings/SettingLevel";
-import { ICrawlerCheckpoint, IEventAndProfile, IIndexStats, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
+import {
+    type ICrawlerCheckpoint,
+    type IEventAndProfile,
+    type IIndexStats,
+    type ILoadArgs,
+    type ISearchArgs,
+} from "./BaseEventIndexManager";
 import { asyncFilter } from "../utils/arrays.ts";
 
 // The time in ms that the crawler will wait loop iterations if there
diff --git a/src/integrations/IntegrationManagerInstance.ts b/src/integrations/IntegrationManagerInstance.ts
index d74b380bfdb52859bc969053c6bd3e496e122494..c6c66c80d68ae0e7791e3784b71972913a4a597a 100644
--- a/src/integrations/IntegrationManagerInstance.ts
+++ b/src/integrations/IntegrationManagerInstance.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ComponentProps } from "react";
+import { type ComponentProps } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import type { Room } from "matrix-js-sdk/src/matrix";
diff --git a/src/integrations/IntegrationManagers.ts b/src/integrations/IntegrationManagers.ts
index a8fe8a33dc34be9cae069f6a33b27af2b57ce4f6..6d8ab69ccc88944df2195aa1638bd7b477958851 100644
--- a/src/integrations/IntegrationManagers.ts
+++ b/src/integrations/IntegrationManagers.ts
@@ -7,9 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type IClientWellKnown, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import type { MatrixEvent } from "matrix-js-sdk/src/matrix";
 import SdkConfig from "../SdkConfig";
 import Modal from "../Modal";
 import { IntegrationManagerInstance, Kind } from "./IntegrationManagerInstance";
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index 937a6c18670656812cdd450a61e547b408b48406..175f9c149fb240e00386810af7a35b7b0fcfa564 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -12,10 +12,10 @@ Please see LICENSE files in the repository root for full details.
 import counterpart from "counterpart";
 import React from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 import { MapWithDefault } from "matrix-js-sdk/src/utils";
-import { normalizeLanguageKey, TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
-import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api";
+import { normalizeLanguageKey, type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
+import { type TranslationStringsObject } from "@matrix-org/react-sdk-module-api";
 import _ from "lodash";
 
 import type Translations from "./i18n/strings/en_EN.json";
@@ -113,6 +113,10 @@ export function _td(s: TranslationKey): TranslationKey {
     return s;
 }
 
+function isValidTranslation(translated: string): boolean {
+    return typeof translated === "string" && !translated.startsWith("missing translation:");
+}
+
 /**
  * to improve screen reader experience translations that are not in the main page language
  * eg a translation that fell back to english from another language
@@ -124,30 +128,26 @@ export function _td(s: TranslationKey): TranslationKey {
  * */
 const translateWithFallback = (text: string, options?: IVariables): { translated: string; isFallback?: boolean } => {
     const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() });
-    if (!translated || translated.startsWith("missing translation:")) {
-        const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE });
-        if (
-            (!fallbackTranslated || fallbackTranslated.startsWith("missing translation:")) &&
-            process.env.NODE_ENV !== "development"
-        ) {
-            // Even the translation via FALLBACK_LOCALE failed; this can happen if
-            //
-            // 1. The string isn't in the translations dictionary, usually because you're in develop
-            // and haven't run yarn i18n
-            // 2. Loading the translation resources over the network failed, which can happen due to
-            // to network or if the client tried to load a translation that's been removed from the
-            // server.
-            //
-            // At this point, its the lesser evil to show the untranslated text, which
-            // will be in English, so the user can still make out *something*, rather than an opaque
-            // "missing translation" error.
-            //
-            // Don't do this in develop so people remember to run yarn i18n.
-            return { translated: text, isFallback: true };
-        }
+    if (isValidTranslation(translated)) {
+        return { translated };
+    }
+
+    const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE });
+    if (isValidTranslation(fallbackTranslated)) {
         return { translated: fallbackTranslated, isFallback: true };
     }
-    return { translated };
+
+    // Even the translation via FALLBACK_LOCALE failed; this can happen if
+    //
+    // 1. The string isn't in the translations dictionary, usually because you're in develop
+    // and haven't run yarn i18n
+    // 2. Loading the translation resources over the network failed, which can happen due to
+    // to network or if the client tried to load a translation that's been removed from the
+    // server.
+    //
+    // At this point, its the lesser evil to show the i18n key which will be in English but not human-friendly,
+    // so the user can still make out *something*, rather than an opaque possibly-untranslated "missing translation" error.
+    return { translated: text, isFallback: true };
 };
 
 // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
@@ -454,55 +454,35 @@ type Languages = {
     [lang: string]: string;
 };
 
-export function setLanguage(preferredLangs: string | string[]): Promise<void> {
-    if (!Array.isArray(preferredLangs)) {
-        preferredLangs = [preferredLangs];
-    }
+export async function setLanguage(...preferredLangs: string[]): Promise<void> {
+    PlatformPeg.get()?.setLanguage(preferredLangs);
 
-    const plaf = PlatformPeg.get();
-    if (plaf) {
-        plaf.setLanguage(preferredLangs);
+    const availableLanguages = await getLangsJson();
+    let chosenLanguage = preferredLangs.find((lang) => availableLanguages.hasOwnProperty(lang));
+    if (!chosenLanguage) {
+        // Fallback to en_EN if none is found
+        chosenLanguage = "en";
+        logger.error("Unable to find an appropriate language, preferred: ", preferredLangs);
     }
 
-    let langToUse: string;
-    let availLangs: Languages;
-    return getLangsJson()
-        .then((result) => {
-            availLangs = result;
+    const languageData = await getLanguageRetry(i18nFolder + availableLanguages[chosenLanguage]);
 
-            for (let i = 0; i < preferredLangs.length; ++i) {
-                if (availLangs.hasOwnProperty(preferredLangs[i])) {
-                    langToUse = preferredLangs[i];
-                    break;
-                }
-            }
-            if (!langToUse) {
-                // Fallback to en_EN if none is found
-                langToUse = "en";
-                logger.error("Unable to find an appropriate language");
-            }
+    counterpart.registerTranslations(chosenLanguage, languageData);
+    counterpart.setLocale(chosenLanguage);
 
-            return getLanguageRetry(i18nFolder + availLangs[langToUse]);
-        })
-        .then(async (langData): Promise<ICounterpartTranslation | undefined> => {
-            counterpart.registerTranslations(langToUse, langData);
-            await registerCustomTranslations();
-            counterpart.setLocale(langToUse);
-            await SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
-            // Adds a lot of noise to test runs, so disable logging there.
-            if (process.env.NODE_ENV !== "test") {
-                logger.log("set language to " + langToUse);
-            }
+    await SettingsStore.setValue("language", null, SettingLevel.DEVICE, chosenLanguage);
+    // Adds a lot of noise to test runs, so disable logging there.
+    if (process.env.NODE_ENV !== "test") {
+        logger.log("set language to " + chosenLanguage);
+    }
 
-            // Set 'en' as fallback language:
-            if (langToUse !== "en") {
-                return getLanguageRetry(i18nFolder + availLangs["en"]);
-            }
-        })
-        .then(async (langData): Promise<void> => {
-            if (langData) counterpart.registerTranslations("en", langData);
-            await registerCustomTranslations();
-        });
+    // Set 'en' as fallback language:
+    if (chosenLanguage !== "en") {
+        const fallbackLanguageData = await getLanguageRetry(i18nFolder + availableLanguages["en"]);
+        counterpart.registerTranslations("en", fallbackLanguageData);
+    }
+
+    await registerCustomTranslations();
 }
 
 type Language = {
@@ -529,8 +509,7 @@ export async function getAllLanguagesWithLabels(): Promise<Language[]> {
 
 export function getLanguagesFromBrowser(): readonly string[] {
     if (navigator.languages && navigator.languages.length) return navigator.languages;
-    if (navigator.language) return [navigator.language];
-    return [navigator.userLanguage || "en"];
+    return [navigator.language ?? "en"];
 }
 
 export function getLanguageFromBrowser(): string {
diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts
index 198d5982c16fa70373cc11155e33f71b6d7c4a46..0a96e088e9f16fcb363f39b4ec4e76419c65867f 100644
--- a/src/linkify-matrix.ts
+++ b/src/linkify-matrix.ts
@@ -8,8 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import * as linkifyjs from "linkifyjs";
-import { EventListeners, Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
-import linkifyElement from "linkify-element";
+import { type EventListeners, type Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
 import linkifyString from "linkify-string";
 import { getHttpUriForMxc, User } from "matrix-js-sdk/src/matrix";
 
@@ -20,8 +19,8 @@ import {
 } from "./utils/permalinks/Permalinks";
 import dis from "./dispatcher/dispatcher";
 import { Action } from "./dispatcher/actions";
-import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
-import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
+import { type ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
+import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
 
@@ -199,7 +198,7 @@ export const options: Opts = {
         rel: "noreferrer noopener",
     },
 
-    ignoreTags: ["pre", "code"],
+    ignoreTags: ["a", "pre", "code"],
 
     className: "linkified",
 
@@ -274,5 +273,4 @@ PERMITTED_URL_SCHEMES.forEach((scheme) => {
 registerCustomProtocol("mxc", false);
 
 export const linkify = linkifyjs;
-export const _linkifyElement = linkifyElement;
 export const _linkifyString = linkifyString;
diff --git a/src/mjolnir/Mjolnir.ts b/src/mjolnir/Mjolnir.ts
index cea128e5efafd834fedcb5a90b8f3e44a892ad71..be4acf5965cf03e36182576110d1ec20ca1b0f93 100644
--- a/src/mjolnir/Mjolnir.ts
+++ b/src/mjolnir/Mjolnir.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, RoomStateEvent, Preset } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, RoomStateEvent, Preset } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../MatrixClientPeg";
@@ -15,8 +15,8 @@ import SettingsStore from "../settings/SettingsStore";
 import { _t } from "../languageHandler";
 import dis from "../dispatcher/dispatcher";
 import { SettingLevel } from "../settings/SettingLevel";
-import { ActionPayload } from "../dispatcher/payloads";
-import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
 import { Action } from "../dispatcher/actions";
 
 // TODO: Move this and related files to the js-sdk or something once finalized.
diff --git a/src/models/Call.ts b/src/models/Call.ts
index 03645ed47eeb6df06c788ec9d426669c5aa49fa1..70c7e4779f69bcd574ed096a5c8925ca55c517e1 100644
--- a/src/models/Call.ts
+++ b/src/models/Call.ts
@@ -11,29 +11,27 @@ import {
     RoomEvent,
     RoomStateEvent,
     EventType,
-    MatrixClient,
-    IMyDevice,
-    Room,
-    RoomMember,
+    type MatrixClient,
+    type IMyDevice,
+    type Room,
+    type RoomMember,
 } from "matrix-js-sdk/src/matrix";
-import { KnownMembership, Membership } from "matrix-js-sdk/src/types";
+import { KnownMembership, type Membership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 import { CallType } from "matrix-js-sdk/src/webrtc/call";
 import { NamespacedValue } from "matrix-js-sdk/src/NamespacedValue";
-import { IWidgetApiRequest } from "matrix-widget-api";
+import { type IWidgetApiRequest, type ClientWidgetApi, type IWidgetData } from "matrix-widget-api";
 import {
     MatrixRTCSession,
     MatrixRTCSessionEvent,
-    CallMembership,
+    type CallMembership,
     MatrixRTCSessionManagerEvents,
-    ICallNotifyContent,
+    type ICallNotifyContent,
 } from "matrix-js-sdk/src/matrixrtc";
 
 import type EventEmitter from "events";
-import type { ClientWidgetApi, IWidgetData } from "matrix-widget-api";
 import type { IApp } from "../stores/WidgetStore";
-import SdkConfig, { DEFAULTS } from "../SdkConfig";
 import SettingsStore from "../settings/SettingsStore";
 import MediaDeviceHandler, { MediaDeviceKindEnum } from "../MediaDeviceHandler";
 import { timeout } from "../utils/promise";
@@ -44,12 +42,13 @@ import WidgetStore from "../stores/WidgetStore";
 import { WidgetMessagingStore, WidgetMessagingStoreEvent } from "../stores/widgets/WidgetMessagingStore";
 import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidgetStore";
 import { getCurrentLanguage } from "../languageHandler";
-import { PosthogAnalytics } from "../PosthogAnalytics";
+import { Anonymity, PosthogAnalytics } from "../PosthogAnalytics";
 import { UPDATE_EVENT } from "../stores/AsyncStore";
 import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
 import { isVideoRoom } from "../utils/video-rooms";
 import { FontWatcher } from "../settings/watchers/FontWatcher";
-import { JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
+import { type JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
+import SdkConfig from "../SdkConfig.ts";
 
 const TIMEOUT_MS = 16000;
 
@@ -78,13 +77,7 @@ const waitForEvent = async (
 };
 
 export enum ConnectionState {
-    // Widget related states that are equivalent to disconnected,
-    // but hold additional information about the state of the widget.
-    Lobby = "lobby",
-    WidgetLoading = "widget_loading",
     Disconnected = "disconnected",
-
-    Connecting = "connecting",
     Connected = "connected",
     Disconnecting = "disconnecting",
 }
@@ -92,15 +85,10 @@ export enum ConnectionState {
 export const isConnected = (state: ConnectionState): boolean =>
     state === ConnectionState.Connected || state === ConnectionState.Disconnecting;
 
-export enum Layout {
-    Tile = "tile",
-    Spotlight = "spotlight",
-}
-
 export enum CallEvent {
     ConnectionState = "connection_state",
     Participants = "participants",
-    Layout = "layout",
+    Close = "close",
     Destroy = "destroy",
 }
 
@@ -110,7 +98,7 @@ interface CallEventHandlerMap {
         participants: Map<RoomMember, Set<string>>,
         prevParticipants: Map<RoomMember, Set<string>>,
     ) => void;
-    [CallEvent.Layout]: (layout: Layout) => void;
+    [CallEvent.Close]: () => void;
     [CallEvent.Destroy]: () => void;
 }
 
@@ -168,6 +156,17 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
         this.emit(CallEvent.Participants, value, prevValue);
     }
 
+    private _presented = false;
+    /**
+     * Whether the call widget is currently being presented in the user interface.
+     */
+    public get presented(): boolean {
+        return this._presented;
+    }
+    public set presented(value: boolean) {
+        this._presented = value;
+    }
+
     protected constructor(
         /**
          * The widget used to access this call.
@@ -178,6 +177,7 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
         super();
         this.widgetUid = WidgetUtils.getWidgetUid(this.widget);
         this.room = this.client.getRoom(this.roomId)!;
+        WidgetMessagingStore.instance.on(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
     }
 
     /**
@@ -222,8 +222,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
      * Only call this if the call state is: ConnectionState.Disconnected.
      */
     public async start(): Promise<void> {
-        this.connectionState = ConnectionState.WidgetLoading;
-
         const { [MediaDeviceKindEnum.AudioInput]: audioInputs, [MediaDeviceKindEnum.VideoInput]: videoInputs } =
             (await MediaDeviceHandler.getDevices())!;
 
@@ -258,16 +256,9 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
                 throw new Error(`Failed to bind call widget in room ${this.roomId}: ${e}`);
             }
         }
-        this.connectionState = ConnectionState.Connecting;
-        try {
-            await this.performConnection(audioInput, videoInput);
-        } catch (e) {
-            this.connectionState = ConnectionState.Disconnected;
-            throw e;
-        }
+        await this.performConnection(audioInput, videoInput);
 
         this.room.on(RoomEvent.MyMembership, this.onMyMembership);
-        WidgetMessagingStore.instance.on(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
         window.addEventListener("beforeunload", this.beforeUnload);
         this.connectionState = ConnectionState.Connected;
     }
@@ -281,39 +272,54 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
         this.connectionState = ConnectionState.Disconnecting;
         await this.performDisconnection();
         this.setDisconnected();
+        this.close();
     }
 
     /**
-     * Manually marks the call as disconnected and cleans up.
+     * Manually marks the call as disconnected.
      */
     public setDisconnected(): void {
         this.room.off(RoomEvent.MyMembership, this.onMyMembership);
-        WidgetMessagingStore.instance.off(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
         window.removeEventListener("beforeunload", this.beforeUnload);
-        this.messaging = null;
         this.connectionState = ConnectionState.Disconnected;
     }
 
+    /**
+     * Stops further communication with the widget and tells the UI to close.
+     */
+    protected close(): void {
+        this.messaging = null;
+        this.emit(CallEvent.Close);
+    }
+
     /**
      * Stops all internal timers and tasks to prepare for garbage collection.
      */
     public destroy(): void {
-        if (this.connected) this.setDisconnected();
+        if (this.connected) {
+            this.setDisconnected();
+            this.close();
+        }
+        WidgetMessagingStore.instance.off(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
         this.emit(CallEvent.Destroy);
     }
 
-    private onMyMembership = async (_room: Room, membership: Membership): Promise<void> => {
+    private readonly onMyMembership = async (_room: Room, membership: Membership): Promise<void> => {
         if (membership !== KnownMembership.Join) this.setDisconnected();
     };
 
-    private onStopMessaging = (uid: string): void => {
-        if (uid === this.widgetUid) {
+    private readonly onStopMessaging = (uid: string): void => {
+        if (uid === this.widgetUid && this.connected) {
             logger.log("The widget died; treating this as a user hangup");
             this.setDisconnected();
+            this.close();
         }
     };
 
-    private beforeUnload = (): void => this.setDisconnected();
+    private beforeUnload = (): void => {
+        this.setDisconnected();
+        this.close();
+    };
 }
 
 export type { JitsiCallMemberContent };
@@ -467,7 +473,6 @@ export class JitsiCall extends Call {
         audioInput: MediaDeviceInfo | null,
         videoInput: MediaDeviceInfo | null,
     ): Promise<void> {
-        this.connectionState = ConnectionState.Lobby;
         // Ensure that the messaging doesn't get stopped while we're waiting for responses
         const dontStopMessaging = new Promise<void>((resolve, reject) => {
             const messagingStore = WidgetMessagingStore.instance;
@@ -570,9 +575,9 @@ export class JitsiCall extends Call {
         super.destroy();
     }
 
-    private onRoomState = (): void => this.updateParticipants();
+    private readonly onRoomState = (): void => this.updateParticipants();
 
-    private onConnectionState = async (state: ConnectionState, prevState: ConnectionState): Promise<void> => {
+    private readonly onConnectionState = async (state: ConnectionState, prevState: ConnectionState): Promise<void> => {
         if (state === ConnectionState.Connected && !isConnected(prevState)) {
             this.updateParticipants(); // Local echo
 
@@ -598,18 +603,18 @@ export class JitsiCall extends Call {
         }
     };
 
-    private onDock = async (): Promise<void> => {
+    private readonly onDock = async (): Promise<void> => {
         // The widget is no longer a PiP, so let's restore the default layout
         await this.messaging!.transport.send(ElementWidgetActions.TileLayout, {});
     };
 
-    private onUndock = async (): Promise<void> => {
+    private readonly onUndock = async (): Promise<void> => {
         // The widget has become a PiP, so let's switch Jitsi to spotlight mode
         // to only show the active speaker and economize on space
         await this.messaging!.transport.send(ElementWidgetActions.SpotlightLayout, {});
     };
 
-    private onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
+    private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
         // If we're already in the middle of a client-initiated disconnection,
         // ignore the event
         if (this.connectionState === ConnectionState.Disconnecting) return;
@@ -618,14 +623,15 @@ export class JitsiCall extends Call {
 
         // In case this hangup is caused by Jitsi Meet crashing at startup,
         // wait for the connection event in order to avoid racing
-        if (this.connectionState === ConnectionState.Connecting) {
+        if (this.connectionState === ConnectionState.Disconnected) {
             await waitForEvent(this, CallEvent.ConnectionState);
         }
 
-        await this.messaging!.transport.reply(ev.detail, {}); // ack
+        this.messaging!.transport.reply(ev.detail, {}); // ack
         this.setDisconnected();
+        this.close();
         // In video rooms we immediately want to restart the call after hangup
-        // The lobby will be shown again and it connects to all signals from EC and Jitsi.
+        // The lobby will be shown again and it connects to all signals from Jitsi.
         if (isVideoRoom(this.room)) {
             this.start();
         }
@@ -645,29 +651,26 @@ export class ElementCall extends Call {
 
     private settingsStoreCallEncryptionWatcher?: string;
     private terminationTimer?: number;
-    private _layout = Layout.Tile;
-    public get layout(): Layout {
-        return this._layout;
+
+    public get presented(): boolean {
+        return super.presented;
     }
-    protected set layout(value: Layout) {
-        this._layout = value;
-        this.emit(CallEvent.Layout, value);
+    public set presented(value: boolean) {
+        super.presented = value;
+        this.checkDestroy();
     }
 
     private static generateWidgetUrl(client: MatrixClient, roomId: string): URL {
-        const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
-        // The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
-        // We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
-        // This is prohibited in EC where a hashed version of the analyticsID is used for the actual posthog identification.
-        // We can pass the raw EW analyticsID here since we need to trust EC with not sending sensitive data to posthog (EC has access to more sensible data than the analyticsID e.g. the username)
-        const analyticsID: string = accountAnalyticsData?.getContent().pseudonymousAnalyticsOptIn
-            ? accountAnalyticsData?.getContent().id
-            : "";
+        const baseUrl = window.location.href;
+        let url = new URL("./widgets/element-call/index.html#", baseUrl); // this strips hash fragment from baseUrl
+
+        const elementCallUrl = SettingsStore.getValue("Developer.elementCallUrl");
+        if (elementCallUrl) url = new URL(elementCallUrl);
+
         // Splice together the Element Call URL for this call
         const params = new URLSearchParams({
             embed: "true", // We're embedding EC within another application
             // Template variables are used, so that this can be configured using the widget data.
-            preload: "$preload", // We want it to load in the background.
             skipLobby: "$skipLobby", // Skip the lobby in case we show a lobby component of our own.
             returnToLobby: "$returnToLobby", // Returns to the lobby (instead of blank screen) when the call ends. (For video rooms)
             perParticipantE2EE: "$perParticipantE2EE",
@@ -679,12 +682,44 @@ export class ElementCall extends Call {
             lang: getCurrentLanguage().replace("_", "-"),
             fontScale: (FontWatcher.getRootFontSize() / FontWatcher.getBrowserDefaultFontSize()).toString(),
             theme: "$org.matrix.msc2873.client_theme",
-            analyticsID,
         });
 
-        if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", "true");
-        if (SettingsStore.getValue("feature_allow_screen_share_only_mode"))
+        const rageshakeSubmitUrl = SdkConfig.get("bug_report_endpoint_url");
+        if (rageshakeSubmitUrl) {
+            params.append("rageshakeSubmitUrl", rageshakeSubmitUrl);
+        }
+
+        const posthogConfig = SdkConfig.get("posthog");
+        if (posthogConfig && PosthogAnalytics.instance.getAnonymity() !== Anonymity.Disabled) {
+            const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE)?.getContent();
+            // The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
+            // We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
+            // This is prohibited in EC where a hashed version of the analyticsID is used for the actual posthog identification.
+            // We can pass the raw EW analyticsID here since we need to trust EC with not sending sensitive data to posthog (EC has access to more sensible data than the analyticsID e.g. the username)
+            const analyticsID: string = accountAnalyticsData?.pseudonymousAnalyticsOptIn
+                ? accountAnalyticsData?.id
+                : "";
+
+            params.append("analyticsID", analyticsID); // Legacy, deprecated in favour of posthogUserId
+            params.append("posthogUserId", analyticsID);
+            params.append("posthogApiHost", posthogConfig.api_host);
+            params.append("posthogApiKey", posthogConfig.project_api_key);
+
+            // We gate passing sentry behind analytics consent as EC shares data automatically without user-consent,
+            // unlike EW where data is shared upon an intentional user action (rageshake).
+            const sentryConfig = SdkConfig.get("sentry");
+            if (sentryConfig) {
+                params.append("sentryDsn", sentryConfig.dsn);
+                params.append("sentryEnvironment", sentryConfig.environment ?? "");
+            }
+        }
+
+        if (SettingsStore.getValue("fallbackICEServerAllowed")) {
+            params.append("allowIceFallback", "true");
+        }
+        if (SettingsStore.getValue("feature_allow_screen_share_only_mode")) {
             params.append("allowVoipWithNoMedia", "true");
+        }
 
         // Set custom fonts
         if (SettingsStore.getValue("useSystemFont")) {
@@ -699,25 +734,19 @@ export class ElementCall extends Call {
                 .forEach((font) => params.append("font", font));
         }
 
-        const url = new URL(SdkConfig.get("element_call").url ?? DEFAULTS.element_call.url!);
-        url.pathname = "/room";
         const replacedUrl = params.toString().replace(/%24/g, "$");
         url.hash = `#?${replacedUrl}`;
         return url;
     }
 
     // Creates a new widget if there isn't any widget of typ Call in this room.
-    // Defaults for creating a new widget are: skipLobby = false, preload = false
+    // Defaults for creating a new widget are: skipLobby = false
     // When there is already a widget the current widget configuration will be used or can be overwritten
-    // by passing the according parameters (skipLobby, preload).
-    //
-    // `preload` is deprecated. We used it for optimizing EC by using a custom EW call lobby and preloading the iframe.
-    // now it should always be false.
+    // by passing the according parameters (skipLobby).
     private static createOrGetCallWidget(
         roomId: string,
         client: MatrixClient,
         skipLobby: boolean | undefined,
-        preload: boolean | undefined,
         returnToLobby: boolean | undefined,
     ): IApp {
         const ecWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.CALL.matches(app.type));
@@ -728,9 +757,6 @@ export class ElementCall extends Call {
             if (skipLobby !== undefined) {
                 overwrites.skipLobby = skipLobby;
             }
-            if (preload !== undefined) {
-                overwrites.preload = preload;
-            }
             if (returnToLobby !== undefined) {
                 overwrites.returnToLobby = returnToLobby;
             }
@@ -741,9 +767,9 @@ export class ElementCall extends Call {
         // To use Element Call without touching room state, we create a virtual
         // widget (one that doesn't have a corresponding state event)
         const url = ElementCall.generateWidgetUrl(client, roomId);
-        return WidgetStore.instance.addVirtualWidget(
+        const createdWidget = WidgetStore.instance.addVirtualWidget(
             {
-                id: randomString(24), // So that it's globally unique
+                id: secureRandomString(24), // So that it's globally unique
                 creatorUserId: client.getUserId()!,
                 name: "Element Call",
                 type: WidgetType.CALL.preferred,
@@ -755,13 +781,14 @@ export class ElementCall extends Call {
                     {},
                     {
                         skipLobby: skipLobby ?? false,
-                        preload: preload ?? false,
                         returnToLobby: returnToLobby ?? false,
                     },
                 ),
             },
             roomId,
         );
+        WidgetStore.instance.emit(UPDATE_EVENT, null);
+        return createdWidget;
     }
 
     private static getWidgetData(
@@ -795,7 +822,7 @@ export class ElementCall extends Call {
         super(widget, client);
 
         this.session.on(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
-        this.client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
+        this.client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, this.checkDestroy);
         SettingsStore.watchSetting(
             "feature_disable_call_per_sender_encryption",
             null,
@@ -819,7 +846,6 @@ export class ElementCall extends Call {
                 room.roomId,
                 room.client,
                 undefined,
-                undefined,
                 isVideoRoom(room),
             );
             return new ElementCall(session, availableOrCreatedWidget, room.client);
@@ -828,9 +854,8 @@ export class ElementCall extends Call {
         return null;
     }
 
-    public static async create(room: Room, skipLobby = false): Promise<void> {
-        ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, false, isVideoRoom(room));
-        WidgetStore.instance.emit(UPDATE_EVENT, null);
+    public static create(room: Room, skipLobby = false): void {
+        ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, isVideoRoom(room));
     }
 
     protected async sendCallNotify(): Promise<void> {
@@ -862,31 +887,10 @@ export class ElementCall extends Call {
         audioInput: MediaDeviceInfo | null,
         videoInput: MediaDeviceInfo | null,
     ): Promise<void> {
-        // The JoinCall action is only send if the widget is waiting for it.
-        if (this.widget.data?.preload) {
-            try {
-                await this.messaging!.transport.send(ElementWidgetActions.JoinCall, {
-                    audioInput: audioInput?.label ?? null,
-                    videoInput: videoInput?.label ?? null,
-                });
-            } catch (e) {
-                throw new Error(`Failed to join call in room ${this.roomId}: ${e}`);
-            }
-        }
-        this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
-        this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
         this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
-        this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, async (ev) => {
-            ev.preventDefault();
-            await this.messaging!.transport.reply(ev.detail, {}); // ack
-        });
+        this.messaging!.once(`action:${ElementWidgetActions.Close}`, this.onClose);
+        this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
 
-        if (!this.widget.data?.skipLobby) {
-            // If we do not skip the lobby we need to wait until the widget has
-            // connected to matrixRTC. This is either observed through the session state
-            // or the MatrixRTCSessionManager session started event.
-            this.connectionState = ConnectionState.Lobby;
-        }
         // TODO: if the widget informs us when the join button is clicked (widget action), so we can
         // - set state to connecting
         // - send call notify
@@ -926,17 +930,16 @@ export class ElementCall extends Call {
     }
 
     public setDisconnected(): void {
-        this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
-        this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
+        this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
+        this.messaging!.off(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
         super.setDisconnected();
     }
 
     public destroy(): void {
         ActiveWidgetStore.instance.destroyPersistentWidget(this.widget.id, this.widget.roomId);
         WidgetStore.instance.removeVirtualWidget(this.widget.id, this.widget.roomId);
-        this.messaging?.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
         this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
-        this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
+        this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.checkDestroy);
 
         SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
         clearTimeout(this.terminationTimer);
@@ -945,23 +948,13 @@ export class ElementCall extends Call {
         super.destroy();
     }
 
-    private onRTCSessionEnded = (roomId: string, session: MatrixRTCSession): void => {
-        // Don't destroy the call on hangup for video call rooms.
-        if (roomId === this.roomId && !this.room.isCallRoom()) {
-            this.destroy();
-        }
+    private checkDestroy = (): void => {
+        // A call ceases to exist as soon as all participants leave and also the
+        // user isn't looking at it (for example, waiting in an empty lobby)
+        if (this.session.memberships.length === 0 && !this.presented && !this.room.isCallRoom()) this.destroy();
     };
 
-    /**
-     * Sets the call's layout.
-     * @param layout The layout to switch to.
-     */
-    public async setLayout(layout: Layout): Promise<void> {
-        const action = layout === Layout.Tile ? ElementWidgetActions.TileLayout : ElementWidgetActions.SpotlightLayout;
-        await this.messaging!.transport.send(action, {});
-    }
-
-    private onMembershipChanged = (): void => this.updateParticipants();
+    private readonly onMembershipChanged = (): void => this.updateParticipants();
 
     private updateParticipants(): void {
         const participants = new Map<RoomMember, Set<string>>();
@@ -981,9 +974,14 @@ export class ElementCall extends Call {
         this.participants = participants;
     }
 
-    private onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
+    private readonly onDeviceMute = (ev: CustomEvent<IWidgetApiRequest>): void => {
+        ev.preventDefault();
+        this.messaging!.transport.reply(ev.detail, {}); // ack
+    };
+
+    private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
         ev.preventDefault();
-        await this.messaging!.transport.reply(ev.detail, {}); // ack
+        this.messaging!.transport.reply(ev.detail, {}); // ack
         this.setDisconnected();
         // In video rooms we immediately want to reconnect after hangup
         // This starts the lobby again and connects to all signals from EC.
@@ -992,16 +990,11 @@ export class ElementCall extends Call {
         }
     };
 
-    private onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
-        ev.preventDefault();
-        this.layout = Layout.Tile;
-        await this.messaging!.transport.reply(ev.detail, {}); // ack
-    };
-
-    private onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
+    private readonly onClose = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
         ev.preventDefault();
-        this.layout = Layout.Spotlight;
-        await this.messaging!.transport.reply(ev.detail, {}); // ack
+        this.messaging!.transport.reply(ev.detail, {}); // ack
+        // User is done with the call; tell the UI to close it
+        this.close();
     };
 
     public clean(): Promise<void> {
diff --git a/src/models/LocalRoom.ts b/src/models/LocalRoom.ts
index 4a233f976f8d3524c97ae0886291372c23834580..a8e2c255b239245a18724d9f21b5c280d6bfe559 100644
--- a/src/models/LocalRoom.ts
+++ b/src/models/LocalRoom.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
 
-import { Member } from "../utils/direct-messages";
+import { type Member } from "../utils/direct-messages";
 
 export const LOCAL_ROOM_ID_PREFIX = "local+";
 
diff --git a/src/models/RoomUpload.ts b/src/models/RoomUpload.ts
index 61add9c7f5a29a2b638e574cc3b19696ef640a2b..3fa75d1881af24dfef736bbfedecc9dee6d449cc 100644
--- a/src/models/RoomUpload.ts
+++ b/src/models/RoomUpload.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IEventRelation, UploadProgress } from "matrix-js-sdk/src/matrix";
-import { EncryptedFile } from "matrix-js-sdk/src/types";
+import { type IEventRelation, type UploadProgress } from "matrix-js-sdk/src/matrix";
+import { type EncryptedFile } from "matrix-js-sdk/src/types";
 
 export class RoomUpload {
     public readonly abortController = new AbortController();
diff --git a/src/models/notificationsettings/PushRuleDiff.ts b/src/models/notificationsettings/PushRuleDiff.ts
index 11d8ce0522ea47f6c1777714a04ced60b457f025..992ea2f982ec8c11463cce9c5108396343ddb073 100644
--- a/src/models/notificationsettings/PushRuleDiff.ts
+++ b/src/models/notificationsettings/PushRuleDiff.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAnnotatedPushRule, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IAnnotatedPushRule, type PushRuleAction, type PushRuleKind, type RuleId } from "matrix-js-sdk/src/matrix";
 
 export type PushRuleDiff = {
     updated: PushRuleUpdate[];
diff --git a/src/models/notificationsettings/PushRuleMap.ts b/src/models/notificationsettings/PushRuleMap.ts
index 8c7ede293cf761a316b6d37cac15c960662de2cf..d57d5bbe183b482e3d29c3c1c5bbb015c3adae67 100644
--- a/src/models/notificationsettings/PushRuleMap.ts
+++ b/src/models/notificationsettings/PushRuleMap.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAnnotatedPushRule, IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IAnnotatedPushRule, type IPushRules, PushRuleKind, type RuleId } from "matrix-js-sdk/src/matrix";
 
 export type PushRuleMap = Map<RuleId | string, IAnnotatedPushRule>;
 
diff --git a/src/models/notificationsettings/reconcileNotificationSettings.ts b/src/models/notificationsettings/reconcileNotificationSettings.ts
index ce67d25d3433516e6afea8e7dcc3d64409f70df7..fdf4f4925ba1d0f453a021a0cfbe68efeb871f36 100644
--- a/src/models/notificationsettings/reconcileNotificationSettings.ts
+++ b/src/models/notificationsettings/reconcileNotificationSettings.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
 import { deepCompare } from "matrix-js-sdk/src/utils";
 
 import { NotificationUtils } from "../../notifications";
 import { StandardActions } from "../../notifications/StandardActions";
 import { RoomNotifState } from "../../RoomNotifs";
-import { NotificationSettings } from "./NotificationSettings";
-import { PushRuleDiff, PushRuleUpdate } from "./PushRuleDiff";
+import { type NotificationSettings } from "./NotificationSettings";
+import { type PushRuleDiff, type PushRuleUpdate } from "./PushRuleDiff";
 import { buildPushRuleMap } from "./PushRuleMap";
 
 function toStandardRules(
diff --git a/src/models/notificationsettings/toNotificationSettings.ts b/src/models/notificationsettings/toNotificationSettings.ts
index 33ed4b5e4accbb9ac496adaa7064e4777f4c88cb..19be22957dad142e9322c79746ffe92197222a47 100644
--- a/src/models/notificationsettings/toNotificationSettings.ts
+++ b/src/models/notificationsettings/toNotificationSettings.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPushRule, IPushRules, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IPushRule, type IPushRules, RuleId } from "matrix-js-sdk/src/matrix";
 
 import { NotificationUtils } from "../../notifications";
 import { RoomNotifState } from "../../RoomNotifs";
-import { NotificationSettings } from "./NotificationSettings";
+import { type NotificationSettings } from "./NotificationSettings";
 import { buildPushRuleMap } from "./PushRuleMap";
 
 function shouldNotify(rules: (IPushRule | null | undefined | false)[]): boolean {
diff --git a/src/models/rooms/RoomMember.ts b/src/models/rooms/RoomMember.ts
index 850ae368b18e07bae599327f6a1e941cfc32f261..eee1ae37ea3c907f3002f7116e653170de5d050d 100644
--- a/src/models/rooms/RoomMember.ts
+++ b/src/models/rooms/RoomMember.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { PresenceState } from "./PresenceState";
+import { type PresenceState } from "./PresenceState";
 
 export type RoomMember = {
     roomId: string;
diff --git a/src/models/rooms/ThreePIDInvite.ts b/src/models/rooms/ThreePIDInvite.ts
index 4fabd6e0972354b937f6054731e1f872847861bc..01e8cd46f772c693d6f4921076a5d0e504b0208b 100644
--- a/src/models/rooms/ThreePIDInvite.ts
+++ b/src/models/rooms/ThreePIDInvite.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 export type ThreePIDInvite = {
     event: MatrixEvent;
diff --git a/src/modules.d.ts b/src/modules.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..632cfe101db4de78013d90b778a81d1a542e10ce
--- /dev/null
+++ b/src/modules.d.ts
@@ -0,0 +1,13 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { ModuleApi, RuntimeModule } from "@matrix-org/react-sdk-module-api";
+
+declare module "./modules.js" {
+    export type RuntimeModuleConstructor = { new (api: ModuleApi): RuntimeModule };
+    export const INSTALLED_MODULES: RuntimeModuleConstructor[];
+}
diff --git a/src/modules/Api.ts b/src/modules/Api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..23abadf52995a8f2f8dbd8f51a6f686706042a2f
--- /dev/null
+++ b/src/modules/Api.ts
@@ -0,0 +1,73 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { createRoot, type Root } from "react-dom/client";
+
+import type { Api, RuntimeModuleConstructor } from "@element-hq/element-web-module-api";
+import { ModuleRunner } from "./ModuleRunner.ts";
+import AliasCustomisations from "../customisations/Alias.ts";
+import { RoomListCustomisations } from "../customisations/RoomList.ts";
+import ChatExportCustomisations from "../customisations/ChatExport.ts";
+import { ComponentVisibilityCustomisations } from "../customisations/ComponentVisibility.ts";
+import DirectoryCustomisations from "../customisations/Directory.ts";
+import LifecycleCustomisations from "../customisations/Lifecycle.ts";
+import * as MediaCustomisations from "../customisations/Media.ts";
+import UserIdentifierCustomisations from "../customisations/UserIdentifier.ts";
+import { WidgetPermissionCustomisations } from "../customisations/WidgetPermissions.ts";
+import { WidgetVariableCustomisations } from "../customisations/WidgetVariables.ts";
+import { ConfigApi } from "./ConfigApi.ts";
+import { I18nApi } from "./I18nApi.ts";
+
+const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
+    let used = false;
+    return (customisations: T) => {
+        if (used) throw new Error("Legacy customisations can only be registered by one module");
+        Object.assign(baseCustomisations, customisations);
+        used = true;
+    };
+};
+
+/**
+ * Implementation of the @element-hq/element-web-module-api runtime module API.
+ */
+class ModuleApi implements Api {
+    /* eslint-disable @typescript-eslint/naming-convention */
+    public async _registerLegacyModule(LegacyModule: RuntimeModuleConstructor): Promise<void> {
+        ModuleRunner.instance.registerModule((api) => new LegacyModule(api));
+    }
+    public readonly _registerLegacyAliasCustomisations = legacyCustomisationsFactory(AliasCustomisations);
+    public readonly _registerLegacyChatExportCustomisations = legacyCustomisationsFactory(ChatExportCustomisations);
+    public readonly _registerLegacyComponentVisibilityCustomisations = legacyCustomisationsFactory(
+        ComponentVisibilityCustomisations,
+    );
+    public readonly _registerLegacyDirectoryCustomisations = legacyCustomisationsFactory(DirectoryCustomisations);
+    public readonly _registerLegacyLifecycleCustomisations = legacyCustomisationsFactory(LifecycleCustomisations);
+    public readonly _registerLegacyMediaCustomisations = legacyCustomisationsFactory(MediaCustomisations);
+    public readonly _registerLegacyRoomListCustomisations = legacyCustomisationsFactory(RoomListCustomisations);
+    public readonly _registerLegacyUserIdentifierCustomisations =
+        legacyCustomisationsFactory(UserIdentifierCustomisations);
+    public readonly _registerLegacyWidgetPermissionsCustomisations =
+        legacyCustomisationsFactory(WidgetPermissionCustomisations);
+    public readonly _registerLegacyWidgetVariablesCustomisations =
+        legacyCustomisationsFactory(WidgetVariableCustomisations);
+    /* eslint-enable @typescript-eslint/naming-convention */
+
+    public readonly config = new ConfigApi();
+    public readonly i18n = new I18nApi();
+    public readonly rootNode = document.getElementById("matrixchat")!;
+
+    public createRoot(element: Element): Root {
+        return createRoot(element);
+    }
+}
+
+export type ModuleApiType = ModuleApi;
+
+if (!window.mxModuleApi) {
+    window.mxModuleApi = new ModuleApi();
+}
+export default window.mxModuleApi;
diff --git a/src/modules/AppModule.ts b/src/modules/AppModule.ts
index b7585fd2c66ef940dcf1b92eb1f8ed5e4ddabf90..a3b0f15d4e753ba5c1aec2f73cb245b3c7760a78 100644
--- a/src/modules/AppModule.ts
+++ b/src/modules/AppModule.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
+import { type RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
 
-import { ModuleFactory } from "./ModuleFactory";
+import { type ModuleFactory } from "./ModuleFactory";
 import { ProxiedModuleApi } from "./ProxiedModuleApi";
 
 /**
diff --git a/src/modules/ConfigApi.ts b/src/modules/ConfigApi.ts
new file mode 100644
index 0000000000000000000000000000000000000000..512a1c4abeab03015bb201b46718b8b5e0aa956a
--- /dev/null
+++ b/src/modules/ConfigApi.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { ConfigApi as IConfigApi, Config } from "@element-hq/element-web-module-api";
+import SdkConfig from "../SdkConfig.ts";
+
+export class ConfigApi implements IConfigApi {
+    public get(): Config;
+    public get<K extends keyof Config>(key: K): Config[K];
+    public get<K extends keyof Config = never>(key?: K): Config | Config[K] {
+        if (key === undefined) {
+            return SdkConfig.get() as Config;
+        }
+        return SdkConfig.get(key);
+    }
+}
diff --git a/src/modules/I18nApi.ts b/src/modules/I18nApi.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43c101eca61d16b6935b6c3d706988d05b954ad7
--- /dev/null
+++ b/src/modules/I18nApi.ts
@@ -0,0 +1,47 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type I18nApi as II18nApi, type Variables, type Translations } from "@element-hq/element-web-module-api";
+import counterpart from "counterpart";
+
+import { _t, getCurrentLanguage, type TranslationKey } from "../languageHandler.tsx";
+
+export class I18nApi implements II18nApi {
+    /**
+     * Read the current language of the user in IETF Language Tag format
+     */
+    public get language(): string {
+        return getCurrentLanguage();
+    }
+
+    /**
+     * Register translations for the module, may override app's existing translations
+     */
+    public register(translations: Partial<Translations>): void {
+        const langs: Record<string, Record<string, string>> = {};
+        for (const key in translations) {
+            for (const lang in translations[key]) {
+                langs[lang] = langs[lang] || {};
+                langs[lang][key] = translations[key][lang];
+            }
+        }
+
+        // Finally, tell counterpart about our translations
+        for (const lang in langs) {
+            counterpart.registerTranslations(lang, langs[lang]);
+        }
+    }
+
+    /**
+     * Perform a translation, with optional variables
+     * @param key - The key to translate
+     * @param variables - Optional variables to interpolate into the translation
+     */
+    public translate(key: TranslationKey, variables?: Variables): string {
+        return _t(key, variables);
+    }
+}
diff --git a/src/modules/ModuleComponents.tsx b/src/modules/ModuleComponents.tsx
index 53ab415603cf8d22110abf902492a9a70e28e597..60ea07a4c93de1cb88fab3346322510e7717d733 100644
--- a/src/modules/ModuleComponents.tsx
+++ b/src/modules/ModuleComponents.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField";
 import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner";
-import React, { ChangeEvent } from "react";
+import React, { type ChangeEvent } from "react";
 
 import Field from "../components/views/elements/Field";
 import Spinner from "../components/views/elements/Spinner";
diff --git a/src/modules/ModuleFactory.ts b/src/modules/ModuleFactory.ts
index 9d72a18d6b2a84a28587070d63b4a6e6a462196f..1e0f205f08813177cf7ee725022d4c13f2028c62 100644
--- a/src/modules/ModuleFactory.ts
+++ b/src/modules/ModuleFactory.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
-import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
+import { type RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
+import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
 
 export type ModuleFactory = (api: ModuleApi) => RuntimeModule;
diff --git a/src/modules/ModuleRunner.ts b/src/modules/ModuleRunner.ts
index c01015206dd086b2fe9fa2b027462bcd70155a53..172bd86aeedd546fab9ddaaf1389db221e01618c 100644
--- a/src/modules/ModuleRunner.ts
+++ b/src/modules/ModuleRunner.ts
@@ -7,19 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { safeSet } from "matrix-js-sdk/src/utils";
-import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
-import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
+import { type TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
+import { type AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
 import {
     DefaultCryptoSetupExtensions,
-    ProvideCryptoSetupExtensions,
+    type ProvideCryptoSetupExtensions,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
 import {
     DefaultExperimentalExtensions,
-    ProvideExperimentalExtensions,
+    type ProvideExperimentalExtensions,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
 
 import { AppModule } from "./AppModule";
-import { ModuleFactory } from "./ModuleFactory";
+import { type ModuleFactory } from "./ModuleFactory";
 
 import "./ModuleComponents";
 
diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts
index 7aa6ccd3b85187d2ef81bc1cefb545a8872745d5..b14b2c0c0c16ffb3ebd04898c1af4888d07d0f93 100644
--- a/src/modules/ProxiedModuleApi.ts
+++ b/src/modules/ProxiedModuleApi.ts
@@ -6,18 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
-import { TranslationStringsObject, PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations";
-import { Optional } from "matrix-events-sdk";
-import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
-import React from "react";
-import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
+import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
+import {
+    type TranslationStringsObject,
+    type PlainSubstitution,
+} from "@matrix-org/react-sdk-module-api/lib/types/translations";
+import { type Optional } from "matrix-events-sdk";
+import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
+import { type AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
 import * as Matrix from "matrix-js-sdk/src/matrix";
-import { IRegisterRequestParams } from "matrix-js-sdk/src/matrix";
-import { ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions";
+import { type IRegisterRequestParams } from "matrix-js-sdk/src/matrix";
+import { type ModuleUiDialogOptions } from "@matrix-org/react-sdk-module-api/lib/types/ModuleUiDialogOptions";
 
+import type React from "react";
 import Modal from "../Modal";
-import { _t, TranslationKey } from "../languageHandler";
+import { _t, type TranslationKey } from "../languageHandler";
 import { ModuleUiDialog } from "../components/views/dialogs/ModuleUiDialog";
 import SdkConfig from "../SdkConfig";
 import PlatformPeg from "../PlatformPeg";
@@ -27,11 +30,11 @@ import { parsePermalink } from "../utils/permalinks/Permalinks";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import { getCachedRoomIDForAlias } from "../RoomAliasCache";
 import { Action } from "../dispatcher/actions";
-import { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload";
+import { type ActionPayload } from "../dispatcher/payloads";
 import SettingsStore from "../settings/SettingsStore";
-import WidgetStore, { IApp } from "../stores/WidgetStore";
-import { Container, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
+import WidgetStore, { type IApp } from "../stores/WidgetStore";
+import { type Container, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
 
 /**
  * Glue between the `ModuleApi` interface and the react-sdk. Anticipates one instance
@@ -78,7 +81,7 @@ export class ProxiedModuleApi implements ModuleApi {
      */
     public openDialog<M extends object, P extends DialogProps, C extends DialogContent<P>>(
         initialTitleOrOptions: string | ModuleUiDialogOptions,
-        body: (props: P, ref: React.RefObject<C>) => React.ReactNode,
+        body: (props: P, ref: React.RefObject<C | null>) => React.ReactNode,
         props?: Omit<P, keyof DialogProps>,
     ): Promise<{ didOkOrSubmit: boolean; model: M }> {
         const initialOptions: ModuleUiDialogOptions =
diff --git a/src/notifications/ContentRules.ts b/src/notifications/ContentRules.ts
index bfe3bed6c295b09f1579f0c9a6e60a0283f49c47..ee8dabd16d0f450e2c51e8f6beea92fc7546beaf 100644
--- a/src/notifications/ContentRules.ts
+++ b/src/notifications/ContentRules.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAnnotatedPushRule, IPushRules, PushRuleKind, PushRuleSet } from "matrix-js-sdk/src/matrix";
+import { type IAnnotatedPushRule, type IPushRules, PushRuleKind, type PushRuleSet } from "matrix-js-sdk/src/matrix";
 
 import { PushRuleVectorState, VectorState } from "./PushRuleVectorState";
 
diff --git a/src/notifications/NotificationUtils.ts b/src/notifications/NotificationUtils.ts
index 0504ba41f7c2bf8b0e68e82e81756c984d8be555..cce937551c24b9f6534745356860bb157854a48f 100644
--- a/src/notifications/NotificationUtils.ts
+++ b/src/notifications/NotificationUtils.ts
@@ -6,7 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { PushRuleAction, PushRuleActionName, TweakHighlight, TweakSound } from "matrix-js-sdk/src/matrix";
+import {
+    type PushRuleAction,
+    PushRuleActionName,
+    type TweakHighlight,
+    type TweakSound,
+} from "matrix-js-sdk/src/matrix";
 
 export interface PushRuleActions {
     notify: boolean;
diff --git a/src/notifications/PushRuleVectorState.ts b/src/notifications/PushRuleVectorState.ts
index eeed5ca99c2df798e0959825f0e3232bbc46dd97..465299095a69f9ee3dd18070f592d584c42bff3a 100644
--- a/src/notifications/PushRuleVectorState.ts
+++ b/src/notifications/PushRuleVectorState.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPushRule, PushRuleAction } from "matrix-js-sdk/src/matrix";
+import { type IPushRule, type PushRuleAction } from "matrix-js-sdk/src/matrix";
 
 import { StandardActions } from "./StandardActions";
 import { NotificationUtils } from "./NotificationUtils";
diff --git a/src/notifications/StandardActions.ts b/src/notifications/StandardActions.ts
index 21dad0e6555635a400770433be0ead6ef0c5ea17..b40e8b4d505b4b7fa6183e7558c610874862c4fb 100644
--- a/src/notifications/StandardActions.ts
+++ b/src/notifications/StandardActions.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { PushRuleAction } from "matrix-js-sdk/src/matrix";
+import { type PushRuleAction } from "matrix-js-sdk/src/matrix";
 
 import { NotificationUtils } from "./NotificationUtils";
 
diff --git a/src/notifications/VectorPushRulesDefinitions.ts b/src/notifications/VectorPushRulesDefinitions.ts
index 77311edceccefccc4975293bb6a59b5dc95c7df3..403efbc49b70d96dab383657aaff44261fdb0c21 100644
--- a/src/notifications/VectorPushRulesDefinitions.ts
+++ b/src/notifications/VectorPushRulesDefinitions.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAnnotatedPushRule, PushRuleAction, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IAnnotatedPushRule, type PushRuleAction, RuleId } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { _td, TranslationKey } from "../languageHandler";
+import { _td, type TranslationKey } from "../languageHandler";
 import { StandardActions } from "./StandardActions";
 import { PushRuleVectorState, VectorState } from "./PushRuleVectorState";
 import { NotificationUtils } from "./NotificationUtils";
diff --git a/src/rageshake/rageshake.ts b/src/rageshake/rageshake.ts
index 2d8ec572d5a0ad98a1a090a2fc1ee71464f5ee19..fbfedf0e4c1dddd3eea8ca89f14ccfc7fa5d47b9 100644
--- a/src/rageshake/rageshake.ts
+++ b/src/rageshake/rageshake.ts
@@ -31,7 +31,7 @@ Please see LICENSE files in the repository root for full details.
 
 // the frequency with which we flush to indexeddb
 import { logger } from "matrix-js-sdk/src/logger";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 
 import { getCircularReplacer } from "../utils/JSON";
 
@@ -135,7 +135,7 @@ export class IndexedDBLogStore {
         private indexedDB: IDBFactory,
         private logger: ConsoleLogger,
     ) {
-        this.id = "instance-" + randomString(16);
+        this.id = "instance-" + secureRandomString(16);
     }
 
     /**
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 05f8a49b43e43faf11497eaa492259ce06808321..2e04d7d9dc6c740b4dc60a024b9d22261b29513d 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019 The Matrix.org Foundation C.I.C.
 Copyright 2018 New Vector Ltd
 Copyright 2017 OpenMarket Ltd
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { Method, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { Method, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
 
 import type * as Pako from "pako";
 import { MatrixClientPeg } from "../MatrixClientPeg";
@@ -30,6 +30,24 @@ interface IOpts {
     customFields?: Record<string, string>;
 }
 
+export class RageshakeError extends Error {
+    /**
+     * This error is thrown when the rageshake server cannot process the request.
+     * @param errorcode Machine-readable error code. See https://github.com/matrix-org/rageshake/blob/main/docs/api.md
+     * @param error A human-readable error.
+     * @param statusCode The HTTP status code.
+     * @param policyURL Optional policy URL that can be presented to the user.
+     */
+    public constructor(
+        public readonly errorcode: string,
+        public readonly error: string,
+        public readonly statusCode: number,
+        public readonly policyURL?: string,
+    ) {
+        super(`The rageshake server responded with an error ${errorcode} (${statusCode}): ${error}`);
+    }
+}
+
 /**
  * Exported only for testing.
  * @internal public for test
@@ -323,6 +341,9 @@ async function collectLogs(
  * @param {function(string)} opts.progressCallback Callback to call with progress updates
  *
  * @return {Promise<string>} URL returned by the rageshake server
+ *
+ * @throws A RageshakeError when the rageshake server responds with an error. This will be `RS_UNKNOWN` if the
+ *         the server does not respond with an expected body format.
  */
 export default async function sendBugReport(bugReportEndpoint?: string, opts: IOpts = {}): Promise<string> {
     if (!bugReportEndpoint) {
@@ -426,24 +447,37 @@ export async function submitFeedback(
     }
 }
 
-function submitReport(endpoint: string, body: FormData, progressCallback: (str: string) => void): Promise<string> {
-    return new Promise<string>((resolve, reject) => {
-        const req = new XMLHttpRequest();
-        req.open("POST", endpoint);
-        req.responseType = "json";
-        req.timeout = 5 * 60 * 1000;
-        req.onreadystatechange = function (): void {
-            if (req.readyState === XMLHttpRequest.LOADING) {
-                progressCallback(_t("bug_reporting|waiting_for_server"));
-            } else if (req.readyState === XMLHttpRequest.DONE) {
-                // on done
-                if (req.status < 200 || req.status >= 400) {
-                    reject(new Error(`HTTP ${req.status}`));
-                    return;
-                }
-                resolve(req.response.report_url || "");
-            }
-        };
-        req.send(body);
+/**
+ * Submit a rageshake report to the rageshake server.
+ *
+ * @param endpoint The endpoint to call.
+ * @param body The report body.
+ * @param progressCallback A callback that will be called when the upload process has begun.
+ * @returns The URL to the public report.
+ * @throws A RageshakeError when the rageshake server responds with an error. This will be `RS_UNKNOWN` if the
+ *         the server does not respond with an expected body format.
+ */
+async function submitReport(
+    endpoint: string,
+    body: FormData,
+    progressCallback: (str: string) => void,
+): Promise<string> {
+    const req = fetch(endpoint, {
+        method: "POST",
+        body,
+        signal: AbortSignal.timeout?.(5 * 60 * 1000),
     });
+    progressCallback(_t("bug_reporting|waiting_for_server"));
+    const response = await req;
+    if (response.headers.get("Content-Type") !== "application/json") {
+        throw new RageshakeError("UNKNOWN", "Rageshake server responded with unexpected type", response.status);
+    }
+    const data = await response.json();
+    if (response.status < 200 || response.status >= 400) {
+        if ("errcode" in data) {
+            throw new RageshakeError(data.errcode, data.error, response.status, data.policy_url);
+        }
+        throw new RageshakeError("UNKNOWN", "Rageshake server responded with unexpected type", response.status);
+    }
+    return data.report_url;
 }
diff --git a/src/renderer/code-block.tsx b/src/renderer/code-block.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..78b8282489b5ebaaca4accef109b60c375bfcd06
--- /dev/null
+++ b/src/renderer/code-block.tsx
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+
+import { type RendererMap } from "./utils.tsx";
+import CodeBlock from "../components/views/messages/CodeBlock.tsx";
+
+/**
+ * Replaces `pre` elements with a CodeBlock component
+ */
+export const codeBlockRenderer: RendererMap = {
+    pre: (pre) => {
+        return <CodeBlock preNode={pre} />;
+    },
+};
diff --git a/src/renderer/index.ts b/src/renderer/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eaed7b71c3a0c1de49b269648fe19825dd853a98
--- /dev/null
+++ b/src/renderer/index.ts
@@ -0,0 +1,18 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+export { ambiguousLinkTooltipRenderer } from "./link-tooltip";
+export { keywordPillRenderer, mentionPillRenderer } from "./pill";
+export { spoilerRenderer } from "./spoiler";
+export { codeBlockRenderer } from "./code-block";
+export {
+    applyReplacerOnString,
+    replacerToRenderFunction,
+    combineRenderers,
+    type RendererMap,
+    type Replacer,
+} from "./utils";
diff --git a/src/renderer/link-tooltip.tsx b/src/renderer/link-tooltip.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1c66b6d8e7bd19b35902bbd21602b6cecd38f299
--- /dev/null
+++ b/src/renderer/link-tooltip.tsx
@@ -0,0 +1,34 @@
+/*
+Copyright 2024-2025 New Vector Ltd.
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { domToReact } from "html-react-parser";
+
+import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
+import { getSingleTextContentNode, type RendererMap } from "./utils.tsx";
+
+/**
+ * Wraps ambiguous links in a tooltip trigger that shows the full URL.
+ */
+export const ambiguousLinkTooltipRenderer: RendererMap = {
+    a: (anchor, { isHtml }) => {
+        // Ambiguous URLs are only possible in HTML content
+        if (!isHtml) return;
+
+        const href = anchor.attribs["href"];
+        if (href && href !== getSingleTextContentNode(anchor)) {
+            let tooltip = href as string;
+            try {
+                tooltip = new URL(href, window.location.href).toString();
+            } catch {
+                // Not all hrefs will be valid URLs
+            }
+            return <LinkWithTooltip tooltip={tooltip}>{domToReact([anchor])}</LinkWithTooltip>;
+        }
+    },
+};
diff --git a/src/renderer/pill.tsx b/src/renderer/pill.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9157c1181aa517f201a957b61571a434f00b4c0c
--- /dev/null
+++ b/src/renderer/pill.tsx
@@ -0,0 +1,102 @@
+/*
+Copyright 2024-2025 New Vector Ltd.
+Copyright 2019-2023 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { RuleId } from "matrix-js-sdk/src/matrix";
+import { type Element } from "html-react-parser";
+import { textContent } from "domutils";
+import reactStringReplace from "react-string-replace";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+
+import { Pill, PillType } from "../components/views/elements/Pill";
+import { parsePermalink } from "../utils/permalinks/Permalinks";
+import { type PermalinkParts } from "../utils/permalinks/PermalinkConstructor";
+import { hasParentMatching, type RendererMap, type ParentNode } from "./utils.tsx";
+
+const AT_ROOM_REGEX = PushProcessor.getPushRuleGlobRegex("@room", true, "gmi");
+
+/**
+ * A node here is an A element with a href attribute tag.
+ *
+ * It should be pillified if the permalink parser returns a result and one of the following conditions match:
+ * - Text content equals href. This is the case when sending a plain permalink inside a message.
+ * - The link is not from linkify (isHtml=true).
+ *   Composer completions already create an A tag.
+ */
+const shouldBePillified = (node: Element, href: string, parts: PermalinkParts | null, isHtml: boolean): boolean => {
+    // permalink parser didn't return any parts
+    if (!parts) return false;
+
+    const text = textContent(node);
+
+    // event permalink with custom label
+    if (parts.eventId && href !== text) return false;
+
+    return href === text || isHtml;
+};
+
+const isPreCode = (domNode: ParentNode | null): boolean =>
+    (domNode as Element)?.tagName === "PRE" || (domNode as Element)?.tagName === "CODE";
+
+/**
+ * Marks the text that activated a push-notification mention pattern.
+ */
+export const mentionPillRenderer: RendererMap = {
+    a: (anchor, { room, shouldShowPillAvatar, isHtml }) => {
+        if (!room) return;
+
+        const href = anchor.attribs["href"];
+        if (
+            href &&
+            !hasParentMatching(anchor, isPreCode) &&
+            shouldBePillified(anchor, href, parsePermalink(href), isHtml)
+        ) {
+            return <Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />;
+        }
+    },
+
+    [Node.TEXT_NODE]: (text, { room, mxEvent, shouldShowPillAvatar }) => {
+        if (!room || !mxEvent) return;
+
+        const atRoomRule = room.client.pushProcessor.getPushRuleById(
+            mxEvent.getContent()["m.mentions"] !== undefined ? RuleId.IsRoomMention : RuleId.AtRoomNotification,
+        );
+        if (atRoomRule && room.client.pushProcessor.ruleMatchesEvent(atRoomRule, mxEvent)) {
+            const parts = reactStringReplace(text.data, AT_ROOM_REGEX, (_match, i) => (
+                <Pill
+                    key={i}
+                    type={PillType.AtRoomMention}
+                    inMessage={true}
+                    room={room}
+                    shouldShowPillAvatar={shouldShowPillAvatar}
+                />
+            ));
+
+            if (parts.length <= 1) return; // no matches, skip replacing
+
+            return <>{parts}</>;
+        }
+    },
+};
+
+/**
+ * Marks the text that activated a push-notification keyword pattern.
+ */
+export const keywordPillRenderer: RendererMap = {
+    [Node.TEXT_NODE]: (text, { keywordRegexpPattern }) => {
+        if (!keywordRegexpPattern) return;
+
+        const parts = reactStringReplace(text.data, keywordRegexpPattern, (match, i) => (
+            <Pill key={i} text={match} type={PillType.Keyword} />
+        ));
+
+        if (parts.length <= 1) return; // no matches, skip replacing
+
+        return <>{parts}</>;
+    },
+};
diff --git a/src/renderer/spoiler.tsx b/src/renderer/spoiler.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ee7f45f48e70905ca5a0561e435706922e199d1f
--- /dev/null
+++ b/src/renderer/spoiler.tsx
@@ -0,0 +1,24 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { domToReact, type DOMNode } from "html-react-parser";
+
+import { type RendererMap } from "./utils.tsx";
+import Spoiler from "../components/views/elements/Spoiler.tsx";
+
+/**
+ * Replaces spans with `data-mx-spoiler` with a Spoiler component.
+ */
+export const spoilerRenderer: RendererMap = {
+    span: (span) => {
+        const reason = span.attribs["data-mx-spoiler"];
+        if (typeof reason === "string") {
+            return <Spoiler reason={reason}>{domToReact(span.children as DOMNode[])}</Spoiler>;
+        }
+    },
+};
diff --git a/src/renderer/utils.tsx b/src/renderer/utils.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0cf37b9a8c29262925b0233ff47ed67c02be8f20
--- /dev/null
+++ b/src/renderer/utils.tsx
@@ -0,0 +1,140 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { type JSX } from "react";
+import { type DOMNode, Element, type HTMLReactParserOptions, Text } from "html-react-parser";
+import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
+import { type Opts } from "linkifyjs";
+
+/**
+ * The type of a parent node of an element, normally exported by domhandler but that is not a direct dependency of ours
+ */
+export type ParentNode = NonNullable<Element["parentNode"]>;
+
+/**
+ * Returns the text content of a node if it is the only child and that child is a text node
+ * @param node - the node to check
+ */
+export const getSingleTextContentNode = (node: Element): string | null => {
+    if (node.childNodes.length === 1 && node.childNodes[0].type === "text") {
+        return node.childNodes[0].data;
+    }
+    return null;
+};
+
+/**
+ * Returns true if the node has a parent that matches the given matcher
+ * @param node - the node to check
+ * @param matcher - a function that returns true if the node matches
+ */
+export const hasParentMatching = (node: Element, matcher: (node: ParentNode | null) => boolean): boolean => {
+    let parent = node.parentNode;
+    while (parent) {
+        if (matcher(parent)) return true;
+        parent = parent.parentNode;
+    }
+    return false;
+};
+
+/**
+ * A replacer function that can be used with html-react-parser
+ */
+export type Replacer = HTMLReactParserOptions["replace"];
+
+/**
+ * Passes through any non-string inputs verbatim, as such they should only be used for emoji bodies
+ */
+export function applyReplacerOnString(
+    input: string | JSX.Element[],
+    replacer: Replacer,
+): JSX.Element | JSX.Element[] | string {
+    if (!replacer) return input;
+
+    const arr = Array.isArray(input) ? input : [input];
+    return arr.map((input, index): JSX.Element => {
+        if (typeof input === "string") {
+            return (
+                <React.Fragment key={index}>{(replacer(new Text(input), 0) as JSX.Element) || input}</React.Fragment>
+            );
+        }
+        return input;
+    });
+}
+
+/**
+ * Converts a Replacer function to a render function for linkify-react
+ * So that we can use the same replacer functions for both
+ * @param replacer The replacer function to convert
+ */
+export function replacerToRenderFunction(replacer: Replacer): Opts["render"] {
+    if (!replacer) return;
+    return ({ tagName, attributes, content }) => {
+        const domNode = new Element(tagName, attributes, [new Text(content)], "tag" as Element["type"]);
+        const result = replacer(domNode, 0);
+        if (result) return result;
+
+        // This is cribbed from the default render function in linkify-react
+        if (attributes.class) {
+            attributes.className = attributes.class;
+            delete attributes.class;
+        }
+        return React.createElement(tagName, attributes, content);
+    };
+}
+
+interface Parameters {
+    isHtml: boolean;
+    // Required for keywordPillRenderer
+    keywordRegexpPattern?: RegExp;
+    // Required for mentionPillRenderer
+    mxEvent?: MatrixEvent;
+    room?: Room;
+    shouldShowPillAvatar?: boolean;
+}
+
+type SpecialisedReplacer<T extends DOMNode> = (
+    node: T,
+    parameters: Parameters,
+    index: number,
+) => JSX.Element | string | void;
+
+/**
+ * A map of replacer functions for different types of nodes/tags.
+ * When a function returns a JSX element, the element will be rendered in place of the node.
+ */
+export type RendererMap = Partial<
+    {
+        [tagName in keyof HTMLElementTagNameMap]: SpecialisedReplacer<Element>;
+    } & {
+        [Node.TEXT_NODE]: SpecialisedReplacer<Text>;
+    }
+>;
+
+type PreparedRenderer = (parameters: Parameters) => Replacer;
+
+/**
+ * Combines multiple renderers into a single Replacer function
+ * @param renderers - the list of renderers to combine
+ */
+export const combineRenderers =
+    (...renderers: RendererMap[]): PreparedRenderer =>
+    (parameters) =>
+    (node, index) => {
+        if (node.type === "text") {
+            for (const replacer of renderers) {
+                const result = replacer[Node.TEXT_NODE]?.(node, parameters, index);
+                if (result) return result;
+            }
+        }
+        if (node instanceof Element) {
+            const tagName = node.tagName.toLowerCase() as keyof HTMLElementTagNameMap;
+            for (const replacer of renderers) {
+                const result = replacer[tagName]?.(node, parameters, index);
+                if (result) return result;
+            }
+        }
+    };
diff --git a/src/resizer/distributors/collapse.ts b/src/resizer/distributors/collapse.ts
index a4693e8e14f8781232723671de1454758ce5c39a..bd0e874cc42dd36e79b457fe817b901630ad7dbf 100644
--- a/src/resizer/distributors/collapse.ts
+++ b/src/resizer/distributors/collapse.ts
@@ -8,8 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import { BaseDistributor } from "./fixed";
 import ResizeItem from "../item";
-import Resizer, { IConfig } from "../resizer";
-import Sizer from "../sizer";
+import { type IConfig } from "../resizer";
+import type Resizer from "../resizer";
+import type Sizer from "../sizer";
 
 export interface ICollapseConfig extends IConfig {
     toggleSize: number;
diff --git a/src/resizer/distributors/fixed.ts b/src/resizer/distributors/fixed.ts
index 609df1deae16004b7d8e6390bf8c9f514b696d5a..1820492ab76665acfd4042108f33d213fec7c148 100644
--- a/src/resizer/distributors/fixed.ts
+++ b/src/resizer/distributors/fixed.ts
@@ -8,7 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import ResizeItem from "../item";
 import Sizer from "../sizer";
-import Resizer, { IConfig } from "../resizer";
+import { type IConfig } from "../resizer";
+import type Resizer from "../resizer";
 
 export abstract class BaseDistributor<C extends IConfig, I extends ResizeItem<C> = ResizeItem<C>> {
     public static createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer {
diff --git a/src/resizer/distributors/percentage.ts b/src/resizer/distributors/percentage.ts
index f8a00107870736bb910525b1a3c1fd940d02a4df..df385868f8ae81145316a5f89aec193114e06994 100644
--- a/src/resizer/distributors/percentage.ts
+++ b/src/resizer/distributors/percentage.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import Sizer from "../sizer";
 import FixedDistributor from "./fixed";
-import { IConfig } from "../resizer";
+import { type IConfig } from "../resizer";
 
 class PercentageSizer extends Sizer {
     public start(item: HTMLElement): void {
diff --git a/src/resizer/item.ts b/src/resizer/item.ts
index 9eeea86fd6fb45e46e1b01a5992f882b678f9006..ed6173069330b29d5dde5931435c4a4d70e138d6 100644
--- a/src/resizer/item.ts
+++ b/src/resizer/item.ts
@@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import Resizer, { IConfig } from "./resizer";
-import Sizer from "./sizer";
+import { type IConfig } from "./resizer";
+import type Resizer from "./resizer";
+import type Sizer from "./sizer";
 
 export default class ResizeItem<C extends IConfig> {
     public readonly domNode: HTMLElement;
diff --git a/src/resizer/resizer.ts b/src/resizer/resizer.ts
index 826b017d56968b65cd212385723e6f8af73300c7..8a814792adfd612b4adf98d327c52ff4e9191ca8 100644
--- a/src/resizer/resizer.ts
+++ b/src/resizer/resizer.ts
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import { throttle } from "lodash";
 
-import FixedDistributor from "./distributors/fixed";
-import ResizeItem from "./item";
-import Sizer from "./sizer";
+import type FixedDistributor from "./distributors/fixed";
+import type ResizeItem from "./item";
+import type Sizer from "./sizer";
 
 interface IClassNames {
     // class on resize-handle
diff --git a/src/sendTimePerformanceMetrics.ts b/src/sendTimePerformanceMetrics.ts
index c57ed3771bea745e52effea2e7f4ed779ee9738f..45298b2cd2942e74b0c4701390156918231b6d31 100644
--- a/src/sendTimePerformanceMetrics.ts
+++ b/src/sendTimePerformanceMetrics.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IContent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IContent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 /**
  * Decorates the given event content object with the "send start time". The
diff --git a/src/sentry.ts b/src/sentry.ts
index 90ce39d671e28c68ad38dc777a0e53d60cdf2e79..92e8403963f9b143b2e97578049ff539cdd43dd5 100644
--- a/src/sentry.ts
+++ b/src/sentry.ts
@@ -7,12 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import * as Sentry from "@sentry/browser";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "./SdkConfig";
 import { MatrixClientPeg } from "./MatrixClientPeg";
 import SettingsStore from "./settings/SettingsStore";
-import { IConfigOptions } from "./IConfigOptions";
+import { type IConfigOptions } from "./IConfigOptions";
 
 /* eslint-disable camelcase */
 
@@ -213,7 +213,6 @@ export async function initSentry(sentryConfig: IConfigOptions["sentry"]): Promis
         release: process.env.VERSION,
         environment: sentryConfig.environment,
         defaultIntegrations: false,
-        autoSessionTracking: false,
         integrations,
         // Set to 1.0 which is reasonable if we're only submitting Rageshakes; will need to be set < 1.0
         // if we collect more frequently.
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 48a271a9a962b6ba59328505dbcf2ef4eb04ae0d..36030713ecc0ed06af73118fe2e5c8c3335109cc 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2018-2024 The Matrix.org Foundation C.I.C.
 Copyright 2017 Travis Ralston
 
@@ -7,10 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { UNSTABLE_MSC4133_EXTENDED_PROFILES } from "matrix-js-sdk/src/matrix";
 
-import { _t, _td, TranslationKey } from "../languageHandler";
+import { type MediaPreviewConfig } from "../@types/media_preview.ts";
+import { _t, _td, type TranslationKey } from "../languageHandler";
 import DeviceIsolationModeController from "./controllers/DeviceIsolationModeController.ts";
 import {
     NotificationBodyEnabledController,
@@ -21,7 +22,7 @@ import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
 import FontSizeController from "./controllers/FontSizeController";
 import SystemFontController from "./controllers/SystemFontController";
 import { SettingLevel } from "./SettingLevel";
-import SettingController from "./controllers/SettingController";
+import type SettingController from "./controllers/SettingController";
 import { IS_MAC } from "../Keyboard";
 import UIFeatureController from "./controllers/UIFeatureController";
 import { UIFeature } from "./UIFeature";
@@ -35,15 +36,17 @@ import SlidingSyncController from "./controllers/SlidingSyncController";
 import { FontWatcher } from "./watchers/FontWatcher";
 import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController";
 import { WatchManager } from "./WatchManager";
-import { CustomTheme } from "../theme";
+import { type CustomTheme } from "../theme";
 import AnalyticsController from "./controllers/AnalyticsController";
 import FallbackIceServerController from "./controllers/FallbackIceServerController";
-import { IRightPanelForRoomStored } from "../stores/right-panel/RightPanelStoreIPanelState.ts";
-import { ILayoutSettings } from "../stores/widgets/WidgetLayoutStore.ts";
-import { ReleaseAnnouncementData } from "../stores/ReleaseAnnouncementStore.ts";
-import { Json, JsonValue } from "../@types/json.ts";
-import { RecentEmojiData } from "../emojipicker/recent.ts";
-import { Assignable } from "../@types/common.ts";
+import { type IRightPanelForRoomStored } from "../stores/right-panel/RightPanelStoreIPanelState.ts";
+import { type ILayoutSettings } from "../stores/widgets/WidgetLayoutStore.ts";
+import { type ReleaseAnnouncementData } from "../stores/ReleaseAnnouncementStore.ts";
+import { type Json, type JsonValue } from "../@types/json.ts";
+import { type RecentEmojiData } from "../emojipicker/recent.ts";
+import { type Assignable } from "../@types/common.ts";
+import { SortingAlgorithm } from "../stores/room-list-v3/skip-list/sorters/index.ts";
+import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts";
 
 export const defaultWatchManager = new WatchManager();
 
@@ -197,7 +200,8 @@ export interface Settings {
     "feature_html_topic": IFeature;
     "feature_bridge_state": IFeature;
     "feature_jump_to_date": IFeature;
-    "feature_sliding_sync": IFeature;
+    "feature_sliding_sync": IBaseSetting<boolean>;
+    "feature_simplified_sliding_sync": IFeature;
     "feature_element_call_video_rooms": IFeature;
     "feature_group_calls": IFeature;
     "feature_disable_call_per_sender_encryption": IFeature;
@@ -205,10 +209,10 @@ export interface Settings {
     "feature_location_share_live": IFeature;
     "feature_dynamic_room_predecessors": IFeature;
     "feature_render_reaction_images": IFeature;
+    "feature_new_room_list": IFeature;
     "feature_ask_to_join": IFeature;
     "feature_notifications": IFeature;
     // These are in the feature namespace but aren't actually features
-    "feature_sliding_sync_proxy_url": IBaseSetting<string>;
     "feature_hidebold": IBaseSetting<boolean>;
 
     "useOnlyCurrentProfiles": IBaseSetting<boolean>;
@@ -271,6 +275,7 @@ export interface Settings {
     "language": IBaseSetting<string>;
     "breadcrumb_rooms": IBaseSetting<string[]>;
     "recent_emoji": IBaseSetting<RecentEmojiData>;
+    "showMediaEventIds": IBaseSetting<{ [eventId: string]: boolean }>;
     "SpotlightSearch.recentSearches": IBaseSetting<string[]>;
     "SpotlightSearch.showNsfwPublicRooms": IBaseSetting<boolean>;
     "room_directory_servers": IBaseSetting<string[]>;
@@ -309,7 +314,8 @@ export interface Settings {
     "showHiddenEventsInTimeline": IBaseSetting<boolean>;
     "lowBandwidth": IBaseSetting<boolean>;
     "fallbackICEServerAllowed": IBaseSetting<boolean | null>;
-    "showImages": IBaseSetting<boolean>;
+    "RoomList.preferredSorting": IBaseSetting<SortingAlgorithm>;
+    "RoomList.showMessagePreview": IBaseSetting<boolean>;
     "RightPanel.phasesGlobal": IBaseSetting<IRightPanelForRoomStored | null>;
     "RightPanel.phases": IBaseSetting<IRightPanelForRoomStored | null>;
     "enableEventIndexing": IBaseSetting<boolean>;
@@ -343,11 +349,14 @@ export interface Settings {
     "Electron.alwaysShowMenuBar": IBaseSetting<boolean>;
     "Electron.showTrayIcon": IBaseSetting<boolean>;
     "Electron.enableHardwareAcceleration": IBaseSetting<boolean>;
+    "mediaPreviewConfig": IBaseSetting<MediaPreviewConfig>;
+    "Developer.elementCallUrl": IBaseSetting<string>;
 }
 
 export type SettingKey = keyof Settings;
 export type FeatureSettingKey = Assignable<Settings, IFeature>;
 export type BooleanSettingKey = Assignable<Settings, IBaseSetting<boolean>> | FeatureSettingKey;
+export type StringSettingKey = Assignable<Settings, IBaseSetting<string>>;
 
 export const SETTINGS: Settings = {
     "feature_video_rooms": {
@@ -419,6 +428,11 @@ export const SETTINGS: Settings = {
         supportedLevelsAreOrdered: true,
         default: false,
     },
+    "mediaPreviewConfig": {
+        controller: new MediaPreviewConfigController(),
+        supportedLevels: LEVELS_ROOM_SETTINGS,
+        default: MediaPreviewConfigController.default,
+    },
     "feature_report_to_moderators": {
         isFeature: true,
         labsGroup: LabGroup.Moderation,
@@ -533,7 +547,14 @@ export const SETTINGS: Settings = {
             true,
         ),
     },
+    // legacy sliding sync flag: no longer works, will error for anyone who's still using it
     "feature_sliding_sync": {
+        supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
+        supportedLevelsAreOrdered: true,
+        shouldWarn: true,
+        default: false,
+    },
+    "feature_simplified_sliding_sync": {
         isFeature: true,
         labsGroup: LabGroup.Developer,
         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
@@ -544,11 +565,6 @@ export const SETTINGS: Settings = {
         default: false,
         controller: new SlidingSyncController(),
     },
-    "feature_sliding_sync_proxy_url": {
-        // This is not a distinct feature, it is a legacy setting for feature_sliding_sync above
-        supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
-        default: "",
-    },
     "feature_element_call_video_rooms": {
         isFeature: true,
         labsGroup: LabGroup.VoiceAndVideo,
@@ -623,6 +639,15 @@ export const SETTINGS: Settings = {
         supportedLevelsAreOrdered: true,
         default: false,
     },
+    "feature_new_room_list": {
+        supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
+        labsGroup: LabGroup.Ui,
+        displayName: _td("labs|new_room_list"),
+        description: _td("labs|under_active_development"),
+        isFeature: true,
+        default: false,
+        controller: new ReloadOnChangeController(),
+    },
     /**
      * With the transition to Compound we are moving to a base font size
      * of 16px. We're taking the opportunity to move away from the `baseFontSize`
@@ -956,6 +981,11 @@ export const SETTINGS: Settings = {
         supportedLevels: [SettingLevel.ACCOUNT],
         default: [], // list of room IDs, most recent first
     },
+    "showMediaEventIds": {
+        // not really a setting
+        supportedLevels: [SettingLevel.DEVICE],
+        default: {}, // List of events => is visible
+    },
     "SpotlightSearch.showNsfwPublicRooms": {
         supportedLevels: LEVELS_ACCOUNT_SETTINGS,
         displayName: _td("settings|show_nsfw_content"),
@@ -1099,10 +1129,13 @@ export const SETTINGS: Settings = {
         default: null,
         controller: new FallbackIceServerController(),
     },
-    "showImages": {
-        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
-        displayName: _td("settings|image_thumbnails"),
-        default: true,
+    "RoomList.preferredSorting": {
+        supportedLevels: [SettingLevel.DEVICE],
+        default: SortingAlgorithm.Recency,
+    },
+    "RoomList.showMessagePreview": {
+        supportedLevels: [SettingLevel.DEVICE],
+        default: false,
     },
     "RightPanel.phasesGlobal": {
         supportedLevels: [SettingLevel.DEVICE],
@@ -1349,4 +1382,9 @@ export const SETTINGS: Settings = {
         displayName: _td("settings|preferences|enable_hardware_acceleration"),
         default: true,
     },
+    "Developer.elementCallUrl": {
+        supportedLevels: [SettingLevel.DEVICE],
+        displayName: _td("devtools|settings|elementCallUrl"),
+        default: "",
+    },
 };
diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts
index f0b74de15ae3467c60e1281c6f249037739a15b3..aaa836b6fc2d1a30c8082c4cb4d9f15dd69a90e2 100644
--- a/src/settings/SettingsStore.ts
+++ b/src/settings/SettingsStore.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
 Copyright 2017 Travis Ralston
 
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { ReactNode } from "react";
-import { ClientEvent, SyncState } from "matrix-js-sdk/src/matrix";
+import { type ReactNode } from "react";
+import { ClientEvent } from "matrix-js-sdk/src/matrix";
 
 import DeviceSettingsHandler from "./handlers/DeviceSettingsHandler";
 import RoomDeviceSettingsHandler from "./handlers/RoomDeviceSettingsHandler";
@@ -20,16 +20,25 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
 import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
 import { _t } from "../languageHandler";
 import dis from "../dispatcher/dispatcher";
-import { IFeature, ISetting, LabGroup, SETTINGS, defaultWatchManager, SettingKey, Settings } from "./Settings";
+import {
+    type IFeature,
+    type ISetting,
+    type LabGroup,
+    SETTINGS,
+    defaultWatchManager,
+    type SettingKey,
+    type Settings,
+} from "./Settings";
 import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
-import { CallbackFn as WatchCallbackFn } from "./WatchManager";
+import { type CallbackFn as WatchCallbackFn } from "./WatchManager";
 import { SettingLevel } from "./SettingLevel";
-import SettingsHandler from "./handlers/SettingsHandler";
-import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
+import type SettingsHandler from "./handlers/SettingsHandler";
+import { type SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
 import { Action } from "../dispatcher/actions";
 import PlatformSettingsHandler from "./handlers/PlatformSettingsHandler";
 import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
 import { MatrixClientPeg } from "../MatrixClientPeg";
+import { MediaPreviewValue } from "../@types/media_preview";
 
 // Convert the settings to easier to manage objects for the handlers
 const defaultSettings: Record<string, any> = {};
@@ -658,35 +667,72 @@ export default class SettingsStore {
 
         const client = MatrixClientPeg.safeGet();
 
-        const doMigration = async (): Promise<void> => {
-            logger.info("Performing one-time settings migration of URL previews in E2EE rooms");
+        while (!client.isInitialSyncComplete()) {
+            await new Promise((r) => client.once(ClientEvent.Sync, r));
+        }
 
-            const roomAccounthandler = LEVEL_HANDLERS[SettingLevel.ROOM_ACCOUNT];
+        logger.info("Performing one-time settings migration of URL previews in E2EE rooms");
 
-            for (const room of client.getRooms()) {
-                // We need to use the handler directly because this setting is no longer supported
-                // at this level at all
-                const val = roomAccounthandler.getValue("urlPreviewsEnabled_e2ee", room.roomId);
+        const roomAccounthandler = LEVEL_HANDLERS[SettingLevel.ROOM_ACCOUNT];
 
-                if (val !== undefined) {
-                    await SettingsStore.setValue("urlPreviewsEnabled_e2ee", room.roomId, SettingLevel.ROOM_DEVICE, val);
-                }
+        for (const room of client.getRooms()) {
+            // We need to use the handler directly because this setting is no longer supported
+            // at this level at all
+            const val = roomAccounthandler.getValue("urlPreviewsEnabled_e2ee", room.roomId);
+
+            if (val !== undefined) {
+                await SettingsStore.setValue("urlPreviewsEnabled_e2ee", room.roomId, SettingLevel.ROOM_DEVICE, val);
             }
+        }
 
-            localStorage.setItem(MIGRATION_DONE_FLAG, "true");
-        };
+        localStorage.setItem(MIGRATION_DONE_FLAG, "true");
+    }
 
-        const onSync = (state: SyncState): void => {
-            if (state === SyncState.Prepared) {
-                client.removeListener(ClientEvent.Sync, onSync);
+    /**
+     * Migrate the setting for visible images to a setting.
+     */
+    private static migrateShowImagesToSettings(): void {
+        const MIGRATION_DONE_FLAG = "mx_show_images_migration_done";
+        if (localStorage.getItem(MIGRATION_DONE_FLAG)) return;
 
-                doMigration().catch((e) => {
-                    logger.error("Failed to migrate URL previews in E2EE rooms:", e);
-                });
-            }
-        };
+        logger.info("Performing one-time settings migration of shown images to settings store");
+        const newValue = Object.fromEntries(
+            Object.keys(localStorage)
+                .filter((k) => k.startsWith("mx_ShowImage_"))
+                .map((k) => [k.slice("mx_ShowImage_".length), true]),
+        );
+        this.setValue("showMediaEventIds", null, SettingLevel.DEVICE, newValue);
 
-        client.on(ClientEvent.Sync, onSync);
+        localStorage.setItem(MIGRATION_DONE_FLAG, "true");
+    }
+
+    /**
+     * Migrate the setting for visible images to a setting.
+     *
+     * @param isFreshLogin True if the user has just logged in, false if a previous session is being restored.
+     */
+    private static async migrateMediaControlsToSetting(isFreshLogin: boolean): Promise<void> {
+        if (isFreshLogin) return;
+        const client = MatrixClientPeg.safeGet();
+
+        while (!client.isInitialSyncComplete()) {
+            await new Promise((r) => client.once(ClientEvent.Sync, r));
+        }
+        // Never migrate if the config already exists.
+        if (client.getAccountData("io.element.msc4278.media_preview_config")) {
+            return;
+        }
+        logger.info("Performing one-time settings migration of show images and invite avatars to account data");
+        const handler = LEVEL_HANDLERS[SettingLevel.ACCOUNT];
+        const showImages = handler.getValue("showImages", null);
+        const showAvatarsOnInvites = handler.getValue("showAvatarsOnInvites", null);
+
+        if (typeof showImages === "boolean" || typeof showAvatarsOnInvites === "boolean") {
+            this.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, {
+                invite_avatars: showAvatarsOnInvites === false ? MediaPreviewValue.Off : MediaPreviewValue.On,
+                media_previews: showImages === false ? MediaPreviewValue.Off : MediaPreviewValue.On,
+            });
+        } // else, we don't set anything and use the server value
     }
 
     /**
@@ -698,8 +744,23 @@ export default class SettingsStore {
         // (so around October 2024).
         // The consequences of missing the migration are only that URL previews will
         // be disabled in E2EE rooms.
-        SettingsStore.migrateURLPreviewsE2EE(isFreshLogin);
+        SettingsStore.migrateURLPreviewsE2EE(isFreshLogin).catch((e) => {
+            logger.error("Failed to migrate URL previews in E2EE rooms:", e);
+        });
 
+        // This can be removed once enough users have run a version of Element with
+        // this migration.
+        // The consequences of missing the migration are that previously shown images
+        // will now be hidden again, so this fails safely.
+        SettingsStore.migrateShowImagesToSettings();
+
+        // This can be removed once enough users have run a version of Element with
+        // this migration.
+        // The consequences of missing the migration are that the previously set
+        // media controls for this user will be missing
+        SettingsStore.migrateMediaControlsToSetting(isFreshLogin).catch((e) => {
+            logger.error("Failed to migrate media config settings", e);
+        });
         // Dev notes: to add your migration, just add a new `migrateMyFeature` function, call it, and
         // add a comment to note when it can be removed.
         return;
diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts
index fd38ae3637f9c1036454cd49f584f1f6665ab252..2a0a523e0d111ccb5f8cb0c09d8ae0e8ea7ffb13 100644
--- a/src/settings/WatchManager.ts
+++ b/src/settings/WatchManager.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SettingLevel } from "./SettingLevel";
+import { type SettingLevel } from "./SettingLevel";
 
 export type CallbackFn = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any) => void;
 
diff --git a/src/settings/controllers/AnalyticsController.ts b/src/settings/controllers/AnalyticsController.ts
index f2b432ef5b54523d2b8872c5d1986cfccc154d2c..9b2bd6b289d9f53dbd3119599acf5f48a58f9eda 100644
--- a/src/settings/controllers/AnalyticsController.ts
+++ b/src/settings/controllers/AnalyticsController.ts
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import SettingController from "./SettingController";
-import { SettingLevel } from "../SettingLevel";
-import PosthogTrackers, { InteractionName } from "../../PosthogTrackers";
+import { type SettingLevel } from "../SettingLevel";
+import PosthogTrackers, { type InteractionName } from "../../PosthogTrackers";
 
 /**
  * Controller that sends events to analytics when a setting is changed.
diff --git a/src/settings/controllers/DeviceIsolationModeController.ts b/src/settings/controllers/DeviceIsolationModeController.ts
index bbdca789ceedc71e174e0a4492b1c0a1bad58aa7..0b619c2eac52f699ca1ef57e59e86fa1ec8bc318 100644
--- a/src/settings/controllers/DeviceIsolationModeController.ts
+++ b/src/settings/controllers/DeviceIsolationModeController.ts
@@ -6,11 +6,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { AllDevicesIsolationMode, OnlySignedDevicesIsolationMode } from "matrix-js-sdk/src/crypto-api";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SettingController from "./SettingController";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 /**
  * A controller for the "exclude_insecure_devices" setting, which will
diff --git a/src/settings/controllers/FallbackIceServerController.ts b/src/settings/controllers/FallbackIceServerController.ts
index e433b64ba3d85f48d675288114056f1a9daeece0..fa87a00bd3d0239d774abc68057ff11d997bce69 100644
--- a/src/settings/controllers/FallbackIceServerController.ts
+++ b/src/settings/controllers/FallbackIceServerController.ts
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type IClientWellKnown, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 import SettingsStore from "../SettingsStore.ts";
 import MatrixClientBackedController from "./MatrixClientBackedController.ts";
 
diff --git a/src/settings/controllers/FontSizeController.ts b/src/settings/controllers/FontSizeController.ts
index 762e18011e7043c5c1f14559a26a04c494955d4f..140369c2b8b8a4fbc3b01281d2e5b2486722e6d9 100644
--- a/src/settings/controllers/FontSizeController.ts
+++ b/src/settings/controllers/FontSizeController.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import SettingController from "./SettingController";
 import dis from "../../dispatcher/dispatcher";
-import { UpdateFontSizeDeltaPayload } from "../../dispatcher/payloads/UpdateFontSizeDeltaPayload";
+import { type UpdateFontSizeDeltaPayload } from "../../dispatcher/payloads/UpdateFontSizeDeltaPayload";
 import { Action } from "../../dispatcher/actions";
 import { SettingLevel } from "../SettingLevel";
 
diff --git a/src/settings/controllers/IncompatibleController.ts b/src/settings/controllers/IncompatibleController.ts
index 37100271f6b7c92ae48150a00ffd3a566963c688..81c8ca74ac0f1a9494a78f1a6eacb5a73c4f97b4 100644
--- a/src/settings/controllers/IncompatibleController.ts
+++ b/src/settings/controllers/IncompatibleController.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import SettingController from "./SettingController";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 import SettingsStore from "../SettingsStore";
-import { BooleanSettingKey } from "../Settings.tsx";
+import { type BooleanSettingKey } from "../Settings.tsx";
 
 /**
  * Enforces that a boolean setting cannot be enabled if the incompatible setting
diff --git a/src/settings/controllers/MatrixClientBackedController.ts b/src/settings/controllers/MatrixClientBackedController.ts
index ab0ca82093a41be954fcab4c9621ffe3afadc7a1..b305ad5fa23024a49af171d1695f89f6c53b8354 100644
--- a/src/settings/controllers/MatrixClientBackedController.ts
+++ b/src/settings/controllers/MatrixClientBackedController.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SettingController from "./SettingController";
 
@@ -26,7 +26,7 @@ export default abstract class MatrixClientBackedController extends SettingContro
         MatrixClientBackedController._matrixClient = client;
 
         for (const instance of MatrixClientBackedController.instances) {
-            instance.initMatrixClient(client, oldClient);
+            instance.initMatrixClient?.(client, oldClient);
         }
     }
 
@@ -40,5 +40,5 @@ export default abstract class MatrixClientBackedController extends SettingContro
         return MatrixClientBackedController._matrixClient;
     }
 
-    protected abstract initMatrixClient(newClient: MatrixClient, oldClient?: MatrixClient): void;
+    protected initMatrixClient?(newClient: MatrixClient, oldClient?: MatrixClient): void;
 }
diff --git a/src/settings/controllers/MediaPreviewConfigController.ts b/src/settings/controllers/MediaPreviewConfigController.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb8b9b34aab01198bc4212b57140ded08b33d488
--- /dev/null
+++ b/src/settings/controllers/MediaPreviewConfigController.ts
@@ -0,0 +1,100 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type IContent } from "matrix-js-sdk/src/matrix";
+import { type AccountDataEvents } from "matrix-js-sdk/src/types";
+
+import {
+    MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+    type MediaPreviewConfig,
+    MediaPreviewValue,
+} from "../../@types/media_preview.ts";
+import { type SettingLevel } from "../SettingLevel.ts";
+import MatrixClientBackedController from "./MatrixClientBackedController.ts";
+
+/**
+ * Handles media preview settings provided by MSC4278.
+ * This uses both account-level and room-level account data.
+ */
+export default class MediaPreviewConfigController extends MatrixClientBackedController {
+    public static readonly default: AccountDataEvents[typeof MEDIA_PREVIEW_ACCOUNT_DATA_TYPE] = {
+        media_previews: MediaPreviewValue.On,
+        invite_avatars: MediaPreviewValue.On,
+    };
+
+    private static getValidSettingData(content: IContent): Partial<MediaPreviewConfig> {
+        const mediaPreviews: MediaPreviewConfig["media_previews"] = content.media_previews;
+        const inviteAvatars: MediaPreviewConfig["invite_avatars"] = content.invite_avatars;
+        const validMediaPreviews = Object.values(MediaPreviewValue);
+        const validInviteAvatars = [MediaPreviewValue.Off, MediaPreviewValue.On];
+        return {
+            invite_avatars: validInviteAvatars.includes(inviteAvatars) ? inviteAvatars : undefined,
+            media_previews: validMediaPreviews.includes(mediaPreviews) ? mediaPreviews : undefined,
+        };
+    }
+
+    public constructor() {
+        super();
+    }
+
+    private getValue = (roomId?: string): MediaPreviewConfig => {
+        const source = roomId ? this.client?.getRoom(roomId) : this.client;
+        const accountData =
+            source?.getAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE)?.getContent<MediaPreviewConfig>() ?? {};
+
+        const calculatedConfig = MediaPreviewConfigController.getValidSettingData(accountData);
+
+        // Save an account data fetch if we have all the values.
+        if (calculatedConfig.invite_avatars && calculatedConfig.media_previews) {
+            return calculatedConfig as MediaPreviewConfig;
+        }
+
+        // We're missing some keys.
+        if (roomId) {
+            const globalConfig = this.getValue();
+            return {
+                invite_avatars:
+                    calculatedConfig.invite_avatars ??
+                    globalConfig.invite_avatars ??
+                    MediaPreviewConfigController.default.invite_avatars,
+                media_previews:
+                    calculatedConfig.media_previews ??
+                    globalConfig.media_previews ??
+                    MediaPreviewConfigController.default.media_previews,
+            };
+        }
+        return {
+            invite_avatars: calculatedConfig.invite_avatars ?? MediaPreviewConfigController.default.invite_avatars,
+            media_previews: calculatedConfig.media_previews ?? MediaPreviewConfigController.default.media_previews,
+        };
+    };
+
+    public getValueOverride(_level: SettingLevel, roomId: string | null): MediaPreviewConfig {
+        return this.getValue(roomId ?? undefined);
+    }
+
+    public get settingDisabled(): false {
+        // No homeserver support is required for this MSC.
+        return false;
+    }
+
+    public async beforeChange(
+        _level: SettingLevel,
+        roomId: string | null,
+        newValue: MediaPreviewConfig,
+    ): Promise<boolean> {
+        if (!this.client) {
+            return false;
+        }
+        if (roomId) {
+            await this.client.setRoomAccountData(roomId, MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, newValue);
+            return true;
+        }
+        await this.client.setAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, newValue);
+        return true;
+    }
+}
diff --git a/src/settings/controllers/NotificationControllers.ts b/src/settings/controllers/NotificationControllers.ts
index effb7aee59ab8a862bf9c7d9bf7d28c4b8da05b4..8ec17977b52e0ab5b6ef62222becd65684455dc4 100644
--- a/src/settings/controllers/NotificationControllers.ts
+++ b/src/settings/controllers/NotificationControllers.ts
@@ -8,20 +8,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-// XXX: This feels wrong.
-import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
 import { PushRuleActionName } from "matrix-js-sdk/src/matrix";
 
 import SettingController from "./SettingController";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 // .m.rule.master being enabled means all events match that push rule
 // default action on this rule is dont_notify, but it could be something else
 export function isPushNotifyDisabled(): boolean {
     // Return the value of the master push rule as a default
-    const processor = new PushProcessor(MatrixClientPeg.safeGet());
-    const masterRule = processor.getPushRuleById(".m.rule.master");
+    const masterRule = MatrixClientPeg.safeGet().pushProcessor.getPushRuleById(".m.rule.master");
 
     if (!masterRule) {
         logger.warn("No master push rule! Notifications are disabled for this user.");
diff --git a/src/settings/controllers/ReducedMotionController.ts b/src/settings/controllers/ReducedMotionController.ts
index 38d2477fc3e5fad2a1d54bfc5740db4ca1607ba3..53db05ac4c914836452ee9a3d2484a4e39fd1e94 100644
--- a/src/settings/controllers/ReducedMotionController.ts
+++ b/src/settings/controllers/ReducedMotionController.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import SettingController from "./SettingController";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 /**
  * For animation-like settings, this controller checks whether the user has
diff --git a/src/settings/controllers/ReloadOnChangeController.ts b/src/settings/controllers/ReloadOnChangeController.ts
index ace6d108d2a5f0b44b5544265c78a29554907e48..96093c907b6a063594ddab15b358d116843b0efa 100644
--- a/src/settings/controllers/ReloadOnChangeController.ts
+++ b/src/settings/controllers/ReloadOnChangeController.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import SettingController from "./SettingController";
 import PlatformPeg from "../../PlatformPeg";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 export default class ReloadOnChangeController extends SettingController {
     public onChange(level: SettingLevel, roomId: string, newValue: any): void {
diff --git a/src/settings/controllers/ServerSupportUnstableFeatureController.ts b/src/settings/controllers/ServerSupportUnstableFeatureController.ts
index 9da9e3befca5dafd8aaafcc4cfdd2760ae98a076..1b6bf9cfff148c1180598bf24672cd6e5ea2834c 100644
--- a/src/settings/controllers/ServerSupportUnstableFeatureController.ts
+++ b/src/settings/controllers/ServerSupportUnstableFeatureController.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 import MatrixClientBackedController from "./MatrixClientBackedController";
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
 import SettingsStore from "../SettingsStore";
-import { SettingKey } from "../Settings.tsx";
+import { type SettingKey } from "../Settings.tsx";
 
 /**
  * Disables a given setting if the server unstable feature it requires is not supported
diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts
index 821b3d203217b945ee29ecbe0d2e1ab29c011b4f..4f8d804adbbfc68a619c7cfb68485309ca55da99 100644
--- a/src/settings/controllers/SettingController.ts
+++ b/src/settings/controllers/SettingController.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 /**
  * Represents a controller for individual settings to alter the reading behaviour
diff --git a/src/settings/controllers/SlidingSyncController.ts b/src/settings/controllers/SlidingSyncController.ts
index 11ae1a2ba02c35538a1dc80bd6d62aff4bb53a6b..44de3ba8eee89ef397ea9bda9a77975e9a773c00 100644
--- a/src/settings/controllers/SlidingSyncController.ts
+++ b/src/settings/controllers/SlidingSyncController.ts
@@ -11,20 +11,19 @@ import SettingController from "./SettingController";
 import PlatformPeg from "../../PlatformPeg";
 import SettingsStore from "../SettingsStore";
 import { _t } from "../../languageHandler";
+import { SlidingSyncManager } from "../../SlidingSyncManager";
 
 export default class SlidingSyncController extends SettingController {
-    public static serverSupportsSlidingSync: boolean;
-
     public async onChange(): Promise<void> {
         PlatformPeg.get()?.reload();
     }
 
     public get settingDisabled(): boolean | string {
         // Cannot be disabled once enabled, user has been warned and must log out and back in.
-        if (SettingsStore.getValue("feature_sliding_sync")) {
+        if (SettingsStore.getValue("feature_simplified_sliding_sync")) {
             return _t("labs|sliding_sync_disabled_notice");
         }
-        if (!SlidingSyncController.serverSupportsSlidingSync) {
+        if (!SlidingSyncManager.serverSupportsSlidingSync) {
             return _t("labs|sliding_sync_server_no_support");
         }
 
diff --git a/src/settings/controllers/SystemFontController.ts b/src/settings/controllers/SystemFontController.ts
index 70fab9eda9cae82e9a70f984b0dad0cc9911d6db..11d9a2cbd6dad1a171626065704982dd2257b333 100644
--- a/src/settings/controllers/SystemFontController.ts
+++ b/src/settings/controllers/SystemFontController.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import SettingController from "./SettingController";
 import SettingsStore from "../SettingsStore";
 import dis from "../../dispatcher/dispatcher";
-import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
+import { type UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
 import { Action } from "../../dispatcher/actions";
 
 export default class SystemFontController extends SettingController {
diff --git a/src/settings/controllers/ThemeController.ts b/src/settings/controllers/ThemeController.ts
index a192a4d080124971bc21909a1f74eb8154eac275..32a13ff8b7e2e9919d4e37efcb01a2d24cbe94a9 100644
--- a/src/settings/controllers/ThemeController.ts
+++ b/src/settings/controllers/ThemeController.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 
 import SettingController from "./SettingController";
 import { DEFAULT_THEME, enumerateThemes } from "../../theme";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 export default class ThemeController extends SettingController {
     public static isLogin = false;
diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts
index 5371751a3c0a914ebc8432f6cd12ca38fbbd14a4..59ab516487daef6cc91928bed05de311623d039a 100644
--- a/src/settings/controllers/UIFeatureController.ts
+++ b/src/settings/controllers/UIFeatureController.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import SettingController from "./SettingController";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 import SettingsStore from "../SettingsStore";
-import { SettingKey } from "../Settings.tsx";
+import { type SettingKey } from "../Settings.tsx";
 
 /**
  * Enforces that a boolean setting cannot be enabled if the corresponding
diff --git a/src/settings/handlers/AccountSettingsHandler.ts b/src/settings/handlers/AccountSettingsHandler.ts
index 0500556931686ce54e6c6afe716f1ec1ebc50687..3ea9db158e6167c9415556c5c76b685768b5c28d 100644
--- a/src/settings/handlers/AccountSettingsHandler.ts
+++ b/src/settings/handlers/AccountSettingsHandler.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
 Copyright 2017 Travis Ralston
 
@@ -7,14 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AccountDataEvents, ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
+import { type AccountDataEvents, ClientEvent, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { isEqual } from "lodash";
 
 import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
 import { objectClone, objectKeyChanges } from "../../utils/objects";
 import { SettingLevel } from "../SettingLevel";
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
+import { MEDIA_PREVIEW_ACCOUNT_DATA_TYPE } from "../../@types/media_preview";
 
 const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
 const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
@@ -68,6 +68,8 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
         } else if (event.getType() === RECENT_EMOJI_EVENT_TYPE) {
             const val = event.getContent()["enabled"];
             this.watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val);
+        } else if (event.getType() === MEDIA_PREVIEW_ACCOUNT_DATA_TYPE) {
+            this.watchers.notifyUpdate("mediaPreviewConfig", null, SettingLevel.ROOM_ACCOUNT, event.getContent());
         }
     };
 
@@ -159,7 +161,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
 
         // Attach a deferred *before* setting the account data to ensure we catch any requests
         // which race between different lines.
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         const handler = (event: MatrixEvent): void => {
             if (event.getType() !== eventType || !isEqual(event.getContent<AccountDataEvents[K]>()[field], value))
                 return;
@@ -173,7 +175,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
         await deferred.promise;
     }
 
-    public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
+    public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
         switch (settingName) {
             // Special case URL previews
             case "urlPreviewsEnabled":
@@ -199,7 +201,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
             // Special case analytics
             case "pseudonymousAnalyticsOptIn":
                 return this.setAccountData(ANALYTICS_EVENT_TYPE, "pseudonymousAnalyticsOptIn", newValue);
-
+            case "mediaPreviewConfig":
+                // Handled in MediaPreviewConfigController.
+                return;
             default:
                 return this.setAccountData(DEFAULT_SETTINGS_EVENT_TYPE, settingName, newValue);
         }
diff --git a/src/settings/handlers/ConfigSettingsHandler.ts b/src/settings/handlers/ConfigSettingsHandler.ts
index 1dd0e19dc5bc8044ac168d21a0a337a411edb596..1b41d676f5bcecf0859c45eacf39a0836a66faba 100644
--- a/src/settings/handlers/ConfigSettingsHandler.ts
+++ b/src/settings/handlers/ConfigSettingsHandler.ts
@@ -12,7 +12,7 @@ import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
 import SettingsHandler from "./SettingsHandler";
 import SdkConfig from "../../SdkConfig";
 import { SnakedObject } from "../../utils/SnakedObject";
-import { IConfigOptions } from "../../IConfigOptions";
+import { type IConfigOptions } from "../../IConfigOptions";
 
 /**
  * Gets and sets settings at the "config" level. This handler does not make use of the
diff --git a/src/settings/handlers/DeviceSettingsHandler.ts b/src/settings/handlers/DeviceSettingsHandler.ts
index 5feb0c1db38b5603e6417e4f8f897063da646c6e..5ca296aae82471580f3822974a18ed19538d58fe 100644
--- a/src/settings/handlers/DeviceSettingsHandler.ts
+++ b/src/settings/handlers/DeviceSettingsHandler.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { SettingLevel } from "../SettingLevel";
-import { CallbackFn, WatchManager } from "../WatchManager";
+import { type CallbackFn, type WatchManager } from "../WatchManager";
 import AbstractLocalStorageSettingsHandler from "./AbstractLocalStorageSettingsHandler";
 
 /**
diff --git a/src/settings/handlers/LocalEchoWrapper.ts b/src/settings/handlers/LocalEchoWrapper.ts
index 2c44fca8a4ec9705df0d08947777de1cf4fb52da..b34dd652295b05dadd89e42833039db493448f3a 100644
--- a/src/settings/handlers/LocalEchoWrapper.ts
+++ b/src/settings/handlers/LocalEchoWrapper.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import SettingsHandler from "./SettingsHandler";
-import { SettingLevel } from "../SettingLevel";
+import { type SettingLevel } from "../SettingLevel";
 
 /**
  * A wrapper for a SettingsHandler that performs local echo on
diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts
index 84d56dcecf2941a87199f8b496c18ffeedb88a66..822018375e4c9866bf9a5aaf166b4bc565e99fd1 100644
--- a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts
+++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SettingsHandler from "./SettingsHandler";
 
diff --git a/src/settings/handlers/PlatformSettingsHandler.ts b/src/settings/handlers/PlatformSettingsHandler.ts
index fb16b182fe2c11596afcbcd861b4cbef10fb8d49..48420650b1e884c8d8339fe31e2d5c69c37a6e3c 100644
--- a/src/settings/handlers/PlatformSettingsHandler.ts
+++ b/src/settings/handlers/PlatformSettingsHandler.ts
@@ -11,7 +11,7 @@ import PlatformPeg from "../../PlatformPeg";
 import { SETTINGS } from "../Settings";
 import { SettingLevel } from "../SettingLevel";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { Action } from "../../dispatcher/actions";
 
 /**
diff --git a/src/settings/handlers/RoomAccountSettingsHandler.ts b/src/settings/handlers/RoomAccountSettingsHandler.ts
index 73a83d4575ef83e24153e016eb301dab9484150e..8fdf412adfd47aa2e80e1f7c5c8dc866bcc4376d 100644
--- a/src/settings/handlers/RoomAccountSettingsHandler.ts
+++ b/src/settings/handlers/RoomAccountSettingsHandler.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
 Copyright 2017 Travis Ralston
 
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
+import { type MatrixClient, type MatrixEvent, type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
 import { objectClone, objectKeyChanges } from "../../utils/objects";
 import { SettingLevel } from "../SettingLevel";
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
+import { MEDIA_PREVIEW_ACCOUNT_DATA_TYPE } from "../../@types/media_preview";
 
 const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
 const DEFAULT_SETTINGS_EVENT_TYPE = "im.vector.web.settings";
@@ -56,6 +56,8 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
             }
         } else if (event.getType() === ALLOWED_WIDGETS_EVENT_TYPE) {
             this.watchers.notifyUpdate("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
+        } else if (event.getType() === MEDIA_PREVIEW_ACCOUNT_DATA_TYPE) {
+            this.watchers.notifyUpdate("mediaPreviewConfig", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
         }
     };
 
@@ -96,7 +98,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
 
         await this.client.setRoomAccountData(roomId, eventType, content);
 
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         const handler = (event: MatrixEvent, room: Room): void => {
             if (room.roomId !== roomId || event.getType() !== eventType) return;
             if (field !== null && event.getContent()[field] !== value) return;
@@ -108,7 +110,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
         await deferred.promise;
     }
 
-    public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
+    public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
         switch (settingName) {
             // Special case URL previews
             case "urlPreviewsEnabled":
@@ -117,7 +119,9 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
             // Special case allowed widgets
             case "allowedWidgets":
                 return this.setRoomAccountData(roomId, ALLOWED_WIDGETS_EVENT_TYPE, null, newValue);
-
+            case "mediaPreviewConfig":
+                // Handled in MediaPreviewConfigController.
+                return;
             default:
                 return this.setRoomAccountData(roomId, DEFAULT_SETTINGS_EVENT_TYPE, settingName, newValue);
         }
diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.ts b/src/settings/handlers/RoomDeviceSettingsHandler.ts
index cf7c17ced6802ec1a1858a0670ea21058e0b634a..2e9187c7905d831d3ad2b82b071ac6c86f8fc6c3 100644
--- a/src/settings/handlers/RoomDeviceSettingsHandler.ts
+++ b/src/settings/handlers/RoomDeviceSettingsHandler.ts
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
 import { safeSet } from "matrix-js-sdk/src/utils";
 
 import { SettingLevel } from "../SettingLevel";
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
 import AbstractLocalStorageSettingsHandler from "./AbstractLocalStorageSettingsHandler";
 
 /**
diff --git a/src/settings/handlers/RoomSettingsHandler.ts b/src/settings/handlers/RoomSettingsHandler.ts
index dd6bc48a012e3000fc534a1e834fc818170c6bce..a31ccb87e9f1991e6f0f5b8234b136d3938f2d6d 100644
--- a/src/settings/handlers/RoomSettingsHandler.ts
+++ b/src/settings/handlers/RoomSettingsHandler.ts
@@ -7,13 +7,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent, StateEvents } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
+import {
+    type MatrixClient,
+    type MatrixEvent,
+    type RoomState,
+    RoomStateEvent,
+    type StateEvents,
+} from "matrix-js-sdk/src/matrix";
 
 import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
 import { objectClone, objectKeyChanges } from "../../utils/objects";
 import { SettingLevel } from "../SettingLevel";
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
 
 const DEFAULT_SETTINGS_EVENT_TYPE = "im.vector.web.settings";
 type RoomSettingsEventType = typeof DEFAULT_SETTINGS_EVENT_TYPE | "org.matrix.room.preview_urls";
@@ -92,7 +97,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
 
         const { event_id: eventId } = await this.client.sendStateEvent(roomId, eventType, content);
 
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         const handler = (event: MatrixEvent): void => {
             if (event.getId() !== eventId) return;
             this.client.off(RoomStateEvent.Events, handler);
diff --git a/src/settings/handlers/SettingsHandler.ts b/src/settings/handlers/SettingsHandler.ts
index 43ec57cc13525c2d1b2819764232f048ec7b5acc..f0157163b4fa2239ecc70becbe4e15fb89f885f0 100644
--- a/src/settings/handlers/SettingsHandler.ts
+++ b/src/settings/handlers/SettingsHandler.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { WatchManager } from "../WatchManager";
+import { type WatchManager } from "../WatchManager";
 
 /**
  * Represents the base class for all level handlers. This class performs no logic
diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts
index 6be4b925819f0151cd25f3e477b60e6d534da392..9a36d8f73e5957d1e71a6941edf9ace7ccb14946 100644
--- a/src/settings/watchers/FontWatcher.ts
+++ b/src/settings/watchers/FontWatcher.ts
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 
 import dis from "../../dispatcher/dispatcher";
 import SettingsStore from "../SettingsStore";
-import IWatcher from "./Watcher";
+import type IWatcher from "./Watcher";
 import { toPx } from "../../utils/units";
 import { Action } from "../../dispatcher/actions";
 import { SettingLevel } from "../SettingLevel";
-import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
+import { type ActionPayload } from "../../dispatcher/payloads";
 
 export class FontWatcher implements IWatcher {
     /**
diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts
index 4d0c4a7171fb8e93efa94bfb6e592a88975303fc..5ee5810884cc6dea0f9f74d507b2a840fc2890d1 100644
--- a/src/settings/watchers/ThemeWatcher.ts
+++ b/src/settings/watchers/ThemeWatcher.ts
@@ -8,16 +8,25 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
+import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 
 import SettingsStore from "../SettingsStore";
 import dis from "../../dispatcher/dispatcher";
 import { Action } from "../../dispatcher/actions";
 import ThemeController from "../controllers/ThemeController";
-import { findHighContrastTheme, setTheme } from "../../theme";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { findHighContrastTheme } from "../../theme";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { SettingLevel } from "../SettingLevel";
 
-export default class ThemeWatcher {
+export enum ThemeWatcherEvent {
+    Change = "change",
+}
+
+interface ThemeWatcherEventHandlerMap {
+    [ThemeWatcherEvent.Change]: (theme: string) => void;
+}
+
+export default class ThemeWatcher extends TypedEventEmitter<ThemeWatcherEvent, ThemeWatcherEventHandlerMap> {
     private themeWatchRef?: string;
     private systemThemeWatchRef?: string;
     private dispatcherRef?: string;
@@ -29,6 +38,7 @@ export default class ThemeWatcher {
     private currentTheme: string;
 
     public constructor() {
+        super();
         // we have both here as each may either match or not match, so by having both
         // we can get the tristate of dark/light/unsupported
         this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
@@ -72,9 +82,7 @@ export default class ThemeWatcher {
     public recheck(forceTheme?: string): void {
         const oldTheme = this.currentTheme;
         this.currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
-        if (oldTheme !== this.currentTheme) {
-            setTheme(this.currentTheme);
-        }
+        if (oldTheme !== this.currentTheme) this.emit(ThemeWatcherEvent.Change, this.currentTheme);
     }
 
     public getEffectiveTheme(): string {
diff --git a/src/shouldHideEvent.ts b/src/shouldHideEvent.ts
index e97afd0fbdd0514f0e53aaed84a1c871e5ab40af..f8bc2fc5179173da2c93838785f813dd29a93795 100644
--- a/src/shouldHideEvent.ts
+++ b/src/shouldHideEvent.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
  */
 
-import { MatrixEvent, EventType, RelationType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, EventType, RelationType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import SettingsStore from "./settings/SettingsStore";
-import { IRoomState } from "./components/structures/RoomView";
-import { SettingKey } from "./settings/Settings.tsx";
+import { type IRoomState } from "./components/structures/RoomView";
+import { type SettingKey } from "./settings/Settings.tsx";
 
 interface IDiff {
     isMemberEvent: boolean;
diff --git a/src/slash-commands/command.ts b/src/slash-commands/command.ts
index 01c62277239e9e0acc16372919ad201f8adae1f4..029d42db45ba5063982844182d26de75005e2096 100644
--- a/src/slash-commands/command.ts
+++ b/src/slash-commands/command.ts
@@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { SlashCommand as SlashCommandEvent } from "@matrix-org/analytics-events/types/typescript/SlashCommand";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type SlashCommand as SlashCommandEvent } from "@matrix-org/analytics-events/types/typescript/SlashCommand";
 
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import { reject } from "./utils";
-import { _t, TranslationKey, UserFriendlyError } from "../languageHandler";
+import { _t, type TranslationKey, UserFriendlyError } from "../languageHandler";
 import { PosthogAnalytics } from "../PosthogAnalytics";
-import { CommandCategories, RunResult } from "./interface";
+import { CommandCategories, type RunResult } from "./interface";
 
 type RunFn = (
     this: Command,
diff --git a/src/slash-commands/interface.ts b/src/slash-commands/interface.ts
index 6f1f0c8af42cabe4002df46f651370bb4cfcdb07..807a3b725dd7b18f474b1a8f05f0fd45d15d031d 100644
--- a/src/slash-commands/interface.ts
+++ b/src/slash-commands/interface.ts
@@ -9,10 +9,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
 import { _td } from "../languageHandler";
-import { XOR } from "../@types/common";
+import { type XOR } from "../@types/common";
 
 export const CommandCategories = {
     messages: _td("slash_command|category_messages"),
diff --git a/src/slash-commands/join.ts b/src/slash-commands/join.ts
index de548cba5f156c395cf83f708287a2caa4648e30..d34fdae0d547e61da865853c777d7aa11c3a9051 100644
--- a/src/slash-commands/join.ts
+++ b/src/slash-commands/join.ts
@@ -9,17 +9,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _td } from "../languageHandler";
 import { reject, success } from "./utils";
 import { isPermalinkHost, parsePermalink } from "../utils/permalinks/Permalinks";
 import dis from "../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../dispatcher/actions";
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import { Command } from "./command";
-import { CommandCategories, RunResult } from "./interface";
+import { CommandCategories, type RunResult } from "./interface";
 
 // A return of undefined here signals a usage error, where the command should return `reject(this.getUsage());`
 function openRoom(cli: MatrixClient, args: string | undefined, autoJoin: boolean): RunResult | undefined {
diff --git a/src/slash-commands/op.ts b/src/slash-commands/op.ts
index 77084bc7a07db86286dd2a2163378f989311001a..09157bf57ed70b51ac19bf4ee3bda26a370fa732 100644
--- a/src/slash-commands/op.ts
+++ b/src/slash-commands/op.ts
@@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 
 import { _td, UserFriendlyError } from "../languageHandler";
 import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
 import { warnSelfDemote } from "../components/views/right_panel/UserInfo";
 import { TimelineRenderingType } from "../contexts/RoomContext";
 import { canAffectPowerlevels, success, reject } from "./utils";
-import { CommandCategories, RunResult } from "./interface";
+import { CommandCategories, type RunResult } from "./interface";
 import { Command } from "./command";
 
 const updatePowerLevel = async (room: Room, member: RoomMember, powerLevel: number | undefined): Promise<unknown> => {
diff --git a/src/slash-commands/utils.ts b/src/slash-commands/utils.ts
index 8bdade9cf1230d3f2250a8cdb98415e0b8ccabbd..d9cf15d16a27c13d33abc56ca43cef2a3800ccbb 100644
--- a/src/slash-commands/utils.ts
+++ b/src/slash-commands/utils.ts
@@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { SdkContextClass } from "../contexts/SDKContext";
 import { isLocalRoom } from "../utils/localRoom/isLocalRoom";
 import Modal from "../Modal";
 import UploadConfirmDialog from "../components/views/dialogs/UploadConfirmDialog";
-import { RunResult } from "./interface";
+import { type RunResult } from "./interface";
 
 export function reject(error?: any): RunResult {
     return { error };
@@ -49,16 +49,14 @@ export const singleMxcUpload = async (cli: MatrixClient): Promise<string | null>
             const file = (ev as HTMLInputEvent).target.files?.[0];
             if (!file) return;
 
-            Modal.createDialog(UploadConfirmDialog, {
-                file,
-                onFinished: async (shouldContinue): Promise<void> => {
-                    if (shouldContinue) {
-                        const { content_uri: uri } = await cli.uploadContent(file);
-                        resolve(uri);
-                    } else {
-                        resolve(null);
-                    }
-                },
+            const { finished } = Modal.createDialog(UploadConfirmDialog, { file });
+            finished.then(async ([shouldContinue]) => {
+                if (shouldContinue) {
+                    const { content_uri: uri } = await cli.uploadContent(file);
+                    resolve(uri);
+                } else {
+                    resolve(null);
+                }
             });
         };
 
diff --git a/src/stores/ActiveWidgetStore.ts b/src/stores/ActiveWidgetStore.ts
index 8d591552248fc4f1fdeb6c3445402f223d276022..74497ebf99cec02105600f418c97a121703b58b5 100644
--- a/src/stores/ActiveWidgetStore.ts
+++ b/src/stores/ActiveWidgetStore.ts
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import { MatrixEvent, RoomStateEvent, RoomState } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, RoomStateEvent, type RoomState } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import WidgetUtils from "../utils/WidgetUtils";
diff --git a/src/stores/AsyncStore.ts b/src/stores/AsyncStore.ts
index 8f73bdb10a26d0cc9592a341bf33a3e2ef44cfb5..86a043f84a6ea8ef3216e766d7fca7983b44097c 100644
--- a/src/stores/AsyncStore.ts
+++ b/src/stores/AsyncStore.ts
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import { EventEmitter } from "events";
 import AwaitLock from "await-lock";
 
-import { ActionPayload } from "../dispatcher/payloads";
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
 
 /**
  * The event/channel to listen for in an AsyncStore.
diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts
index 598594ca3b90d30e2f6ca2deb4e86ad02d3213ae..6a40787607a3ddbd3223b7b2caa949a17854d701 100644
--- a/src/stores/AsyncStoreWithClient.ts
+++ b/src/stores/AsyncStoreWithClient.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { AsyncStore } from "./AsyncStore";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import { ReadyWatchingStore } from "./ReadyWatchingStore";
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
 
 export abstract class AsyncStoreWithClient<T extends object> extends AsyncStore<T> {
     protected readyStore: ReadyWatchingStore;
diff --git a/src/stores/AutoRageshakeStore.ts b/src/stores/AutoRageshakeStore.ts
index a972099473a0f99a2eb3708f0995ca7670a3c9f6..b7900012505e51c0ed108f46dfa55278d5f53fc0 100644
--- a/src/stores/AutoRageshakeStore.ts
+++ b/src/stores/AutoRageshakeStore.ts
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     ClientEvent,
-    MatrixEvent,
+    type MatrixEvent,
     MatrixEventEvent,
-    SyncStateData,
-    SyncState,
+    type SyncStateData,
+    type SyncState,
     ToDeviceMessageId,
 } from "matrix-js-sdk/src/matrix";
 import { sleep } from "matrix-js-sdk/src/utils";
@@ -22,7 +22,7 @@ import SdkConfig from "../SdkConfig";
 import sendBugReport from "../rageshake/submit-rageshake";
 import defaultDispatcher from "../dispatcher/dispatcher";
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import SettingsStore from "../settings/SettingsStore";
 import { Action } from "../dispatcher/actions";
 
diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts
index c02d1181fe750725290a62d7af3f4366ebd7affb..e3b01cae0b10460a1a2056854d0e988a446126f7 100644
--- a/src/stores/BreadcrumbsStore.ts
+++ b/src/stores/BreadcrumbsStore.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
 
@@ -16,9 +16,9 @@ import defaultDispatcher from "../dispatcher/dispatcher";
 import { arrayHasDiff, filterBoolean } from "../utils/arrays";
 import { SettingLevel } from "../settings/SettingLevel";
 import { Action } from "../dispatcher/actions";
-import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
-import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
+import { type SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
 
 const MAX_ROOMS = 20; // arbitrary
 const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
diff --git a/src/stores/CallStore.ts b/src/stores/CallStore.ts
index d667e0b811bd78c29d31f309c40da2c5dc925bc2..6347cc898ed6be6a264e9b5584e8b26d83aadd51 100644
--- a/src/stores/CallStore.ts
+++ b/src/stores/CallStore.ts
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import { logger } from "matrix-js-sdk/src/logger";
 import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEventHandler";
-import { MatrixRTCSession, MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc";
+import { type MatrixRTCSession, MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc";
 
-import type { GroupCall, Room } from "matrix-js-sdk/src/matrix";
+import type { EmptyObject, GroupCall, Room } from "matrix-js-sdk/src/matrix";
 import defaultDispatcher from "../dispatcher/dispatcher";
 import { UPDATE_EVENT } from "./AsyncStore";
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
@@ -26,7 +26,7 @@ export enum CallStoreEvent {
     ConnectedCalls = "connected_calls",
 }
 
-export class CallStore extends AsyncStoreWithClient<{}> {
+export class CallStore extends AsyncStoreWithClient<EmptyObject> {
     private static _instance: CallStore;
     public static get instance(): CallStore {
         if (!this._instance) {
diff --git a/src/stores/InitialCryptoSetupStore.ts b/src/stores/InitialCryptoSetupStore.ts
index 593b9c1108ef6d89e04c9f599172b079ecd870a7..226d3f4b2ad5f666f3db1c2589b21c7424461002 100644
--- a/src/stores/InitialCryptoSetupStore.ts
+++ b/src/stores/InitialCryptoSetupStore.ts
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { useEffect, useState } from "react";
 
diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts
index 8a369cb51e4c3d543fe0185809998d24a1894d7b..6efeffa013f632c3868c428e65a0c4c3eadf0b0f 100644
--- a/src/stores/LifecycleStore.ts
+++ b/src/stores/LifecycleStore.ts
@@ -12,8 +12,8 @@ import { logger } from "matrix-js-sdk/src/logger";
 
 import { Action } from "../dispatcher/actions";
 import dis from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
-import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
 import { AsyncStore } from "./AsyncStore";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import ToastStore from "./ToastStore";
diff --git a/src/stores/MemberListStore.ts b/src/stores/MemberListStore.ts
index 12878c4ec9c8fb26cf5e9665d5a04c54709e32a6..29269bac9437a516aadb8c4eb3b94858ea23be4f 100644
--- a/src/stores/MemberListStore.ts
+++ b/src/stores/MemberListStore.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import SettingsStore from "../settings/SettingsStore";
-import { SdkContextClass } from "../contexts/SDKContext";
+import { type SdkContextClass } from "../contexts/SDKContext";
 import SdkConfig from "../SdkConfig";
 
 // Regex applied to filter our punctuation in member names before applying sort, to fuzzy it a little
@@ -122,7 +122,7 @@ export class MemberListStore {
      * @returns True if enabled
      */
     private async isLazyLoadingEnabled(roomId: string): Promise<boolean> {
-        if (SettingsStore.getValue("feature_sliding_sync")) {
+        if (SettingsStore.getValue("feature_simplified_sliding_sync")) {
             // only unencrypted rooms use lazy loading
             return !(await this.stores.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
         }
@@ -134,7 +134,7 @@ export class MemberListStore {
      * @returns True if there is storage for lazy loading members
      */
     private isLazyMemberStorageEnabled(): boolean {
-        if (SettingsStore.getValue("feature_sliding_sync")) {
+        if (SettingsStore.getValue("feature_simplified_sliding_sync")) {
             return false;
         }
         return this.stores.client!.hasLazyLoadMembersEnabled();
diff --git a/src/stores/ModalWidgetStore.ts b/src/stores/ModalWidgetStore.ts
index 9f5ddca25d42580f33209f5fe9dea7876494ad96..2a2fbdd8da55cdc37c288c02cb31e6f3b05affef 100644
--- a/src/stores/ModalWidgetStore.ts
+++ b/src/stores/ModalWidgetStore.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IModalWidgetOpenRequestData, IModalWidgetReturnData, Widget } from "matrix-widget-api";
+import { type IModalWidgetOpenRequestData, type IModalWidgetReturnData, type Widget } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
-import Modal, { IHandle, IModal } from "../Modal";
+import { type ActionPayload } from "../dispatcher/payloads";
+import Modal, { type IHandle, type IModal } from "../Modal";
 import ModalWidgetDialog from "../components/views/dialogs/ModalWidgetDialog";
 import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore";
 
@@ -61,18 +61,18 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
                 widgetDefinition: { ...requestData },
                 widgetRoomId,
                 sourceWidgetId: sourceWidget.id,
-                onFinished: (success, data) => {
-                    this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true });
-
-                    this.openSourceWidgetId = null;
-                    this.openSourceWidgetRoomId = null;
-                    this.modalInstance = null;
-                },
             },
             undefined,
             /* priority = */ false,
             /* static = */ true,
         );
+        this.modalInstance!.finished.then(([success, data]) => {
+            this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true });
+
+            this.openSourceWidgetId = null;
+            this.openSourceWidgetRoomId = null;
+            this.modalInstance = null;
+        });
     };
 
     public closeModalWidget = (
diff --git a/src/stores/NonUrgentToastStore.ts b/src/stores/NonUrgentToastStore.ts
index 97a4bbdfdf69046a58e4987b783933830b5178d9..3912fb57885fec00a6b71e3a6c1510aaa5cfdeb1 100644
--- a/src/stores/NonUrgentToastStore.ts
+++ b/src/stores/NonUrgentToastStore.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import EventEmitter from "events";
 
-import { ComponentClass } from "../@types/common";
+import { type ComponentClass } from "../@types/common";
 import { UPDATE_EVENT } from "./AsyncStore";
 
 export type ToastReference = symbol;
diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts
index 72c67c3635fc6c3a215188f4e5ef0fee0f14abbb..4b6f0ddc11efec28850143f1f0ca98c7c0b01f96 100644
--- a/src/stores/OwnBeaconStore.ts
+++ b/src/stores/OwnBeaconStore.ts
@@ -8,31 +8,31 @@ Please see LICENSE files in the repository root for full details.
 
 import { debounce } from "lodash";
 import {
-    Beacon,
-    BeaconIdentifier,
+    type Beacon,
+    type BeaconIdentifier,
     BeaconEvent,
-    MatrixEvent,
-    Room,
-    RoomMember,
-    RoomState,
+    type MatrixEvent,
+    type Room,
+    type RoomMember,
+    type RoomState,
     RoomStateEvent,
     ContentHelpers,
-    MBeaconInfoEventContent,
+    type MBeaconInfoEventContent,
     M_BEACON,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
 import { arrayDiff } from "../utils/arrays";
 import {
-    ClearWatchCallback,
+    type ClearWatchCallback,
     GeolocationError,
     mapGeolocationPositionToTimedGeo,
     sortBeaconsByLatestCreation,
-    TimedGeoUri,
+    type TimedGeoUri,
     watchPosition,
     getCurrentPosition,
 } from "../utils/beacon";
diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts
index ef721ee4aa3611c285f4c45e3d84469b03293b47..b2d35a390f74a7a4ebda6d6e3933bd89eeff3db9 100644
--- a/src/stores/OwnProfileStore.ts
+++ b/src/stores/OwnProfileStore.ts
@@ -6,10 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, RoomStateEvent, MatrixError, User, UserEvent, EventType } from "matrix-js-sdk/src/matrix";
+import {
+    type MatrixEvent,
+    RoomStateEvent,
+    MatrixError,
+    type User,
+    UserEvent,
+    EventType,
+} from "matrix-js-sdk/src/matrix";
 import { throttle } from "lodash";
 
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
 import defaultDispatcher from "../dispatcher/dispatcher";
 import { MatrixClientPeg } from "../MatrixClientPeg";
diff --git a/src/stores/ReadyWatchingStore.ts b/src/stores/ReadyWatchingStore.ts
index cb99cf12688037d97ec9adaec57e34c7a09a4f22..91d7144c711c27f0d0615d167390c6abcbb773b0 100644
--- a/src/stores/ReadyWatchingStore.ts
+++ b/src/stores/ReadyWatchingStore.ts
@@ -6,14 +6,14 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { MatrixClient, SyncState } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, SyncState } from "matrix-js-sdk/src/matrix";
 import { EventEmitter } from "events";
 
 import { MatrixClientPeg } from "../MatrixClientPeg";
-import { ActionPayload } from "../dispatcher/payloads";
-import { IDestroyable } from "../utils/IDestroyable";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type IDestroyable } from "../utils/IDestroyable";
 import { Action } from "../dispatcher/actions";
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
 
 export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
     protected matrixClient?: MatrixClient;
diff --git a/src/stores/ReleaseAnnouncementStore.ts b/src/stores/ReleaseAnnouncementStore.ts
index 05362dde5a9dc3fd900c1d4bc23f9c41c66fb345..0b287d44b986d84675ba78ef294a36c0189f6ac7 100644
--- a/src/stores/ReleaseAnnouncementStore.ts
+++ b/src/stores/ReleaseAnnouncementStore.ts
@@ -17,7 +17,7 @@ import { Features } from "../settings/Settings";
 /**
  * The features are shown in the array order.
  */
-const FEATURES = ["threadsActivityCentre", "pinningMessageList"] as const;
+const FEATURES = ["pinningMessageList"] as const;
 /**
  * All the features that can be shown in the release announcements.
  */
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index 822a6a9dd1faad018f87062d1bf73de040a4a79a..633d55f3f63bc59cbf36c375565d551949e4dcb1 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -8,18 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import * as utils from "matrix-js-sdk/src/utils";
-import { MatrixError, JoinRule, Room, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { MatrixError, JoinRule, type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
-import { JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
-import { Optional } from "matrix-events-sdk";
+import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
+import { type JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
+import { type Optional } from "matrix-events-sdk";
 import EventEmitter from "events";
-import { RoomViewLifecycle, ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import {
+    RoomViewLifecycle,
+    type ViewRoomOpts,
+} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
 
-import { MatrixDispatcher } from "../dispatcher/dispatcher";
+import { type MatrixDispatcher } from "../dispatcher/dispatcher";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import Modal from "../Modal";
 import { _t } from "../languageHandler";
@@ -27,26 +30,28 @@ import { getCachedRoomIDForAlias, storeRoomAliasInCache } from "../RoomAliasCach
 import { Action } from "../dispatcher/actions";
 import { retry } from "../utils/promise";
 import { TimelineRenderingType } from "../contexts/RoomContext";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 import DMRoomMap from "../utils/DMRoomMap";
 import { isMetaSpace, MetaSpace } from "./spaces";
-import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
-import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
-import { JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload";
-import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload";
+import { type JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
+import { type JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
+import { type JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload";
+import { type ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload";
 import ErrorDialog from "../components/views/dialogs/ErrorDialog";
-import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
+import { type ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
 import SettingsStore from "../settings/SettingsStore";
 import { awaitRoomDownSync } from "../utils/RoomUpgrade";
 import { UPDATE_EVENT } from "./AsyncStore";
-import { SdkContextClass } from "../contexts/SDKContext";
+import { type SdkContextClass } from "../contexts/SDKContext";
 import { CallStore } from "./CallStore";
-import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
-import { ActionPayload } from "../dispatcher/payloads";
-import { CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload";
-import { SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
+import { type ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload";
+import { type SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
 import { ModuleRunner } from "../modules/ModuleRunner";
 import { setMarkedUnreadState } from "../utils/notifications";
+import { ConnectionState, ElementCall } from "../models/Call";
+import { isVideoRoom } from "../utils/video-rooms";
 
 const NUM_JOIN_RETRY = 5;
 
@@ -350,11 +355,24 @@ export class RoomViewStore extends EventEmitter {
                 });
             }
 
-            if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) {
-                if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) {
-                    // unsubscribe from this room, but don't await it as we don't care when this gets done.
-                    this.stores.slidingSyncManager.setRoomVisible(this.state.subscribingRoomId, false);
+            if (room && (payload.view_call || isVideoRoom(room))) {
+                let call = CallStore.instance.getCall(payload.room_id);
+                // Start a call if not already there
+                if (call === null) {
+                    ElementCall.create(room, false);
+                    call = CallStore.instance.getCall(payload.room_id)!;
                 }
+                call.presented = true;
+                // Immediately start the call. This will connect to all required widget events
+                // and allow the widget to show the lobby.
+                if (call.connectionState === ConnectionState.Disconnected) call.start();
+            }
+            // If we switch to a different room from the call, we are no longer presenting it
+            const prevRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null;
+            if (prevRoomCall !== null && (!payload.view_call || payload.room_id !== this.state.roomId))
+                prevRoomCall.presented = false;
+
+            if (SettingsStore.getValue("feature_simplified_sliding_sync") && this.state.roomId !== payload.room_id) {
                 this.setState({
                     subscribingRoomId: payload.room_id,
                     roomId: payload.room_id,
@@ -370,13 +388,8 @@ export class RoomViewStore extends EventEmitter {
                 });
                 // set this room as the room subscription. We need to await for it as this will fetch
                 // all room state for this room, which is required before we get the state below.
-                await this.stores.slidingSyncManager.setRoomVisible(payload.room_id, true);
-                // Whilst we were subscribing another room was viewed, so stop what we're doing and
-                // unsubscribe
-                if (this.state.subscribingRoomId !== payload.room_id) {
-                    this.stores.slidingSyncManager.setRoomVisible(payload.room_id, false);
-                    return;
-                }
+                await this.stores.slidingSyncManager.setRoomVisible(payload.room_id);
+
                 // Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now
                 this.dis?.dispatch({
                     ...payload,
diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts
index 3aa6ad18661b65eea60e0617012bac7af0280db2..9e324b64965e523d09268e88a20f7e06daf4c111 100644
--- a/src/stores/SetupEncryptionStore.ts
+++ b/src/stores/SetupEncryptionStore.ts
@@ -8,19 +8,19 @@ Please see LICENSE files in the repository root for full details.
 
 import EventEmitter from "events";
 import {
-    KeyBackupInfo,
+    type KeyBackupInfo,
     VerificationPhase,
-    VerificationRequest,
+    type VerificationRequest,
     VerificationRequestEvent,
     CryptoEvent,
 } from "matrix-js-sdk/src/crypto-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Device, SecretStorage } from "matrix-js-sdk/src/matrix";
+import { type Device, type SecretStorage } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
 import { asyncSome } from "../utils/arrays";
-import { initialiseDehydration } from "../utils/device/dehydration";
+import { initialiseDehydrationIfEnabled } from "../utils/device/dehydration";
 
 export enum Phase {
     Loading = 0,
@@ -29,7 +29,6 @@ export enum Phase {
     Done = 3, // final done stage, but still showing UX
     ConfirmSkip = 4,
     Finished = 5, // UX can be closed
-    ConfirmReset = 6,
 }
 
 /**
@@ -149,7 +148,7 @@ export class SetupEncryptionStore extends EventEmitter {
                     );
                     resolve();
 
-                    await initialiseDehydration();
+                    await initialiseDehydrationIfEnabled(cli);
 
                     if (backupInfo) {
                         await cli.getCrypto()?.loadSessionBackupPrivateKeyFromSecretStorage();
@@ -220,38 +219,6 @@ export class SetupEncryptionStore extends EventEmitter {
         this.emit("update");
     }
 
-    public reset(): void {
-        this.phase = Phase.ConfirmReset;
-        this.emit("update");
-    }
-
-    public async resetConfirm(): Promise<void> {
-        try {
-            // If we've gotten here, the user presumably lost their
-            // secret storage key if they had one. Start by resetting
-            // secret storage and setting up a new recovery key, then
-            // create new cross-signing keys once that succeeds.
-            await accessSecretStorage(
-                async (): Promise<void> => {
-                    this.phase = Phase.Finished;
-                },
-                {
-                    forceReset: true,
-                    resetCrossSigning: true,
-                },
-            );
-        } catch (e) {
-            logger.error("Error resetting cross-signing", e);
-            this.phase = Phase.Intro;
-        }
-        this.emit("update");
-    }
-
-    public returnAfterReset(): void {
-        this.phase = Phase.Intro;
-        this.emit("update");
-    }
-
     public done(): void {
         this.phase = Phase.Finished;
         this.emit("update");
diff --git a/src/stores/ThreepidInviteStore.ts b/src/stores/ThreepidInviteStore.ts
index 92fa7d5bd8d9b3a17338111b70ca7b87edd0afb4..271d75b1309a10fa24257ae3af835859484c829a 100644
--- a/src/stores/ThreepidInviteStore.ts
+++ b/src/stores/ThreepidInviteStore.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import EventEmitter from "events";
 import { base32 } from "rfc4648";
-import { RoomType } from "matrix-js-sdk/src/matrix";
+import { type RoomType } from "matrix-js-sdk/src/matrix";
 
 // Dev note: the interface is split in two so we don't have to disable the
 // linter across the whole project.
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index 3ebb0d945cc5690839a0132ca9fedfa602d5df55..7cb9d60925b6d48e4fb7e3ee54fd559bde2bc0b9 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -7,9 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import React from "react";
+import { logger } from "matrix-js-sdk/src/logger";
 
-import { ComponentClass } from "../@types/common";
+import type React from "react";
+import { type ComponentClass } from "../@types/common";
 
 export interface IToast<C extends ComponentClass> {
     key: string;
@@ -54,10 +55,12 @@ export default class ToastStore extends EventEmitter {
     public addOrReplaceToast<C extends ComponentClass>(newToast: IToast<C>): void {
         const oldIndex = this.toasts.findIndex((t) => t.key === newToast.key);
         if (oldIndex === -1) {
+            logger.info(`Opening toast with key '${newToast.key}': title '${newToast.title}'`);
             let newIndex = this.toasts.length;
             while (newIndex > 0 && this.toasts[newIndex - 1].priority < newToast.priority) --newIndex;
             this.toasts.splice(newIndex, 0, newToast);
         } else {
+            logger.info(`Replacing existing toast with key '${newToast.key}': title now '${newToast.title}'`);
             this.toasts[oldIndex] = newToast;
         }
         this.emit("update");
@@ -71,6 +74,7 @@ export default class ToastStore extends EventEmitter {
         const length = this.toasts.length;
         this.toasts = this.toasts.filter((t) => t.key !== key);
         if (length !== this.toasts.length) {
+            logger.info(`Removed toast with key '${key}'`);
             if (this.toasts.length === 0) {
                 this.countSeen = 0;
             }
diff --git a/src/stores/TypingStore.ts b/src/stores/TypingStore.ts
index 26fd4967e7b2018869355568a7223086c5b1c401..31f04121b1380aa680e15532acbc003ba78bbcc9 100644
--- a/src/stores/TypingStore.ts
+++ b/src/stores/TypingStore.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SdkContextClass } from "../contexts/SDKContext";
+import { type SdkContextClass } from "../contexts/SDKContext";
 import SettingsStore from "../settings/SettingsStore";
 import { isLocalRoom } from "../utils/localRoom/isLocalRoom";
 import Timer from "../utils/Timer";
diff --git a/src/stores/UserProfilesStore.ts b/src/stores/UserProfilesStore.ts
index eb3c25a309095bf70083d0913611a7aaefb4cb48..79bef7d3947813e9e8dfac2bba2895fe62003023 100644
--- a/src/stores/UserProfilesStore.ts
+++ b/src/stores/UserProfilesStore.ts
@@ -8,11 +8,11 @@ Please see LICENSE files in the repository root for full details.
 
 import { logger } from "matrix-js-sdk/src/logger";
 import {
-    IMatrixProfile,
-    MatrixClient,
+    type IMatrixProfile,
+    type MatrixClient,
     MatrixError,
-    MatrixEvent,
-    RoomMember,
+    type MatrixEvent,
+    type RoomMember,
     RoomMemberEvent,
 } from "matrix-js-sdk/src/matrix";
 
diff --git a/src/stores/VoiceRecordingStore.ts b/src/stores/VoiceRecordingStore.ts
index b94301f62cccf8c0c36fd45e36c154b28e25a252..07153949968120997a26dbf9ef06be28d3b45859 100644
--- a/src/stores/VoiceRecordingStore.ts
+++ b/src/stores/VoiceRecordingStore.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Optional } from "matrix-events-sdk";
-import { Room, IEventRelation, RelationType } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
+import { type Room, type IEventRelation, RelationType } from "matrix-js-sdk/src/matrix";
 
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
-import { createVoiceMessageRecording, VoiceMessageRecording } from "../audio/VoiceMessageRecording";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { createVoiceMessageRecording, type VoiceMessageRecording } from "../audio/VoiceMessageRecording";
 
 const SEPARATOR = "|";
 
diff --git a/src/stores/WidgetEchoStore.ts b/src/stores/WidgetEchoStore.ts
index ef81ec10d76f5d7484ddf59526bd265bc2d8ce37..7be77652c769c95f6677e247732ddc4413bc611b 100644
--- a/src/stores/WidgetEchoStore.ts
+++ b/src/stores/WidgetEchoStore.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import { IWidget } from "matrix-widget-api";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type IWidget } from "matrix-widget-api";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { WidgetType } from "../widgets/WidgetType";
+import { type WidgetType } from "../widgets/WidgetType";
 
 /**
  * Acts as a place to get & set widget state, storing local echo state and
diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts
index 8c69770930323b2ad6f2f25c84a884ca6203462e..5a76df710353a6b84a1dd96ac89bf688a2d5c7a6 100644
--- a/src/stores/WidgetStore.ts
+++ b/src/stores/WidgetStore.ts
@@ -6,20 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomStateEvent, MatrixEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
-import { IWidget } from "matrix-widget-api";
+import { type Room, RoomStateEvent, type MatrixEvent, ClientEvent, type EmptyObject } from "matrix-js-sdk/src/matrix";
+import { type IWidget } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
 import defaultDispatcher from "../dispatcher/dispatcher";
 import WidgetEchoStore from "../stores/WidgetEchoStore";
 import ActiveWidgetStore from "../stores/ActiveWidgetStore";
 import WidgetUtils from "../utils/WidgetUtils";
 import { UPDATE_EVENT } from "./AsyncStore";
-import { IApp } from "../utils/WidgetUtils-types";
-
-interface IState {}
+import { type IApp } from "../utils/WidgetUtils-types";
 
 export type { IApp };
 
@@ -36,7 +34,7 @@ interface IRoomWidgets {
 
 // TODO consolidate WidgetEchoStore into this
 // TODO consolidate ActiveWidgetStore into this
-export default class WidgetStore extends AsyncStoreWithClient<IState> {
+export default class WidgetStore extends AsyncStoreWithClient<EmptyObject> {
     private static readonly internalInstance = (() => {
         const instance = new WidgetStore();
         instance.start();
diff --git a/src/stores/local-echo/EchoChamber.ts b/src/stores/local-echo/EchoChamber.ts
index 1cc9bd981edbbd193bebdd92003c3c5b49418c3b..553c1a120c881fd441550396c5c0c2cae0dafe99 100644
--- a/src/stores/local-echo/EchoChamber.ts
+++ b/src/stores/local-echo/EchoChamber.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { RoomEchoChamber } from "./RoomEchoChamber";
+import { type RoomEchoChamber } from "./RoomEchoChamber";
 import { EchoStore } from "./EchoStore";
 
 /**
diff --git a/src/stores/local-echo/EchoContext.ts b/src/stores/local-echo/EchoContext.ts
index 17ba5710d4fe4c99983d0af6b7cb6a0f09e0bdca..df9abff1581856889498368e5b10ec973f2cc514 100644
--- a/src/stores/local-echo/EchoContext.ts
+++ b/src/stores/local-echo/EchoContext.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EchoTransaction, RunFn, TransactionStatus } from "./EchoTransaction";
+import { EchoTransaction, type RunFn, TransactionStatus } from "./EchoTransaction";
 import { arrayFastClone } from "../../utils/arrays";
-import { IDestroyable } from "../../utils/IDestroyable";
+import { type IDestroyable } from "../../utils/IDestroyable";
 import { Whenable } from "../../utils/Whenable";
 
 export enum ContextTransactionState {
diff --git a/src/stores/local-echo/EchoStore.ts b/src/stores/local-echo/EchoStore.ts
index c38995cd4e84ca2421e8e64649cb1dd8a3b3f39a..90a52f6cac140d91d2900cef3635055a5c33a327 100644
--- a/src/stores/local-echo/EchoStore.ts
+++ b/src/stores/local-echo/EchoStore.ts
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { GenericEchoChamber } from "./GenericEchoChamber";
+import { type GenericEchoChamber } from "./GenericEchoChamber";
 import { RoomEchoChamber } from "./RoomEchoChamber";
 import { RoomEchoContext } from "./RoomEchoContext";
 import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
-import { ContextTransactionState, EchoContext } from "./EchoContext";
-import NonUrgentToastStore, { ToastReference } from "../NonUrgentToastStore";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import { ContextTransactionState, type EchoContext } from "./EchoContext";
+import NonUrgentToastStore, { type ToastReference } from "../NonUrgentToastStore";
 import NonUrgentEchoFailureToast from "../../components/views/toasts/NonUrgentEchoFailureToast";
 
 interface IState {
diff --git a/src/stores/local-echo/GenericEchoChamber.ts b/src/stores/local-echo/GenericEchoChamber.ts
index 3bda937d3c7eb9d7e50dd2e61dda6ab96f778348..6dbd7f587b20fc06a62b74cbc3243e1fe4e879f6 100644
--- a/src/stores/local-echo/GenericEchoChamber.ts
+++ b/src/stores/local-echo/GenericEchoChamber.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { EventEmitter } from "events";
 
-import { EchoContext } from "./EchoContext";
-import { EchoTransaction, RunFn, TransactionStatus } from "./EchoTransaction";
+import { type EchoContext } from "./EchoContext";
+import { type EchoTransaction, type RunFn, TransactionStatus } from "./EchoTransaction";
 
 export async function implicitlyReverted(): Promise<void> {
     // do nothing :D
diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts
index e3188752265275301668e3e2b35a261cca0907e2..90e1b645f7c83dec52dcae24b1fe2513157e41f3 100644
--- a/src/stores/local-echo/RoomEchoChamber.ts
+++ b/src/stores/local-echo/RoomEchoChamber.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, ClientEvent, MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, ClientEvent, type MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
 
 import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber";
-import { getRoomNotifsState, RoomNotifState, setRoomNotifsState } from "../../RoomNotifs";
-import { RoomEchoContext } from "./RoomEchoContext";
+import { getRoomNotifsState, type RoomNotifState, setRoomNotifsState } from "../../RoomNotifs";
+import { type RoomEchoContext } from "./RoomEchoContext";
 import { _t } from "../../languageHandler";
 
 export enum CachedRoomKey {
diff --git a/src/stores/local-echo/RoomEchoContext.ts b/src/stores/local-echo/RoomEchoContext.ts
index ae8573c9224137b7105f60990b8a1d476267703f..e935024cd4611dc80d2c818670202b884d6209b0 100644
--- a/src/stores/local-echo/RoomEchoContext.ts
+++ b/src/stores/local-echo/RoomEchoContext.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { EchoContext } from "./EchoContext";
 
diff --git a/src/stores/notifications/ListNotificationState.ts b/src/stores/notifications/ListNotificationState.ts
index f6af7d35c77b34f0f9a5f0203c87722b200e4f4d..884b318838cd89fc8f0e52799a04a9fe461b27c0 100644
--- a/src/stores/notifications/ListNotificationState.ts
+++ b/src/stores/notifications/ListNotificationState.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { NotificationLevel } from "./NotificationLevel";
 import { arrayDiff } from "../../utils/arrays";
-import { RoomNotificationState } from "./RoomNotificationState";
+import { type RoomNotificationState } from "./RoomNotificationState";
 import { NotificationState, NotificationStateEvents } from "./NotificationState";
 
 export type FetchRoomFn = (room: Room) => RoomNotificationState;
diff --git a/src/stores/notifications/NotificationState.ts b/src/stores/notifications/NotificationState.ts
index 9c72c9fef0370ea0f3e19cc436a7a38d545b9544..b3f30229a2e807778e0b083030a14f6bb6615635 100644
--- a/src/stores/notifications/NotificationState.ts
+++ b/src/stores/notifications/NotificationState.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 
 import { NotificationLevel } from "./NotificationLevel";
-import { IDestroyable } from "../../utils/IDestroyable";
+import { type IDestroyable } from "../../utils/IDestroyable";
 import SettingsStore from "../../settings/SettingsStore";
 
 export interface INotificationStateSnapshotParams {
@@ -18,6 +18,7 @@ export interface INotificationStateSnapshotParams {
     level: NotificationLevel;
     muted: boolean;
     knocked: boolean;
+    invited: boolean;
 }
 
 export enum NotificationStateEvents {
@@ -38,6 +39,7 @@ export abstract class NotificationState
     protected _level: NotificationLevel = NotificationLevel.None;
     protected _muted = false;
     protected _knocked = false;
+    protected _invited = false;
 
     private watcherReferences: string[] = [];
 
@@ -70,10 +72,22 @@ export abstract class NotificationState
         return this._knocked;
     }
 
+    /**
+     * True if the notification is an invitation notification.
+     * Invite notifications are a special case of highlight notifications
+     */
+    public get invited(): boolean {
+        return this._invited;
+    }
+
     public get isIdle(): boolean {
         return this.level <= NotificationLevel.None;
     }
 
+    /**
+     * True if the notification is higher than an activity notification or if the feature_hidebold is disabled with an activity notification.
+     * The "unread" term used here is different from the "Unread" in the UI. Unread in the UI doesn't include activity notifications even with feature_hidebold disabled.
+     */
     public get isUnread(): boolean {
         if (this.level > NotificationLevel.Activity) {
             return true;
@@ -83,10 +97,19 @@ export abstract class NotificationState
         }
     }
 
+    /**
+     * True if the notification has a count or a symbol and is equal or greater than an NotificationLevel.Notification.
+     */
     public get hasUnreadCount(): boolean {
         return this.level >= NotificationLevel.Notification && (!!this.count || !!this.symbol);
     }
 
+    /**
+     * True if the notification is a mention, an invitation, a knock or a unset message.
+     *
+     * @deprecated because the name is confusing. A mention is not an invitation, a knock or an unsent message.
+     * In case of a {@link RoomNotificationState}, use {@link RoomNotificationState.isMention} instead.
+     */
     public get hasMentions(): boolean {
         return this.level >= NotificationLevel.Highlight;
     }
@@ -116,6 +139,7 @@ export class NotificationStateSnapshot {
     private readonly level: NotificationLevel;
     private readonly muted: boolean;
     private readonly knocked: boolean;
+    private readonly isInvitation: boolean;
 
     public constructor(state: INotificationStateSnapshotParams) {
         this.symbol = state.symbol;
@@ -123,6 +147,7 @@ export class NotificationStateSnapshot {
         this.level = state.level;
         this.muted = state.muted;
         this.knocked = state.knocked;
+        this.isInvitation = state.invited;
     }
 
     public isDifferentFrom(other: INotificationStateSnapshotParams): boolean {
@@ -132,6 +157,7 @@ export class NotificationStateSnapshot {
             level: this.level,
             muted: this.muted,
             knocked: this.knocked,
+            is: this.isInvitation,
         };
         const after = {
             count: other.count,
diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts
index c4f677130d2a3c99dea7cfe7b5e4703e1f90d99c..af873f712e8e7790d0533e6d119fb1c3edc82c42 100644
--- a/src/stores/notifications/RoomNotificationState.ts
+++ b/src/stores/notifications/RoomNotificationState.ts
@@ -17,6 +17,7 @@ import * as RoomNotifs from "../../RoomNotifs";
 import { NotificationState } from "./NotificationState";
 import SettingsStore from "../../settings/SettingsStore";
 import { MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE } from "../../utils/notifications";
+import { NotificationLevel } from "./NotificationLevel";
 
 export class RoomNotificationState extends NotificationState implements IDestroyable {
     public constructor(
@@ -51,6 +52,52 @@ export class RoomNotificationState extends NotificationState implements IDestroy
         cli.removeListener(ClientEvent.AccountData, this.handleAccountDataUpdate);
     }
 
+    /**
+     * True if the notification is a mention.
+     */
+    public get isMention(): boolean {
+        if (this.invited || this.knocked) return false;
+
+        return this.level === NotificationLevel.Highlight;
+    }
+
+    /**
+     * True if the notification is an unsent message.
+     */
+    public get isUnsentMessage(): boolean {
+        return this.level === NotificationLevel.Unsent;
+    }
+
+    /**
+     * Activity notifications are the lowest level of notification (except none and muted)
+     */
+    public get isActivityNotification(): boolean {
+        return this.level === NotificationLevel.Activity;
+    }
+
+    /**
+     * This is the case for notifications with a level:
+     * - is a knock
+     * - greater Activity
+     * - equal Activity and feature_hidebold is disabled.
+     */
+    public get hasAnyNotificationOrActivity(): boolean {
+        if (this.knocked) return true;
+
+        // If the feature_hidebold is enabled, we don't want to show activity notifications
+        const hideBold = SettingsStore.getValue("feature_hidebold");
+        if (!hideBold && this.level === NotificationLevel.Activity) return true;
+
+        return this.level >= NotificationLevel.Notification;
+    }
+
+    /**
+     * True if the notification is a NotificationLevel.Notification.
+     */
+    public get isNotification(): boolean {
+        return this.level === NotificationLevel.Notification;
+    }
+
     private handleLocalEchoUpdated = (): void => {
         this.updateNotificationState();
     };
@@ -95,7 +142,11 @@ export class RoomNotificationState extends NotificationState implements IDestroy
     private updateNotificationState(): void {
         const snapshot = this.snapshot();
 
-        const { level, symbol, count } = RoomNotifs.determineUnreadState(this.room, undefined, this.includeThreads);
+        const { level, symbol, count, invited } = RoomNotifs.determineUnreadState(
+            this.room,
+            undefined,
+            this.includeThreads,
+        );
         const muted =
             RoomNotifs.getRoomNotifsState(this.room.client, this.room.roomId) === RoomNotifs.RoomNotifState.Mute;
         const knocked =
@@ -105,6 +156,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
         this._count = count;
         this._muted = muted;
         this._knocked = knocked;
+        this._invited = invited;
 
         // finally, publish an update if needed
         this.emitIfUpdated(snapshot);
diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts
index c58125b0bab29a7aee1a3b42558b0535777756d5..12447a3983857be751d475bff5da54b643d2a3d9 100644
--- a/src/stores/notifications/RoomNotificationStateStore.ts
+++ b/src/stores/notifications/RoomNotificationStateStore.ts
@@ -6,24 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, ClientEvent, SyncState } from "matrix-js-sdk/src/matrix";
+import { type Room, ClientEvent, SyncState, type EmptyObject } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
-import defaultDispatcher, { MatrixDispatcher } from "../../dispatcher/dispatcher";
-import { DefaultTagID, TagID } from "../room-list/models";
-import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
+import defaultDispatcher, { type MatrixDispatcher } from "../../dispatcher/dispatcher";
+import { DefaultTagID, type TagID } from "../room-list/models";
+import { type FetchRoomFn, ListNotificationState } from "./ListNotificationState";
 import { RoomNotificationState } from "./RoomNotificationState";
 import { SummarizedNotificationState } from "./SummarizedNotificationState";
 import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
 import { PosthogAnalytics } from "../../PosthogAnalytics";
 import SettingsStore from "../../settings/SettingsStore";
 
-interface IState {}
-
 export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator");
 
-export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
+export class RoomNotificationStateStore extends AsyncStoreWithClient<EmptyObject> {
     private static readonly internalInstance = (() => {
         const instance = new RoomNotificationStateStore();
         instance.start();
diff --git a/src/stores/notifications/SpaceNotificationState.ts b/src/stores/notifications/SpaceNotificationState.ts
index e5aedf63179c02f8f596e8c2ff5f91949c69a545..4ea24b7e10cc521d633f22a0264bb1d882a408e2 100644
--- a/src/stores/notifications/SpaceNotificationState.ts
+++ b/src/stores/notifications/SpaceNotificationState.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { NotificationLevel } from "./NotificationLevel";
 import { arrayDiff } from "../../utils/arrays";
-import { RoomNotificationState } from "./RoomNotificationState";
+import { type RoomNotificationState } from "./RoomNotificationState";
 import { NotificationState, NotificationStateEvents } from "./NotificationState";
-import { FetchRoomFn } from "./ListNotificationState";
+import { type FetchRoomFn } from "./ListNotificationState";
 import { DefaultTagID } from "../room-list/models";
 import RoomListStore from "../room-list/RoomListStore";
 
diff --git a/src/stores/oidc/OidcClientStore.ts b/src/stores/oidc/OidcClientStore.ts
index f814b1a6cc29189a63ee96731a6278c1b9a0aeeb..1edfb4b59ca83ff36790897bb488703283b3096e 100644
--- a/src/stores/oidc/OidcClientStore.ts
+++ b/src/stores/oidc/OidcClientStore.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, discoverAndValidateOIDCIssuerWellKnown } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, discoverAndValidateOIDCIssuerWellKnown } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { OidcClient } from "oidc-client-ts";
 
@@ -50,11 +50,8 @@ export class OidcClientStore {
         } else {
             // We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC.
             try {
-                const authIssuer = await this.matrixClient.getAuthIssuer();
-                const { accountManagementEndpoint, metadata } = await discoverAndValidateOIDCIssuerWellKnown(
-                    authIssuer.issuer,
-                );
-                this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
+                const authMetadata = await this.matrixClient.getAuthMetadata();
+                this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
             } catch (e) {
                 console.log("Auth issuer not found", e);
             }
@@ -153,14 +150,11 @@ export class OidcClientStore {
 
         try {
             const clientId = getStoredOidcClientId();
-            const { accountManagementEndpoint, metadata, signingKeys } = await discoverAndValidateOIDCIssuerWellKnown(
-                this.authenticatedIssuer,
-            );
-            this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer);
+            const authMetadata = await discoverAndValidateOIDCIssuerWellKnown(this.authenticatedIssuer);
+            this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer);
             this.oidcClient = new OidcClient({
-                ...metadata,
-                authority: metadata.issuer,
-                signingKeys,
+                authority: authMetadata.issuer,
+                signingKeys: authMetadata.signingKeys ?? undefined,
                 redirect_uri: PlatformPeg.get()!.getOidcCallbackUrl().href,
                 client_id: clientId,
             });
diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts
index ea9b722071d4f6ff29e149d04b88a41ef22c5a5a..b3f8d934608b8f647adcff9194311b5c12b75763 100644
--- a/src/stores/right-panel/RightPanelStore.ts
+++ b/src/stores/right-panel/RightPanelStore.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { logger } from "matrix-js-sdk/src/logger";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import defaultDispatcher from "../../dispatcher/dispatcher";
 import { pendingVerificationRequestForUser } from "../../verification";
@@ -20,16 +20,33 @@ import { ReadyWatchingStore } from "../ReadyWatchingStore";
 import {
     convertToStatePanel,
     convertToStorePanel,
-    IRightPanelCard,
-    IRightPanelCardState,
-    IRightPanelForRoom,
+    type IRightPanelCard,
+    type IRightPanelCardState,
+    type IRightPanelForRoom,
 } from "./RightPanelStoreIPanelState";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { Action } from "../../dispatcher/actions";
-import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload";
+import { type ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 
+/**
+ * @see RightPanelStore#generateHistoryForPhase
+ */
+function getPhasesForPhase(phase: IRightPanelCard["phase"]): RightPanelPhases[] {
+    switch (phase) {
+        case RightPanelPhases.ThreadPanel:
+        case RightPanelPhases.MemberList:
+        case RightPanelPhases.PinnedMessages:
+            return [RightPanelPhases.RoomSummary];
+        case RightPanelPhases.MemberInfo:
+        case RightPanelPhases.ThreePidMemberInfo:
+            return [RightPanelPhases.RoomSummary, RightPanelPhases.MemberList];
+        default:
+            return [];
+    }
+}
+
 /**
  * A class for tracking the state of the right panel between layouts and
  * sessions. This state includes a history for each room. Each history element
@@ -134,16 +151,20 @@ export default class RightPanelStore extends ReadyWatchingStore {
         return { state: {}, phase: null };
     }
 
-    // Setters
+    /**
+     * This function behaves as following:
+     * - If the same phase is sent along with a non-empty state, only the state is updated and history is retained.
+     * - If the provided phase is different to the current phase:
+     *     - Existing history is thrown away.
+     *     - New card is added along with a different history, see {@link generateHistoryForPhase}
+     *
+     * If the right panel was set, this function also shows the right panel.
+     */
     public setCard(card: IRightPanelCard, allowClose = true, roomId?: string): void {
         const rId = roomId ?? this.viewedRoomId ?? "";
-        // This function behaves as following:
-        // Update state: if the same phase is send but with a state
-        // Set right panel and erase history: if a "different to the current" phase is send (with or without a state)
-        // If the right panel is set, this function also shows the right panel.
         const redirect = this.getVerificationRedirect(card);
         const targetPhase = redirect?.phase ?? card.phase;
-        const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state);
+        const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? undefined : card.state);
 
         // Checks for wrong SetRightPanelPhase requests
         if (!this.isPhaseValid(targetPhase, Boolean(rId))) return;
@@ -155,7 +176,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
             this.emitAndUpdateSettings();
         } else if (targetPhase !== this.currentCardForRoom(rId)?.phase || !this.byRoom[rId]) {
             // Set right panel and initialize/erase history
-            const history = [{ phase: targetPhase, state: cardState ?? {} }];
+            const history = this.generateHistoryForPhase(targetPhase!, cardState ?? {});
             this.byRoom[rId] = { history, isOpen: true };
             this.emitAndUpdateSettings();
         } else {
@@ -239,7 +260,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
      * @param cardState The state within the phase.
      */
     public showOrHidePhase(phase: RightPanelPhases, cardState?: Partial<IRightPanelCardState>): void {
-        if (this.currentCard.phase == phase && !cardState && this.isOpen) {
+        if (this.currentCard.phase === phase && !cardState && this.isOpen) {
             this.togglePanel(null);
         } else {
             this.setCard({ phase, state: cardState });
@@ -247,6 +268,31 @@ export default class RightPanelStore extends ReadyWatchingStore {
         }
     }
 
+    /**
+     * For a given phase, generates card history such that it looks
+     * similar to how an user typically would reach said phase in the app.
+     * eg: User would usually reach the memberlist via room-info panel, so
+     * that history is added.
+     */
+    private generateHistoryForPhase(
+        phase: IRightPanelCard["phase"],
+        cardState?: Partial<IRightPanelCardState>,
+    ): IRightPanelCard[] {
+        const card = { phase, state: cardState };
+        if (!this.isCardStateValid(card)) {
+            /**
+             * If the card we're adding is not valid, then we just return
+             * an empty history.
+             * This is to avoid a scenario where, for eg, you set a member info
+             * card with invalid card state (no member) but the member list is
+             * shown since the created history is valid except for the last card.
+             */
+            return [];
+        }
+        const cards = getPhasesForPhase(phase).map((p) => ({ phase: p, state: {} }));
+        return [...cards, card];
+    }
+
     private loadCacheFromSettings(): void {
         if (this.viewedRoomId) {
             const room = this.mxClient?.getRoom(this.viewedRoomId);
diff --git a/src/stores/right-panel/RightPanelStoreIPanelState.ts b/src/stores/right-panel/RightPanelStoreIPanelState.ts
index 1ee5f2a95de68e09592a246c8f9e65a88ef35247..8b145c25ec717401c02ff0e526a8c829ddfef2a1 100644
--- a/src/stores/right-panel/RightPanelStoreIPanelState.ts
+++ b/src/stores/right-panel/RightPanelStoreIPanelState.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room, RoomMember, User } from "matrix-js-sdk/src/matrix";
-import { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
+import { type MatrixEvent, type Room, type RoomMember, type User } from "matrix-js-sdk/src/matrix";
+import { type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
 
-import { RightPanelPhases } from "./RightPanelStorePhases";
+import { type RightPanelPhases } from "./RightPanelStorePhases";
 
 export interface IRightPanelCardState {
     member?: RoomMember | User;
@@ -38,8 +38,6 @@ export interface IRightPanelCardStateStored {
     initialEventId?: string;
     isInitialEventHighlighted?: boolean;
     initialEventScrollIntoView?: boolean;
-    // room summary card
-    focusRoomSearch?: boolean;
 }
 
 export interface IRightPanelCard {
@@ -84,7 +82,6 @@ export function convertCardToStore(panelState: IRightPanelCard): IRightPanelCard
         memberInfoEventId: !!state?.memberInfoEvent?.getId() ? state.memberInfoEvent.getId() : undefined,
         initialEventId: !!state?.initialEvent?.getId() ? state.initialEvent.getId() : undefined,
         memberId: !!state?.member?.userId ? state.member.userId : undefined,
-        focusRoomSearch: state.focusRoomSearch,
     };
 
     return { state: stateStored, phase: panelState.phase };
@@ -104,7 +101,6 @@ function convertStoreToCard(panelStateStore: IRightPanelCardStored, room: Room):
             : undefined,
         initialEvent: !!stateStored?.initialEventId ? room.findEventById(stateStored.initialEventId) : undefined,
         member: (!!stateStored?.memberId && room.getMember(stateStored.memberId)) || undefined,
-        focusRoomSearch: stateStored?.focusRoomSearch,
     };
 
     return { state: state, phase: panelStateStore.phase };
diff --git a/src/stores/right-panel/action-handlers/View3pidInvite.ts b/src/stores/right-panel/action-handlers/View3pidInvite.ts
index 8a54c56c994201211908462949d61b9e2a8808aa..a57fd9b990c4cd8d718fca17281153fac4ca263a 100644
--- a/src/stores/right-panel/action-handlers/View3pidInvite.ts
+++ b/src/stores/right-panel/action-handlers/View3pidInvite.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ActionPayload } from "../../../dispatcher/payloads";
-import RightPanelStore from "../RightPanelStore";
+import { type ActionPayload } from "../../../dispatcher/payloads";
+import type RightPanelStore from "../RightPanelStore";
 import { RightPanelPhases } from "../RightPanelStorePhases";
 
 /**
diff --git a/src/stores/room-list-v3/RoomListStoreV3.ts b/src/stores/room-list-v3/RoomListStoreV3.ts
new file mode 100644
index 0000000000000000000000000000000000000000..92de63285e79f4e51f2788f14aba907e8b553add
--- /dev/null
+++ b/src/stores/room-list-v3/RoomListStoreV3.ts
@@ -0,0 +1,359 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { logger } from "matrix-js-sdk/src/logger";
+import { EventType, KnownMembership } from "matrix-js-sdk/src/matrix";
+
+import type { EmptyObject, Room, RoomState } from "matrix-js-sdk/src/matrix";
+import type { MatrixDispatcher } from "../../dispatcher/dispatcher";
+import type { ActionPayload } from "../../dispatcher/payloads";
+import type { FilterKey } from "./skip-list/filters";
+import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
+import SettingsStore from "../../settings/SettingsStore";
+import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
+import defaultDispatcher from "../../dispatcher/dispatcher";
+import { RoomSkipList } from "./skip-list/RoomSkipList";
+import { RecencySorter } from "./skip-list/sorters/RecencySorter";
+import { AlphabeticSorter } from "./skip-list/sorters/AlphabeticSorter";
+import { readReceiptChangeIsFor } from "../../utils/read-receipts";
+import { EffectiveMembership, getEffectiveMembership, getEffectiveMembershipTag } from "../../utils/membership";
+import SpaceStore from "../spaces/SpaceStore";
+import { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
+import { FavouriteFilter } from "./skip-list/filters/FavouriteFilter";
+import { UnreadFilter } from "./skip-list/filters/UnreadFilter";
+import { PeopleFilter } from "./skip-list/filters/PeopleFilter";
+import { RoomsFilter } from "./skip-list/filters/RoomsFilter";
+import { InvitesFilter } from "./skip-list/filters/InvitesFilter";
+import { MentionsFilter } from "./skip-list/filters/MentionsFilter";
+import { LowPriorityFilter } from "./skip-list/filters/LowPriorityFilter";
+import { type Sorter, SortingAlgorithm } from "./skip-list/sorters";
+import { SettingLevel } from "../../settings/SettingLevel";
+import { MARKED_UNREAD_TYPE_STABLE, MARKED_UNREAD_TYPE_UNSTABLE } from "../../utils/notifications";
+import { getChangedOverrideRoomMutePushRules } from "../room-list/utils/roomMute";
+import { Action } from "../../dispatcher/actions";
+
+/**
+ * These are the filters passed to the room skip list.
+ */
+const FILTERS = [
+    new FavouriteFilter(),
+    new UnreadFilter(),
+    new PeopleFilter(),
+    new RoomsFilter(),
+    new InvitesFilter(),
+    new MentionsFilter(),
+    new LowPriorityFilter(),
+];
+
+export enum RoomListStoreV3Event {
+    // The event/channel which is called when the room lists have been changed.
+    ListsUpdate = "lists_update",
+    // The event which is called when the room list is loaded.
+    ListsLoaded = "lists_loaded",
+}
+
+export const LISTS_UPDATE_EVENT = RoomListStoreV3Event.ListsUpdate;
+export const LISTS_LOADED_EVENT = RoomListStoreV3Event.ListsLoaded;
+/**
+ * This store allows for fast retrieval of the room list in a sorted and filtered manner.
+ * This is the third such implementation hence the "V3".
+ * This store is being actively developed so expect the methods to change in future.
+ */
+export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
+    private roomSkipList?: RoomSkipList;
+    private readonly msc3946ProcessDynamicPredecessor: boolean;
+
+    public constructor(dispatcher: MatrixDispatcher) {
+        super(dispatcher);
+        this.msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
+        SpaceStore.instance.on(UPDATE_SELECTED_SPACE, () => {
+            this.onActiveSpaceChanged();
+        });
+        SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, () => this.onActiveSpaceChanged());
+    }
+
+    /**
+     * Get a list of unsorted, unfiltered rooms.
+     */
+    public getRooms(): Room[] {
+        let rooms = this.matrixClient?.getVisibleRooms(this.msc3946ProcessDynamicPredecessor) ?? [];
+        rooms = rooms.filter((r) => VisibilityProvider.instance.isRoomVisible(r));
+        return rooms;
+    }
+
+    /**
+     * Check whether the initial list of rooms has loaded.
+     */
+    public get isLoadingRooms(): boolean {
+        return !this.roomSkipList?.initialized;
+    }
+
+    /**
+     * Get a list of sorted rooms.
+     */
+    public getSortedRooms(): Room[] {
+        if (this.roomSkipList?.initialized) return Array.from(this.roomSkipList);
+        else return [];
+    }
+
+    /**
+     * Get a list of sorted rooms that belong to the currently active space.
+     * If filterKeys is passed, only the rooms that match the given filters are
+     * returned.
+
+     * @param filterKeys Optional array of filters that the rooms must match against.
+     */
+    public getSortedRoomsInActiveSpace(filterKeys?: FilterKey[]): Room[] {
+        if (this.roomSkipList?.initialized) return Array.from(this.roomSkipList.getRoomsInActiveSpace(filterKeys));
+        else return [];
+    }
+
+    /**
+     * Resort the list of rooms using a different algorithm.
+     * @param algorithm The sorting algorithm to use.
+     */
+    public resort(algorithm: SortingAlgorithm): void {
+        if (!this.roomSkipList) throw new Error("Cannot resort room list before skip list is created.");
+        if (!this.matrixClient) throw new Error("Cannot resort room list without matrix client.");
+        if (this.roomSkipList.activeSortAlgorithm === algorithm) return;
+        const sorter =
+            algorithm === SortingAlgorithm.Alphabetic
+                ? new AlphabeticSorter()
+                : new RecencySorter(this.matrixClient.getSafeUserId());
+        this.roomSkipList.useNewSorter(sorter, this.getRooms());
+        this.emit(LISTS_UPDATE_EVENT);
+        SettingsStore.setValue("RoomList.preferredSorting", null, SettingLevel.DEVICE, algorithm);
+    }
+
+    /**
+     * Currently active sorting algorithm if the store is ready or undefined otherwise.
+     */
+    public get activeSortAlgorithm(): SortingAlgorithm | undefined {
+        return this.roomSkipList?.activeSortAlgorithm;
+    }
+
+    protected async onReady(): Promise<any> {
+        if (this.roomSkipList?.initialized || !this.matrixClient) return;
+        const sorter = this.getPreferredSorter(this.matrixClient.getSafeUserId());
+        this.roomSkipList = new RoomSkipList(sorter, FILTERS);
+        await SpaceStore.instance.storeReadyPromise;
+        const rooms = this.getRooms();
+        this.roomSkipList.seed(rooms);
+        this.emit(LISTS_LOADED_EVENT);
+        this.emit(LISTS_UPDATE_EVENT);
+    }
+
+    protected async onAction(payload: ActionPayload): Promise<void> {
+        if (!this.matrixClient || !this.roomSkipList?.initialized) return;
+
+        /**
+         * For the kind of updates that we care about (represented by the cases below),
+         * we try to find the associated room and simply re-insert it into the
+         * skiplist. If the position of said room in the sorted list changed, re-inserting
+         * would put it in the correct place.
+         */
+        switch (payload.action) {
+            case "MatrixActions.Room.receipt": {
+                if (readReceiptChangeIsFor(payload.event, this.matrixClient)) {
+                    const room = payload.room;
+                    if (!room) {
+                        logger.warn(`Own read receipt was in unknown room ${room.roomId}`);
+                        return;
+                    }
+                    this.addRoomAndEmit(room);
+                }
+                break;
+            }
+
+            case "MatrixActions.Room.tags": {
+                const room = payload.room;
+                this.addRoomAndEmit(room);
+                break;
+            }
+
+            case "MatrixActions.Room.accountData": {
+                const eventType = payload.event_type;
+                if (eventType === MARKED_UNREAD_TYPE_STABLE || eventType === MARKED_UNREAD_TYPE_UNSTABLE) {
+                    const room = payload.room;
+                    this.addRoomAndEmit(room);
+                }
+                break;
+            }
+
+            case "MatrixActions.Event.decrypted": {
+                const roomId = payload.event.getRoomId();
+                if (!roomId) return;
+                const room = this.matrixClient.getRoom(roomId);
+                if (!room) {
+                    logger.warn(`Event ${payload.event.getId()} was decrypted in an unknown room ${roomId}`);
+                    return;
+                }
+                this.addRoomAndEmit(room);
+                break;
+            }
+
+            case "MatrixActions.accountData": {
+                this.handleAccountDataPayload(payload);
+                break;
+            }
+
+            case "MatrixActions.Room.timeline": {
+                // Ignore non-live events (backfill) and notification timeline set events (without a room)
+                if (!payload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent || !payload.room) return;
+                this.addRoomAndEmit(payload.room);
+                break;
+            }
+
+            case "MatrixActions.Room.myMembership": {
+                const oldMembership = getEffectiveMembership(payload.oldMembership);
+                const newMembership = getEffectiveMembershipTag(payload.room, payload.membership);
+
+                // If the user is kicked, re-insert the room and do nothing more.
+                const ownUserId = this.matrixClient.getSafeUserId();
+                const isKicked = (payload.room as Room).getMember(ownUserId)?.isKicked();
+                if (isKicked) {
+                    this.addRoomAndEmit(payload.room);
+                    return;
+                }
+
+                // If the user has left this room, remove it from the skiplist.
+                if (
+                    (payload.oldMembership === KnownMembership.Invite ||
+                        payload.oldMembership === KnownMembership.Join) &&
+                    payload.membership === KnownMembership.Leave
+                ) {
+                    this.roomSkipList.removeRoom(payload.room);
+                    this.emit(LISTS_UPDATE_EVENT);
+                    return;
+                }
+
+                // If we're joining an upgraded room, we'll want to make sure we don't proliferate
+                // the dead room in the list.
+                if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
+                    const roomState: RoomState = payload.room.currentState;
+                    const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor);
+                    if (predecessor) {
+                        const prevRoom = this.matrixClient?.getRoom(predecessor.roomId);
+                        if (prevRoom) this.roomSkipList.removeRoom(prevRoom);
+                        else logger.warn(`Unable to find predecessor room with id ${predecessor.roomId}`);
+                    }
+                }
+
+                this.addRoomAndEmit(payload.room, true);
+                break;
+            }
+
+            case Action.AfterForgetRoom: {
+                const room = payload.room;
+                this.roomSkipList.removeRoom(room);
+                this.emit(LISTS_UPDATE_EVENT);
+                break;
+            }
+        }
+    }
+
+    /**
+     * This method deals with the two types of account data payloads that we care about.
+     */
+    private handleAccountDataPayload(payload: ActionPayload): void {
+        const eventType = payload.event_type;
+        let needsEmit = false;
+        switch (eventType) {
+            // When we're told about new DMs, insert the associated dm rooms.
+            case EventType.Direct: {
+                const dmMap = payload.event.getContent();
+                for (const userId of Object.keys(dmMap)) {
+                    const roomIds = dmMap[userId];
+                    for (const roomId of roomIds) {
+                        const room = this.matrixClient!.getRoom(roomId);
+                        if (!room) {
+                            logger.warn(`${roomId} was found in DMs but the room is not in the store`);
+                            continue;
+                        }
+                        this.roomSkipList!.reInsertRoom(room);
+                        needsEmit = true;
+                    }
+                }
+                break;
+            }
+            case EventType.PushRules: {
+                // When a room becomes muted/unmuted, re-insert that room.
+                const possibleMuteChangeRoomIds = getChangedOverrideRoomMutePushRules(payload);
+                if (!possibleMuteChangeRoomIds) return;
+                const rooms = possibleMuteChangeRoomIds
+                    .map((id) => this.matrixClient?.getRoom(id))
+                    .filter((room) => !!room);
+                for (const room of rooms) {
+                    this.roomSkipList!.reInsertRoom(room);
+                    needsEmit = true;
+                }
+                break;
+            }
+        }
+        if (needsEmit) this.emit(LISTS_UPDATE_EVENT);
+    }
+
+    /**
+     * Create the correct sorter depending on the persisted user preference.
+     * @param myUserId The user-id of our user.
+     * @returns Sorter object that can be passed to the skip list.
+     */
+    private getPreferredSorter(myUserId: string): Sorter {
+        const preferred = SettingsStore.getValue("RoomList.preferredSorting");
+        switch (preferred) {
+            case SortingAlgorithm.Alphabetic:
+                return new AlphabeticSorter();
+            case SortingAlgorithm.Recency:
+                return new RecencySorter(myUserId);
+            default:
+                throw new Error(`Got unknown sort preference from RoomList.preferredSorting setting`);
+        }
+    }
+
+    /**
+     * Add a room to the skiplist and emit an update.
+     * @param room The room to add to the skiplist
+     * @param isNewRoom Set this to true if this a new room that the isn't already in the skiplist
+     */
+    private addRoomAndEmit(room: Room, isNewRoom = false): void {
+        if (!this.roomSkipList) throw new Error("roomSkipList hasn't been created yet!");
+        if (isNewRoom) {
+            if (!VisibilityProvider.instance.isRoomVisible(room)) {
+                logger.info(
+                    `RoomListStoreV3: Refusing to add new room ${room.roomId} because isRoomVisible returned false.`,
+                );
+                return;
+            }
+            this.roomSkipList.addNewRoom(room);
+        } else {
+            this.roomSkipList.reInsertRoom(room);
+        }
+        this.emit(LISTS_UPDATE_EVENT);
+    }
+
+    private onActiveSpaceChanged(): void {
+        if (!this.roomSkipList) return;
+        this.roomSkipList.calculateActiveSpaceForNodes();
+        this.emit(LISTS_UPDATE_EVENT);
+    }
+}
+
+export default class RoomListStoreV3 {
+    private static internalInstance: RoomListStoreV3Class;
+
+    public static get instance(): RoomListStoreV3Class {
+        if (!RoomListStoreV3.internalInstance) {
+            const instance = new RoomListStoreV3Class(defaultDispatcher);
+            instance.start();
+            RoomListStoreV3.internalInstance = instance;
+        }
+
+        return this.internalInstance;
+    }
+}
+
+window.mxRoomListStoreV3 = RoomListStoreV3.instance;
diff --git a/src/stores/room-list-v3/skip-list/Level.ts b/src/stores/room-list-v3/skip-list/Level.ts
new file mode 100644
index 0000000000000000000000000000000000000000..16f42634b70d1eb8e4be2d197d7dc77bf7d199bb
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/Level.ts
@@ -0,0 +1,114 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { RoomNode } from "./RoomNode";
+import { shouldPromote } from "./utils";
+
+/**
+ * Represents one level of the skip list
+ */
+export class Level {
+    public head?: RoomNode;
+    private current?: RoomNode;
+    private _size: number = 0;
+
+    /**
+     * The number of elements in this level
+     */
+    public get size(): number {
+        return this._size;
+    }
+
+    public constructor(public readonly level: number) {}
+
+    /**
+     * Insert node after current
+     */
+    public setNext(node: RoomNode): void {
+        if (!this.head) this.head = node;
+        if (!this.current) {
+            this.current = node;
+        } else {
+            node.previous[this.level] = this.current;
+            this.current.next[this.level] = node;
+            this.current = node;
+        }
+        this._size++;
+    }
+
+    /**
+     * Iterate through the elements in this level and create
+     * a new level above this level by probabilistically determining
+     * whether a given element must be promoted to the new level.
+     */
+    public generateNextLevel(): Level {
+        const nextLevelSentinel = new Level(this.level + 1);
+        let current = this.head;
+        while (current) {
+            if (shouldPromote()) {
+                nextLevelSentinel.setNext(current);
+            }
+            current = current.next[this.level];
+        }
+        return nextLevelSentinel;
+    }
+
+    /**
+     * Removes a given node from this level.
+     * Does nothing if the given node is not present in this level.
+     */
+    public removeNode(node: RoomNode): void {
+        // Let's first see if this node is even in this level
+        const nodeInThisLevel = this.head === node || node.previous[this.level];
+        if (!nodeInThisLevel) {
+            // This node is not in this sentinel level, so nothing to do.
+            return;
+        }
+        const prev = node.previous[this.level];
+        if (prev) {
+            const nextNode = node.next[this.level];
+            prev.next[this.level] = nextNode;
+            if (nextNode) nextNode.previous[this.level] = prev;
+        } else {
+            // This node was the head since it has no back links!
+            // so update the head.
+            const next = node.next[this.level];
+            this.head = next;
+            if (next) next.previous[this.level] = node.previous[this.level];
+        }
+        this._size--;
+    }
+
+    /**
+     * Put newNode after node in this level. No checks are done to ensure
+     * that node is actually present in this level.
+     */
+    public insertAfter(node: RoomNode, newNode: RoomNode): void {
+        const level = this.level;
+        const nextNode = node.next[level];
+        if (nextNode) {
+            newNode.next[level] = nextNode;
+            nextNode.previous[level] = newNode;
+        }
+        node.next[level] = newNode;
+        newNode.previous[level] = node;
+        this._size++;
+    }
+
+    /**
+     *  Insert a given node at the head of this level.
+     */
+    public insertAtHead(newNode: RoomNode): void {
+        const existingNode = this.head;
+        this.head = newNode;
+        if (existingNode) {
+            newNode.next[this.level] = existingNode;
+            existingNode.previous[this.level] = newNode;
+        }
+        this._size++;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/RoomNode.ts b/src/stores/room-list-v3/skip-list/RoomNode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6fbcb65588f02a94d3b5f2764b6ef591eeadd748
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/RoomNode.ts
@@ -0,0 +1,79 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter, FilterKey } from "./filters";
+import SpaceStore from "../../spaces/SpaceStore";
+
+/**
+ * Room skip list stores room nodes.
+ * These hold the actual room object and provides references to other nodes
+ * in different levels.
+ */
+export class RoomNode {
+    private _isInActiveSpace: boolean = false;
+
+    public constructor(public readonly room: Room) {}
+
+    /**
+     * This array holds references to the next node in a given level.
+     * eg: next[i] gives the next room node from this room node in level i.
+     */
+    public next: RoomNode[] = [];
+
+    /**
+     * This array holds references to the previous node in a given level.
+     * eg: previous[i] gives the previous room node from this room node in level i.
+     */
+    public previous: RoomNode[] = [];
+
+    /**
+     * Whether the room associated with this room node belongs to
+     * the currently active space.
+     * @see {@link SpaceStoreClass#activeSpace} to understand what active
+     * space means.
+     */
+    public get isInActiveSpace(): boolean {
+        return this._isInActiveSpace;
+    }
+
+    /**
+     * Check if this room belongs to the active space and store the result
+     * in {@link RoomNode#isInActiveSpace}.
+     */
+    public checkIfRoomBelongsToActiveSpace(): void {
+        const activeSpace = SpaceStore.instance.activeSpace;
+        this._isInActiveSpace = SpaceStore.instance.isRoomInSpace(activeSpace, this.room.roomId);
+    }
+
+    /**
+     * Aggregates all the filter keys that apply to this room.
+     * eg: if filterKeysSet.has(Filter.FavouriteFilter) is true, then this room is a favourite room.
+     */
+    private filterKeysSet: Set<FilterKey> = new Set();
+
+    /**
+     * Returns true if the associated room matches all the provided filters.
+     * Returns false otherwise.
+     * @param filterKeys An array of filter keys to check against.
+     */
+    public doesRoomMatchFilters(filterKeys: FilterKey[]): boolean {
+        return !filterKeys.some((key) => !this.filterKeysSet.has(key));
+    }
+
+    /**
+     * Populates {@link RoomNode#filterKeysSet} by checking if the associated room
+     * satisfies the given filters.
+     * @param filters A list of filters
+     */
+    public applyFilters(filters: Filter[]): void {
+        this.filterKeysSet = new Set();
+        for (const filter of filters) {
+            if (filter.matches(this.room)) this.filterKeysSet.add(filter.key);
+        }
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/RoomSkipList.ts b/src/stores/room-list-v3/skip-list/RoomSkipList.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93c898ee2160291b8eb7358b1c832d99d5520cd2
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/RoomSkipList.ts
@@ -0,0 +1,233 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Sorter, SortingAlgorithm } from "./sorters";
+import type { Filter, FilterKey } from "./filters";
+import { RoomNode } from "./RoomNode";
+import { shouldPromote } from "./utils";
+import { Level } from "./Level";
+import { SortedRoomIterator, SortedSpaceFilteredIterator } from "./iterators";
+
+/**
+ * Implements a skip list that stores rooms using a given sorting algorithm.
+ * See See https://en.wikipedia.org/wiki/Skip_list
+ */
+export class RoomSkipList implements Iterable<Room> {
+    private levels: Level[] = [new Level(0)];
+    private roomNodeMap: Map<string, RoomNode> = new Map();
+    public initialized: boolean = false;
+
+    public constructor(
+        private sorter: Sorter,
+        private filters: Filter[] = [],
+    ) {}
+
+    private reset(): void {
+        this.levels = [new Level(0)];
+        this.roomNodeMap = new Map();
+    }
+
+    /**
+     * Seed the list with an initial list of rooms.
+     */
+    public seed(rooms: Room[]): void {
+        // 1. First sort the rooms and create a base sorted linked list
+        const sortedRoomNodes = this.sorter.sort(rooms).map((room) => new RoomNode(room));
+        let currentLevel = this.levels[0];
+        for (const node of sortedRoomNodes) {
+            node.applyFilters(this.filters);
+            currentLevel.setNext(node);
+            this.roomNodeMap.set(node.room.roomId, node);
+        }
+
+        // 2. Create the rest of the sub linked lists
+        do {
+            this.levels[currentLevel.level] = currentLevel;
+            currentLevel = currentLevel.generateNextLevel();
+        } while (currentLevel.size > 1);
+
+        // 3. Go through the list of rooms and mark nodes in active space
+        this.calculateActiveSpaceForNodes();
+
+        this.initialized = true;
+    }
+
+    /**
+     * Go through all the room nodes and check if they belong to the active space.
+     */
+    public calculateActiveSpaceForNodes(): void {
+        for (const node of this.roomNodeMap.values()) {
+            node.checkIfRoomBelongsToActiveSpace();
+        }
+    }
+
+    /**
+     * Change the sorting algorithm used by the skip list.
+     * This will reset the list and will rebuild from scratch.
+     */
+    public useNewSorter(sorter: Sorter, rooms: Room[]): void {
+        this.reset();
+        this.sorter = sorter;
+        this.seed(rooms);
+    }
+
+    /**
+     * Removes a given room from the skip list.
+     */
+    public removeRoom(room: Room): void {
+        const existingNode = this.roomNodeMap.get(room.roomId);
+        this.roomNodeMap.delete(room.roomId);
+        if (existingNode) {
+            for (const level of this.levels) {
+                level.removeNode(existingNode);
+            }
+        }
+    }
+
+    /**
+     * Re-inserts a room that is already in the skiplist.
+     * This method does nothing if the room isn't already in the skiplist.
+     * @param room the room to add
+     */
+    public reInsertRoom(room: Room): void {
+        if (!this.roomNodeMap.has(room.roomId)) {
+            return;
+        }
+        this.removeRoom(room);
+        this.addNewRoom(room);
+    }
+
+    /**
+     * Adds a new room to the skiplist.
+     * This method will throw an error if the room is already in the skiplist.
+     * @param room the room to add
+     */
+    public addNewRoom(room: Room): void {
+        if (this.roomNodeMap.has(room.roomId)) {
+            throw new Error(`Can't add room to skiplist: ${room.roomId} is already in the skiplist!`);
+        }
+        this.insertRoom(room);
+    }
+
+    /**
+     * Adds a given room to the correct sorted position in the list.
+     */
+    private insertRoom(room: Room): void {
+        const newNode = new RoomNode(room);
+        newNode.checkIfRoomBelongsToActiveSpace();
+        newNode.applyFilters(this.filters);
+        this.roomNodeMap.set(room.roomId, newNode);
+
+        /**
+         * This array tracks where the new node must be inserted in a
+         * given level.
+         * The index is the level and the value represents where the
+         * insertion must happen.
+         * If the value is null, it simply means that we need to insert
+         * at the head.
+         * If the value is a RoomNode, simply insert after this node.
+         */
+        const insertionNodes: (RoomNode | null)[] = [];
+
+        /**
+         * Now we'll do the actual work of finding where to insert this
+         * node.
+         *
+         * We start at the top most level and move downwards ...
+         */
+        for (let j = this.levels.length - 1; j >= 0; --j) {
+            const level = this.levels[j];
+
+            /**
+             * If the head is undefined, that means this level is empty.
+             * So mark it as such in insertionNodes and skip over this
+             * level.
+             */
+            if (!level.head) {
+                insertionNodes[j] = null;
+                continue;
+            }
+
+            /**
+             * So there's actually some nodes in this level ...
+             * All we need to do is find the node that is smaller or
+             * equal to the node that we wish to insert.
+             */
+            let current = level.head;
+            let previous: RoomNode | null = null;
+            while (current) {
+                if (this.sorter.comparator(current.room, room) < 0) {
+                    previous = current;
+                    current = current.next[j];
+                } else break;
+            }
+
+            /**
+             * previous will now be null if there's no node in this level
+             * smaller than the node we wish to insert or it will be a
+             * RoomNode.
+             * This is exactly what we need to track in insertionNodes!
+             */
+            insertionNodes[j] = previous;
+        }
+
+        /**
+         * We're done with difficult part, now we just need to do the
+         * actual node insertion.
+         */
+        for (const [level, node] of insertionNodes.entries()) {
+            /**
+             * Whether our new node should be present in a level
+             * is decided by coin toss.
+             */
+            if (level === 0 || shouldPromote()) {
+                const levelObj = this.levels[level];
+                if (node) levelObj.insertAfter(node, newNode);
+                else levelObj.insertAtHead(newNode);
+            } else {
+                break;
+            }
+        }
+    }
+
+    public [Symbol.iterator](): SortedRoomIterator {
+        return new SortedRoomIterator(this.levels[0].head!);
+    }
+
+    /**
+     * Returns an iterator that can be used to generate a list of sorted rooms that belong
+     * to the currently active space. Passing filterKeys will further filter the list such
+     * that only rooms that match the filters are returned.
+     *
+     * @example To get an array of rooms:
+     * Array.from(RLS.getRoomsInActiveSpace());
+     *
+     * @example Use a for ... of loop to iterate over rooms:
+     * for(const room of RLS.getRoomsInActiveSpace()) { something(room); }
+     *
+     * @example Additional filtering:
+     * Array.from(RLS.getRoomsInActiveSpace([FilterKeys.Favourite]));
+     */
+    public getRoomsInActiveSpace(filterKeys: FilterKey[] = []): SortedSpaceFilteredIterator {
+        return new SortedSpaceFilteredIterator(this.levels[0].head!, filterKeys);
+    }
+
+    /**
+     * The number of rooms currently in the skip list.
+     */
+    public get size(): number {
+        return this.levels[0].size;
+    }
+
+    /**
+     * The currently active sorting algorithm.
+     */
+    public get activeSortAlgorithm(): SortingAlgorithm {
+        return this.sorter.type;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts b/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6af657b81e3e94628fbeb02ebc130e6e719be854
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import { DefaultTagID } from "../../../room-list/models";
+
+export class FavouriteFilter implements Filter {
+    public matches(room: Room): boolean {
+        return !!room.tags[DefaultTagID.Favourite];
+    }
+
+    public get key(): FilterKey.FavouriteFilter {
+        return FilterKey.FavouriteFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts b/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fb9fff9b4419eeb5439d3ccffaa2fb022a04b1ce
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type Room, KnownMembership } from "matrix-js-sdk/src/matrix";
+
+import type { Filter } from ".";
+import { FilterKey } from ".";
+
+export class InvitesFilter implements Filter {
+    public matches(room: Room): boolean {
+        return room.getMyMembership() === KnownMembership.Invite;
+    }
+
+    public get key(): FilterKey.InvitesFilter {
+        return FilterKey.InvitesFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da47761d6e306de61bf3539f6d6cc48d6bf24e49
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import { DefaultTagID } from "../../../room-list/models";
+
+export class LowPriorityFilter implements Filter {
+    public matches(room: Room): boolean {
+        return !!room.tags[DefaultTagID.LowPriority];
+    }
+
+    public get key(): FilterKey.LowPriorityFilter {
+        return FilterKey.LowPriorityFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts b/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fb6e45b9228dfccf4f5c9e1647c1071a944e132c
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
+
+export class MentionsFilter implements Filter {
+    public matches(room: Room): boolean {
+        return RoomNotificationStateStore.instance.getRoomState(room).isMention;
+    }
+
+    public get key(): FilterKey.MentionsFilter {
+        return FilterKey.MentionsFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/PeopleFilter.ts b/src/stores/room-list-v3/skip-list/filters/PeopleFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..742eb40abe1208c827ed1ed0c3047a5783aaad3c
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/PeopleFilter.ts
@@ -0,0 +1,21 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import DMRoomMap from "../../../../utils/DMRoomMap";
+
+export class PeopleFilter implements Filter {
+    public matches(room: Room): boolean {
+        // Match rooms that are DMs
+        return !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
+    }
+
+    public get key(): FilterKey.PeopleFilter {
+        return FilterKey.PeopleFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/RoomsFilter.ts b/src/stores/room-list-v3/skip-list/filters/RoomsFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58349dcea215f405a40b2def09ffc694b8a7cce0
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/RoomsFilter.ts
@@ -0,0 +1,21 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import DMRoomMap from "../../../../utils/DMRoomMap";
+
+export class RoomsFilter implements Filter {
+    public matches(room: Room): boolean {
+        // This should filter rooms that are not DMs
+        return !DMRoomMap.shared().getUserIdForRoomId(room.roomId);
+    }
+
+    public get key(): FilterKey.RoomsFilter {
+        return FilterKey.RoomsFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/UnreadFilter.ts b/src/stores/room-list-v3/skip-list/filters/UnreadFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db29861cd1e6e61141522e8f09fea2cdf3d54d81
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/UnreadFilter.ts
@@ -0,0 +1,21 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Filter } from ".";
+import { FilterKey } from ".";
+import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
+import { getMarkedUnreadState } from "../../../../utils/notifications";
+
+export class UnreadFilter implements Filter {
+    public matches(room: Room): boolean {
+        return RoomNotificationStateStore.instance.getRoomState(room).hasUnreadCount || !!getMarkedUnreadState(room);
+    }
+
+    public get key(): FilterKey.UnreadFilter {
+        return FilterKey.UnreadFilter;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/filters/index.ts b/src/stores/room-list-v3/skip-list/filters/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4c65167b3cdd1e7937d0c90d9ab3efa76cc5986
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/filters/index.ts
@@ -0,0 +1,30 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+
+export const enum FilterKey {
+    FavouriteFilter,
+    UnreadFilter,
+    PeopleFilter,
+    RoomsFilter,
+    LowPriorityFilter,
+    MentionsFilter,
+    InvitesFilter,
+}
+
+export interface Filter {
+    /**
+     * Boolean return value indicates whether this room satisfies
+     * the filter condition.
+     */
+    matches(room: Room): boolean;
+
+    /**
+     * Used to identify this particular filter.
+     */
+    key: FilterKey;
+}
diff --git a/src/stores/room-list-v3/skip-list/iterators.ts b/src/stores/room-list-v3/skip-list/iterators.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a992c79aef907f99982cc634acb8d33438e4526f
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/iterators.ts
@@ -0,0 +1,47 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { RoomNode } from "./RoomNode";
+import type { FilterKey } from "./filters";
+
+export class SortedRoomIterator implements Iterator<Room> {
+    public constructor(private current: RoomNode) {}
+
+    public next(): IteratorResult<Room> {
+        const current = this.current;
+        if (!current) return { value: undefined, done: true };
+        this.current = current.next[0];
+        return {
+            value: current.room,
+        };
+    }
+}
+
+export class SortedSpaceFilteredIterator implements Iterator<Room> {
+    public constructor(
+        private current: RoomNode,
+        private readonly filters: FilterKey[],
+    ) {}
+
+    public [Symbol.iterator](): SortedSpaceFilteredIterator {
+        return this;
+    }
+
+    public next(): IteratorResult<Room> {
+        let current = this.current;
+        while (current) {
+            if (current.isInActiveSpace && current.doesRoomMatchFilters(this.filters)) break;
+            current = current.next[0];
+        }
+        if (!current) return { value: undefined, done: true };
+        this.current = current.next[0];
+        return {
+            value: current.room,
+        };
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter.ts b/src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..50a1b09d174db3f5685032c1f36d7daf1568f561
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import { type Sorter, SortingAlgorithm } from ".";
+
+export class AlphabeticSorter implements Sorter {
+    private readonly collator = new Intl.Collator();
+
+    public sort(rooms: Room[]): Room[] {
+        return [...rooms].sort((a, b) => {
+            return this.comparator(a, b);
+        });
+    }
+
+    public comparator(roomA: Room, roomB: Room): number {
+        return this.collator.compare(roomA.name, roomB.name);
+    }
+
+    public get type(): SortingAlgorithm.Alphabetic {
+        return SortingAlgorithm.Alphabetic;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07c902e3eea6edbaf2ed5e569f7628221e6c9ac2
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts
@@ -0,0 +1,45 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import { type Sorter, SortingAlgorithm } from ".";
+import { getLastTs } from "../../../room-list/algorithms/tag-sorting/RecentAlgorithm";
+import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
+
+export class RecencySorter implements Sorter {
+    public constructor(private myUserId: string) {}
+
+    public sort(rooms: Room[]): Room[] {
+        const tsCache: { [roomId: string]: number } = {};
+        return [...rooms].sort((a, b) => this.comparator(a, b, tsCache));
+    }
+
+    public comparator(roomA: Room, roomB: Room, cache?: any): number {
+        // Check mute status first; muted rooms should be at the bottom
+        const isRoomAMuted = RoomNotificationStateStore.instance.getRoomState(roomA).muted;
+        const isRoomBMuted = RoomNotificationStateStore.instance.getRoomState(roomB).muted;
+        if (isRoomAMuted && !isRoomBMuted) return 1;
+        if (isRoomBMuted && !isRoomAMuted) return -1;
+
+        // Then check recency; recent rooms should be at the top
+        const roomALastTs = this.getTs(roomA, cache);
+        const roomBLastTs = this.getTs(roomB, cache);
+        return roomBLastTs - roomALastTs;
+    }
+
+    public get type(): SortingAlgorithm.Recency {
+        return SortingAlgorithm.Recency;
+    }
+
+    private getTs(room: Room, cache?: { [roomId: string]: number }): number {
+        const ts = cache?.[room.roomId] ?? getLastTs(room, this.myUserId);
+        if (cache) {
+            cache[room.roomId] = ts;
+        }
+        return ts;
+    }
+}
diff --git a/src/stores/room-list-v3/skip-list/sorters/index.ts b/src/stores/room-list-v3/skip-list/sorters/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40381448c8affa3139b829c45de0402c68a2179f
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/sorters/index.ts
@@ -0,0 +1,36 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+
+export interface Sorter {
+    /**
+     * Performs an initial sort of rooms and returns a new array containing
+     * the result.
+     * @param rooms An array of rooms.
+     */
+    sort(rooms: Room[]): Room[];
+    /**
+     * The comparator used for sorting.
+     * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#comparefn
+     * @param roomA Room
+     * @param roomB Room
+     */
+    comparator(roomA: Room, roomB: Room): number;
+    /**
+     * A string that uniquely identifies this given sorter.
+     */
+    type: SortingAlgorithm;
+}
+
+/**
+ * All the available sorting algorithms.
+ */
+export const enum SortingAlgorithm {
+    Recency = "Recency",
+    Alphabetic = "Alphabetic",
+}
diff --git a/src/stores/room-list-v3/skip-list/utils.ts b/src/stores/room-list-v3/skip-list/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4c0bac4af656be729efba07fbfc7a876420cecdd
--- /dev/null
+++ b/src/stores/room-list-v3/skip-list/utils.ts
@@ -0,0 +1,10 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+export function shouldPromote(): boolean {
+    return Math.random() < 0.5;
+}
diff --git a/src/stores/room-list/Interface.ts b/src/stores/room-list/Interface.ts
index f9e32b6f40af4b60e29d745dfcff99ab58054a0f..f913f7e7ed629726429ef81a329fc6dd7321654e 100644
--- a/src/stores/room-list/Interface.ts
+++ b/src/stores/room-list/Interface.ts
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import type { Room } from "matrix-js-sdk/src/matrix";
 import type { EventEmitter } from "events";
-import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
-import { RoomUpdateCause, TagID } from "./models";
-import { IFilterCondition } from "./filters/IFilterCondition";
+import { type ITagMap, type ListAlgorithm, type SortAlgorithm } from "./algorithms/models";
+import { type RoomUpdateCause, type TagID } from "./models";
+import { type IFilterCondition } from "./filters/IFilterCondition";
 
 export enum RoomListStoreEvent {
     // The event/channel which is called when the room lists have been changed.
diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts
index e85703fa5f364b308b7d575a9bbfe3065081d6bc..a4468dfffeefe846160b173af4553f131daa5209 100644
--- a/src/stores/room-list/ListLayout.ts
+++ b/src/stores/room-list/ListLayout.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { TagID } from "./models";
+import { type TagID } from "./models";
 
 const TILE_HEIGHT_PX = 44;
 
diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts
index 3f1614df7028f59ef946eec970531f9e7db7cf78..51e413940dea2aca6c8aae36f2f42ebec8bf41fc 100644
--- a/src/stores/room-list/MessagePreviewStore.ts
+++ b/src/stores/room-list/MessagePreviewStore.ts
@@ -6,22 +6,30 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RelationType, MatrixEvent, Thread, M_POLL_START, RoomEvent } from "matrix-js-sdk/src/matrix";
+import {
+    type Room,
+    RelationType,
+    type MatrixEvent,
+    type Thread,
+    M_POLL_START,
+    RoomEvent,
+    type EmptyObject,
+} from "matrix-js-sdk/src/matrix";
 import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
 
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
 import defaultDispatcher from "../../dispatcher/dispatcher";
 import { MessageEventPreview } from "./previews/MessageEventPreview";
 import { PollStartEventPreview } from "./previews/PollStartEventPreview";
-import { TagID } from "./models";
+import { type TagID } from "./models";
 import { LegacyCallInviteEventPreview } from "./previews/LegacyCallInviteEventPreview";
 import { LegacyCallAnswerEventPreview } from "./previews/LegacyCallAnswerEventPreview";
 import { LegacyCallHangupEvent } from "./previews/LegacyCallHangupEvent";
 import { StickerEventPreview } from "./previews/StickerEventPreview";
 import { ReactionEventPreview } from "./previews/ReactionEventPreview";
 import { UPDATE_EVENT } from "../AsyncStore";
-import { IPreview } from "./previews/IPreview";
+import { type IPreview } from "./previews/IPreview";
 import shouldHideEvent from "../../shouldHideEvent";
 
 // Emitted event for when a room's preview has changed. First argument will the room for which
@@ -76,10 +84,6 @@ const MAX_EVENTS_BACKWARDS = 50;
 type TAG_ANY = "im.vector.any"; // eslint-disable-line @typescript-eslint/naming-convention
 const TAG_ANY: TAG_ANY = "im.vector.any";
 
-interface IState {
-    // Empty because we don't actually use the state
-}
-
 export interface MessagePreview {
     event: MatrixEvent;
     isThreadReply: boolean;
@@ -117,7 +121,7 @@ const mkMessagePreview = (text: string, event: MatrixEvent): MessagePreview => {
     };
 };
 
-export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
+export class MessagePreviewStore extends AsyncStoreWithClient<EmptyObject> {
     private static readonly internalInstance = (() => {
         const instance = new MessagePreviewStore();
         instance.start();
diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts
index 90461a581c0a9cfb3abd865121e9f30e6a7a85db..c7b2b7f31e035fc37a27c205f585523c72b0e4a5 100644
--- a/src/stores/room-list/RoomListLayoutStore.ts
+++ b/src/stores/room-list/RoomListLayoutStore.ts
@@ -7,16 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "./models";
+import { type TagID } from "./models";
 import { ListLayout } from "./ListLayout";
 import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 
-interface IState {}
-
-export default class RoomListLayoutStore extends AsyncStoreWithClient<IState> {
+export default class RoomListLayoutStore extends AsyncStoreWithClient<EmptyObject> {
     private static internalInstance: RoomListLayoutStore;
 
     private readonly layoutMap = new Map<TagID, ListLayout>();
diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts
index 0b179f7db5499d1e4c68e2ad4b144f176dec1b6c..2fc396b8b39ad4f4fbf2ed70616fb8084f3f4162 100644
--- a/src/stores/room-list/RoomListStore.ts
+++ b/src/stores/room-list/RoomListStore.ts
@@ -6,17 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, RoomState, EventType } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
+import { type MatrixClient, type Room, type RoomState, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import SettingsStore from "../../settings/SettingsStore";
-import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models";
-import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
-import { ActionPayload } from "../../dispatcher/payloads";
-import defaultDispatcher, { MatrixDispatcher } from "../../dispatcher/dispatcher";
+import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, type TagID } from "./models";
+import {
+    type IListOrderingMap,
+    type ITagMap,
+    type ITagSortingMap,
+    ListAlgorithm,
+    SortAlgorithm,
+} from "./algorithms/models";
+import { type ActionPayload } from "../../dispatcher/payloads";
+import defaultDispatcher, { type MatrixDispatcher } from "../../dispatcher/dispatcher";
 import { readReceiptChangeIsFor } from "../../utils/read-receipts";
-import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
+import { FILTER_CHANGED, type IFilterCondition } from "./filters/IFilterCondition";
 import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
 import { EffectiveMembership, getEffectiveMembership, getEffectiveMembershipTag } from "../../utils/membership";
 import RoomListLayoutStore from "./RoomListLayoutStore";
@@ -25,21 +30,16 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
 import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
 import { VisibilityProvider } from "./filters/VisibilityProvider";
 import { SpaceWatcher } from "./SpaceWatcher";
-import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
-import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
-import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
+import { type IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
+import { type RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
 import { UPDATE_EVENT } from "../AsyncStore";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { getChangedOverrideRoomMutePushRules } from "./utils/roomMute";
 
-interface IState {
-    // state is tracked in underlying classes
-}
-
 export const LISTS_UPDATE_EVENT = RoomListStoreEvent.ListsUpdate;
 export const LISTS_LOADING_EVENT = RoomListStoreEvent.ListsLoading; // unused; used by SlidingRoomListStore
 
-export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements Interface {
+export class RoomListStoreClass extends AsyncStoreWithClient<EmptyObject> implements Interface {
     /**
      * Set to true if you're running tests on the store. Should not be touched in
      * any other environment.
@@ -348,15 +348,6 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
     }
 
     private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
-        if (cause === RoomUpdateCause.NewRoom && room.getMyMembership() === KnownMembership.Invite) {
-            // Let the visibility provider know that there is a new invited room. It would be nice
-            // if this could just be an event that things listen for but the point of this is that
-            // we delay doing anything about this room until the VoipUserMapper had had a chance
-            // to do the things it needs to do to decide if we should show this room or not, so
-            // an even wouldn't et us do that.
-            await VisibilityProvider.instance.onNewInvitedRoom(room);
-        }
-
         if (!VisibilityProvider.instance.isRoomVisible(room)) {
             return; // don't do anything on rooms that aren't visible
         }
@@ -404,6 +395,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
 
     public setTagSorting(tagId: TagID, sort: SortAlgorithm): void {
         this.setAndPersistTagSorting(tagId, sort);
+        // We'll always need an update after changing the sort order, so mark for update and trigger
+        // immediately.
+        this.updateFn.mark();
         this.updateFn.trigger();
     }
 
@@ -640,16 +634,9 @@ export default class RoomListStore {
 
     public static get instance(): Interface {
         if (!RoomListStore.internalInstance) {
-            if (SettingsStore.getValue("feature_sliding_sync")) {
-                logger.info("using SlidingRoomListStoreClass");
-                const instance = new SlidingRoomListStoreClass(defaultDispatcher, SdkContextClass.instance);
-                instance.start();
-                RoomListStore.internalInstance = instance;
-            } else {
-                const instance = new RoomListStoreClass(defaultDispatcher);
-                instance.start();
-                RoomListStore.internalInstance = instance;
-            }
+            const instance = new RoomListStoreClass(defaultDispatcher);
+            instance.start();
+            RoomListStore.internalInstance = instance;
         }
 
         return this.internalInstance;
diff --git a/src/stores/room-list/SlidingRoomListStore.ts b/src/stores/room-list/SlidingRoomListStore.ts
deleted file mode 100644
index ba585f3218192b1315932c9fd7e5814fc539c0af..0000000000000000000000000000000000000000
--- a/src/stores/room-list/SlidingRoomListStore.ts
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { Room } from "matrix-js-sdk/src/matrix";
-import { logger } from "matrix-js-sdk/src/logger";
-import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
-import { Optional } from "matrix-events-sdk";
-
-import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
-import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
-import { ActionPayload } from "../../dispatcher/payloads";
-import { MatrixDispatcher } from "../../dispatcher/dispatcher";
-import { IFilterCondition } from "./filters/IFilterCondition";
-import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
-import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
-import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces";
-import { LISTS_LOADING_EVENT } from "./RoomListStore";
-import { UPDATE_EVENT } from "../AsyncStore";
-import { SdkContextClass } from "../../contexts/SDKContext";
-
-interface IState {
-    // state is tracked in underlying classes
-}
-
-export const SlidingSyncSortToFilter: Record<SortAlgorithm, string[]> = {
-    [SortAlgorithm.Alphabetic]: ["by_name", "by_recency"],
-    [SortAlgorithm.Recent]: ["by_notification_level", "by_recency"],
-    [SortAlgorithm.Manual]: ["by_recency"],
-};
-
-const filterConditions: Record<TagID, MSC3575Filter> = {
-    [DefaultTagID.Invite]: {
-        is_invite: true,
-    },
-    [DefaultTagID.Favourite]: {
-        tags: ["m.favourite"],
-    },
-    [DefaultTagID.DM]: {
-        is_dm: true,
-        is_invite: false,
-        // If a DM has a Favourite & Low Prio tag then it'll be shown in those lists instead
-        not_tags: ["m.favourite", "m.lowpriority"],
-    },
-    [DefaultTagID.Untagged]: {
-        is_dm: false,
-        is_invite: false,
-        not_room_types: ["m.space"],
-        not_tags: ["m.favourite", "m.lowpriority"],
-        // spaces filter added dynamically
-    },
-    [DefaultTagID.LowPriority]: {
-        tags: ["m.lowpriority"],
-        // If a room has both Favourite & Low Prio tags then it'll be shown under Favourites
-        not_tags: ["m.favourite"],
-    },
-    // TODO https://github.com/vector-im/element-web/issues/23207
-    // DefaultTagID.ServerNotice,
-    // DefaultTagID.Suggested,
-    // DefaultTagID.Archived,
-};
-
-export const LISTS_UPDATE_EVENT = RoomListStoreEvent.ListsUpdate;
-
-export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> implements Interface {
-    private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {};
-    private tagMap: ITagMap = {};
-    private counts: Record<TagID, number> = {};
-    private stickyRoomId: Optional<string>;
-
-    public constructor(
-        dis: MatrixDispatcher,
-        private readonly context: SdkContextClass,
-    ) {
-        super(dis);
-        this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
-    }
-
-    public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {
-        logger.info("SlidingRoomListStore.setTagSorting ", tagId, sort);
-        this.tagIdToSortAlgo[tagId] = sort;
-        switch (sort) {
-            case SortAlgorithm.Alphabetic:
-                await this.context.slidingSyncManager.ensureListRegistered(tagId, {
-                    sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
-                });
-                break;
-            case SortAlgorithm.Recent:
-                await this.context.slidingSyncManager.ensureListRegistered(tagId, {
-                    sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
-                });
-                break;
-            case SortAlgorithm.Manual:
-                logger.error("cannot enable manual sort in sliding sync mode");
-                break;
-            default:
-                logger.error("unknown sort mode: ", sort);
-        }
-    }
-
-    public getTagSorting(tagId: TagID): SortAlgorithm {
-        let algo = this.tagIdToSortAlgo[tagId];
-        if (!algo) {
-            logger.warn("SlidingRoomListStore.getTagSorting: no sort algorithm for tag ", tagId);
-            algo = SortAlgorithm.Recent; // why not, we have to do something..
-        }
-        return algo;
-    }
-
-    public getCount(tagId: TagID): number {
-        return this.counts[tagId] || 0;
-    }
-
-    public setListOrder(tagId: TagID, order: ListAlgorithm): void {
-        // TODO: https://github.com/vector-im/element-web/issues/23207
-    }
-
-    public getListOrder(tagId: TagID): ListAlgorithm {
-        // TODO: handle unread msgs first? https://github.com/vector-im/element-web/issues/23207
-        return ListAlgorithm.Natural;
-    }
-
-    /**
-     * Adds a filter condition to the room list store. Filters may be applied async,
-     * and thus might not cause an update to the store immediately.
-     * @param {IFilterCondition} filter The filter condition to add.
-     */
-    public async addFilter(filter: IFilterCondition): Promise<void> {
-        // Do nothing, the filters are only used by SpaceWatcher to see if a room should appear
-        // in the room list. We do not support arbitrary code for filters in sliding sync.
-    }
-
-    /**
-     * Removes a filter condition from the room list store. If the filter was
-     * not previously added to the room list store, this will no-op. The effects
-     * of removing a filter may be applied async and therefore might not cause
-     * an update right away.
-     * @param {IFilterCondition} filter The filter condition to remove.
-     */
-    public removeFilter(filter: IFilterCondition): void {
-        // Do nothing, the filters are only used by SpaceWatcher to see if a room should appear
-        // in the room list. We do not support arbitrary code for filters in sliding sync.
-    }
-
-    /**
-     * Gets the tags for a room identified by the store. The returned set
-     * should never be empty, and will contain DefaultTagID.Untagged if
-     * the store is not aware of any tags.
-     * @param room The room to get the tags for.
-     * @returns The tags for the room.
-     */
-    public getTagsForRoom(room: Room): TagID[] {
-        // check all lists for each tag we know about and see if the room is there
-        const tags: TagID[] = [];
-        for (const tagId in this.tagIdToSortAlgo) {
-            const listData = this.context.slidingSyncManager.slidingSync?.getListData(tagId);
-            if (!listData) {
-                continue;
-            }
-            for (const roomIndex in listData.roomIndexToRoomId) {
-                const roomId = listData.roomIndexToRoomId[roomIndex];
-                if (roomId === room.roomId) {
-                    tags.push(tagId);
-                    break;
-                }
-            }
-        }
-        return tags;
-    }
-
-    /**
-     * Manually update a room with a given cause. This should only be used if the
-     * room list store would otherwise be incapable of doing the update itself. Note
-     * that this may race with the room list's regular operation.
-     * @param {Room} room The room to update.
-     * @param {RoomUpdateCause} cause The cause to update for.
-     */
-    public async manualRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<void> {
-        // TODO: this is only used when you forget a room, not that important for now.
-    }
-
-    public get orderedLists(): ITagMap {
-        return this.tagMap;
-    }
-
-    private refreshOrderedLists(tagId: string, roomIndexToRoomId: Record<number, string>): void {
-        const tagMap = this.tagMap;
-
-        // this room will not move due to it being viewed: it is sticky. This can be null to indicate
-        // no sticky room if you aren't viewing a room.
-        this.stickyRoomId = this.context.roomViewStore.getRoomId();
-        let stickyRoomNewIndex = -1;
-        const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room): boolean => {
-            return room.roomId === this.stickyRoomId;
-        });
-
-        // order from low to high
-        const orderedRoomIndexes = Object.keys(roomIndexToRoomId)
-            .map((numStr) => {
-                return Number(numStr);
-            })
-            .sort((a, b) => {
-                return a - b;
-            });
-        const seenRoomIds = new Set<string>();
-        const orderedRoomIds = orderedRoomIndexes.map((i) => {
-            const rid = roomIndexToRoomId[i];
-            if (seenRoomIds.has(rid)) {
-                logger.error("room " + rid + " already has an index position: duplicate room!");
-            }
-            seenRoomIds.add(rid);
-            if (!rid) {
-                throw new Error("index " + i + " has no room ID: Map => " + JSON.stringify(roomIndexToRoomId));
-            }
-            if (rid === this.stickyRoomId) {
-                stickyRoomNewIndex = i;
-            }
-            return rid;
-        });
-        logger.debug(
-            `SlidingRoomListStore.refreshOrderedLists ${tagId} sticky: ${this.stickyRoomId}`,
-            `${stickyRoomOldIndex} -> ${stickyRoomNewIndex}`,
-            "rooms:",
-            orderedRoomIds.length < 30 ? orderedRoomIds : orderedRoomIds.length,
-        );
-
-        if (this.stickyRoomId && stickyRoomOldIndex >= 0 && stickyRoomNewIndex >= 0) {
-            // this update will move this sticky room from old to new, which we do not want.
-            // Instead, keep the sticky room ID index position as it is, swap it with
-            // whatever was in its place.
-            // Some scenarios with sticky room S and bump room B (other letters unimportant):
-            // A, S, C, B                                  S, A, B
-            // B, A, S, C  <---- without sticky rooms ---> B, S, A
-            // B, S, A, C  <- with sticky rooms applied -> S, B, A
-            // In other words, we need to swap positions to keep it locked in place.
-            const inWayRoomId = orderedRoomIds[stickyRoomOldIndex];
-            orderedRoomIds[stickyRoomOldIndex] = this.stickyRoomId;
-            orderedRoomIds[stickyRoomNewIndex] = inWayRoomId;
-        }
-
-        // now set the rooms
-        const rooms: Room[] = [];
-        orderedRoomIds.forEach((roomId) => {
-            const room = this.matrixClient?.getRoom(roomId);
-            if (!room) {
-                return;
-            }
-            rooms.push(room);
-        });
-        tagMap[tagId] = rooms;
-        this.tagMap = tagMap;
-    }
-
-    private onSlidingSyncListUpdate(tagId: string, joinCount: number, roomIndexToRoomId: Record<number, string>): void {
-        this.counts[tagId] = joinCount;
-        this.refreshOrderedLists(tagId, roomIndexToRoomId);
-        // let the UI update
-        this.emit(LISTS_UPDATE_EVENT);
-    }
-
-    private onRoomViewStoreUpdated(): void {
-        // we only care about this to know when the user has clicked on a room to set the stickiness value
-        if (this.context.roomViewStore.getRoomId() === this.stickyRoomId) {
-            return;
-        }
-
-        let hasUpdatedAnyList = false;
-
-        // every list with the OLD sticky room ID needs to be resorted because it now needs to take
-        // its proper place as it is no longer sticky. The newly sticky room can remain the same though,
-        // as we only actually care about its sticky status when we get list updates.
-        const oldStickyRoom = this.stickyRoomId;
-        // it's not safe to check the data in slidingSync as it is tracking the server's view of the
-        // room list. There's an edge case whereby the sticky room has gone outside the window and so
-        // would not be present in the roomIndexToRoomId map anymore, and hence clicking away from it
-        // will make it disappear eventually. We need to check orderedLists as that is the actual
-        // sorted renderable list of rooms which sticky rooms apply to.
-        for (const tagId in this.orderedLists) {
-            const list = this.orderedLists[tagId];
-            const room = list.find((room) => {
-                return room.roomId === oldStickyRoom;
-            });
-            if (room) {
-                // resort it based on the slidingSync view of the list. This may cause this old sticky
-                // room to cease to exist.
-                const listData = this.context.slidingSyncManager.slidingSync?.getListData(tagId);
-                if (!listData) {
-                    continue;
-                }
-                this.refreshOrderedLists(tagId, listData.roomIndexToRoomId);
-                hasUpdatedAnyList = true;
-            }
-        }
-        // in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID.
-        this.stickyRoomId = this.context.roomViewStore.getRoomId();
-
-        if (hasUpdatedAnyList) {
-            this.emit(LISTS_UPDATE_EVENT);
-        }
-    }
-
-    protected async onReady(): Promise<any> {
-        logger.info("SlidingRoomListStore.onReady");
-        // permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation.
-        this.context.slidingSyncManager.slidingSync!.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this));
-        this.context.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
-        this.context.spaceStore.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this));
-        if (this.context.spaceStore.activeSpace) {
-            this.onSelectedSpaceUpdated(this.context.spaceStore.activeSpace, false);
-        }
-
-        // sliding sync has an initial response for spaces. Now request all the lists.
-        // We do the spaces list _first_ to avoid potential flickering on DefaultTagID.Untagged list
-        // which would be caused by initially having no `spaces` filter set, and then suddenly setting one.
-        OrderedDefaultTagIDs.forEach((tagId) => {
-            const filter = filterConditions[tagId];
-            if (!filter) {
-                logger.info("SlidingRoomListStore.onReady unsupported list ", tagId);
-                return; // we do not support this list yet.
-            }
-            const sort = SortAlgorithm.Recent; // default to recency sort, TODO: read from config
-            this.tagIdToSortAlgo[tagId] = sort;
-            this.emit(LISTS_LOADING_EVENT, tagId, true);
-            this.context.slidingSyncManager
-                .ensureListRegistered(tagId, {
-                    filters: filter,
-                    sort: SlidingSyncSortToFilter[sort],
-                })
-                .then(() => {
-                    this.emit(LISTS_LOADING_EVENT, tagId, false);
-                });
-        });
-    }
-
-    private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome: boolean): void => {
-        logger.info("SlidingRoomListStore.onSelectedSpaceUpdated", activeSpace);
-        // update the untagged filter
-        const tagId = DefaultTagID.Untagged;
-        const filters = filterConditions[tagId];
-        const oldSpace = filters.spaces?.[0];
-        filters.spaces = activeSpace && activeSpace != MetaSpace.Home ? [activeSpace] : undefined;
-        if (oldSpace !== activeSpace) {
-            // include subspaces in this list
-            this.context.spaceStore.traverseSpace(
-                activeSpace,
-                (roomId: string) => {
-                    if (roomId === activeSpace) {
-                        return;
-                    }
-                    if (!filters.spaces) {
-                        filters.spaces = [];
-                    }
-                    filters.spaces.push(roomId); // add subspace
-                },
-                false,
-            );
-
-            this.emit(LISTS_LOADING_EVENT, tagId, true);
-            this.context.slidingSyncManager
-                .ensureListRegistered(tagId, {
-                    filters: filters,
-                })
-                .then(() => {
-                    this.emit(LISTS_LOADING_EVENT, tagId, false);
-                });
-        }
-    };
-
-    // Intended for test usage
-    public async resetStore(): Promise<void> {
-        // Test function
-    }
-
-    /**
-     * Regenerates the room whole room list, discarding any previous results.
-     *
-     * Note: This is only exposed externally for the tests. Do not call this from within
-     * the app.
-     * @param trigger Set to false to prevent a list update from being sent. Should only
-     * be used if the calling code will manually trigger the update.
-     */
-    public regenerateAllLists({ trigger = true }): void {
-        // Test function
-    }
-
-    protected async onNotReady(): Promise<any> {
-        await this.resetStore();
-    }
-
-    protected async onAction(payload: ActionPayload): Promise<void> {}
-}
diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts
index 1757637c706c80f45b30b09b93452a463b544404..83d2b1a5db667df17c94ecfd4fe63dab2f6a5b69 100644
--- a/src/stores/room-list/SpaceWatcher.ts
+++ b/src/stores/room-list/SpaceWatcher.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomListStore as Interface } from "./Interface";
+import { type RoomListStore as Interface } from "./Interface";
 import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
 import SpaceStore from "../spaces/SpaceStore";
-import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
+import { MetaSpace, type SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
 
 /**
  * Watches for changes in spaces to manage the filter on the provided RoomListStore
diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts
index 2ae3a1ff9951b1724577f9c9515b3f63dcb887d5..5419a6e47cc1cd8c7b29b97905578c7d70dd9b18 100644
--- a/src/stores/room-list/algorithms/Algorithm.ts
+++ b/src/stores/room-list/algorithms/Algorithm.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JoinRule, Room } from "matrix-js-sdk/src/matrix";
+import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
 import { EventEmitter } from "events";
@@ -14,14 +14,14 @@ import { logger } from "matrix-js-sdk/src/logger";
 
 import DMRoomMap from "../../../utils/DMRoomMap";
 import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
-import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
+import { DefaultTagID, RoomUpdateCause, type TagID } from "../models";
 import {
-    IListOrderingMap,
-    IOrderingAlgorithmMap,
-    ITagMap,
-    ITagSortingMap,
-    ListAlgorithm,
-    SortAlgorithm,
+    type IListOrderingMap,
+    type IOrderingAlgorithmMap,
+    type ITagMap,
+    type ITagSortingMap,
+    type ListAlgorithm,
+    type SortAlgorithm,
 } from "./models";
 import {
     EffectiveMembership,
@@ -29,7 +29,7 @@ import {
     getEffectiveMembershipTag,
     splitRoomsByMembership,
 } from "../../../utils/membership";
-import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
+import { type OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
 import { getListAlgorithmInstance } from "./list-ordering";
 import { VisibilityProvider } from "../filters/VisibilityProvider";
 import { CallStore, CallStoreEvent } from "../../CallStore";
diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
index 492e9ed4e7083a646f1ac9a7b549ed945dcec46c..5bd26c273f920dd6d689e9dd17a4d534203b558c 100644
--- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
@@ -7,10 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { RoomUpdateCause, TagID } from "../../models";
+import { RoomUpdateCause, type TagID } from "../../models";
 import { SortAlgorithm } from "../models";
 import { sortRoomsWithAlgorithm } from "../tag-sorting";
 import { OrderingAlgorithm } from "./OrderingAlgorithm";
diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
index f44b0e1aec297a5cc727765f3b6dac621eb976e1..08014822ea67f573cf757be7bd04420df952bedd 100644
--- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { SortAlgorithm } from "../models";
+import { type SortAlgorithm } from "../models";
 import { sortRoomsWithAlgorithm } from "../tag-sorting";
 import { OrderingAlgorithm } from "./OrderingAlgorithm";
-import { RoomUpdateCause, TagID } from "../../models";
+import { RoomUpdateCause, type TagID } from "../../models";
 import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore";
 
 type NaturalCategorizedRoomMap = {
diff --git a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
index 5cf7631cfeba5b65bf3201e3af339c81349458e1..0f95fe78c2ce9ca043f844de8fbd316b95cb3c99 100644
--- a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { RoomUpdateCause, TagID } from "../../models";
+import { type RoomUpdateCause, type TagID } from "../../models";
 import { SortAlgorithm } from "../models";
 
 /**
diff --git a/src/stores/room-list/algorithms/list-ordering/index.ts b/src/stores/room-list/algorithms/list-ordering/index.ts
index aae3242a09ac482482bb130b45a139bc8526156c..c492ddf4a954d678d3d0962483cf492200925c98 100644
--- a/src/stores/room-list/algorithms/list-ordering/index.ts
+++ b/src/stores/room-list/algorithms/list-ordering/index.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { ImportanceAlgorithm } from "./ImportanceAlgorithm";
-import { ListAlgorithm, SortAlgorithm } from "../models";
+import { ListAlgorithm, type SortAlgorithm } from "../models";
 import { NaturalAlgorithm } from "./NaturalAlgorithm";
-import { TagID } from "../../models";
-import { OrderingAlgorithm } from "./OrderingAlgorithm";
+import { type TagID } from "../../models";
+import { type OrderingAlgorithm } from "./OrderingAlgorithm";
 
 interface AlgorithmFactory {
     (tagId: TagID, initialSortingAlgorithm: SortAlgorithm): OrderingAlgorithm;
diff --git a/src/stores/room-list/algorithms/models.ts b/src/stores/room-list/algorithms/models.ts
index 7cc9bd1c17e63e854781e37c23a83bb44a6c45cb..710c4f15a8905f6bc457ff5e85cd16f87e99344d 100644
--- a/src/stores/room-list/algorithms/models.ts
+++ b/src/stores/room-list/algorithms/models.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../models";
-import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
+import { type TagID } from "../models";
+import { type OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
 
 export enum SortAlgorithm {
     Manual = "MANUAL",
diff --git a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
index 77f530bad4368471f57d9c2ae4f547111c1720a3..759d578c96757c9552863189eeab6f4a21f8ae44 100644
--- a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../../models";
-import { IAlgorithm } from "./IAlgorithm";
+import { type TagID } from "../../models";
+import { type IAlgorithm } from "./IAlgorithm";
 
 /**
  * Sorts rooms according to the browser's determination of alphabetic.
diff --git a/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
index eea3db1db353a0769ed6c487cb6ada218bea62c3..cc0d0b5cc75d5c83eeeac4f6bd9df8bae2a654d3 100644
--- a/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../../models";
+import { type TagID } from "../../models";
 
 /**
  * Represents a tag sorting algorithm.
diff --git a/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
index 7dd28695060016dbe6762c18080493a455ff43ba..b880b6baaf114f5e4ebf39b305a39c10a0f4fb43 100644
--- a/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../../models";
-import { IAlgorithm } from "./IAlgorithm";
+import { type TagID } from "../../models";
+import { type IAlgorithm } from "./IAlgorithm";
 
 /**
  * Sorts rooms according to the tag's `order` property on the room.
diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
index 2c6375e7dfeb745f602aed4e8e7e40b2bed5698e..e41eeee8160f44297a5138be122eb5a4d9db35c1 100644
--- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { type Room, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../../models";
-import { IAlgorithm } from "./IAlgorithm";
+import { type TagID } from "../../models";
+import { type IAlgorithm } from "./IAlgorithm";
 import { MatrixClientPeg } from "../../../../MatrixClientPeg";
 import * as Unread from "../../../../Unread";
 import { EffectiveMembership, getEffectiveMembership } from "../../../../utils/membership";
@@ -62,13 +62,19 @@ export const sortRooms = (rooms: Room[]): Room[] => {
     });
 };
 
-const getLastTs = (r: Room, userId: string): number => {
+export const getLastTs = (r: Room, userId: string): number => {
     const mainTimelineLastTs = ((): number => {
         // Apparently we can have rooms without timelines, at least under testing
         // environments. Just return MAX_INT when this happens.
         if (!r?.timeline) {
             return Number.MAX_SAFE_INTEGER;
         }
+        // MSC4186: Simplified Sliding Sync sets this.
+        // If it's present, sort by it.
+        const bumpStamp = r.getBumpStamp();
+        if (bumpStamp) {
+            return bumpStamp;
+        }
 
         // If the room hasn't been joined yet, it probably won't have a timeline to
         // parse. We'll still fall back to the timeline if this fails, but chances
diff --git a/src/stores/room-list/algorithms/tag-sorting/index.ts b/src/stores/room-list/algorithms/tag-sorting/index.ts
index 912ae5d167f59328a574565101a7fd8fb0e1a6b6..bd728c821b2f6a06e00222997576281aef231588 100644
--- a/src/stores/room-list/algorithms/tag-sorting/index.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/index.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { SortAlgorithm } from "../models";
 import { ManualAlgorithm } from "./ManualAlgorithm";
-import { IAlgorithm } from "./IAlgorithm";
-import { TagID } from "../../models";
+import { type IAlgorithm } from "./IAlgorithm";
+import { type TagID } from "../../models";
 import { RecentAlgorithm } from "./RecentAlgorithm";
 import { AlphabeticAlgorithm } from "./AlphabeticAlgorithm";
 
diff --git a/src/stores/room-list/filters/IFilterCondition.ts b/src/stores/room-list/filters/IFilterCondition.ts
index 00248ff74052d9dec6803f2a213aa49a9256e151..8dd77fd4b95186816fed3fae819c585b471a4694 100644
--- a/src/stores/room-list/filters/IFilterCondition.ts
+++ b/src/stores/room-list/filters/IFilterCondition.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
-import { EventEmitter } from "events";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import { type EventEmitter } from "events";
 
 export const FILTER_CHANGED = "filter_changed";
 
diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts
index 7c4316ff21afcd2928b74b0d7c317d8a5e63d33c..02e20e910313c86737cf52a8f8cad32190ef230f 100644
--- a/src/stores/room-list/filters/SpaceFilterCondition.ts
+++ b/src/stores/room-list/filters/SpaceFilterCondition.ts
@@ -7,12 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { EventEmitter } from "events";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { FILTER_CHANGED, IFilterCondition } from "./IFilterCondition";
-import { IDestroyable } from "../../../utils/IDestroyable";
+import { FILTER_CHANGED, type IFilterCondition } from "./IFilterCondition";
+import { type IDestroyable } from "../../../utils/IDestroyable";
 import SpaceStore from "../../spaces/SpaceStore";
-import { isMetaSpace, MetaSpace, SpaceKey } from "../../spaces";
+import { isMetaSpace, MetaSpace, type SpaceKey } from "../../spaces";
 import { setHasDiff } from "../../../utils/sets";
 import SettingsStore from "../../../settings/SettingsStore";
 
diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts
index 4332ba0f98ba4fc9d4e00bc417cefcf30f903b10..178a1ed553e6e313c113a3605d6a8695cc06941a 100644
--- a/src/stores/room-list/filters/VisibilityProvider.ts
+++ b/src/stores/room-list/filters/VisibilityProvider.ts
@@ -6,12 +6,10 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import LegacyCallHandler from "../../../LegacyCallHandler";
 import { RoomListCustomisations } from "../../../customisations/RoomList";
 import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
-import VoipUserMapper from "../../../VoipUserMapper";
 
 export class VisibilityProvider {
     private static internalInstance: VisibilityProvider;
@@ -25,22 +23,11 @@ export class VisibilityProvider {
         return VisibilityProvider.internalInstance;
     }
 
-    public async onNewInvitedRoom(room: Room): Promise<void> {
-        await VoipUserMapper.sharedInstance().onNewInvitedRoom(room);
-    }
-
     public isRoomVisible(room?: Room): boolean {
         if (!room) {
             return false;
         }
 
-        if (
-            LegacyCallHandler.instance.getSupportsVirtualRooms() &&
-            VoipUserMapper.sharedInstance().isVirtualRoom(room)
-        ) {
-            return false;
-        }
-
         // hide space rooms as they'll be shown in the SpacePanel
         if (room.isSpaceRoom()) {
             return false;
diff --git a/src/stores/room-list/previews/IPreview.ts b/src/stores/room-list/previews/IPreview.ts
index 6defb8bfd855553e1ca156d16de12cb5bcbfa702..bdc5912821210b7d8117cf43e0ba3fcc4eb437d8 100644
--- a/src/stores/room-list/previews/IPreview.ts
+++ b/src/stores/room-list/previews/IPreview.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { TagID } from "../models";
+import { type TagID } from "../models";
 
 /**
  * Represents an event preview.
diff --git a/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts
index eaf2de5e169e76c7fe64a26b6742d0a2f61f987e..8dda1f64bbfda090816adb57df173fb7fe8b1967 100644
--- a/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts
+++ b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import { _t } from "../../../languageHandler";
 
diff --git a/src/stores/room-list/previews/LegacyCallHangupEvent.ts b/src/stores/room-list/previews/LegacyCallHangupEvent.ts
index 58487db6508d343c601065ace2bf54a25b8bee20..3088c9590c4546e654d8a921c1d8fcbf03fdd070 100644
--- a/src/stores/room-list/previews/LegacyCallHangupEvent.ts
+++ b/src/stores/room-list/previews/LegacyCallHangupEvent.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import { _t } from "../../../languageHandler";
 
diff --git a/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts
index 4b3d20e6db035043ea9619e381a8ed7f9873c546..14ff23456ed7726558658d515de87b2c31e47675 100644
--- a/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts
+++ b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import { _t } from "../../../languageHandler";
 
diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts
index fe63ee1c1bbdfde39d29ae981e022b6720ebb5db..cf69e6903d4f9b1e6b75fcb8f1c8fb878485c198 100644
--- a/src/stores/room-list/previews/MessageEventPreview.ts
+++ b/src/stores/room-list/previews/MessageEventPreview.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { _t, sanitizeForTranslation } from "../../../languageHandler";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import { getHtmlText } from "../../../HtmlUtils";
diff --git a/src/stores/room-list/previews/PollStartEventPreview.ts b/src/stores/room-list/previews/PollStartEventPreview.ts
index 09679335c48a921a34cc6154b4ae40efd6c42aa7..d614e736a3606dc7861fdd687b3b76314d589dae 100644
--- a/src/stores/room-list/previews/PollStartEventPreview.ts
+++ b/src/stores/room-list/previews/PollStartEventPreview.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, PollStartEventContent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type PollStartEventContent } from "matrix-js-sdk/src/matrix";
 import { InvalidEventError } from "matrix-js-sdk/src/extensible_events_v1/InvalidEventError";
 import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { _t, sanitizeForTranslation } from "../../../languageHandler";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import MatrixClientContext from "../../../contexts/MatrixClientContext";
diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts
index cee18dda2be41c229d1d43b96f00670251239ee3..15cb0c34891cdf16a0b295daa3973205d7fd6494 100644
--- a/src/stores/room-list/previews/ReactionEventPreview.ts
+++ b/src/stores/room-list/previews/ReactionEventPreview.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { getSenderName, isSelf } from "./utils";
 import { _t } from "../../../languageHandler";
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts
index a368e675e3633700625c1907ae19455f408fc61e..701b97ffc8e4eb96f64dbfc4e1d204fb74b477bd 100644
--- a/src/stores/room-list/previews/StickerEventPreview.ts
+++ b/src/stores/room-list/previews/StickerEventPreview.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { IPreview } from "./IPreview";
-import { TagID } from "../models";
+import { type IPreview } from "./IPreview";
+import { type TagID } from "../models";
 import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
 import { _t } from "../../../languageHandler";
 
diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts
index ffb1602829b39c2768ac68cf5054d81befa03860..d9ce0512de0d09f30621b786fc666b2df4d8355a 100644
--- a/src/stores/room-list/previews/utils.ts
+++ b/src/stores/room-list/previews/utils.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { DefaultTagID, TagID } from "../models";
+import { DefaultTagID, type TagID } from "../models";
 
 export function isSelf(event: MatrixEvent): boolean {
     const selfUserId = MatrixClientPeg.safeGet().getSafeUserId();
diff --git a/src/stores/room-list/utils/roomMute.ts b/src/stores/room-list/utils/roomMute.ts
index df3dbb591490ac327070126d20d256b44d8ccafc..47135edcba48b43750e910e87483f664ad0c3ed3 100644
--- a/src/stores/room-list/utils/roomMute.ts
+++ b/src/stores/room-list/utils/roomMute.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, EventType, IPushRules } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, EventType, type IPushRules } from "matrix-js-sdk/src/matrix";
 
-import { ActionPayload } from "../../../dispatcher/payloads";
+import { type ActionPayload } from "../../../dispatcher/payloads";
 import { isRuleMaybeRoomMuteRule } from "../../../RoomNotifs";
 import { arrayDiff } from "../../../utils/arrays";
 
diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts
index 50aa7748a559abf1e2a9a0cdbcfd231d76b444be..015c9fa0fd38a8c878b5463ab5b9a6bf53533406 100644
--- a/src/stores/spaces/SpaceStore.ts
+++ b/src/stores/spaces/SpaceStore.ts
@@ -6,17 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ListIteratee, Many, sortBy } from "lodash";
+import { type ListIteratee, type Many, sortBy } from "lodash";
 import {
     EventType,
     RoomType,
-    Room,
+    type Room,
     RoomEvent,
-    RoomMember,
+    type RoomMember,
     RoomStateEvent,
-    MatrixEvent,
+    type MatrixEvent,
     ClientEvent,
-    ISendEventResponse,
+    type ISendEventResponse,
+    type EmptyObject,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
@@ -26,7 +27,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
 import RoomListStore from "../room-list/RoomListStore";
 import SettingsStore from "../../settings/SettingsStore";
 import DMRoomMap from "../../utils/DMRoomMap";
-import { FetchRoomFn } from "../notifications/ListNotificationState";
+import { type FetchRoomFn } from "../notifications/ListNotificationState";
 import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
 import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
 import { DefaultTagID } from "../room-list/models";
@@ -35,13 +36,13 @@ import { setDiff, setHasDiff } from "../../utils/sets";
 import { Action } from "../../dispatcher/actions";
 import { arrayHasDiff, arrayHasOrderChange, filterBoolean } from "../../utils/arrays";
 import { reorderLexicographically } from "../../utils/stringOrderField";
-import { TAG_ORDER } from "../../components/views/rooms/RoomList";
-import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
+import { TAG_ORDER } from "../../components/views/rooms/LegacyRoomList";
+import { type SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
 import {
     isMetaSpace,
-    ISuggestedRoom,
+    type ISuggestedRoom,
     MetaSpace,
-    SpaceKey,
+    type SpaceKey,
     UPDATE_HOME_BEHAVIOUR,
     UPDATE_INVITED_SPACES,
     UPDATE_SELECTED_SPACE,
@@ -52,19 +53,17 @@ import { getCachedRoomIDForAlias } from "../../RoomAliasCache";
 import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
 import {
     flattenSpaceHierarchyWithCache,
-    SpaceEntityMap,
-    SpaceDescendantMap,
+    type SpaceEntityMap,
+    type SpaceDescendantMap,
     flattenSpaceHierarchy,
 } from "./flattenSpaceHierarchy";
 import { PosthogAnalytics } from "../../PosthogAnalytics";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
-import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
-import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
+import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
+import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
 import { SdkContextClass } from "../../contexts/SDKContext";
 
-interface IState {}
-
 const ACTIVE_SPACE_LS_KEY = "mx_active_space";
 
 const metaSpaceOrder: MetaSpace[] = [
@@ -123,7 +122,7 @@ type SpaceStoreActions =
     | SwitchSpacePayload
     | AfterLeaveRoomPayload;
 
-export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
+export class SpaceStoreClass extends AsyncStoreWithClient<EmptyObject> {
     // The spaces representing the roots of the various tree-like hierarchies
     private rootSpaces: Room[] = [];
     // Map from room/space ID to set of spaces which list it as a child
@@ -153,6 +152,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
     private _enabledMetaSpaces: MetaSpace[] = [];
     /** Whether the feature flag is set for MSC3946 */
     private _msc3946ProcessDynamicPredecessor: boolean = SettingsStore.getValue("feature_dynamic_room_predecessors");
+    private _storeReadyDeferred = Promise.withResolvers<void>();
 
     public constructor() {
         super(defaultDispatcher, {});
@@ -163,6 +163,28 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
         SettingsStore.monitorSetting("feature_dynamic_room_predecessors", null);
     }
 
+    /**
+     * A promise that resolves when the space store is ready.
+     * This happens after an initial hierarchy of spaces and rooms has been computed.
+     */
+    public get storeReadyPromise(): Promise<void> {
+        return this._storeReadyDeferred.promise;
+    }
+
+    /**
+     * Get the order of meta spaces to display in the space panel.
+     *
+     * This accessor should be removed when the "feature_new_room_list" labs flag is removed.
+     * "People" and "Favourites" will be removed from the "metaSpaceOrder" array and this filter will no longer be needed.
+     * @private
+     */
+    private get metaSpaceOrder(): MetaSpace[] {
+        if (!SettingsStore.getValue("feature_new_room_list")) return metaSpaceOrder;
+
+        // People and Favourites are not shown when the new room list is enabled
+        return metaSpaceOrder.filter((space) => space !== MetaSpace.People && space !== MetaSpace.Favourites);
+    }
+
     public get invitedSpaces(): Room[] {
         return Array.from(this._invitedSpaces);
     }
@@ -247,7 +269,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
 
         if (contextSwitch) {
             // view last selected room from space
-            const roomId = window.localStorage.getItem(getSpaceContextKey(space));
+            const roomId = this.getLastSelectedRoomIdForSpace(space);
 
             // if the space being selected is an invite then always view that invite
             // else if the last viewed room in this space is joined then view that
@@ -297,6 +319,17 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
         }
     }
 
+    /**
+     * Returns the room-id of the last active room in a given space.
+     * This is the room that would be opened when you switch to a given space.
+     * @param space The space you're interested in.
+     * @returns room-id of the room or null if there's no last active room.
+     */
+    public getLastSelectedRoomIdForSpace(space: SpaceKey): string | null {
+        const roomId = window.localStorage.getItem(getSpaceContextKey(space));
+        return roomId;
+    }
+
     private async loadSuggestedRooms(space: Room): Promise<void> {
         const suggestedRooms = await this.fetchSuggestedRooms(space);
         if (this._activeSpace === space.roomId) {
@@ -1165,7 +1198,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
 
         const oldMetaSpaces = this._enabledMetaSpaces;
         const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces");
-        this._enabledMetaSpaces = metaSpaceOrder.filter((k) => enabledMetaSpaces[k]);
+        this._enabledMetaSpaces = this.metaSpaceOrder.filter((k) => enabledMetaSpaces[k]);
 
         this._allRoomsInHome = SettingsStore.getValue("Spaces.allRoomsInHome");
         this.sendUserProperties();
@@ -1188,6 +1221,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
         } else {
             this.switchSpaceIfNeeded();
         }
+        this._storeReadyDeferred.resolve();
     }
 
     private sendUserProperties(): void {
@@ -1279,7 +1313,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
 
                     case "Spaces.enabledMetaSpaces": {
                         const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces");
-                        const enabledMetaSpaces = metaSpaceOrder.filter((k) => newValue[k]);
+                        const enabledMetaSpaces = this.metaSpaceOrder.filter((k) => newValue[k]);
                         if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) {
                             const hadPeopleOrHomeEnabled = this.enabledMetaSpaces.some((s) => {
                                 return s === MetaSpace.Home || s === MetaSpace.People;
diff --git a/src/stores/spaces/flattenSpaceHierarchy.ts b/src/stores/spaces/flattenSpaceHierarchy.ts
index 15d583617d83a67d5d6ee47d0049b8956d5e8d47..ca393d841b37be3cbd716d05526e05c19ee36b20 100644
--- a/src/stores/spaces/flattenSpaceHierarchy.ts
+++ b/src/stores/spaces/flattenSpaceHierarchy.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SpaceKey } from ".";
+import { type SpaceKey } from ".";
 
 export type SpaceEntityMap = Map<SpaceKey, Set<string>>;
 export type SpaceDescendantMap = Map<SpaceKey, Set<SpaceKey>>;
diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts
index b8853d606ef4230eb159ff058064550549ea3296..5a775692c97159e6672a91845816571d503fa935 100644
--- a/src/stores/spaces/index.ts
+++ b/src/stores/spaces/index.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, HierarchyRoom } from "matrix-js-sdk/src/matrix";
+import { type Room, type HierarchyRoom } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../languageHandler";
 
@@ -30,7 +30,7 @@ export enum MetaSpace {
 export const getMetaSpaceName = (spaceKey: MetaSpace, allRoomsInHome = false): string => {
     switch (spaceKey) {
         case MetaSpace.Home:
-            return allRoomsInHome ? _t("common|all_rooms") : _t("common|home");
+            return allRoomsInHome ? _t("common|all_chats") : _t("common|home");
         case MetaSpace.Favourites:
             return _t("common|favourites");
         case MetaSpace.People:
diff --git a/src/stores/widgets/ElementWidgetActions.ts b/src/stores/widgets/ElementWidgetActions.ts
index 8e4142f77d138b0b95558011c7ed0c6837228e35..c7bcfcd8cd535acdf754b410b9b7cacf09441c3b 100644
--- a/src/stores/widgets/ElementWidgetActions.ts
+++ b/src/stores/widgets/ElementWidgetActions.ts
@@ -6,12 +6,13 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { IWidgetApiRequest } from "matrix-widget-api";
+import { type IWidgetApiRequest } from "matrix-widget-api";
 
 export enum ElementWidgetActions {
     // All of these actions are currently specific to Jitsi and Element Call
     JoinCall = "io.element.join",
     HangupCall = "im.vector.hangup",
+    Close = "io.element.close",
     CallParticipants = "io.element.participants",
     StartLiveStream = "im.vector.start_live_stream",
 
diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts
index 40e36473e310a0d693a2d6a8d15df9df185c8eb5..672c1b27b48e4e550984b8ec183e4c211273c7fe 100644
--- a/src/stores/widgets/StopGapWidget.ts
+++ b/src/stores/widgets/StopGapWidget.ts
@@ -6,27 +6,33 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix";
+import {
+    type Room,
+    type MatrixEvent,
+    MatrixEventEvent,
+    type MatrixClient,
+    ClientEvent,
+    RoomStateEvent,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import {
     ClientWidgetApi,
-    IModalWidgetOpenRequest,
-    IRoomEvent,
-    IStickerActionRequest,
-    IStickyActionRequest,
-    ITemplateParams,
-    IWidget,
-    IWidgetApiErrorResponseData,
-    IWidgetApiRequest,
-    IWidgetApiRequestEmptyData,
-    IWidgetData,
+    type IModalWidgetOpenRequest,
+    type IRoomEvent,
+    type IStickerActionRequest,
+    type IStickyActionRequest,
+    type ITemplateParams,
+    type IWidget,
+    type IWidgetApiErrorResponseData,
+    type IWidgetApiRequest,
+    type IWidgetApiRequestEmptyData,
+    type IWidgetData,
     MatrixCapabilities,
     runTemplate,
     Widget,
     WidgetApiFromWidgetAction,
     WidgetKind,
 } from "matrix-widget-api";
-import { Optional } from "matrix-events-sdk";
 import { EventEmitter } from "events";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -37,25 +43,25 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
 import { OwnProfileStore } from "../OwnProfileStore";
 import WidgetUtils from "../../utils/WidgetUtils";
 import { IntegrationManagers } from "../../integrations/IntegrationManagers";
-import SettingsStore from "../../settings/SettingsStore";
 import { WidgetType } from "../../widgets/WidgetType";
 import ActiveWidgetStore from "../ActiveWidgetStore";
 import { objectShallowClone } from "../../utils/objects";
 import defaultDispatcher from "../../dispatcher/dispatcher";
 import { Action } from "../../dispatcher/actions";
-import { ElementWidgetActions, IHangupCallApiRequest, IViewRoomApiRequest } from "./ElementWidgetActions";
+import { ElementWidgetActions, type IHangupCallApiRequest, type IViewRoomApiRequest } from "./ElementWidgetActions";
 import { ModalWidgetStore } from "../ModalWidgetStore";
-import { IApp, isAppWidget } from "../WidgetStore";
-import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
+import { type IApp, isAppWidget } from "../WidgetStore";
+import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
 import { getCustomTheme } from "../../theme";
 import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
 import { ELEMENT_CLIENT_ID } from "../../identifiers";
 import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables";
 import { arrayFastClone } from "../../utils/arrays";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import Modal from "../../Modal";
 import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
 import { SdkContextClass } from "../../contexts/SDKContext";
+import { UPDATE_EVENT } from "../AsyncStore";
 
 // TODO: Destroy all of this code
 
@@ -151,8 +157,12 @@ export class StopGapWidget extends EventEmitter {
     private mockWidget: ElementWidget;
     private scalarToken?: string;
     private roomId?: string;
+    // The room that we're currently allowing the widget to interact with. Only
+    // used for account widgets, which may follow the user to different rooms.
+    private viewedRoomId: string | null = null;
     private kind: WidgetKind;
     private readonly virtual: boolean;
+    private readonly themeWatcher = new ThemeWatcher();
     private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
     // This promise will be called and needs to resolve before the widget will actually become sticky.
     private stickyPromise?: () => Promise<void>;
@@ -177,17 +187,6 @@ export class StopGapWidget extends EventEmitter {
         this.stickyPromise = appTileProps.stickyPromise;
     }
 
-    private get eventListenerRoomId(): Optional<string> {
-        // When widgets are listening to events, we need to make sure they're only
-        // receiving events for the right room. In particular, room widgets get locked
-        // to the room they were added in while account widgets listen to the currently
-        // active room.
-
-        if (this.roomId) return this.roomId;
-
-        return SdkContextClass.instance.roomViewStore.getRoomId();
-    }
-
     public get widgetApi(): ClientWidgetApi | null {
         return this.messaging;
     }
@@ -214,7 +213,7 @@ export class StopGapWidget extends EventEmitter {
             userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
             userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
             clientId: ELEMENT_CLIENT_ID,
-            clientTheme: SettingsStore.getValue("theme"),
+            clientTheme: this.themeWatcher.getEffectiveTheme(),
             clientLanguage: getUserLanguage(),
             deviceId: this.client.getDeviceId() ?? undefined,
             baseUrl: this.client.baseUrl,
@@ -246,6 +245,10 @@ export class StopGapWidget extends EventEmitter {
         return !!this.messaging;
     }
 
+    private onThemeChange = (theme: string): void => {
+        this.messaging?.updateTheme({ name: theme });
+    };
+
     private onOpenModal = async (ev: CustomEvent<IModalWidgetOpenRequest>): Promise<void> => {
         ev.preventDefault();
         if (ModalWidgetStore.instance.canOpenModalWidget()) {
@@ -259,6 +262,17 @@ export class StopGapWidget extends EventEmitter {
             });
         }
     };
+
+    // This listener is only active for account widgets, which may follow the
+    // user to different rooms
+    private onRoomViewStoreUpdate = (): void => {
+        const roomId = SdkContextClass.instance.roomViewStore.getRoomId() ?? null;
+        if (roomId !== this.viewedRoomId) {
+            this.messaging!.setViewedRoomId(roomId);
+            this.viewedRoomId = roomId;
+        }
+    };
+
     /**
      * This starts the messaging for the widget if it is not in the state `started` yet.
      * @param iframe the iframe the widget should use
@@ -278,13 +292,29 @@ export class StopGapWidget extends EventEmitter {
         this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver);
         this.messaging.on("preparing", () => this.emit("preparing"));
         this.messaging.on("error:preparing", (err: unknown) => this.emit("error:preparing", err));
-        this.messaging.on("ready", () => {
+        this.messaging.once("ready", () => {
             WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.roomId, this.messaging!);
             this.emit("ready");
+
+            this.themeWatcher.start();
+            this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
+            // Theme may have changed while messaging was starting
+            this.onThemeChange(this.themeWatcher.getEffectiveTheme());
         });
         this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
         this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);
 
+        // When widgets are listening to events, we need to make sure they're only
+        // receiving events for the right room
+        if (this.roomId === undefined) {
+            // Account widgets listen to the currently active room
+            this.messaging.setViewedRoomId(SdkContextClass.instance.roomViewStore.getRoomId() ?? null);
+            SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
+        } else {
+            // Room widgets get locked to the room they were added in
+            this.messaging.setViewedRoomId(this.roomId);
+        }
+
         // Always attach a handler for ViewRoom, but permission check it internally
         this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent<IViewRoomApiRequest>) => {
             ev.preventDefault(); // stop the widget API from auto-rejecting this
@@ -329,6 +359,7 @@ export class StopGapWidget extends EventEmitter {
         // Attach listeners for feeding events - the underlying widget classes handle permissions for us
         this.client.on(ClientEvent.Event, this.onEvent);
         this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
+        this.client.on(RoomStateEvent.Events, this.onStateUpdate);
         this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
 
         this.messaging.on(
@@ -457,8 +488,11 @@ export class StopGapWidget extends EventEmitter {
         WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId);
         this.messaging = null;
 
+        SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
+
         this.client.off(ClientEvent.Event, this.onEvent);
         this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted);
+        this.client.off(RoomStateEvent.Events, this.onStateUpdate);
         this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
     }
 
@@ -471,6 +505,14 @@ export class StopGapWidget extends EventEmitter {
         this.feedEvent(ev);
     };
 
+    private onStateUpdate = (ev: MatrixEvent): void => {
+        if (this.messaging === null) return;
+        const raw = ev.getEffectiveEvent();
+        this.messaging.feedStateUpdate(raw as IRoomEvent).catch((e) => {
+            logger.error("Error sending state update to widget: ", e);
+        });
+    };
+
     private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
         await this.client.decryptEventIfNeeded(ev);
         if (ev.isDecryptionFailure()) return;
@@ -570,7 +612,7 @@ export class StopGapWidget extends EventEmitter {
                 this.eventsToFeed.add(ev);
             } else {
                 const raw = ev.getEffectiveEvent();
-                this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => {
+                this.messaging.feedEvent(raw as IRoomEvent).catch((e) => {
                     logger.error("Error sending event to widget: ", e);
                 });
             }
diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts
index fa5a43f248c975d616c32694b44418a1ca6baad4..d4c0f8930c347d3c982d1fa4aec1481a7c07cd49 100644
--- a/src/stores/widgets/StopGapWidgetDriver.ts
+++ b/src/stores/widgets/StopGapWidgetDriver.ts
@@ -7,50 +7,47 @@
  */
 
 import {
-    Capability,
+    type Capability,
     EventDirection,
-    IOpenIDCredentials,
-    IOpenIDUpdate,
-    ISendDelayedEventDetails,
-    ISendEventDetails,
-    ITurnServer,
-    IReadEventRelationsResult,
-    IRoomEvent,
+    type IOpenIDCredentials,
+    type IOpenIDUpdate,
+    type ISendDelayedEventDetails,
+    type ISendEventDetails,
+    type ITurnServer,
+    type IReadEventRelationsResult,
+    type IRoomEvent,
     MatrixCapabilities,
     OpenIDRequestState,
-    SimpleObservable,
-    Symbols,
-    Widget,
+    type SimpleObservable,
+    type Widget,
     WidgetDriver,
     WidgetEventCapability,
     WidgetKind,
-    IWidgetApiErrorResponseDataDetails,
-    ISearchUserDirectoryResult,
-    IGetMediaConfigResult,
-    UpdateDelayedEventAction,
+    type IWidgetApiErrorResponseDataDetails,
+    type ISearchUserDirectoryResult,
+    type IGetMediaConfigResult,
+    type UpdateDelayedEventAction,
 } from "matrix-widget-api";
 import {
     ClientEvent,
-    ITurnServer as IClientTurnServer,
+    type ITurnServer as IClientTurnServer,
     EventType,
-    IContent,
+    type IContent,
     MatrixError,
-    MatrixEvent,
-    Room,
+    type MatrixEvent,
     Direction,
     THREAD_RELATION_TYPE,
-    SendDelayedEventResponse,
-    StateEvents,
-    TimelineEvents,
+    type SendDelayedEventResponse,
+    type StateEvents,
+    type TimelineEvents,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import {
-    ApprovalOpts,
-    CapabilitiesOpts,
+    type ApprovalOpts,
+    type CapabilitiesOpts,
     WidgetLifecycle,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
 
-import SdkConfig, { DEFAULTS } from "../../SdkConfig";
 import { iterableDiff, iterableIntersection } from "../../utils/iterables";
 import { MatrixClientPeg } from "../../MatrixClientPeg";
 import Modal from "../../Modal";
@@ -67,7 +64,7 @@ import { navigateToPermalink } from "../../utils/permalinks/navigator";
 import { SdkContextClass } from "../../contexts/SDKContext";
 import { ModuleRunner } from "../../modules/ModuleRunner";
 import SettingsStore from "../../settings/SettingsStore";
-import { Media } from "../../customisations/Media";
+import { mediaFromMxc } from "../../customisations/Media";
 
 // TODO: Purge this from the universe
 
@@ -118,10 +115,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
             // Auto-approve the legacy visibility capability. We send it regardless of capability.
             // Widgets don't technically need to request this capability, but Scalar still does.
             this.allowedCapabilities.add("visibility");
-        } else if (
-            virtual &&
-            new URL(SdkConfig.get("element_call").url ?? DEFAULTS.element_call.url!).origin === this.forWidget.origin
-        ) {
+        } else if (virtual && WidgetType.CALL.matches(this.forWidget.type) && forWidgetKind === WidgetKind.Room) {
             // This is a trusted Element Call widget that we control
             this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen);
             this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers);
@@ -469,70 +463,69 @@ export class StopGapWidgetDriver extends WidgetDriver {
         }
     }
 
-    private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
-        const client = MatrixClientPeg.get();
-        if (!client) throw new Error("Not attached to a client");
-
-        const targetRooms = roomIds
-            ? roomIds.includes(Symbols.AnyRoom)
-                ? client.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
-                : roomIds.map((r) => client.getRoom(r))
-            : [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()!)];
-        return targetRooms.filter((r) => !!r) as Room[];
-    }
-
-    public async readRoomEvents(
+    /**
+     * Reads all events of the given type, and optionally `msgtype` (if applicable/defined),
+     * the user has access to. The widget API will have already verified that the widget is
+     * capable of receiving the events. Less events than the limit are allowed to be returned,
+     * but not more.
+     * @param roomId The ID of the room to look within.
+     * @param eventType The event type to be read.
+     * @param msgtype The msgtype of the events to be read, if applicable/defined.
+     * @param stateKey The state key of the events to be read, if applicable/defined.
+     * @param limit The maximum number of events to retrieve. Will be zero to denote "as many as
+     * possible".
+     * @param since When null, retrieves the number of events specified by the "limit" parameter.
+     * Otherwise, the event ID at which only subsequent events will be returned, as many as specified
+     * in "limit".
+     * @returns {Promise<IRoomEvent[]>} Resolves to the room events, or an empty array.
+     */
+    public async readRoomTimeline(
+        roomId: string,
         eventType: string,
         msgtype: string | undefined,
-        limitPerRoom: number,
-        roomIds?: (string | Symbols.AnyRoom)[],
+        stateKey: string | undefined,
+        limit: number,
+        since: string | undefined,
     ): Promise<IRoomEvent[]> {
-        limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
-
-        const rooms = this.pickRooms(roomIds);
-        const allResults: IRoomEvent[] = [];
-        for (const room of rooms) {
-            const results: MatrixEvent[] = [];
-            const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
-            for (let i = events.length - 1; i > 0; i--) {
-                if (results.length >= limitPerRoom) break;
-
-                const ev = events[i];
-                if (ev.getType() !== eventType || ev.isState()) continue;
-                if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
-                results.push(ev);
-            }
-
-            results.forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
+        limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
+
+        const room = MatrixClientPeg.safeGet().getRoom(roomId);
+        if (room === null) return [];
+        const results: MatrixEvent[] = [];
+        const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
+        for (let i = events.length - 1; i >= 0; i--) {
+            const ev = events[i];
+            if (results.length >= limit) break;
+            if (since !== undefined && ev.getId() === since) break;
+
+            if (ev.getType() !== eventType) continue;
+            if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
+            if (stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
+            results.push(ev);
         }
-        return allResults;
-    }
 
-    public async readStateEvents(
-        eventType: string,
-        stateKey: string | undefined,
-        limitPerRoom: number,
-        roomIds?: (string | Symbols.AnyRoom)[],
-    ): Promise<IRoomEvent[]> {
-        limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
-
-        const rooms = this.pickRooms(roomIds);
-        const allResults: IRoomEvent[] = [];
-        for (const room of rooms) {
-            const results: MatrixEvent[] = [];
-            const state = room.currentState.events.get(eventType);
-            if (state) {
-                if (stateKey === "" || !!stateKey) {
-                    const forKey = state.get(stateKey);
-                    if (forKey) results.push(forKey);
-                } else {
-                    results.push(...Array.from(state.values()));
-                }
-            }
+        return results.map((e) => e.getEffectiveEvent() as IRoomEvent);
+    }
 
-            results.slice(0, limitPerRoom).forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
-        }
-        return allResults;
+    /**
+     * Reads the current values of all matching room state entries.
+     * @param roomId The ID of the room.
+     * @param eventType The event type of the entries to be read.
+     * @param stateKey The state key of the entry to be read. If undefined,
+     * all room state entries with a matching event type should be returned.
+     * @returns {Promise<IRoomEvent[]>} Resolves to the events representing the
+     * current values of the room state entries.
+     */
+    public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise<IRoomEvent[]> {
+        const room = MatrixClientPeg.safeGet().getRoom(roomId);
+        if (room === null) return [];
+        const state = room.getLiveTimeline().getState(Direction.Forward);
+        if (state === undefined) return [];
+
+        if (stateKey === undefined)
+            return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
+        const event = state.getStateEvents(eventType, stateKey);
+        return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
     }
 
     public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
@@ -564,19 +557,17 @@ export class StopGapWidgetDriver extends WidgetDriver {
 
         observer.update({ state: OpenIDRequestState.PendingUserConfirmation });
 
-        Modal.createDialog(WidgetOpenIDPermissionsDialog, {
+        const { finished } = Modal.createDialog(WidgetOpenIDPermissionsDialog, {
             widget: this.forWidget,
             widgetKind: this.forWidgetKind,
             inRoomId: this.inRoomId,
-
-            onFinished: async (confirm): Promise<void> => {
-                if (!confirm) {
-                    return observer.update({ state: OpenIDRequestState.Blocked });
-                }
-
-                return observer.update({ state: OpenIDRequestState.Allowed, token: await getToken() });
-            },
         });
+        const [confirm] = await finished;
+        if (!confirm) {
+            observer.update({ state: OpenIDRequestState.Blocked });
+        } else {
+            observer.update({ state: OpenIDRequestState.Allowed, token: await getToken() });
+        }
     }
 
     public async navigate(uri: string): Promise<void> {
@@ -687,12 +678,23 @@ export class StopGapWidgetDriver extends WidgetDriver {
      */
     public async downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> {
         const client = MatrixClientPeg.safeGet();
-        const media = new Media({ mxc: contentUri }, client);
+        const media = mediaFromMxc(contentUri, client);
         const response = await media.downloadSource();
         const blob = await response.blob();
         return { file: blob };
     }
 
+    /**
+     * Gets the IDs of all joined or invited rooms currently known to the
+     * client.
+     * @returns The room IDs.
+     */
+    public getKnownRooms(): string[] {
+        return MatrixClientPeg.safeGet()
+            .getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
+            .map((r) => r.roomId);
+    }
+
     /**
      * Expresses a {@link MatrixError} as a JSON payload
      * for use by Widget API error responses.
diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts
index 5e587c548716b613a8d9b3d2df7fbdde3dfbab60..eaea0957b0f7f3495d1b806d75e010947dd0f6cf 100644
--- a/src/stores/widgets/WidgetLayoutStore.ts
+++ b/src/stores/widgets/WidgetLayoutStore.ts
@@ -6,13 +6,13 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Room, RoomStateEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type Room, RoomStateEvent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
 import { MapWithDefault, recursiveMapToObject } from "matrix-js-sdk/src/utils";
-import { IWidget } from "matrix-widget-api";
+import { type IWidget } from "matrix-widget-api";
 
 import SettingsStore from "../../settings/SettingsStore";
-import WidgetStore, { IApp } from "../WidgetStore";
+import WidgetStore, { type IApp } from "../WidgetStore";
 import { WidgetType } from "../../widgets/WidgetType";
 import { clamp, defaultNumber, sum } from "../../utils/numbers";
 import defaultDispatcher from "../../dispatcher/dispatcher";
@@ -20,7 +20,13 @@ import { ReadyWatchingStore } from "../ReadyWatchingStore";
 import { SettingLevel } from "../../settings/SettingLevel";
 import { arrayFastClone } from "../../utils/arrays";
 import { UPDATE_EVENT } from "../AsyncStore";
-import { Container, IStoredLayout, ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE, IWidgetLayouts } from "./types";
+import {
+    Container,
+    type IStoredLayout,
+    type ILayoutStateEvent,
+    WIDGET_LAYOUT_EVENT_TYPE,
+    type IWidgetLayouts,
+} from "./types";
 
 export type { IStoredLayout, ILayoutStateEvent };
 export { Container, WIDGET_LAYOUT_EVENT_TYPE };
diff --git a/src/stores/widgets/WidgetMessagingStore.ts b/src/stores/widgets/WidgetMessagingStore.ts
index f73fd15c5130b26a1e9df1ccfd6b98f4bcbf5147..2dcf8c4fdc69e9407484ad402a526a9d529341ec 100644
--- a/src/stores/widgets/WidgetMessagingStore.ts
+++ b/src/stores/widgets/WidgetMessagingStore.ts
@@ -6,11 +6,12 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { ClientWidgetApi, Widget } from "matrix-widget-api";
+import { type ClientWidgetApi, type Widget } from "matrix-widget-api";
+import { type EmptyObject } from "matrix-js-sdk/src/matrix";
 
 import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { EnhancedMap } from "../../utils/maps";
 import WidgetUtils from "../../utils/WidgetUtils";
 
@@ -24,7 +25,7 @@ export enum WidgetMessagingStoreEvent {
  * going to be merged with a more complete WidgetStore, but for now it's
  * easiest to split this into a single place.
  */
-export class WidgetMessagingStore extends AsyncStoreWithClient<{}> {
+export class WidgetMessagingStore extends AsyncStoreWithClient<EmptyObject> {
     private static readonly internalInstance = (() => {
         const instance = new WidgetMessagingStore();
         instance.start();
@@ -71,8 +72,11 @@ export class WidgetMessagingStore extends AsyncStoreWithClient<{}> {
      * @param {string} widgetUid The widget UID.
      */
     public stopMessagingByUid(widgetUid: string): void {
-        this.widgetMap.remove(widgetUid)?.stop();
-        this.emit(WidgetMessagingStoreEvent.StopMessaging, widgetUid);
+        const messaging = this.widgetMap.remove(widgetUid);
+        if (messaging !== undefined) {
+            messaging.stop();
+            this.emit(WidgetMessagingStoreEvent.StopMessaging, widgetUid);
+        }
     }
 
     /**
diff --git a/src/stores/widgets/WidgetPermissionStore.ts b/src/stores/widgets/WidgetPermissionStore.ts
index 482704b65f3df22752c92dd807fc06f7118893d5..75f491fcf5a450bcbc5ca8397a398dc07615c115 100644
--- a/src/stores/widgets/WidgetPermissionStore.ts
+++ b/src/stores/widgets/WidgetPermissionStore.ts
@@ -6,11 +6,11 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { Widget, WidgetKind } from "matrix-widget-api";
+import { type Widget, WidgetKind } from "matrix-widget-api";
 
 import SettingsStore from "../../settings/SettingsStore";
 import { SettingLevel } from "../../settings/SettingLevel";
-import { SdkContextClass } from "../../contexts/SDKContext";
+import { type SdkContextClass } from "../../contexts/SDKContext";
 
 export enum OIDCState {
     Allowed, // user has set the remembered value as allowed
diff --git a/src/theme.ts b/src/theme.ts
index de384021aaac8f3e16c8f5193a1dc6a7e77b76c2..bfc471544c2eb8615beb7c4d6c05c4865ca4ab8f 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -129,7 +129,7 @@ function clearCustomTheme(): void {
     // remove all css variables, we assume these are there because of the custom theme
     const inlineStyleProps = Object.values(document.body.style);
     for (const prop of inlineStyleProps) {
-        if (prop.startsWith("--")) {
+        if (typeof prop === "string" && prop.startsWith("--")) {
             document.body.style.removeProperty(prop);
         }
     }
@@ -206,16 +206,49 @@ function generateCustomCompoundCSS(theme: CompoundTheme): string {
     return `@layer compound.custom { :root, [class*="cpd-theme-"] { ${properties.join(" ")} } }`;
 }
 
+/**
+ * Normalizes the hex colour to 8 characters (including alpha)
+ * @param hexColor the hex colour to normalize
+ */
+function normalizeHexColour(hexColor: string): string {
+    switch (hexColor.length) {
+        case 4:
+        case 5:
+            // Short RGB or RGBA hex
+            return `#${hexColor
+                .slice(1)
+                .split("")
+                .map((c) => c + c)
+                .join("")}`;
+        case 7:
+            // Long RGB hex
+            return `${hexColor}ff`;
+        default:
+            return hexColor;
+    }
+}
+
+function setHexAlpha(normalizedHexColor: string, alpha: number): string {
+    return normalizeHexColour(normalizedHexColor).slice(0, 7) + Math.round(alpha).toString(16).padStart(2, "0");
+}
+
+function parseAlpha(normalizedHexColor: string): number {
+    return parseInt(normalizedHexColor.slice(7), 16);
+}
+
 function setCustomThemeVars(customTheme: CustomTheme): void {
     const { style } = document.body;
 
     function setCSSColorVariable(name: string, hexColor: string, doPct = true): void {
         style.setProperty(`--${name}`, hexColor);
+        const normalizedHexColor = normalizeHexColour(hexColor);
+        const baseAlpha = parseAlpha(normalizedHexColor);
+
         if (doPct) {
-            // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50%
-            style.setProperty(`--${name}-0pct`, hexColor + "00");
-            style.setProperty(`--${name}-15pct`, hexColor + "26");
-            style.setProperty(`--${name}-50pct`, hexColor + "7F");
+            // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50% (relative to base alpha channel)
+            style.setProperty(`--${name}-0pct`, setHexAlpha(normalizedHexColor, 0));
+            style.setProperty(`--${name}-15pct`, setHexAlpha(normalizedHexColor, baseAlpha * 0.15));
+            style.setProperty(`--${name}-50pct`, setHexAlpha(normalizedHexColor, baseAlpha * 0.5));
         }
     }
 
diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx
index 8ac0c4710ae82dc21e5adce023609a53974b9f7d..351ccdf4b2cd7efc9e0aaa97621abf9b2e7ec18f 100644
--- a/src/toasts/AnalyticsToast.tsx
+++ b/src/toasts/AnalyticsToast.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 
 import { _t } from "../languageHandler";
 import SdkConfig from "../SdkConfig";
@@ -34,34 +34,36 @@ const onReject = (): void => {
 };
 
 const onLearnMoreNoOptIn = (): void => {
-    showAnalyticsLearnMoreDialog({
-        onFinished: (buttonClicked?: ButtonClicked) => {
-            if (buttonClicked === ButtonClicked.Primary) {
-                // user clicked "Enable"
-                onAccept();
-            }
-            // otherwise, the user either clicked "Cancel", or closed the dialog without making a choice,
-            // leave the toast open
-        },
+    const { finished } = showAnalyticsLearnMoreDialog({
         primaryButton: _t("action|enable"),
     });
+
+    finished.then(([buttonClicked]) => {
+        if (buttonClicked === ButtonClicked.Primary) {
+            // user clicked "Enable"
+            onAccept();
+        }
+        // otherwise, the user either clicked "Cancel", or closed the dialog without making a choice,
+        // leave the toast open
+    });
 };
 
 const onLearnMorePreviouslyOptedIn = (): void => {
-    showAnalyticsLearnMoreDialog({
-        onFinished: (buttonClicked?: ButtonClicked) => {
-            if (buttonClicked === ButtonClicked.Primary) {
-                // user clicked "That's fine"
-                onAccept();
-            } else if (buttonClicked === ButtonClicked.Cancel) {
-                // user clicked "Stop"
-                onReject();
-            }
-            // otherwise, the user closed the dialog without making a choice, leave the toast open
-        },
+    const { finished } = showAnalyticsLearnMoreDialog({
         primaryButton: _t("analytics|accept_button"),
         cancelButton: _t("action|stop"),
     });
+
+    finished.then(([buttonClicked]) => {
+        if (buttonClicked === ButtonClicked.Primary) {
+            // user clicked "That's fine"
+            onAccept();
+        } else if (buttonClicked === ButtonClicked.Cancel) {
+            // user clicked "Stop"
+            onReject();
+        }
+        // otherwise, the user closed the dialog without making a choice, leave the toast open
+    });
 };
 
 const TOAST_KEY = "analytics";
diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx
index 197c70186c83df759e69018dc53c5f6e1d2926b8..9f393beb06d6753403436e9d885282cc49c36da6 100644
--- a/src/toasts/IncomingCallToast.tsx
+++ b/src/toasts/IncomingCallToast.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { useCallback, useEffect, useState } from "react";
-import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type JSX, useCallback, useEffect, useState } from "react";
+import { type MatrixEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { Button, Tooltip, TooltipProvider } from "@vector-im/compound-web";
 import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
 
@@ -15,7 +15,7 @@ import { _t } from "../languageHandler";
 import RoomAvatar from "../components/views/avatars/RoomAvatar";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../dispatcher/actions";
 import ToastStore from "../stores/ToastStore";
 import {
@@ -24,10 +24,10 @@ import {
     LiveContentType,
 } from "../components/views/rooms/LiveContentSummary";
 import { useCall, useJoinCallButtonDisabledTooltip } from "../hooks/useCall";
-import AccessibleButton, { ButtonEvent } from "../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton";
 import { useDispatcher } from "../hooks/useDispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
-import { Call, CallEvent } from "../models/Call";
+import { type ActionPayload } from "../dispatcher/payloads";
+import { type Call, CallEvent } from "../models/Call";
 import LegacyCallHandler, { AudioID } from "../LegacyCallHandler";
 import { useEventEmitter } from "../hooks/useEventEmitter";
 import { CallStore, CallStoreEvent } from "../stores/CallStore";
diff --git a/src/toasts/IncomingLegacyCallToast.tsx b/src/toasts/IncomingLegacyCallToast.tsx
index a8e9668198040dcae98c46fbb77b98e357ef7415..18c3f9f5068481a60d59fe3d3b07cf49aac7bc16 100644
--- a/src/toasts/IncomingLegacyCallToast.tsx
+++ b/src/toasts/IncomingLegacyCallToast.tsx
@@ -10,14 +10,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { CallType, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import classNames from "classnames";
 
 import LegacyCallHandler, { LegacyCallHandlerEvent } from "../LegacyCallHandler";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import { _t } from "../languageHandler";
 import RoomAvatar from "../components/views/avatars/RoomAvatar";
-import AccessibleButton, { ButtonEvent } from "../components/views/elements/AccessibleButton";
+import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton";
 
 export const getIncomingLegacyCallToastKey = (callId: string): string => `call_${callId}`;
 
diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts
index 3b8e85eb44497c0f0f83039f26a80a5dc8bfd354..802da5b8950e8848973b0bd539135aa8a0d5f2f2 100644
--- a/src/toasts/SetupEncryptionToast.ts
+++ b/src/toasts/SetupEncryptionToast.ts
@@ -6,16 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
+import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
+import { type ComponentType } from "react";
+
+import type React from "react";
 import Modal from "../Modal";
 import { _t } from "../languageHandler";
 import DeviceListener from "../DeviceListener";
 import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEncryptionDialog";
-import { accessSecretStorage } from "../SecurityManager";
+import { AccessCancelledError, accessSecretStorage } from "../SecurityManager";
 import ToastStore from "../stores/ToastStore";
 import GenericToast from "../components/views/toasts/GenericToast";
 import { ModuleRunner } from "../modules/ModuleRunner";
 import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
 import Spinner from "../components/views/elements/Spinner";
+import { type OpenToTabPayload } from "../dispatcher/payloads/OpenToTabPayload";
+import { Action } from "../dispatcher/actions";
+import { UserTab } from "../components/views/dialogs/UserTab";
+import defaultDispatcher from "../dispatcher/dispatcher";
+import ConfirmKeyStorageOffDialog from "../components/views/dialogs/ConfirmKeyStorageOffDialog";
 
 const TOAST_KEY = "setupencryption";
 
@@ -29,6 +38,8 @@ const getTitle = (kind: Kind): string => {
             return _t("encryption|verify_toast_title");
         case Kind.KEY_STORAGE_OUT_OF_SYNC:
             return _t("encryption|key_storage_out_of_sync");
+        case Kind.TURN_ON_KEY_STORAGE:
+            return _t("encryption|turn_on_key_storage");
     }
 };
 
@@ -41,6 +52,8 @@ const getIcon = (kind: Kind): string | undefined => {
         case Kind.VERIFY_THIS_SESSION:
         case Kind.KEY_STORAGE_OUT_OF_SYNC:
             return "verification_warning";
+        case Kind.TURN_ON_KEY_STORAGE:
+            return "key_storage";
     }
 };
 
@@ -54,6 +67,21 @@ const getSetupCaption = (kind: Kind): string => {
             return _t("action|verify");
         case Kind.KEY_STORAGE_OUT_OF_SYNC:
             return _t("encryption|enter_recovery_key");
+        case Kind.TURN_ON_KEY_STORAGE:
+            return _t("action|continue");
+    }
+};
+
+/**
+ * Get the icon to show on the primary button.
+ * @param kind
+ */
+const getPrimaryButtonIcon = (kind: Kind): ComponentType<React.SVGAttributes<SVGElement>> | undefined => {
+    switch (kind) {
+        case Kind.KEY_STORAGE_OUT_OF_SYNC:
+            return KeyIcon;
+        default:
+            return;
     }
 };
 
@@ -66,6 +94,8 @@ const getSecondaryButtonLabel = (kind: Kind): string => {
             return _t("encryption|verification|unverified_sessions_toast_reject");
         case Kind.KEY_STORAGE_OUT_OF_SYNC:
             return _t("encryption|forgot_recovery_key");
+        case Kind.TURN_ON_KEY_STORAGE:
+            return _t("action|dismiss");
     }
 };
 
@@ -79,6 +109,8 @@ const getDescription = (kind: Kind): string => {
             return _t("encryption|verify_toast_description");
         case Kind.KEY_STORAGE_OUT_OF_SYNC:
             return _t("encryption|key_storage_out_of_sync_description");
+        case Kind.TURN_ON_KEY_STORAGE:
+            return _t("encryption|turn_on_key_storage_description");
     }
 };
 
@@ -102,12 +134,12 @@ export enum Kind {
      * Prompt the user to enter their recovery key
      */
     KEY_STORAGE_OUT_OF_SYNC = "key_storage_out_of_sync",
+    /**
+     * Prompt the user to turn on key storage
+     */
+    TURN_ON_KEY_STORAGE = "turn_on_key_storage",
 }
 
-const onReject = (): void => {
-    DeviceListener.sharedInstance().dismissEncryptionSetup();
-};
-
 /**
  * Show a toast prompting the user for some action related to setting up their encryption.
  *
@@ -123,9 +155,16 @@ export const showToast = (kind: Kind): void => {
         return;
     }
 
-    const onAccept = async (): Promise<void> => {
+    const onPrimaryClick = async (): Promise<void> => {
         if (kind === Kind.VERIFY_THIS_SESSION) {
             Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true);
+        } else if (kind == Kind.TURN_ON_KEY_STORAGE) {
+            // Open the user settings dialog to the encryption tab
+            const payload: OpenToTabPayload = {
+                action: Action.ViewUserSettings,
+                initialTabId: UserTab.Encryption,
+            };
+            defaultDispatcher.dispatch(payload);
         } else {
             const modal = Modal.createDialog(
                 Spinner,
@@ -136,12 +175,58 @@ export const showToast = (kind: Kind): void => {
             );
             try {
                 await accessSecretStorage();
+            } catch (error) {
+                onAccessSecretStorageFailed(error as Error);
             } finally {
                 modal.close();
             }
         }
     };
 
+    const onSecondaryClick = async (): Promise<void> => {
+        if (kind === Kind.KEY_STORAGE_OUT_OF_SYNC) {
+            // Open the user settings dialog to the encryption tab and start the flow to reset encryption
+            const payload: OpenToTabPayload = {
+                action: Action.ViewUserSettings,
+                initialTabId: UserTab.Encryption,
+                props: { initialEncryptionState: "reset_identity_forgot" },
+            };
+            defaultDispatcher.dispatch(payload);
+        } else if (kind === Kind.TURN_ON_KEY_STORAGE) {
+            // The user clicked "Dismiss": offer them "Are you sure?"
+            const modal = Modal.createDialog(ConfirmKeyStorageOffDialog, undefined, "mx_ConfirmKeyStorageOffDialog");
+            const [dismissed] = await modal.finished;
+            if (dismissed) {
+                const deviceListener = DeviceListener.sharedInstance();
+                await deviceListener.recordKeyBackupDisabled();
+                deviceListener.dismissEncryptionSetup();
+            }
+        } else {
+            DeviceListener.sharedInstance().dismissEncryptionSetup();
+        }
+    };
+
+    /**
+     * We tried to accessSecretStorage, which triggered us to ask for the
+     * recovery key, but this failed. If the user just gave up, that is fine,
+     * but if not, that means downloading encryption info from 4S did not fix
+     * the problem we identified. Presumably, something is wrong with what
+     * they have in 4S: we tell them to reset their identity.
+     */
+    const onAccessSecretStorageFailed = (error: Error): void => {
+        if (error instanceof AccessCancelledError) {
+            // The user cancelled the dialog - just allow it to close
+        } else {
+            // A real error happened - jump to the reset identity tab
+            const payload: OpenToTabPayload = {
+                action: Action.ViewUserSettings,
+                initialTabId: UserTab.Encryption,
+                props: { initialEncryptionState: "reset_identity_sync_failed" },
+            };
+            defaultDispatcher.dispatch(payload);
+        }
+    };
+
     ToastStore.sharedInstance().addOrReplaceToast({
         key: TOAST_KEY,
         title: getTitle(kind),
@@ -149,9 +234,10 @@ export const showToast = (kind: Kind): void => {
         props: {
             description: getDescription(kind),
             primaryLabel: getSetupCaption(kind),
-            onPrimaryClick: onAccept,
+            PrimaryIcon: getPrimaryButtonIcon(kind),
+            onPrimaryClick,
             secondaryLabel: getSecondaryButtonLabel(kind),
-            onSecondaryClick: onReject,
+            onSecondaryClick,
             overrideWidth: kind === Kind.KEY_STORAGE_OUT_OF_SYNC ? "366px" : undefined,
         },
         component: GenericToast,
diff --git a/src/toasts/UpdateToast.tsx b/src/toasts/UpdateToast.tsx
index 0abc95c066ec47f8695ba0e94b5481d315696e3c..4ced8144c18f680366dfd807460f7a8a7d81d329 100644
--- a/src/toasts/UpdateToast.tsx
+++ b/src/toasts/UpdateToast.tsx
@@ -32,27 +32,27 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
     let acceptLabel = _t("update|see_changes_button");
     if (releaseNotes) {
         onAccept = () => {
-            Modal.createDialog(QuestionDialog, {
+            const { finished } = Modal.createDialog(QuestionDialog, {
                 title: _t("update|release_notes_toast_title"),
                 description: <pre>{releaseNotes}</pre>,
                 button: _t("action|update"),
-                onFinished: (update) => {
-                    if (update && PlatformPeg.get()) {
-                        PlatformPeg.get()!.installUpdate();
-                    }
-                },
+            });
+            finished.then(([update]) => {
+                if (update && PlatformPeg.get()) {
+                    PlatformPeg.get()!.installUpdate();
+                }
             });
         };
     } else if (checkVersion(version) && checkVersion(newVersion)) {
         onAccept = () => {
-            Modal.createDialog(ChangelogDialog, {
+            const { finished } = Modal.createDialog(ChangelogDialog, {
                 version,
                 newVersion,
-                onFinished: (update) => {
-                    if (update && PlatformPeg.get()) {
-                        PlatformPeg.get()!.installUpdate();
-                    }
-                },
+            });
+            finished.then(([update]) => {
+                if (update && PlatformPeg.get()) {
+                    PlatformPeg.get()!.installUpdate();
+                }
             });
         };
     } else {
diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx
index efc8f285cfd21f60d3d3a114073c62dc3403c1ec..e5359c48f83cee1e263abd056af30ad52b108ceb 100644
--- a/src/utils/AutoDiscoveryUtils.tsx
+++ b/src/utils/AutoDiscoveryUtils.tsx
@@ -6,22 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import {
     AutoDiscovery,
     AutoDiscoveryError,
-    ClientConfig,
-    discoverAndValidateOIDCIssuerWellKnown,
-    IClientWellKnown,
+    type ClientConfig,
+    type IClientWellKnown,
     MatrixClient,
     MatrixError,
-    OidcClientConfig,
+    type OidcClientConfig,
 } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { _t, _td, TranslationKey, UserFriendlyError } from "../languageHandler";
+import { _t, _td, type TranslationKey, UserFriendlyError } from "../languageHandler";
 import SdkConfig from "../SdkConfig";
-import { ValidatedServerConfig } from "./ValidatedServerConfig";
+import { type ValidatedServerConfig } from "./ValidatedServerConfig";
 
 const LIVELINESS_DISCOVERY_ERRORS: AutoDiscoveryError[] = [
     AutoDiscovery.ERROR_INVALID_HOMESERVER,
@@ -293,8 +292,7 @@ export default class AutoDiscoveryUtils {
         let delegatedAuthenticationError: Error | undefined;
         try {
             const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl });
-            const { issuer } = await tempClient.getAuthIssuer();
-            delegatedAuthentication = await discoverAndValidateOIDCIssuerWellKnown(issuer);
+            delegatedAuthentication = await tempClient.getAuthMetadata();
         } catch (e) {
             if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") {
                 // 404 M_UNRECOGNIZED means the server does not support OIDC
diff --git a/src/utils/BrowserWorkarounds.ts b/src/utils/BrowserWorkarounds.ts
index 8a83d67d037ec5e212d43d064cff63a05d982349..de10e90eec66b0db2eda20bbf4a9f1fa44dd3699 100644
--- a/src/utils/BrowserWorkarounds.ts
+++ b/src/utils/BrowserWorkarounds.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MouseEvent } from "react";
+import { type MouseEvent } from "react";
 
 export function chromeFileInputFix(event: MouseEvent<HTMLInputElement>): void {
     // Workaround for Chromium Bug
diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts
index 77b4c6554594cc1d5665b1d4d17c63cc9f16fc3b..df1cc2d3641820530aaea6f041b17e4a3b34e4d7 100644
--- a/src/utils/DMRoomMap.ts
+++ b/src/utils/DMRoomMap.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { uniq } from "lodash";
-import { Room, MatrixEvent, EventType, ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Room, type MatrixEvent, EventType, ClientEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Optional } from "matrix-events-sdk";
+import { type Optional } from "matrix-events-sdk";
 
 import { filterValidMDirect } from "./dm/filterValidMDirect";
 
diff --git a/src/utils/DecryptFile.ts b/src/utils/DecryptFile.ts
index 25ea5838196c37011ccdc817072d7a46216c17e9..baf2acafc1d7486671f85dbc8e27e2106f64a230 100644
--- a/src/utils/DecryptFile.ts
+++ b/src/utils/DecryptFile.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 // Pull in the encryption lib so that we can decrypt attachments.
 import encrypt from "matrix-encrypt-attachment";
 import { parseErrorResponse } from "matrix-js-sdk/src/matrix";
-import { EncryptedFile, MediaEventInfo } from "matrix-js-sdk/src/types";
+import { type EncryptedFile, type MediaEventInfo } from "matrix-js-sdk/src/types";
 
 import { mediaFromContent } from "../customisations/Media";
 import { getBlobSafeMimeType } from "./blobs";
diff --git a/src/utils/DialogOpener.ts b/src/utils/DialogOpener.ts
index c7b2212a21bcd0232d4efa53bb07178f7cb4a27f..9eadf63adf4f1a7ffa0807e4683674ffac9b6043 100644
--- a/src/utils/DialogOpener.ts
+++ b/src/utils/DialogOpener.ts
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import classnames from "classnames";
-import { ComponentProps } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type ComponentProps } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { ActionPayload } from "../dispatcher/payloads";
+import { type ActionPayload } from "../dispatcher/payloads";
 import Modal from "../Modal";
 import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog";
 import ForwardDialog from "../components/views/dialogs/ForwardDialog";
@@ -21,7 +21,7 @@ import SpacePreferencesDialog from "../components/views/dialogs/SpacePreferences
 import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
 import InviteDialog from "../components/views/dialogs/InviteDialog";
 import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
-import { ButtonEvent } from "../components/views/elements/AccessibleButton";
+import { type ButtonEvent } from "../components/views/elements/AccessibleButton";
 import PosthogTrackers from "../PosthogTrackers";
 import { showAddExistingSubspace, showCreateNewRoom } from "./space";
 import { SdkContextClass } from "../contexts/SDKContext";
@@ -119,7 +119,7 @@ export class DialogOpener {
                 break;
             case Action.OpenAddToExistingSpaceDialog: {
                 const space = payload.space;
-                Modal.createDialog(
+                const { finished } = Modal.createDialog(
                     AddExistingToSpaceDialog,
                     {
                         onCreateRoomClick: (ev: ButtonEvent) => {
@@ -128,14 +128,14 @@ export class DialogOpener {
                         },
                         onAddSubspaceClick: () => showAddExistingSubspace(space),
                         space,
-                        onFinished: (added: boolean) => {
-                            if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
-                                defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
-                            }
-                        },
                     },
                     "mx_AddExistingToSpaceDialog_wrapper",
                 );
+                finished.then(([added]) => {
+                    if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
+                        defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+                    }
+                });
                 break;
             }
         }
diff --git a/src/utils/DirectoryUtils.ts b/src/utils/DirectoryUtils.ts
index ff2407f256af00be003753d6e666d1581e63c7ac..732f5c3cc7f4892c57389e231573cf086c76775a 100644
--- a/src/utils/DirectoryUtils.ts
+++ b/src/utils/DirectoryUtils.ts
@@ -6,6 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IProtocol } from "matrix-js-sdk/src/matrix";
+import { type IProtocol } from "matrix-js-sdk/src/matrix";
 
 export type Protocols = Record<string, IProtocol>;
diff --git a/src/utils/EditorStateTransfer.ts b/src/utils/EditorStateTransfer.ts
index e104a5244c46b236ec214a0a79eaeeee2f4706c8..57667321365083cf32cd97ef3f7143ac105bbb21 100644
--- a/src/utils/EditorStateTransfer.ts
+++ b/src/utils/EditorStateTransfer.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import { SerializedPart } from "../editor/parts";
-import DocumentOffset from "../editor/offset";
+import { type SerializedPart } from "../editor/parts";
+import type DocumentOffset from "../editor/offset";
 
 /**
  * Used while editing, to pass the event, and to preserve editor state
diff --git a/src/utils/ErrorUtils.tsx b/src/utils/ErrorUtils.tsx
index 997dd5684031c1d3358157da88a6c2ec7a418d73..2772350a0c10a25a9cd039b590b1f2cd3dfbca46 100644
--- a/src/utils/ErrorUtils.tsx
+++ b/src/utils/ErrorUtils.tsx
@@ -6,13 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type ReactNode } from "react";
 import { MatrixError, ConnectionError } from "matrix-js-sdk/src/matrix";
+import { logger } from "matrix-js-sdk/src/logger";
 
-import { _t, _td, lookupString, Tags, TranslatedString, TranslationKey } from "../languageHandler";
+import { _t, _td, lookupString, type Tags, type TranslatedString, type TranslationKey } from "../languageHandler";
 import SdkConfig from "../SdkConfig";
-import { ValidatedServerConfig } from "./ValidatedServerConfig";
+import { type ValidatedServerConfig } from "./ValidatedServerConfig";
 import ExternalLink from "../components/views/elements/ExternalLink";
+import Modal from "../Modal.tsx";
+import ErrorDialog from "../components/views/dialogs/ErrorDialog.tsx";
 
 export const resourceLimitStrings = {
     "monthly_active_user": _td("error|mau"),
@@ -191,3 +194,27 @@ export function messageForConnectionError(
 
     return errorText;
 }
+
+/**
+ * Utility for handling unexpected errors: pops up the error dialog.
+ *
+ * Example usage:
+ * ```
+ * try {
+ *     /// complicated operation
+ * } catch (e) {
+ *     logErrorAndShowErrorDialog("Failed complicated operation", e);
+ * }
+ * ```
+ *
+ * This isn't particularly intended to be pretty; rather it lets the user know that *something* has gone wrong so that
+ * they can report a bug. The general idea is that it's better to let the user know of a failure, even if they
+ * can't do anything about it, than it is to fail silently with the appearance of success.
+ *
+ * @param title - Title for the error dialog.
+ * @param error - The thrown error. Becomes the content of the error dialog.
+ */
+export function logErrorAndShowErrorDialog(title: string, error: any): void {
+    logger.error(`${title}:`, error);
+    Modal.createDialog(ErrorDialog, { title, description: `${error}` });
+}
diff --git a/src/utils/EventRenderingUtils.ts b/src/utils/EventRenderingUtils.ts
index 0bef43df12244a4c0803ec0c9ce62419d8093f1b..5ac6629b8b1147e1f3d3f2ae92167bab5a1897b2 100644
--- a/src/utils/EventRenderingUtils.ts
+++ b/src/utils/EventRenderingUtils.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixEvent,
-    IContent,
-    MatrixClient,
+    type MatrixEvent,
+    type IContent,
+    type MatrixClient,
     EventType,
     MsgType,
     M_POLL_END,
diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts
index 2ed3bceb0d74c2be47d4909fa840836d7ddc2a1e..9c387b16b04b1e33030487de6ad80ebd47b08d43 100644
--- a/src/utils/EventUtils.ts
+++ b/src/utils/EventUtils.ts
@@ -13,7 +13,7 @@ import {
     EVENT_VISIBILITY_CHANGE_TYPE,
     MsgType,
     RelationType,
-    MatrixClient,
+    type MatrixClient,
     THREAD_RELATION_TYPE,
     M_POLL_END,
     M_POLL_START,
@@ -23,13 +23,13 @@ import {
 import { logger } from "matrix-js-sdk/src/logger";
 
 import shouldHideEvent from "../shouldHideEvent";
-import { GetRelationsForEvent } from "../components/views/rooms/EventTile";
+import { type GetRelationsForEvent } from "../components/views/rooms/EventTile";
 import SettingsStore from "../settings/SettingsStore";
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { TimelineRenderingType } from "../contexts/RoomContext";
+import { type TimelineRenderingType } from "../contexts/RoomContext";
 import { launchPollEditor } from "../components/views/messages/MPollBody";
 import { Action } from "../dispatcher/actions";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 
 /**
  * Returns whether an event should allow actions like reply, reactions, edit, etc.
diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts
index 6792ad254e9a69b2616701127ff0fc214e04ee72..1240dc61dba75a044e52183eefc20bea690a330f 100644
--- a/src/utils/FileUtils.ts
+++ b/src/utils/FileUtils.ts
@@ -9,15 +9,15 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     filesize,
-    FileSizeOptionsArray,
-    FileSizeOptionsBase,
-    FileSizeOptionsExponent,
-    FileSizeOptionsObject,
-    FileSizeOptionsString,
-    FileSizeReturnArray,
-    FileSizeReturnObject,
+    type FileSizeOptionsArray,
+    type FileSizeOptionsBase,
+    type FileSizeOptionsExponent,
+    type FileSizeOptionsObject,
+    type FileSizeOptionsString,
+    type FileSizeReturnArray,
+    type FileSizeReturnObject,
 } from "filesize";
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 
 import { _t } from "../languageHandler";
 
diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts
index ae92cf610492b6c83b0655edc2c0221239852ca3..051ef7a371e663506d00297272d68105ddffa219 100644
--- a/src/utils/FormattingUtils.ts
+++ b/src/utils/FormattingUtils.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactElement, ReactNode } from "react";
+import { type ReactElement, type ReactNode } from "react";
 import { useIdColorHash } from "@vector-im/compound-web";
 
 import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler";
@@ -56,17 +56,6 @@ export function formatBytes(bytes: number, decimals = 2): string {
     return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
 }
 
-/**
- * format a key into groups of 4 characters, for easier visual inspection
- *
- * @param {string} key key to format
- *
- * @return {string}
- */
-export function formatCryptoKey(key: string): string {
-    return key.match(/.{1,4}/g)!.join(" ");
-}
-
 export function getUserNameColorClass(userId: string): string {
     // eslint-disable-next-line react-hooks/rules-of-hooks
     const number = useIdColorHash(userId);
diff --git a/src/utils/IdentityServerUtils.ts b/src/utils/IdentityServerUtils.ts
index fd3de6b2f404db9a77a22cf045571c7de62e662d..df4f269c34ed911e7db2d08728966578b27289eb 100644
--- a/src/utils/IdentityServerUtils.ts
+++ b/src/utils/IdentityServerUtils.ts
@@ -6,11 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SERVICE_TYPES, HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { SERVICE_TYPES, HTTPError, type MatrixClient, type Terms } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import SdkConfig from "../SdkConfig";
-import { Policies } from "../Terms";
 
 export function getDefaultIdentityServerUrl(): string | undefined {
     return SdkConfig.get("validated_server_config")?.isUrl;
@@ -25,7 +24,7 @@ export function setToDefaultIdentityServer(matrixClient: MatrixClient): void {
 }
 
 export async function doesIdentityServerHaveTerms(matrixClient: MatrixClient, fullUrl: string): Promise<boolean> {
-    let terms: { policies?: Policies } | null;
+    let terms: Partial<Terms> | null;
     try {
         terms = await matrixClient.getTerms(SERVICE_TYPES.IS, fullUrl);
     } catch (e) {
diff --git a/src/utils/Image.ts b/src/utils/Image.ts
index eed70bdf32a40f4fe6dde9dc151920e98c2469d8..ab7c67d47774259a5a6e4a16c29d7853985e437b 100644
--- a/src/utils/Image.ts
+++ b/src/utils/Image.ts
@@ -31,13 +31,13 @@ export async function blobIsAnimated(mimeType: string | undefined, blob: Blob):
         case "image/webp": {
             // Only extended file format WEBP images support animation, so grab the expected data range and verify header.
             // Based on https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
-            const arr = await blob.slice(0, 17).arrayBuffer();
+            const arr = await blob.slice(0, 21).arrayBuffer();
             if (
                 arrayBufferReadStr(arr, 0, 4) === "RIFF" &&
                 arrayBufferReadStr(arr, 8, 4) === "WEBP" &&
                 arrayBufferReadStr(arr, 12, 4) === "VP8X"
             ) {
-                const [flags] = arrayBufferRead(arr, 16, 1);
+                const [flags] = arrayBufferRead(arr, 20, 1);
                 // Flags: R R I L E X _A_ R (reversed)
                 const animationFlagMask = 1 << 1;
                 return (flags & animationFlagMask) != 0;
diff --git a/src/utils/KeyVerificationStateObserver.ts b/src/utils/KeyVerificationStateObserver.ts
index 64f50ab201128f63d151df2a9f19111d2ca05980..2dfd78e0a80fecdbe486c6b900b76e3d0f610e8b 100644
--- a/src/utils/KeyVerificationStateObserver.ts
+++ b/src/utils/KeyVerificationStateObserver.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../languageHandler";
 
diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts
index 118b5baadb70f8bc77a201e841daf6ee0f5d6952..98cbe4da58324d5c01003b57393d79465ac2ecab 100644
--- a/src/utils/MediaEventHelper.ts
+++ b/src/utils/MediaEventHelper.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
-import { FileContent, ImageContent, MediaEventContent } from "matrix-js-sdk/src/types";
+import { type MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
+import { type FileContent, type ImageContent, type MediaEventContent } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { LazyValue } from "./LazyValue";
-import { Media, mediaFromContent } from "../customisations/Media";
+import { type Media, mediaFromContent } from "../customisations/Media";
 import { decryptFile } from "./DecryptFile";
-import { IDestroyable } from "./IDestroyable";
+import { type IDestroyable } from "./IDestroyable";
 
 // TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
 
@@ -113,4 +113,18 @@ export class MediaEventHelper implements IDestroyable {
         // Finally, it's probably not media
         return false;
     }
+
+    /**
+     * Determine if the media event in question supports being hidden in the timeline.
+     * @param event Any matrix event.
+     * @returns `true` if the media can be hidden, otherwise false.
+     */
+    public static canHide(event: MatrixEvent): boolean {
+        if (!event) return false;
+        if (event.isRedacted()) return false;
+        const content = event.getContent();
+        const hideTypes: string[] = [MsgType.Video, MsgType.Image];
+        if (hideTypes.includes(content.msgtype!)) return true;
+        return false;
+    }
 }
diff --git a/src/utils/MessageDiffUtils.tsx b/src/utils/MessageDiffUtils.tsx
index 08af2379e6616094893f100fdc17d9b243d199a1..addb8265be29f2bd12ad4fe05a67f4546979eb47 100644
--- a/src/utils/MessageDiffUtils.tsx
+++ b/src/utils/MessageDiffUtils.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import classNames from "classnames";
 import DiffMatchPatch from "diff-match-patch";
-import { DiffDOM, IDiff } from "diff-dom";
-import { IContent } from "matrix-js-sdk/src/matrix";
+import { DiffDOM, type IDiff } from "diff-dom";
+import { type IContent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import { unescape } from "lodash";
 
-import { bodyToHtml, checkBlockNode, EventRenderOpts } from "../HtmlUtils";
+import { bodyToHtml, checkBlockNode, type EventRenderOpts } from "../HtmlUtils";
 
 function textToHtml(text: string): string {
     const container = document.createElement("div");
diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts
index 1350eb94a439f1ec62dcdd74c869cfa54afd63f7..f8310de8bdffc9836ceb5429528f5631a3deb2c8 100644
--- a/src/utils/MultiInviter.ts
+++ b/src/utils/MultiInviter.ts
@@ -6,9 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixError, MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
+import { MatrixError, type MatrixClient, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { defer, IDeferred } from "matrix-js-sdk/src/utils";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { AddressType, getAddressType } from "../UserAddress";
@@ -51,7 +50,7 @@ export default class MultiInviter {
     private _fatal = false;
     private completionStates: CompletionStates = {}; // State of each address (invited or error)
     private errors: Record<string, IError> = {}; // { address: {errorText, errcode} }
-    private deferred: IDeferred<CompletionStates> | null = null;
+    private deferred: PromiseWithResolvers<CompletionStates> | null = null;
     private reason: string | undefined;
 
     /**
@@ -93,7 +92,7 @@ export default class MultiInviter {
                 };
             }
         }
-        this.deferred = defer<CompletionStates>();
+        this.deferred = Promise.withResolvers<CompletionStates>();
         this.inviteMore(0);
 
         return this.deferred.promise;
@@ -117,7 +116,7 @@ export default class MultiInviter {
         return this.errors[addr]?.errorText ?? null;
     }
 
-    private async inviteToRoom(roomId: string, addr: string, ignoreProfile = false): Promise<{}> {
+    private async inviteToRoom(roomId: string, addr: string, ignoreProfile = false): Promise<EmptyObject> {
         const addrType = getAddressType(addr);
 
         if (addrType === AddressType.Email) {
diff --git a/src/utils/NativeEventUtils.ts b/src/utils/NativeEventUtils.ts
index eebd2fcb5154f2007735324cf173e47349f12ea4..d6373a61a203dacbb4041d658700c0b9e2b54103 100644
--- a/src/utils/NativeEventUtils.ts
+++ b/src/utils/NativeEventUtils.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import type React from "react";
 
 // Wrap DOM event handlers with stopPropagation and preventDefault
 export const preventDefaultWrapper =
diff --git a/src/utils/PasswordScorer.ts b/src/utils/PasswordScorer.ts
index 876461920cd0d0e8544b13a8a1d10b142a856613..b01e5051cf1b29f2711a97a1eede100c105b268e 100644
--- a/src/utils/PasswordScorer.ts
+++ b/src/utils/PasswordScorer.ts
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { zxcvbn, zxcvbnOptions, ZxcvbnResult, TranslationKeys } from "@zxcvbn-ts/core";
+import { zxcvbn, zxcvbnOptions, type ZxcvbnResult, type TranslationKeys } from "@zxcvbn-ts/core";
 import * as zxcvbnCommonPackage from "@zxcvbn-ts/language-common";
 import * as zxcvbnEnPackage from "@zxcvbn-ts/language-en";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../languageHandler";
 import SdkConfig from "../SdkConfig";
diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts
index a1304598f7c8cf9295d361db7c164e9f22a3e0a7..9575e3b7675130b549f68243db169df445ca6639 100644
--- a/src/utils/PinningUtils.ts
+++ b/src/utils/PinningUtils.ts
@@ -6,7 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline, Room } from "matrix-js-sdk/src/matrix";
+import {
+    type MatrixEvent,
+    EventType,
+    M_POLL_START,
+    type MatrixClient,
+    EventTimeline,
+    type Room,
+    type EmptyObject,
+} from "matrix-js-sdk/src/matrix";
 
 import { isContentActionable } from "./EventUtils";
 import { ReadPinsEventId } from "../components/views/right_panel/types";
@@ -123,7 +131,7 @@ export default class PinningUtils {
                 ?.getStateEvents(EventType.RoomPinnedEvents, "")
                 ?.getContent().pinned || [];
 
-        let roomAccountDataPromise: Promise<{} | void> = Promise.resolve();
+        let roomAccountDataPromise: Promise<EmptyObject | void> = Promise.resolve();
         // If the event is already pinned, unpin it
         if (pinnedIds.includes(eventId)) {
             pinnedIds.splice(pinnedIds.indexOf(eventId), 1);
diff --git a/src/utils/ReactUtils.tsx b/src/utils/ReactUtils.tsx
index 2180aa81ae299a8977e0ffa4c036b186762796c2..40217b1adb547003cd5a09c0a0d133f2dbd20cd7 100644
--- a/src/utils/ReactUtils.tsx
+++ b/src/utils/ReactUtils.tsx
@@ -6,18 +6,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactNode } from "react";
+import React, { type JSX, type ReactNode } from "react";
 
 /**
- * Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <span>hello world</span>
+ * Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <>hello world</>
  * @param array the array of element to join
  * @param joiner the string/JSX.Element to join with
  * @returns the joined array
  */
 export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element {
-    const newArray: ReactNode[] = [];
-    array.forEach((element, index) => {
-        newArray.push(element, index === array.length - 1 ? null : joiner);
-    });
-    return <span>{newArray}</span>;
+    return (
+        <>
+            {array.map((element, index) => (
+                <React.Fragment key={index}>
+                    {element}
+                    {index === array.length - 1 ? null : joiner}
+                </React.Fragment>
+            ))}
+        </>
+    );
 }
diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts
index fee65a7b556101032967a35370b378649133d514..e5909ffcc08fe14353584c0ab29da6b69fe7ab48 100644
--- a/src/utils/Reply.ts
+++ b/src/utils/Reply.ts
@@ -7,7 +7,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { IContent, IEventRelation, MatrixEvent, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
+import { type IContent, type IEventRelation, type MatrixEvent, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
 import sanitizeHtml from "sanitize-html";
 
 import { PERMITTED_URL_SCHEMES } from "./UrlUtils";
diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts
index 8979b1f0c8c1337a8b58f927a43b46d1e635c91e..aa106f1a1aa1430bcad2044b28b50f0a5098a6e6 100644
--- a/src/utils/RoomUpgrade.ts
+++ b/src/utils/RoomUpgrade.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, EventType, ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Room, EventType, ClientEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { inviteUsersToRoom } from "../RoomInvite";
-import Modal, { IHandle } from "../Modal";
+import Modal, { type IHandle } from "../Modal";
 import { _t } from "../languageHandler";
 import ErrorDialog from "../components/views/dialogs/ErrorDialog";
 import SpaceStore from "../stores/spaces/SpaceStore";
diff --git a/src/utils/SessionLock.ts b/src/utils/SessionLock.ts
index 861c87ead5551f36c2ea76d428f55d660f73a177..c93097aba179b7e09c07e039435b5f0a7f1580f6 100644
--- a/src/utils/SessionLock.ts
+++ b/src/utils/SessionLock.ts
@@ -57,7 +57,7 @@ export const SESSION_LOCK_CONSTANTS = {
     /**
      * The number of milliseconds after which we consider a lock claim stale
      */
-    LOCK_EXPIRY_TIME_MS: 30000,
+    LOCK_EXPIRY_TIME_MS: 15000,
 };
 
 /**
@@ -140,7 +140,10 @@ export async function getSessionLock(onNewInstance: () => Promise<void>): Promis
         }
 
         const timeAgo = Date.now() - parseInt(lastPingTime);
-        const remaining = SESSION_LOCK_CONSTANTS.LOCK_EXPIRY_TIME_MS - timeAgo;
+        // If the last ping time is in the future (i.e., timeAgo is negative), the chances are that the system clock has
+        // been wound back since the ping. Rather than waiting hours/days/millenia for us to get there, treat a future
+        // ping as "just now" by clipping to 0.
+        const remaining = SESSION_LOCK_CONSTANTS.LOCK_EXPIRY_TIME_MS - Math.max(timeAgo, 0);
         if (remaining <= 0) {
             // another session claimed the lock, but it is stale.
             prefixedLogger.info(`Last ping (from ${lockHolder}) was ${timeAgo}ms ago: proceeding with startup`);
@@ -242,13 +245,23 @@ export async function getSessionLock(onNewInstance: () => Promise<void>): Promis
             };
         });
 
+        // We construct our own promise here rather than using the `sleep` utility, to make it easier to test the
+        // SessionLock in a separate Window.
         const sleepPromise = new Promise((resolve) => {
             setTimeout(resolve, remaining, undefined);
         });
 
         window.addEventListener("storage", onStorageUpdate!);
-        await Promise.race([sleepPromise, storageUpdatePromise]);
+        const winner = await Promise.race([sleepPromise, storageUpdatePromise]);
         window.removeEventListener("storage", onStorageUpdate!);
+
+        // If we got through the whole of the sleep without any writes to the store, we know that the
+        // ping is now stale. There's no point in going round and calling `checkLock` again: we know that
+        // nothing has changed since last time.
+        if (!(winner instanceof StorageEvent)) {
+            prefixedLogger.info("Existing claim went stale: proceeding with startup");
+            break;
+        }
     }
 
     // If we get here, we know the lock is ours for the taking.
diff --git a/src/utils/ShieldUtils.ts b/src/utils/ShieldUtils.ts
index d1466609e472e7fd2d16b99765504fad43bf6155..7802d513417afaeac886d0b1e29a92c70fb92874 100644
--- a/src/utils/ShieldUtils.ts
+++ b/src/utils/ShieldUtils.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientStoppedError, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { ClientStoppedError, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import DMRoomMap from "./DMRoomMap";
diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts
index 2c76bb6ed49a97d80929ccdda09931f84ce8309a..65a8f77f2bf18db5994353c6865482af7a75ebfa 100644
--- a/src/utils/SortMembers.ts
+++ b/src/utils/SortMembers.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { groupBy, mapValues, maxBy, minBy, sumBy, takeRight } from "lodash";
-import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
-import { Member } from "./direct-messages";
+import { type Member } from "./direct-messages";
 import DMRoomMap from "./DMRoomMap";
 
 export const compareMembers =
diff --git a/src/utils/StorageManager.ts b/src/utils/StorageManager.ts
index 3b63f0a636661a3092f8740978b9cb88a0f5e22f..478d30af6ffe778a0a82a6e73e2d6bbe971a0749 100644
--- a/src/utils/StorageManager.ts
+++ b/src/utils/StorageManager.ts
@@ -92,8 +92,7 @@ export async function checkConsistency(): Promise<{
     if (dataInLocalStorage && cryptoInited && !dataInCryptoStore) {
         healthy = false;
         error(
-            "Data exists in local storage and crypto is marked as initialised " +
-                " but no data found in crypto store. " +
+            "Data exists in local storage and crypto is marked as initialised but no data found in crypto store. " +
                 "IndexedDB storage has likely been evicted by the browser!",
         );
     }
diff --git a/src/utils/Timer.ts b/src/utils/Timer.ts
index ee05e39b347858fc36fcadad33a313dc6e80b242..c5d9084c31014438d7d479a44f08f5ec21d4e75a 100644
--- a/src/utils/Timer.ts
+++ b/src/utils/Timer.ts
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IDeferred, defer } from "matrix-js-sdk/src/utils";
-
 /**
 A countdown timer, exposing a promise api.
 A timer starts in a non-started state,
@@ -22,7 +20,7 @@ a new one through `clone()` or `cloneIfRun()`.
 export default class Timer {
     private timerHandle?: number;
     private startTs?: number;
-    private deferred!: IDeferred<void>;
+    private deferred!: PromiseWithResolvers<void>;
 
     public constructor(private timeout: number) {
         this.setNotStarted();
@@ -31,7 +29,7 @@ export default class Timer {
     private setNotStarted(): void {
         this.timerHandle = undefined;
         this.startTs = undefined;
-        this.deferred = defer();
+        this.deferred = Promise.withResolvers();
         this.deferred.promise = this.deferred.promise.finally(() => {
             this.timerHandle = undefined;
         });
diff --git a/src/utils/ValidatedServerConfig.ts b/src/utils/ValidatedServerConfig.ts
index a464514317e25dc0a62496b1f6783f9966337d3c..2e83ba20c593773e8d8733b781fe19c97b5570af 100644
--- a/src/utils/ValidatedServerConfig.ts
+++ b/src/utils/ValidatedServerConfig.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { type OidcClientConfig } from "matrix-js-sdk/src/matrix";
 
 export interface ValidatedServerConfig {
     hsUrl: string;
diff --git a/src/utils/WellKnownUtils.ts b/src/utils/WellKnownUtils.ts
index 5634a94b59f096adf2871b637ea9bfed96a482dc..b2af51e2c3bf9125d38698be1c6118e7e692077a 100644
--- a/src/utils/WellKnownUtils.ts
+++ b/src/utils/WellKnownUtils.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IClientWellKnown, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
 
 const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
diff --git a/src/utils/Whenable.ts b/src/utils/Whenable.ts
index c584c4aa4c1337a5c92f039cc13fe4fdf285c205..4f21f584244ed4943f46fc9a9e58378060c458fc 100644
--- a/src/utils/Whenable.ts
+++ b/src/utils/Whenable.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { IDestroyable } from "./IDestroyable";
+import { type IDestroyable } from "./IDestroyable";
 import { arrayFastClone } from "./arrays";
 
 export type WhenFn<T extends string | number> = (w: Whenable<T>) => void;
diff --git a/src/utils/WidgetUtils-types.ts b/src/utils/WidgetUtils-types.ts
index 734a4d002ea1c257dd01c4b9321cbedabf7a9c1b..374b37632f8543b9fcf98dca9ae78c6da586761b 100644
--- a/src/utils/WidgetUtils-types.ts
+++ b/src/utils/WidgetUtils-types.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IWidget } from "matrix-widget-api";
+import { type IWidget } from "matrix-widget-api";
 
 export interface IApp extends IWidget {
     "roomId": string;
diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts
index d9de59e4013ddfdb26e5245461d9e87b81af51eb..41232db3c448767dace313ca6f123809e7898ad8 100644
--- a/src/utils/WidgetUtils.ts
+++ b/src/utils/WidgetUtils.ts
@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
 
 import { useCallback, useEffect, useState } from "react";
 import { base32 } from "rfc4648";
-import { IWidget, IWidgetData } from "matrix-widget-api";
-import { Room, ClientEvent, MatrixClient, RoomStateEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { capitalize } from "lodash";
+import { type IWidget, type IWidgetData } from "matrix-widget-api";
+import { type Room, ClientEvent, type MatrixClient, RoomStateEvent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 import { CallType } from "matrix-js-sdk/src/webrtc/call";
-import { randomString, randomLowercaseString, randomUppercaseString } from "matrix-js-sdk/src/randomstring";
+import { LOWERCASE, secureRandomString, secureRandomStringFrom } from "matrix-js-sdk/src/randomstring";
 
 import PlatformPeg from "../PlatformPeg";
 import SdkConfig from "../SdkConfig";
@@ -25,11 +26,11 @@ import { WidgetType } from "../widgets/WidgetType";
 import { Jitsi } from "../widgets/Jitsi";
 import { objectClone } from "./objects";
 import { _t } from "../languageHandler";
-import WidgetStore, { IApp, isAppWidget } from "../stores/WidgetStore";
+import WidgetStore, { type IApp, isAppWidget } from "../stores/WidgetStore";
 import { parseUrl } from "./UrlUtils";
 import { useEventEmitter } from "../hooks/useEventEmitter";
 import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
-import { IWidgetEvent, UserWidget } from "./WidgetUtils-types";
+import { type IWidgetEvent, type UserWidget } from "./WidgetUtils-types";
 
 // How long we wait for the state event echo to come back from the server
 // before waitFor[Room/User]Widget rejects its promise
@@ -427,7 +428,10 @@ export default class WidgetUtils {
     ): Promise<void> {
         const domain = Jitsi.getInstance().preferredDomain;
         const auth = (await Jitsi.getInstance().getJitsiAuth()) ?? undefined;
-        const widgetId = randomString(24); // Must be globally unique
+
+        // Must be globally unique, although predicatablity is not important, the js-sdk has functions to generate
+        // secure ranom strings, and speed is not important here.
+        const widgetId = secureRandomString(24);
 
         let confId: string;
         if (auth === "openidtoken-jwt") {
@@ -437,8 +441,8 @@ export default class WidgetUtils {
             // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
             confId = base32.stringify(new TextEncoder().encode(roomId), { pad: false });
         } else {
-            // Create a random conference ID
-            confId = `Jitsi${randomUppercaseString(1)}${randomLowercaseString(23)}`;
+            // Create a random conference ID (capitalised so the name looks sensible in Jitsi)
+            confId = `Jitsi${capitalize(secureRandomStringFrom(24, LOWERCASE))}`;
         }
 
         // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
diff --git a/src/utils/beacon/bounds.ts b/src/utils/beacon/bounds.ts
index a23e3367da7fca81d382265acfad430ee3d2678f..4df186b037305892de16903b8eb1ba6c6dec50ef 100644
--- a/src/utils/beacon/bounds.ts
+++ b/src/utils/beacon/bounds.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Beacon } from "matrix-js-sdk/src/matrix";
+import { type Beacon } from "matrix-js-sdk/src/matrix";
 
 import { filterBoolean } from "../arrays";
 import { parseGeoUri } from "../location";
diff --git a/src/utils/beacon/duration.ts b/src/utils/beacon/duration.ts
index d967bb521724d517e4021067234a57b2a0425df4..1cda8cf8dd2bb497a5c04123a2183b00e9666b8f 100644
--- a/src/utils/beacon/duration.ts
+++ b/src/utils/beacon/duration.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Beacon, ContentHelpers } from "matrix-js-sdk/src/matrix";
+import { type Beacon, type ContentHelpers } from "matrix-js-sdk/src/matrix";
 
 /**
  * Get ms until expiry
diff --git a/src/utils/beacon/getShareableLocation.ts b/src/utils/beacon/getShareableLocation.ts
index 4a141ec969b48bdf3a8238a6e1f63d82ba9a8fcf..f548ad2fb5a26158c89e82b241cb50a105744203 100644
--- a/src/utils/beacon/getShareableLocation.ts
+++ b/src/utils/beacon/getShareableLocation.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
 
 /**
  * Beacons should only have shareable locations (open in external mapping tool, forward)
diff --git a/src/utils/beacon/timeline.ts b/src/utils/beacon/timeline.ts
index 25e0e9c008d5d659639b8bde4d1612ce0d5f7647..1a5cc1ca2c424976f61d3c9c4cab5a739acf72fe 100644
--- a/src/utils/beacon/timeline.ts
+++ b/src/utils/beacon/timeline.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, M_BEACON_INFO } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, M_BEACON_INFO } from "matrix-js-sdk/src/matrix";
 
 /**
  * beacon_info events without live property set to true
diff --git a/src/utils/beacon/useBeacon.ts b/src/utils/beacon/useBeacon.ts
index 80fe14abcd909e105f283c3b34142252b04f8257..b405eb654be6a70993d12dc91ae3a7c0c22792c9 100644
--- a/src/utils/beacon/useBeacon.ts
+++ b/src/utils/beacon/useBeacon.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useContext, useEffect, useState } from "react";
-import { Beacon, BeaconEvent, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
+import { type Beacon, BeaconEvent, type MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
 
 import MatrixClientContext from "../../contexts/MatrixClientContext";
 import { useEventEmitterState } from "../../hooks/useEventEmitter";
diff --git a/src/utils/beacon/useLiveBeacons.ts b/src/utils/beacon/useLiveBeacons.ts
index c99faeb72f69f12e07993eac223092f1afa3870d..8d2d956a2f8e2f527aed18008c597b0d2cf74a0d 100644
--- a/src/utils/beacon/useLiveBeacons.ts
+++ b/src/utils/beacon/useLiveBeacons.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Beacon, Room, RoomStateEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Beacon, type Room, RoomStateEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useEventEmitterState } from "../../hooks/useEventEmitter";
 
diff --git a/src/utils/beacon/useOwnLiveBeacons.ts b/src/utils/beacon/useOwnLiveBeacons.ts
index 37387fda98bb44ec96dbf4564bfa4b469a9ebdac..f9696fc880693a2eeb3bbe1cf82cbaa74f1c84c3 100644
--- a/src/utils/beacon/useOwnLiveBeacons.ts
+++ b/src/utils/beacon/useOwnLiveBeacons.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { useEffect, useState } from "react";
-import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
+import { type Beacon, type BeaconIdentifier } from "matrix-js-sdk/src/matrix";
 
 import { useEventEmitterState } from "../../hooks/useEventEmitter";
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../stores/OwnBeaconStore";
diff --git a/src/utils/connection.ts b/src/utils/connection.ts
index 9c2866d305129f3bf47bf1f4005fc620b81935b5..66ecde56b06aa13d5e5f80f0a3cbba8bca49d4cc 100644
--- a/src/utils/connection.ts
+++ b/src/utils/connection.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientEvent, ClientEventHandlerMap, SyncState } from "matrix-js-sdk/src/matrix";
+import { type ClientEvent, type ClientEventHandlerMap, SyncState } from "matrix-js-sdk/src/matrix";
 
 /**
  * Creates a MatrixClient event listener function that can be used to get notified about reconnects.
diff --git a/src/utils/createMatrixClient.ts b/src/utils/createMatrixClient.ts
index ad156b8ab4205611257034696638dce5a446ec50..3cbc7a62f3cebecf912b2962d9021d2056899918 100644
--- a/src/utils/createMatrixClient.ts
+++ b/src/utils/createMatrixClient.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 import {
-    MatrixClient,
+    type MatrixClient,
     createClient,
-    ICreateClientOpts,
+    type ICreateClientOpts,
     MemoryCryptoStore,
     MemoryStore,
     IndexedDBCryptoStore,
diff --git a/src/utils/createVoiceMessageContent.ts b/src/utils/createVoiceMessageContent.ts
index eb40406dbcab480cccd019b28f1297475073f5ab..4d7aa761bb8ba8fa7db92fb760661e6a92908ace 100644
--- a/src/utils/createVoiceMessageContent.ts
+++ b/src/utils/createVoiceMessageContent.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { MsgType } from "matrix-js-sdk/src/matrix";
-import { EncryptedFile, RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type EncryptedFile, type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
 /**
  * @param {string} mxc MXC URL of the file
diff --git a/src/utils/crypto/deviceInfo.ts b/src/utils/crypto/deviceInfo.ts
index dda634545d372eb888d1f0e4ad6fc42b07caefa1..f758e853ead7507857274f0291176b18044bfdb9 100644
--- a/src/utils/crypto/deviceInfo.ts
+++ b/src/utils/crypto/deviceInfo.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Device, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 /**
  * Get crypto information on a specific device.
diff --git a/src/utils/crypto/shouldForceDisableEncryption.ts b/src/utils/crypto/shouldForceDisableEncryption.ts
index c30f58636e1ba647401bc95d329f99ed569ec92f..04a2fe5818592369a2ef6e81b0d35891ffa99f81 100644
--- a/src/utils/crypto/shouldForceDisableEncryption.ts
+++ b/src/utils/crypto/shouldForceDisableEncryption.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { getE2EEWellKnown } from "../WellKnownUtils";
 
diff --git a/src/utils/crypto/shouldSkipSetupEncryption.ts b/src/utils/crypto/shouldSkipSetupEncryption.ts
index c431833b76cc39fd161c9f8df6ebbce0aab72539..8b235c5e3e0f87472545e87f754285523a65d772 100644
--- a/src/utils/crypto/shouldSkipSetupEncryption.ts
+++ b/src/utils/crypto/shouldSkipSetupEncryption.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { shouldForceDisableEncryption } from "./shouldForceDisableEncryption";
 import { asyncSomeParallel } from "../arrays.ts";
diff --git a/src/utils/device/clientInformation.ts b/src/utils/device/clientInformation.ts
index a8e2383bfdf000a03952ca67d6ac16e8fad9f815..3c2fb544bdb6f0e06e569b8fbfd29a9238b36832 100644
--- a/src/utils/device/clientInformation.ts
+++ b/src/utils/device/clientInformation.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AccountDataEvents, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type AccountDataEvents, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import BasePlatform from "../../BasePlatform";
-import { IConfigOptions } from "../../IConfigOptions";
-import { DeepReadonly } from "../../@types/common";
-import { DeviceClientInformation } from "./types";
+import type BasePlatform from "../../BasePlatform";
+import { type IConfigOptions } from "../../IConfigOptions";
+import { type DeepReadonly } from "../../@types/common";
+import { type DeviceClientInformation } from "./types";
 
 export type { DeviceClientInformation };
 
diff --git a/src/utils/device/dehydration.ts b/src/utils/device/dehydration.ts
index d87d43e13a76be0a746bb7d911295760be4f1f83..5dbec2973962608e05db00a893b739f68cb290c9 100644
--- a/src/utils/device/dehydration.ts
+++ b/src/utils/device/dehydration.ts
@@ -7,43 +7,24 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { type StartDehydrationOpts } from "matrix-js-sdk/src/crypto-api";
 
-import { MatrixClientPeg } from "../../MatrixClientPeg";
+import type { MatrixClient } from "matrix-js-sdk/src/matrix";
 
 /**
- * Check if device dehydration is enabled.
- *
- * Note that this doesn't necessarily mean that device dehydration has been initialised
- * (yet) on this client; rather, it means that the server supports it, the crypto backend
- * supports it, and the application configuration suggests that it *should* be
- * initialised on this device.
- *
- * Dehydration can currently only be enabled by setting a flag in the .well-known file.
- */
-async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise<boolean> {
-    if (!crypto) {
-        return false;
-    }
-    if (!(await crypto.isDehydrationSupported())) {
-        return false;
-    }
-    const wellknown = await MatrixClientPeg.safeGet().waitForClientWellKnown();
-    return !!wellknown?.["org.matrix.msc3814"];
-}
-
-/**
- * If dehydration is enabled (i.e., it is supported by the server and enabled in
- * the configuration), rehydrate a device (if available) and create
+ * If dehydration is supported by the server, rehydrate a device (if available) and create
  * a new dehydrated device.
  *
- * @param createNewKey: force a new dehydration key to be created, even if one
- *   already exists.  This is used when we reset secret storage.
+ * @param client - MatrixClient to use for the operation
+ * @param opts - options for the startDehydration operation, if one is performed.
  */
-export async function initialiseDehydration(createNewKey: boolean = false): Promise<void> {
-    const crypto = MatrixClientPeg.safeGet().getCrypto();
-    if (await deviceDehydrationEnabled(crypto)) {
-        logger.log("Device dehydration enabled");
-        await crypto!.startDehydration(createNewKey);
+export async function initialiseDehydrationIfEnabled(
+    client: MatrixClient,
+    opts: StartDehydrationOpts = {},
+): Promise<void> {
+    const crypto = client.getCrypto();
+    if (crypto && (await crypto.isDehydrationSupported())) {
+        logger.debug("Starting device dehydration");
+        await crypto.startDehydration(opts);
     }
 }
diff --git a/src/utils/device/isDeviceVerified.ts b/src/utils/device/isDeviceVerified.ts
index 0eacb4d54eac1cc43c3efbb5d78925d5bd9537f3..6eb69709d8f51be1cf8daf42de331a231e2401bb 100644
--- a/src/utils/device/isDeviceVerified.ts
+++ b/src/utils/device/isDeviceVerified.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 /**
  * Check if one of our own devices is verified via cross signing
diff --git a/src/utils/direct-messages.ts b/src/utils/direct-messages.ts
index ef80016962e307cc984e3051796b58de2d717534..e1e518421d87b2932eda5ec8a36c4830217b6df4 100644
--- a/src/utils/direct-messages.ts
+++ b/src/utils/direct-messages.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { canEncryptToAllUsers } from "../createRoom";
 import { Action } from "../dispatcher/actions";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 import dis from "../dispatcher/dispatcher";
-import { LocalRoom, LocalRoomState } from "../models/LocalRoom";
+import { type LocalRoom, LocalRoomState } from "../models/LocalRoom";
 import { waitForRoomReadyAndApplyAfterCreateCallbacks } from "./local-room";
 import { findDMRoom } from "./dm/findDMRoom";
 import { privateShouldBeEncrypted } from "./rooms";
diff --git a/src/utils/dm/createDmLocalRoom.ts b/src/utils/dm/createDmLocalRoom.ts
index 60dbf216b0f6a97f561e2b9a2fab0dba9b3ffcfa..e29d808ff5fe0a3cedb2096765387373bcac774a 100644
--- a/src/utils/dm/createDmLocalRoom.ts
+++ b/src/utils/dm/createDmLocalRoom.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, KNOWN_SAFE_ROOM_VERSION, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { EventType, KNOWN_SAFE_ROOM_VERSION, type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { LOCAL_ROOM_ID_PREFIX, LocalRoom } from "../../../src/models/LocalRoom";
-import { determineCreateRoomEncryptionOption, Member } from "../../../src/utils/direct-messages";
+import { determineCreateRoomEncryptionOption, type Member } from "../../../src/utils/direct-messages";
 import { MEGOLM_ENCRYPTION_ALGORITHM } from "../crypto";
 
 /**
diff --git a/src/utils/dm/findDMForUser.ts b/src/utils/dm/findDMForUser.ts
index b5e13aebaaaf38fdb436ad59d7c38fe8efd63db1..4e1168595fd4729fcb3fdeba55254db5a03944dc 100644
--- a/src/utils/dm/findDMForUser.ts
+++ b/src/utils/dm/findDMForUser.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import DMRoomMap from "../DMRoomMap";
diff --git a/src/utils/dm/findDMRoom.ts b/src/utils/dm/findDMRoom.ts
index 52c35ec084c12080e992e48e620cbe5d2d6e1f96..7b4bed796a486c85cfbcb911b011f9a9eac89d36 100644
--- a/src/utils/dm/findDMRoom.ts
+++ b/src/utils/dm/findDMRoom.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
-import { Member } from "../direct-messages";
+import { type Member } from "../direct-messages";
 import DMRoomMap from "../DMRoomMap";
 import { findDMForUser } from "./findDMForUser";
 
diff --git a/src/utils/dm/startDm.ts b/src/utils/dm/startDm.ts
index 44b7b1d3d880e3f23348c3bfff8b29d18f4c8fed..c88ad15c77606e769350baa49a9f1b92df00209f 100644
--- a/src/utils/dm/startDm.ts
+++ b/src/utils/dm/startDm.ts
@@ -6,18 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IInvite3PID, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
-import { Optional } from "matrix-events-sdk";
+import { type IInvite3PID, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+import { type Optional } from "matrix-events-sdk";
 
 import { Action } from "../../dispatcher/actions";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
-import { determineCreateRoomEncryptionOption, Member } from "../direct-messages";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { determineCreateRoomEncryptionOption, type Member } from "../direct-messages";
 import DMRoomMap from "../DMRoomMap";
 import { isLocalRoom } from "../localRoom/isLocalRoom";
 import { findDMForUser } from "./findDMForUser";
 import dis from "../../dispatcher/dispatcher";
 import { getAddressType } from "../../UserAddress";
-import createRoom, { IOpts } from "../../createRoom";
+import createRoom, { type IOpts } from "../../createRoom";
 
 /**
  * Start a DM.
diff --git a/src/utils/event/getSenderName.ts b/src/utils/event/getSenderName.ts
index 450edd8f20c4caa892277ba8f93a40383f0e2af7..fbb3524d1851ee31bd02540b0da395280d3314d0 100644
--- a/src/utils/event/getSenderName.ts
+++ b/src/utils/event/getSenderName.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { _t } from "../../languageHandler";
 
diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts
index 0c549038b47df0a684709b067485cc5539e1f3ae..54091869412f23e9434b64bd6c34c130216d5ec8 100644
--- a/src/utils/exportUtils/Exporter.ts
+++ b/src/utils/exportUtils/Exporter.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Direction, MatrixEvent, Relations, Room } from "matrix-js-sdk/src/matrix";
-import { EventType, MediaEventContent, RelationType } from "matrix-js-sdk/src/types";
+import { Direction, type MatrixEvent, type Relations, type Room } from "matrix-js-sdk/src/matrix";
+import { type EventType, type MediaEventContent, type RelationType } from "matrix-js-sdk/src/types";
 import { saveAs } from "file-saver";
 import { logger } from "matrix-js-sdk/src/logger";
 import sanitizeFilename from "sanitize-filename";
 
-import { ExportType, IExportOptions } from "./exportUtils";
+import { ExportType, type IExportOptions } from "./exportUtils";
 import { decryptFile } from "../DecryptFile";
 import { mediaFromContent } from "../../customisations/Media";
 import { formatFullDateNoDay, formatFullDateNoDayISO } from "../../DateUtils";
diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx
index 9bd88dbd45e259718a1372c244e88be25f5b1ba8..c20ea8762f7e3133fc97c8102735be15f7c91043 100644
--- a/src/utils/exportUtils/HtmlExport.tsx
+++ b/src/utils/exportUtils/HtmlExport.tsx
@@ -6,14 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { createRoot } from "react-dom/client";
-import { Room, MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
+import { type Room, MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
 import { renderToStaticMarkup } from "react-dom/server";
 import { logger } from "matrix-js-sdk/src/logger";
 import escapeHtml from "escape-html";
 import { TooltipProvider } from "@vector-im/compound-web";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import Exporter from "./Exporter";
 import { mediaFromMxc } from "../../customisations/Media";
@@ -26,7 +25,7 @@ import * as Avatar from "../../Avatar";
 import EventTile from "../../components/views/rooms/EventTile";
 import DateSeparator from "../../components/views/messages/DateSeparator";
 import BaseAvatar from "../../components/views/avatars/BaseAvatar";
-import { ExportType, IExportOptions } from "./exportUtils";
+import { type ExportType, type IExportOptions } from "./exportUtils";
 import MatrixClientContext from "../../contexts/MatrixClientContext";
 import getExportCSS from "./exportCSS";
 import { textForEvent } from "../../TextForEvent";
@@ -302,7 +301,7 @@ export default class HTMLExporter extends Exporter {
         if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
         // We have to wait for the component to be rendered before we can get the markup
         // so pass a deferred as a ref to the component.
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         const EventTile = this.getEventTile(mxEv, continuation, deferred.resolve);
         let eventTileMarkup: string;
 
diff --git a/src/utils/exportUtils/JSONExport.ts b/src/utils/exportUtils/JSONExport.ts
index bcd5c5cdc20ccc91295b54a9933e730f9bbfec8b..49549c5ae8107478e93db6c9d814bf274afc3db4 100644
--- a/src/utils/exportUtils/JSONExport.ts
+++ b/src/utils/exportUtils/JSONExport.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, IEvent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { type Room, type IEvent, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import Exporter from "./Exporter";
 import { formatFullDateNoDayNoTime } from "../../DateUtils";
-import { ExportType, IExportOptions } from "./exportUtils";
+import { type ExportType, type IExportOptions } from "./exportUtils";
 import { _t } from "../../languageHandler";
 import { haveRendererForEvent } from "../../events/EventTileFactory";
 
diff --git a/src/utils/exportUtils/PlainTextExport.ts b/src/utils/exportUtils/PlainTextExport.ts
index 48354d6f2acca43b8c7d618c22f49909cc1f8867..821233ef013a61d3b4ac076b8ebd485949fe25a6 100644
--- a/src/utils/exportUtils/PlainTextExport.ts
+++ b/src/utils/exportUtils/PlainTextExport.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, type IContent, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import React from "react";
 
+import type React from "react";
 import Exporter from "./Exporter";
 import { _t } from "../../languageHandler";
-import { ExportType, IExportOptions } from "./exportUtils";
+import { type ExportType, type IExportOptions } from "./exportUtils";
 import { textForEvent } from "../../TextForEvent";
 import { haveRendererForEvent } from "../../events/EventTileFactory";
 import SettingsStore from "../../settings/SettingsStore";
diff --git a/src/utils/i18n-helpers.ts b/src/utils/i18n-helpers.ts
index 195d3afd7a641636df16be799dcbd6f381efe262..408ec269e5a2165c28557e4621d5d9f71665d97d 100644
--- a/src/utils/i18n-helpers.ts
+++ b/src/utils/i18n-helpers.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import SpaceStore from "../stores/spaces/SpaceStore";
 import { _t } from "../languageHandler";
diff --git a/src/utils/image-media.ts b/src/utils/image-media.ts
index 2192297bdee713dfd06ac60dcc49de682e19e293..03c34fb39ea9417cdfbc720780383ca7c7ee28b9 100644
--- a/src/utils/image-media.ts
+++ b/src/utils/image-media.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ImageInfo } from "matrix-js-sdk/src/types";
+import { type ImageInfo } from "matrix-js-sdk/src/types";
 
 import { BlurhashEncoder } from "../BlurhashEncoder";
 
diff --git a/src/utils/leave-behaviour.ts b/src/utils/leave-behaviour.ts
index 8924c3693b2d0ca5eede841daba872094bd0669b..2ad6e7d4fe91369767ee4725e6d08022b98ab3a8 100644
--- a/src/utils/leave-behaviour.ts
+++ b/src/utils/leave-behaviour.ts
@@ -7,21 +7,21 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { sleep } from "matrix-js-sdk/src/utils";
-import React, { ReactNode } from "react";
-import { EventStatus, MatrixEventEvent, Room, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import React, { type ReactNode } from "react";
+import { EventStatus, MatrixEventEvent, type Room, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 
-import Modal, { IHandle } from "../Modal";
+import Modal, { type IHandle } from "../Modal";
 import Spinner from "../components/views/elements/Spinner";
 import { _t } from "../languageHandler";
 import ErrorDialog from "../components/views/dialogs/ErrorDialog";
 import { isMetaSpace } from "../stores/spaces";
 import SpaceStore from "../stores/spaces/SpaceStore";
 import dis from "../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../dispatcher/actions";
-import { ViewHomePagePayload } from "../dispatcher/payloads/ViewHomePagePayload";
+import { type ViewHomePagePayload } from "../dispatcher/payloads/ViewHomePagePayload";
 import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
-import { AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload";
+import { type AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload";
 import { bulkSpaceBehaviour } from "./space";
 import { SdkContextClass } from "../contexts/SDKContext";
 import SettingsStore from "../settings/SettingsStore";
@@ -171,20 +171,20 @@ export async function leaveRoomBehaviour(
 }
 
 export const leaveSpace = (space: Room): void => {
-    Modal.createDialog(
+    const { finished } = Modal.createDialog(
         LeaveSpaceDialog,
         {
             space,
-            onFinished: async (leave: boolean, rooms: Room[]): Promise<void> => {
-                if (!leave) return;
-                await bulkSpaceBehaviour(space, rooms, (room) => leaveRoomBehaviour(space.client, room.roomId));
-
-                dis.dispatch<AfterLeaveRoomPayload>({
-                    action: Action.AfterLeaveRoom,
-                    room_id: space.roomId,
-                });
-            },
         },
         "mx_LeaveSpaceDialog_wrapper",
     );
+    finished.then(async ([leave, rooms]) => {
+        if (!leave) return;
+        await bulkSpaceBehaviour(space, rooms!, (room) => leaveRoomBehaviour(space.client, room.roomId));
+
+        dis.dispatch<AfterLeaveRoomPayload>({
+            action: Action.AfterLeaveRoom,
+            room_id: space.roomId,
+        });
+    });
 };
diff --git a/src/utils/local-room.ts b/src/utils/local-room.ts
index 304471e93a3984585d89dedb899563376c7e7288..90d25bd4c01be413a9a33c06131986a83a33dd48 100644
--- a/src/utils/local-room.ts
+++ b/src/utils/local-room.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import defaultDispatcher from "../dispatcher/dispatcher";
-import { LocalRoom, LocalRoomState } from "../models/LocalRoom";
+import { type LocalRoom, LocalRoomState } from "../models/LocalRoom";
 import { isLocalRoom } from "./localRoom/isLocalRoom";
 import { isRoomReady } from "./localRoom/isRoomReady";
 
diff --git a/src/utils/localRoom/isLocalRoom.ts b/src/utils/localRoom/isLocalRoom.ts
index 6888c9aafdc750c04aabfed70a2db76d028c735b..d0e96828a2b53bac5c4e334b98bb4a0245091b1b 100644
--- a/src/utils/localRoom/isLocalRoom.ts
+++ b/src/utils/localRoom/isLocalRoom.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
 
diff --git a/src/utils/localRoom/isRoomReady.ts b/src/utils/localRoom/isRoomReady.ts
index 8d84f3abe84aa10aa8fd050cdcb0607b1f0df30d..3093f82b85466a49e1404c48bb112aabfee32f30 100644
--- a/src/utils/localRoom/isRoomReady.ts
+++ b/src/utils/localRoom/isRoomReady.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import { LocalRoom } from "../../models/LocalRoom";
+import { type LocalRoom } from "../../models/LocalRoom";
 
 /**
  * Tests whether a room created based on a local room is ready.
diff --git a/src/utils/location/findMapStyleUrl.ts b/src/utils/location/findMapStyleUrl.ts
index 10915a9c336d331a30af68873b1c798e86aaf0ab..3504f684b8cbc66c7c39f305d16c8e3dc7753bc9 100644
--- a/src/utils/location/findMapStyleUrl.ts
+++ b/src/utils/location/findMapStyleUrl.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../../SdkConfig";
 import { getTileServerWellKnown } from "../WellKnownUtils";
diff --git a/src/utils/location/isSelfLocation.ts b/src/utils/location/isSelfLocation.ts
index 17d9e9ea94d92996d48b1c35875639d7b2190bae..da0cdaf9abc04e32589e68f82a5a4a32def34869 100644
--- a/src/utils/location/isSelfLocation.ts
+++ b/src/utils/location/isSelfLocation.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ILocationContent, LocationAssetType, M_ASSET } from "matrix-js-sdk/src/matrix";
+import { type ILocationContent, LocationAssetType, M_ASSET } from "matrix-js-sdk/src/matrix";
 
 export const isSelfLocation = (locationContent: ILocationContent): boolean => {
     const asset = M_ASSET.findIn(locationContent) as { type: string };
diff --git a/src/utils/location/links.ts b/src/utils/location/links.ts
index cf957af3e8f9342c08d0fe452ecdfe3a5ffabeae..03f0e3eda14a6c7eb6eca6a0d8c4ff45d4b4921c 100644
--- a/src/utils/location/links.ts
+++ b/src/utils/location/links.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, M_LOCATION } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, M_LOCATION } from "matrix-js-sdk/src/matrix";
 
 import { parseGeoUri } from "./parseGeoUri";
 
diff --git a/src/utils/location/locationEventGeoUri.ts b/src/utils/location/locationEventGeoUri.ts
index 171952b07bbd1b9ba913392ad5d4abb0d661aae6..76481201028d5aa1790679a0b8d7c74f5f5be90e 100644
--- a/src/utils/location/locationEventGeoUri.ts
+++ b/src/utils/location/locationEventGeoUri.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, M_LOCATION } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, M_LOCATION } from "matrix-js-sdk/src/matrix";
 
 /**
  * Find the geo-URI contained within a location event.
@@ -18,5 +18,5 @@ export const locationEventGeoUri = (mxEvent: MatrixEvent): string => {
     // https://github.com/matrix-org/matrix-doc/issues/3516
     const content = mxEvent.getContent();
     const loc = M_LOCATION.findIn(content) as { uri?: string };
-    return loc ? loc.uri : content["geo_uri"];
+    return loc?.uri ?? content["geo_uri"];
 };
diff --git a/src/utils/location/map.ts b/src/utils/location/map.ts
index 22ceb998d5bc4730e9b74214e80ccd5c27a544ae..b16ee5b1fb5e4ca22d19963c3eae92e49e1233d5 100644
--- a/src/utils/location/map.ts
+++ b/src/utils/location/map.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import * as maplibregl from "maplibre-gl";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { _t } from "../../languageHandler";
diff --git a/src/utils/membership.ts b/src/utils/membership.ts
index 79a1771e661c01fbc57e3d87bc5bbaea0e25ab2d..ba67d5a6c3e4abcf64c8936aacc8c779fa04847b 100644
--- a/src/utils/membership.ts
+++ b/src/utils/membership.ts
@@ -6,8 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomMember, RoomState, RoomStateEvent, MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { KnownMembership, Membership } from "matrix-js-sdk/src/types";
+import {
+    type Room,
+    type RoomMember,
+    type RoomState,
+    RoomStateEvent,
+    type MatrixEvent,
+    type MatrixClient,
+} from "matrix-js-sdk/src/matrix";
+import { KnownMembership, type Membership } from "matrix-js-sdk/src/types";
 
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import SettingsStore from "../settings/SettingsStore";
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
index 9119ef9bcb7f6f7122651f81b2544744fe709596..ebfd7e90f635e95bc4dc90d4530b15d575aa1616 100644
--- a/src/utils/notifications.ts
+++ b/src/utils/notifications.ts
@@ -7,20 +7,21 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    MatrixClient,
+    type MatrixClient,
     LOCAL_NOTIFICATION_SETTINGS_PREFIX,
     NotificationCountType,
-    Room,
-    LocalNotificationSettings,
+    type Room,
+    type LocalNotificationSettings,
     ReceiptType,
-    IMarkedUnreadEvent,
+    type IMarkedUnreadEvent,
+    type EmptyObject,
 } from "matrix-js-sdk/src/matrix";
-import { IndicatorIcon } from "@vector-im/compound-web";
+import { type IndicatorIcon } from "@vector-im/compound-web";
 
 import SettingsStore from "../settings/SettingsStore";
 import { NotificationLevel } from "../stores/notifications/NotificationLevel";
 import { doesRoomHaveUnreadMessages } from "../Unread";
-import { SettingKey } from "../settings/Settings.tsx";
+import { type SettingKey } from "../settings/Settings.tsx";
 
 // MSC2867 is not yet spec at time of writing. We read from both stable
 // and unstable prefixes and accept the risk that the format may change,
@@ -80,7 +81,7 @@ export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
  * @param client
  * @returns a promise that resolves when the room has been marked as read
  */
-export async function clearRoomNotification(room: Room, client: MatrixClient): Promise<{} | undefined> {
+export async function clearRoomNotification(room: Room, client: MatrixClient): Promise<EmptyObject | undefined> {
     const lastEvent = room.getLastLiveEvent();
 
     await setMarkedUnreadState(room, client, false);
@@ -115,15 +116,17 @@ export async function clearRoomNotification(room: Room, client: MatrixClient): P
  * @param client The matrix client
  * @returns a promise that resolves when all rooms have been marked as read
  */
-export function clearAllNotifications(client: MatrixClient): Promise<Array<{} | undefined>> {
-    const receiptPromises = client.getRooms().reduce((promises: Array<Promise<{} | undefined>>, room: Room) => {
-        if (doesRoomHaveUnreadMessages(room, true)) {
-            const promise = clearRoomNotification(room, client);
-            promises.push(promise);
-        }
-
-        return promises;
-    }, []);
+export function clearAllNotifications(client: MatrixClient): Promise<Array<EmptyObject | undefined>> {
+    const receiptPromises = client
+        .getRooms()
+        .reduce((promises: Array<Promise<EmptyObject | undefined>>, room: Room) => {
+            if (doesRoomHaveUnreadMessages(room, true)) {
+                const promise = clearRoomNotification(room, client);
+                promises.push(promise);
+            }
+
+            return promises;
+        }, []);
 
     return Promise.all(receiptPromises);
 }
diff --git a/src/utils/objects.ts b/src/utils/objects.ts
index a919699eac6f9216ccb5b646f783eef024a09e76..60bf70240477102ba8766120e435f33bea35be3b 100644
--- a/src/utils/objects.ts
+++ b/src/utils/objects.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { arrayDiff, arrayUnion, arrayIntersection } from "./arrays";
 
-type ObjectExcluding<O extends {}, P extends (keyof O)[]> = { [k in Exclude<keyof O, P[number]>]: O[k] };
+type ObjectExcluding<O extends object, P extends (keyof O)[]> = { [k in Exclude<keyof O, P[number]>]: O[k] };
 
 /**
  * Gets a new object which represents the provided object, excluding some properties.
@@ -16,7 +16,7 @@ type ObjectExcluding<O extends {}, P extends (keyof O)[]> = { [k in Exclude<keyo
  * @param props The property names to remove.
  * @returns The new object without the provided properties.
  */
-export function objectExcluding<O extends {}, P extends Array<keyof O>>(a: O, props: P): ObjectExcluding<O, P> {
+export function objectExcluding<O extends object, P extends Array<keyof O>>(a: O, props: P): ObjectExcluding<O, P> {
     // We use a Map to avoid hammering the `delete` keyword, which is slow and painful.
     const tempMap = new Map<keyof O, any>(Object.entries(a) as [keyof O, any][]);
     for (const prop of props) {
@@ -37,7 +37,7 @@ export function objectExcluding<O extends {}, P extends Array<keyof O>>(a: O, pr
  * @param props The property names to keep.
  * @returns The new object with only the provided properties.
  */
-export function objectWithOnly<O extends {}, P extends Array<keyof O>>(a: O, props: P): { [k in P[number]]: O[k] } {
+export function objectWithOnly<O extends object, P extends Array<keyof O>>(a: O, props: P): { [k in P[number]]: O[k] } {
     const existingProps = Object.keys(a) as (keyof O)[];
     const diff = arrayDiff(existingProps, props);
     if (diff.removed.length === 0) {
@@ -58,7 +58,7 @@ export function objectWithOnly<O extends {}, P extends Array<keyof O>>(a: O, pro
  * First argument is the property key with the second being the current value.
  * @returns A cloned object.
  */
-export function objectShallowClone<O extends {}>(a: O, propertyCloner?: (k: keyof O, v: O[keyof O]) => any): O {
+export function objectShallowClone<O extends object>(a: O, propertyCloner?: (k: keyof O, v: O[keyof O]) => any): O {
     const newObj = {} as O;
     for (const [k, v] of Object.entries(a) as [keyof O, O[keyof O]][]) {
         newObj[k] = v;
@@ -77,7 +77,7 @@ export function objectShallowClone<O extends {}>(a: O, propertyCloner?: (k: keyo
  * @param b The second object. Must be defined.
  * @returns True if there's a difference between the objects, false otherwise
  */
-export function objectHasDiff<O extends {}>(a: O, b: O): boolean {
+export function objectHasDiff<O extends object>(a: O, b: O): boolean {
     if (a === b) return false;
     const aKeys = Object.keys(a);
     const bKeys = Object.keys(b);
@@ -99,7 +99,7 @@ type Diff<K> = { changed: K[]; added: K[]; removed: K[] };
  * @param b The second object. Must be defined.
  * @returns The difference between the keys of each object.
  */
-export function objectDiff<O extends {}>(a: O, b: O): Diff<keyof O> {
+export function objectDiff<O extends object>(a: O, b: O): Diff<keyof O> {
     const aKeys = Object.keys(a) as (keyof O)[];
     const bKeys = Object.keys(b) as (keyof O)[];
     const keyDiff = arrayDiff(aKeys, bKeys);
@@ -118,7 +118,7 @@ export function objectDiff<O extends {}>(a: O, b: O): Diff<keyof O> {
  * @returns The keys which have been added, removed, or changed between the
  * two objects.
  */
-export function objectKeyChanges<O extends {}>(a: O, b: O): (keyof O)[] {
+export function objectKeyChanges<O extends object>(a: O, b: O): (keyof O)[] {
     const diff = objectDiff(a, b);
     return arrayUnion(diff.removed, diff.added, diff.changed);
 }
@@ -130,7 +130,7 @@ export function objectKeyChanges<O extends {}>(a: O, b: O): (keyof O)[] {
  * @param obj The object to clone.
  * @returns The cloned object
  */
-export function objectClone<O extends {}>(obj: O): O {
+export function objectClone<O extends object>(obj: O): O {
     return JSON.parse(JSON.stringify(obj));
 }
 
diff --git a/src/utils/oidc/TokenRefresher.ts b/src/utils/oidc/TokenRefresher.ts
index 7c6848e39e70f7a71128883545405d71ef86fba8..a525f9fd1d79a00b8e8d6d74834f8fd5bdffedac 100644
--- a/src/utils/oidc/TokenRefresher.ts
+++ b/src/utils/oidc/TokenRefresher.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { OidcTokenRefresher, AccessTokens } from "matrix-js-sdk/src/matrix";
-import { IdTokenClaims } from "oidc-client-ts";
+import { OidcTokenRefresher, type AccessTokens } from "matrix-js-sdk/src/matrix";
+import { type IdTokenClaims } from "oidc-client-ts";
 
 import PlatformPeg from "../../PlatformPeg";
 import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "../tokens/tokens";
diff --git a/src/utils/oidc/authorize.ts b/src/utils/oidc/authorize.ts
index 50c9e07228b986965a7e57d01e890e4d8f2b0c75..d409396db92706485f466d84d890f7d76a44ed64 100644
--- a/src/utils/oidc/authorize.ts
+++ b/src/utils/oidc/authorize.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { completeAuthorizationCodeGrant, generateOidcAuthorizationUrl } from "matrix-js-sdk/src/oidc/authorize";
-import { QueryDict } from "matrix-js-sdk/src/utils";
-import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
-import { randomString } from "matrix-js-sdk/src/randomstring";
-import { IdTokenClaims } from "oidc-client-ts";
+import { type QueryDict } from "matrix-js-sdk/src/utils";
+import { type OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
+import { type IdTokenClaims } from "oidc-client-ts";
 
 import { OidcClientError } from "./error";
 import PlatformPeg from "../../PlatformPeg";
@@ -34,12 +34,12 @@ export const startOidcLogin = async (
 ): Promise<void> => {
     const redirectUri = PlatformPeg.get()!.getOidcCallbackUrl().href;
 
-    const nonce = randomString(10);
+    const nonce = secureRandomString(10);
 
     const prompt = isRegistration ? "create" : undefined;
 
     const authorizationUrl = await generateOidcAuthorizationUrl({
-        metadata: delegatedAuthConfig.metadata,
+        metadata: delegatedAuthConfig,
         redirectUri,
         clientId,
         homeserverUrl,
diff --git a/src/utils/oidc/error.ts b/src/utils/oidc/error.ts
index 94313648a2599705a88a690c8327b80e6becd4d3..f9334a739c50e1d7b3ced10f8b6858d1563ee8db 100644
--- a/src/utils/oidc/error.ts
+++ b/src/utils/oidc/error.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactNode } from "react";
+import { type ReactNode } from "react";
 import { OidcError } from "matrix-js-sdk/src/oidc/error";
 
 import { _t } from "../../languageHandler";
diff --git a/src/utils/oidc/isUserRegistrationSupported.ts b/src/utils/oidc/isUserRegistrationSupported.ts
index 8c91ee543b9d2545ee3bb2a6dbc96f72c9e2d26d..805206635d1a5a0eaa71706ca7342468c93bc40f 100644
--- a/src/utils/oidc/isUserRegistrationSupported.ts
+++ b/src/utils/oidc/isUserRegistrationSupported.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { type OidcClientConfig } from "matrix-js-sdk/src/matrix";
 
 /**
  * Check the create prompt is supported by the OP, if so, we can do a registration flow
@@ -15,8 +15,6 @@ import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
  * @returns whether user registration is supported
  */
 export const isUserRegistrationSupported = (delegatedAuthConfig: OidcClientConfig): boolean => {
-    // The OidcMetadata type from oidc-client-ts does not include `prompt_values_supported`
-    // even though it is part of the OIDC spec, so cheat TS here to access it
-    const supportedPrompts = (delegatedAuthConfig.metadata as Record<string, unknown>)["prompt_values_supported"];
+    const supportedPrompts = delegatedAuthConfig.prompt_values_supported;
     return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create");
 };
diff --git a/src/utils/oidc/persistOidcSettings.ts b/src/utils/oidc/persistOidcSettings.ts
index 6ca1295e0362bd93d367ce7a983fc27eae761282..38426e79cb638e8211fe6895ae23f5f114125436 100644
--- a/src/utils/oidc/persistOidcSettings.ts
+++ b/src/utils/oidc/persistOidcSettings.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IdTokenClaims } from "oidc-client-ts";
+import { type IdTokenClaims } from "oidc-client-ts";
 import { decodeIdToken } from "matrix-js-sdk/src/matrix";
 
 const clientIdStorageKey = "mx_oidc_client_id";
diff --git a/src/utils/oidc/registerClient.ts b/src/utils/oidc/registerClient.ts
index 61ec4ee3f297b66106ac7aa0a9adc4f520ccdf3f..92b7cf57d01b14b3c5768037f12a1a11e8a622e9 100644
--- a/src/utils/oidc/registerClient.ts
+++ b/src/utils/oidc/registerClient.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { registerOidcClient, OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { registerOidcClient, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
 
-import { IConfigOptions } from "../../IConfigOptions";
+import { type IConfigOptions } from "../../IConfigOptions";
 import PlatformPeg from "../../PlatformPeg";
 
 /**
@@ -40,9 +40,9 @@ export const getOidcClientId = async (
     delegatedAuthConfig: OidcClientConfig,
     staticOidcClients?: IConfigOptions["oidc_static_clients"],
 ): Promise<string> => {
-    const staticClientId = getStaticOidcClientId(delegatedAuthConfig.metadata.issuer, staticOidcClients);
+    const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, staticOidcClients);
     if (staticClientId) {
-        logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.metadata.issuer}`);
+        logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`);
         return staticClientId;
     }
     return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata());
diff --git a/src/utils/pages.ts b/src/utils/pages.ts
index efa25c803e7c1153759d40a742ec426086d42211..4181e987f9955ced81896536e646181bd9bd1ee5 100644
--- a/src/utils/pages.ts
+++ b/src/utils/pages.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import { IConfigOptions } from "../IConfigOptions";
+import { type IConfigOptions } from "../IConfigOptions";
 import { getEmbeddedPagesWellKnown } from "../utils/WellKnownUtils";
 import { SnakedObject } from "./SnakedObject";
 
diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts
index 644118949780f5227abf7f5e28f2a2d5247bbbcd..0df4b4beb12683e208e767d59b5987c9d0523030 100644
--- a/src/utils/permalinks/Permalinks.ts
+++ b/src/utils/permalinks/Permalinks.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import isIp from "is-ip";
 import * as utils from "matrix-js-sdk/src/utils";
-import { Room, MatrixClient, RoomStateEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { type Room, type MatrixClient, RoomStateEvent, EventType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -16,7 +16,8 @@ import MatrixToPermalinkConstructor, {
     baseUrl as matrixtoBaseUrl,
     baseUrlPattern as matrixToBaseUrlPattern,
 } from "./MatrixToPermalinkConstructor";
-import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
+import { type PermalinkParts } from "./PermalinkConstructor";
+import type PermalinkConstructor from "./PermalinkConstructor";
 import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
 import SdkConfig from "../../SdkConfig";
 import { ELEMENT_URL_PATTERN } from "../../linkify-matrix";
diff --git a/src/utils/pillify.tsx b/src/utils/pillify.tsx
deleted file mode 100644
index efcc9937467d95826c922e62971aed69b7680ee1..0000000000000000000000000000000000000000
--- a/src/utils/pillify.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2019-2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { StrictMode } from "react";
-import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
-import { MatrixClient, MatrixEvent, RuleId } from "matrix-js-sdk/src/matrix";
-import { TooltipProvider } from "@vector-im/compound-web";
-
-import SettingsStore from "../settings/SettingsStore";
-import { Pill, pillRoomNotifLen, pillRoomNotifPos, PillType } from "../components/views/elements/Pill";
-import { parsePermalink } from "./permalinks/Permalinks";
-import { PermalinkParts } from "./permalinks/PermalinkConstructor";
-import { ReactRootManager } from "./react";
-
-/**
- * A node here is an A element with a href attribute tag.
- *
- * It should be pillified if the permalink parser returns a result and one of the following conditions match:
- * - Text content equals href. This is the case when sending a plain permalink inside a message.
- * - The link does not have the "linkified" class.
- *   Composer completions already create an A tag.
- *   Linkify will not linkify things again. → There won't be a "linkified" class.
- */
-const shouldBePillified = (node: Element, href: string, parts: PermalinkParts | null): boolean => {
-    // permalink parser didn't return any parts
-    if (!parts) return false;
-
-    const textContent = node.textContent;
-
-    // event permalink with custom label
-    if (parts.eventId && href !== textContent) return false;
-
-    return href === textContent || !node.classList.contains("linkified");
-};
-
-/**
- * Recurses depth-first through a DOM tree, converting matrix.to links
- * into pills based on the context of a given room.  Returns a list of
- * the resulting React nodes so they can be unmounted rather than leaking.
- *
- * @param matrixClient the client of the logged-in user
- * @param {Element[]} nodes - a list of sibling DOM nodes to traverse to try
- *   to turn into pills.
- * @param {MatrixEvent} mxEvent - the matrix event which the DOM nodes are
- *   part of representing.
- * @param {ReactRootManager} pills - an accumulator of the DOM nodes which contain
- *   React components which have been mounted as part of this.
- *   The initial caller should pass in an empty array to seed the accumulator.
- */
-export function pillifyLinks(
-    matrixClient: MatrixClient,
-    nodes: ArrayLike<Element>,
-    mxEvent: MatrixEvent,
-    pills: ReactRootManager,
-): void {
-    const room = matrixClient.getRoom(mxEvent.getRoomId()) ?? undefined;
-    const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
-    let node = nodes[0];
-    while (node) {
-        let pillified = false;
-
-        if (node.tagName === "PRE" || node.tagName === "CODE" || pills.elements.includes(node)) {
-            // Skip code blocks and existing pills
-            node = node.nextSibling as Element;
-            continue;
-        } else if (node.tagName === "A" && node.getAttribute("href")) {
-            const href = node.getAttribute("href")!;
-            const parts = parsePermalink(href);
-
-            if (shouldBePillified(node, href, parts)) {
-                const pillContainer = document.createElement("span");
-
-                const pill = (
-                    <StrictMode>
-                        <TooltipProvider>
-                            <Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />
-                        </TooltipProvider>
-                    </StrictMode>
-                );
-
-                pills.render(pill, pillContainer, node);
-                node.replaceWith(pillContainer);
-                // Pills within pills aren't going to go well, so move on
-                pillified = true;
-
-                // update the current node with one that's now taken its place
-                node = pillContainer;
-            }
-        } else if (
-            node.nodeType === Node.TEXT_NODE &&
-            // as applying pills happens outside of react, make sure we're not doubly
-            // applying @room pills here, as a rerender with the same content won't touch the DOM
-            // to clear the pills from the last run of pillifyLinks
-            !node.parentElement?.classList.contains("mx_AtRoomPill")
-        ) {
-            let currentTextNode = node as Node as Text | null;
-            const roomNotifTextNodes: Text[] = [];
-
-            // Take a textNode and break it up to make all the instances of @room their
-            // own textNode, adding those nodes to roomNotifTextNodes
-            while (currentTextNode !== null) {
-                const roomNotifPos = pillRoomNotifPos(currentTextNode.textContent);
-                let nextTextNode: Text | null = null;
-                if (roomNotifPos > -1) {
-                    let roomTextNode = currentTextNode;
-
-                    if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos);
-                    if (roomTextNode.textContent && roomTextNode.textContent.length > pillRoomNotifLen()) {
-                        nextTextNode = roomTextNode.splitText(pillRoomNotifLen());
-                    }
-                    roomNotifTextNodes.push(roomTextNode);
-                }
-                currentTextNode = nextTextNode;
-            }
-
-            if (roomNotifTextNodes.length > 0) {
-                const pushProcessor = new PushProcessor(matrixClient);
-                const atRoomRule = pushProcessor.getPushRuleById(
-                    mxEvent.getContent()["m.mentions"] !== undefined ? RuleId.IsRoomMention : RuleId.AtRoomNotification,
-                );
-                if (atRoomRule && pushProcessor.ruleMatchesEvent(atRoomRule, mxEvent)) {
-                    // Now replace all those nodes with Pills
-                    for (const roomNotifTextNode of roomNotifTextNodes) {
-                        // Set the next node to be processed to the one after the node
-                        // we're adding now, since we've just inserted nodes into the structure
-                        // we're iterating over.
-                        // Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once
-                        node = roomNotifTextNode.nextSibling as Element;
-
-                        const pillContainer = document.createElement("span");
-                        const pill = (
-                            <StrictMode>
-                                <TooltipProvider>
-                                    <Pill
-                                        type={PillType.AtRoomMention}
-                                        inMessage={true}
-                                        room={room}
-                                        shouldShowPillAvatar={shouldShowPillAvatar}
-                                    />
-                                </TooltipProvider>
-                            </StrictMode>
-                        );
-
-                        pills.render(pill, pillContainer, roomNotifTextNode);
-                        roomNotifTextNode.replaceWith(pillContainer);
-                    }
-                    // Nothing else to do for a text node (and we don't need to advance
-                    // the loop pointer because we did it above)
-                    continue;
-                }
-            }
-        }
-
-        if (node.childNodes && node.childNodes.length && !pillified) {
-            pillifyLinks(matrixClient, node.childNodes as NodeListOf<Element>, mxEvent, pills);
-        }
-
-        node = node.nextSibling as Element;
-    }
-}
diff --git a/src/utils/presence.ts b/src/utils/presence.ts
index 798efdde5dcbd0a8885a8c8c31735f0577ab2def..9942ab8a3f093166e1281ebd0d1c5c0a218fce8f 100644
--- a/src/utils/presence.ts
+++ b/src/utils/presence.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../SdkConfig";
 
diff --git a/src/utils/pushRules/monitorSyncedPushRules.ts b/src/utils/pushRules/monitorSyncedPushRules.ts
index fcc41aae82add0d420df900b2490beb1a26565c2..0e9620fe1350b4c244206b9cd7384a99792ca40a 100644
--- a/src/utils/pushRules/monitorSyncedPushRules.ts
+++ b/src/utils/pushRules/monitorSyncedPushRules.ts
@@ -6,15 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, EventType, RuleId, IAnnotatedPushRule } from "matrix-js-sdk/src/matrix";
-import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+import {
+    type MatrixClient,
+    type MatrixEvent,
+    EventType,
+    type RuleId,
+    type IAnnotatedPushRule,
+    type IPushRule,
+    type PushRuleKind,
+} from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { VectorPushRulesDefinitions, VectorPushRuleDefinition } from "../../notifications";
+import { VectorPushRulesDefinitions, type VectorPushRuleDefinition } from "../../notifications";
 import { updateExistingPushRulesWithActions } from "./updatePushRuleActions";
 
 const pushRuleAndKindToAnnotated = (
-    ruleAndKind: ReturnType<PushProcessor["getPushRuleAndKindById"]>,
+    ruleAndKind: { rule: IPushRule; kind: PushRuleKind } | null,
 ): IAnnotatedPushRule | undefined =>
     ruleAndKind
         ? {
@@ -28,23 +35,21 @@ const pushRuleAndKindToAnnotated = (
  * And updates any that are out of sync
  * Ignores ruleIds that do not exist for the user
  * @param matrixClient - cli
- * @param pushProcessor - processor used to retrieve current state of rules
  * @param ruleId - primary rule
  * @param definition - VectorPushRuleDefinition of the primary rule
  */
 const monitorSyncedRule = async (
     matrixClient: MatrixClient,
-    pushProcessor: PushProcessor,
     ruleId: RuleId | string,
     definition: VectorPushRuleDefinition,
 ): Promise<void> => {
-    const primaryRule = pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId));
+    const primaryRule = pushRuleAndKindToAnnotated(matrixClient.pushProcessor.getPushRuleAndKindById(ruleId));
 
     if (!primaryRule) {
         return;
     }
     const syncedRules: IAnnotatedPushRule[] | undefined = definition.syncedRuleIds
-        ?.map((ruleId) => pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId)))
+        ?.map((ruleId) => pushRuleAndKindToAnnotated(matrixClient.pushProcessor.getPushRuleAndKindById(ruleId)))
         .filter((n?: IAnnotatedPushRule): n is IAnnotatedPushRule => Boolean(n));
 
     // no synced rules to manage
@@ -88,11 +93,10 @@ export const monitorSyncedPushRules = async (
     if (accountDataEvent?.getType() !== EventType.PushRules) {
         return;
     }
-    const pushProcessor = new PushProcessor(matrixClient);
 
     Object.entries(VectorPushRulesDefinitions).forEach(async ([ruleId, definition]) => {
         try {
-            await monitorSyncedRule(matrixClient, pushProcessor, ruleId, definition);
+            await monitorSyncedRule(matrixClient, ruleId, definition);
         } catch (error) {
             logger.error(`Failed to fully synchronise push rules for ${ruleId}`, error);
         }
diff --git a/src/utils/pushRules/updatePushRuleActions.ts b/src/utils/pushRules/updatePushRuleActions.ts
index e7ce8f7e8ea3f8fb374f7c0b3e760b5901804f39..8a383cace6988a22ce02efd1f59d89ad76dddd8a 100644
--- a/src/utils/pushRules/updatePushRuleActions.ts
+++ b/src/utils/pushRules/updatePushRuleActions.ts
@@ -6,8 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, IPushRule, PushRuleAction, PushRuleKind } from "matrix-js-sdk/src/matrix";
-import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+import { type MatrixClient, type IPushRule, type PushRuleAction, type PushRuleKind } from "matrix-js-sdk/src/matrix";
 
 /**
  * Sets the actions for a given push rule id and kind
@@ -51,10 +50,8 @@ export const updateExistingPushRulesWithActions = async (
     ruleIds?: IPushRule["rule_id"][],
     actions?: PushRuleAction[],
 ): Promise<void> => {
-    const pushProcessor = new PushProcessor(matrixClient);
-
     const rules: PushRuleAndKind[] | undefined = ruleIds
-        ?.map((ruleId) => pushProcessor.getPushRuleAndKindById(ruleId))
+        ?.map((ruleId) => matrixClient.pushProcessor.getPushRuleAndKindById(ruleId))
         .filter((n: PushRuleAndKind | null): n is PushRuleAndKind => Boolean(n));
 
     if (!rules?.length) {
diff --git a/src/utils/react.tsx b/src/utils/react.tsx
deleted file mode 100644
index 435485214d53bd46bc7b25f2b1aefe2a0c013c30..0000000000000000000000000000000000000000
--- a/src/utils/react.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { ReactNode } from "react";
-import { createRoot, Root } from "react-dom/client";
-
-/**
- * Utility class to render & unmount additional React roots,
- * e.g. for pills, tooltips and other components rendered atop user-generated events.
- */
-export class ReactRootManager {
-    private roots: Root[] = [];
-    private rootElements: Element[] = [];
-    private revertElements: Array<Node | null> = [];
-
-    public get elements(): Element[] {
-        return this.rootElements;
-    }
-
-    /**
-     * Render a React component into a new root based on the given root element
-     * @param children the React component to render
-     * @param rootElement the root element to render the component into
-     * @param revertElement the element to replace the root element with when unmounting
-     *     needed to support double-rendering in React 18 Strict Dev mode
-     */
-    public render(children: ReactNode, rootElement: Element, revertElement: Node | null): void {
-        const root = createRoot(rootElement);
-        this.roots.push(root);
-        this.rootElements.push(rootElement);
-        this.revertElements.push(revertElement);
-        root.render(children);
-    }
-
-    /**
-     * Unmount all roots and revert the elements they were rendered into
-     */
-    public unmount(): void {
-        while (this.roots.length) {
-            const root = this.roots.pop()!;
-            const rootElement = this.rootElements.pop();
-            const revertElement = this.revertElements.pop();
-            root.unmount();
-            if (revertElement) {
-                rootElement?.replaceWith(revertElement);
-            }
-        }
-    }
-}
diff --git a/src/utils/read-receipts.ts b/src/utils/read-receipts.ts
index 51a4d6705e9b9d8750c32a54feae25db50b82928..ee949fa034f1643011cf503fe3b40296dd5df322 100644
--- a/src/utils/read-receipts.ts
+++ b/src/utils/read-receipts.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
 
 /**
diff --git a/src/utils/room/canInviteTo.ts b/src/utils/room/canInviteTo.ts
index 3d0b0c8f50f65c2ef6a9b464a12dfe5dbd03f4f6..f6bb76c1e050e95f6c81248468cc56a8f9dbdf94 100644
--- a/src/utils/room/canInviteTo.ts
+++ b/src/utils/room/canInviteTo.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { JoinRule, Room } from "matrix-js-sdk/src/matrix";
+import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
diff --git a/src/utils/room/getFunctionalMembers.ts b/src/utils/room/getFunctionalMembers.ts
index f107fc0d1aef4bc42e5aae548b8cea51627ab68d..ac9859b28aeeb479ef060a203e4a800c65c0debe 100644
--- a/src/utils/room/getFunctionalMembers.ts
+++ b/src/utils/room/getFunctionalMembers.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
+import { type Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
 
 export const getFunctionalMembers = (room: Room): string[] => {
     const [functionalUsersStateEvent] = room.currentState.getStateEvents(UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name);
diff --git a/src/utils/room/getJoinedNonFunctionalMembers.ts b/src/utils/room/getJoinedNonFunctionalMembers.ts
index b8434987532419aa00710b99b2aa59dc97a0eb7d..8c8ac9106903c530fb9b56d12d0a60c2dce2d117 100644
--- a/src/utils/room/getJoinedNonFunctionalMembers.ts
+++ b/src/utils/room/getJoinedNonFunctionalMembers.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
 
 import { getFunctionalMembers } from "./getFunctionalMembers";
 
diff --git a/src/utils/room/inviteToRoom.ts b/src/utils/room/inviteToRoom.ts
index c7010ec94744459cdf884d24e56298b9a1879e84..4435acc4cf2c063b68a2f49d166f2a1721e1dc92 100644
--- a/src/utils/room/inviteToRoom.ts
+++ b/src/utils/room/inviteToRoom.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../dispatcher/dispatcher";
 
diff --git a/src/utils/room/placeCall.ts b/src/utils/room/placeCall.ts
index 04043fc3d31f61e8f57be247b8440cfd72c1419e..1f0d67c1e6f989b11e5bcb98cc5b42c4b5256fc2 100644
--- a/src/utils/room/placeCall.ts
+++ b/src/utils/room/placeCall.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { CallType } from "matrix-js-sdk/src/webrtc/call";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type CallType } from "matrix-js-sdk/src/webrtc/call";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import LegacyCallHandler from "../../LegacyCallHandler";
 import { getPlatformCallTypeProps, PlatformCallType } from "../../hooks/room/useRoomCall";
 import defaultDispatcher from "../../dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../dispatcher/actions";
 import PosthogTrackers from "../../PosthogTrackers";
 
diff --git a/src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite.ts b/src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite.ts
index 4192f0af1ada964bd716e23ff6a6f89f53158dc1..a86f15974ad732a0ee2f816e3f70515d99a418b4 100644
--- a/src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite.ts
+++ b/src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 
 import DMRoomMap from "../DMRoomMap";
 import { privateShouldBeEncrypted } from "../rooms";
diff --git a/src/utils/room/tagRoom.ts b/src/utils/room/tagRoom.ts
index 7ed707d874182323d35d8115c034305c1b3ede67..716dd959a867f4dd9f8e5d137152d8de5ed53dd9 100644
--- a/src/utils/room/tagRoom.ts
+++ b/src/utils/room/tagRoom.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import RoomListStore from "../../stores/room-list/RoomListStore";
-import { DefaultTagID, TagID } from "../../stores/room-list/models";
+import { DefaultTagID, type TagID } from "../../stores/room-list/models";
 import RoomListActions from "../../actions/RoomListActions";
 import dis from "../../dispatcher/dispatcher";
 
diff --git a/src/utils/rooms.ts b/src/utils/rooms.ts
index d32e2e933f1d6b8373692965192948b40123a2f4..b0d2a60422a43837779e81ffdee6cb818201b2ac 100644
--- a/src/utils/rooms.ts
+++ b/src/utils/rooms.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { shouldForceDisableEncryption } from "./crypto/shouldForceDisableEncryption";
 import { getE2EEWellKnown } from "./WellKnownUtils";
diff --git a/src/utils/sets.ts b/src/utils/sets.ts
index 0d871f6b26ea04e5fb4b7a3f6c40fe245a5608fe..6a635326e4cb7e11a54bd16fd8c21704d098f652 100644
--- a/src/utils/sets.ts
+++ b/src/utils/sets.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { arrayDiff, Diff } from "./arrays";
+import { arrayDiff, type Diff } from "./arrays";
 
 /**
  * Determines if two sets are different through a shallow comparison.
diff --git a/src/utils/space.tsx b/src/utils/space.tsx
index 9c3e0bc40c73c5e4f64f472ce8f2605c94c25b63..230c783322b480dc14a32517a6e3c6fcd5d99253 100644
--- a/src/utils/space.tsx
+++ b/src/utils/space.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room, ICreateRoomStateEvent, RoomType, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
+import { type Room, type ICreateRoomStateEvent, type RoomType, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { calculateRoomVia } from "./permalinks/Permalinks";
@@ -25,9 +25,12 @@ import { Action } from "../dispatcher/actions";
 import Spinner from "../components/views/elements/Spinner";
 import { shouldShowComponent } from "../customisations/helpers/UIComponents";
 import { UIComponent } from "../settings/UIFeature";
-import { OpenSpacePreferencesPayload, SpacePreferenceTab } from "../dispatcher/payloads/OpenSpacePreferencesPayload";
-import { OpenSpaceSettingsPayload } from "../dispatcher/payloads/OpenSpaceSettingsPayload";
-import { OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/OpenAddExistingToSpaceDialogPayload";
+import {
+    type OpenSpacePreferencesPayload,
+    type SpacePreferenceTab,
+} from "../dispatcher/payloads/OpenSpacePreferencesPayload";
+import { type OpenSpaceSettingsPayload } from "../dispatcher/payloads/OpenSpaceSettingsPayload";
+import { type OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/OpenAddExistingToSpaceDialogPayload";
 import { SdkContextClass } from "../contexts/SDKContext";
 
 export const shouldShowSpaceSettings = (space: Room): boolean => {
@@ -72,7 +75,7 @@ export const showCreateNewRoom = async (space: Room, type?: RoomType): Promise<b
     });
     const [shouldCreate, opts] = await modal.finished;
     if (shouldCreate) {
-        await createRoom(space.client, opts);
+        await createRoom(space.client, opts!);
     }
     return !!shouldCreate;
 };
@@ -103,35 +106,35 @@ export const showSpaceInvite = (space: Room, initialText = ""): void => {
 };
 
 export const showAddExistingSubspace = (space: Room): void => {
-    Modal.createDialog(
+    const { finished } = Modal.createDialog(
         AddExistingSubspaceDialog,
         {
             space,
             onCreateSubspaceClick: () => showCreateNewSubspace(space),
-            onFinished: (added: boolean) => {
-                if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
-                    defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
-                }
-            },
         },
         "mx_AddExistingToSpaceDialog_wrapper",
     );
+    finished.then(([added]) => {
+        if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
+            defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+        }
+    });
 };
 
 export const showCreateNewSubspace = (space: Room): void => {
-    Modal.createDialog(
+    const { finished } = Modal.createDialog(
         CreateSubspaceDialog,
         {
             space,
             onAddExistingSpaceClick: () => showAddExistingSubspace(space),
-            onFinished: (added: boolean) => {
-                if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
-                    defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
-                }
-            },
         },
         "mx_CreateSubspaceDialog_wrapper",
     );
+    finished.then(([added]) => {
+        if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
+            defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+        }
+    });
 };
 
 export const bulkSpaceBehaviour = async (
diff --git a/src/utils/threepids.ts b/src/utils/threepids.ts
index 5882c51c189697e22de610d59d53c64026fccb1d..cfd5235fbb0b638e030e17f67df3ac75bfff31f1 100644
--- a/src/utils/threepids.ts
+++ b/src/utils/threepids.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import { DirectoryMember, Member, ThreepidMember } from "./direct-messages";
+import { DirectoryMember, type Member, ThreepidMember } from "./direct-messages";
 
 /**
  * Tries to resolve the ThreepidMembers to DirectoryMembers.
diff --git a/src/utils/tokens/tokens.ts b/src/utils/tokens/tokens.ts
index a25117beb8dba9a45badeffe661cd0fc01a174c9..9716e5af8a213f37d17f80b3c1e8ed178052811f 100644
--- a/src/utils/tokens/tokens.ts
+++ b/src/utils/tokens/tokens.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import { logger } from "matrix-js-sdk/src/logger";
 import decryptAESSecretStorageItem from "matrix-js-sdk/src/utils/decryptAESSecretStorageItem";
 import encryptAESSecretStorageItem from "matrix-js-sdk/src/utils/encryptAESSecretStorageItem";
-import { AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types";
+import { type AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types";
 
 import * as StorageAccess from "../StorageAccess";
 
diff --git a/src/utils/tooltipify.tsx b/src/utils/tooltipify.tsx
deleted file mode 100644
index 26d43cf033009e6881539e489a9afc2993803441..0000000000000000000000000000000000000000
--- a/src/utils/tooltipify.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React, { StrictMode } from "react";
-import { TooltipProvider } from "@vector-im/compound-web";
-
-import PlatformPeg from "../PlatformPeg";
-import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
-import { ReactRootManager } from "./react";
-
-/**
- * If the platform enabled needsUrlTooltips, recurses depth-first through a DOM tree, adding tooltip previews
- * for link elements. Otherwise, does nothing.
- *
- * @param {Element[]} rootNodes - a list of sibling DOM nodes to traverse to try
- *   to add tooltips.
- * @param {Element[]} ignoredNodes - a list of nodes to not recurse into.
- * @param {ReactRootManager} tooltips - an accumulator of the DOM nodes which contain
- *   React components that have been mounted by this function. The initial caller
- *   should pass in an empty array to seed the accumulator.
- */
-export function tooltipifyLinks(
-    rootNodes: ArrayLike<Element>,
-    ignoredNodes: Element[],
-    tooltips: ReactRootManager,
-): void {
-    if (!PlatformPeg.get()?.needsUrlTooltips()) {
-        return;
-    }
-
-    let node = rootNodes[0];
-
-    while (node) {
-        if (ignoredNodes.includes(node) || tooltips.elements.includes(node)) {
-            node = node.nextSibling as Element;
-            continue;
-        }
-
-        if (
-            node.tagName === "A" &&
-            node.getAttribute("href") &&
-            node.getAttribute("href") !== node.textContent?.trim()
-        ) {
-            let href = node.getAttribute("href")!;
-            try {
-                href = new URL(href, window.location.href).toString();
-            } catch {
-                // Not all hrefs will be valid URLs
-            }
-
-            // The node's innerHTML was already sanitized before being rendered in the first place, here we are just
-            // wrapping the link with the LinkWithTooltip component, keeping the same children. Ideally we'd do this
-            // without the superfluous span but this is not something React trivially supports at this time.
-            const tooltip = (
-                <StrictMode>
-                    <TooltipProvider>
-                        <LinkWithTooltip tooltip={href}>
-                            <span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
-                        </LinkWithTooltip>
-                    </TooltipProvider>
-                </StrictMode>
-            );
-
-            tooltips.render(tooltip, node, null);
-        } else if (node.childNodes?.length) {
-            tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes, tooltips);
-        }
-
-        node = node.nextSibling as Element;
-    }
-}
diff --git a/src/vector/app.tsx b/src/vector/app.tsx
index 2ae9e6fa03fa0f1744461ef986b880823bdc7cc6..d0c689a2b4a785495410bef215306ec0c6547b6c 100644
--- a/src/vector/app.tsx
+++ b/src/vector/app.tsx
@@ -12,28 +12,25 @@ Please see LICENSE files in the repository root for full details.
 
 // To ensure we load the browser-matrix version first
 import "matrix-js-sdk/src/browser-index";
-import React, { ReactElement, StrictMode } from "react";
+import React, { type ReactElement, StrictMode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { createClient, AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/matrix";
-import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
+import { createClient, AutoDiscovery, type ClientConfig } from "matrix-js-sdk/src/matrix";
+import { WrapperLifecycle, type WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
 
+import type { QueryDict } from "matrix-js-sdk/src/utils";
 import PlatformPeg from "../PlatformPeg";
 import AutoDiscoveryUtils from "../utils/AutoDiscoveryUtils";
 import * as Lifecycle from "../Lifecycle";
 import SdkConfig, { parseSsoRedirectOptions } from "../SdkConfig";
-import { IConfigOptions } from "../IConfigOptions";
+import { type IConfigOptions } from "../IConfigOptions";
 import { SnakedObject } from "../utils/SnakedObject";
 import MatrixChat from "../components/structures/MatrixChat";
-import { ValidatedServerConfig } from "../utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../utils/ValidatedServerConfig";
 import { ModuleRunner } from "../modules/ModuleRunner";
 import { parseQs } from "./url_utils";
 import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
 import { UserFriendlyError } from "../languageHandler";
 
-// add React and ReactPerf to the global namespace, to make them easier to access via the console
-// this incidentally means we can forget our React imports in JSX files without penalty.
-window.React = React;
-
 logger.log(`Application is running in ${process.env.NODE_ENV} mode`);
 
 window.matrixLogger = logger;
@@ -54,7 +51,7 @@ function onTokenLoginCompleted(): void {
     window.history.replaceState(null, "", url.href);
 }
 
-export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixChat>): Promise<ReactElement> {
+export async function loadApp(fragParams: QueryDict, matrixChatRef: React.Ref<MatrixChat>): Promise<ReactElement> {
     initRouting();
     const platform = PlatformPeg.get();
 
diff --git a/src/vector/index.ts b/src/vector/index.ts
index c398c0b788630bf9fa6ac6dbb7e1a0f33015adbf..943ed49c6a80439d65482916324043e86b360ee7 100644
--- a/src/vector/index.ts
+++ b/src/vector/index.ts
@@ -49,6 +49,8 @@ function checkBrowserFeatures(): boolean {
     window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
     // ES2020: http://262.ecma-international.org/#sec-promise.allsettled
     window.Modernizr.addTest("promiseallsettled", () => typeof window.Promise?.allSettled === "function");
+    // ES2024: https://2ality.com/2024/05/proposal-promise-with-resolvers.html
+    window.Modernizr.addTest("promisewithresolvers", () => typeof window.Promise?.withResolvers === "function");
     // ES2018: https://262.ecma-international.org/9.0/#sec-get-regexp.prototype.dotAll
     window.Modernizr.addTest(
         "regexpdotall",
@@ -114,6 +116,7 @@ async function start(): Promise<void> {
         loadTheme,
         loadApp,
         loadModules,
+        loadPlugins,
         showError,
         showIncompatibleBrowser,
         _t,
@@ -159,19 +162,18 @@ async function start(): Promise<void> {
         // now that the config is ready, try to persist logs
         const persistLogsPromise = setupLogStorage();
 
-        // Load modules before language to ensure any custom translations are respected, and any app
-        // startup functionality is run
-        const loadModulesPromise = loadModules();
-        await settled(loadModulesPromise);
-
         // Load language after loading config.json so that settingsDefaults.language can be applied
         const loadLanguagePromise = loadLanguage();
         // as quickly as we possibly can, set a default theme...
         const loadThemePromise = loadTheme();
-
         // await things settling so that any errors we have to render have features like i18n running
         await settled(loadThemePromise, loadLanguagePromise);
 
+        const loadModulesPromise = loadModules();
+        await settled(loadModulesPromise);
+        const loadPluginsPromise = loadPlugins();
+        await settled(loadPluginsPromise);
+
         let acceptBrowser = supportedBrowser;
         if (!acceptBrowser && window.localStorage) {
             acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
@@ -215,6 +217,7 @@ async function start(): Promise<void> {
         // app load critical path starts here
         // assert things started successfully
         // ##################################
+        await loadPluginsPromise;
         await loadModulesPromise;
         await loadThemePromise;
         await loadLanguagePromise;
diff --git a/src/vector/init.tsx b/src/vector/init.tsx
index 34f5b9fc08cab412b6948bc016aa6ef449beb8e0..e481e34b97f9e00b89c74d95170356242b6ce241 100644
--- a/src/vector/init.tsx
+++ b/src/vector/init.tsx
@@ -11,18 +11,21 @@ Please see LICENSE files in the repository root for full details.
 import { createRoot } from "react-dom/client";
 import React, { StrictMode } from "react";
 import { logger } from "matrix-js-sdk/src/logger";
+import { ModuleLoader } from "@element-hq/element-web-module-api";
 
+import type { QueryDict } from "matrix-js-sdk/src/utils";
 import * as languageHandler from "../languageHandler";
 import SettingsStore from "../settings/SettingsStore";
 import PlatformPeg from "../PlatformPeg";
 import SdkConfig from "../SdkConfig";
 import { setTheme } from "../theme";
 import { ModuleRunner } from "../modules/ModuleRunner";
-import MatrixChat from "../components/structures/MatrixChat";
+import type MatrixChat from "../components/structures/MatrixChat";
 import ElectronPlatform from "./platform/ElectronPlatform";
 import PWAPlatform from "./platform/PWAPlatform";
 import WebPlatform from "./platform/WebPlatform";
 import { initRageshake, initRageshakeStore } from "./rageshakesetup";
+import ModuleApi from "../modules/Api.ts";
 
 export const rageshakePromise = initRageshake();
 
@@ -72,7 +75,7 @@ export async function loadLanguage(): Promise<void> {
         langs = [prefLang];
     }
     try {
-        await languageHandler.setLanguage(langs);
+        await languageHandler.setLanguage(...langs);
         document.documentElement.setAttribute("lang", languageHandler.getCurrentLanguage());
     } catch (e) {
         logger.error("Unable to set language", e);
@@ -83,7 +86,7 @@ export async function loadTheme(): Promise<void> {
     return setTheme();
 }
 
-export async function loadApp(fragParams: {}): Promise<void> {
+export async function loadApp(fragParams: QueryDict): Promise<void> {
     // load app.js async so that its code is not executed immediately and we can catch any exceptions
     const module = await import(
         /* webpackChunkName: "element-web-app" */
@@ -124,17 +127,34 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
     );
 }
 
+/**
+ * @deprecated in favour of the plugin system
+ */
 export async function loadModules(): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore - this path is created at runtime and therefore won't exist at typecheck time
-    const { INSTALLED_MODULES } = await import("../modules");
+    const { INSTALLED_MODULES } = await import("../modules.js");
     for (const InstalledModule of INSTALLED_MODULES) {
-        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-        // @ts-ignore - we know the constructor exists even if TypeScript can't be convinced of that
         ModuleRunner.instance.registerModule((api) => new InstalledModule(api));
     }
 }
 
+export async function loadPlugins(): Promise<void> {
+    // Add React to the global namespace, this is part of the new Module API contract to avoid needing
+    // every single module to ship its own copy of React. This also makes it easier to access via the console
+    // and incidentally means we can forget our React imports in JSX files without penalty.
+    window.React = React;
+
+    const modules = SdkConfig.get("modules");
+    if (!modules?.length) return;
+    const moduleLoader = new ModuleLoader(ModuleApi);
+    window.mxModuleLoader = moduleLoader;
+    for (const src of modules) {
+        // We need to instruct webpack to not mangle this import as it is not available at compile time
+        const module = await import(/* webpackIgnore: true */ src);
+        await moduleLoader.load(module);
+    }
+    await moduleLoader.start();
+}
+
 export { _t } from "../languageHandler";
 
 export { extractErrorMessageFromError } from "../components/views/dialogs/ErrorDialog";
diff --git a/src/vector/jitsi/index.ts b/src/vector/jitsi/index.ts
index 273e80488c61e318ae64d9b411f1a22c8a868785..a5ea8cba6d8386d175662d916823911de08f9a85 100644
--- a/src/vector/jitsi/index.ts
+++ b/src/vector/jitsi/index.ts
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 
 import { KJUR } from "jsrsasign";
 import {
-    IOpenIDCredentials,
-    IWidgetApiRequest,
-    IWidgetApiRequestData,
-    IWidgetApiResponseData,
+    type IOpenIDCredentials,
+    type IWidgetApiRequest,
+    type IWidgetApiRequestData,
+    type IWidgetApiResponseData,
     VideoConferenceCapabilities,
     WidgetApi,
-    WidgetApiAction,
+    type WidgetApiAction,
 } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -28,7 +28,7 @@ import type {
     InterfaceConfig as _InterfaceConfig,
 } from "jitsi-meet";
 import { ElementWidgetActions } from "../../stores/widgets/ElementWidgetActions";
-import { IConfigOptions } from "../../IConfigOptions";
+import { type IConfigOptions } from "../../IConfigOptions";
 import { SnakedObject } from "../../utils/SnakedObject";
 import { ElementWidgetCapabilities } from "../../stores/widgets/ElementWidgetCapabilities";
 import { getVectorConfig } from "../getconfig";
diff --git a/src/vector/mobile_guide/index.html b/src/vector/mobile_guide/index.html
index b4d67ba2d3715fbed3317a9f76c9a509b9038668..d58842d6a64c9c1b229565a0979527ab648ec661 100644
--- a/src/vector/mobile_guide/index.html
+++ b/src/vector/mobile_guide/index.html
@@ -16,7 +16,8 @@
                 background: #f9fafb;
                 max-width: 680px;
                 margin: auto;
-                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
+                font-family:
+                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
                     "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
             }
 
@@ -781,6 +782,8 @@
             <div class="mx_HomePage_row mx_Center mx_Spacer">
                 <p class="mx_Spacer">
                     <a id="back_to_element_button" href="#" class="mx_FooterLink"> Go to Desktop Site </a>
+                    <br />
+                    Please note the Desktop site does <b>not</b> work on mobile.
                 </p>
             </div>
         </div>
diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx
index 6a6409ea42f3b4cfd305368a8531636b19c8be5e..c8b544daf1508c6684c56a358ffcef89de7ecabc 100644
--- a/src/vector/platform/ElectronPlatform.tsx
+++ b/src/vector/platform/ElectronPlatform.tsx
@@ -10,24 +10,28 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, MatrixEvent, OidcRegistrationClientMetadata } from "matrix-js-sdk/src/matrix";
+import {
+    type MatrixClient,
+    type Room,
+    type MatrixEvent,
+    type OidcRegistrationClientMetadata,
+} from "matrix-js-sdk/src/matrix";
 import React from "react";
-import { randomString } from "matrix-js-sdk/src/randomstring";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
-import BaseEventIndexManager from "../../indexing/BaseEventIndexManager";
+import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform";
+import type BaseEventIndexManager from "../../indexing/BaseEventIndexManager";
 import dis from "../../dispatcher/dispatcher";
 import SdkConfig from "../../SdkConfig";
-import { IConfigOptions } from "../../IConfigOptions";
+import { type IConfigOptions } from "../../IConfigOptions";
 import * as rageshake from "../../rageshake/rageshake";
 import Modal from "../../Modal";
 import InfoDialog from "../../components/views/dialogs/InfoDialog";
 import Spinner from "../../components/views/elements/Spinner";
 import { Action } from "../../dispatcher/actions";
-import { ActionPayload } from "../../dispatcher/payloads";
+import { type ActionPayload } from "../../dispatcher/payloads";
 import { showToast as showUpdateToast } from "../../toasts/UpdateToast";
-import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
+import { type CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
 import ToastStore from "../../stores/ToastStore";
 import GenericExpiringToast from "../../components/views/toasts/GenericExpiringToast";
 import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
@@ -92,8 +96,10 @@ function getUpdateCheckStatus(status: boolean | string): UpdateStatus {
 export default class ElectronPlatform extends BasePlatform {
     private readonly ipc = new IPCManager("ipcCall", "ipcReply");
     private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
-    // this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
-    private readonly ssoID: string = randomString(32);
+    private readonly initialised: Promise<void>;
+    private protocol!: string;
+    private sessionId!: string;
+    private config!: IConfigOptions;
 
     public constructor() {
         super();
@@ -181,13 +187,21 @@ export default class ElectronPlatform extends BasePlatform {
             await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
         });
 
-        void this.ipc.call("startSSOFlow", this.ssoID);
-
         BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
+
+        this.initialised = this.initialise();
+    }
+
+    private async initialise(): Promise<void> {
+        const { protocol, sessionId, config } = await window.electron!.initialise();
+        this.protocol = protocol;
+        this.sessionId = sessionId;
+        this.config = config;
     }
 
     public async getConfig(): Promise<IConfigOptions | undefined> {
-        return this.ipc.call("getConfig");
+        await this.initialised;
+        return this.config;
     }
 
     private onBreadcrumbsUpdate = (): void => {
@@ -386,7 +400,7 @@ export default class ElectronPlatform extends BasePlatform {
     public getSSOCallbackUrl(fragmentAfterLogin?: string): URL {
         const url = super.getSSOCallbackUrl(fragmentAfterLogin);
         url.protocol = "element";
-        url.searchParams.set(SSO_ID_KEY, this.ssoID);
+        url.searchParams.set(SSO_ID_KEY, this.sessionId);
         return url;
     }
 
@@ -464,7 +478,7 @@ export default class ElectronPlatform extends BasePlatform {
     }
 
     public getOidcClientState(): string {
-        return `:${SSO_ID_KEY}:${this.ssoID}`;
+        return `:${SSO_ID_KEY}:${this.sessionId}`;
     }
 
     /**
@@ -472,9 +486,9 @@ export default class ElectronPlatform extends BasePlatform {
      */
     public getOidcCallbackUrl(): URL {
         const url = super.getOidcCallbackUrl();
-        url.protocol = "io.element.desktop";
+        url.protocol = this.protocol;
         // Trim the double slash into a single slash to comply with https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
-        if (url.href.startsWith(`${url.protocol}://`)) {
+        if (url.href.startsWith(`${url.protocol}//`)) {
             url.href = url.href.replace("://", ":/");
         }
         return url;
diff --git a/src/vector/platform/IPCManager.ts b/src/vector/platform/IPCManager.ts
index 7d329b8b2cbbb187ebf85bf817991910de0643ae..dc183f0cfb5d0a7fede7d8adb70ee0bed6524d5d 100644
--- a/src/vector/platform/IPCManager.ts
+++ b/src/vector/platform/IPCManager.ts
@@ -5,10 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { defer, IDeferred } from "matrix-js-sdk/src/utils";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { ElectronChannel } from "../../@types/global";
+import { type ElectronChannel } from "../../@types/global";
 
 interface IPCPayload {
     id?: number;
@@ -17,7 +16,7 @@ interface IPCPayload {
 }
 
 export class IPCManager {
-    private pendingIpcCalls: { [ipcCallId: number]: IDeferred<any> } = {};
+    private pendingIpcCalls: { [ipcCallId: number]: PromiseWithResolvers<any> } = {};
     private nextIpcCallId = 0;
 
     public constructor(
@@ -33,14 +32,14 @@ export class IPCManager {
     public async call(name: string, ...args: any[]): Promise<any> {
         // TODO this should be moved into the preload.js file.
         const ipcCallId = ++this.nextIpcCallId;
-        const deferred = defer<any>();
+        const deferred = Promise.withResolvers<any>();
         this.pendingIpcCalls[ipcCallId] = deferred;
         // Maybe add a timeout to these? Probably not necessary.
         window.electron!.send(this.sendChannel, { id: ipcCallId, name, args });
         return deferred.promise;
     }
 
-    private onIpcReply = (_ev: {}, payload: IPCPayload): void => {
+    private onIpcReply = (_ev: Event, payload: IPCPayload): void => {
         if (payload.id === undefined) {
             logger.warn("Ignoring IPC reply with no ID");
             return;
diff --git a/src/vector/platform/SeshatIndexManager.ts b/src/vector/platform/SeshatIndexManager.ts
index 2f281812f67724e6d61813dc9237ad36c517e54a..88a8889b722e81995bf20151f3b0e61aceefd6f4 100644
--- a/src/vector/platform/SeshatIndexManager.ts
+++ b/src/vector/platform/SeshatIndexManager.ts
@@ -6,14 +6,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 // eslint-disable-next-line no-restricted-imports
-import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search";
+import {
+    type IMatrixProfile,
+    type IEventWithRoomId as IMatrixEvent,
+    type IResultRoomEvents,
+} from "matrix-js-sdk/src/@types/search";
 
 import BaseEventIndexManager, {
-    ICrawlerCheckpoint,
-    IEventAndProfile,
-    IIndexStats,
-    ISearchArgs,
-    ILoadArgs,
+    type ICrawlerCheckpoint,
+    type IEventAndProfile,
+    type IIndexStats,
+    type ISearchArgs,
+    type ILoadArgs,
 } from "../../indexing/BaseEventIndexManager";
 import { IPCManager } from "./IPCManager";
 
diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts
index ff575cb7b36a83576aef6b0bc9c8419204b08474..46b6a61f836e391a9322436a255c1c6198057436 100644
--- a/src/vector/platform/WebPlatform.ts
+++ b/src/vector/platform/WebPlatform.ts
@@ -11,13 +11,17 @@ import UAParser from "ua-parser-js";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import { MatrixClientPeg } from "../../MatrixClientPeg";
-import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
+import BasePlatform, { UpdateCheckStatus, type UpdateStatus } from "../../BasePlatform";
 import dis from "../../dispatcher/dispatcher";
 import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast";
 import { Action } from "../../dispatcher/actions";
-import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
+import { type CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload";
 import { parseQs } from "../url_utils";
 import { _t } from "../../languageHandler";
+import ToastStore from "../../stores/ToastStore.ts";
+import GenericToast from "../../components/views/toasts/GenericToast.tsx";
+import SdkConfig from "../../SdkConfig.ts";
+import type { ActionPayload } from "../../dispatcher/payloads.ts";
 
 const POKE_RATE_MS = 10 * 60 * 1000; // 10 min
 
@@ -32,32 +36,59 @@ function getNormalizedAppVersion(version: string): string {
 
 export default class WebPlatform extends BasePlatform {
     private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
+    private readonly registerServiceWorkerPromise: Promise<void>;
 
     public constructor() {
         super();
 
         // Register the service worker in the background
-        this.tryRegisterServiceWorker().catch((e) => console.error("Error registering/updating service worker:", e));
+        this.registerServiceWorkerPromise = this.registerServiceWorker();
+        this.registerServiceWorkerPromise.catch((e) => {
+            console.error("Error registering/updating service worker:", e);
+        });
     }
 
-    private async tryRegisterServiceWorker(): Promise<void> {
-        if (!("serviceWorker" in navigator)) {
-            return; // not available on this platform - don't try to register the service worker
+    protected onAction(payload: ActionPayload): void {
+        super.onAction(payload);
+
+        switch (payload.action) {
+            case "client_started":
+                // Defer drawing the toast until the client is started as the lifecycle methods reset the ToastStore right before
+                this.registerServiceWorkerPromise.catch(this.handleServiceWorkerRegistrationError);
+                break;
         }
+    }
 
+    private async registerServiceWorker(): Promise<void> {
         // sw.js is exported by webpack, sourced from `/src/serviceworker/index.ts`
         const registration = await navigator.serviceWorker.register("sw.js");
         if (!registration) {
-            // Registration didn't work for some reason - assume failed and ignore.
-            // This typically happens in Jest.
-            return;
+            throw new Error("Service worker registration failed");
         }
 
-        navigator.serviceWorker.addEventListener("message", this.onServiceWorkerPostMessage.bind(this));
+        navigator.serviceWorker.addEventListener("message", this.onServiceWorkerPostMessage);
         await registration.update();
     }
 
-    private onServiceWorkerPostMessage(event: MessageEvent): void {
+    private handleServiceWorkerRegistrationError = (): void => {
+        const key = "service_worker_error";
+        const brand = SdkConfig.get().brand;
+        ToastStore.sharedInstance().addOrReplaceToast({
+            key,
+            title: _t("service_worker_error|title"),
+            props: {
+                description: _t("service_worker_error|description", { brand }),
+                primaryLabel: _t("action|ok"),
+                onPrimaryClick: () => {
+                    ToastStore.sharedInstance().dismissToast(key);
+                },
+            },
+            component: GenericToast,
+            priority: 95,
+        });
+    };
+
+    private onServiceWorkerPostMessage = (event: MessageEvent): void => {
         try {
             if (event.data?.["type"] === "userinfo" && event.data?.["responseKey"]) {
                 const userId = localStorage.getItem("mx_user_id");
@@ -73,7 +104,7 @@ export default class WebPlatform extends BasePlatform {
         } catch (e) {
             console.error("Error responding to service worker: ", e);
         }
-    }
+    };
 
     public getHumanReadableName(): string {
         return "Web Platform"; // no translation required: only used for analytics
diff --git a/src/vector/routing.ts b/src/vector/routing.ts
index 98da8817365961ace49ebddc57b35e1a066aac64..964c0c0684542fcd8f4a9d60d78bdd91b70312ae 100644
--- a/src/vector/routing.ts
+++ b/src/vector/routing.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 // Parse the given window.location and return parameters that can be used when calling
 // MatrixChat.showScreen(screen, params)
 import { logger } from "matrix-js-sdk/src/logger";
-import { QueryDict } from "matrix-js-sdk/src/utils";
+import { type QueryDict } from "matrix-js-sdk/src/utils";
 
 import { parseQsFromFragment } from "./url_utils";
 
diff --git a/src/vector/static/incompatible-browser.html b/src/vector/static/incompatible-browser.html
index 38d3ee18af0e44443dbaf746c915753744099431..6515b82eea85720ad1cedbb91f127abace05b515 100644
--- a/src/vector/static/incompatible-browser.html
+++ b/src/vector/static/incompatible-browser.html
@@ -16,7 +16,8 @@
             background: #f9fafb;
             max-width: 680px;
             margin: auto;
-            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
+            font-family:
+                -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
                 "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
         }
 
diff --git a/src/vector/static/unable-to-load.html b/src/vector/static/unable-to-load.html
index 5cb007a1a72175720cd03e36c76da2179eb86ae0..0c79bd8969693de8aef92640c88927cc140ae3bd 100644
--- a/src/vector/static/unable-to-load.html
+++ b/src/vector/static/unable-to-load.html
@@ -16,7 +16,8 @@
             background: #f9fafb;
             max-width: 680px;
             margin: auto;
-            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
+            font-family:
+                -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
                 "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
         }
 
diff --git a/src/vector/url_utils.ts b/src/vector/url_utils.ts
index 82430a10c87264919aa3cf11f76dddd5b4769dd8..2b5202806e4ad338db988d819e9ecf39ca1bba4e 100644
--- a/src/vector/url_utils.ts
+++ b/src/vector/url_utils.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { QueryDict, decodeParams } from "matrix-js-sdk/src/utils";
+import { type QueryDict, decodeParams } from "matrix-js-sdk/src/utils";
 
 // We want to support some name / value pairs in the fragment
 // so we're re-using query string like format
diff --git a/src/verification.ts b/src/verification.ts
index 90dacbf6ac2d7377941cd85508f568afd755c411..5dc3ea29790ccfd4469c1ca4fcf6dd900bcb7a06 100644
--- a/src/verification.ts
+++ b/src/verification.ts
@@ -6,17 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { User, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
-import { CrossSigningKey, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
+import { type User, type MatrixClient, type RoomMember } from "matrix-js-sdk/src/matrix";
+import { CrossSigningKey, type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
 
 import dis from "./dispatcher/dispatcher";
-import Modal from "./Modal";
 import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
 import { accessSecretStorage } from "./SecurityManager";
-import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog";
-import { IDevice } from "./components/views/right_panel/UserInfo";
 import RightPanelStore from "./stores/right-panel/RightPanelStore";
-import { IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
+import { type IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
 import { findDMForUser } from "./utils/dm/findDMForUser";
 
 async function enable4SIfNeeded(matrixClient: MatrixClient): Promise<boolean> {
@@ -31,32 +28,6 @@ async function enable4SIfNeeded(matrixClient: MatrixClient): Promise<boolean> {
     return true;
 }
 
-export async function verifyDevice(matrixClient: MatrixClient, user: User, device: IDevice): Promise<void> {
-    if (matrixClient.isGuest()) {
-        dis.dispatch({ action: "require_registration" });
-        return;
-    }
-    // if cross-signing is not explicitly disabled, check if it should be enabled first.
-    if (matrixClient.getCrypto()?.getTrustCrossSignedDevices()) {
-        if (!(await enable4SIfNeeded(matrixClient))) {
-            return;
-        }
-    }
-
-    Modal.createDialog(UntrustedDeviceDialog, {
-        user,
-        device,
-        onFinished: async (action): Promise<void> => {
-            if (action === "sas") {
-                const verificationRequestPromise = matrixClient
-                    .getCrypto()
-                    ?.requestDeviceVerification(user.userId, device.deviceId);
-                setRightPanel({ member: user, verificationRequestPromise });
-            }
-        },
-    });
-}
-
 export async function verifyUser(matrixClient: MatrixClient, user: User): Promise<void> {
     if (matrixClient.isGuest()) {
         dis.dispatch({ action: "require_registration" });
diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx
index ea910c21c0ef87e6f8e54f71de8401490972a661..effaa0975bacc87861d35c079de73912c7f969e1 100644
--- a/src/widgets/CapabilityText.tsx
+++ b/src/widgets/CapabilityText.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    Capability,
+    type Capability,
     EventDirection,
     EventKind,
     getTimelineRoomIDFromCapability,
@@ -21,7 +21,7 @@ import {
 import { EventType, MsgType } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
-import { _t, _td, TranslatedString, TranslationKey } from "../languageHandler";
+import { _t, _td, type TranslatedString, type TranslationKey } from "../languageHandler";
 import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities";
 import { MatrixClientPeg } from "../MatrixClientPeg";
 import TextWithTooltip from "../components/views/elements/TextWithTooltip";
diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts
index e76e86157d8953a25d9ee74d4785baf3c387d3d6..c3eecb6a5c422337f3f6464b2cf5e655c8371e13 100644
--- a/src/widgets/Jitsi.ts
+++ b/src/widgets/Jitsi.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type IClientWellKnown } from "matrix-js-sdk/src/matrix";
 
 import SdkConfig from "../SdkConfig";
 import { MatrixClientPeg } from "../MatrixClientPeg";
diff --git a/src/widgets/ManagedHybrid.ts b/src/widgets/ManagedHybrid.ts
index b4ef4cba8b3f98f5057883c5489ca49cd499ebd3..b1a67db4fb498ace9f3de9c636b423db4040c59a 100644
--- a/src/widgets/ManagedHybrid.ts
+++ b/src/widgets/ManagedHybrid.ts
@@ -6,16 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IWidget } from "matrix-widget-api";
+import { type IWidget } from "matrix-widget-api";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import { MatrixClientPeg } from "../MatrixClientPeg";
 import { getCallBehaviourWellKnown } from "../utils/WellKnownUtils";
 import WidgetUtils from "../utils/WidgetUtils";
-import { IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
+import { type IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
 import WidgetEchoStore from "../stores/WidgetEchoStore";
-import WidgetStore, { IApp } from "../stores/WidgetStore";
+import WidgetStore, { type IApp } from "../stores/WidgetStore";
 import SdkConfig from "../SdkConfig";
 import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
 
@@ -37,7 +36,7 @@ function getWidgetBuildUrl(room: Room): string | undefined {
         return SdkConfig.get().widget_build_url;
     }
 
-    const wellKnown = getCallBehaviourWellKnown(MatrixClientPeg.safeGet());
+    const wellKnown = getCallBehaviourWellKnown(room.client);
     if (isDm && wellKnown?.ignore_dm) {
         return undefined;
     }
diff --git a/src/workers/blurhash.worker.ts b/src/workers/blurhash.worker.ts
index 9d9c2fbf52e4c7d4da14d469b3be164ea83a5132..a9adfd4f8eb513cbcccb139b1a69a2f04ca3de12 100644
--- a/src/workers/blurhash.worker.ts
+++ b/src/workers/blurhash.worker.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { encode } from "blurhash";
 
-import { WorkerPayload } from "./worker";
+import { type WorkerPayload } from "./worker";
 
 const ctx: Worker = self as any;
 
diff --git a/src/workers/playback.worker.ts b/src/workers/playback.worker.ts
index 47479e6adb0f879d078a95ce37dbdf206c2e206f..3c77f9a9b45485107a64e7edeaec6be4002aea9a 100644
--- a/src/workers/playback.worker.ts
+++ b/src/workers/playback.worker.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { WorkerPayload } from "./worker";
+import { type WorkerPayload } from "./worker";
 import { arrayRescale, arraySmoothingResample } from "../utils/arrays";
 import { PLAYBACK_WAVEFORM_SAMPLES } from "../audio/consts";
 
diff --git a/test/CreateCrossSigning-test.ts b/test/CreateCrossSigning-test.ts
index 6863b0500ec6a0346688ae584c4ba015f17c6dc1..cd2bf904cd66ddf045ded4174c2cee6249ad510c 100644
--- a/test/CreateCrossSigning-test.ts
+++ b/test/CreateCrossSigning-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { HTTPError, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { HTTPError, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import { createCrossSigning } from "../src/CreateCrossSigning";
diff --git a/test/app-tests/wrapper-test.tsx b/test/app-tests/wrapper-test.tsx
index 47e2ad2ce1d6de3a79df609f17adc0b03bf19fbf..10297542968e704e51a81e2063a4d2607430284c 100644
--- a/test/app-tests/wrapper-test.tsx
+++ b/test/app-tests/wrapper-test.tsx
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import fetchMock from "fetch-mock-jest";
-import { render, RenderResult, screen } from "jest-matrix-react";
-import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
+import { render, type RenderResult, screen } from "jest-matrix-react";
+import { WrapperLifecycle, type WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
 
 import SdkConfig from "../../src/SdkConfig";
 import PlatformPeg from "../../src/PlatformPeg";
diff --git a/test/components/views/dialogs/ModalWidgetDialog-test.tsx b/test/components/views/dialogs/ModalWidgetDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..134aa46ad69695929c4522e5d3fc78dd615b1a06
--- /dev/null
+++ b/test/components/views/dialogs/ModalWidgetDialog-test.tsx
@@ -0,0 +1,56 @@
+/*
+Copyright 2024 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { fireEvent, render } from "jest-matrix-react";
+import { ClientWidgetApi, MatrixWidgetType } from "matrix-widget-api";
+import React from "react";
+import { TooltipProvider } from "@vector-im/compound-web";
+import { mocked } from "jest-mock";
+import { findLast, last } from "lodash";
+
+import ModalWidgetDialog from "../../../../src/components/views/dialogs/ModalWidgetDialog";
+import { stubClient } from "../../../test-utils";
+import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../src/dispatcher/actions";
+import SettingsStore from "../../../../src/settings/SettingsStore";
+
+jest.mock("matrix-widget-api", () => ({
+    ...jest.requireActual("matrix-widget-api"),
+    ClientWidgetApi: (jest.createMockFromModule("matrix-widget-api") as any).ClientWidgetApi,
+}));
+
+describe("ModalWidgetDialog", () => {
+    it("informs the widget of theme changes", () => {
+        stubClient();
+        let theme = "light";
+        const settingsSpy = jest
+            .spyOn(SettingsStore, "getValue")
+            .mockImplementation((name) => (name === "theme" ? theme : null));
+        try {
+            render(
+                <TooltipProvider>
+                    <ModalWidgetDialog
+                        widgetDefinition={{ type: MatrixWidgetType.Custom, url: "https://example.org" }}
+                        sourceWidgetId=""
+                        onFinished={() => {}}
+                    />
+                </TooltipProvider>,
+            );
+            // Indicate that the widget is loaded and ready
+            fireEvent.load(document.getElementsByTagName("iframe").item(0)!);
+            const messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
+            findLast(messaging.once.mock.calls, ([eventName]) => eventName === "ready")![1]();
+
+            // Now change the theme
+            theme = "dark";
+            defaultDispatcher.dispatch({ action: Action.RecheckTheme }, true);
+            expect(messaging.updateTheme).toHaveBeenLastCalledWith({ name: "dark" });
+        } finally {
+            settingsSpy.mockRestore();
+        }
+    });
+});
diff --git a/test/components/views/dialogs/security/ResetIdentityDialog-test.tsx b/test/components/views/dialogs/security/ResetIdentityDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5de2a0a0435e0cb670e703ec25311b0305b788f3
--- /dev/null
+++ b/test/components/views/dialogs/security/ResetIdentityDialog-test.tsx
@@ -0,0 +1,63 @@
+/*
+Copyright 2024 New Vector Ltd.
+Copyright 2018-2022 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { act } from "react";
+import { render } from "jest-matrix-react";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { type Mocked } from "jest-mock";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+
+import { getMockClientWithEventEmitter } from "../../../../test-utils";
+import { ResetIdentityDialog } from "../../../../../src/components/views/dialogs/ResetIdentityDialog";
+
+describe("ResetIdentityDialog", () => {
+    afterEach(() => {
+        jest.resetAllMocks();
+        jest.restoreAllMocks();
+    });
+
+    it("should call onReset and onFinished when we click Continue", async () => {
+        const client = mockClient();
+
+        const onFinished = jest.fn();
+        const onReset = jest.fn();
+        const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
+
+        await act(async () => dialog.getByRole("button", { name: "Continue" }).click());
+
+        expect(onReset).toHaveBeenCalled();
+        expect(onFinished).toHaveBeenCalled();
+
+        expect(client.getCrypto()?.resetEncryption).toHaveBeenCalled();
+    });
+
+    it("should call onFinished when we click Cancel", async () => {
+        const client = mockClient();
+
+        const onFinished = jest.fn();
+        const onReset = jest.fn();
+        const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
+
+        await act(async () => dialog.getByRole("button", { name: "Cancel" }).click());
+
+        expect(onFinished).toHaveBeenCalled();
+
+        expect(onReset).not.toHaveBeenCalled();
+        expect(client.getCrypto()?.resetEncryption).not.toHaveBeenCalled();
+    });
+});
+
+function mockClient(): Mocked<MatrixClient> {
+    const mockCrypto = {
+        resetEncryption: jest.fn().mockResolvedValue(null),
+    } as unknown as Mocked<CryptoApi>;
+
+    return getMockClientWithEventEmitter({
+        getCrypto: jest.fn().mockReturnValue(mockCrypto),
+    });
+}
diff --git a/test/components/views/dialogs/security/SetupEncryptionDialog-test.tsx b/test/components/views/dialogs/security/SetupEncryptionDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..885366c4e291d96b962bcc8d7fb0e62e86125966
--- /dev/null
+++ b/test/components/views/dialogs/security/SetupEncryptionDialog-test.tsx
@@ -0,0 +1,88 @@
+/*
+Copyright 2024 New Vector Ltd.
+Copyright 2018-2022 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React, { act } from "react";
+import { render, screen } from "jest-matrix-react";
+import { type Mocked } from "jest-mock";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
+
+import SetupEncryptionDialog from "../../../../../src/components/views/dialogs/security/SetupEncryptionDialog";
+import { getMockClientWithEventEmitter } from "../../../../test-utils";
+import { Phase, SetupEncryptionStore } from "../../../../../src/stores/SetupEncryptionStore";
+import Modal from "../../../../../src/Modal";
+
+describe("SetupEncryptionDialog", () => {
+    afterEach(() => {
+        jest.resetAllMocks();
+        jest.restoreAllMocks();
+    });
+
+    it("should launch a dialog when I say Proceed, then be finished when I reset", async () => {
+        mockClient();
+        const store = new SetupEncryptionStore();
+        jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
+
+        // Given when you open the reset dialog we immediately reset
+        jest.spyOn(Modal, "createDialog").mockImplementation((_, props) => {
+            // Simulate doing the reset in the dialog
+            props?.onReset();
+
+            return {
+                close: jest.fn(),
+                finished: Promise.resolve([]),
+            };
+        });
+
+        // When we launch the dialog and set it ready to start
+        const onFinished = jest.fn();
+        render(<SetupEncryptionDialog onFinished={onFinished} />);
+        await act(async () => await store.fetchKeyInfo());
+        expect(store.phase).toBe(Phase.Intro);
+
+        // And we hit the Proceed with reset button.
+        // (The createDialog mock above simulates the user doing the reset)
+        await act(async () => screen.getByRole("button", { name: "Proceed with reset" }).click());
+
+        // Then the phase has been set to Finished
+        expect(store.phase).toBe(Phase.Finished);
+    });
+});
+
+function mockClient() {
+    const mockCrypto = {
+        getDeviceVerificationStatus: jest.fn().mockResolvedValue({
+            crossSigningVerified: false,
+        }),
+        getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
+        isCrossSigningReady: jest.fn().mockResolvedValue(true),
+        isSecretStorageReady: jest.fn().mockResolvedValue(true),
+        userHasCrossSigningKeys: jest.fn(),
+        getActiveSessionBackupVersion: jest.fn(),
+        getCrossSigningStatus: jest.fn().mockReturnValue({
+            publicKeysOnDevice: true,
+            privateKeysInSecretStorage: true,
+            privateKeysCachedLocally: {
+                masterKey: true,
+                selfSigningKey: true,
+                userSigningKey: true,
+            },
+        }),
+        getSessionBackupPrivateKey: jest.fn(),
+        isEncryptionEnabledInRoom: jest.fn(),
+        getKeyBackupInfo: jest.fn().mockResolvedValue(null),
+        getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
+    } as unknown as Mocked<CryptoApi>;
+
+    const userId = "@user:server";
+
+    getMockClientWithEventEmitter({
+        getCrypto: jest.fn().mockReturnValue(mockCrypto),
+        getUserId: jest.fn().mockReturnValue(userId),
+        secretStorage: { isStored: jest.fn().mockReturnValue({}) },
+    });
+}
diff --git a/test/setupTests.ts b/test/setupTests.ts
index 5d1740f5f49067bd58a9eb00a4c5daa6e6cb727b..144f27e15af2fbd3b810d9c5ebd09f702bea37a6 100644
--- a/test/setupTests.ts
+++ b/test/setupTests.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import "@testing-library/jest-dom";
 import "blob-polyfill";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 import { mocked } from "jest-mock";
 
 import { PredictableRandom } from "./test-utils/predictableRandom"; // https://github.com/jsdom/jsdom/issues/2555
@@ -25,7 +25,8 @@ jest.mock("matrix-js-sdk/src/randomstring");
 beforeEach(() => {
     const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
     const mockRandom = new PredictableRandom();
-    mocked(randomString).mockImplementation((len) => {
+    // needless to say, the mock is not cryptographically secure
+    mocked(secureRandomString).mockImplementation((len) => {
         let ret = "";
         for (let i = 0; i < len; ++i) {
             const v = mockRandom.get() * chars.length;
diff --git a/test/test-utils/audio.ts b/test/test-utils/audio.ts
index 5de94b529d8a247f4bf75574d2fae0b708eafe38..eb9f91bcc658e20cac65c3b520490bde15013a91 100644
--- a/test/test-utils/audio.ts
+++ b/test/test-utils/audio.ts
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
 import EventEmitter from "events";
 import { SimpleObservable } from "matrix-widget-api";
 
-import { Playback, PlaybackState } from "../../src/audio/Playback";
-import { PlaybackClock } from "../../src/audio/PlaybackClock";
+import { type Playback, PlaybackState } from "../../src/audio/Playback";
+import { type PlaybackClock } from "../../src/audio/PlaybackClock";
 import { UPDATE_EVENT } from "../../src/stores/AsyncStore";
-import { PublicInterface } from "../@types/common";
+import { type PublicInterface } from "../@types/common";
 
 export const createTestPlayback = (overrides: Partial<Playback> = {}): Playback => {
     const eventEmitter = new EventEmitter();
diff --git a/test/test-utils/beacon.ts b/test/test-utils/beacon.ts
index aca294546f14794d43924aa2d54c7f79ffa0a211..2688d223add5b5a641f0aadc2bcb9bab336dd10d 100644
--- a/test/test-utils/beacon.ts
+++ b/test/test-utils/beacon.ts
@@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MockedObject } from "jest-mock";
+import { type MockedObject } from "jest-mock";
 import {
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
-    Beacon,
+    type Beacon,
     getBeaconInfoIdentifier,
     ContentHelpers,
-    LocationAssetType,
+    type LocationAssetType,
     M_BEACON,
     M_BEACON_INFO,
 } from "matrix-js-sdk/src/matrix";
diff --git a/test/test-utils/call.ts b/test/test-utils/call.ts
index 04b46f1597987a5613d478173026a7143e76c225..a0a1c84536afba0c611ee0639d32f20322c76766 100644
--- a/test/test-utils/call.ts
+++ b/test/test-utils/call.ts
@@ -10,7 +10,7 @@ import { MatrixWidgetType } from "matrix-widget-api";
 
 import type { GroupCall, Room, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { mkEvent } from "./test-utils";
-import { Call, ConnectionState, ElementCall, JitsiCall } from "../../src/models/Call";
+import { Call, type ConnectionState, ElementCall, JitsiCall } from "../../src/models/Call";
 import { CallStore } from "../../src/stores/CallStore";
 
 export class MockedCall extends Call {
@@ -109,5 +109,5 @@ export class MockedCall extends Call {
 export const useMockedCalls = () => {
     Call.get = (room) => MockedCall.get(room);
     JitsiCall.create = async (room) => MockedCall.create(room, "1");
-    ElementCall.create = async (room) => MockedCall.create(room, "1");
+    ElementCall.create = (room) => MockedCall.create(room, "1");
 };
diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts
index f1b2cf967cab51fcb817f42c9d6b5cbb2e7afdaa..cdda7b7dea047618c2ba9a5d3287c02722e085c0 100644
--- a/test/test-utils/client.ts
+++ b/test/test-utils/client.ts
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import { MethodLikeKeys, mocked, MockedObject, PropertyLikeKeys } from "jest-mock";
+import { type MethodLikeKeys, mocked, type MockedObject, type PropertyLikeKeys } from "jest-mock";
 import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
-import { MatrixClient, Room, MatrixError, User } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room, MatrixError, User } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../src/MatrixClientPeg";
 
@@ -115,6 +115,16 @@ export const mockClientMethodsEvents = () => ({
     getPushActionsForEvent: jest.fn(),
 });
 
+/**
+ * Returns basic mocked pushProcessor
+ */
+export const mockClientPushProcessor = () => ({
+    pushProcessor: {
+        getPushRuleById: jest.fn(),
+        ruleMatchesEvent: jest.fn(),
+    },
+});
+
 /**
  * Returns basic mocked client methods related to server support
  */
@@ -143,7 +153,7 @@ export const mockClientMethodsCrypto = (): Partial<
 > => ({
     isKeyBackupKeyStored: jest.fn(),
     getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }),
-    secretStorage: { hasKey: jest.fn() },
+    secretStorage: { hasKey: jest.fn(), isStored: jest.fn().mockResolvedValue(null) },
     getCrypto: jest.fn().mockReturnValue({
         getUserDeviceInfo: jest.fn(),
         getCrossSigningStatus: jest.fn().mockResolvedValue({
diff --git a/test/test-utils/composer.ts b/test/test-utils/composer.ts
index 73a3d990cf0c7bcf4d9494a00c508c2adfcf23e4..6439e70fc2605605f3ec569794d3d4d8c015e558 100644
--- a/test/test-utils/composer.ts
+++ b/test/test-utils/composer.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { act, fireEvent, RenderResult } from "jest-matrix-react";
+import { act, fireEvent, type RenderResult } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 export const addTextToComposer = (container: HTMLElement, text: string) =>
diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts
index 0fd5797b3f77bed06cedbc75335b782425836ecf..d1298ca3dfa55e11fc549fe230a7237866dfee31 100644
--- a/test/test-utils/index.ts
+++ b/test/test-utils/index.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RenderResult, screen, waitFor } from "jest-matrix-react";
+import { type RenderResult, screen, waitFor } from "jest-matrix-react";
 
 export * from "./beacon";
 export * from "./client";
diff --git a/test/test-utils/jest-matrix-react.tsx b/test/test-utils/jest-matrix-react.tsx
index 765f84a5c9541d7bd4783b3e87b8a6c1a575796f..ed5ff0075e595d26452039d36f3d2240317260c2 100644
--- a/test/test-utils/jest-matrix-react.tsx
+++ b/test/test-utils/jest-matrix-react.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 // eslint-disable-next-line no-restricted-imports
-import { render, RenderOptions } from "@testing-library/react";
+import { render, type RenderOptions } from "@testing-library/react";
 import { TooltipProvider } from "@vector-im/compound-web";
 
 const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
diff --git a/test/test-utils/location.ts b/test/test-utils/location.ts
index 0b558b5b8c53f4ec8689d151947cc39df6901fdd..b14f19cf986770300551cec376727b8179591486 100644
--- a/test/test-utils/location.ts
+++ b/test/test-utils/location.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { LocationAssetType, M_LOCATION, MatrixEvent, EventType, ContentHelpers } from "matrix-js-sdk/src/matrix";
+import { type LocationAssetType, M_LOCATION, MatrixEvent, EventType, ContentHelpers } from "matrix-js-sdk/src/matrix";
 
 let id = 1;
 export const makeLegacyLocationEvent = (geoUri: string): MatrixEvent => {
diff --git a/test/test-utils/oidc.ts b/test/test-utils/oidc.ts
index 72cdad04ef1ab7b4764bbecbf506227829150b3e..c8032551b0c07d89d1a6e5db3bf1b5b91b58eab6 100644
--- a/test/test-utils/oidc.ts
+++ b/test/test-utils/oidc.ts
@@ -6,41 +6,4 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
-import { ValidatedIssuerMetadata } from "matrix-js-sdk/src/oidc/validate";
-
-/**
- * Makes a valid OidcClientConfig with minimum valid values
- * @param issuer used as the base for all other urls
- * @returns OidcClientConfig
- */
-export const makeDelegatedAuthConfig = (issuer = "https://auth.org/"): OidcClientConfig => {
-    const metadata = mockOpenIdConfiguration(issuer);
-
-    return {
-        accountManagementEndpoint: issuer + "account",
-        registrationEndpoint: metadata.registration_endpoint,
-        authorizationEndpoint: metadata.authorization_endpoint,
-        tokenEndpoint: metadata.token_endpoint,
-        metadata,
-    };
-};
-
-/**
- * Useful for mocking <issuer>/.well-known/openid-configuration
- * @param issuer used as the base for all other urls
- * @returns ValidatedIssuerMetadata
- */
-export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): ValidatedIssuerMetadata => ({
-    issuer,
-    revocation_endpoint: issuer + "revoke",
-    token_endpoint: issuer + "token",
-    authorization_endpoint: issuer + "auth",
-    registration_endpoint: issuer + "registration",
-    device_authorization_endpoint: issuer + "device",
-    jwks_uri: issuer + "jwks",
-    response_types_supported: ["code"],
-    grant_types_supported: ["authorization_code", "refresh_token"],
-    code_challenge_methods_supported: ["S256"],
-    account_management_uri: issuer + "account",
-});
+export { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "matrix-js-sdk/src/testing";
diff --git a/test/test-utils/platform.ts b/test/test-utils/platform.ts
index e360bb5e0ca0b5f9b9b12a2eb4116b2792820b3e..37698265764f57f59474a4902947c37ec6c07425 100644
--- a/test/test-utils/platform.ts
+++ b/test/test-utils/platform.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MethodLikeKeys, mocked, MockedObject } from "jest-mock";
+import { type MethodLikeKeys, mocked, type MockedObject } from "jest-mock";
 
 import BasePlatform from "../../src/BasePlatform";
 import PlatformPeg from "../../src/PlatformPeg";
diff --git a/test/test-utils/poll.ts b/test/test-utils/poll.ts
index b7fea58259906fd67b969e75ce3576f8ac450af4..a9896c62cadca1a10e56e26fbd0178dbc52fbd95 100644
--- a/test/test-utils/poll.ts
+++ b/test/test-utils/poll.ts
@@ -6,19 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked } from "jest-mock";
+import { type Mocked } from "jest-mock";
 import {
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
     Room,
     M_POLL_START,
-    PollAnswer,
+    type PollAnswer,
     M_POLL_KIND_DISCLOSED,
     M_POLL_END,
     M_POLL_RESPONSE,
     M_TEXT,
 } from "matrix-js-sdk/src/matrix";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 
 import { flushPromises } from "./utilities";
 
@@ -67,7 +67,7 @@ export const makePollEndEvent = (
     id?: string,
 ): MatrixEvent => {
     return new MatrixEvent({
-        event_id: id || randomString(16),
+        event_id: id || secureRandomString(16),
         room_id: roomId,
         origin_server_ts: ts,
         type: M_POLL_END.name,
@@ -91,7 +91,7 @@ export const makePollResponseEvent = (
     ts = 0,
 ): MatrixEvent =>
     new MatrixEvent({
-        event_id: randomString(16),
+        event_id: secureRandomString(16),
         room_id: roomId,
         origin_server_ts: ts,
         type: M_POLL_RESPONSE.name,
diff --git a/test/test-utils/pushRules.ts b/test/test-utils/pushRules.ts
index 15796f8a096ed7b6aecec553ac01404da95f9369..a7b050c8ba481abbd751fddda637e7dc68bfba85 100644
--- a/test/test-utils/pushRules.ts
+++ b/test/test-utils/pushRules.ts
@@ -6,7 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IAnnotatedPushRule, IPushRule, IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import {
+    type IAnnotatedPushRule,
+    type IPushRule,
+    type IPushRules,
+    type PushRuleKind,
+    type RuleId,
+} from "matrix-js-sdk/src/matrix";
 
 /**
  * Default set of push rules for a new account
diff --git a/test/test-utils/relations.ts b/test/test-utils/relations.ts
index 1acf16af008cba759400191efb793486d8cf2290..4bc5b26e4c30b5ad7c66612d384c9dadd39144c4 100644
--- a/test/test-utils/relations.ts
+++ b/test/test-utils/relations.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Relations } from "matrix-js-sdk/src/matrix";
-import { RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
+import { type Relations } from "matrix-js-sdk/src/matrix";
+import { type RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
 
-import { PublicInterface } from "../@types/common";
+import { type PublicInterface } from "../@types/common";
 
 export const mkRelations = (): Relations => {
     return {} as PublicInterface<Relations> as Relations;
diff --git a/test/test-utils/room.ts b/test/test-utils/room.ts
index 21c82c9ae3aa12dd0cb33ef08044eff744e27f98..d4c6466ef287254e701ed60adf5e56dfd9f536a1 100644
--- a/test/test-utils/room.ts
+++ b/test/test-utils/room.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MockedObject } from "jest-mock";
-import { EventTimeline, EventType, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MockedObject } from "jest-mock";
+import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
-import { IRoomState, MainSplitContentType } from "../../src/components/structures/RoomView";
+import { type IRoomState, MainSplitContentType } from "../../src/components/structures/RoomView";
 import { TimelineRenderingType } from "../../src/contexts/RoomContext";
 import { Layout } from "../../src/settings/enums/Layout";
 import { mkEvent } from "./test-utils";
@@ -80,7 +80,6 @@ export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoom
         canSelfRedact: false,
         resizing: false,
         narrow: false,
-        activeCall: null,
         msc3946ProcessDynamicPredecessor: false,
         canAskToJoin: false,
         promptAskToJoin: false,
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index 1809fa91c8c0fefb2bedc18853bd8279310b7152..8243fe5083e0e39681861474917f534ebb46f60d 100644
--- a/test/test-utils/test-utils.ts
+++ b/test/test-utils/test-utils.ts
@@ -7,43 +7,43 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EventEmitter from "events";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import {
     MatrixEvent,
-    Room,
-    User,
-    IContent,
-    IEvent,
-    RoomMember,
-    MatrixClient,
-    EventTimeline,
-    RoomState,
+    type Room,
+    type User,
+    type IContent,
+    type IEvent,
+    type RoomMember,
+    type MatrixClient,
+    type EventTimeline,
+    type RoomState,
     EventType,
-    IEventRelation,
-    IUnsigned,
-    IPusher,
+    type IEventRelation,
+    type IUnsigned,
+    type IPusher,
     RoomType,
     KNOWN_SAFE_ROOM_VERSION,
     ConditionKind,
-    IPushRules,
+    type IPushRules,
     RelationType,
     JoinRule,
-    OidcClientConfig,
+    type OidcClientConfig,
+    type GroupCall,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { normalize } from "matrix-js-sdk/src/utils";
 import { ReEmitter } from "matrix-js-sdk/src/ReEmitter";
-import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
+import { type MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
 import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
-import { MapperOpts } from "matrix-js-sdk/src/event-mapper";
-import { MatrixRTCSessionManager, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
+import { type MapperOpts } from "matrix-js-sdk/src/event-mapper";
+import { type MatrixRTCSessionManager, type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 
-import type { GroupCall } from "matrix-js-sdk/src/matrix";
 import type { Membership } from "matrix-js-sdk/src/types";
 import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
-import { ValidatedServerConfig } from "../../src/utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../src/utils/ValidatedServerConfig";
 import { EnhancedMap } from "../../src/utils/maps";
-import { AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient";
+import { type AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient";
 import MatrixClientBackedSettingsHandler from "../../src/settings/handlers/MatrixClientBackedSettingsHandler";
 
 /**
@@ -116,7 +116,7 @@ export function createTestClient(): MatrixClient {
         },
 
         getCrypto: jest.fn().mockReturnValue({
-            getOwnDeviceKeys: jest.fn(),
+            getOwnDeviceKeys: jest.fn().mockResolvedValue({ ed25519: "ed25519", curve25519: "curve25519" }),
             getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
             getUserVerificationStatus: jest.fn(),
             getDeviceVerificationStatus: jest.fn(),
@@ -151,6 +151,11 @@ export function createTestClient(): MatrixClient {
                 },
             }),
             isCrossSigningReady: jest.fn().mockResolvedValue(false),
+            disableKeyStorage: jest.fn(),
+            resetEncryption: jest.fn(),
+            getSessionBackupPrivateKey: jest.fn().mockResolvedValue(null),
+            isSecretStorageReady: jest.fn().mockResolvedValue(false),
+            deleteKeyBackupVersion: jest.fn(),
         }),
 
         getPushActionsForEvent: jest.fn(),
@@ -159,6 +164,7 @@ export function createTestClient(): MatrixClient {
         getVisibleRooms: jest.fn().mockReturnValue([]),
         loginFlows: jest.fn(),
         on: eventEmitter.on.bind(eventEmitter),
+        once: eventEmitter.once.bind(eventEmitter),
         off: eventEmitter.off.bind(eventEmitter),
         removeListener: eventEmitter.removeListener.bind(eventEmitter),
         emit: eventEmitter.emit.bind(eventEmitter),
@@ -189,6 +195,7 @@ export function createTestClient(): MatrixClient {
         }),
         mxcUrlToHttp: jest.fn().mockImplementation((mxc: string) => `http://this.is.a.url/${mxc.substring(6)}`),
         setAccountData: jest.fn(),
+        deleteAccountData: jest.fn(),
         setRoomAccountData: jest.fn(),
         setRoomTopic: jest.fn(),
         setRoomReadMarkers: jest.fn().mockResolvedValue({}),
@@ -214,6 +221,7 @@ export function createTestClient(): MatrixClient {
         registerWithIdentityServer: jest.fn().mockResolvedValue({}),
         getIdentityAccount: jest.fn().mockResolvedValue({}),
         getTerms: jest.fn().mockResolvedValue({ policies: [] }),
+        agreeToTerms: jest.fn(),
         doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(undefined),
         isVersionSupported: jest.fn().mockResolvedValue(undefined),
         getPushRules: jest.fn().mockResolvedValue(undefined),
@@ -296,6 +304,14 @@ export function createTestClient(): MatrixClient {
         getLocalAliases: jest.fn().mockReturnValue([]),
         uploadDeviceSigningKeys: jest.fn(),
         isKeyBackupKeyStored: jest.fn().mockResolvedValue(null),
+        getIgnoredUsers: jest.fn().mockReturnValue([]),
+        setIgnoredUsers: jest.fn(),
+        reportRoom: jest.fn(),
+        pushProcessor: {
+            getPushRuleById: jest.fn(),
+        },
+        search: jest.fn().mockResolvedValue({}),
+        processRoomEventsSearch: jest.fn().mockResolvedValue({ highlights: [], results: [] }),
     } as unknown as MatrixClient;
 
     client.reEmitter = new ReEmitter(client);
@@ -599,7 +615,7 @@ export function mkStubRoom(
         getState: (): RoomState | undefined => undefined,
     } as unknown as EventTimeline;
     return {
-        canInvite: jest.fn(),
+        canInvite: jest.fn().mockReturnValue(false),
         client,
         findThreadForEvent: jest.fn(),
         createThreadsTimelineSets: jest.fn().mockReturnValue(new Promise(() => {})),
@@ -654,6 +670,7 @@ export function mkStubRoom(
         getUnreadNotificationCount: jest.fn(() => 0),
         getRoomUnreadNotificationCount: jest.fn().mockReturnValue(0),
         getVersion: jest.fn().mockReturnValue("1"),
+        getBumpStamp: jest.fn().mockReturnValue(0),
         hasMembershipState: () => false,
         isElementVideoRoom: jest.fn().mockReturnValue(false),
         isSpaceRoom: jest.fn().mockReturnValue(false),
diff --git a/test/test-utils/threads.ts b/test/test-utils/threads.ts
index 12b0575ba715f2e4301a42b2dea47940ce90d198..7dca8b2ca0730a0403d65e4ad7ce4b3db2f79ab9 100644
--- a/test/test-utils/threads.ts
+++ b/test/test-utils/threads.ts
@@ -6,9 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, MatrixEventEvent, RelationType, Room, Thread } from "matrix-js-sdk/src/matrix";
-
-import { mkMessage, MessageEventProps } from "./test-utils";
+import {
+    type MatrixClient,
+    type MatrixEvent,
+    MatrixEventEvent,
+    RelationType,
+    type Room,
+    type Thread,
+} from "matrix-js-sdk/src/matrix";
+
+import { mkMessage, type MessageEventProps } from "./test-utils";
 
 export const makeThreadEvent = ({
     rootEventId,
diff --git a/test/test-utils/utilities.ts b/test/test-utils/utilities.ts
index e43f35616157c1d8a0b34f9a1f31541250fc91f0..dd3e5e6a624097cbf9b8da8ac336448fb1b41793 100644
--- a/test/test-utils/utilities.ts
+++ b/test/test-utils/utilities.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import EventEmitter from "events";
 import { act } from "jest-matrix-react";
 
-import { ActionPayload } from "../../src/dispatcher/payloads";
+import type EventEmitter from "events";
+import { type ActionPayload } from "../../src/dispatcher/payloads";
 import defaultDispatcher from "../../src/dispatcher/dispatcher";
-import { DispatcherAction } from "../../src/dispatcher/actions";
+import { type DispatcherAction } from "../../src/dispatcher/actions";
 import Modal from "../../src/Modal";
 
 export const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise((r) => e.once(k, r));
diff --git a/test/test-utils/wrappers.tsx b/test/test-utils/wrappers.tsx
index 8b582ccf00f7c610f2f3410fd7c0910068bc1bf4..7c4fd4b968d7e2a4ebe0616f74ced3ab0cba5966 100644
--- a/test/test-utils/wrappers.tsx
+++ b/test/test-utils/wrappers.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentType, Ref } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { RenderOptions } from "jest-matrix-react";
+import React, { type ComponentType, type Ref } from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type RenderOptions } from "jest-matrix-react";
 
 import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
 import MatrixClientContext from "../../src/contexts/MatrixClientContext";
-import { SDKContext, SdkContextClass } from "../../src/contexts/SDKContext";
+import { SDKContext, type SdkContextClass } from "../../src/contexts/SDKContext";
 
 type WrapperProps<T> = { wrappedRef?: Ref<ComponentType<T>> } & T;
 
diff --git a/test/unit-tests/Avatar-test.ts b/test/unit-tests/Avatar-test.ts
index c068b8e4652730f9d3a6012ed08c86b3369fd5df..ced974a0594617bc6b064955ec53ca08744547d7 100644
--- a/test/unit-tests/Avatar-test.ts
+++ b/test/unit-tests/Avatar-test.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { Room, RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
 
 import { avatarUrlForRoom } from "../../src/Avatar";
-import { Media, mediaFromMxc } from "../../src/customisations/Media";
+import { type Media, mediaFromMxc } from "../../src/customisations/Media";
 import DMRoomMap from "../../src/utils/DMRoomMap";
 
 jest.mock("../../src/customisations/Media", () => ({
diff --git a/test/unit-tests/ContentMessages-test.ts b/test/unit-tests/ContentMessages-test.ts
index 67ab58251116d48c3e7d578d08a4761ef0b10265..80b400568e713f4b632b0a04fc8779cd420411ce 100644
--- a/test/unit-tests/ContentMessages-test.ts
+++ b/test/unit-tests/ContentMessages-test.ts
@@ -7,10 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
-import { ImageInfo } from "matrix-js-sdk/src/types";
-import { defer } from "matrix-js-sdk/src/utils";
-import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
+import {
+    type ISendEventResponse,
+    type MatrixClient,
+    RelationType,
+    type UploadResponse,
+} from "matrix-js-sdk/src/matrix";
+import { type ImageInfo } from "matrix-js-sdk/src/types";
+import encrypt, { type IEncryptedFile } from "matrix-encrypt-attachment";
 
 import ContentMessages, { UploadCanceledError, uploadFile } from "../../src/ContentMessages";
 import { doMaybeLocalRoomAction } from "../../src/utils/local-room";
@@ -333,7 +337,7 @@ describe("ContentMessages", () => {
 
     describe("cancelUpload", () => {
         it("should cancel in-flight upload", async () => {
-            const deferred = defer<UploadResponse>();
+            const deferred = Promise.withResolvers<UploadResponse>();
             mocked(client.uploadContent).mockReturnValue(deferred.promise);
             const file1 = new File([], "file1");
             const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
diff --git a/test/unit-tests/DecryptionFailureTracker-test.ts b/test/unit-tests/DecryptionFailureTracker-test.ts
index adadcc397373f59ac410c93dd1459c062b3f2471..754a621a4f3aa0850a7f9e0c889d4668fd74b656 100644
--- a/test/unit-tests/DecryptionFailureTracker-test.ts
+++ b/test/unit-tests/DecryptionFailureTracker-test.ts
@@ -5,13 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked, MockedObject } from "jest-mock";
-import { HttpApiEvent, MatrixClient, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
+import { mocked, type Mocked, type MockedObject } from "jest-mock";
+import { HttpApiEvent, type MatrixClient, type MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
 import { decryptExistingEvent, mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
-import { CryptoApi, DecryptionFailureCode, UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import {
+    type CryptoApi,
+    DecryptionFailureCode,
+    UserVerificationStatus,
+    CryptoEvent,
+} from "matrix-js-sdk/src/crypto-api";
 import { sleep } from "matrix-js-sdk/src/utils";
 
-import { DecryptionFailureTracker, ErrorProperties } from "../../src/DecryptionFailureTracker";
+import { DecryptionFailureTracker, type ErrorProperties } from "../../src/DecryptionFailureTracker";
 import { stubClient } from "../test-utils";
 import * as Lifecycle from "../../src/Lifecycle";
 
diff --git a/test/unit-tests/DeviceListener-test.ts b/test/unit-tests/DeviceListener-test.ts
index bdbf86637d26c7ab7caf1848bb36db9f0a9a0c65..62221d066518d62f62002de84c9979c2d2882676 100644
--- a/test/unit-tests/DeviceListener-test.ts
+++ b/test/unit-tests/DeviceListener-test.ts
@@ -6,19 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked, mocked } from "jest-mock";
-import { MatrixEvent, Room, MatrixClient, Device, ClientStoppedError } from "matrix-js-sdk/src/matrix";
-import { logger } from "matrix-js-sdk/src/logger";
+import { type Mocked, mocked } from "jest-mock";
+import {
+    MatrixEvent,
+    type Room,
+    type MatrixClient,
+    Device,
+    ClientStoppedError,
+    ClientEvent,
+} from "matrix-js-sdk/src/matrix";
 import {
     CryptoEvent,
-    CrossSigningStatus,
-    CryptoApi,
+    type CrossSigningStatus,
+    type CryptoApi,
     DeviceVerificationStatus,
-    KeyBackupInfo,
+    type KeyBackupInfo,
 } from "matrix-js-sdk/src/crypto-api";
-import { CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
+import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
 
-import DeviceListener from "../../src/DeviceListener";
+import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../src/DeviceListener";
 import { MatrixClientPeg } from "../../src/MatrixClientPeg";
 import * as SetupEncryptionToast from "../../src/toasts/SetupEncryptionToast";
 import * as UnverifiedSessionToast from "../../src/toasts/UnverifiedSessionToast";
@@ -33,9 +39,6 @@ import { UIFeature } from "../../src/settings/UIFeature";
 import { isBulkUnverifiedDeviceReminderSnoozed } from "../../src/utils/device/snoozeBulkUnverifiedDeviceReminder";
 import { PosthogAnalytics } from "../../src/PosthogAnalytics";
 
-// don't litter test console with logs
-jest.mock("matrix-js-sdk/src/logger");
-
 jest.mock("../../src/dispatcher/dispatcher", () => ({
     dispatch: jest.fn(),
     register: jest.fn(),
@@ -63,6 +66,12 @@ describe("DeviceListener", () => {
     beforeEach(() => {
         jest.resetAllMocks();
 
+        // don't litter the console with logs
+        jest.spyOn(console, "debug").mockImplementation(() => {});
+        jest.spyOn(console, "info").mockImplementation(() => {});
+        jest.spyOn(console, "warn").mockImplementation(() => {});
+        jest.spyOn(console, "error").mockImplementation(() => {});
+
         // spy on various toasts' hide and show functions
         // easier than mocking
         jest.spyOn(SetupEncryptionToast, "showToast").mockReturnValue(undefined);
@@ -79,7 +88,6 @@ describe("DeviceListener", () => {
             getDeviceVerificationStatus: jest.fn().mockResolvedValue({
                 crossSigningVerified: false,
             }),
-            getCrossSigningKeyId: jest.fn(),
             getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
             isCrossSigningReady: jest.fn().mockResolvedValue(true),
             isSecretStorageReady: jest.fn().mockResolvedValue(true),
@@ -110,6 +118,7 @@ describe("DeviceListener", () => {
             getDeviceId: jest.fn().mockReturnValue(deviceId),
             setAccountData: jest.fn(),
             getAccountData: jest.fn(),
+            getAccountDataFromServer: jest.fn(),
             deleteAccountData: jest.fn(),
             getCrypto: jest.fn().mockReturnValue(mockCrypto),
             secretStorage: {
@@ -158,14 +167,17 @@ describe("DeviceListener", () => {
             });
 
             it("catches error and logs when saving client information fails", async () => {
-                const errorLogSpy = jest.spyOn(logger, "error");
                 const error = new Error("oups");
                 mockClient!.setAccountData.mockRejectedValue(error);
 
                 // doesn't throw
                 await createAndStart();
 
-                expect(errorLogSpy).toHaveBeenCalledWith("Failed to update client information", error);
+                expect(console.error).toHaveBeenCalledWith(
+                    "DeviceListener:",
+                    "Failed to update client information",
+                    error,
+                );
             });
 
             it("saves client information on logged in action", async () => {
@@ -275,14 +287,14 @@ describe("DeviceListener", () => {
                 throw new ClientStoppedError();
             });
             await createAndStart();
-            expect(logger.error).not.toHaveBeenCalled();
+            expect(console.error).not.toHaveBeenCalled();
         });
         it("correctly handles other errors", async () => {
             mockCrypto!.isCrossSigningReady.mockImplementation(() => {
                 throw new Error("blah");
             });
             await createAndStart();
-            expect(logger.error).toHaveBeenCalledTimes(1);
+            expect(console.error).toHaveBeenCalledTimes(1);
         });
 
         describe("set up encryption", () => {
@@ -298,6 +310,8 @@ describe("DeviceListener", () => {
             it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
                 mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
                 mockCrypto!.isSecretStorageReady.mockResolvedValue(true);
+                mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
+
                 await createAndStart();
                 expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
             });
@@ -323,26 +337,21 @@ describe("DeviceListener", () => {
                 expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
             });
 
-            describe("when user does not have a cross signing id on this device", () => {
-                beforeEach(() => {
-                    mockCrypto!.getCrossSigningKeyId.mockResolvedValue(null);
-                });
-
-                it("shows verify session toast when account has cross signing", async () => {
-                    mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
-                    await createAndStart();
+            it("shows verify session toast when account has cross signing", async () => {
+                mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
+                await createAndStart();
 
-                    expect(mockCrypto!.getUserDeviceInfo).toHaveBeenCalled();
-                    expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
-                        SetupEncryptionToast.Kind.VERIFY_THIS_SESSION,
-                    );
-                });
+                expect(mockCrypto!.getUserDeviceInfo).toHaveBeenCalled();
+                expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
+                    SetupEncryptionToast.Kind.VERIFY_THIS_SESSION,
+                );
             });
 
-            describe("when user does have a cross signing id on this device", () => {
+            describe("when current device is verified", () => {
                 beforeEach(() => {
                     mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
-                    mockCrypto!.getCrossSigningKeyId.mockResolvedValue("abc");
+
+                    // current device is verified
                     mockCrypto!.getDeviceVerificationStatus.mockResolvedValue(
                         new DeviceVerificationStatus({
                             trustCrossSignedDevices: true,
@@ -351,9 +360,65 @@ describe("DeviceListener", () => {
                     );
                 });
 
+                it("shows an out-of-sync toast when one of the secrets is missing", async () => {
+                    mockCrypto!.getCrossSigningStatus.mockResolvedValue({
+                        publicKeysOnDevice: true,
+                        privateKeysInSecretStorage: true,
+                        privateKeysCachedLocally: {
+                            masterKey: false,
+                            selfSigningKey: true,
+                            userSigningKey: true,
+                        },
+                    });
+
+                    await createAndStart();
+
+                    expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.KEY_STORAGE_OUT_OF_SYNC,
+                    );
+                });
+
+                it("hides the out-of-sync toast when one of the secrets is missing", async () => {
+                    mockCrypto!.isSecretStorageReady.mockResolvedValue(true);
+                    mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
+
+                    // First show the toast
+                    mockCrypto!.getCrossSigningStatus.mockResolvedValue({
+                        publicKeysOnDevice: true,
+                        privateKeysInSecretStorage: true,
+                        privateKeysCachedLocally: {
+                            masterKey: false,
+                            selfSigningKey: true,
+                            userSigningKey: true,
+                        },
+                    });
+
+                    await createAndStart();
+
+                    expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.KEY_STORAGE_OUT_OF_SYNC,
+                    );
+
+                    // Then, when we receive the secret, it should be hidden.
+                    mockCrypto!.getCrossSigningStatus.mockResolvedValue({
+                        publicKeysOnDevice: true,
+                        privateKeysInSecretStorage: true,
+                        privateKeysCachedLocally: {
+                            masterKey: true,
+                            selfSigningKey: true,
+                            userSigningKey: true,
+                        },
+                    });
+
+                    mockClient.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({ type: "m.secret.send" }));
+                    await flushPromises();
+                    expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
+                });
+
                 it("shows set up recovery toast when user has a key backup available", async () => {
                     // non falsy response
                     mockCrypto.getKeyBackupInfo.mockResolvedValue({} as unknown as KeyBackupInfo);
+                    mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1");
                     mockClient.secretStorage.getDefaultKeyId.mockResolvedValue(null);
 
                     await createAndStart();
@@ -384,8 +449,13 @@ describe("DeviceListener", () => {
 
             it("dispatches keybackup event when key backup is not enabled", async () => {
                 mockCrypto.getActiveSessionBackupVersion.mockResolvedValue(null);
+                mockClient.getAccountDataFromServer.mockImplementation((eventType) =>
+                    eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY ? ({ disabled: true } as any) : null,
+                );
                 await createAndStart();
-                expect(mockDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ReportKeyBackupNotEnabled });
+                expect(mockDispatcher.dispatch).toHaveBeenCalledWith({
+                    action: Action.ReportKeyBackupNotEnabled,
+                });
             });
 
             it("does not check key backup status again after check is complete", async () => {
@@ -401,6 +471,137 @@ describe("DeviceListener", () => {
             });
         });
 
+        it("sets backup_disabled account data when we call recordKeyBackupDisabled", async () => {
+            const instance = await createAndStart();
+            await instance.recordKeyBackupDisabled();
+
+            expect(mockClient.setAccountData).toHaveBeenCalledWith("m.org.matrix.custom.backup_disabled", {
+                disabled: true,
+            });
+        });
+
+        describe("when crypto is in use and set up", () => {
+            beforeEach(() => {
+                // Encryption is in use
+                mockClient.getRooms.mockReturnValue([{ roomId: "!room1" }, { roomId: "!room2" }] as unknown as Room[]);
+                jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
+
+                // The device is verified
+                mockCrypto.getDeviceVerificationStatus.mockResolvedValue(
+                    new DeviceVerificationStatus({ crossSigningVerified: true }),
+                );
+            });
+
+            describe("but key storage is off", () => {
+                beforeEach(() => {
+                    // There is no active key backup/storage
+                    mockCrypto.getActiveSessionBackupVersion.mockResolvedValue(null);
+                });
+
+                it("shows the 'Turn on key storage' toast if we never explicitly turned off key storage", async () => {
+                    // Given key backup is off but the account data saying we turned it off is not set
+                    // (m.org.matrix.custom.backup_disabled)
+                    mockClient.getAccountData.mockReturnValue(undefined);
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is displayed
+                    expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+
+                it("shows the 'Turn on key storage' toast if we turned on key storage", async () => {
+                    // Given key backup is off but the account data says we turned it on (this should not happen - the
+                    // account data should only be updated if we turn on key storage)
+                    mockClient.getAccountData.mockImplementation((eventType) =>
+                        eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
+                            ? new MatrixEvent({ content: { disabled: false } })
+                            : undefined,
+                    );
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is displayed
+                    expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+
+                it("does not show the 'Turn on key storage' toast if we turned off key storage", async () => {
+                    // Given key backup is off but the account data saying we turned it off is set
+                    mockClient.getAccountDataFromServer.mockImplementation((eventType) =>
+                        eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY ? ({ disabled: true } as any) : null,
+                    );
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is not displayed
+                    expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+            });
+
+            describe("and key storage is on", () => {
+                beforeEach(() => {
+                    // There is an active key backup/storage
+                    mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1");
+                });
+
+                it("does not show the 'Turn on key storage' toast if we never explicitly turned off key storage", async () => {
+                    // Given key backup is on and the account data saying we turned it off is not set
+                    mockClient.getAccountData.mockReturnValue(undefined);
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is not displayed
+                    expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+
+                it("does not show the 'Turn on key storage' toast if we turned on key storage", async () => {
+                    // Given key backup is on and the account data says we turned it on
+                    mockClient.getAccountData.mockImplementation((eventType) =>
+                        eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
+                            ? new MatrixEvent({ content: { disabled: false } })
+                            : undefined,
+                    );
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is not displayed
+                    expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+
+                it("does not show the 'Turn on key storage' toast if we turned off key storage", async () => {
+                    // Given key backup is on but the account data saying we turned it off is set (this should never
+                    // happen - it should only be set when we turn off key storage or dismiss the toast)
+                    mockClient.getAccountData.mockImplementation((eventType) =>
+                        eventType === BACKUP_DISABLED_ACCOUNT_DATA_KEY
+                            ? new MatrixEvent({ content: { disabled: true } })
+                            : undefined,
+                    );
+
+                    // When we launch the DeviceListener
+                    await createAndStart();
+
+                    // Then the toast is not displayed
+                    expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                        SetupEncryptionToast.Kind.TURN_ON_KEY_STORAGE,
+                    );
+                });
+            });
+        });
+
         describe("unverified sessions toasts", () => {
             const currentDevice = new Device({ deviceId, userId: userId, algorithms: [], keys: new Map() });
             const device2 = new Device({ deviceId: "d2", userId: userId, algorithms: [], keys: new Map() });
@@ -916,5 +1117,64 @@ describe("DeviceListener", () => {
                 });
             });
         });
+
+        describe("set up recovery", () => {
+            const rooms = [{ roomId: "!room1" }] as unknown as Room[];
+
+            beforeEach(() => {
+                mockCrypto!.getDeviceVerificationStatus.mockResolvedValue(
+                    new DeviceVerificationStatus({
+                        trustCrossSignedDevices: true,
+                        crossSigningVerified: true,
+                    }),
+                );
+                mockCrypto!.isCrossSigningReady.mockResolvedValue(true);
+                mockCrypto!.isSecretStorageReady.mockResolvedValue(false);
+                mockClient.secretStorage.getDefaultKeyId.mockResolvedValue(null);
+                mockClient!.getRooms.mockReturnValue(rooms);
+                jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
+            });
+
+            it("shows the 'set up recovery' toast if user has not set up 4S", async () => {
+                mockCrypto!.getActiveSessionBackupVersion.mockResolvedValue("1");
+
+                await createAndStart();
+
+                expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(SetupEncryptionToast.Kind.SET_UP_RECOVERY);
+            });
+
+            it("does not show the 'set up recovery' toast if secret storage is set up", async () => {
+                mockCrypto!.isSecretStorageReady.mockResolvedValue(true);
+                mockClient.secretStorage.getDefaultKeyId.mockResolvedValue("thiskey");
+                await createAndStart();
+
+                expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                    SetupEncryptionToast.Kind.SET_UP_RECOVERY,
+                );
+            });
+
+            it("does not show the 'set up recovery' toast if user has no encrypted rooms", async () => {
+                jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
+                await createAndStart();
+
+                expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                    SetupEncryptionToast.Kind.SET_UP_RECOVERY,
+                );
+            });
+
+            it("does not show the 'set up recovery' toast if the user has chosen to disable key storage", async () => {
+                mockClient!.getAccountData.mockImplementation((k: string) => {
+                    if (k === "m.org.matrix.custom.backup_disabled") {
+                        return new MatrixEvent({ content: { disabled: true } });
+                    }
+                    return undefined;
+                });
+                await createAndStart();
+
+                expect(SetupEncryptionToast.showToast).not.toHaveBeenCalledWith(
+                    SetupEncryptionToast.Kind.SET_UP_RECOVERY,
+                );
+            });
+        });
     });
 });
diff --git a/test/unit-tests/HtmlUtils-test.tsx b/test/unit-tests/HtmlUtils-test.tsx
index e4fccb185ba08e52a1c518fcc37c1c9e606cf414..97c8da60133bfa30a13b7c8031910854b6366be2 100644
--- a/test/unit-tests/HtmlUtils-test.tsx
+++ b/test/unit-tests/HtmlUtils-test.tsx
@@ -6,21 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
-import { mocked } from "jest-mock";
+import React from "react";
 import { render, screen } from "jest-matrix-react";
-import { IContent } from "matrix-js-sdk/src/matrix";
+import parse from "html-react-parser";
 
-import { bodyToSpan, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
+import { bodyToHtml, bodyToNode, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
 import SettingsStore from "../../src/settings/SettingsStore";
-
-jest.mock("../../src/settings/SettingsStore");
-
-const enableHtmlTopicFeature = () => {
-    mocked(SettingsStore).getValue.mockImplementation((arg): any => {
-        return arg === "feature_html_topic";
-    });
-};
+import { getMockClientWithEventEmitter } from "../test-utils";
+import { SettingLevel } from "../../src/settings/SettingLevel";
+import SdkConfig from "../../src/SdkConfig";
 
 describe("topicToHtml", () => {
     function getContent() {
@@ -38,31 +32,24 @@ describe("topicToHtml", () => {
     });
 
     it("converts literal HTML topic to HTML", async () => {
-        enableHtmlTopicFeature();
         render(<div role="contentinfo">{topicToHtml("<b>pizza</b>", undefined, null, false)}</div>);
         expect(getContent()).toEqual("&lt;b&gt;pizza&lt;/b&gt;");
     });
 
     it("converts true HTML topic to HTML", async () => {
-        enableHtmlTopicFeature();
         render(<div role="contentinfo">{topicToHtml("**pizza**", "<b>pizza</b>", null, false)}</div>);
         expect(getContent()).toEqual("<b>pizza</b>");
     });
 
     it("converts true HTML topic with emoji to HTML", async () => {
-        enableHtmlTopicFeature();
         render(<div role="contentinfo">{topicToHtml("**pizza** 🍕", "<b>pizza</b> 🍕", null, false)}</div>);
         expect(getContent()).toEqual('<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>');
     });
 });
 
 describe("bodyToHtml", () => {
-    function getHtml(content: IContent, highlights?: string[]): string {
-        return (bodyToSpan(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html;
-    }
-
     it("should apply highlights to HTML messages", () => {
-        const html = getHtml(
+        const html = bodyToHtml(
             {
                 body: "test **foo** bar",
                 msgtype: "m.text",
@@ -76,7 +63,7 @@ describe("bodyToHtml", () => {
     });
 
     it("should apply highlights to plaintext messages", () => {
-        const html = getHtml(
+        const html = bodyToHtml(
             {
                 body: "test foo bar",
                 msgtype: "m.text",
@@ -88,7 +75,7 @@ describe("bodyToHtml", () => {
     });
 
     it("should not respect HTML tags in plaintext message highlighting", () => {
-        const html = getHtml(
+        const html = bodyToHtml(
             {
                 body: "test foo <b>bar",
                 msgtype: "m.text",
@@ -99,85 +86,75 @@ describe("bodyToHtml", () => {
         expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo &lt;b&gt;bar"`);
     });
 
-    it("generates big emoji for emoji made of multiple characters", () => {
-        const { asFragment } = render(bodyToSpan({ body: "👨‍👩‍👧‍👦 ↔️ 🇮🇸", msgtype: "m.text" }, [], {}) as ReactElement);
-
-        expect(asFragment()).toMatchSnapshot();
-    });
-
-    it("should generate big emoji for an emoji-only reply to a message", () => {
+    it("does not mistake characters in text presentation mode for emoji", () => {
         const { asFragment } = render(
-            bodyToSpan(
-                {
-                    "body": "> <@sender1:server> Test\n\n🥰",
-                    "format": "org.matrix.custom.html",
-                    "formatted_body":
-                        '<mx-reply><blockquote><a href="https://matrix.to/#/!roomId:server/$eventId">In reply to</a> <a href="https://matrix.to/#/@sender1:server">@sender1:server</a><br>Test</blockquote></mx-reply>🥰',
-                    "m.relates_to": {
-                        "m.in_reply_to": {
-                            event_id: "$eventId",
-                        },
-                    },
-                    "msgtype": "m.text",
-                },
-                [],
-                {
-                    stripReplyFallback: true,
-                },
-            ) as ReactElement,
+            <span className="mx_EventTile_body translate" dir="auto">
+                {parse(bodyToHtml({ body: "↔ ❗︎", msgtype: "m.text" }, [], {}))}
+            </span>,
         );
 
         expect(asFragment()).toMatchSnapshot();
     });
 
-    it("does not mistake characters in text presentation mode for emoji", () => {
-        const { asFragment } = render(bodyToSpan({ body: "↔ ❗︎", msgtype: "m.text" }, [], {}) as ReactElement);
-
-        expect(asFragment()).toMatchSnapshot();
-    });
-
     describe("feature_latex_maths", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature === "feature_latex_maths");
+            SettingsStore.setValue("feature_latex_maths", null, SettingLevel.DEVICE, true);
+        });
+
+        afterEach(() => {
+            SettingsStore.reset();
+            SdkConfig.reset();
         });
 
         it("should render inline katex", () => {
-            const html = getHtml({
-                body: "hello \\xi world",
-                msgtype: "m.text",
-                formatted_body: 'hello <span data-mx-maths="\\xi"><code>\\xi</code></span> world',
-                format: "org.matrix.custom.html",
-            });
+            const html = bodyToHtml(
+                {
+                    body: "hello \\xi world",
+                    msgtype: "m.text",
+                    formatted_body: 'hello <span data-mx-maths="\\xi"><code>\\xi</code></span> world',
+                    format: "org.matrix.custom.html",
+                },
+                [],
+            );
             expect(html).toMatchSnapshot();
         });
 
         it("should render block katex", () => {
-            const html = getHtml({
-                body: "hello \\xi world",
-                msgtype: "m.text",
-                formatted_body: '<p>hello</p><div data-mx-maths="\\xi"><code>\\xi</code></div><p>world</p>',
-                format: "org.matrix.custom.html",
-            });
+            const html = bodyToHtml(
+                {
+                    body: "hello \\xi world",
+                    msgtype: "m.text",
+                    formatted_body: '<p>hello</p><div data-mx-maths="\\xi"><code>\\xi</code></div><p>world</p>',
+                    format: "org.matrix.custom.html",
+                },
+                [],
+            );
             expect(html).toMatchSnapshot();
         });
 
         it("should not mangle code blocks", () => {
-            const html = getHtml({
-                body: "hello \\xi world",
-                msgtype: "m.text",
-                formatted_body: "<p>hello</p><pre><code>$\\xi$</code></pre><p>world</p>",
-                format: "org.matrix.custom.html",
-            });
+            const html = bodyToHtml(
+                {
+                    body: "hello \\xi world",
+                    msgtype: "m.text",
+                    formatted_body: "<p>hello</p><pre><code>$\\xi$</code></pre><p>world</p>",
+                    format: "org.matrix.custom.html",
+                },
+                [],
+            );
             expect(html).toMatchSnapshot();
         });
 
         it("should not mangle divs", () => {
-            const html = getHtml({
-                body: "hello world",
-                msgtype: "m.text",
-                formatted_body: "<p>hello</p><div>world</div>",
-                format: "org.matrix.custom.html",
-            });
+            const html = bodyToHtml(
+                {
+                    body: "hello world",
+                    msgtype: "m.text",
+                    formatted_body: "<p>hello</p><div>world</div>",
+                    format: "org.matrix.custom.html",
+                },
+                [],
+            );
             expect(html).toMatchSnapshot();
         });
     });
@@ -198,3 +175,88 @@ describe("formatEmojis", () => {
         }
     });
 });
+
+describe("bodyToNode", () => {
+    it("generates big emoji for emoji made of multiple characters", () => {
+        const { className, emojiBodyElements } = bodyToNode(
+            {
+                body: "👨‍👩‍👧‍👦 ↔️ 🇮🇸",
+                msgtype: "m.text",
+            },
+            [],
+            {
+                stripReplyFallback: true,
+            },
+        );
+
+        const { asFragment } = render(
+            <span className={className} dir="auto">
+                {emojiBodyElements}
+            </span>,
+        );
+
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should generate big emoji for an emoji-only reply to a message", () => {
+        const { className, formattedBody } = bodyToNode(
+            {
+                "body": "> <@sender1:server> Test\n\n🥰",
+                "format": "org.matrix.custom.html",
+                "formatted_body":
+                    '<mx-reply><blockquote><a href="https://matrix.to/#/!roomId:server/$eventId">In reply to</a> <a href="https://matrix.to/#/@sender1:server">@sender1:server</a><br>Test</blockquote></mx-reply>🥰',
+                "m.relates_to": {
+                    "m.in_reply_to": {
+                        event_id: "$eventId",
+                    },
+                },
+                "msgtype": "m.text",
+            },
+            [],
+            {
+                stripReplyFallback: true,
+            },
+        );
+
+        const { asFragment } = render(
+            <span className={className} dir="auto" dangerouslySetInnerHTML={{ __html: formattedBody! }} />,
+        );
+
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it.each([[true], [false]])("should handle inline media when mediaIsVisible is %s", (mediaIsVisible) => {
+        const cli = getMockClientWithEventEmitter({
+            mxcUrlToHttp: jest.fn().mockReturnValue("https://example.org/img"),
+        });
+        const { className, formattedBody } = bodyToNode(
+            {
+                "body": "![foo](mxc://going/knowwhere) Hello there",
+                "format": "org.matrix.custom.html",
+                "formatted_body": `<img src="mxc://going/knowwhere">foo</img> Hello there`,
+                "m.relates_to": {
+                    "m.in_reply_to": {
+                        event_id: "$eventId",
+                    },
+                },
+                "msgtype": "m.text",
+            },
+            [],
+            {
+                mediaIsVisible,
+            },
+        );
+
+        const { asFragment } = render(
+            <span className={className} dir="auto" dangerouslySetInnerHTML={{ __html: formattedBody! }} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+        // We do not want to download untrusted media.
+        // eslint-disable-next-line no-restricted-properties
+        expect(cli.mxcUrlToHttp).toHaveBeenCalledTimes(mediaIsVisible ? 1 : 0);
+    });
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+});
diff --git a/test/unit-tests/Image-test.ts b/test/unit-tests/Image-test.ts
index 149ee0ff26f6a096984704977762aa5497c4e651..966236bc8a266f7b2ef188d535712c56ef1acbea 100644
--- a/test/unit-tests/Image-test.ts
+++ b/test/unit-tests/Image-test.ts
@@ -51,6 +51,13 @@ describe("Image", () => {
             expect(await blobIsAnimated("image/webp", img)).toBeFalsy();
         });
 
+        it("Static WEBP in extended file format", async () => {
+            const img = new Blob([
+                fs.readFileSync(path.resolve(__dirname, "images", "static-logo-extended-file-format.webp")),
+            ]);
+            expect(await blobIsAnimated("image/webp", img)).toBeFalsy();
+        });
+
         it("Animated PNG", async () => {
             const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.apng"))]);
             expect(await blobIsAnimated("image/png", img)).toBeTruthy();
diff --git a/test/unit-tests/KeyBindingsManager-test.ts b/test/unit-tests/KeyBindingsManager-test.ts
index 91adfc0effdc3816989a00578c56d35483fbe578..ed0be01fe2ce18a513eb1d5a2fdd6fe691ef45e9 100644
--- a/test/unit-tests/KeyBindingsManager-test.ts
+++ b/test/unit-tests/KeyBindingsManager-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { isKeyComboMatch, KeyCombo } from "../../src/KeyBindingsManager";
+import { isKeyComboMatch, type KeyCombo } from "../../src/KeyBindingsManager";
 
 function mockKeyEvent(
     key: string,
diff --git a/test/unit-tests/LegacyCallHandler-test.ts b/test/unit-tests/LegacyCallHandler-test.ts
index 1aee2d2231711c142773a3cefb6fb1bc8754360e..972f17b7e0ddc976dddf3a4426fa3d9412b491d2 100644
--- a/test/unit-tests/LegacyCallHandler-test.ts
+++ b/test/unit-tests/LegacyCallHandler-test.ts
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import {
-    IProtocol,
+    type IProtocol,
     LOCAL_NOTIFICATION_SETTINGS_PREFIX,
     MatrixEvent,
     PushRuleKind,
-    Room,
+    type Room,
     RuleId,
     TweakName,
 } from "matrix-js-sdk/src/matrix";
@@ -22,14 +22,13 @@ import { mocked } from "jest-mock";
 import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
 import fetchMock from "fetch-mock-jest";
 import { waitFor } from "jest-matrix-react";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
 
 import LegacyCallHandler, {
     AudioID,
     LegacyCallHandlerEvent,
     PROTOCOL_PSTN,
     PROTOCOL_PSTN_PREFIXED,
-    PROTOCOL_SIP_NATIVE,
-    PROTOCOL_SIP_VIRTUAL,
 } from "../../src/LegacyCallHandler";
 import { mkStubRoom, stubClient, untilDispatch } from "../test-utils";
 import { MatrixClientPeg } from "../../src/MatrixClientPeg";
@@ -74,9 +73,6 @@ const NATIVE_ALICE = "@alice:example.org";
 const NATIVE_BOB = "@bob:example.org";
 const NATIVE_CHARLIE = "@charlie:example.org";
 
-// Virtual user for Bob
-const VIRTUAL_BOB = "@virtual_bob:example.org";
-
 //const REAL_ROOM_ID = "$room1:example.org";
 // The rooms the user sees when they're communicating with these users
 const NATIVE_ROOM_ALICE = "$alice_room:example.org";
@@ -85,10 +81,6 @@ const NATIVE_ROOM_CHARLIE = "$charlie_room:example.org";
 
 const FUNCTIONAL_USER = "@bot:example.com";
 
-// The room we use to talk to virtual Bob (but that the user does not see)
-// Bob has a virtual room, but Alice doesn't
-const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
-
 // Bob's phone number
 const BOB_PHONE_NUMBER = "01818118181";
 
@@ -145,14 +137,6 @@ class FakeCall extends EventEmitter {
     }
 }
 
-function untilCallHandlerEvent(callHandler: LegacyCallHandler, event: LegacyCallHandlerEvent): Promise<void> {
-    return new Promise<void>((resolve) => {
-        callHandler.addListener(event, () => {
-            resolve();
-        });
-    });
-}
-
 describe("LegacyCallHandler", () => {
     let dmRoomMap;
     let callHandler: LegacyCallHandler;
@@ -161,7 +145,6 @@ describe("LegacyCallHandler", () => {
 
     // what addresses the app has looked up via pstn and native lookup
     let pstnLookup: string | null;
-    let nativeLookup: string | null;
     const deviceId = "my-device";
 
     beforeEach(async () => {
@@ -179,8 +162,6 @@ describe("LegacyCallHandler", () => {
         MatrixClientPeg.safeGet().getThirdpartyProtocols = () => {
             return Promise.resolve({
                 "m.id.phone": {} as IProtocol,
-                "im.vector.protocol.sip_native": {} as IProtocol,
-                "im.vector.protocol.sip_virtual": {} as IProtocol,
             });
         };
 
@@ -192,7 +173,6 @@ describe("LegacyCallHandler", () => {
         const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
         const nativeRoomBob = mkStubDM(NATIVE_ROOM_BOB, NATIVE_BOB);
         const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
-        const virtualBobRoom = mkStubDM(VIRTUAL_ROOM_BOB, VIRTUAL_BOB);
 
         MatrixClientPeg.safeGet().getRoom = (roomId: string): Room | null => {
             switch (roomId) {
@@ -202,8 +182,6 @@ describe("LegacyCallHandler", () => {
                     return nativeRoomBob;
                 case NATIVE_ROOM_CHARLIE:
                     return nativeRoomCharie;
-                case VIRTUAL_ROOM_BOB:
-                    return virtualBobRoom;
             }
 
             return null;
@@ -217,8 +195,6 @@ describe("LegacyCallHandler", () => {
                     return NATIVE_BOB;
                 } else if (roomId === NATIVE_ROOM_CHARLIE) {
                     return NATIVE_CHARLIE;
-                } else if (roomId === VIRTUAL_ROOM_BOB) {
-                    return VIRTUAL_BOB;
                 } else {
                     return null;
                 }
@@ -230,8 +206,6 @@ describe("LegacyCallHandler", () => {
                     return [NATIVE_ROOM_BOB];
                 } else if (userId === NATIVE_CHARLIE) {
                     return [NATIVE_ROOM_CHARLIE];
-                } else if (userId === VIRTUAL_BOB) {
-                    return [VIRTUAL_ROOM_BOB];
                 } else {
                     return [];
                 }
@@ -240,52 +214,18 @@ describe("LegacyCallHandler", () => {
         DMRoomMap.setShared(dmRoomMap);
 
         pstnLookup = null;
-        nativeLookup = null;
 
         MatrixClientPeg.safeGet().getThirdpartyUser = (proto: string, params: any) => {
             if ([PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED].includes(proto)) {
                 pstnLookup = params["m.id.phone"];
                 return Promise.resolve([
                     {
-                        userid: VIRTUAL_BOB,
+                        userid: NATIVE_BOB,
                         protocol: "m.id.phone",
-                        fields: {
-                            is_native: true,
-                            lookup_success: true,
-                        },
+                        fields: {},
                     },
                 ]);
-            } else if (proto === PROTOCOL_SIP_NATIVE) {
-                nativeLookup = params["virtual_mxid"];
-                if (params["virtual_mxid"] === VIRTUAL_BOB) {
-                    return Promise.resolve([
-                        {
-                            userid: NATIVE_BOB,
-                            protocol: "im.vector.protocol.sip_native",
-                            fields: {
-                                is_native: true,
-                                lookup_success: true,
-                            },
-                        },
-                    ]);
-                }
-                return Promise.resolve([]);
-            } else if (proto === PROTOCOL_SIP_VIRTUAL) {
-                if (params["native_mxid"] === NATIVE_BOB) {
-                    return Promise.resolve([
-                        {
-                            userid: VIRTUAL_BOB,
-                            protocol: "im.vector.protocol.sip_virtual",
-                            fields: {
-                                is_virtual: true,
-                                lookup_success: true,
-                            },
-                        },
-                    ]);
-                }
-                return Promise.resolve([]);
             }
-
             return Promise.resolve([]);
         };
 
@@ -311,16 +251,14 @@ describe("LegacyCallHandler", () => {
         await callHandler.dialNumber(BOB_PHONE_NUMBER);
 
         expect(pstnLookup).toEqual(BOB_PHONE_NUMBER);
-        expect(nativeLookup).toEqual(VIRTUAL_BOB);
 
         // we should have switched to the native room for Bob
         const viewRoomPayload = await untilDispatch(Action.ViewRoom);
         expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
 
         // Check that a call was started: its room on the protocol level
-        // should be the virtual room
         expect(fakeCall).not.toBeNull();
-        expect(fakeCall?.roomId).toEqual(VIRTUAL_ROOM_BOB);
+        expect(fakeCall?.roomId).toEqual(NATIVE_ROOM_BOB);
 
         // but it should appear to the user to be in thw native room for Bob
         expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
@@ -337,7 +275,7 @@ describe("LegacyCallHandler", () => {
         expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
 
         expect(fakeCall).not.toBeNull();
-        expect(fakeCall!.roomId).toEqual(VIRTUAL_ROOM_BOB);
+        expect(fakeCall!.roomId).toEqual(NATIVE_ROOM_BOB);
 
         expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
     });
@@ -345,8 +283,6 @@ describe("LegacyCallHandler", () => {
     it("should move calls between rooms when remote asserted identity changes", async () => {
         callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
 
-        await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
-
         // We placed the call in Alice's room so it should start off there
         expect(callHandler.getCallForRoom(NATIVE_ROOM_ALICE)).toBe(fakeCall);
 
@@ -473,6 +409,9 @@ describe("LegacyCallHandler without third party protocols", () => {
             throw new Error("Endpoint unsupported.");
         };
 
+        // @ts-expect-error
+        MatrixClientPeg.safeGet().pushProcessor = new PushProcessor(MatrixClientPeg.safeGet());
+
         audioElement = document.createElement("audio");
         audioElement.id = "remoteAudio";
         document.body.appendChild(audioElement);
@@ -513,10 +452,7 @@ describe("LegacyCallHandler without third party protocols", () => {
     it("should still start a native call", async () => {
         callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
 
-        await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
-
         // Check that a call was started: its room on the protocol level
-        // should be the virtual room
         expect(fakeCall).not.toBeNull();
         expect(fakeCall!.roomId).toEqual(NATIVE_ROOM_ALICE);
 
diff --git a/test/unit-tests/Lifecycle-test.ts b/test/unit-tests/Lifecycle-test.ts
index ba5885c414ceffb375f8074551d07f541962d093..97ab3c610987b3facbe758d247dd6db967fe0e76 100644
--- a/test/unit-tests/Lifecycle-test.ts
+++ b/test/unit-tests/Lifecycle-test.ts
@@ -11,11 +11,11 @@ import { logger } from "matrix-js-sdk/src/logger";
 import * as MatrixJs from "matrix-js-sdk/src/matrix";
 import { decodeBase64, encodeUnpaddedBase64 } from "matrix-js-sdk/src/matrix";
 import * as encryptAESSecretStorageItemModule from "matrix-js-sdk/src/utils/encryptAESSecretStorageItem";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
 
 import StorageEvictedDialog from "../../src/components/views/dialogs/StorageEvictedDialog";
-import { logout, restoreSessionFromStorage, setLoggedIn } from "../../src/Lifecycle";
+import * as Lifecycle from "../../src/Lifecycle";
 import { MatrixClientPeg } from "../../src/MatrixClientPeg";
 import Modal from "../../src/Modal";
 import * as StorageAccess from "../../src/utils/StorageAccess";
@@ -28,26 +28,38 @@ import { Action } from "../../src/dispatcher/actions";
 import PlatformPeg from "../../src/PlatformPeg";
 import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "../../src/utils/tokens/tokens";
 import { encryptPickleKey } from "../../src/utils/tokens/pickling";
+import * as StorageManager from "../../src/utils/StorageManager.ts";
+import type BasePlatform from "../../src/BasePlatform.ts";
+
+const { logout, restoreSessionFromStorage, setLoggedIn } = Lifecycle;
 
 const webCrypto = new Crypto();
 
 const windowCrypto = window.crypto;
 
 describe("Lifecycle", () => {
-    const mockPlatform = mockPlatformPeg();
+    const homeserverUrl = "https://domain";
+    const identityServerUrl = "https://is.org";
+    const userId = "@alice:domain";
+    const deviceId = "abc123";
+    const accessToken = "test-access-token";
+
+    let mockPlatform: MockedObject<BasePlatform>;
 
     const realLocalStorage = global.localStorage;
 
     let mockClient!: MockedObject<MatrixJs.MatrixClient>;
 
     beforeEach(() => {
+        jest.restoreAllMocks();
+        mockPlatform = mockPlatformPeg();
         mockClient = getMockClientWithEventEmitter({
             ...mockClientMethodsUser(),
             stopClient: jest.fn(),
             removeAllListeners: jest.fn(),
             clearStores: jest.fn(),
             getAccountData: jest.fn(),
-            getDeviceId: jest.fn(),
+            getDeviceId: jest.fn().mockReturnValue(deviceId),
             isVersionSupported: jest.fn().mockResolvedValue(true),
             getCrypto: jest.fn(),
             getClientWellKnown: jest.fn(),
@@ -150,11 +162,6 @@ describe("Lifecycle", () => {
             });
     };
 
-    const homeserverUrl = "https://server.org";
-    const identityServerUrl = "https://is.org";
-    const userId = "@alice:server.org";
-    const deviceId = "abc123";
-    const accessToken = "test-access-token";
     const localStorageSession = {
         mx_hs_url: homeserverUrl,
         mx_is_url: identityServerUrl,
@@ -182,6 +189,32 @@ describe("Lifecycle", () => {
         mac: expect.any(String),
     };
 
+    describe("loadSession", () => {
+        beforeEach(() => {
+            // stub this out
+            jest.spyOn(Modal, "createDialog").mockReturnValue(
+                // @ts-ignore allow bad mock
+                { finished: Promise.resolve([true]) },
+            );
+        });
+
+        it("should not show any error dialog when checkConsistency throws but abortSignal has triggered", async () => {
+            jest.spyOn(StorageManager, "checkConsistency").mockRejectedValue(new Error("test error"));
+
+            const abortController = new AbortController();
+            const prom = Lifecycle.loadSession({
+                enableGuest: true,
+                guestHsUrl: "https://guest.server",
+                fragmentQueryParams: { guest_user_id: "a", guest_access_token: "b" },
+                abortSignal: abortController.signal,
+            });
+            abortController.abort();
+            await expect(prom).resolves.toBeFalsy();
+
+            expect(Modal.createDialog).not.toHaveBeenCalled();
+        });
+    });
+
     describe("restoreSessionFromStorage()", () => {
         beforeEach(() => {
             initLocalStorageMock();
@@ -573,6 +606,38 @@ describe("Lifecycle", () => {
             expect(MatrixClientPeg.start).toHaveBeenCalled();
         });
 
+        describe("after a soft-logout", () => {
+            beforeEach(async () => {
+                await setLoggedIn(credentials);
+                localStorage.setItem("mx_soft_logout", "true");
+            });
+
+            it("should not clear the storage if device is the same", async () => {
+                await Lifecycle.hydrateSession(credentials);
+
+                expect(localStorage.removeItem).toHaveBeenCalledWith("mx_soft_logout");
+                expect(mockClient.getUserId).toHaveReturnedWith(userId);
+                expect(mockClient.getDeviceId).toHaveReturnedWith(deviceId);
+                expect(mockClient.clearStores).toHaveBeenCalledTimes(1);
+            });
+
+            it("should clear the storage if device is not the same", async () => {
+                const fakeCredentials = {
+                    homeserverUrl,
+                    identityServerUrl,
+                    userId: "@bob:domain",
+                    deviceId,
+                    accessToken,
+                };
+                await Lifecycle.hydrateSession(fakeCredentials);
+
+                expect(localStorage.removeItem).toHaveBeenCalledWith("mx_soft_logout");
+                expect(mockClient.getUserId).toHaveReturnedWith(userId);
+                expect(mockClient.getDeviceId).toHaveReturnedWith(deviceId);
+                expect(mockClient.clearStores).toHaveBeenCalledTimes(2);
+            });
+        });
+
         describe("without a pickle key", () => {
             beforeEach(() => {
                 jest.spyOn(mockPlatform, "createPickleKey").mockResolvedValue(null);
@@ -749,11 +814,8 @@ describe("Lifecycle", () => {
                 "eyJhbGciOiJSUzI1NiIsImtpZCI6Imh4ZEhXb0Y5bW4ifQ.eyJzdWIiOiIwMUhQUDJGU0JZREU5UDlFTU04REQ3V1pIUiIsImlzcyI6Imh0dHBzOi8vYXV0aC1vaWRjLmxhYi5lbGVtZW50LmRldi8iLCJpYXQiOjE3MTUwNzE5ODUsImF1dGhfdGltZSI6MTcwNzk5MDMxMiwiY19oYXNoIjoidGt5R1RhUjU5aTk3YXoyTU4yMGdidyIsImV4cCI6MTcxNTA3NTU4NSwibm9uY2UiOiJxaXhwM0hFMmVaIiwiYXVkIjoiMDFIWDk0Mlg3QTg3REgxRUs2UDRaNjI4WEciLCJhdF9oYXNoIjoiNFlFUjdPRlVKTmRTeEVHV2hJUDlnZyJ9.HxODneXvSTfWB5Vc4cf7b8GiN2gdwUuTiyVqZuupWske2HkZiJZUt5Lsxg9BW3gz28POkE0Ln17snlkmy02B_AD3DQxKOOxQCzIIARHdfFvZxgGWsMdFcVQZDW7rtXcqgj-SpVaUQ_8acsgxSrz_DF2o0O4tto0PT6wVUiw8KlBmgWTscWPeAWe-39T-8EiQ8Wi16h6oSPcz2NzOQ7eOM_S9fDkOorgcBkRGLl1nrahrPSdWJSGAeruk5mX4YxN714YThFDyEA2t9YmKpjaiSQ2tT-Xkd7tgsZqeirNs2ni9mIiFX3bRX6t2AhUNzA7MaX9ZyizKGa6go3BESO_oDg";
 
             beforeAll(() => {
-                fetchMock.get(
-                    `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
-                    delegatedAuthConfig.metadata,
-                );
-                fetchMock.get(`${delegatedAuthConfig.metadata.issuer}jwks`, {
+                fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
+                fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, {
                     status: 200,
                     headers: {
                         "Content-Type": "application/json",
@@ -772,9 +834,7 @@ describe("Lifecycle", () => {
                 await setLoggedIn(credentials);
 
                 // didn't try to initialise token refresher
-                expect(fetchMock).not.toHaveFetched(
-                    `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
-                );
+                expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
             });
 
             it("should not try to create a token refresher without a deviceId", async () => {
@@ -785,9 +845,7 @@ describe("Lifecycle", () => {
                 });
 
                 // didn't try to initialise token refresher
-                expect(fetchMock).not.toHaveFetched(
-                    `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
-                );
+                expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
             });
 
             it("should not try to create a token refresher without an issuer in session storage", async () => {
@@ -803,9 +861,7 @@ describe("Lifecycle", () => {
                 });
 
                 // didn't try to initialise token refresher
-                expect(fetchMock).not.toHaveFetched(
-                    `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
-                );
+                expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`);
             });
 
             it("should create a client with a tokenRefreshFunction", async () => {
diff --git a/test/unit-tests/MatrixClientPeg-test.ts b/test/unit-tests/MatrixClientPeg-test.ts
index c46edad55cf6bc2981f0b9e1953f104b22045950..abf730d960b025812c8a8f2fc99346ff1d39a2ea 100644
--- a/test/unit-tests/MatrixClientPeg-test.ts
+++ b/test/unit-tests/MatrixClientPeg-test.ts
@@ -10,7 +10,7 @@ import { logger } from "matrix-js-sdk/src/logger";
 import fetchMockJest from "fetch-mock-jest";
 
 import { advanceDateAndTime, stubClient } from "../test-utils";
-import { IMatrixClientPeg, MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
+import { type IMatrixClientPeg, MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
 
 jest.useFakeTimers();
 
@@ -86,6 +86,27 @@ describe("MatrixClientPeg", () => {
             expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey });
         });
 
+        it("should try to start dehydration if dehydration is enabled", async () => {
+            const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined);
+            const mockStartDehydration = jest.fn();
+            jest.spyOn(testPeg.safeGet(), "getCrypto").mockReturnValue({
+                isDehydrationSupported: jest.fn().mockResolvedValue(true),
+                startDehydration: mockStartDehydration,
+                setDeviceIsolationMode: jest.fn(),
+            } as any);
+            jest.spyOn(testPeg.safeGet(), "waitForClientWellKnown").mockResolvedValue({
+                "m.homeserver": {
+                    base_url: "http://example.com",
+                },
+                "org.matrix.msc3814": true,
+            } as any);
+
+            const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]);
+            await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey });
+            expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey });
+            expect(mockStartDehydration).toHaveBeenCalledWith({ onlyIfKeyCached: true, rehydrate: false });
+        });
+
         it("Should migrate existing login", async () => {
             const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined);
 
diff --git a/test/unit-tests/Modal-test.ts b/test/unit-tests/Modal-test.ts
index d542fa38434feff13f30f5204f9ac816219d7048..121c027b01e1dd8b6472158bc788c589c0911f32 100644
--- a/test/unit-tests/Modal-test.ts
+++ b/test/unit-tests/Modal-test.ts
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import Modal from "../../src/Modal";
 import QuestionDialog from "../../src/components/views/dialogs/QuestionDialog";
 import defaultDispatcher from "../../src/dispatcher/dispatcher";
+import { flushPromises } from "../test-utils";
 
 describe("Modal", () => {
     test("forceCloseAllModals should close all open modals", () => {
@@ -23,7 +24,7 @@ describe("Modal", () => {
         expect(Modal.hasDialogs()).toBe(false);
     });
 
-    test("open modals should be closed on logout", () => {
+    test("open modals should be closed on logout", async () => {
         const modal1OnFinished = jest.fn();
         const modal2OnFinished = jest.fn();
 
@@ -31,18 +32,18 @@ describe("Modal", () => {
             title: "Test dialog 1",
             description: "This is a test dialog",
             button: "Word",
-            onFinished: modal1OnFinished,
-        });
+        }).finished.then(modal1OnFinished);
 
         Modal.createDialog(QuestionDialog, {
             title: "Test dialog 2",
             description: "This is a test dialog",
             button: "Word",
-            onFinished: modal2OnFinished,
-        });
+        }).finished.then(modal2OnFinished);
 
         defaultDispatcher.dispatch({ action: "logout" }, true);
 
+        await flushPromises();
+
         expect(modal1OnFinished).toHaveBeenCalled();
         expect(modal2OnFinished).toHaveBeenCalled();
     });
diff --git a/test/unit-tests/Notifier-test.ts b/test/unit-tests/Notifier-test.ts
index 3335b297873fd7146bbc59538c0b659957480c40..92df3bc385d3039a7619639f1e19bd43fa5612e5 100644
--- a/test/unit-tests/Notifier-test.ts
+++ b/test/unit-tests/Notifier-test.ts
@@ -5,23 +5,23 @@ Copyright 2022 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import {
     ClientEvent,
-    MatrixClient,
+    type MatrixClient,
     Room,
     RoomEvent,
     EventType,
     MsgType,
-    IContent,
+    type IContent,
     MatrixEvent,
     SyncState,
-    AccountDataEvents,
+    type AccountDataEvents,
 } from "matrix-js-sdk/src/matrix";
 import { waitFor } from "jest-matrix-react";
 import { CallMembership, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 
-import BasePlatform from "../../src/BasePlatform";
+import type BasePlatform from "../../src/BasePlatform";
 import Notifier from "../../src/Notifier";
 import SettingsStore from "../../src/settings/SettingsStore";
 import ToastStore from "../../src/stores/ToastStore";
@@ -42,7 +42,7 @@ import UserActivity from "../../src/UserActivity";
 import Modal from "../../src/Modal";
 import { mkThread } from "../test-utils/threads";
 import dis from "../../src/dispatcher/dispatcher";
-import { ThreadPayload } from "../../src/dispatcher/payloads/ThreadPayload";
+import { type ThreadPayload } from "../../src/dispatcher/payloads/ThreadPayload";
 import { Action } from "../../src/dispatcher/actions";
 import { addReplyToMessageContent } from "../../src/utils/Reply";
 
diff --git a/test/unit-tests/PosthogAnalytics-test.ts b/test/unit-tests/PosthogAnalytics-test.ts
index 27552bbba6c17f1380fe324f5c90b955b14b1db3..91fdd32f2eaecedfdac6159fd56d74408d2173bb 100644
--- a/test/unit-tests/PosthogAnalytics-test.ts
+++ b/test/unit-tests/PosthogAnalytics-test.ts
@@ -7,11 +7,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { PostHog } from "posthog-js";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
-
-import { Anonymity, getRedactedCurrentLocation, IPosthogEvent, PosthogAnalytics } from "../../src/PosthogAnalytics";
+import { type PostHog } from "posthog-js";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
+
+import {
+    Anonymity,
+    getRedactedCurrentLocation,
+    type IPosthogEvent,
+    PosthogAnalytics,
+} from "../../src/PosthogAnalytics";
 import SdkConfig from "../../src/SdkConfig";
 import { getMockClientWithEventEmitter } from "../test-utils";
 import SettingsStore from "../../src/settings/SettingsStore";
diff --git a/test/unit-tests/RoomNotifs-test.ts b/test/unit-tests/RoomNotifs-test.ts
index 1452306de66192d07e31a80f593b3fb4a65e3517..cff4ecef75e273d048fbeb1df9cb3a6e7e9d7257 100644
--- a/test/unit-tests/RoomNotifs-test.ts
+++ b/test/unit-tests/RoomNotifs-test.ts
@@ -14,12 +14,12 @@ import {
     Room,
     EventStatus,
     EventType,
-    MatrixEvent,
+    type MatrixEvent,
     PendingEventOrdering,
+    type MatrixClient,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
-import type { MatrixClient } from "matrix-js-sdk/src/matrix";
 import { mkEvent, mkRoom, mkRoomMember, muteRoom, stubClient, upsertRoomStateEvents } from "../test-utils";
 import {
     getRoomNotifsState,
diff --git a/test/unit-tests/Rooms-test.ts b/test/unit-tests/Rooms-test.ts
index 32b5513f8220cbb7dcce0f594ccbf4ffb48f27c9..841b6f372229627f3600b40ea549b3d50e50e2f6 100644
--- a/test/unit-tests/Rooms-test.ts
+++ b/test/unit-tests/Rooms-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { setDMRoom } from "../../src/Rooms";
 import { mkEvent, stubClient } from "../test-utils";
@@ -79,16 +79,6 @@ describe("setDMRoom", () => {
         });
     });
 
-    describe("when trying to add a DM, that already exists", () => {
-        beforeEach(() => {
-            setDMRoom(client, roomId1, userId1);
-        });
-
-        it("should not update the account data", () => {
-            expect(client.setAccountData).not.toHaveBeenCalled();
-        });
-    });
-
     describe("when removing an existing DM", () => {
         beforeEach(() => {
             setDMRoom(client, roomId1, null);
@@ -102,16 +92,6 @@ describe("setDMRoom", () => {
         });
     });
 
-    describe("when removing an unknown room", () => {
-        beforeEach(() => {
-            setDMRoom(client, roomId4, null);
-        });
-
-        it("should not update the account data", () => {
-            expect(client.setAccountData).not.toHaveBeenCalled();
-        });
-    });
-
     describe("when the direct event is undefined", () => {
         beforeEach(() => {
             mocked(client.getAccountData).mockReturnValue(undefined);
diff --git a/test/unit-tests/ScalarAuthClient-test.ts b/test/unit-tests/ScalarAuthClient-test.ts
index bbf29a8652f4f4bff1cb2daa2913dc4b6a18fd15..4480318729d7743e36fc604b8aea679aac092971 100644
--- a/test/unit-tests/ScalarAuthClient-test.ts
+++ b/test/unit-tests/ScalarAuthClient-test.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { mocked } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import ScalarAuthClient from "../../src/ScalarAuthClient";
 import { stubClient } from "../test-utils";
@@ -97,7 +97,7 @@ describe("ScalarAuthClient", function () {
                 body: { errcode: "M_TERMS_NOT_SIGNED" },
             });
             sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken1"));
-            mocked(client.getTerms).mockResolvedValue({ policies: [] });
+            mocked(client.getTerms).mockResolvedValue({ policies: {} });
 
             await expect(sac.registerForToken()).resolves.toBe("testtoken1");
         });
diff --git a/test/unit-tests/SecurityManager-test.ts b/test/unit-tests/SecurityManager-test.ts
index 4575223a50ce47209fb1c47ea5abdb7f06087b6e..8c1671b52ed06ac66b65a4d10d3ba3b2b31d3cb7 100644
--- a/test/unit-tests/SecurityManager-test.ts
+++ b/test/unit-tests/SecurityManager-test.ts
@@ -7,11 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { act } from "react";
+import { Crypto } from "@peculiar/webcrypto";
+import { type CryptoApi, deriveRecoveryKeyFromPassphrase } from "matrix-js-sdk/src/crypto-api";
+import { SecretStorage } from "matrix-js-sdk/src/matrix";
 
-import { accessSecretStorage } from "../../src/SecurityManager";
+import { accessSecretStorage, crossSigningCallbacks } from "../../src/SecurityManager";
 import { filterConsole, stubClient } from "../test-utils";
 import Modal from "../../src/Modal.tsx";
+import {
+    default as AccessSecretStorageDialog,
+    type KeyParams,
+} from "../../src/components/views/dialogs/security/AccessSecretStorageDialog.tsx";
 
 jest.mock("react", () => {
     const React = jest.requireActual("react");
@@ -19,6 +26,10 @@ jest.mock("react", () => {
     return React;
 });
 
+afterEach(() => {
+    jest.restoreAllMocks();
+});
+
 describe("SecurityManager", () => {
     describe("accessSecretStorage", () => {
         filterConsole("Not setting dehydration key: no SSSS key found");
@@ -74,4 +85,81 @@ describe("SecurityManager", () => {
             await expect(spy.mock.lastCall![0]).resolves.toEqual(expect.objectContaining({ __test: true }));
         });
     });
+
+    describe("getSecretStorageKey", () => {
+        const { getSecretStorageKey } = crossSigningCallbacks;
+
+        /** Polyfill crypto.subtle, which is unavailable in jsdom */
+        function polyFillSubtleCrypto() {
+            Object.defineProperty(globalThis.crypto, "subtle", { value: new Crypto().subtle });
+        }
+
+        it("should prompt the user if the key is uncached", async () => {
+            polyFillSubtleCrypto();
+
+            const client = stubClient();
+            mocked(client.secretStorage.getDefaultKeyId).mockResolvedValue("my_default_key");
+
+            const passphrase = "s3cret";
+            const { recoveryKey, keyInfo } = await deriveKeyFromPassphrase(passphrase);
+
+            jest.spyOn(Modal, "createDialog").mockImplementation((component) => {
+                expect(component).toBe(AccessSecretStorageDialog);
+
+                const modalFunc = async () => [{ passphrase }] as [KeyParams];
+                return {
+                    finished: modalFunc(),
+                    close: () => {},
+                };
+            });
+
+            const [keyId, key] = (await act(() =>
+                getSecretStorageKey!({ keys: { my_default_key: keyInfo } }, "my_secret"),
+            ))!;
+            expect(keyId).toEqual("my_default_key");
+            expect(key).toEqual(recoveryKey);
+        });
+
+        it("should not prompt the user if the requested key is not the default", async () => {
+            const client = stubClient();
+            mocked(client.secretStorage.getDefaultKeyId).mockResolvedValue("my_default_key");
+            const createDialogSpy = jest.spyOn(Modal, "createDialog");
+
+            await expect(
+                act(() =>
+                    getSecretStorageKey!(
+                        { keys: { other_key: {} as SecretStorage.SecretStorageKeyDescription } },
+                        "my_secret",
+                    ),
+                ),
+            ).rejects.toThrow("Request for non-default 4S key");
+            expect(createDialogSpy).not.toHaveBeenCalled();
+        });
+    });
 });
+
+/** Derive a key from a passphrase, also returning the KeyInfo */
+async function deriveKeyFromPassphrase(
+    passphrase: string,
+): Promise<{ recoveryKey: Uint8Array; keyInfo: SecretStorage.SecretStorageKeyDescription }> {
+    const salt = "SALTYGOODNESS";
+    const iterations = 1000;
+
+    const recoveryKey = await deriveRecoveryKeyFromPassphrase(passphrase, salt, iterations);
+
+    const check = await SecretStorage.calculateKeyCheck(recoveryKey);
+    return {
+        recoveryKey,
+        keyInfo: {
+            iv: check.iv,
+            mac: check.mac,
+            algorithm: SecretStorage.SECRET_STORAGE_ALGORITHM_V1_AES,
+            name: "",
+            passphrase: {
+                algorithm: "m.pbkdf2",
+                iterations,
+                salt,
+            },
+        },
+    };
+}
diff --git a/test/unit-tests/SlashCommands-test.tsx b/test/unit-tests/SlashCommands-test.tsx
index c2b3da7f2dac35f9259aed46c5a6aeed9584fcd1..b30bc69176c1521008f531d942ec5cf4a9bb7df6 100644
--- a/test/unit-tests/SlashCommands-test.tsx
+++ b/test/unit-tests/SlashCommands-test.tsx
@@ -6,15 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { mocked } from "jest-mock";
 
-import { Command, Commands, getCommand } from "../../src/SlashCommands";
+import { type Command, Commands, getCommand } from "../../src/SlashCommands";
 import { createTestClient } from "../test-utils";
 import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom";
 import SettingsStore from "../../src/settings/SettingsStore";
-import LegacyCallHandler from "../../src/LegacyCallHandler";
 import { SdkContextClass } from "../../src/contexts/SDKContext";
 import Modal from "../../src/Modal";
 import WidgetUtils from "../../src/utils/WidgetUtils";
@@ -196,46 +195,6 @@ describe("SlashCommands", () => {
         });
     });
 
-    describe("/tovirtual", () => {
-        beforeEach(() => {
-            command = findCommand("tovirtual")!;
-        });
-
-        describe("isEnabled", () => {
-            describe("when virtual rooms are supported", () => {
-                beforeEach(() => {
-                    jest.spyOn(LegacyCallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(true);
-                });
-
-                it("should return true for Room", () => {
-                    setCurrentRoom();
-                    expect(command.isEnabled(client)).toBe(true);
-                });
-
-                it("should return false for LocalRoom", () => {
-                    setCurrentLocalRoom();
-                    expect(command.isEnabled(client)).toBe(false);
-                });
-            });
-
-            describe("when virtual rooms are not supported", () => {
-                beforeEach(() => {
-                    jest.spyOn(LegacyCallHandler.instance, "getSupportsVirtualRooms").mockReturnValue(false);
-                });
-
-                it("should return false for Room", () => {
-                    setCurrentRoom();
-                    expect(command.isEnabled(client)).toBe(false);
-                });
-
-                it("should return false for LocalRoom", () => {
-                    setCurrentLocalRoom();
-                    expect(command.isEnabled(client)).toBe(false);
-                });
-            });
-        });
-    });
-
     describe("/part", () => {
         it("should part room matching alias if found", async () => {
             const room1 = new Room("room-id", client, client.getSafeUserId());
diff --git a/test/unit-tests/SlidingSyncManager-test.ts b/test/unit-tests/SlidingSyncManager-test.ts
index 66f4c48c25507949323b0e20d67e01b62e33a304..20d9110bcc4b954c9899523f0d56fe2df2373ef2 100644
--- a/test/unit-tests/SlidingSyncManager-test.ts
+++ b/test/unit-tests/SlidingSyncManager-test.ts
@@ -6,18 +6,31 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { SlidingSync } from "matrix-js-sdk/src/sliding-sync";
+import { type SlidingSync, SlidingSyncEvent, SlidingSyncState } from "matrix-js-sdk/src/sliding-sync";
 import { mocked } from "jest-mock";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import fetchMockJest from "fetch-mock-jest";
+import EventEmitter from "events";
+import { waitFor } from "jest-matrix-react";
 
 import { SlidingSyncManager } from "../../src/SlidingSyncManager";
-import { stubClient } from "../test-utils";
-import SlidingSyncController from "../../src/settings/controllers/SlidingSyncController";
-import SettingsStore from "../../src/settings/SettingsStore";
+import { mkStubRoom, stubClient } from "../test-utils";
 
-jest.mock("matrix-js-sdk/src/sliding-sync");
-const MockSlidingSync = <jest.Mock<SlidingSync>>(<unknown>SlidingSync);
+class MockSlidingSync extends EventEmitter {
+    lists = {};
+    listModifiedCount = 0;
+    terminated = false;
+    needsResend = false;
+    modifyRoomSubscriptions = jest.fn();
+    getRoomSubscriptions = jest.fn();
+    useCustomSubscription = jest.fn();
+    getListParams = jest.fn();
+    setList = jest.fn();
+    setListRanges = jest.fn();
+    getListData = jest.fn();
+    extensions = jest.fn();
+    desiredRoomSubscriptions = jest.fn();
+}
 
 describe("SlidingSyncManager", () => {
     let manager: SlidingSyncManager;
@@ -25,12 +38,12 @@ describe("SlidingSyncManager", () => {
     let client: MatrixClient;
 
     beforeEach(() => {
-        slidingSync = new MockSlidingSync();
+        slidingSync = new MockSlidingSync() as unknown as SlidingSync;
         manager = new SlidingSyncManager();
         client = stubClient();
         // by default the client has no rooms: stubClient magically makes rooms annoyingly.
         mocked(client.getRoom).mockReturnValue(null);
-        manager.configure(client, "invalid");
+        (manager as any).configure(client, "invalid");
         manager.slidingSync = slidingSync;
         fetchMockJest.reset();
         fetchMockJest.get("https://proxy/client/server.json", {});
@@ -39,12 +52,13 @@ describe("SlidingSyncManager", () => {
     describe("setRoomVisible", () => {
         it("adds a subscription for the room", async () => {
             const roomId = "!room:id";
+            mocked(client.getRoom).mockReturnValue(mkStubRoom(roomId, "foo", client));
             const subs = new Set<string>();
             mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs);
-            mocked(slidingSync.modifyRoomSubscriptions).mockResolvedValue("yep");
-            await manager.setRoomVisible(roomId, true);
+            await manager.setRoomVisible(roomId);
             expect(slidingSync.modifyRoomSubscriptions).toHaveBeenCalledWith(new Set<string>([roomId]));
         });
+
         it("adds a custom subscription for a lazy-loadable room", async () => {
             const roomId = "!lazy:id";
             const room = new Room(roomId, client, client.getUserId()!);
@@ -67,19 +81,37 @@ describe("SlidingSyncManager", () => {
             });
             const subs = new Set<string>();
             mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs);
-            mocked(slidingSync.modifyRoomSubscriptions).mockResolvedValue("yep");
-            await manager.setRoomVisible(roomId, true);
+            await manager.setRoomVisible(roomId);
             expect(slidingSync.modifyRoomSubscriptions).toHaveBeenCalledWith(new Set<string>([roomId]));
             // we aren't prescriptive about what the sub name is.
             expect(slidingSync.useCustomSubscription).toHaveBeenCalledWith(roomId, expect.anything());
         });
+
+        it("waits if the room is not yet known", async () => {
+            const roomId = "!room:id";
+            mocked(client.getRoom).mockReturnValue(null);
+            const subs = new Set<string>();
+            mocked(slidingSync.getRoomSubscriptions).mockReturnValue(subs);
+
+            const setVisibleDone = jest.fn();
+            manager.setRoomVisible(roomId).then(setVisibleDone);
+
+            await waitFor(() => expect(client.getRoom).toHaveBeenCalledWith(roomId));
+
+            expect(setVisibleDone).not.toHaveBeenCalled();
+
+            const stubRoom = mkStubRoom(roomId, "foo", client);
+            mocked(client.getRoom).mockReturnValue(stubRoom);
+            client.emit(ClientEvent.Room, stubRoom);
+
+            await waitFor(() => expect(setVisibleDone).toHaveBeenCalled());
+        });
     });
 
     describe("ensureListRegistered", () => {
         it("creates a new list based on the key", async () => {
             const listKey = "key";
             mocked(slidingSync.getListParams).mockReturnValue(null);
-            mocked(slidingSync.setList).mockResolvedValue("yep");
             await manager.ensureListRegistered(listKey, {
                 sort: ["by_recency"],
             });
@@ -96,7 +128,6 @@ describe("SlidingSyncManager", () => {
             mocked(slidingSync.getListParams).mockReturnValue({
                 ranges: [[0, 42]],
             });
-            mocked(slidingSync.setList).mockResolvedValue("yep");
             await manager.ensureListRegistered(listKey, {
                 sort: ["by_recency"],
             });
@@ -114,7 +145,6 @@ describe("SlidingSyncManager", () => {
             mocked(slidingSync.getListParams).mockReturnValue({
                 ranges: [[0, 42]],
             });
-            mocked(slidingSync.setList).mockResolvedValue("yep");
             await manager.ensureListRegistered(listKey, {
                 ranges: [[0, 52]],
             });
@@ -128,7 +158,6 @@ describe("SlidingSyncManager", () => {
                 ranges: [[0, 42]],
                 sort: ["by_recency"],
             });
-            mocked(slidingSync.setList).mockResolvedValue("yep");
             await manager.ensureListRegistered(listKey, {
                 ranges: [[0, 42]],
                 sort: ["by_recency"],
@@ -139,183 +168,77 @@ describe("SlidingSyncManager", () => {
     });
 
     describe("startSpidering", () => {
-        it("requests in batchSizes", async () => {
+        it("requests in expanding batchSizes", async () => {
             const gapMs = 1;
             const batchSize = 10;
-            mocked(slidingSync.setList).mockResolvedValue("yep");
-            mocked(slidingSync.setListRanges).mockResolvedValue("yep");
             mocked(slidingSync.getListData).mockImplementation((key) => {
                 return {
                     joinedCount: 64,
                     roomIndexToRoomId: {},
                 };
             });
-            await manager.startSpidering(batchSize, gapMs);
+            await (manager as any).startSpidering(slidingSync, batchSize, gapMs);
+
             // we expect calls for 10,19 -> 20,29 -> 30,39 -> 40,49 -> 50,59 -> 60,69
             const wantWindows = [
-                [10, 19],
-                [20, 29],
-                [30, 39],
-                [40, 49],
-                [50, 59],
-                [60, 69],
+                [0, 10],
+                [0, 20],
+                [0, 30],
+                [0, 40],
+                [0, 50],
+                [0, 60],
+                [0, 70],
             ];
-            expect(slidingSync.getListData).toHaveBeenCalledTimes(wantWindows.length);
-            expect(slidingSync.setList).toHaveBeenCalledTimes(1);
-            expect(slidingSync.setListRanges).toHaveBeenCalledTimes(wantWindows.length - 1);
-            wantWindows.forEach((range, i) => {
-                if (i === 0) {
-                    // eslint-disable-next-line jest/no-conditional-expect
-                    expect(slidingSync.setList).toHaveBeenCalledWith(
-                        SlidingSyncManager.ListSearch,
-                        // eslint-disable-next-line jest/no-conditional-expect
-                        expect.objectContaining({
-                            ranges: [[0, batchSize - 1], range],
-                        }),
-                    );
-                    return;
-                }
-                expect(slidingSync.setListRanges).toHaveBeenCalledWith(SlidingSyncManager.ListSearch, [
-                    [0, batchSize - 1],
-                    range,
-                ]);
-            });
+
+            for (let i = 1; i < wantWindows.length; ++i) {
+                // each time we emit, it should expand the range of all 5 lists by 10 until
+                // they all include all the rooms (64), which is 6 emits.
+                slidingSync.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, null, undefined);
+                await waitFor(() => expect(slidingSync.getListData).toHaveBeenCalledTimes(i * 5));
+                expect(slidingSync.setListRanges).toHaveBeenCalledTimes(i * 5);
+                expect(slidingSync.setListRanges).toHaveBeenCalledWith("spaces", [wantWindows[i]]);
+            }
         });
         it("handles accounts with zero rooms", async () => {
             const gapMs = 1;
             const batchSize = 10;
-            mocked(slidingSync.setList).mockResolvedValue("yep");
             mocked(slidingSync.getListData).mockImplementation((key) => {
                 return {
                     joinedCount: 0,
                     roomIndexToRoomId: {},
                 };
             });
-            await manager.startSpidering(batchSize, gapMs);
-            expect(slidingSync.getListData).toHaveBeenCalledTimes(1);
-            expect(slidingSync.setList).toHaveBeenCalledTimes(1);
-            expect(slidingSync.setList).toHaveBeenCalledWith(
-                SlidingSyncManager.ListSearch,
-                expect.objectContaining({
-                    ranges: [
-                        [0, batchSize - 1],
-                        [batchSize, batchSize + batchSize - 1],
-                    ],
-                }),
-            );
-        });
-        it("continues even when setList rejects", async () => {
-            const gapMs = 1;
-            const batchSize = 10;
-            mocked(slidingSync.setList).mockRejectedValue("narp");
-            mocked(slidingSync.getListData).mockImplementation((key) => {
-                return {
-                    joinedCount: 0,
-                    roomIndexToRoomId: {},
-                };
-            });
-            await manager.startSpidering(batchSize, gapMs);
-            expect(slidingSync.getListData).toHaveBeenCalledTimes(1);
-            expect(slidingSync.setList).toHaveBeenCalledTimes(1);
-            expect(slidingSync.setList).toHaveBeenCalledWith(
-                SlidingSyncManager.ListSearch,
-                expect.objectContaining({
-                    ranges: [
-                        [0, batchSize - 1],
-                        [batchSize, batchSize + batchSize - 1],
-                    ],
-                }),
-            );
+            await (manager as any).startSpidering(slidingSync, batchSize, gapMs);
+            slidingSync.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, null, undefined);
+            await waitFor(() => expect(slidingSync.getListData).toHaveBeenCalledTimes(5));
+            // should not have needed to expand the range
+            expect(slidingSync.setListRanges).not.toHaveBeenCalled();
         });
     });
     describe("checkSupport", () => {
         beforeEach(() => {
-            SlidingSyncController.serverSupportsSlidingSync = false;
-            jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("https://proxy/");
+            SlidingSyncManager.serverSupportsSlidingSync = false;
         });
         it("shorts out if the server has 'native' sliding sync support", async () => {
             jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(true);
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
-            await manager.checkSupport(client);
-            expect(manager.getProxyFromWellKnown).not.toHaveBeenCalled(); // We return earlier
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
-        });
-        it("tries to find a sliding sync proxy url from the client well-known if there's no 'native' support", async () => {
-            jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(false);
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
-            await manager.checkSupport(client);
-            expect(manager.getProxyFromWellKnown).toHaveBeenCalled();
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
-        });
-        it("should query well-known on server_name not baseUrl", async () => {
-            fetchMockJest.get("https://matrix.org/.well-known/matrix/client", {
-                "m.homeserver": {
-                    base_url: "https://matrix-client.matrix.org",
-                    server: "matrix.org",
-                },
-                "org.matrix.msc3575.proxy": {
-                    url: "https://proxy/",
-                },
-            });
-            fetchMockJest.get("https://matrix-client.matrix.org/_matrix/client/versions", { versions: ["v1.4"] });
-
-            mocked(manager.getProxyFromWellKnown).mockRestore();
-            jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(false);
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
+            expect(SlidingSyncManager.serverSupportsSlidingSync).toBeFalsy();
             await manager.checkSupport(client);
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
-            expect(fetchMockJest).not.toHaveFetched("https://matrix-client.matrix.org/.well-known/matrix/client");
-        });
-    });
-    describe("nativeSlidingSyncSupport", () => {
-        beforeEach(() => {
-            SlidingSyncController.serverSupportsSlidingSync = false;
-        });
-        it("should make an OPTIONS request to avoid unintended side effects", async () => {
-            // See https://github.com/element-hq/element-web/issues/27426
-
-            const unstableSpy = jest
-                .spyOn(client, "doesServerSupportUnstableFeature")
-                .mockImplementation(async (feature: string) => {
-                    expect(feature).toBe("org.matrix.msc3575");
-                    return true;
-                });
-            const proxySpy = jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("https://proxy/");
-
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
-            await manager.checkSupport(client); // first thing it does is call nativeSlidingSyncSupport
-            expect(proxySpy).not.toHaveBeenCalled();
-            expect(unstableSpy).toHaveBeenCalled();
-            expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
+            expect(SlidingSyncManager.serverSupportsSlidingSync).toBeTruthy();
         });
     });
     describe("setup", () => {
+        let untypedManager: any;
+
         beforeEach(() => {
-            jest.spyOn(manager, "configure");
-            jest.spyOn(manager, "startSpidering");
-        });
-        it("uses the baseUrl as a proxy if no proxy is set in the client well-known and the server has no native support", async () => {
-            await manager.setup(client);
-            expect(manager.configure).toHaveBeenCalled();
-            expect(manager.configure).toHaveBeenCalledWith(client, client.baseUrl);
-            expect(manager.startSpidering).toHaveBeenCalled();
+            untypedManager = manager;
+            jest.spyOn(untypedManager, "configure");
+            jest.spyOn(untypedManager, "startSpidering");
         });
-        it("uses the proxy declared in the client well-known", async () => {
-            jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("https://proxy/");
-            await manager.setup(client);
-            expect(manager.configure).toHaveBeenCalled();
-            expect(manager.configure).toHaveBeenCalledWith(client, "https://proxy/");
-            expect(manager.startSpidering).toHaveBeenCalled();
-        });
-        it("uses the legacy `feature_sliding_sync_proxy_url` if it was set", async () => {
-            jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("https://proxy/");
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
-                if (name === "feature_sliding_sync_proxy_url") return "legacy-proxy";
-            });
+        it("uses the baseUrl", async () => {
             await manager.setup(client);
-            expect(manager.configure).toHaveBeenCalled();
-            expect(manager.configure).toHaveBeenCalledWith(client, "legacy-proxy");
-            expect(manager.startSpidering).toHaveBeenCalled();
+            expect(untypedManager.configure).toHaveBeenCalled();
+            expect(untypedManager.configure).toHaveBeenCalledWith(client, client.baseUrl);
+            expect(untypedManager.startSpidering).toHaveBeenCalled();
         });
     });
 });
diff --git a/test/unit-tests/SupportedBrowser-test.ts b/test/unit-tests/SupportedBrowser-test.ts
index d3fa2803eabf8346255e49e3f1501612cba0227a..2b0f92b756a86d0d2004a5e38ad47e0deaebaa83 100644
--- a/test/unit-tests/SupportedBrowser-test.ts
+++ b/test/unit-tests/SupportedBrowser-test.ts
@@ -66,17 +66,17 @@ describe("SupportedBrowser", () => {
         // Safari 18.0 on macOS Sonoma
         "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
         // Latest Firefox on macOS Sonoma
-        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko/20100101 Firefox/133.0",
-        // Edge 131 on Windows
-        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.70",
-        // Edge 131 on macOS
-        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.70",
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:137.0) Gecko/20100101 Firefox/137.0",
+        // Latest Edge on Windows
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.3240.92",
+        // Latest Edge on macOS
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.3240.92",
         // Latest Firefox on Windows
-        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
         // Latest Firefox on Linux
-        "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0",
+        "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0",
         // Latest Chrome on Windows
-        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
     ])("should not warn for supported browsers", testUserAgentFactory());
 
     it.each([
diff --git a/test/unit-tests/Terms-test.tsx b/test/unit-tests/Terms-test.tsx
index 9fc29bde9a9fbc614be3f7cebc27523d1461d805..ca2476c0828bf7b3f355e40486f7ab3e5b989424 100644
--- a/test/unit-tests/Terms-test.tsx
+++ b/test/unit-tests/Terms-test.tsx
@@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, EventType, SERVICE_TYPES } from "matrix-js-sdk/src/matrix";
+import { EventType, MatrixEvent, type Policy, SERVICE_TYPES, type Terms } from "matrix-js-sdk/src/matrix";
+import { screen, within } from "jest-matrix-react";
 
-import { startTermsFlow, Service } from "../../src/Terms";
+import { dialogTermsInteractionCallback, Service, startTermsFlow } from "../../src/Terms";
 import { getMockClientWithEventEmitter } from "../test-utils";
 import { MatrixClientPeg } from "../../src/MatrixClientPeg";
 
@@ -18,7 +19,7 @@ const POLICY_ONE = {
         name: "The first policy",
         url: "http://example.com/one",
     },
-};
+} satisfies Policy;
 
 const POLICY_TWO = {
     version: "IX",
@@ -26,7 +27,7 @@ const POLICY_TWO = {
         name: "The second policy",
         url: "http://example.com/two",
     },
-};
+} satisfies Policy;
 
 const IM_SERVICE_ONE = new Service(SERVICE_TYPES.IM, "https://imone.test", "a token token");
 const IM_SERVICE_TWO = new Service(SERVICE_TYPES.IM, "https://imtwo.test", "a token token");
@@ -42,7 +43,7 @@ describe("Terms", function () {
     beforeEach(function () {
         jest.clearAllMocks();
         mockClient.getAccountData.mockReturnValue(undefined);
-        mockClient.getTerms.mockResolvedValue(null);
+        mockClient.getTerms.mockResolvedValue({ policies: {} });
         mockClient.setAccountData.mockResolvedValue({});
     });
 
@@ -141,22 +142,25 @@ describe("Terms", function () {
         });
         mockClient.getAccountData.mockReturnValue(directEvent);
 
-        mockClient.getTerms.mockImplementation(async (_serviceTypes: SERVICE_TYPES, baseUrl: string) => {
-            switch (baseUrl) {
-                case "https://imone.test":
-                    return {
-                        policies: {
-                            policy_the_first: POLICY_ONE,
-                        },
-                    };
-                case "https://imtwo.test":
-                    return {
-                        policies: {
-                            policy_the_second: POLICY_TWO,
-                        },
-                    };
-            }
-        });
+        mockClient.getTerms.mockImplementation(
+            async (_serviceTypes: SERVICE_TYPES, baseUrl: string): Promise<Terms> => {
+                switch (baseUrl) {
+                    case "https://imone.test":
+                        return {
+                            policies: {
+                                policy_the_first: POLICY_ONE,
+                            },
+                        };
+                    case "https://imtwo.test":
+                        return {
+                            policies: {
+                                policy_the_second: POLICY_TWO,
+                            },
+                        };
+                }
+                return { policies: {} };
+            },
+        );
 
         const interactionCallback = jest.fn().mockResolvedValue(["http://example.com/one", "http://example.com/two"]);
         await startTermsFlow(mockClient, [IM_SERVICE_ONE, IM_SERVICE_TWO], interactionCallback);
@@ -180,3 +184,29 @@ describe("Terms", function () {
         ]);
     });
 });
+
+describe("dialogTermsInteractionCallback", () => {
+    it("should render a dialog with the expected terms", async () => {
+        dialogTermsInteractionCallback(
+            [
+                {
+                    service: new Service(SERVICE_TYPES.IS, "http://base_url", "access_token"),
+                    policies: {
+                        sample: {
+                            version: "VERSION",
+                            en: {
+                                name: "Terms",
+                                url: "http://base_url/terms",
+                            },
+                        },
+                    },
+                },
+            ],
+            [],
+        );
+
+        const dialog = await screen.findByRole("dialog");
+        expect(within(dialog).getByRole("link")).toHaveAttribute("href", "http://base_url/terms");
+        expect(dialog).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/TestSdkContext.ts b/test/unit-tests/TestSdkContext.ts
index 1a8f9565a81d1967982d7b762b65daa158287bde..f4d7eea9e2814b94beaf938fc1d6d709de4502a2 100644
--- a/test/unit-tests/TestSdkContext.ts
+++ b/test/unit-tests/TestSdkContext.ts
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { SdkContextClass } from "../../src/contexts/SDKContext";
-import { PosthogAnalytics } from "../../src/PosthogAnalytics";
-import { SlidingSyncManager } from "../../src/SlidingSyncManager";
-import { RoomNotificationStateStore } from "../../src/stores/notifications/RoomNotificationStateStore";
-import RightPanelStore from "../../src/stores/right-panel/RightPanelStore";
-import { RoomViewStore } from "../../src/stores/RoomViewStore";
-import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
-import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
-import { WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
-import WidgetStore from "../../src/stores/WidgetStore";
+import { type PosthogAnalytics } from "../../src/PosthogAnalytics";
+import { type SlidingSyncManager } from "../../src/SlidingSyncManager";
+import { type RoomNotificationStateStore } from "../../src/stores/notifications/RoomNotificationStateStore";
+import type RightPanelStore from "../../src/stores/right-panel/RightPanelStore";
+import { type RoomViewStore } from "../../src/stores/RoomViewStore";
+import { type SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
+import { type WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
+import { type WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
+import type WidgetStore from "../../src/stores/WidgetStore";
 
 /**
  * A class which provides the same API as SdkContextClass but adds additional unsafe setters which can
diff --git a/test/unit-tests/TextForEvent-test.ts b/test/unit-tests/TextForEvent-test.ts
index 4dfccbb93e939d5095f6a53f0a55bd834db3fe25..a505d6510f28b2acfd0b01d5f2e20c59ac0edaab 100644
--- a/test/unit-tests/TextForEvent-test.ts
+++ b/test/unit-tests/TextForEvent-test.ts
@@ -10,15 +10,16 @@ import {
     EventType,
     HistoryVisibility,
     JoinRule,
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
+    type MRoomTopicEventContent,
     Room,
-    RoomMember,
+    type RoomMember,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { render } from "jest-matrix-react";
-import { ReactElement } from "react";
-import { Mocked, mocked } from "jest-mock";
+import { type ReactElement } from "react";
+import { type Mocked, mocked } from "jest-mock";
 
 import { textForEvent } from "../../src/TextForEvent";
 import SettingsStore from "../../src/settings/SettingsStore";
@@ -518,6 +519,49 @@ describe("TextForEvent", () => {
                 ),
             ).toMatchInlineSnapshot(`"Andy changed their display name and profile picture"`);
         });
+
+        it("should handle rejected invites", () => {
+            expect(
+                textForEvent(
+                    new MatrixEvent({
+                        type: "m.room.member",
+                        sender: "@a:foo",
+                        content: {
+                            membership: KnownMembership.Leave,
+                        },
+                        unsigned: {
+                            prev_content: {
+                                membership: KnownMembership.Invite,
+                            },
+                        },
+                        state_key: "@a:foo",
+                    }),
+                    mockClient,
+                ),
+            ).toMatchInlineSnapshot(`"Member rejected the invitation"`);
+        });
+
+        it("should handle rejected invites with a reason", () => {
+            expect(
+                textForEvent(
+                    new MatrixEvent({
+                        type: "m.room.member",
+                        sender: "@a:foo",
+                        content: {
+                            membership: KnownMembership.Leave,
+                            reason: "I don't want to be in this room.",
+                        },
+                        unsigned: {
+                            prev_content: {
+                                membership: KnownMembership.Invite,
+                            },
+                        },
+                        state_key: "@a:foo",
+                    }),
+                    mockClient,
+                ),
+            ).toMatchInlineSnapshot(`"Member rejected the invitation: I don't want to be in this room."`);
+        });
     });
 
     describe("textForJoinRulesEvent()", () => {
@@ -613,4 +657,47 @@ describe("TextForEvent", () => {
             },
         );
     });
+
+    describe("textForTopicEvent()", () => {
+        type TestCase = [string, MRoomTopicEventContent, { result: string }];
+        const testCases: TestCase[] = [
+            ["the legacy key", { topic: "My topic" }, { result: '@a changed the topic to "My topic".' }],
+            [
+                "the legacy key with an empty m.topic key",
+                { "topic": "My topic", "m.topic": [] },
+                { result: '@a changed the topic to "My topic".' },
+            ],
+            [
+                "the m.topic key",
+                { "topic": "Ignore this", "m.topic": [{ mimetype: "text/plain", body: "My topic" }] },
+                { result: '@a changed the topic to "My topic".' },
+            ],
+            [
+                "the m.topic key and the legacy key undefined",
+                { "topic": undefined, "m.topic": [{ mimetype: "text/plain", body: "My topic" }] },
+                { result: '@a changed the topic to "My topic".' },
+            ],
+            ["the legacy key undefined", { topic: undefined }, { result: "@a removed the topic." }],
+            ["the legacy key empty string", { topic: "" }, { result: "@a removed the topic." }],
+            [
+                "both the legacy and new keys removed",
+                { "topic": undefined, "m.topic": [] },
+                { result: "@a removed the topic." },
+            ],
+        ];
+
+        it.each(testCases)("returns correct message for topic event with %s", (_caseName, content, { result }) => {
+            expect(
+                textForEvent(
+                    new MatrixEvent({
+                        type: "m.room.topic",
+                        sender: "@a",
+                        content: content,
+                        state_key: "",
+                    }),
+                    mockClient,
+                ),
+            ).toEqual(result);
+        });
+    });
 });
diff --git a/test/unit-tests/__snapshots__/HtmlUtils-test.tsx.snap b/test/unit-tests/__snapshots__/HtmlUtils-test.tsx.snap
index 1a6e6dfe48aa60091efd48e1d2c26b5ebbc3155a..018a6721c1ab24f6e3d1665505d1d4a1f76cb21a 100644
--- a/test/unit-tests/__snapshots__/HtmlUtils-test.tsx.snap
+++ b/test/unit-tests/__snapshots__/HtmlUtils-test.tsx.snap
@@ -19,7 +19,7 @@ exports[`bodyToHtml feature_latex_maths should render block katex 1`] = `"<p>hel
 
 exports[`bodyToHtml feature_latex_maths should render inline katex 1`] = `"hello <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ξ</mi></mrow><annotation encoding="application/x-tex">\\xi</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.04601em;">ξ</span></span></span></span> world"`;
 
-exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`] = `
+exports[`bodyToNode generates big emoji for emoji made of multiple characters 1`] = `
 <DocumentFragment>
   <span
     class="mx_EventTile_body mx_EventTile_bigEmoji translate"
@@ -49,7 +49,7 @@ exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`
 </DocumentFragment>
 `;
 
-exports[`bodyToHtml should generate big emoji for an emoji-only reply to a message 1`] = `
+exports[`bodyToNode should generate big emoji for an emoji-only reply to a message 1`] = `
 <DocumentFragment>
   <span
     class="mx_EventTile_body mx_EventTile_bigEmoji translate"
@@ -64,3 +64,30 @@ exports[`bodyToHtml should generate big emoji for an emoji-only reply to a messa
   </span>
 </DocumentFragment>
 `;
+
+exports[`bodyToNode should handle inline media when mediaIsVisible is false 1`] = `
+<DocumentFragment>
+  <span
+    class="mx_EventTile_body markdown-body translate"
+    dir="auto"
+  >
+    <img />
+    foo Hello there
+  </span>
+</DocumentFragment>
+`;
+
+exports[`bodyToNode should handle inline media when mediaIsVisible is true 1`] = `
+<DocumentFragment>
+  <span
+    class="mx_EventTile_body markdown-body translate"
+    dir="auto"
+  >
+    <img
+      src="https://example.org/img"
+      style="max-width:800px;max-height:600px"
+    />
+    foo Hello there
+  </span>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/__snapshots__/Terms-test.tsx.snap b/test/unit-tests/__snapshots__/Terms-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..a35963cd51439a7f5fe20bf8a40295d5c1f9c00b
--- /dev/null
+++ b/test/unit-tests/__snapshots__/Terms-test.tsx.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`dialogTermsInteractionCallback should render a dialog with the expected terms 1`] = `
+<div
+  aria-describedby="mx_Dialog_content"
+  aria-labelledby="mx_BaseDialog_title"
+  class=""
+  data-focus-lock-disabled="false"
+  role="dialog"
+>
+  <div
+    class="mx_Dialog_header"
+  >
+    <h1
+      class="mx_Heading_h3 mx_Dialog_title"
+      id="mx_BaseDialog_title"
+    >
+      Terms of Service
+    </h1>
+  </div>
+  <div
+    id="mx_Dialog_content"
+  >
+    <p>
+      To continue you need to accept the terms of this service.
+    </p>
+    <table
+      class="mx_TermsDialog_termsTable"
+    >
+      <tbody>
+        <tr
+          class="mx_TermsDialog_termsTableHeader"
+        >
+          <th>
+            Service
+          </th>
+          <th>
+            Summary
+          </th>
+          <th>
+            Document
+          </th>
+          <th>
+            Accept
+          </th>
+        </tr>
+        <tr>
+          <td
+            class="mx_TermsDialog_service"
+          >
+            <div>
+              Identity server
+              <br />
+              (
+              base_url
+              )
+            </div>
+          </td>
+          <td
+            class="mx_TermsDialog_summary"
+          >
+            <div>
+              Find others by phone or email
+              <br />
+              Be found by phone or email
+            </div>
+          </td>
+          <td>
+            <a
+              class="mx_ExternalLink"
+              href="http://base_url/terms"
+              rel="noreferrer noopener"
+              target="_blank"
+            >
+              Terms
+              <i
+                class="mx_ExternalLink_icon"
+              />
+            </a>
+          </td>
+          <td>
+            <input
+              type="checkbox"
+            />
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+  <div
+    class="mx_Dialog_buttons"
+  >
+    <span
+      class="mx_Dialog_buttons_row"
+    >
+      <button
+        data-testid="dialog-cancel-button"
+        type="button"
+      >
+        Cancel
+      </button>
+      <button
+        class="mx_Dialog_primary"
+        data-testid="dialog-primary-button"
+        disabled=""
+        type="button"
+      >
+        Next
+      </button>
+    </span>
+  </div>
+</div>
+`;
diff --git a/test/unit-tests/__snapshots__/TextForEvent-test.ts.snap b/test/unit-tests/__snapshots__/TextForEvent-test.ts.snap
index 1ab32ffd04720408708efca5b88b58caa6945009..6713c3d4495721e797f017a2d4f00e04fc00c5e1 100644
--- a/test/unit-tests/__snapshots__/TextForEvent-test.ts.snap
+++ b/test/unit-tests/__snapshots__/TextForEvent-test.ts.snap
@@ -7,8 +7,6 @@ exports[`TextForEvent textForJoinRulesEvent() returns correct JSX message when r
     <AccessibleButton
       kind="link_inline"
       onClick={[Function]}
-      role="button"
-      tabIndex={0}
     >
       View settings
     </AccessibleButton>
diff --git a/test/unit-tests/accessibility/RovingTabIndex-test.tsx b/test/unit-tests/accessibility/RovingTabIndex-test.tsx
index e0efaf6b1b1a2b83ba27856df7c4514edbcf6136..00c0c679137db7756de90da380b10e585635bfcb 100644
--- a/test/unit-tests/accessibility/RovingTabIndex-test.tsx
+++ b/test/unit-tests/accessibility/RovingTabIndex-test.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { HTMLAttributes } from "react";
+import React, { type HTMLAttributes } from "react";
 import { act, render } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 import {
-    IState,
+    type IState,
     reducer,
     RovingTabIndexProvider,
     RovingTabIndexWrapper,
diff --git a/test/unit-tests/async-components/dialogs/security/NewRecoveryMethodDialog-test.tsx b/test/unit-tests/async-components/dialogs/security/NewRecoveryMethodDialog-test.tsx
index cca65265823dd47ba512aa2a3fca2adf37e3e9c5..c49f2c48a44c4fb433b6e766118e7e22224be82e 100644
--- a/test/unit-tests/async-components/dialogs/security/NewRecoveryMethodDialog-test.tsx
+++ b/test/unit-tests/async-components/dialogs/security/NewRecoveryMethodDialog-test.tsx
@@ -6,7 +6,7 @@
  */
 
 import React from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { render, screen, act } from "jest-matrix-react";
 import { waitFor } from "@testing-library/dom";
 import userEvent from "@testing-library/user-event";
diff --git a/test/unit-tests/async-components/structures/ErrorView-test.tsx b/test/unit-tests/async-components/structures/ErrorView-test.tsx
index da4ff9c8e0f82667732774a2577617827a0a20d4..aeb0e0c2a52ff412a099c45f94f78824270aa3d9 100644
--- a/test/unit-tests/async-components/structures/ErrorView-test.tsx
+++ b/test/unit-tests/async-components/structures/ErrorView-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { render } from "jest-matrix-react";
 
 import SdkConfig from "../../../../src/SdkConfig";
diff --git a/test/unit-tests/async-components/structures/__snapshots__/ErrorView-test.tsx.snap b/test/unit-tests/async-components/structures/__snapshots__/ErrorView-test.tsx.snap
index f3a36896468b557cd529d576c69f6f676ea3fde9..3cc2f1630fb9578e83981c8bdd69af6971720249 100644
--- a/test/unit-tests/async-components/structures/__snapshots__/ErrorView-test.tsx.snap
+++ b/test/unit-tests/async-components/structures/__snapshots__/ErrorView-test.tsx.snap
@@ -15,17 +15,17 @@ exports[`<ErrorView /> should match snapshot 1`] = `
       class="mx_ErrorView_container"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
+        class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
       >
         TITLE
       </h1>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         MSG1
       </p>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         MSG2
       </p>
@@ -49,17 +49,17 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
       class="mx_ErrorView_container"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
+        class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
       >
         Element does not support this browser
       </h1>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         Element uses some browser features which are not available in your current browser. Try updating this browser if you're not using the latest version and try again.
       </p>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         <span>
           For the best experience, use 
@@ -99,10 +99,10 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
       </p>
       <div
         class="mx_Flex mx_ErrorView_buttons"
-        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <button
-          class="_button_i91xf_17 _has-icon_i91xf_66"
+          class="_button_vczzf_8 _has-icon_vczzf_57"
           data-kind="secondary"
           data-size="sm"
           role="button"
@@ -117,10 +117,10 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"
+              d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2"
             />
             <path
-              d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2Z"
+              d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2"
             />
           </svg>
           Learn more
@@ -128,22 +128,22 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
       </div>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
     />
     <h2
-      class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+      class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
     >
       Use Element Desktop instead
     </h2>
     <div
       class="mx_Flex"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
     >
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://packages.element.io/desktop/install/macos/Element.dmg"
@@ -158,7 +158,7 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
         Mac
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe"
@@ -173,10 +173,10 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
         Windows (64-bit)
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
-        href="https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe"
+        href="https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe"
         role="link"
         tabindex="0"
       >
@@ -185,10 +185,10 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
           height="20"
           width="20"
         />
-        Windows (32-bit)
+        Windows (ARM 64-bit)
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://element.io/download#linux"
@@ -204,13 +204,13 @@ exports[`<UnsupportedBrowserView /> should match snapshot 1`] = `
       </a>
     </div>
     <h2
-      class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+      class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
     >
       Or use our mobile app
     </h2>
     <div
       class="mx_Flex"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-6x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-6x); --mx-flex-wrap: nowrap;"
     >
       <a
         href="https://apps.apple.com/app/vector/id1083446067"
diff --git a/test/unit-tests/audio/Playback-test.ts b/test/unit-tests/audio/Playback-test.ts
index 0aeea5c832219472b6b7447308b36dc7600d5e5b..7f14685f4504903ac998fd692c80013605704fa9 100644
--- a/test/unit-tests/audio/Playback-test.ts
+++ b/test/unit-tests/audio/Playback-test.ts
@@ -47,7 +47,7 @@ describe("Playback", () => {
     beforeEach(() => {
         jest.spyOn(logger, "error").mockRestore();
         mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
-        mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
+        mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer);
         mockAudioContext.resume.mockClear().mockResolvedValue(undefined);
         mockAudioContext.suspend.mockClear().mockResolvedValue(undefined);
         mocked(decodeOgg).mockClear().mockResolvedValue(new ArrayBuffer(1));
@@ -131,8 +131,8 @@ describe("Playback", () => {
             const buffer = new ArrayBuffer(8);
             const decodingError = new Error("test");
             mockAudioContext.decodeAudioData
-                .mockImplementationOnce((_b, _callback, error) => error(decodingError))
-                .mockImplementationOnce((_b, callback) => callback(mockAudioBuffer));
+                .mockRejectedValueOnce(decodingError)
+                .mockResolvedValueOnce(mockAudioBuffer);
 
             const playback = new Playback(buffer);
 
diff --git a/test/unit-tests/audio/VoiceMessageRecording-test.ts b/test/unit-tests/audio/VoiceMessageRecording-test.ts
index 8cb1ac3afbd3e23c81a15a370a404d47b5c8d7b4..aec34fa236c6439ea46ebe342ad61cda28cd2f8c 100644
--- a/test/unit-tests/audio/VoiceMessageRecording-test.ts
+++ b/test/unit-tests/audio/VoiceMessageRecording-test.ts
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { UploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { EncryptedFile } from "matrix-js-sdk/src/types";
+import { type UploadOpts, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type EncryptedFile } from "matrix-js-sdk/src/types";
 
 import { createVoiceMessageRecording, VoiceMessageRecording } from "../../../src/audio/VoiceMessageRecording";
-import { RecordingState, VoiceRecording } from "../../../src/audio/VoiceRecording";
+import { RecordingState, type VoiceRecording } from "../../../src/audio/VoiceRecording";
 import { uploadFile } from "../../../src/ContentMessages";
 import { stubClient } from "../../test-utils";
 import { Playback } from "../../../src/audio/Playback";
diff --git a/test/unit-tests/audio/compat-test.ts b/test/unit-tests/audio/compat-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da90d6171177e9f3778946e78110be3cb711c235
--- /dev/null
+++ b/test/unit-tests/audio/compat-test.ts
@@ -0,0 +1,15 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { createAudioContext } from "../../../src/audio/compat";
+
+describe("createAudioContext", () => {
+    it("should throw if AudioContext is not supported", () => {
+        window.AudioContext = undefined as any;
+        expect(createAudioContext).toThrow("Unsupported browser");
+    });
+});
diff --git a/test/unit-tests/autocomplete/EmojiProvider-test.ts b/test/unit-tests/autocomplete/EmojiProvider-test.ts
index 81fd832e64342cfd7bff0e49fa9a7dee3983c795..c2c42691b73334c6363ecf1bebf80e754f9d3dd8 100644
--- a/test/unit-tests/autocomplete/EmojiProvider-test.ts
+++ b/test/unit-tests/autocomplete/EmojiProvider-test.ts
@@ -33,6 +33,10 @@ const EMOJI_SHORTCODES = [
 // to simply assert that the final completion with the colon is the exact emoji.
 const TOO_SHORT_EMOJI_SHORTCODE = [{ emojiShortcode: ":o", expectedEmoji: "⭕️" }];
 
+interface CompletionComponentProps {
+    title: string;
+}
+
 describe("EmojiProvider", function () {
     const testRoom = mkStubRoom(undefined, undefined, undefined);
     stubClient();
@@ -69,8 +73,8 @@ describe("EmojiProvider", function () {
 
         const ep = new EmojiProvider(testRoom);
         const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 });
-        expect(completionsList[0]?.component?.props.title).toEqual(":heartpulse:");
-        expect(completionsList[1]?.component?.props.title).toEqual(":heart_eyes:");
+        expect((completionsList[0]?.component?.props as CompletionComponentProps).title).toEqual(":heartpulse:");
+        expect((completionsList[1]?.component?.props as CompletionComponentProps).title).toEqual(":heart_eyes:");
     });
 
     it("Exact match in recently used takes the lead", async function () {
@@ -83,8 +87,8 @@ describe("EmojiProvider", function () {
         const ep = new EmojiProvider(testRoom);
         const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 });
 
-        expect(completionsList[0]?.component?.props.title).toEqual(":heart:");
-        expect(completionsList[1]?.component?.props.title).toEqual(":heartpulse:");
-        expect(completionsList[2]?.component?.props.title).toEqual(":heart_eyes:");
+        expect((completionsList[0]?.component?.props as CompletionComponentProps).title).toEqual(":heart:");
+        expect((completionsList[1]?.component?.props as CompletionComponentProps).title).toEqual(":heartpulse:");
+        expect((completionsList[2]?.component?.props as CompletionComponentProps).title).toEqual(":heart_eyes:");
     });
 });
diff --git a/test/unit-tests/autocomplete/RoomProvider-test.ts b/test/unit-tests/autocomplete/RoomProvider-test.ts
index 80b92511343a5f9daeb7febef15bc4b46a1f791c..3da37528ed083e2f5d3595df4cc0e5c9e60cb56c 100644
--- a/test/unit-tests/autocomplete/RoomProvider-test.ts
+++ b/test/unit-tests/autocomplete/RoomProvider-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
 import RoomProvider from "../../../src/autocomplete/RoomProvider";
 import SettingsStore from "../../../src/settings/SettingsStore";
diff --git a/test/unit-tests/autocomplete/SpaceProvider-test.ts b/test/unit-tests/autocomplete/SpaceProvider-test.ts
index 789a3bd4a6bf7d3112afd678f39b5e21e66e5d74..9769d556607f944be16a582f8fedb43ae43a8c1f 100644
--- a/test/unit-tests/autocomplete/SpaceProvider-test.ts
+++ b/test/unit-tests/autocomplete/SpaceProvider-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
 import SpaceProvider from "../../../src/autocomplete/SpaceProvider";
 import SettingsStore from "../../../src/settings/SettingsStore";
diff --git a/test/unit-tests/components/structures/AutocompleteInput-test.tsx b/test/unit-tests/components/structures/AutocompleteInput-test.tsx
index d34c521f9719424580355f7fa952ed50332ad6c3..17d443564c312e995d6392c31d6481dd8bc20c74 100644
--- a/test/unit-tests/components/structures/AutocompleteInput-test.tsx
+++ b/test/unit-tests/components/structures/AutocompleteInput-test.tsx
@@ -10,8 +10,8 @@ import React from "react";
 import { screen, render, fireEvent, waitFor, within, act } from "jest-matrix-react";
 
 import * as TestUtils from "../../../test-utils";
-import AutocompleteProvider from "../../../../src/autocomplete/AutocompleteProvider";
-import { ICompletion } from "../../../../src/autocomplete/Autocompleter";
+import type AutocompleteProvider from "../../../../src/autocomplete/AutocompleteProvider";
+import { type ICompletion } from "../../../../src/autocomplete/Autocompleter";
 import { AutocompleteInput } from "../../../../src/components/structures/AutocompleteInput";
 
 describe("AutocompleteInput", () => {
@@ -63,12 +63,12 @@ describe("AutocompleteInput", () => {
 
         const input = getEditorInput();
 
-        act(() => {
+        await act(async () => {
             fireEvent.focus(input);
             fireEvent.change(input, { target: { value: "user" } });
+            await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
         });
 
-        await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
         expect(screen.getByTestId("autocomplete-matches").childNodes).toHaveLength(mockCompletion.length);
     });
 
@@ -152,12 +152,12 @@ describe("AutocompleteInput", () => {
 
         const input = getEditorInput();
 
-        act(() => {
+        await act(async () => {
             fireEvent.focus(input);
             fireEvent.change(input, { target: { value: "user" } });
+            await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
         });
 
-        await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
         expect(screen.getAllByTestId("custom-suggestion-element")).toHaveLength(mockCompletion.length);
     });
 
diff --git a/test/unit-tests/components/structures/FilePanel-test.tsx b/test/unit-tests/components/structures/FilePanel-test.tsx
index e81b15d95a933c4d36777d15641d727e5f60410e..0b9b18c38eaca86ea6c5e34a7efb72b3ea037fc2 100644
--- a/test/unit-tests/components/structures/FilePanel-test.tsx
+++ b/test/unit-tests/components/structures/FilePanel-test.tsx
@@ -65,7 +65,9 @@ describe("FilePanel", () => {
                     roomId={room.roomId}
                     onClose={jest.fn()}
                     resizeNotifier={new ResizeNotifier()}
-                    ref={(ref) => (filePanel = ref)}
+                    ref={(ref) => {
+                        filePanel = ref;
+                    }}
                 />,
             );
             await screen.findByText("No files visible in this room");
diff --git a/test/unit-tests/components/structures/LeftPanel-test.tsx b/test/unit-tests/components/structures/LeftPanel-test.tsx
index 93f014567c8d3b6c1774c75bbdabb985ceba1595..518dc948deabeeb2f1927830df9a11185af3c0e4 100644
--- a/test/unit-tests/components/structures/LeftPanel-test.tsx
+++ b/test/unit-tests/components/structures/LeftPanel-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult, screen } from "jest-matrix-react";
+import { render, type RenderResult, screen } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 
 import LeftPanel from "../../../../src/components/structures/LeftPanel";
diff --git a/test/unit-tests/components/structures/LegacyCallEventGrouper-test.ts b/test/unit-tests/components/structures/LegacyCallEventGrouper-test.ts
index 22fb5d300c56e8d7f0d0e4a939cb67fdbd65648a..6c4fd2301895f72ff7a9e33201afb475377db010 100644
--- a/test/unit-tests/components/structures/LegacyCallEventGrouper-test.ts
+++ b/test/unit-tests/components/structures/LegacyCallEventGrouper-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
 import { CallState } from "matrix-js-sdk/src/webrtc/call";
 
 import { stubClient } from "../../../test-utils";
diff --git a/test/unit-tests/components/structures/LoggedInView-test.tsx b/test/unit-tests/components/structures/LoggedInView-test.tsx
index b4df9e292636e8be1cd1414dfc7e77a4a8abde61..6d24cbe416ba47f3c364444c45001b746d646fa7 100644
--- a/test/unit-tests/components/structures/LoggedInView-test.tsx
+++ b/test/unit-tests/components/structures/LoggedInView-test.tsx
@@ -7,11 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult } from "jest-matrix-react";
-import { ConditionKind, EventType, IPushRule, MatrixEvent, ClientEvent, PushRuleKind } from "matrix-js-sdk/src/matrix";
+import { render, type RenderResult } from "jest-matrix-react";
+import {
+    ConditionKind,
+    EventType,
+    type IPushRule,
+    MatrixEvent,
+    ClientEvent,
+    PushRuleKind,
+} from "matrix-js-sdk/src/matrix";
 import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
 import { logger } from "matrix-js-sdk/src/logger";
 import userEvent from "@testing-library/user-event";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
 
 import LoggedInView from "../../../../src/components/structures/LoggedInView";
 import { SDKContext } from "../../../../src/contexts/SDKContext";
@@ -68,6 +76,8 @@ describe("<LoggedInView />", () => {
         jest.clearAllMocks();
         mockClient.getMediaHandler.mockReturnValue(mediaHandler);
         mockClient.setPushRuleActions.mockReset().mockResolvedValue({});
+        // @ts-expect-error
+        mockClient.pushProcessor = new PushProcessor(mockClient);
     });
 
     describe("synced push rules", () => {
@@ -414,6 +424,14 @@ describe("<LoggedInView />", () => {
         expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: Action.ViewHomePage });
     });
 
+    it("should open spotlight when Ctrl+k is fired", async () => {
+        jest.spyOn(defaultDispatcher, "fire");
+
+        getComponent();
+        await userEvent.keyboard("{Control>}k{/Control}");
+        expect(defaultDispatcher.fire).toHaveBeenCalledWith(Action.OpenSpotlight);
+    });
+
     describe("timezone updates", () => {
         const userTimezone = "Europe/London";
         const originalController = SETTINGS["userTimezonePublish"].controller;
diff --git a/test/unit-tests/components/structures/MainSplit-test.tsx b/test/unit-tests/components/structures/MainSplit-test.tsx
index 1b9501ee27c4c27191baac1b8619d0bae1671798..2feb9b22f676aac4b501b282adecba8fb4391665 100644
--- a/test/unit-tests/components/structures/MainSplit-test.tsx
+++ b/test/unit-tests/components/structures/MainSplit-test.tsx
@@ -86,7 +86,7 @@ describe("<MainSplit/>", () => {
 
         const handle = container.querySelector(".mx_ResizeHandle--horizontal")!;
         fireEvent.mouseDown(handle);
-        fireEvent.mouseMove(handle, { clientX: 0 });
+        fireEvent.resize(handle, { clientX: 0 });
         fireEvent.mouseUp(handle);
 
         expect(spy).toHaveBeenCalledWith({
diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx
index a29834d51f7026e5c17424962accbb975452e514..62fcf40f4d3f755230dad9b671379178242f111a 100644
--- a/test/unit-tests/components/structures/MatrixChat-test.tsx
+++ b/test/unit-tests/components/structures/MatrixChat-test.tsx
@@ -10,19 +10,24 @@ Please see LICENSE files in the repository root for full details.
 // https://github.com/dumbmatter/fakeIndexedDB?tab=readme-ov-file#jsdom-often-used-with-jest
 import "core-js/stable/structured-clone";
 import "fake-indexeddb/auto";
-import React, { ComponentProps } from "react";
-import { fireEvent, render, RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
+import React, { type ComponentProps } from "react";
+import { fireEvent, render, type RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
 import fetchMock from "fetch-mock-jest";
-import { Mocked, mocked } from "jest-mock";
-import { ClientEvent, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
-import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
+import { type Mocked, mocked } from "jest-mock";
+import { ClientEvent, type MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
+import { type MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
 import * as MatrixJs from "matrix-js-sdk/src/matrix";
 import { completeAuthorizationCodeGrant } from "matrix-js-sdk/src/oidc/authorize";
 import { logger } from "matrix-js-sdk/src/logger";
 import { OidcError } from "matrix-js-sdk/src/oidc/error";
-import { BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
-import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils";
-import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
+import { type BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
+import { sleep } from "matrix-js-sdk/src/utils";
+import {
+    CryptoEvent,
+    type DeviceVerificationStatus,
+    UserVerificationStatus,
+    type CryptoApi,
+} from "matrix-js-sdk/src/crypto-api";
 
 import MatrixChat from "../../../../src/components/structures/MatrixChat";
 import * as StorageAccess from "../../../../src/utils/StorageAccess";
@@ -46,7 +51,7 @@ import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour";
 import { OidcClientError } from "../../../../src/utils/oidc/error";
 import LegacyCallHandler from "../../../../src/LegacyCallHandler";
 import { CallStore } from "../../../../src/stores/CallStore";
-import { Call } from "../../../../src/models/Call";
+import { type Call } from "../../../../src/models/Call";
 import { PosthogAnalytics } from "../../../../src/PosthogAnalytics";
 import PlatformPeg from "../../../../src/PlatformPeg";
 import EventIndexPeg from "../../../../src/indexing/EventIndexPeg";
@@ -60,8 +65,12 @@ import { ReleaseAnnouncementStore } from "../../../../src/stores/ReleaseAnnounce
 import { DRAFT_LAST_CLEANUP_KEY } from "../../../../src/DraftCleaner";
 import { UIFeature } from "../../../../src/settings/UIFeature";
 import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils";
-import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
 import Modal from "../../../../src/Modal.tsx";
+import { SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore.ts";
+import { ShareFormat } from "../../../../src/dispatcher/payloads/SharePayload.ts";
+import { clearStorage } from "../../../../src/Lifecycle";
+import RoomListStore from "../../../../src/stores/room-list/RoomListStore.ts";
 
 jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
     completeAuthorizationCodeGrant: jest.fn(),
@@ -79,7 +88,7 @@ describe("<MatrixChat />", () => {
     const deviceId = "qwertyui";
     const accessToken = "abc123";
     const refreshToken = "def456";
-    let bootstrapDeferred: IDeferred<void>;
+    let bootstrapDeferred: PromiseWithResolvers<void>;
     // reused in createClient mock below
     const getMockClientMethods = () => ({
         ...mockClientMethodsUser(userId),
@@ -112,7 +121,7 @@ describe("<MatrixChat />", () => {
             startup: jest.fn(),
         },
         login: jest.fn(),
-        loginFlows: jest.fn(),
+        loginFlows: jest.fn().mockResolvedValue({ flows: [] }),
         isGuest: jest.fn().mockReturnValue(false),
         clearStores: jest.fn(),
         setGuest: jest.fn(),
@@ -125,10 +134,10 @@ describe("<MatrixChat />", () => {
         }),
         getVisibleRooms: jest.fn().mockReturnValue([]),
         getRooms: jest.fn().mockReturnValue([]),
-        setGlobalErrorOnUnknownDevices: jest.fn(),
         getCrypto: jest.fn().mockReturnValue({
             getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
             isCrossSigningReady: jest.fn().mockReturnValue(false),
+            isDehydrationSupported: jest.fn().mockReturnValue(false),
             getUserDeviceInfo: jest.fn().mockReturnValue(new Map()),
             getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
             getVersion: jest.fn().mockReturnValue("1"),
@@ -148,6 +157,7 @@ describe("<MatrixChat />", () => {
         whoami: jest.fn(),
         logout: jest.fn(),
         getDeviceId: jest.fn(),
+        forget: () => Promise.resolve(),
     });
     let mockClient: Mocked<MatrixClient>;
     const serverConfig = {
@@ -159,12 +169,9 @@ describe("<MatrixChat />", () => {
         isNameResolvable: true,
         warning: "",
     };
-    let initPromise: Promise<void> | undefined;
     let defaultProps: ComponentProps<typeof MatrixChat>;
     const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) => {
-        // MatrixChat does many questionable things which bomb tests in modern React mode,
-        // we'll want to refactor and break up MatrixChat before turning off legacyRoot mode
-        return render(<MatrixChat {...defaultProps} {...props} />, { legacyRoot: true });
+        return render(<MatrixChat {...defaultProps} {...props} />);
     };
 
     // make test results readable
@@ -214,6 +221,11 @@ describe("<MatrixChat />", () => {
     };
 
     beforeEach(async () => {
+        await clearStorage();
+        Lifecycle.setSessionLockNotStolen();
+
+        localStorage.clear();
+        jest.restoreAllMocks();
         defaultProps = {
             config: {
                 brand: "Test",
@@ -229,10 +241,8 @@ describe("<MatrixChat />", () => {
             onNewScreen: jest.fn(),
             onTokenLoginCompleted: jest.fn(),
             realQueryParams: {},
-            initPromiseCallback: (p: Promise<void>) => (initPromise = p),
         };
 
-        initPromise = undefined;
         mockClient = getMockClientWithEventEmitter(getMockClientMethods());
         jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient);
 
@@ -245,24 +255,15 @@ describe("<MatrixChat />", () => {
             {} as ValidatedServerConfig,
         );
 
-        bootstrapDeferred = defer();
+        bootstrapDeferred = Promise.withResolvers();
 
         await clearAllModals();
     });
 
     afterEach(async () => {
-        // Wait for the promise that MatrixChat gives us to complete so that we know
-        // it's finished running its login code. We either need to do this or make the
-        // login code abort halfway through once the test finishes testing whatever it
-        // needs to test. If we do nothing, the login code will just continue running
-        // and interfere with the subsequent tests.
-        await initPromise;
-
         // @ts-ignore
         DMRoomMap.setShared(null);
 
-        jest.restoreAllMocks();
-
         // emit a loggedOut event so that all of the Store singletons forget about their references to the mock client
         // (must be sync otherwise the next test will start before it happens)
         act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true));
@@ -307,7 +308,6 @@ describe("<MatrixChat />", () => {
             state: state,
         };
 
-        const userId = "@alice:server.org";
         const deviceId = "test-device-id";
         const accessToken = "test-access-token-from-oidc";
 
@@ -328,9 +328,7 @@ describe("<MatrixChat />", () => {
             await flushPromises();
             const dialog = await screen.findByRole("dialog");
 
-            expect(within(dialog).getByText(errorMessage)).toBeInTheDocument();
-            // just check we're back on welcome page
-            await expect(screen.findByTestId("mx_welcome_screen")).resolves.toBeInTheDocument();
+            await waitFor(() => expect(within(dialog).getByText(errorMessage)).toBeInTheDocument());
         };
 
         beforeEach(() => {
@@ -353,10 +351,6 @@ describe("<MatrixChat />", () => {
                     },
                 });
 
-            jest.spyOn(logger, "error").mockClear();
-        });
-
-        beforeEach(() => {
             loginClient = getMockClientWithEventEmitter(getMockClientMethods());
             // this is used to create a temporary client during login
             jest.spyOn(MatrixJs, "createClient").mockReturnValue(loginClient);
@@ -493,25 +487,32 @@ describe("<MatrixChat />", () => {
                 );
             });
 
+            afterEach(() => {
+                SettingsStore.reset();
+            });
+
             it("should persist login credentials", async () => {
                 getComponent({ realQueryParams });
 
-                await waitFor(() => expect(localStorage.getItem("mx_hs_url")).toEqual(homeserverUrl));
+                await waitFor(() => expect(localStorage.getItem("mx_device_id")).toEqual(deviceId));
+                expect(localStorage.getItem("mx_hs_url")).toEqual(homeserverUrl);
                 expect(localStorage.getItem("mx_user_id")).toEqual(userId);
                 expect(localStorage.getItem("mx_has_access_token")).toEqual("true");
-                expect(localStorage.getItem("mx_device_id")).toEqual(deviceId);
             });
 
             it("should store clientId and issuer in session storage", async () => {
                 getComponent({ realQueryParams });
 
                 await waitFor(() => expect(localStorage.getItem("mx_oidc_client_id")).toEqual(clientId));
-                expect(localStorage.getItem("mx_oidc_token_issuer")).toEqual(issuer);
+                await waitFor(() => expect(localStorage.getItem("mx_oidc_token_issuer")).toEqual(issuer));
             });
 
             it("should set logged in and start MatrixClient", async () => {
                 getComponent({ realQueryParams });
 
+                defaultDispatcher.dispatch({
+                    action: "will_start_client",
+                });
                 // client successfully started
                 await waitFor(() =>
                     expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }),
@@ -591,9 +592,7 @@ describe("<MatrixChat />", () => {
             // wait for logged in view to load
             await screen.findByLabelText("User menu");
 
-            expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
-            const h1Element = screen.getByRole("heading", { level: 1 });
-            expect(h1Element).toHaveTextContent(`Welcome Ernie`);
+            await screen.findByRole("heading", { level: 1, name: "Welcome Ernie" });
         });
 
         describe("clean up drafts", () => {
@@ -606,9 +605,6 @@ describe("<MatrixChat />", () => {
                 localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content");
                 mockClient.getRoom.mockImplementation((id) => [room].find((room) => room.roomId === id) || null);
             });
-            afterEach(() => {
-                jest.restoreAllMocks();
-            });
             it("should clean up drafts", async () => {
                 Date.now = jest.fn(() => timestamp);
                 localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content");
@@ -682,6 +678,34 @@ describe("<MatrixChat />", () => {
                     jest.restoreAllMocks();
                 });
 
+                describe("forget_room", () => {
+                    it("should dispatch after_forget_room action on successful forget", async () => {
+                        await clearAllModals();
+                        await getComponentAndWaitForReady();
+
+                        // Mock out the old room list store
+                        jest.spyOn(RoomListStore.instance, "manualRoomUpdate").mockImplementation(async () => {});
+
+                        // Register a mock function to the dispatcher
+                        const fn = jest.fn();
+                        defaultDispatcher.register(fn);
+
+                        // Forge the room
+                        defaultDispatcher.dispatch({
+                            action: "forget_room",
+                            room_id: roomId,
+                        });
+
+                        // On success, we expect the following action to have been dispatched.
+                        await waitFor(() => {
+                            expect(fn).toHaveBeenCalledWith({
+                                action: Action.AfterForgetRoom,
+                                room: room,
+                            });
+                        });
+                    });
+                });
+
                 describe("leave_room", () => {
                     beforeEach(async () => {
                         await clearAllModals();
@@ -790,6 +814,108 @@ describe("<MatrixChat />", () => {
                         });
                     });
                 });
+
+                it("should open forward dialog when text message shared", async () => {
+                    await getComponentAndWaitForReady();
+                    defaultDispatcher.dispatch({ action: Action.Share, format: ShareFormat.Text, msg: "Hello world" });
+                    await waitFor(() => {
+                        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
+                            action: Action.OpenForwardDialog,
+                            event: expect.any(MatrixEvent),
+                            permalinkCreator: null,
+                        });
+                    });
+                    const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
+                        ([call]) => call.action === Action.OpenForwardDialog,
+                    );
+
+                    const payload = forwardCall?.[0];
+
+                    expect(payload!.event.getContent()).toEqual({
+                        msgtype: MatrixJs.MsgType.Text,
+                        body: "Hello world",
+                    });
+                });
+
+                it("should open forward dialog when html message shared", async () => {
+                    await getComponentAndWaitForReady();
+                    defaultDispatcher.dispatch({ action: Action.Share, format: ShareFormat.Html, msg: "Hello world" });
+                    await waitFor(() => {
+                        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
+                            action: Action.OpenForwardDialog,
+                            event: expect.any(MatrixEvent),
+                            permalinkCreator: null,
+                        });
+                    });
+                    const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
+                        ([call]) => call.action === Action.OpenForwardDialog,
+                    );
+
+                    const payload = forwardCall?.[0];
+
+                    expect(payload!.event.getContent()).toEqual({
+                        msgtype: MatrixJs.MsgType.Text,
+                        format: "org.matrix.custom.html",
+                        body: expect.stringContaining("Hello world"),
+                        formatted_body: expect.stringContaining("Hello world"),
+                    });
+                });
+
+                it("should open forward dialog when markdown message shared", async () => {
+                    await getComponentAndWaitForReady();
+                    defaultDispatcher.dispatch({
+                        action: Action.Share,
+                        format: ShareFormat.Markdown,
+                        msg: "Hello *world*",
+                    });
+                    await waitFor(() => {
+                        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
+                            action: Action.OpenForwardDialog,
+                            event: expect.any(MatrixEvent),
+                            permalinkCreator: null,
+                        });
+                    });
+                    const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
+                        ([call]) => call.action === Action.OpenForwardDialog,
+                    );
+
+                    const payload = forwardCall?.[0];
+
+                    expect(payload!.event.getContent()).toEqual({
+                        msgtype: MatrixJs.MsgType.Text,
+                        format: "org.matrix.custom.html",
+                        body: "Hello *world*",
+                        formatted_body: "Hello <em>world</em>",
+                    });
+                });
+
+                it("should strip malicious tags from shared html message", async () => {
+                    await getComponentAndWaitForReady();
+                    defaultDispatcher.dispatch({
+                        action: Action.Share,
+                        format: ShareFormat.Html,
+                        msg: `evil<script src="http://evil.dummy/bad.js" />`,
+                    });
+                    await waitFor(() => {
+                        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
+                            action: Action.OpenForwardDialog,
+                            event: expect.any(MatrixEvent),
+                            permalinkCreator: null,
+                        });
+                    });
+                    const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
+                        ([call]) => call.action === Action.OpenForwardDialog,
+                    );
+
+                    const payload = forwardCall?.[0];
+
+                    expect(payload!.event.getContent()).toEqual({
+                        msgtype: MatrixJs.MsgType.Text,
+                        format: "org.matrix.custom.html",
+                        body: "evil",
+                        formatted_body: "evil",
+                    });
+                });
             });
 
             describe("logout", () => {
@@ -894,12 +1020,107 @@ describe("<MatrixChat />", () => {
         });
 
         describe("unskippable verification", () => {
-            it("should show the complete security screen if unskippable verification is enabled", async () => {
+            beforeEach(() => {
+                // Force verification is turned on in settings
                 defaultProps.config.force_verification = true;
+
+                // And this device is being force-verified (because it logged in after
+                // enforcement was turned on).
                 localStorage.setItem("must_verify_device", "true");
+
+                // lostKeys returns false, meaning there are other devices to verify against
+                const realStore = SetupEncryptionStore.sharedInstance();
+                jest.spyOn(realStore, "lostKeys").mockReturnValue(false);
+            });
+
+            afterEach(() => {
+                jest.restoreAllMocks();
+                // Reset things back to how they were before we started
+                defaultProps.config.force_verification = false;
+                localStorage.removeItem("must_verify_device");
+            });
+
+            it("should show the complete security screen if unskippable verification is enabled", async () => {
+                // Given we have force verification on, and an existing logged-in session
+                // that is not verified (see beforeEach())
+
+                // When we render MatrixChat
                 getComponent();
 
-                await screen.findByRole("heading", { name: "Unable to verify this device", level: 1 });
+                // Then we are asked to verify our device
+                await screen.findByRole("heading", { name: "Verify this device", level: 1 });
+
+                // Sanity: we are not racing with another screen update, so this heading stays visible
+                await screen.findByRole("heading", { name: "Verify this device", level: 1 });
+            });
+
+            it("should not open app after cancelling device verify if unskippable verification is on", async () => {
+                // See https://github.com/element-hq/element-web/issues/29230
+                // We used to allow bypassing force verification by choosing "Verify with
+                // another device" and not completing the verification.
+
+                // Given we have force verification on, and an existing logged-in session
+                // that is not verified (see beforeEach())
+
+                // And our crypto is set up
+                mockClient.getCrypto.mockReturnValue(createMockCrypto());
+
+                // And MatrixChat is rendered
+                getComponent();
+
+                // When we click "Verify with another device"
+                await screen.findByRole("heading", { name: "Verify this device", level: 1 });
+                const verify = screen.getByRole("button", { name: "Verify with another device" });
+                act(() => verify.click());
+
+                // And close the device verification dialog
+                const closeButton = await screen.findByRole("button", { name: "Close dialog" });
+                act(() => closeButton.click());
+
+                // Then we are not allowed in - we are still being asked to verify
+                await screen.findByRole("heading", { name: "Verify this device", level: 1 });
+            });
+
+            function createMockCrypto(): CryptoApi {
+                return {
+                    getVersion: jest.fn().mockReturnValue("Version 0"),
+                    getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
+                    getUserDeviceInfo: jest.fn().mockReturnValue({
+                        get: jest
+                            .fn()
+                            .mockReturnValue(
+                                new Map([
+                                    ["devid", { dehydrated: false, getIdentityKey: jest.fn().mockReturnValue("k") }],
+                                ]),
+                            ),
+                    }),
+                    getUserVerificationStatus: jest
+                        .fn()
+                        .mockResolvedValue(new UserVerificationStatus(true, true, false)),
+                    setDeviceIsolationMode: jest.fn(),
+                    isDehydrationSupported: jest.fn().mockReturnValue(false),
+                    getDeviceVerificationStatus: jest
+                        .fn()
+                        .mockResolvedValue({ signedByOwner: true } as DeviceVerificationStatus),
+                    isCrossSigningReady: jest.fn().mockReturnValue(false),
+                    requestOwnUserVerification: jest.fn().mockResolvedValue({ cancel: jest.fn() }),
+                } as any;
+            }
+        });
+
+        describe("showScreen", () => {
+            it("should show the 'share' screen", async () => {
+                await getComponent({
+                    initialScreenAfterLogin: { screen: "share", params: { msg: "Hello", format: ShareFormat.Text } },
+                });
+
+                await waitFor(() => {
+                    expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
+                        action: "share",
+                        msg: "Hello",
+                        format: ShareFormat.Text,
+                    });
+                });
             });
         });
     });
@@ -1006,6 +1227,7 @@ describe("<MatrixChat />", () => {
                     resetKeyBackup: jest.fn(),
                     isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
                     checkKeyBackupAndEnable: jest.fn().mockResolvedValue(null),
+                    isDehydrationSupported: jest.fn().mockReturnValue(false),
                 };
                 loginClient.getCrypto.mockReturnValue(mockCrypto as any);
             });
@@ -1027,8 +1249,6 @@ describe("<MatrixChat />", () => {
                     "org.matrix.e2e_cross_signing",
                 );
 
-                await flushPromises();
-
                 // logged in
                 await screen.findByLabelText("User menu");
             });
@@ -1124,7 +1344,6 @@ describe("<MatrixChat />", () => {
         };
 
         let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
-        const userId = "@alice:server.org";
         const deviceId = "test-device-id";
         const accessToken = "test-access-token";
         const clientLoginResponse = {
@@ -1218,6 +1437,7 @@ describe("<MatrixChat />", () => {
                     },
                 );
             });
+
             it("should clear storage", async () => {
                 const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear");
 
@@ -1263,6 +1483,9 @@ describe("<MatrixChat />", () => {
 
             it("should continue to post login setup when no session is found in local storage", async () => {
                 getComponent({ realQueryParams });
+                defaultDispatcher.dispatch({
+                    action: "will_start_client",
+                });
 
                 // logged in but waiting for sync screen
                 await screen.findByText("Logout");
@@ -1439,7 +1662,7 @@ describe("<MatrixChat />", () => {
                 jest.spyOn(MatrixJs, "createClient").mockReturnValue(client);
 
                 // intercept initCrypto and have it block until we complete the deferred
-                const initCryptoCompleteDefer = defer();
+                const initCryptoCompleteDefer = Promise.withResolvers<void>();
                 const initCryptoCalled = new Promise<void>((resolve) => {
                     client.initRustCrypto.mockImplementation(() => {
                         resolve();
@@ -1518,8 +1741,13 @@ describe("<MatrixChat />", () => {
             });
             await flushPromises();
             mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
-            await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
-            expect((spy.mock.lastCall![0] as any)._payload._result).toEqual(expect.objectContaining({ __test: true }));
+            await waitFor(() =>
+                expect(spy).toHaveBeenCalledWith(
+                    expect.objectContaining({
+                        _payload: expect.objectContaining({ _result: expect.objectContaining({ __test: true }) }),
+                    }),
+                ),
+            );
         });
 
         it("should show the recovery method removed dialog", async () => {
@@ -1536,8 +1764,13 @@ describe("<MatrixChat />", () => {
             });
             await flushPromises();
             mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
-            await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
-            expect((spy.mock.lastCall![0] as any)._payload._result).toEqual(expect.objectContaining({ __test: true }));
+            await waitFor(() =>
+                expect(spy).toHaveBeenCalledWith(
+                    expect.objectContaining({
+                        _payload: expect.objectContaining({ _result: expect.objectContaining({ __test: true }) }),
+                    }),
+                ),
+            );
         });
     });
 });
diff --git a/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx b/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
index e5ae9d9769981e69501d33507e72cf6902302722..2710dcd57a05d47d8d0a698884f67a2d6b583539 100644
--- a/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
+++ b/test/unit-tests/components/structures/MatrixClientContextProvider-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { act, render } from "jest-matrix-react";
 import React, { useContext } from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 
 import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
diff --git a/test/unit-tests/components/structures/MessagePanel-test.tsx b/test/unit-tests/components/structures/MessagePanel-test.tsx
index c881823903743e544e9e24ba2064f7999bc987c0..ae12768c2fad689dad41d0dfcec7f5511d3e01b0 100644
--- a/test/unit-tests/components/structures/MessagePanel-test.tsx
+++ b/test/unit-tests/components/structures/MessagePanel-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { EventEmitter } from "events";
-import { MatrixEvent, Room, RoomMember, Thread, ReceiptType } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, Room, RoomMember, type Thread, ReceiptType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { render } from "jest-matrix-react";
 
@@ -26,9 +26,10 @@ import {
     mockClientMethodsCrypto,
     mockClientMethodsEvents,
     mockClientMethodsUser,
+    mockClientPushProcessor,
 } from "../../../test-utils";
-import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
-import { IRoomState } from "../../../../src/components/structures/RoomView";
+import type ResizeNotifier from "../../../../src/utils/ResizeNotifier";
+import { type IRoomState } from "../../../../src/components/structures/RoomView";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
 
@@ -45,6 +46,7 @@ describe("MessagePanel", function () {
         ...mockClientMethodsUser(userId),
         ...mockClientMethodsEvents(),
         ...mockClientMethodsCrypto(),
+        ...mockClientPushProcessor(),
         getAccountData: jest.fn(),
         isUserIgnored: jest.fn().mockReturnValue(false),
         isRoomEncrypted: jest.fn().mockReturnValue(false),
diff --git a/test/unit-tests/components/structures/PictureInPictureDragger-test.tsx b/test/unit-tests/components/structures/PictureInPictureDragger-test.tsx
index 6cbf0842a9c08f64ba1ad456d9024fca9b199e57..de9075d79f366adc5abacf7d468380e8d6f7416d 100644
--- a/test/unit-tests/components/structures/PictureInPictureDragger-test.tsx
+++ b/test/unit-tests/components/structures/PictureInPictureDragger-test.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { MouseEventHandler } from "react";
-import { screen, render, RenderResult } from "jest-matrix-react";
+import React, { type MouseEventHandler } from "react";
+import { screen, render, type RenderResult } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 import PictureInPictureDragger, {
-    CreatePipChildren,
+    type CreatePipChildren,
 } from "../../../../src/components/structures/PictureInPictureDragger";
 
 describe("PictureInPictureDragger", () => {
diff --git a/test/unit-tests/components/structures/PipContainer-test.tsx b/test/unit-tests/components/structures/PipContainer-test.tsx
index 06cad2a290ba0ccb8daad140f2e289c829a3bf0d..c401853c2feefda271fb8e9af17e37eae0eb2af8 100644
--- a/test/unit-tests/components/structures/PipContainer-test.tsx
+++ b/test/unit-tests/components/structures/PipContainer-test.tsx
@@ -7,14 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 import { screen, render, act, cleanup } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
-import { Widget, ClientWidgetApi } from "matrix-widget-api";
-import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
+import {
+    type MatrixClient,
+    PendingEventOrdering,
+    Room,
+    RoomStateEvent,
+    type RoomMember,
+} from "matrix-js-sdk/src/matrix";
+import { Widget, type ClientWidgetApi } from "matrix-widget-api";
+import { type UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
 
-import type { RoomMember } from "matrix-js-sdk/src/matrix";
 import {
     useMockedCalls,
     MockedCall,
@@ -36,7 +41,7 @@ import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
 import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
 import { Action } from "../../../../src/dispatcher/actions";
-import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
 import { TestSdkContext } from "../../TestSdkContext";
 import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
 import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
@@ -143,6 +148,8 @@ describe("PipContainer", () => {
             WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
             WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
                 stop: () => {},
+                hasCapability: jest.fn(),
+                feedStateUpdate: jest.fn().mockResolvedValue(undefined),
             } as unknown as ClientWidgetApi);
 
             await call.start();
@@ -266,7 +273,10 @@ describe("PipContainer", () => {
                     Parameters<ClientWidgetApi["transport"]["send"]>
                 >()
                 .mockResolvedValue({});
-            const mockMessaging = { transport: { send: sendSpy }, stop: () => {} } as unknown as ClientWidgetApi;
+            const mockMessaging = {
+                transport: { send: sendSpy },
+                stop: () => {},
+            } as unknown as ClientWidgetApi;
             WidgetMessagingStore.instance.storeMessaging(new Widget(widget), room.roomId, mockMessaging);
             await user.click(screen.getByRole("button", { name: "Leave" }));
             expect(sendSpy).toHaveBeenCalledWith(ElementWidgetActions.HangupCall, {});
diff --git a/test/unit-tests/components/structures/ReleaseAnnouncement-test.tsx b/test/unit-tests/components/structures/ReleaseAnnouncement-test.tsx
index c68d755a3a8f017c9f9991b048929a1bd03e2ba4..e53bf06d4815c5cd1491743ac2636712d7326cc9 100644
--- a/test/unit-tests/components/structures/ReleaseAnnouncement-test.tsx
+++ b/test/unit-tests/components/structures/ReleaseAnnouncement-test.tsx
@@ -23,7 +23,7 @@ describe("ReleaseAnnouncement", () => {
     function renderReleaseAnnouncement() {
         return render(
             <ReleaseAnnouncement
-                feature="threadsActivityCentre"
+                feature="pinningMessageList"
                 header="header"
                 description="description"
                 closeLabel="close"
diff --git a/test/unit-tests/components/structures/RightPanel-test.tsx b/test/unit-tests/components/structures/RightPanel-test.tsx
index 83f79665a91b792e888ce61665378571fab36024..5a5b9237af77b00bd95b8d6d0b69dd9360369d2a 100644
--- a/test/unit-tests/components/structures/RightPanel-test.tsx
+++ b/test/unit-tests/components/structures/RightPanel-test.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import _RightPanel from "../../../../src/components/structures/RightPanel";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/structures/RoomSearchView-test.tsx b/test/unit-tests/components/structures/RoomSearchView-test.tsx
index 56f19b830eb63424c5bfe01b4e97b35ae89840a3..ab52e8012be9156e3c0ecc0fe01c868e695c1af3 100644
--- a/test/unit-tests/components/structures/RoomSearchView-test.tsx
+++ b/test/unit-tests/components/structures/RoomSearchView-test.tsx
@@ -11,14 +11,13 @@ import { mocked } from "jest-mock";
 import { render, screen } from "jest-matrix-react";
 import {
     Room,
-    MatrixClient,
-    IEvent,
+    type MatrixClient,
+    type IEvent,
     MatrixEvent,
     EventType,
     SearchResult,
-    ISearchResults,
+    type ISearchResults,
 } from "matrix-js-sdk/src/matrix";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import { RoomSearchView } from "../../../../src/components/structures/RoomSearchView";
 import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
@@ -53,7 +52,7 @@ describe("<RoomSearchView/>", () => {
     });
 
     it("should show a spinner before the promise resolves", async () => {
-        const deferred = defer<ISearchResults>();
+        const deferred = Promise.withResolvers<ISearchResults>();
 
         render(
             <RoomSearchView
@@ -247,7 +246,7 @@ describe("<RoomSearchView/>", () => {
 
         await screen.findByRole("progressbar");
         await screen.findByText("Potato");
-        expect(onUpdate).toHaveBeenCalledWith(false, expect.objectContaining({}));
+        expect(onUpdate).toHaveBeenCalledWith(false, expect.objectContaining({}), null);
 
         rerender(
             <MatrixClientContext.Provider value={client}>
@@ -267,7 +266,7 @@ describe("<RoomSearchView/>", () => {
     });
 
     it("should handle resolutions after unmounting sanely", async () => {
-        const deferred = defer<ISearchResults>();
+        const deferred = Promise.withResolvers<ISearchResults>();
 
         const { unmount } = render(
             <MatrixClientContext.Provider value={client}>
@@ -291,7 +290,7 @@ describe("<RoomSearchView/>", () => {
     });
 
     it("should handle rejections after unmounting sanely", async () => {
-        const deferred = defer<ISearchResults>();
+        const deferred = Promise.withResolvers<ISearchResults>();
 
         const { unmount } = render(
             <MatrixClientContext.Provider value={client}>
@@ -314,8 +313,9 @@ describe("<RoomSearchView/>", () => {
         });
     });
 
-    it("should show modal if error is encountered", async () => {
-        const deferred = defer<ISearchResults>();
+    it("report error if one is encountered", async () => {
+        const onUpdate = jest.fn();
+        const deferred = Promise.withResolvers<ISearchResults>();
 
         render(
             <MatrixClientContext.Provider value={client}>
@@ -326,14 +326,18 @@ describe("<RoomSearchView/>", () => {
                     promise={deferred.promise}
                     resizeNotifier={resizeNotifier}
                     className="someClass"
-                    onUpdate={jest.fn()}
+                    onUpdate={onUpdate}
                 />
             </MatrixClientContext.Provider>,
         );
-        deferred.reject(new Error("Some error"));
-
-        await screen.findByText("Search failed");
-        await screen.findByText("Some error");
+        deferred.reject("Some error");
+        try {
+            // Wait for RoomSearchView to process the promise
+            await deferred.promise;
+        } catch {}
+
+        expect(onUpdate).toHaveBeenCalledWith(false, null, "Some error");
+        expect(onUpdate).toHaveBeenCalledTimes(2);
     });
 
     it("should combine search results when the query is present in multiple sucessive messages", async () => {
diff --git a/test/unit-tests/components/structures/RoomStatusBar-test.tsx b/test/unit-tests/components/structures/RoomStatusBar-test.tsx
index 5d130b0205a08475fc70665b5bab352401deea61..844ed8ab2d083e42c7cf2178dabf13144a6e9372 100644
--- a/test/unit-tests/components/structures/RoomStatusBar-test.tsx
+++ b/test/unit-tests/components/structures/RoomStatusBar-test.tsx
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { render } from "jest-matrix-react";
 import {
-    MatrixClient,
+    type MatrixClient,
     PendingEventOrdering,
     EventStatus,
-    MatrixEvent,
+    type MatrixEvent,
     Room,
     MatrixError,
 } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx
index eabfe0c85ce3b49647607c8b4db92632cbfa0cad..2d0d7f0d36d35ffa3b01671f657782404639e870 100644
--- a/test/unit-tests/components/structures/RoomView-test.tsx
+++ b/test/unit-tests/components/structures/RoomView-test.tsx
@@ -6,36 +6,35 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, RefObject } from "react";
-import { mocked, MockedObject } from "jest-mock";
+import React, { createRef, type RefObject } from "react";
+import { mocked, type MockedObject } from "jest-mock";
 import {
-    ClientEvent,
     EventTimeline,
     EventType,
-    IEvent,
+    type IEvent,
     JoinRule,
-    MatrixClient,
+    type MatrixClient,
     MatrixError,
     MatrixEvent,
     Room,
     RoomEvent,
+    RoomMember,
     RoomStateEvent,
     SearchResult,
 } from "matrix-js-sdk/src/matrix";
-import { CryptoApi, UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
+import { type CryptoApi, UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import {
     fireEvent,
     render,
     screen,
-    RenderResult,
+    type RenderResult,
     waitForElementToBeRemoved,
     waitFor,
     act,
     cleanup,
 } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import {
     stubClient,
@@ -54,7 +53,7 @@ import {
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import { Action } from "../../../../src/dispatcher/actions";
 import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
 import { RoomView } from "../../../../src/components/structures/RoomView";
 import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
 import SettingsStore from "../../../../src/settings/SettingsStore";
@@ -62,20 +61,29 @@ import { SettingLevel } from "../../../../src/settings/SettingLevel";
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
 import { NotificationState } from "../../../../src/stores/notifications/NotificationState";
 import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
-import { LocalRoom, LocalRoomState } from "../../../../src/models/LocalRoom";
+import { type LocalRoom, LocalRoomState } from "../../../../src/models/LocalRoom";
 import { DirectoryMember } from "../../../../src/utils/direct-messages";
 import { createDmLocalRoom } from "../../../../src/utils/dm/createDmLocalRoom";
 import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore";
 import { SDKContext, SdkContextClass } from "../../../../src/contexts/SDKContext";
-import VoipUserMapper from "../../../../src/VoipUserMapper";
 import WidgetUtils from "../../../../src/utils/WidgetUtils";
 import { WidgetType } from "../../../../src/widgets/WidgetType";
 import WidgetStore from "../../../../src/stores/WidgetStore";
-import { ViewRoomErrorPayload } from "../../../../src/dispatcher/payloads/ViewRoomErrorPayload";
+import { type ViewRoomErrorPayload } from "../../../../src/dispatcher/payloads/ViewRoomErrorPayload";
 import { SearchScope } from "../../../../src/Searching";
 import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../../src/utils/crypto";
 import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
-import { ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUserPayload.ts";
+import { type ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUserPayload.ts";
+import { CallStore } from "../../../../src/stores/CallStore.ts";
+import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler.ts";
+import Modal from "../../../../src/Modal.tsx";
+
+// Used by group calls
+jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
+    [MediaDeviceKindEnum.AudioInput]: [],
+    [MediaDeviceKindEnum.VideoInput]: [],
+    [MediaDeviceKindEnum.AudioOutput]: [],
+});
 
 describe("RoomView", () => {
     let cli: MockedObject<MatrixClient>;
@@ -98,6 +106,7 @@ describe("RoomView", () => {
         rooms = new Map();
         rooms.set(room.roomId, room);
         cli.getRoom.mockImplementation((roomId: string | undefined) => rooms.get(roomId || "") || null);
+        cli.getRooms.mockImplementation(() => [...rooms.values()]);
         // Re-emit certain events on the mocked client
         room.on(RoomEvent.Timeline, (...args) => cli.emit(RoomEvent.Timeline, ...args));
         room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args));
@@ -107,7 +116,6 @@ describe("RoomView", () => {
         stores.client = cli;
         stores.rightPanelStore.useUnitTestClient(cli);
 
-        jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
         crypto = cli.getCrypto()!;
         jest.spyOn(cli, "getCrypto").mockReturnValue(undefined);
     });
@@ -118,7 +126,7 @@ describe("RoomView", () => {
         cleanup();
     });
 
-    const mountRoomView = async (ref?: RefObject<RoomView>): Promise<RenderResult> => {
+    const mountRoomView = async (ref?: RefObject<RoomView | null>): Promise<RenderResult> => {
         if (stores.roomViewStore.getRoomId() !== room.roomId) {
             const switchedRoom = new Promise<void>((resolve) => {
                 const subFn = () => {
@@ -186,7 +194,7 @@ describe("RoomView", () => {
                     <RoomView
                         // threepidInvite should be optional on RoomView props
                         // it is treated as optional in RoomView
-                        threepidInvite={undefined as any}
+                        threepidInvite={undefined}
                         resizeNotifier={new ResizeNotifier()}
                         forceTimeline={false}
                         onRegistered={jest.fn()}
@@ -223,6 +231,62 @@ describe("RoomView", () => {
         expect(instance.getHiddenHighlightCount()).toBe(0);
     });
 
+    describe("invites", () => {
+        beforeEach(() => {
+            const member = new RoomMember(room.roomId, cli.getSafeUserId());
+            member.membership = KnownMembership.Invite;
+            member.events.member = new MatrixEvent({
+                sender: "@bob:example.org",
+            });
+            room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Invite);
+            room.getMember = jest.fn().mockReturnValue(member);
+        });
+
+        it("renders an invite room", async () => {
+            const { asFragment } = await mountRoomView();
+            expect(asFragment()).toMatchSnapshot();
+        });
+
+        it("handles accepting an invite", async () => {
+            const { getByRole } = await mountRoomView();
+
+            await fireEvent.click(getByRole("button", { name: "Accept" }));
+
+            await untilDispatch(Action.JoinRoomReady, defaultDispatcher);
+        });
+        it("handles declining an invite", async () => {
+            const { getByRole } = await mountRoomView();
+            jest.spyOn(Modal, "createDialog").mockReturnValue({
+                finished: Promise.resolve([true, false, false]),
+                close: jest.fn(),
+            });
+            await fireEvent.click(getByRole("button", { name: "Decline" }));
+            await waitFor(() => expect(cli.leave).toHaveBeenCalledWith(room.roomId));
+            expect(cli.setIgnoredUsers).not.toHaveBeenCalled();
+        });
+        it("handles declining an invite and ignoring the user", async () => {
+            const { getByRole } = await mountRoomView();
+            cli.getIgnoredUsers.mockReturnValue(["@carol:example.org"]);
+            jest.spyOn(Modal, "createDialog").mockReturnValue({
+                finished: Promise.resolve([true, true, false]),
+                close: jest.fn(),
+            });
+            await fireEvent.click(getByRole("button", { name: "Decline and block" }));
+            expect(cli.leave).toHaveBeenCalledWith(room.roomId);
+            expect(cli.setIgnoredUsers).toHaveBeenCalledWith(["@carol:example.org", "@bob:example.org"]);
+        });
+        it("handles declining an invite and reporting the room", async () => {
+            const { getByRole } = await mountRoomView();
+            jest.spyOn(Modal, "createDialog").mockReturnValue({
+                finished: Promise.resolve([true, false, "with a reason"]),
+                close: jest.fn(),
+            });
+            await fireEvent.click(getByRole("button", { name: "Decline and block" }));
+            expect(cli.leave).toHaveBeenCalledWith(room.roomId);
+            expect(cli.reportRoom).toHaveBeenCalledWith(room.roomId, "with a reason");
+        });
+    });
+
     describe("when there is an old room", () => {
         let instance: RoomView;
         let oldRoom: Room;
@@ -303,7 +367,7 @@ describe("RoomView", () => {
     it("should not display the timeline when the room encryption is loading", async () => {
         jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
         jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
-        const deferred = defer<boolean>();
+        const deferred = Promise.withResolvers<boolean>();
         jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(() => deferred.promise);
 
         const { asFragment, container } = await mountRoomView();
@@ -349,28 +413,9 @@ describe("RoomView", () => {
         await waitFor(() => expect(container.querySelector(".mx_E2EIcon_verified")).toBeInTheDocument());
     });
 
-    describe("with virtual rooms", () => {
-        it("checks for a virtual room on initial load", async () => {
-            const { container } = await renderRoomView();
-            expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
-
-            // quick check that rendered without error
-            expect(container.querySelector(".mx_ErrorBoundary")).toBeFalsy();
-        });
-
-        it("checks for a virtual room on room event", async () => {
-            await renderRoomView();
-            expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
-
-            act(() => cli.emit(ClientEvent.Room, room));
-
-            // called again after room event
-            expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledTimes(2);
-        });
-    });
-
     describe("video rooms", () => {
         beforeEach(async () => {
+            await setupAsyncStoreWithClient(CallStore.instance, MatrixClientPeg.safeGet());
             // Make it a video room
             room.isElementVideoRoom = () => true;
             await SettingsStore.setValue("feature_video_rooms", null, SettingLevel.DEVICE, true);
@@ -424,7 +469,7 @@ describe("RoomView", () => {
                     jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
                     jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
                     jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(
-                        new UserVerificationStatus(false, true, false),
+                        new UserVerificationStatus(false, false, false),
                     );
                     localRoom.encrypted = true;
                     localRoom.currentState.setStateEvents([
@@ -562,129 +607,148 @@ describe("RoomView", () => {
         });
     });
 
-    it("should close search results when edit is clicked", async () => {
-        room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
+    describe("message search", () => {
+        it("should close search results when edit is clicked", async () => {
+            room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
 
-        const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
+            const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
 
-        const roomViewRef = createRef<RoomView>();
-        const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
-        await waitFor(() => expect(roomViewRef.current).toBeTruthy());
-        // @ts-ignore - triggering a search organically is a lot of work
-        act(() =>
-            roomViewRef.current!.setState({
-                search: {
-                    searchId: 1,
-                    roomId: room.roomId,
-                    term: "search term",
-                    scope: SearchScope.Room,
-                    promise: Promise.resolve({
-                        results: [
-                            SearchResult.fromJson(
-                                {
-                                    rank: 1,
-                                    result: {
-                                        content: {
-                                            body: "search term",
-                                            msgtype: "m.text",
+            const roomViewRef = createRef<RoomView>();
+            const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
+            await waitFor(() => expect(roomViewRef.current).toBeTruthy());
+            // @ts-ignore - triggering a search organically is a lot of work
+            act(() =>
+                roomViewRef.current!.setState({
+                    search: {
+                        searchId: 1,
+                        roomId: room.roomId,
+                        term: "search term",
+                        scope: SearchScope.Room,
+                        promise: Promise.resolve({
+                            results: [
+                                SearchResult.fromJson(
+                                    {
+                                        rank: 1,
+                                        result: {
+                                            content: {
+                                                body: "search term",
+                                                msgtype: "m.text",
+                                            },
+                                            type: "m.room.message",
+                                            event_id: "$eventId",
+                                            sender: cli.getSafeUserId(),
+                                            origin_server_ts: 123456789,
+                                            room_id: room.roomId,
+                                        },
+                                        context: {
+                                            events_before: [],
+                                            events_after: [],
+                                            profile_info: {},
                                         },
-                                        type: "m.room.message",
-                                        event_id: "$eventId",
-                                        sender: cli.getSafeUserId(),
-                                        origin_server_ts: 123456789,
-                                        room_id: room.roomId,
-                                    },
-                                    context: {
-                                        events_before: [],
-                                        events_after: [],
-                                        profile_info: {},
                                     },
-                                },
-                                eventMapper,
-                            ),
-                        ],
-                        highlights: [],
+                                    eventMapper,
+                                ),
+                            ],
+                            highlights: [],
+                            count: 1,
+                        }),
+                        inProgress: false,
                         count: 1,
-                    }),
-                    inProgress: false,
-                    count: 1,
-                },
-            }),
-        );
+                    },
+                }),
+            );
 
-        await waitFor(() => {
-            expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
-        });
-        const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel"));
+            await waitFor(() => {
+                expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
+            });
+            const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel"));
 
-        await userEvent.hover(getByText("search term"));
-        await userEvent.click(await findByLabelText("Edit"));
+            await userEvent.hover(getByText("search term"));
+            await userEvent.click(await findByLabelText("Edit"));
 
-        await prom;
-    });
+            await prom;
+        });
 
-    it("should switch rooms when edit is clicked on a search result for a different room", async () => {
-        const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
-        rooms.set(room2.roomId, room2);
+        it("should switch rooms when edit is clicked on a search result for a different room", async () => {
+            const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
+            rooms.set(room2.roomId, room2);
 
-        room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
+            room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
 
-        const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
+            const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
 
-        const roomViewRef = createRef<RoomView>();
-        const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
-        await waitFor(() => expect(roomViewRef.current).toBeTruthy());
-        // @ts-ignore - triggering a search organically is a lot of work
-        act(() =>
-            roomViewRef.current!.setState({
-                search: {
-                    searchId: 1,
-                    roomId: room.roomId,
-                    term: "search term",
-                    scope: SearchScope.All,
-                    promise: Promise.resolve({
-                        results: [
-                            SearchResult.fromJson(
-                                {
-                                    rank: 1,
-                                    result: {
-                                        content: {
-                                            body: "search term",
-                                            msgtype: "m.text",
+            const roomViewRef = createRef<RoomView>();
+            const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
+            await waitFor(() => expect(roomViewRef.current).toBeTruthy());
+            // @ts-ignore - triggering a search organically is a lot of work
+            act(() =>
+                roomViewRef.current!.setState({
+                    search: {
+                        searchId: 1,
+                        roomId: room.roomId,
+                        term: "search term",
+                        scope: SearchScope.All,
+                        promise: Promise.resolve({
+                            results: [
+                                SearchResult.fromJson(
+                                    {
+                                        rank: 1,
+                                        result: {
+                                            content: {
+                                                body: "search term",
+                                                msgtype: "m.text",
+                                            },
+                                            type: "m.room.message",
+                                            event_id: "$eventId",
+                                            sender: cli.getSafeUserId(),
+                                            origin_server_ts: 123456789,
+                                            room_id: room2.roomId,
+                                        },
+                                        context: {
+                                            events_before: [],
+                                            events_after: [],
+                                            profile_info: {},
                                         },
-                                        type: "m.room.message",
-                                        event_id: "$eventId",
-                                        sender: cli.getSafeUserId(),
-                                        origin_server_ts: 123456789,
-                                        room_id: room2.roomId,
-                                    },
-                                    context: {
-                                        events_before: [],
-                                        events_after: [],
-                                        profile_info: {},
                                     },
-                                },
-                                eventMapper,
-                            ),
-                        ],
-                        highlights: [],
+                                    eventMapper,
+                                ),
+                            ],
+                            highlights: [],
+                            count: 1,
+                        }),
+                        inProgress: false,
                         count: 1,
-                    }),
-                    inProgress: false,
-                    count: 1,
-                },
-            }),
-        );
+                    },
+                }),
+            );
 
-        await waitFor(() => {
-            expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
+            await waitFor(() => {
+                expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
+            });
+            const prom = untilDispatch(Action.ViewRoom, defaultDispatcher);
+
+            await userEvent.hover(getByText("search term"));
+            await userEvent.click(await findByLabelText("Edit"));
+
+            await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId }));
         });
-        const prom = untilDispatch(Action.ViewRoom, defaultDispatcher);
 
-        await userEvent.hover(getByText("search term"));
-        await userEvent.click(await findByLabelText("Edit"));
+        it("should pre-fill search field on FocusMessageSearch dispatch", async () => {
+            room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
+
+            const roomViewRef = createRef<RoomView>();
+            const { findByPlaceholderText } = await mountRoomView(roomViewRef);
+            await waitFor(() => expect(roomViewRef.current).toBeTruthy());
+
+            act(() =>
+                defaultDispatcher.dispatch({
+                    action: Action.FocusMessageSearch,
+                    initialText: "search term",
+                }),
+            );
 
-        await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId }));
+            await expect(findByPlaceholderText("Search messages…")).resolves.toHaveValue("search term");
+        });
     });
 
     it("fires Action.RoomLoaded", async () => {
@@ -693,6 +757,31 @@ describe("RoomView", () => {
         expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.RoomLoaded });
     });
 
+    // Regression test for https://github.com/element-hq/element-web/issues/29072
+    it("does not force a reload on sync unless the client is coming back online", async () => {
+        cli.isInitialSyncComplete.mockReturnValue(false);
+
+        const instance = await getRoomViewInstance();
+        const onRoomViewUpdateMock = jest.fn();
+        (instance as any).onRoomViewStoreUpdate = onRoomViewUpdateMock;
+
+        act(() => {
+            // As if a connectivity check happened (we are still offline)
+            defaultDispatcher.dispatch({ action: "MatrixActions.sync" }, true);
+            // ...so it still should not force a reload
+            expect(onRoomViewUpdateMock).not.toHaveBeenCalledWith(true);
+        });
+
+        act(() => {
+            // set us to online again
+            cli.isInitialSyncComplete.mockReturnValue(true);
+            defaultDispatcher.dispatch({ action: "MatrixActions.sync" }, true);
+        });
+
+        // It should now force a reload
+        expect(onRoomViewUpdateMock).toHaveBeenCalledWith(true);
+    });
+
     describe("when there is a RoomView", () => {
         const widget1Id = "widget1";
         const widget2Id = "widget2";
diff --git a/test/unit-tests/components/structures/SpaceHierarchy-test.tsx b/test/unit-tests/components/structures/SpaceHierarchy-test.tsx
index c2b71e304f93f63095b5c7ddd7bb6286215c5b42..cea704b90b7d0aa26f0c4a09417b84f2d025f2ab 100644
--- a/test/unit-tests/components/structures/SpaceHierarchy-test.tsx
+++ b/test/unit-tests/components/structures/SpaceHierarchy-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { mocked } from "jest-mock";
 import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
-import { HierarchyRoom, JoinRule, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type HierarchyRoom, JoinRule, type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
 
diff --git a/test/unit-tests/components/structures/SpaceRoomView-test.tsx b/test/unit-tests/components/structures/SpaceRoomView-test.tsx
index 156f2d1cdbe8b3f6336267d0387da67e2bbd8325..f181b90576b057c4071bcc703c523826959d859e 100644
--- a/test/unit-tests/components/structures/SpaceRoomView-test.tsx
+++ b/test/unit-tests/components/structures/SpaceRoomView-test.tsx
@@ -6,8 +6,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { render, cleanup, screen, fireEvent } from "jest-matrix-react";
 
 import { stubClient, mockPlatformPeg, unmockPlatformPeg, withClientContextRenderOptions } from "../../../test-utils";
diff --git a/test/unit-tests/components/structures/TabbedView-test.tsx b/test/unit-tests/components/structures/TabbedView-test.tsx
index 9c30bc443ae217bde09e19a5d08e1f7275dd0187..38349e288a5e4ed62fc150da1c7579635b983a1c 100644
--- a/test/unit-tests/components/structures/TabbedView-test.tsx
+++ b/test/unit-tests/components/structures/TabbedView-test.tsx
@@ -10,21 +10,21 @@ import React from "react";
 import { act, fireEvent, render } from "jest-matrix-react";
 
 import TabbedView, { Tab, TabLocation } from "../../../../src/components/structures/TabbedView";
-import { NonEmptyArray } from "../../../../src/@types/common";
+import { type NonEmptyArray } from "../../../../src/@types/common";
 import { _t } from "../../../../src/languageHandler";
 
 describe("<TabbedView />", () => {
     const generalTab = new Tab("GENERAL", "common|general", "general", <div>general</div>);
     const labsTab = new Tab("LABS", "common|labs", "labs", <div>labs</div>);
-    const securityTab = new Tab("SECURITY", "common|security", "security", <div>security</div>);
+    const appearanceTab = new Tab("APPEARANCE", "common|appearance", "appearance", <div>appearance</div>);
     const defaultProps = {
         tabLocation: TabLocation.LEFT,
-        tabs: [generalTab, labsTab, securityTab] as NonEmptyArray<Tab<any>>,
+        tabs: [generalTab, labsTab, appearanceTab] as NonEmptyArray<Tab<any>>,
         onChange: () => {},
     };
     const getComponent = (
         props: {
-            activeTabId: "GENERAL" | "LABS" | "SECURITY";
+            activeTabId: "GENERAL" | "LABS" | "APPEARANCE";
             onChange?: () => any;
             tabs?: NonEmptyArray<Tab<any>>;
         } = {
@@ -44,9 +44,9 @@ describe("<TabbedView />", () => {
     });
 
     it("renders activeTabId tab as active when valid", () => {
-        const { container } = render(getComponent({ activeTabId: securityTab.id }));
-        expect(getActiveTab(container)?.textContent).toEqual(_t(securityTab.label));
-        expect(getActiveTabBody(container)?.textContent).toEqual("security");
+        const { container } = render(getComponent({ activeTabId: appearanceTab.id }));
+        expect(getActiveTab(container)?.textContent).toEqual(_t(appearanceTab.label));
+        expect(getActiveTabBody(container)?.textContent).toEqual("appearance");
     });
 
     it("calls onchange on on tab click", () => {
@@ -54,10 +54,10 @@ describe("<TabbedView />", () => {
         const { getByTestId } = render(getComponent({ activeTabId: "GENERAL", onChange }));
 
         act(() => {
-            fireEvent.click(getByTestId(getTabTestId(securityTab)));
+            fireEvent.click(getByTestId(getTabTestId(appearanceTab)));
         });
 
-        expect(onChange).toHaveBeenCalledWith(securityTab.id);
+        expect(onChange).toHaveBeenCalledWith(appearanceTab.id);
     });
 
     it("keeps same tab active when order of tabs changes", () => {
@@ -66,7 +66,7 @@ describe("<TabbedView />", () => {
 
         expect(getActiveTab(container)?.textContent).toEqual(_t(labsTab.label));
 
-        rerender(getComponent({ tabs: [labsTab, generalTab, securityTab], activeTabId: labsTab.id }));
+        rerender(getComponent({ tabs: [labsTab, generalTab, appearanceTab], activeTabId: labsTab.id }));
 
         // labs tab still active
         expect(getActiveTab(container)?.textContent).toEqual(_t(labsTab.label));
diff --git a/test/unit-tests/components/structures/ThreadPanel-test.tsx b/test/unit-tests/components/structures/ThreadPanel-test.tsx
index c58194f0b9c31190e062b0270b9341d03d2eeb2f..e2596b072f0f175fcf62d56fe0333ac59a338796 100644
--- a/test/unit-tests/components/structures/ThreadPanel-test.tsx
+++ b/test/unit-tests/components/structures/ThreadPanel-test.tsx
@@ -10,8 +10,8 @@ import React from "react";
 import { render, screen, fireEvent, waitFor, getByRole } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 import {
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     PendingEventOrdering,
     Room,
     FeatureSupport,
@@ -26,7 +26,7 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
 import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
 import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
 import { mkThread } from "../../../test-utils/threads";
-import { IRoomState } from "../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../src/components/structures/RoomView";
 import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
 
 jest.mock("../../../../src/utils/Feedback");
diff --git a/test/unit-tests/components/structures/ThreadView-test.tsx b/test/unit-tests/components/structures/ThreadView-test.tsx
index f67e2f28a357c3394650f7fc6294ef9e61fd5ebe..365e72b3dbf52564372bf8c3e1a85bae0b79b729 100644
--- a/test/unit-tests/components/structures/ThreadView-test.tsx
+++ b/test/unit-tests/components/structures/ThreadView-test.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { act, getByTestId, render, RenderResult, waitFor } from "jest-matrix-react";
+import { act, getByTestId, render, type RenderResult, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { mocked } from "jest-mock";
 import {
     MsgType,
     RelationType,
     EventStatus,
-    MatrixEvent,
+    type MatrixEvent,
     Room,
-    MatrixClient,
+    type MatrixClient,
     PendingEventOrdering,
     THREAD_RELATION_TYPE,
 } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/components/structures/TimelinePanel-test.tsx b/test/unit-tests/components/structures/TimelinePanel-test.tsx
index 2e10f74a694e701412c4e2cbb0df0b8b2aa3562c..354036e282cf075c8c59a6634264217cf1c7efd3 100644
--- a/test/unit-tests/components/structures/TimelinePanel-test.tsx
+++ b/test/unit-tests/components/structures/TimelinePanel-test.tsx
@@ -11,7 +11,7 @@ import {
     ReceiptType,
     EventTimelineSet,
     EventType,
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
     PendingEventOrdering,
     RelationType,
@@ -29,20 +29,28 @@ import {
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import React from "react";
-import { Mocked, mocked } from "jest-mock";
+import { type Mocked, mocked } from "jest-mock";
 import { forEachRight } from "lodash";
 
 import TimelinePanel from "../../../../src/components/structures/TimelinePanel";
 import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
-import { isCallEvent } from "../../../../src/components/structures/LegacyCallEventGrouper";
-import { filterConsole, flushPromises, mkMembership, mkRoom, stubClient } from "../../../test-utils";
+import {
+    filterConsole,
+    flushPromises,
+    mkMembership,
+    mkRoom,
+    stubClient,
+    withClientContextRenderOptions,
+} from "../../../test-utils";
 import { mkThread } from "../../../test-utils/threads";
 import { createMessageEventContent } from "../../../test-utils/events";
 import SettingsStore from "../../../../src/settings/SettingsStore";
 import ScrollPanel from "../../../../src/components/structures/ScrollPanel";
 import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
 import { Action } from "../../../../src/dispatcher/actions";
+import { SettingLevel } from "../../../../src/settings/SettingLevel";
+import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
 
 // ScrollPanel calls this, but jsdom doesn't mock it for us
 HTMLDivElement.prototype.scrollBy = () => {};
@@ -106,22 +114,6 @@ const setupTestData = (): [MatrixClient, Room, MatrixEvent[]] => {
     return [client, room, events];
 };
 
-const setupOverlayTestData = (client: MatrixClient, mainEvents: MatrixEvent[]): [Room, MatrixEvent[]] => {
-    const virtualRoom = mkRoom(client, "virtualRoomId");
-    const overlayEvents = mockEvents(virtualRoom, 5);
-
-    // Set the event order that we'll be looking for in the timeline
-    overlayEvents[0].localTimestamp = 1000;
-    mainEvents[0].localTimestamp = 2000;
-    overlayEvents[1].localTimestamp = 3000;
-    overlayEvents[2].localTimestamp = 4000;
-    overlayEvents[3].localTimestamp = 5000;
-    mainEvents[1].localTimestamp = 6000;
-    overlayEvents[4].localTimestamp = 7000;
-
-    return [virtualRoom, overlayEvents];
-};
-
 const expectEvents = (container: HTMLElement, events: MatrixEvent[]): void => {
     const eventTiles = container.querySelectorAll(".mx_EventTile");
     const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id"));
@@ -204,8 +196,11 @@ describe("TimelinePanel", () => {
                     timelineSet={timelineSet}
                     manageReadMarkers={true}
                     manageReadReceipts={true}
-                    ref={(ref) => (timelinePanel = ref)}
+                    ref={(ref) => {
+                        timelinePanel = ref;
+                    }}
                 />,
+                withClientContextRenderOptions(MatrixClientPeg.safeGet()),
             );
             await flushPromises();
             await waitFor(() => expect(timelinePanel).toBeTruthy());
@@ -300,18 +295,14 @@ describe("TimelinePanel", () => {
 
             describe("and sending receipts is disabled", () => {
                 beforeEach(async () => {
-                    client.isVersionSupported.mockResolvedValue(true);
-                    client.doesServerSupportUnstableFeature.mockResolvedValue(true);
-
-                    jest.spyOn(SettingsStore, "getValue").mockImplementation((setting: string): any => {
-                        if (setting === "sendReadReceipts") return false;
-
-                        return undefined;
-                    });
+                    // Ensure this setting is supported, otherwise it will use the default value.
+                    client.isVersionSupported.mockImplementation(async (v) => v === "v1.4");
+                    MatrixClientBackedController.matrixClient = client;
+                    SettingsStore.setValue("sendReadReceipts", null, SettingLevel.DEVICE, false);
                 });
 
                 afterEach(() => {
-                    mocked(SettingsStore.getValue).mockReset();
+                    SettingsStore.reset();
                 });
 
                 it("should send a fully read marker and a private receipt", async () => {
@@ -403,7 +394,10 @@ describe("TimelinePanel", () => {
         setupPagination(client, timeline, eventsPage1, null);
 
         await withScrollPanelMountSpy(async (mountSpy) => {
-            const { container } = render(<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />);
+            const { container } = render(
+                <TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />,
+                withClientContextRenderOptions(MatrixClientPeg.safeGet()),
+            );
 
             await waitFor(() => expectEvents(container, [events[1]]));
 
@@ -420,7 +414,10 @@ describe("TimelinePanel", () => {
         const [, room, events] = setupTestData();
 
         await withScrollPanelMountSpy(async (mountSpy) => {
-            const { container } = render(<TimelinePanel {...getProps(room, events)} />);
+            const { container } = render(
+                <TimelinePanel {...getProps(room, events)} />,
+                withClientContextRenderOptions(MatrixClientPeg.safeGet()),
+            );
 
             await waitFor(() => expectEvents(container, [events[0], events[1]]));
 
@@ -504,292 +501,6 @@ describe("TimelinePanel", () => {
 
             expect(paginateSpy).toHaveBeenCalledWith(EventTimeline.FORWARDS, 1, false);
         });
-
-        it("advances the overlay timeline window", async () => {
-            const [client, room, events] = setupTestData();
-
-            const virtualRoom = mkRoom(client, "virtualRoomId");
-            const virtualEvents = mockEvents(virtualRoom);
-            const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
-
-            const props = {
-                ...getProps(room, events),
-                overlayTimelineSet,
-            };
-
-            const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
-
-            render(<TimelinePanel {...props} />);
-
-            await flushPromises();
-
-            const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 });
-            const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true };
-            client.emit(RoomEvent.Timeline, event, room, false, false, data);
-
-            await flushPromises();
-
-            expect(paginateSpy).toHaveBeenCalledTimes(2);
-        });
-    });
-
-    describe("with overlayTimeline", () => {
-        it("renders merged timeline", async () => {
-            const [client, room, events] = setupTestData();
-            const virtualRoom = mkRoom(client, "virtualRoomId");
-            const virtualCallInvite = new MatrixEvent({
-                type: "m.call.invite",
-                room_id: virtualRoom.roomId,
-                event_id: `virtualCallEvent1`,
-                origin_server_ts: 0,
-            });
-            virtualCallInvite.localTimestamp = 2;
-            const virtualCallMetaEvent = new MatrixEvent({
-                type: "org.matrix.call.sdp_stream_metadata_changed",
-                room_id: virtualRoom.roomId,
-                event_id: `virtualCallEvent2`,
-                origin_server_ts: 0,
-            });
-            virtualCallMetaEvent.localTimestamp = 2;
-            const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent];
-            const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
-
-            const { container } = render(
-                <TimelinePanel
-                    {...getProps(room, events)}
-                    overlayTimelineSet={overlayTimelineSet}
-                    overlayTimelineSetFilter={isCallEvent}
-                />,
-            );
-            await waitFor(() =>
-                expectEvents(container, [
-                    // main timeline events are included
-                    events[0],
-                    events[1],
-                    // virtual timeline call event is included
-                    virtualCallInvite,
-                    // virtual call event has no tile renderer => not rendered
-                ]),
-            );
-        });
-
-        it.each([
-            ["when it starts with no overlay events", true],
-            ["to get enough overlay events", false],
-        ])("expands the initial window %s", async (_s, startWithEmptyOverlayWindow) => {
-            const [client, room, events] = setupTestData();
-            const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
-            let overlayEventsPage1: MatrixEvent[];
-            let overlayEventsPage2: MatrixEvent[];
-            let overlayEventsPage3: MatrixEvent[];
-            if (startWithEmptyOverlayWindow) {
-                overlayEventsPage1 = overlayEvents.slice(0, 3);
-                overlayEventsPage2 = [];
-                overlayEventsPage3 = overlayEvents.slice(3, 5);
-            } else {
-                overlayEventsPage1 = overlayEvents.slice(0, 2);
-                overlayEventsPage2 = overlayEvents.slice(2, 3);
-                overlayEventsPage3 = overlayEvents.slice(3, 5);
-            }
-
-            // Start with only page 2 of the overlay events in the window
-            const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage2);
-            setupPagination(client, overlayTimeline, overlayEventsPage1, overlayEventsPage3);
-
-            const { container } = render(
-                <TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
-            );
-
-            await waitFor(() =>
-                expectEvents(container, [
-                    overlayEvents[0],
-                    events[0],
-                    overlayEvents[1],
-                    overlayEvents[2],
-                    overlayEvents[3],
-                    events[1],
-                    overlayEvents[4],
-                ]),
-            );
-        });
-
-        it("extends overlay window beyond main window at the start of the timeline", async () => {
-            const [client, room, events] = setupTestData();
-            const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-            // Delete event 0 so the TimelinePanel will still leave some stuff
-            // unloaded for us to test with
-            events.shift();
-
-            const overlayEventsPage1 = overlayEvents.slice(0, 2);
-            const overlayEventsPage2 = overlayEvents.slice(2, 5);
-
-            // Start with only page 2 of the overlay events in the window
-            const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage2);
-            setupPagination(client, overlayTimeline, overlayEventsPage1, null);
-
-            const { container } = render(
-                <TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
-            );
-
-            await waitFor(() =>
-                expectEvents(container, [
-                    // These first two are the newly loaded events
-                    overlayEvents[0],
-                    overlayEvents[1],
-                    overlayEvents[2],
-                    overlayEvents[3],
-                    events[0],
-                    overlayEvents[4],
-                ]),
-            );
-        });
-
-        it("extends overlay window beyond main window at the end of the timeline", async () => {
-            const [client, room, events] = setupTestData();
-            const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-            // Delete event 1 so the TimelinePanel will still leave some stuff
-            // unloaded for us to test with
-            events.pop();
-
-            const overlayEventsPage1 = overlayEvents.slice(0, 2);
-            const overlayEventsPage2 = overlayEvents.slice(2, 5);
-
-            // Start with only page 1 of the overlay events in the window
-            const [overlayTimeline, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEventsPage1);
-            setupPagination(client, overlayTimeline, null, overlayEventsPage2);
-
-            const { container } = render(
-                <TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
-            );
-
-            await waitFor(() =>
-                expectEvents(container, [
-                    overlayEvents[0],
-                    events[0],
-                    overlayEvents[1],
-                    // These are the newly loaded events
-                    overlayEvents[2],
-                    overlayEvents[3],
-                    overlayEvents[4],
-                ]),
-            );
-        });
-
-        it("paginates", async () => {
-            const [client, room, events] = setupTestData();
-            const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
-            const eventsPage1 = events.slice(0, 1);
-            const eventsPage2 = events.slice(1, 2);
-
-            // Start with only page 1 of the main events in the window
-            const [timeline, timelineSet] = mkTimeline(room, eventsPage1);
-            const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents);
-            setupPagination(client, timeline, null, eventsPage2);
-
-            await withScrollPanelMountSpy(async (mountSpy) => {
-                const { container } = render(
-                    <TimelinePanel
-                        {...getProps(room, events)}
-                        timelineSet={timelineSet}
-                        overlayTimelineSet={overlayTimelineSet}
-                    />,
-                );
-
-                await waitFor(() => expectEvents(container, [overlayEvents[0], events[0]]));
-
-                // ScrollPanel has no chance of working in jsdom, so we've no choice
-                // but to do some shady stuff to trigger the fill callback by hand
-                const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel;
-                scrollPanel.props.onFillRequest!(false);
-
-                await waitFor(() =>
-                    expectEvents(container, [
-                        overlayEvents[0],
-                        events[0],
-                        overlayEvents[1],
-                        overlayEvents[2],
-                        overlayEvents[3],
-                        events[1],
-                        overlayEvents[4],
-                    ]),
-                );
-            });
-        });
-
-        it.each([
-            ["down", "main", true, false],
-            ["down", "overlay", true, true],
-            ["up", "main", false, false],
-            ["up", "overlay", false, true],
-        ])("unpaginates %s to an event from the %s timeline", async (_s1, _s2, backwards, fromOverlay) => {
-            const [client, room, events] = setupTestData();
-            const [virtualRoom, overlayEvents] = setupOverlayTestData(client, events);
-
-            let marker: MatrixEvent;
-            let expectedEvents: MatrixEvent[];
-            if (backwards) {
-                if (fromOverlay) {
-                    marker = overlayEvents[1];
-                    // Overlay events 0−1 and event 0 should be unpaginated
-                    // Overlay events 2−3 should be hidden since they're at the edge of the window
-                    expectedEvents = [events[1], overlayEvents[4]];
-                } else {
-                    marker = events[0];
-                    // Overlay event 0 and event 0 should be unpaginated
-                    // Overlay events 1−3 should be hidden since they're at the edge of the window
-                    expectedEvents = [events[1], overlayEvents[4]];
-                }
-            } else {
-                if (fromOverlay) {
-                    marker = overlayEvents[4];
-                    // Only the last overlay event should be unpaginated
-                    expectedEvents = [
-                        overlayEvents[0],
-                        events[0],
-                        overlayEvents[1],
-                        overlayEvents[2],
-                        overlayEvents[3],
-                        events[1],
-                    ];
-                } else {
-                    // Get rid of overlay event 4 so we can test the case where no overlay events get unpaginated
-                    overlayEvents.pop();
-                    marker = events[1];
-                    // Only event 1 should be unpaginated
-                    // Overlay events 1−2 should be hidden since they're at the edge of the window
-                    expectedEvents = [overlayEvents[0], events[0]];
-                }
-            }
-
-            const [, overlayTimelineSet] = mkTimeline(virtualRoom, overlayEvents);
-
-            await withScrollPanelMountSpy(async (mountSpy) => {
-                const { container } = render(
-                    <TimelinePanel {...getProps(room, events)} overlayTimelineSet={overlayTimelineSet} />,
-                );
-
-                await waitFor(() =>
-                    expectEvents(container, [
-                        overlayEvents[0],
-                        events[0],
-                        overlayEvents[1],
-                        overlayEvents[2],
-                        overlayEvents[3],
-                        events[1],
-                        ...(!backwards && !fromOverlay ? [] : [overlayEvents[4]]),
-                    ]),
-                );
-
-                // ScrollPanel has no chance of working in jsdom, so we've no choice
-                // but to do some shady stuff to trigger the unfill callback by hand
-                const scrollPanel = mountSpy.mock.contexts[0] as ScrollPanel;
-                scrollPanel.props.onUnfillRequest!(backwards, marker.getId()!);
-
-                await waitFor(() => expectEvents(container, expectedEvents));
-            });
-        });
     });
 
     describe("when a thread updates", () => {
@@ -1027,7 +738,10 @@ describe("TimelinePanel", () => {
         room.getTimelineSets = jest.fn().mockReturnValue([timelineSet]);
 
         await withScrollPanelMountSpy(async () => {
-            const { container } = render(<TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />);
+            const { container } = render(
+                <TimelinePanel {...getProps(room, events)} timelineSet={timelineSet} />,
+                withClientContextRenderOptions(MatrixClientPeg.safeGet()),
+            );
 
             await waitFor(() => expectEvents(container, [events[1]]));
         });
diff --git a/test/unit-tests/components/structures/UserMenu-test.tsx b/test/unit-tests/components/structures/UserMenu-test.tsx
index b6eb1bef5d1f0e1eb8dd7bd945f25b8b5084932b..177adbaf11e27ff6fbd91c5c01650bd8e7eb425e 100644
--- a/test/unit-tests/components/structures/UserMenu-test.tsx
+++ b/test/unit-tests/components/structures/UserMenu-test.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
-import { DEVICE_CODE_SCOPE, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { DEVICE_CODE_SCOPE, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
 import { mocked } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
 
diff --git a/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
index 6a58ac98115686c82cc1d1c3df09962a95ef9fca..f311521960923c970a6a4bbec8438ea919199754 100644
--- a/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/FilePanel-test.tsx.snap
@@ -12,22 +12,22 @@ exports[`FilePanel renders empty state 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Files
         </p>
       </div>
       <button
-        aria-labelledby=":r0:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r0»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -38,7 +38,7 @@ exports[`FilePanel renders empty state 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -52,7 +52,7 @@ exports[`FilePanel renders empty state 1`] = `
       >
         <div
           class="mx_Flex mx_EmptyState"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
         >
           <svg
             fill="currentColor"
@@ -62,16 +62,16 @@ exports[`FilePanel renders empty state 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V4c0-.55.196-1.02.588-1.413A1.926 1.926 0 0 1 6 2h7.175a1.975 1.975 0 0 1 1.4.575l4.85 4.85a1.975 1.975 0 0 1 .575 1.4V20c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm7-14V4H6v16h12V9h-4a.968.968 0 0 1-.713-.287A.967.967 0 0 1 13 8Z"
+              d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V4q0-.824.588-1.412A1.93 1.93 0 0 1 6 2h7.175a1.98 1.98 0 0 1 1.4.575l4.85 4.85q.275.275.425.638.15.361.15.762V20q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zm7-14V4H6v16h12V9h-4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 13 8"
             />
           </svg>
           <p
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
           >
             No files visible in this room
           </p>
           <p
-            class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+            class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
           >
             Attach files from chat or just drag and drop them anywhere in a room.
           </p>
diff --git a/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap
index e57c5363af7de8e2d5101762f06b741f17317043..bb748e820a28804ca5bd5f0210cedf923bc10c75 100644
--- a/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap
@@ -94,7 +94,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
                 class="mx_GenericEventListSummary_avatars"
               >
                 <span
-                  class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                  class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                   data-color="1"
                   data-testid="avatar-img"
                   data-type="round"
@@ -108,9 +108,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
               <span
                 class="mx_TextualEvent mx_GenericEventListSummary_summary"
               >
-                <span>
-                  @user:id made no changes 100 times
-                </span>
+                @user:id made no changes 100 times
               </span>
             </div>
           </div>
diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
index 65a755058bfd5946b73da7d358d9aec498c13883..b99ca68c28c97c2c1fcb563a2efffe4197d0a53d 100644
--- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap
@@ -7,12 +7,12 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
   >
     <header
       class="mx_Flex mx_RoomHeader light-panel"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
     >
       <button
         aria-label="Open room settings"
         aria-live="off"
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -33,7 +33,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
         >
           <div
             aria-level="1"
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
             dir="auto"
             role="heading"
           >
@@ -48,17 +48,17 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
       <button
         aria-disabled="false"
         aria-label="Video call"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
-            aria-labelledby=":rg4:"
+            aria-labelledby="«rg4»"
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -66,7 +66,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
             />
           </svg>
         </div>
@@ -74,14 +74,14 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
       <button
         aria-disabled="false"
         aria-label="Voice call"
-        aria-labelledby=":rg9:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rg9»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -92,24 +92,25 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+              d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Threads"
-        aria-labelledby=":rge:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rge»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -117,24 +118,25 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Room info"
-        aria-labelledby=":rgj:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rgj»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -142,26 +144,26 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
         </div>
       </button>
       <div
-        class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+        class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
       >
         <div
           aria-label="2 members"
-          aria-labelledby=":rgo:"
+          aria-labelledby="«rgo»"
           class="mx_AccessibleButton mx_FacePile"
           role="button"
           tabindex="0"
         >
           <div
-            class="_stacked-avatars_mcap2_111"
+            class="_stacked-avatars_1qbcf_102"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -171,7 +173,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
               u
             </span>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -220,12 +222,12 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
   >
     <header
       class="mx_Flex mx_RoomHeader light-panel"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
     >
       <button
         aria-label="Open room settings"
         aria-live="off"
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -246,7 +248,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
         >
           <div
             aria-level="1"
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
             dir="auto"
             role="heading"
           >
@@ -261,17 +263,17 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
       <button
         aria-disabled="false"
         aria-label="Video call"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
-            aria-labelledby=":rh2:"
+            aria-labelledby="«rh2»"
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -279,7 +281,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
             />
           </svg>
         </div>
@@ -287,14 +289,14 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
       <button
         aria-disabled="false"
         aria-label="Voice call"
-        aria-labelledby=":rh7:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rh7»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -305,24 +307,25 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+              d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Threads"
-        aria-labelledby=":rhc:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rhc»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -330,24 +333,25 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Room info"
-        aria-labelledby=":rhh:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rhh»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -355,26 +359,26 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
         </div>
       </button>
       <div
-        class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+        class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
       >
         <div
           aria-label="2 members"
-          aria-labelledby=":rhm:"
+          aria-labelledby="«rhm»"
           class="mx_AccessibleButton mx_FacePile"
           role="button"
           tabindex="0"
         >
           <div
-            class="_stacked-avatars_mcap2_111"
+            class="_stacked-avatars_1qbcf_102"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -384,7 +388,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
               u
             </span>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -441,7 +445,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
                 <button
                   aria-label="Avatar"
                   aria-live="off"
-                  class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                  class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                   data-color="3"
                   data-testid="avatar-img"
                   data-type="round"
@@ -518,12 +522,12 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
   >
     <header
       class="mx_Flex mx_RoomHeader light-panel"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
     >
       <button
         aria-label="Open room settings"
         aria-live="off"
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -544,7 +548,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
         >
           <div
             aria-level="1"
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
             dir="auto"
             role="heading"
           >
@@ -559,17 +563,17 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
       <button
         aria-disabled="false"
         aria-label="Video call"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
-            aria-labelledby=":rbo:"
+            aria-labelledby="«rbo»"
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -577,7 +581,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
             />
           </svg>
         </div>
@@ -585,14 +589,14 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
       <button
         aria-disabled="false"
         aria-label="Voice call"
-        aria-labelledby=":rbt:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rbt»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -603,24 +607,25 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+              d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Threads"
-        aria-labelledby=":rc2:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rc2»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -628,24 +633,25 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Room info"
-        aria-labelledby=":rc7:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«rc7»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -653,26 +659,26 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
         </div>
       </button>
       <div
-        class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+        class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
       >
         <div
           aria-label="2 members"
-          aria-labelledby=":rcc:"
+          aria-labelledby="«rcc»"
           class="mx_AccessibleButton mx_FacePile"
           role="button"
           tabindex="0"
         >
           <div
-            class="_stacked-avatars_mcap2_111"
+            class="_stacked-avatars_1qbcf_102"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -682,7 +688,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
               u
             </span>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -739,7 +745,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
                 <button
                   aria-label="Avatar"
                   aria-live="off"
-                  class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                  class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                   data-color="3"
                   data-testid="avatar-img"
                   data-type="round"
@@ -893,12 +899,12 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
   >
     <header
       class="mx_Flex mx_RoomHeader light-panel"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
     >
       <button
         aria-label="Open room settings"
         aria-live="off"
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -919,7 +925,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
         >
           <div
             aria-level="1"
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
             dir="auto"
             role="heading"
           >
@@ -934,17 +940,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
       <button
         aria-disabled="false"
         aria-label="Video call"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
-            aria-labelledby=":rdu:"
+            aria-labelledby="«rdu»"
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -952,7 +958,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+              d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
             />
           </svg>
         </div>
@@ -960,14 +966,14 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
       <button
         aria-disabled="false"
         aria-label="Voice call"
-        aria-labelledby=":re3:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«re3»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -978,24 +984,25 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+              d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Threads"
-        aria-labelledby=":re8:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«re8»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -1003,24 +1010,25 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
       <button
         aria-label="Room info"
-        aria-labelledby=":red:"
-        class="_icon-button_bh2qc_17"
+        aria-labelledby="«red»"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
+            class=""
             fill="currentColor"
             height="1em"
             viewBox="0 0 24 24"
@@ -1028,26 +1036,26 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
         </div>
       </button>
       <div
-        class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+        class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
       >
         <div
           aria-label="2 members"
-          aria-labelledby=":rei:"
+          aria-labelledby="«rei»"
           class="mx_AccessibleButton mx_FacePile"
           role="button"
           tabindex="0"
         >
           <div
-            class="_stacked-avatars_mcap2_111"
+            class="_stacked-avatars_1qbcf_102"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -1057,7 +1065,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
               u
             </span>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -1109,7 +1117,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
                 <button
                   aria-label="Avatar"
                   aria-live="off"
-                  class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                  class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                   data-color="3"
                   data-testid="avatar-img"
                   data-type="round"
@@ -1256,6 +1264,79 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
 </div>
 `;
 
+exports[`RoomView invites renders an invite room 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomView"
+  >
+    <div
+      class="mx_RoomPreviewBar mx_RoomPreviewBar_Invite mx_RoomPreviewBar_dialog"
+      role="complementary"
+    >
+      <div
+        class="mx_RoomPreviewBar_message"
+      >
+        <h3>
+          Do you want to join !2:example.org?
+        </h3>
+        <p>
+          <span
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="4"
+            data-testid="avatar-img"
+            data-type="round"
+            role="presentation"
+            style="--cpd-avatar-size: 36px;"
+          >
+            !
+          </span>
+        </p>
+        <p>
+          <span>
+            Invited by 
+            <span
+              class="mx_RoomPreviewBar_inviter"
+            >
+              @bob:example.org
+            </span>
+          </span>
+        </p>
+      </div>
+      <div
+        class="mx_RoomPreviewBar_actions"
+      >
+        <div
+          class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
+          role="button"
+          tabindex="0"
+        >
+          Accept
+        </div>
+        <div
+          class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
+          role="button"
+          tabindex="0"
+        >
+          Decline
+        </div>
+        <button
+          class="_button_vczzf_8 _destructive_vczzf_107"
+          data-kind="tertiary"
+          data-size="lg"
+          role="button"
+          tabindex="0"
+        >
+          Decline and block
+        </button>
+      </div>
+      <div
+        class="mx_RoomPreviewBar_footer"
+      />
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
 exports[`RoomView should not display the timeline when the room encryption is loading 1`] = `
 <DocumentFragment>
   <div
@@ -1276,13 +1357,13 @@ exports[`RoomView should not display the timeline when the room encryption is lo
       >
         <header
           class="mx_Flex mx_RoomHeader light-panel"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
         >
           <button
             aria-label="Open room settings"
             aria-live="off"
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-            data-color="2"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="4"
             data-testid="avatar-img"
             data-type="round"
             role="button"
@@ -1302,14 +1383,14 @@ exports[`RoomView should not display the timeline when the room encryption is lo
             >
               <div
                 aria-level="1"
-                class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+                class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
                 dir="auto"
                 role="heading"
               >
                 <span
                   class="mx_RoomHeader_truncated mx_lineClamp"
                 >
-                  !6:example.org
+                  !11:example.org
                 </span>
               </div>
             </div>
@@ -1317,17 +1398,17 @@ exports[`RoomView should not display the timeline when the room encryption is lo
           <button
             aria-disabled="true"
             aria-label="There's no one here to call"
-            class="_icon-button_bh2qc_17"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
             >
               <svg
-                aria-labelledby=":r2c:"
+                aria-labelledby="«r2c»"
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1335,7 +1416,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+                  d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
                 />
               </svg>
             </div>
@@ -1343,14 +1424,14 @@ exports[`RoomView should not display the timeline when the room encryption is lo
           <button
             aria-disabled="true"
             aria-label="There's no one here to call"
-            aria-labelledby=":r2h:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2h»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
             >
               <svg
@@ -1361,24 +1442,25 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+                  d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Threads"
-            aria-labelledby=":r2m:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2m»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1386,24 +1468,25 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Room info"
-            aria-labelledby=":r2r:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2r»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1411,23 +1494,23 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+                  d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
                 />
               </svg>
             </div>
           </button>
           <div
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
           >
             <div
               aria-label="0 members"
-              aria-labelledby=":r30:"
+              aria-labelledby="«r30»"
               class="mx_AccessibleButton mx_FacePile"
               role="button"
               tabindex="0"
             >
               <div
-                class="_stacked-avatars_mcap2_111"
+                class="_stacked-avatars_1qbcf_102"
               />
               0
             </div>
@@ -1482,13 +1565,13 @@ exports[`RoomView should not display the timeline when the room encryption is lo
       >
         <header
           class="mx_Flex mx_RoomHeader light-panel"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
         >
           <button
             aria-label="Open room settings"
             aria-live="off"
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-            data-color="2"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="4"
             data-testid="avatar-img"
             data-type="round"
             role="button"
@@ -1508,14 +1591,14 @@ exports[`RoomView should not display the timeline when the room encryption is lo
             >
               <div
                 aria-level="1"
-                class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+                class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
                 dir="auto"
                 role="heading"
               >
                 <span
                   class="mx_RoomHeader_truncated mx_lineClamp"
                 >
-                  !6:example.org
+                  !11:example.org
                 </span>
               </div>
             </div>
@@ -1523,17 +1606,17 @@ exports[`RoomView should not display the timeline when the room encryption is lo
           <button
             aria-disabled="true"
             aria-label="There's no one here to call"
-            class="_icon-button_bh2qc_17"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
             >
               <svg
-                aria-labelledby=":r2c:"
+                aria-labelledby="«r2c»"
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1541,7 +1624,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+                  d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
                 />
               </svg>
             </div>
@@ -1549,14 +1632,14 @@ exports[`RoomView should not display the timeline when the room encryption is lo
           <button
             aria-disabled="true"
             aria-label="There's no one here to call"
-            aria-labelledby=":r2h:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2h»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
             >
               <svg
@@ -1567,24 +1650,25 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+                  d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Threads"
-            aria-labelledby=":r2m:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2m»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1592,24 +1676,25 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Room info"
-            aria-labelledby=":r2r:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r2r»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1617,23 +1702,23 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+                  d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
                 />
               </svg>
             </div>
           </button>
           <div
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
           >
             <div
               aria-label="0 members"
-              aria-labelledby=":r30:"
+              aria-labelledby="«r30»"
               class="mx_AccessibleButton mx_FacePile"
               role="button"
               tabindex="0"
             >
               <div
-                class="_stacked-avatars_mcap2_111"
+                class="_stacked-avatars_1qbcf_102"
               />
               0
             </div>
@@ -1695,7 +1780,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
                   tabindex="0"
                 >
                   <div
-                    aria-labelledby=":r3e:"
+                    aria-labelledby="«r3e»"
                     class="mx_E2EIcon mx_E2EIcon_verified mx_MessageComposer_e2eIcon"
                   />
                 </span>
@@ -1861,13 +1946,13 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
       >
         <header
           class="mx_Flex mx_RoomHeader light-panel"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
         >
           <button
             aria-label="Open room settings"
             aria-live="off"
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-            data-color="6"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="3"
             data-testid="avatar-img"
             data-type="round"
             role="button"
@@ -1887,31 +1972,32 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
             >
               <div
                 aria-level="1"
-                class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+                class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
                 dir="auto"
                 role="heading"
               >
                 <span
                   class="mx_RoomHeader_truncated mx_lineClamp"
                 >
-                  !13:example.org
+                  !16:example.org
                 </span>
               </div>
             </div>
           </button>
           <button
             aria-label="Chat"
-            aria-labelledby=":r7c:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r7c»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class="mx_RoomHeader_toggled"
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1919,24 +2005,25 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M2.95 16.3 1.5 21.25a.936.936 0 0 0 .25 1 .936.936 0 0 0 1 .25l4.95-1.45a10.23 10.23 0 0 0 2.1.712c.717.159 1.45.238 2.2.238a9.737 9.737 0 0 0 3.9-.788 10.098 10.098 0 0 0 3.175-2.137c.9-.9 1.613-1.958 2.137-3.175A9.738 9.738 0 0 0 22 12a9.738 9.738 0 0 0-.788-3.9 10.099 10.099 0 0 0-2.137-3.175c-.9-.9-1.958-1.612-3.175-2.137A9.737 9.737 0 0 0 12 2a9.737 9.737 0 0 0-3.9.788 10.099 10.099 0 0 0-3.175 2.137c-.9.9-1.612 1.958-2.137 3.175A9.738 9.738 0 0 0 2 12a10.179 10.179 0 0 0 .95 4.3Z"
+                  d="M2.95 16.3 1.5 21.25a.94.94 0 0 0 .25 1 .94.94 0 0 0 1 .25l4.95-1.45a10.2 10.2 0 0 0 2.1.712Q10.875 22 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 1.125.238 2.2.237 1.076.712 2.1"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Threads"
-            aria-labelledby=":r7h:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r7h»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1944,24 +2031,25 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+                  d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
                 />
               </svg>
             </div>
           </button>
           <button
             aria-label="Room info"
-            aria-labelledby=":r7m:"
-            class="_icon-button_bh2qc_17"
+            aria-labelledby="«r7m»"
+            class="_icon-button_m2erp_8"
             role="button"
             style="--cpd-icon-button-size: 32px;"
             tabindex="0"
           >
             <div
-              class="_indicator-icon_133tf_26"
+              class="_indicator-icon_zr2a0_17"
               style="--cpd-icon-button-size: 100%;"
             >
               <svg
+                class=""
                 fill="currentColor"
                 height="1em"
                 viewBox="0 0 24 24"
@@ -1969,28 +2057,63 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+                  d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
                 />
               </svg>
             </div>
           </button>
           <div
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41"
           >
             <div
               aria-label="0 members"
-              aria-labelledby=":r7r:"
+              aria-labelledby="«r7r»"
               class="mx_AccessibleButton mx_FacePile"
               role="button"
               tabindex="0"
             >
               <div
-                class="_stacked-avatars_mcap2_111"
+                class="_stacked-avatars_1qbcf_102"
               />
               0
             </div>
           </div>
         </header>
+        <div
+          class="mx_CallView"
+          role="main"
+        >
+          <div
+            class="mx_AppTile"
+            id="vY7Q4uEh9K38QgU2PomxwKpa"
+          >
+            <div
+              class="mx_AppTileBody mx_AppTileBody--large mx_AppTileBody--loading mx_AppTileBody--call"
+            >
+              <div
+                class="mx_AppTileBody_fadeInSpinner"
+              >
+                <div
+                  class="mx_Spinner"
+                >
+                  <div
+                    class="mx_Spinner_Msg"
+                  >
+                    Loading…
+                  </div>
+                   
+                  <div
+                    aria-label="Loading…"
+                    class="mx_Spinner_icon"
+                    data-testid="spinner"
+                    role="progressbar"
+                    style="width: 32px; height: 32px;"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
       </div>
       <div
         class="mx_RightPanel_ResizeWrapper"
@@ -1998,6 +2121,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
       >
         <aside
           class="mx_RightPanel"
+          data-testid="right-panel"
           id="mx_RightPanel"
         >
           <div
@@ -2010,22 +2134,22 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
                 class="mx_BaseCard_header_title"
               >
                 <p
-                  class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+                  class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
                   role="heading"
                 >
                   Chat
                 </p>
               </div>
               <button
-                aria-labelledby=":r84:"
-                class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+                aria-labelledby="«r84»"
+                class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
                 data-testid="base-card-close-button"
                 role="button"
                 style="--cpd-icon-button-size: 28px;"
                 tabindex="0"
               >
                 <div
-                  class="_indicator-icon_133tf_26"
+                  class="_indicator-icon_zr2a0_17"
                   style="--cpd-icon-button-size: 100%;"
                 >
                   <svg
@@ -2036,7 +2160,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
                     xmlns="http://www.w3.org/2000/svg"
                   >
                     <path
-                      d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                      d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                     />
                   </svg>
                 </div>
diff --git a/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap
index f0d88637da71d9bc9bed27cda3b6a23203c9b8fb..0a64ec5f6190fbc7fd98eed1f85982ca571c48de 100644
--- a/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/SpaceHierarchy-test.tsx.snap
@@ -58,6 +58,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
     role="tree"
   >
     <li
+      aria-labelledby="«r8»"
       class="mx_SpaceHierarchy_roomTileWrapper"
       role="treeitem"
     >
@@ -73,7 +74,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             class="mx_SpaceHierarchy_roomTile_avatar"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="5"
               data-testid="avatar-img"
               data-type="round"
@@ -86,7 +87,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           <div
             class="mx_SpaceHierarchy_roomTile_name"
           >
-            Unnamed Room
+            <span
+              id="«r8»"
+            >
+              Unnamed Room
+            </span>
           </div>
           <div
             class="mx_SpaceHierarchy_roomTile_info"
@@ -104,30 +109,54 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           >
             Join
           </div>
-          <span
-            class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+          <form
+            class="_root_19upo_16"
           >
-            <input
-              id="checkbox_vY7Q4uEh9K"
-              tabindex="0"
-              type="checkbox"
-            />
-            <label
-              for="checkbox_vY7Q4uEh9K"
+            <div
+              class="_inline-field_19upo_32"
             >
               <div
-                class="mx_Checkbox_background"
+                class="_inline-field-control_19upo_44"
               >
                 <div
-                  class="mx_Checkbox_checkmark"
-                />
+                  class="_container_1hel1_10"
+                >
+                  <input
+                    aria-labelledby="«r8»"
+                    class="_input_1hel1_18"
+                    id="checkbox_MwbPDmfGtm"
+                    role="presentation"
+                    tabindex="-1"
+                    type="checkbox"
+                  />
+                  <div
+                    class="_ui_1hel1_19"
+                  >
+                    <svg
+                      aria-hidden="true"
+                      fill="currentColor"
+                      height="1em"
+                      viewBox="0 0 24 24"
+                      width="1em"
+                      xmlns="http://www.w3.org/2000/svg"
+                    >
+                      <path
+                        d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                      />
+                    </svg>
+                  </div>
+                </div>
               </div>
-            </label>
-          </span>
+              <div
+                class="_inline-field-body_19upo_38"
+              />
+            </div>
+          </form>
         </div>
       </div>
     </li>
     <li
+      aria-labelledby="«rc»"
       class="mx_SpaceHierarchy_roomTileWrapper"
       role="treeitem"
     >
@@ -143,7 +172,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             class="mx_SpaceHierarchy_roomTile_avatar"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="6"
               data-testid="avatar-img"
               data-type="round"
@@ -156,7 +185,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           <div
             class="mx_SpaceHierarchy_roomTile_name"
           >
-            Unnamed Room
+            <span
+              id="«rc»"
+            >
+              Unnamed Room
+            </span>
           </div>
           <div
             class="mx_SpaceHierarchy_roomTile_info"
@@ -174,30 +207,54 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           >
             Join
           </div>
-          <span
-            class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+          <form
+            class="_root_19upo_16"
           >
-            <input
-              id="checkbox_38QgU2Pomx"
-              tabindex="-1"
-              type="checkbox"
-            />
-            <label
-              for="checkbox_38QgU2Pomx"
+            <div
+              class="_inline-field_19upo_32"
             >
               <div
-                class="mx_Checkbox_background"
+                class="_inline-field-control_19upo_44"
               >
                 <div
-                  class="mx_Checkbox_checkmark"
-                />
+                  class="_container_1hel1_10"
+                >
+                  <input
+                    aria-labelledby="«rc»"
+                    class="_input_1hel1_18"
+                    id="checkbox_GQvdMWe954"
+                    role="presentation"
+                    tabindex="-1"
+                    type="checkbox"
+                  />
+                  <div
+                    class="_ui_1hel1_19"
+                  >
+                    <svg
+                      aria-hidden="true"
+                      fill="currentColor"
+                      height="1em"
+                      viewBox="0 0 24 24"
+                      width="1em"
+                      xmlns="http://www.w3.org/2000/svg"
+                    >
+                      <path
+                        d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                      />
+                    </svg>
+                  </div>
+                </div>
               </div>
-            </label>
-          </span>
+              <div
+                class="_inline-field-body_19upo_38"
+              />
+            </div>
+          </form>
         </div>
       </div>
     </li>
     <li
+      aria-labelledby="«rg»"
       class="mx_SpaceHierarchy_roomTileWrapper"
       role="treeitem"
     >
@@ -213,7 +270,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             class="mx_SpaceHierarchy_roomTile_avatar"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="6"
               data-testid="avatar-img"
               data-type="round"
@@ -226,7 +283,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           <div
             class="mx_SpaceHierarchy_roomTile_name"
           >
-            Knock room
+            <span
+              id="«rg»"
+            >
+              Knock room
+            </span>
           </div>
           <div
             class="mx_SpaceHierarchy_roomTile_info"
@@ -244,31 +305,55 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           >
             View
           </div>
-          <span
-            class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+          <form
+            class="_root_19upo_16"
           >
-            <input
-              id="checkbox_wKpa6hpi3Y"
-              tabindex="-1"
-              type="checkbox"
-            />
-            <label
-              for="checkbox_wKpa6hpi3Y"
+            <div
+              class="_inline-field_19upo_32"
             >
               <div
-                class="mx_Checkbox_background"
+                class="_inline-field-control_19upo_44"
               >
                 <div
-                  class="mx_Checkbox_checkmark"
-                />
+                  class="_container_1hel1_10"
+                >
+                  <input
+                    aria-labelledby="«rg»"
+                    class="_input_1hel1_18"
+                    id="checkbox_DVIAu5CsiH"
+                    role="presentation"
+                    tabindex="-1"
+                    type="checkbox"
+                  />
+                  <div
+                    class="_ui_1hel1_19"
+                  >
+                    <svg
+                      aria-hidden="true"
+                      fill="currentColor"
+                      height="1em"
+                      viewBox="0 0 24 24"
+                      width="1em"
+                      xmlns="http://www.w3.org/2000/svg"
+                    >
+                      <path
+                        d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                      />
+                    </svg>
+                  </div>
+                </div>
               </div>
-            </label>
-          </span>
+              <div
+                class="_inline-field-body_19upo_38"
+              />
+            </div>
+          </form>
         </div>
       </div>
     </li>
     <li
       aria-expanded="true"
+      aria-labelledby="«rk»"
       class="mx_SpaceHierarchy_roomTileWrapper"
       role="treeitem"
     >
@@ -284,7 +369,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             class="mx_SpaceHierarchy_roomTile_avatar"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -297,7 +382,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           <div
             class="mx_SpaceHierarchy_roomTile_name"
           >
-            Nested space
+            <span
+              id="«rk»"
+            >
+              Nested space
+            </span>
           </div>
           <div
             class="mx_SpaceHierarchy_roomTile_info"
@@ -315,26 +404,49 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           >
             Join
           </div>
-          <span
-            class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+          <form
+            class="_root_19upo_16"
           >
-            <input
-              id="checkbox_EetmBG4yVC"
-              tabindex="-1"
-              type="checkbox"
-            />
-            <label
-              for="checkbox_EetmBG4yVC"
+            <div
+              class="_inline-field_19upo_32"
             >
               <div
-                class="mx_Checkbox_background"
+                class="_inline-field-control_19upo_44"
               >
                 <div
-                  class="mx_Checkbox_checkmark"
-                />
+                  class="_container_1hel1_10"
+                >
+                  <input
+                    aria-labelledby="«rk»"
+                    class="_input_1hel1_18"
+                    id="checkbox_RD7nyrA2oh"
+                    role="presentation"
+                    tabindex="-1"
+                    type="checkbox"
+                  />
+                  <div
+                    class="_ui_1hel1_19"
+                  >
+                    <svg
+                      aria-hidden="true"
+                      fill="currentColor"
+                      height="1em"
+                      viewBox="0 0 24 24"
+                      width="1em"
+                      xmlns="http://www.w3.org/2000/svg"
+                    >
+                      <path
+                        d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                      />
+                    </svg>
+                  </div>
+                </div>
               </div>
-            </label>
-          </span>
+              <div
+                class="_inline-field-body_19upo_38"
+              />
+            </div>
+          </form>
         </div>
         <div
           class="mx_SpaceHierarchy_subspace_toggle mx_SpaceHierarchy_subspace_toggle_shown"
@@ -346,6 +458,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
       />
     </li>
     <li
+      aria-labelledby="«ro»"
       class="mx_SpaceHierarchy_roomTileWrapper"
       role="treeitem"
     >
@@ -361,7 +474,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             class="mx_SpaceHierarchy_roomTile_avatar"
           >
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="2"
               data-testid="avatar-img"
               data-type="round"
@@ -374,7 +487,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
           <div
             class="mx_SpaceHierarchy_roomTile_name"
           >
-            Nested room
+            <span
+              id="«ro»"
+            >
+              Nested room
+            </span>
           </div>
           <div
             class="mx_SpaceHierarchy_roomTile_info"
@@ -393,30 +510,53 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
             Join
           </div>
           <span
-            aria-labelledby=":r8:"
+            aria-labelledby="«rp»"
             tabindex="0"
           >
-            <span
-              class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+            <form
+              class="_root_19upo_16"
             >
-              <input
-                disabled=""
-                id="checkbox_eEefiPqpMR"
-                tabindex="-1"
-                type="checkbox"
-              />
-              <label
-                for="checkbox_eEefiPqpMR"
+              <div
+                class="_inline-field_19upo_32"
               >
                 <div
-                  class="mx_Checkbox_background"
+                  class="_inline-field-control_19upo_44"
                 >
                   <div
-                    class="mx_Checkbox_checkmark"
-                  />
+                    class="_container_1hel1_10"
+                  >
+                    <input
+                      aria-labelledby="«ro»"
+                      class="_input_1hel1_18"
+                      disabled=""
+                      id="checkbox_jWVJIPauy1"
+                      role="presentation"
+                      tabindex="-1"
+                      type="checkbox"
+                    />
+                    <div
+                      class="_ui_1hel1_19"
+                    >
+                      <svg
+                        aria-hidden="true"
+                        fill="currentColor"
+                        height="1em"
+                        viewBox="0 0 24 24"
+                        width="1em"
+                        xmlns="http://www.w3.org/2000/svg"
+                      >
+                        <path
+                          d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                        />
+                      </svg>
+                    </div>
+                  </div>
                 </div>
-              </label>
-            </span>
+                <div
+                  class="_inline-field-body_19upo_38"
+                />
+              </div>
+            </form>
           </span>
         </div>
       </div>
diff --git a/test/unit-tests/components/structures/__snapshots__/TabbedView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/TabbedView-test.tsx.snap
index fc2b259aaa65e2fbde303e13ab61ceec5fde45ca..540cfd233a5604af3b287d911e245b12f9956a9b 100644
--- a/test/unit-tests/components/structures/__snapshots__/TabbedView-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/TabbedView-test.tsx.snap
@@ -47,21 +47,21 @@ exports[`<TabbedView /> renders tabs 1`] = `
         </span>
       </li>
       <li
-        aria-controls="mx_tabpanel_SECURITY"
+        aria-controls="mx_tabpanel_APPEARANCE"
         aria-selected="false"
         class="mx_AccessibleButton mx_TabbedView_tabLabel"
-        data-testid="settings-tab-SECURITY"
+        data-testid="settings-tab-APPEARANCE"
         role="tab"
         tabindex="-1"
       >
         <span
-          class="mx_TabbedView_maskedIcon security"
+          class="mx_TabbedView_maskedIcon appearance"
         />
         <span
           class="mx_TabbedView_tabLabel_text"
-          id="mx_tabpanel_SECURITY_label"
+          id="mx_tabpanel_APPEARANCE_label"
         >
-          Security
+          Appearance
         </span>
       </li>
     </ul>
diff --git a/test/unit-tests/components/structures/__snapshots__/ThreadPanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
index 0ea46210025edfc6cc1a080fd51316e99d5c30d7..41b789d83140b17a1f1f230fa50bf2b9d2174553 100644
--- a/test/unit-tests/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
+++ b/test/unit-tests/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
@@ -3,20 +3,23 @@
 exports[`ThreadPanel Header expect that All filter for ThreadPanelHeader properly renders Show: All threads 1`] = `
 <DocumentFragment>
   <div
-    class="mx_BaseCard_header_title"
+    class="mx_ThreadPanelHeader"
   >
     <button
-      aria-labelledby=":r0:"
-      class="_icon-button_bh2qc_17"
+      aria-labelledby="«r0»"
+      class="_icon-button_m2erp_8"
       role="button"
-      style="--cpd-icon-button-size: 24px;"
+      style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
-        <div />
+        <div
+          height="20"
+          width="20"
+        />
       </div>
     </button>
     <div
@@ -38,20 +41,23 @@ exports[`ThreadPanel Header expect that All filter for ThreadPanelHeader properl
 exports[`ThreadPanel Header expect that My filter for ThreadPanelHeader properly renders Show: My threads 1`] = `
 <DocumentFragment>
   <div
-    class="mx_BaseCard_header_title"
+    class="mx_ThreadPanelHeader"
   >
     <button
-      aria-labelledby=":r6:"
-      class="_icon-button_bh2qc_17"
+      aria-labelledby="«r6»"
+      class="_icon-button_m2erp_8"
       role="button"
-      style="--cpd-icon-button-size: 24px;"
+      style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
-        <div />
+        <div
+          height="20"
+          width="20"
+        />
       </div>
     </button>
     <div
diff --git a/test/unit-tests/components/structures/auth/CompleteSecurity-test.tsx b/test/unit-tests/components/structures/auth/CompleteSecurity-test.tsx
index 1c9cd97ce21509e1a174c109b0137905e26c3472..c5efd81f0d3dd62ead2f32919720e1a00c1f8061 100644
--- a/test/unit-tests/components/structures/auth/CompleteSecurity-test.tsx
+++ b/test/unit-tests/components/structures/auth/CompleteSecurity-test.tsx
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, screen } from "jest-matrix-react";
+import { act, render, screen } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 import EventEmitter from "events";
 
@@ -76,4 +76,20 @@ describe("CompleteSecurity", () => {
 
         expect(screen.queryByRole("button", { name: "Skip verification for now" })).not.toBeInTheDocument();
     });
+
+    it("Renders a warning if user hits Reset", async () => {
+        // Given a store and a dialog based on it
+        const store = new SetupEncryptionStore();
+        jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
+        const panel = await act(() => render(<CompleteSecurity onFinished={() => {}} />));
+
+        // When we hit reset
+        await act(async () => panel.getByRole("button", { name: "Proceed with reset" }).click());
+
+        // Then the reset identity dialog appears
+        expect(
+            screen.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
+        ).toBeInTheDocument();
+        expect(panel.getByRole("button", { name: "Continue" })).toBeInTheDocument();
+    });
 });
diff --git a/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx b/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx
index b55fd2c99c32e66407f07ccd2ac5f1c5438fd05d..3ff77f9be8f4899fe25caaf4b87dc3493dc2140d 100644
--- a/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx
+++ b/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { mocked } from "jest-mock";
-import { render, RenderResult, screen, waitFor, cleanup } from "jest-matrix-react";
+import { render, type RenderResult, screen, waitFor, cleanup } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient, createClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, createClient } from "matrix-js-sdk/src/matrix";
 
 import ForgotPassword from "../../../../../src/components/structures/auth/ForgotPassword";
-import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
 import { clearAllModals, filterConsole, stubClient, waitEnoughCyclesForModal } from "../../../../test-utils";
 import AutoDiscoveryUtils from "../../../../../src/utils/AutoDiscoveryUtils";
 
diff --git a/test/unit-tests/components/structures/auth/Login-test.tsx b/test/unit-tests/components/structures/auth/Login-test.tsx
index 3b1a85b3e9b3f968dc0c4059d9248cd296359465..d6fec89dcb5361343d08a24feda26b84cbbe5f13 100644
--- a/test/unit-tests/components/structures/auth/Login-test.tsx
+++ b/test/unit-tests/components/structures/auth/Login-test.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
-import { DELEGATED_OIDC_COMPATIBILITY, IdentityProviderBrand, OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { DELEGATED_OIDC_COMPATIBILITY, IdentityProviderBrand, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import * as Matrix from "matrix-js-sdk/src/matrix";
 import { OidcError } from "matrix-js-sdk/src/oidc/error";
@@ -17,7 +17,7 @@ import { OidcError } from "matrix-js-sdk/src/oidc/error";
 import SdkConfig from "../../../../../src/SdkConfig";
 import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../../test-utils";
 import Login from "../../../../../src/components/structures/auth/Login";
-import BasePlatform from "../../../../../src/BasePlatform";
+import type BasePlatform from "../../../../../src/BasePlatform";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import * as registerClientUtils from "../../../../../src/utils/oidc/registerClient";
 import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
@@ -384,7 +384,7 @@ describe("Login", function () {
             await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
 
             // didn't try to register
-            expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
+            expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
             // continued with normal setup
             expect(mockClient.loginFlows).toHaveBeenCalled();
             // normal password login rendered
@@ -394,25 +394,25 @@ describe("Login", function () {
         it("should attempt to register oidc client", async () => {
             // dont mock, spy so we can check config values were correctly passed
             jest.spyOn(registerClientUtils, "getOidcClientId");
-            fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
+            fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
             getComponent(hsUrl, isUrl, delegatedAuth);
 
             await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
 
             // tried to register
-            expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
+            expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
             // called with values from config
             expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig);
         });
 
         it("should fallback to normal login when client registration fails", async () => {
-            fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 });
+            fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 });
             getComponent(hsUrl, isUrl, delegatedAuth);
 
             await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
 
             // tried to register
-            expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object));
+            expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object));
             expect(logger.error).toHaveBeenCalledWith(new Error(OidcError.DynamicRegistrationFailed));
 
             // continued with normal setup
@@ -423,7 +423,7 @@ describe("Login", function () {
 
         // short term during active development, UI will be added in next PRs
         it("should show continue button when oidc native flow is correctly configured", async () => {
-            fetchMock.post(delegatedAuth.registrationEndpoint!, { client_id: "abc123" });
+            fetchMock.post(delegatedAuth.registration_endpoint!, { client_id: "abc123" });
             getComponent(hsUrl, isUrl, delegatedAuth);
 
             await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
@@ -455,7 +455,7 @@ describe("Login", function () {
             await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
 
             // didn't try to register
-            expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint);
+            expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint);
             // continued with normal setup
             expect(mockClient.loginFlows).toHaveBeenCalled();
             // oidc-aware 'continue' button displayed
diff --git a/test/unit-tests/components/structures/auth/LoginSplashView-test.tsx b/test/unit-tests/components/structures/auth/LoginSplashView-test.tsx
index 17582167303fdb68ebc2b15ea8505c978db7d182..c2eed442c584413679bddabcc2afff37bed2855e 100644
--- a/test/unit-tests/components/structures/auth/LoginSplashView-test.tsx
+++ b/test/unit-tests/components/structures/auth/LoginSplashView-test.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { act, render, RenderResult } from "jest-matrix-react";
-import React, { ComponentProps } from "react";
+import { act, render, type RenderResult } from "jest-matrix-react";
+import React, { type ComponentProps } from "react";
 import EventEmitter from "events";
 import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 import { sleep } from "matrix-js-sdk/src/utils";
diff --git a/test/unit-tests/components/structures/auth/Registration-test.tsx b/test/unit-tests/components/structures/auth/Registration-test.tsx
index 82ac0b666cdea3265111c75accadf55e0ca80769..0a24fcf25bcdc1c10033e6c8ac86b28ef791c1dd 100644
--- a/test/unit-tests/components/structures/auth/Registration-test.tsx
+++ b/test/unit-tests/components/structures/auth/Registration-test.tsx
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
-import { createClient, MatrixClient, MatrixError, OidcClientConfig } from "matrix-js-sdk/src/matrix";
-import { mocked, MockedObject } from "jest-mock";
+import { createClient, type MatrixClient, MatrixError, type OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
 
 import SdkConfig, { DEFAULTS } from "../../../../../src/SdkConfig";
@@ -158,24 +158,26 @@ describe("Registration", function () {
     describe("when delegated authentication is configured and enabled", () => {
         const authConfig = makeDelegatedAuthConfig();
         const clientId = "test-client-id";
-        // @ts-ignore
-        authConfig.metadata["prompt_values_supported"] = ["create"];
+        authConfig.prompt_values_supported = ["create"];
 
         beforeEach(() => {
             // mock a statically registered client to avoid dynamic registration
             SdkConfig.put({
                 oidc_static_clients: {
-                    [authConfig.metadata.issuer]: {
+                    [authConfig.issuer]: {
                         client_id: clientId,
                     },
                 },
             });
 
             fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, {
-                issuer: authConfig.metadata.issuer,
+                issuer: authConfig.issuer,
             });
-            fetchMock.get("https://auth.org/.well-known/openid-configuration", authConfig.metadata);
-            fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
+            fetchMock.get("https://auth.org/.well-known/openid-configuration", {
+                ...authConfig,
+                signingKeys: undefined,
+            });
+            fetchMock.get(authConfig.jwks_uri!, { keys: [] });
         });
 
         it("should display oidc-native continue button", async () => {
diff --git a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ffaa152a53c1c57f0748a482e7f504c395545371
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook, waitFor } from "jest-matrix-react";
+import { JoinRule, type MatrixClient, type Room, RoomMember, User } from "matrix-js-sdk/src/matrix";
+
+import { useRoomAvatarViewModel } from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel";
+import { createTestClient, mkStubRoom } from "../../../../test-utils";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import * as PresenceIndicatorModule from "../../../../../src/components/views/avatars/WithPresenceIndicator";
+
+jest.mock("../../../../../src/utils/room/getJoinedNonFunctionalMembers", () => ({
+    getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([]),
+}));
+
+describe("RoomAvatarViewModel", () => {
+    let matrixClient: MatrixClient;
+    let room: Room;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+        room = mkStubRoom("roomId", "roomName", matrixClient);
+
+        DMRoomMap.makeShared(matrixClient);
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+
+        jest.spyOn(PresenceIndicatorModule, "useDmMember").mockReturnValue(null);
+        jest.spyOn(PresenceIndicatorModule, "usePresence").mockReturnValue(null);
+    });
+
+    it("should has hasDecoration to false", async () => {
+        const { result: vm } = renderHook(() => useRoomAvatarViewModel(room));
+        expect(vm.current.hasDecoration).toBe(false);
+    });
+
+    it("should has isVideoRoom set to true", () => {
+        jest.spyOn(room, "isCallRoom").mockReturnValue(true);
+        const { result: vm } = renderHook(() => useRoomAvatarViewModel(room));
+        expect(vm.current.isVideoRoom).toBe(true);
+        expect(vm.current.hasDecoration).toBe(true);
+    });
+
+    it("should has isPublic set to true", () => {
+        jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
+
+        const { result: vm } = renderHook(() => useRoomAvatarViewModel(room));
+        expect(vm.current.isPublic).toBe(true);
+        expect(vm.current.hasDecoration).toBe(true);
+    });
+
+    it("should recompute isPublic when room changed", async () => {
+        const { result: vm, rerender } = renderHook((props) => useRoomAvatarViewModel(props), { initialProps: room });
+        expect(vm.current.isPublic).toBe(false);
+
+        const publicRoom = mkStubRoom("roomId2", "roomName2", matrixClient);
+        jest.spyOn(publicRoom, "getJoinRule").mockReturnValue(JoinRule.Public);
+        rerender(publicRoom);
+
+        await waitFor(() => expect(vm.current.isPublic).toBe(true));
+    });
+
+    it("should return presence", async () => {
+        const user = User.createUser("userId", matrixClient);
+        const roomMember = new RoomMember(room.roomId, "userId");
+        roomMember.user = user;
+        jest.spyOn(PresenceIndicatorModule, "useDmMember").mockReturnValue(roomMember);
+        jest.spyOn(PresenceIndicatorModule, "usePresence").mockReturnValue(PresenceIndicatorModule.Presence.Online);
+
+        const { result: vm } = renderHook(() => useRoomAvatarViewModel(room));
+        expect(vm.current.presence).toBe(PresenceIndicatorModule.Presence.Online);
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel-test.tsx b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b1db7bcb19b61151ca67cc131817392af4516531
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel-test.tsx
@@ -0,0 +1,91 @@
+/*
+Copyright 2025 New Vector Ltd.
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { renderHook } from "jest-matrix-react";
+import { act, type SyntheticEvent } from "react";
+
+import { useRoomTopicViewModel } from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardTopicViewModel";
+import { createTestClient, mkStubRoom } from "../../../../test-utils";
+import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
+import { onRoomTopicLinkClick } from "../../../../../src/components/views/elements/RoomTopic";
+
+jest.mock("../../../../../src/components/views/elements/RoomTopic");
+
+describe("RoomSummaryCardTopicViewModel", () => {
+    const client = createTestClient();
+    const mockRoom = mkStubRoom("!room:example.com", "Test Room", client);
+    const mockUserId = "@user:example.com";
+    const mockEvent = { preventDefault: jest.fn(), stopPropagation: jest.fn() } as unknown as SyntheticEvent;
+
+    beforeEach(() => {
+        // Mock room client's getSafeUserId
+        mockRoom.client.getSafeUserId = jest.fn().mockReturnValue(mockUserId);
+        jest.spyOn(defaultDispatcher, "dispatch");
+
+        (onRoomTopicLinkClick as jest.Mock).mockReset();
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    function render() {
+        return renderHook(() => useRoomTopicViewModel(mockRoom));
+    }
+
+    it("should initialize with expanded state", () => {
+        const { result } = render();
+        expect(result.current.expanded).toBe(true);
+    });
+
+    it("should toggle expanded state on click", async () => {
+        const { result } = render();
+
+        await act(() => {
+            result.current.onExpandedClick(mockEvent);
+        });
+
+        expect(result.current.expanded).toBe(false);
+    });
+
+    it("should handle edit click", () => {
+        const { result } = render();
+        result.current.onEditClick(mockEvent);
+        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "open_room_settings" });
+    });
+
+    it("should handle topic link clicks when the target is an anchor element", () => {
+        const { result } = render();
+        const mockAnchorEvent = { target: document.createElement("a") } as unknown as React.MouseEvent<HTMLElement>;
+
+        result.current.onTopicLinkClick(mockAnchorEvent);
+        expect(onRoomTopicLinkClick).toHaveBeenCalledWith(mockAnchorEvent);
+    });
+
+    it("should handle topic link clicks when the target is not an anchor element", () => {
+        const { result } = render();
+        const mockNonAnchorEvent = {
+            target: document.createElement("div"),
+        } as unknown as React.MouseEvent<HTMLElement>;
+
+        result.current.onTopicLinkClick(mockNonAnchorEvent);
+        expect(onRoomTopicLinkClick).not.toHaveBeenCalled();
+    });
+
+    describe("Topic editing permissions", () => {
+        it("should allow editing when user has permission", () => {
+            mockRoom.currentState.maySendStateEvent = jest.fn().mockReturnValue(true);
+            const { result } = render();
+            expect(result.current.canEditTopic).toBe(true);
+        });
+
+        it("should not allow editing when user lacks permission", () => {
+            mockRoom.currentState.maySendStateEvent = jest.fn().mockReturnValue(false);
+            const { result } = render();
+            expect(result.current.canEditTopic).toBe(false);
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0c5745a086029abc01450ce4d4a1ce475587831e
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { act, renderHook, waitFor } from "jest-matrix-react";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+
+import { useRoomSummaryCardViewModel } from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel";
+import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
+import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
+import RoomListStore from "../../../../../src/stores/room-list/RoomListStore";
+import { DefaultTagID } from "../../../../../src/stores/room-list/models";
+import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
+import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
+import Modal from "../../../../../src/Modal";
+import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
+import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
+import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/PollHistoryDialog";
+import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog";
+import { inviteToRoom } from "../../../../../src/utils/room/inviteToRoom";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import * as hooks from "../../../../../src/hooks/useAccountData";
+
+jest.mock("../../../../../src/utils/room/inviteToRoom", () => ({
+    inviteToRoom: jest.fn(),
+}));
+
+describe("useRoomSummaryCardViewModel", () => {
+    let matrixClient: MatrixClient;
+    let room: Room;
+    let permalinkCreator: any;
+    const onSearchCancel = jest.fn();
+
+    beforeEach(() => {
+        matrixClient = stubClient();
+        room = mkStubRoom("roomId", "roomName", matrixClient);
+        permalinkCreator = {};
+
+        DMRoomMap.setShared({
+            getUserIdForRoomId: jest.fn(),
+        } as unknown as DMRoomMap);
+
+        jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]);
+    });
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    function render() {
+        return renderHook(
+            () => useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel),
+            withClientContextRenderOptions(matrixClient),
+        );
+    }
+
+    it("should return correct initial state", () => {
+        const { result } = render();
+
+        expect(result.current.isDirectMessage).toBe(false);
+        expect(result.current.isRoomEncrypted).toBe(false);
+        expect(result.current.isVideoRoom).toBe(false);
+        expect(result.current.isFavorite).toBe(false);
+        expect(result.current.pinCount).toBe(0);
+        expect(result.current.searchInputRef.current).toBe(null);
+    });
+
+    it("should handle room members click", () => {
+        const spy = jest.spyOn(RightPanelStore.instance, "pushCard");
+        const { result } = render();
+
+        result.current.onRoomMembersClick();
+        expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true);
+    });
+
+    it("should handle room settings click", () => {
+        const spy = jest.spyOn(defaultDispatcher, "dispatch");
+        const { result } = render();
+
+        result.current.onRoomSettingsClick(new Event("click"));
+        expect(spy).toHaveBeenCalledWith({ action: "open_room_settings" });
+    });
+
+    it("should handle leave room click", () => {
+        const spy = jest.spyOn(defaultDispatcher, "dispatch");
+        const { result } = render();
+
+        result.current.onLeaveRoomClick();
+        expect(spy).toHaveBeenCalledWith({
+            action: "leave_room",
+            room_id: room.roomId,
+        });
+    });
+
+    it("should handle room threads click", () => {
+        const spy = jest.spyOn(RightPanelStore.instance, "pushCard");
+        const { result } = render();
+
+        result.current.onRoomThreadsClick();
+        expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel }, true);
+    });
+
+    it("should handle room files click", () => {
+        const spy = jest.spyOn(RightPanelStore.instance, "pushCard");
+        const { result } = render();
+
+        result.current.onRoomFilesClick();
+        expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true);
+    });
+
+    it("should handle room extensions click", () => {
+        const spy = jest.spyOn(RightPanelStore.instance, "pushCard");
+        const { result } = render();
+
+        result.current.onRoomExtensionsClick();
+        expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.Extensions }, true);
+    });
+
+    it("should handle room pins click", () => {
+        const spy = jest.spyOn(RightPanelStore.instance, "pushCard");
+        const { result } = render();
+
+        result.current.onRoomPinsClick();
+        expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.PinnedMessages }, true);
+    });
+
+    it("should handle room invite click", () => {
+        const { result } = render();
+
+        result.current.onInviteToRoomClick();
+        expect(inviteToRoom).toHaveBeenCalledWith(room);
+    });
+
+    describe("action that trigger a dialog", () => {
+        let createDialogSpy: jest.SpyInstance;
+
+        beforeEach(() => {
+            createDialogSpy = jest.spyOn(Modal, "createDialog").mockImplementation(() => ({
+                finished: Promise.resolve([false]),
+                close: jest.fn(),
+            }));
+        });
+
+        afterEach(() => {
+            createDialogSpy.mockRestore();
+        });
+
+        it("should handle room export click", async () => {
+            const { result } = render();
+
+            await act(async () => {
+                await result.current.onRoomExportClick();
+            });
+            expect(createDialogSpy).toHaveBeenCalledWith(ExportDialog, { room });
+        });
+
+        it("should handle room poll history click", async () => {
+            const { result } = render();
+
+            await act(async () => {
+                await result.current.onRoomPollHistoryClick();
+            });
+            expect(createDialogSpy).toHaveBeenCalledWith(PollHistoryDialog, {
+                room,
+                matrixClient,
+                permalinkCreator,
+            });
+        });
+
+        it("should handle room report click", async () => {
+            const { result } = render();
+
+            await act(async () => {
+                await result.current.onReportRoomClick();
+            });
+
+            expect(createDialogSpy).toHaveBeenCalledWith(ReportRoomDialog, { roomId: room.roomId });
+        });
+
+        it("should handle share room click", async () => {
+            const { result } = render();
+
+            await act(async () => {
+                await result.current.onShareRoomClick();
+            });
+
+            expect(createDialogSpy).toHaveBeenCalledWith(ShareDialog, {
+                target: room,
+            });
+        });
+    });
+
+    describe("favorite room state", () => {
+        it("should identify favorite rooms", () => {
+            jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([DefaultTagID.Favourite]);
+            const { result } = render();
+
+            expect(result.current.isFavorite).toBe(true);
+        });
+
+        it("should identify non-favorite rooms", () => {
+            jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]);
+            const { result } = render();
+
+            expect(result.current.isFavorite).toBe(false);
+        });
+    });
+
+    describe("direct message state", () => {
+        it("should identify direct message rooms", async () => {
+            // Mock the direct rooms account data
+            const directRoomsList = {
+                "@user:domain.com": [room.roomId],
+            };
+            // Mock the useAccountData hook result
+            jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList);
+
+            const { result } = render();
+
+            await waitFor(() => {
+                expect(result.current.isDirectMessage).toBe(true);
+            });
+        });
+
+        it("should identify non-direct message rooms", async () => {
+            // Mock the direct rooms account data
+            const directRoomsList = {};
+            // Mock the useAccountData hook result
+            jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList);
+
+            const { result } = render();
+
+            await waitFor(() => {
+                expect(result.current.isDirectMessage).toBe(false);
+            });
+        });
+    });
+
+    describe("search input", () => {
+        it("should handle search input escape key", () => {
+            const directRoomsList = {};
+            jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList);
+            const { result } = render();
+            // Create a mock input element and set it as the current ref value
+            const mockInputElement = document.createElement("input");
+            mockInputElement.value = "some search text";
+
+            result.current.searchInputRef.current = mockInputElement;
+
+            const event = {
+                key: "Escape",
+                preventDefault: jest.fn(),
+                stopPropagation: jest.fn(),
+            };
+
+            result.current.onUpdateSearchInput(event as any);
+
+            expect(onSearchCancel).toHaveBeenCalled();
+            expect(mockInputElement?.value).toBe("");
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c5b1210226c83a0fbaf7aa21347200c625e3aa9
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook, waitFor } from "jest-matrix-react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+
+import { createTestClient, mkStubRoom } from "../../../../test-utils";
+import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore";
+import { useMessagePreviewViewModel } from "../../../../../src/components/viewmodels/roomlist/MessagePreviewViewModel";
+
+describe("MessagePreviewViewModel", () => {
+    let room: Room;
+
+    beforeEach(() => {
+        const matrixClient = createTestClient();
+        room = mkStubRoom("roomId", "roomName", matrixClient);
+    });
+
+    it("should do an initial fetch of the message preview", async () => {
+        // Mock the store to return some text.
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => {
+            return { text: "Hello world!" } as MessagePreview;
+        });
+
+        const { result: vm } = renderHook(() => useMessagePreviewViewModel(room));
+
+        // Eventually, vm.message should have the text from the store.
+        await waitFor(() => {
+            expect(vm.current.message).toEqual("Hello world!");
+        });
+    });
+
+    it("should fetch message preview again on update from store", async () => {
+        // Mock the store to return the text in variable message.
+        let message = "Hello World!";
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => {
+            return { text: message } as MessagePreview;
+        });
+        jest.spyOn(MessagePreviewStore, "getPreviewChangedEventName").mockImplementation((room) => {
+            return "UPDATE";
+        });
+
+        const { result: vm } = renderHook(() => useMessagePreviewViewModel(room));
+
+        // Let's assume the message changed.
+        message = "New message!";
+        MessagePreviewStore.instance.emit("UPDATE");
+
+        /// vm.message should be the updated message.
+        await waitFor(() => {
+            expect(vm.current.message).toEqual(message);
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..09109366cf5418802fc440fc249baf1a0ab7f0f7
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook, act } from "jest-matrix-react";
+import { JoinRule, type MatrixClient, type Room, RoomType } from "matrix-js-sdk/src/matrix";
+import { mocked } from "jest-mock";
+import { range } from "lodash";
+
+import { useRoomListHeaderViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel";
+import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
+import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
+import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
+import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../../src/dispatcher/actions";
+import {
+    shouldShowSpaceSettings,
+    showCreateNewRoom,
+    showSpaceInvite,
+    showSpacePreferences,
+    showSpaceSettings,
+} from "../../../../../src/utils/space";
+import { createRoom, hasCreateRoomRights } from "../../../../../src/components/viewmodels/roomlist/utils";
+import { SettingLevel } from "../../../../../src/settings/SettingLevel";
+import RoomListStoreV3 from "../../../../../src/stores/room-list-v3/RoomListStoreV3";
+import { SortOption } from "../../../../../src/components/viewmodels/roomlist/useSorter";
+import { SortingAlgorithm } from "../../../../../src/stores/room-list-v3/skip-list/sorters";
+
+jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
+    hasCreateRoomRights: jest.fn().mockReturnValue(false),
+    createRoom: jest.fn(),
+}));
+
+jest.mock("../../../../../src/utils/space", () => ({
+    shouldShowSpaceSettings: jest.fn(),
+    showCreateNewRoom: jest.fn(),
+    showSpaceInvite: jest.fn(),
+    showSpacePreferences: jest.fn(),
+    showSpaceSettings: jest.fn(),
+}));
+
+describe("useRoomListHeaderViewModel", () => {
+    let matrixClient: MatrixClient;
+    let space: Room;
+
+    beforeEach(() => {
+        matrixClient = stubClient();
+        space = mkStubRoom("spaceId", "spaceName", matrixClient);
+    });
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    function render() {
+        return renderHook(() => useRoomListHeaderViewModel(), withClientContextRenderOptions(matrixClient));
+    }
+
+    describe("title", () => {
+        it("should return Home as title", () => {
+            const { result } = render();
+            expect(result.current.title).toStrictEqual("Home");
+        });
+
+        it("should return the current space name as title", () => {
+            jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+            const { result } = render();
+
+            expect(result.current.title).toStrictEqual("spaceName");
+        });
+    });
+
+    it("should be displayComposeMenu=true and canCreateRoom=true if the user can creates room", () => {
+        mocked(hasCreateRoomRights).mockReturnValue(false);
+        const { result, rerender } = render();
+        expect(result.current.displayComposeMenu).toBe(false);
+        expect(result.current.canCreateRoom).toBe(false);
+
+        mocked(hasCreateRoomRights).mockReturnValue(true);
+        rerender();
+        expect(result.current.displayComposeMenu).toBe(true);
+        expect(result.current.canCreateRoom).toBe(true);
+    });
+
+    it("should be displaySpaceMenu=true if the user is in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        expect(result.current.displaySpaceMenu).toBe(true);
+    });
+
+    it("should be canInviteInSpace=true if the space join rule is public", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        jest.spyOn(space, "getJoinRule").mockReturnValue(JoinRule.Public);
+
+        const { result } = render();
+        expect(result.current.displaySpaceMenu).toBe(true);
+    });
+
+    it("should be canInviteInSpace=true if the user has the right", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        jest.spyOn(space, "canInvite").mockReturnValue(true);
+
+        const { result } = render();
+        expect(result.current.displaySpaceMenu).toBe(true);
+    });
+
+    it("should be canAccessSpaceSettings=true if the user has the right", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        mocked(shouldShowSpaceSettings).mockReturnValue(true);
+
+        const { result } = render();
+        expect(result.current.canAccessSpaceSettings).toBe(true);
+    });
+
+    it("should be canCreateVideoRoom=true if feature_video_rooms is enabled and can create room", () => {
+        mocked(hasCreateRoomRights).mockReturnValue(true);
+        jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
+
+        const { result } = render();
+        expect(result.current.canCreateVideoRoom).toBe(true);
+    });
+
+    it("should fire Action.CreateChat when createChatRoom is called", () => {
+        const spy = jest.spyOn(defaultDispatcher, "fire");
+        const { result } = render();
+        result.current.createChatRoom(new Event("click"));
+
+        expect(spy).toHaveBeenCalledWith(Action.CreateChat);
+    });
+
+    it("should call createRoom from utils when createRoom is called", () => {
+        const { result } = render();
+        result.current.createRoom(new Event("click"));
+
+        expect(createRoom).toHaveBeenCalled();
+    });
+
+    it("should call createRoom from utils when createRoom is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        result.current.createRoom(new Event("click"));
+
+        expect(createRoom).toHaveBeenCalledWith(space);
+    });
+
+    it("should fire Action.CreateRoom with RoomType.UnstableCall when createVideoRoom is called and feature_element_call_video_rooms is enabled", () => {
+        jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
+        const spy = jest.spyOn(defaultDispatcher, "dispatch");
+        const { result } = render();
+        result.current.createVideoRoom();
+
+        expect(spy).toHaveBeenCalledWith({ action: Action.CreateRoom, type: RoomType.UnstableCall });
+    });
+
+    it("should fire Action.CreateRoom with RoomType.ElementVideo when createVideoRoom is called and feature_element_call_video_rooms is disabled", () => {
+        jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
+        const spy = jest.spyOn(defaultDispatcher, "dispatch");
+        const { result } = render();
+        result.current.createVideoRoom();
+
+        expect(spy).toHaveBeenCalledWith({ action: Action.CreateRoom, type: RoomType.ElementVideo });
+    });
+
+    it("should call showCreateNewRoom when createVideoRoom is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        result.current.createVideoRoom();
+
+        expect(showCreateNewRoom).toHaveBeenCalledWith(space, RoomType.ElementVideo);
+    });
+
+    it("should fire Action.ViewRoom when openSpaceHome is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const spy = jest.spyOn(defaultDispatcher, "dispatch");
+        const { result } = render();
+        result.current.openSpaceHome();
+
+        expect(spy).toHaveBeenCalledWith({ action: Action.ViewRoom, room_id: space.roomId, metricsTrigger: undefined });
+    });
+
+    it("should call showSpaceInvite when inviteInSpace is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        result.current.inviteInSpace();
+
+        expect(showSpaceInvite).toHaveBeenCalledWith(space);
+    });
+
+    it("should call showSpacePreferences when openSpacePreferences is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        result.current.openSpacePreferences();
+
+        expect(showSpacePreferences).toHaveBeenCalledWith(space);
+    });
+
+    it("should call showSpaceSettings when openSpaceSettings is called in a space", () => {
+        jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space);
+        const { result } = render();
+        result.current.openSpaceSettings();
+
+        expect(showSpaceSettings).toHaveBeenCalledWith(space);
+    });
+
+    describe("Sorting", () => {
+        function mockAndCreateRooms() {
+            const rooms = range(10).map((i) => mkStubRoom(`foo${i}:matrix.org`, `Foo ${i}`, undefined));
+            const fn = jest
+                .spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace")
+                .mockImplementation(() => [...rooms]);
+            return { rooms, fn };
+        }
+
+        it("should change sort order", () => {
+            mockAndCreateRooms();
+            const { result: vm } = render();
+
+            const resort = jest.spyOn(RoomListStoreV3.instance, "resort").mockImplementation(() => {});
+
+            // Change the sort option
+            act(() => {
+                vm.current.sort(SortOption.AToZ);
+            });
+
+            // Resort method in RLS must have been called
+            expect(resort).toHaveBeenCalledWith(SortingAlgorithm.Alphabetic);
+        });
+
+        it("should set activeSortOption based on value from settings", () => {
+            // Let's say that the user's preferred sorting is alphabetic
+            jest.spyOn(SettingsStore, "getValue").mockImplementation(() => SortingAlgorithm.Alphabetic);
+
+            mockAndCreateRooms();
+            const { result: vm } = render();
+            expect(vm.current.activeSortOption).toEqual(SortOption.AToZ);
+        });
+    });
+
+    describe("message preview toggle", () => {
+        it("should return shouldShowMessagePreview based on setting", () => {
+            jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true);
+            const { result: vm } = render();
+            expect(vm.current.shouldShowMessagePreview).toEqual(true);
+        });
+
+        it("should update when setting changes", async () => {
+            jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true);
+
+            let watchFn: CallbackFn;
+            jest.spyOn(SettingsStore, "watchSetting").mockImplementation((settingName, _roomId, fn) => {
+                if (settingName === "RoomList.showMessagePreview") watchFn = fn;
+                return "";
+            });
+            const { result: vm } = render();
+            expect(vm.current.shouldShowMessagePreview).toEqual(true);
+
+            jest.spyOn(SettingsStore, "getValue").mockImplementation(() => false);
+            act(() => watchFn("RoomList.showMessagePreview", "", SettingLevel.DEVICE, false, false));
+            expect(vm.current.shouldShowMessagePreview).toEqual(false);
+        });
+
+        it("should change setting on toggle", () => {
+            jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true);
+            const fn = jest.spyOn(SettingsStore, "setValue").mockImplementation(async () => {});
+
+            const { result: vm } = render();
+            expect(vm.current.shouldShowMessagePreview).toEqual(true);
+            act(() => vm.current.toggleMessagePreview());
+            expect(fn).toHaveBeenCalledWith("RoomList.showMessagePreview", null, "device", false);
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ddb3cba609ae17cb74b527ce6386d1ea1e3d1218
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook } from "jest-matrix-react";
+import { mocked } from "jest-mock";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+
+import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
+import { useRoomListItemMenuViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel";
+import {
+    hasAccessToNotificationMenu,
+    hasAccessToOptionsMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import { DefaultTagID } from "../../../../../src/stores/room-list/models";
+import { useUnreadNotifications } from "../../../../../src/hooks/useUnreadNotifications";
+import { NotificationLevel } from "../../../../../src/stores/notifications/NotificationLevel";
+import { clearRoomNotification, setMarkedUnreadState } from "../../../../../src/utils/notifications";
+import { tagRoom } from "../../../../../src/utils/room/tagRoom";
+import dispatcher from "../../../../../src/dispatcher/dispatcher";
+import { useNotificationState } from "../../../../../src/hooks/useRoomNotificationState";
+import { RoomNotifState } from "../../../../../src/RoomNotifs";
+
+jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
+    hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
+    hasAccessToNotificationMenu: jest.fn().mockReturnValue(false),
+}));
+
+jest.mock("../../../../../src/hooks/useUnreadNotifications", () => ({
+    useUnreadNotifications: jest.fn(),
+}));
+
+jest.mock("../../../../../src/hooks/useRoomNotificationState", () => ({
+    useNotificationState: jest.fn(),
+}));
+
+jest.mock("../../../../../src/utils/notifications", () => ({
+    clearRoomNotification: jest.fn(),
+    setMarkedUnreadState: jest.fn(),
+}));
+
+jest.mock("../../../../../src/utils/room/tagRoom", () => ({
+    tagRoom: jest.fn(),
+}));
+
+describe("RoomListItemMenuViewModel", () => {
+    let matrixClient: MatrixClient;
+    let room: Room;
+
+    beforeEach(() => {
+        matrixClient = stubClient();
+        room = mkStubRoom("roomId", "roomName", matrixClient);
+
+        DMRoomMap.makeShared(matrixClient);
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+
+        mocked(useUnreadNotifications).mockReturnValue({ symbol: null, count: 0, level: NotificationLevel.None });
+        mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, jest.fn()]);
+        jest.spyOn(dispatcher, "dispatch");
+    });
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    function render() {
+        return renderHook(() => useRoomListItemMenuViewModel(room), withClientContextRenderOptions(matrixClient));
+    }
+
+    it("default", () => {
+        const { result } = render();
+        expect(result.current.showMoreOptionsMenu).toBe(false);
+        expect(result.current.canInvite).toBe(false);
+        expect(result.current.isFavourite).toBe(false);
+        expect(result.current.canCopyRoomLink).toBe(true);
+        expect(result.current.canMarkAsRead).toBe(false);
+        expect(result.current.canMarkAsUnread).toBe(true);
+    });
+
+    it("should has showMoreOptionsMenu to be true", () => {
+        mocked(hasAccessToOptionsMenu).mockReturnValue(true);
+        const { result } = render();
+        expect(result.current.showMoreOptionsMenu).toBe(true);
+    });
+
+    it("should has showNotificationMenu to be true", () => {
+        mocked(hasAccessToNotificationMenu).mockReturnValue(true);
+        const { result } = render();
+        expect(result.current.showNotificationMenu).toBe(true);
+    });
+
+    it("should be able to invite", () => {
+        jest.spyOn(room, "canInvite").mockReturnValue(true);
+        const { result } = render();
+        expect(result.current.canInvite).toBe(true);
+    });
+
+    it("should be a favourite", () => {
+        room.tags = { [DefaultTagID.Favourite]: { order: 0 } };
+        const { result } = render();
+        expect(result.current.isFavourite).toBe(true);
+    });
+
+    it("should not be able to copy the room link", () => {
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue("userId");
+        const { result } = render();
+        expect(result.current.canCopyRoomLink).toBe(false);
+    });
+
+    it("should be able to mark as read", () => {
+        // Add a notification
+        mocked(useUnreadNotifications).mockReturnValue({
+            symbol: null,
+            count: 1,
+            level: NotificationLevel.Notification,
+        });
+        const { result } = render();
+        expect(result.current.canMarkAsRead).toBe(true);
+        expect(result.current.canMarkAsUnread).toBe(false);
+    });
+
+    it("should has isNotificationAllMessage to be true", () => {
+        const { result } = render();
+        expect(result.current.isNotificationAllMessage).toBe(true);
+    });
+
+    it("should has isNotificationAllMessageLoud to be true", () => {
+        mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessagesLoud, jest.fn()]);
+        const { result } = render();
+        expect(result.current.isNotificationAllMessageLoud).toBe(true);
+    });
+
+    it("should has isNotificationMentionOnly to be true", () => {
+        mocked(useNotificationState).mockReturnValue([RoomNotifState.MentionsOnly, jest.fn()]);
+        const { result } = render();
+        expect(result.current.isNotificationMentionOnly).toBe(true);
+    });
+
+    it("should has isNotificationMute to be true", () => {
+        mocked(useNotificationState).mockReturnValue([RoomNotifState.Mute, jest.fn()]);
+        const { result } = render();
+        expect(result.current.isNotificationMute).toBe(true);
+    });
+
+    // Actions
+
+    it("should mark as read", () => {
+        const { result } = render();
+        result.current.markAsRead(new Event("click"));
+        expect(mocked(clearRoomNotification)).toHaveBeenCalledWith(room, matrixClient);
+    });
+
+    it("should mark as unread", () => {
+        const { result } = render();
+        result.current.markAsUnread(new Event("click"));
+        expect(mocked(setMarkedUnreadState)).toHaveBeenCalledWith(room, matrixClient, true);
+    });
+
+    it("should tag a room as favourite", () => {
+        const { result } = render();
+        result.current.toggleFavorite(new Event("click"));
+        expect(mocked(tagRoom)).toHaveBeenCalledWith(room, DefaultTagID.Favourite);
+    });
+
+    it("should tag a room as low priority", () => {
+        const { result } = render();
+        result.current.toggleLowPriority();
+        expect(mocked(tagRoom)).toHaveBeenCalledWith(room, DefaultTagID.LowPriority);
+    });
+
+    it("should dispatch invite action", () => {
+        const { result } = render();
+        result.current.invite(new Event("click"));
+        expect(dispatcher.dispatch).toHaveBeenCalledWith({
+            action: "view_invite",
+            roomId: room.roomId,
+        });
+    });
+
+    it("should dispatch a copy room action", () => {
+        const { result } = render();
+        result.current.copyRoomLink(new Event("click"));
+        expect(dispatcher.dispatch).toHaveBeenCalledWith({
+            action: "copy_room",
+            room_id: room.roomId,
+        });
+    });
+
+    it("should dispatch forget room action", () => {
+        // forget room is only available for archived rooms
+        room.tags = { [DefaultTagID.Archived]: { order: 0 } };
+
+        const { result } = render();
+        result.current.leaveRoom(new Event("click"));
+        expect(dispatcher.dispatch).toHaveBeenCalledWith({
+            action: "forget_room",
+            room_id: room.roomId,
+        });
+    });
+
+    it("should dispatch leave room action", () => {
+        const { result } = render();
+        result.current.leaveRoom(new Event("click"));
+        expect(dispatcher.dispatch).toHaveBeenCalledWith({
+            action: "leave_room",
+            room_id: room.roomId,
+        });
+    });
+
+    it("should call setRoomNotifState", () => {
+        const setRoomNotifState = jest.fn();
+        mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, setRoomNotifState]);
+        const { result } = render();
+        result.current.setRoomNotifState(RoomNotifState.Mute);
+        expect(setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute);
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c58ae4168c1baac25e6e572c2a1551ff4d37655e
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook, waitFor } from "jest-matrix-react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import { mocked } from "jest-mock";
+
+import dispatcher from "../../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../../src/dispatcher/actions";
+import { useRoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel";
+import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils";
+import {
+    hasAccessToNotificationMenu,
+    hasAccessToOptionsMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
+import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
+import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
+import * as UseCallModule from "../../../../../src/hooks/useCall";
+import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import { useMessagePreviewToggle } from "../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle";
+
+jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
+    hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
+    hasAccessToNotificationMenu: jest.fn().mockReturnValue(false),
+}));
+
+jest.mock("../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle", () => ({
+    useMessagePreviewToggle: jest.fn().mockReturnValue({ shouldShowMessagePreview: true }),
+}));
+
+describe("RoomListItemViewModel", () => {
+    let room: Room;
+
+    beforeEach(() => {
+        const matrixClient = createTestClient();
+        room = mkStubRoom("roomId", "roomName", matrixClient);
+
+        const dmRoomMap = {
+            getUserIdForRoomId: jest.fn(),
+            getDMRoomsForUserId: jest.fn(),
+        } as unknown as DMRoomMap;
+        DMRoomMap.setShared(dmRoomMap);
+
+        mocked(useMessagePreviewToggle).mockReturnValue({
+            shouldShowMessagePreview: false,
+            toggleMessagePreview: jest.fn(),
+        });
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should dispatch view room action on openRoom", async () => {
+        const { result: vm } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+
+        const fn = jest.spyOn(dispatcher, "dispatch");
+        vm.current.openRoom();
+        expect(fn).toHaveBeenCalledWith(
+            expect.objectContaining({
+                action: Action.ViewRoom,
+                room_id: room.roomId,
+                metricsTrigger: "RoomList",
+            }),
+        );
+    });
+
+    it("should show hover menu if user has access to options menu", async () => {
+        mocked(hasAccessToOptionsMenu).mockReturnValue(true);
+        const { result: vm } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+        expect(vm.current.showHoverMenu).toBe(true);
+    });
+
+    it("should show hover menu if user has access to notification menu", async () => {
+        mocked(hasAccessToNotificationMenu).mockReturnValue(true);
+        const { result: vm } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+        expect(vm.current.showHoverMenu).toBe(true);
+    });
+
+    it("should not show hover menu if user has an invitation notification", async () => {
+        mocked(hasAccessToOptionsMenu).mockReturnValue(true);
+
+        const notificationState = new RoomNotificationState(room, false);
+        jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState);
+        jest.spyOn(notificationState, "invited", "get").mockReturnValue(false);
+
+        const { result: vm } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+        expect(vm.current.showHoverMenu).toBe(true);
+    });
+
+    it("should return a message preview if one is available and they are enabled", async () => {
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
+            text: "Message look like this",
+        } as MessagePreview);
+        mocked(useMessagePreviewToggle).mockReturnValue({
+            shouldShowMessagePreview: true,
+            toggleMessagePreview: jest.fn(),
+        });
+
+        const { result: vm } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+        await waitFor(() => expect(vm.current.messagePreview).toBe("Message look like this"));
+    });
+
+    it("should hide message previews when disabled", async () => {
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
+            text: "Message look like this",
+        } as MessagePreview);
+
+        const { result: vm, rerender } = renderHook(
+            () => useRoomListItemViewModel(room),
+            withClientContextRenderOptions(room.client),
+        );
+
+        // This doesn't seem to test that the hook actually triggers an update,
+        // but I can't see how to test that.
+        rerender();
+
+        expect(vm.current.messagePreview).toBe(undefined);
+    });
+
+    it("should check message preview when room change", async () => {
+        const otherRoom = mkStubRoom("roomId2", "roomName2", room.client);
+
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({
+            text: "Message look like this",
+        } as MessagePreview);
+        mocked(useMessagePreviewToggle).mockReturnValue({
+            shouldShowMessagePreview: true,
+            toggleMessagePreview: jest.fn(),
+        });
+
+        const { result: vm, rerender } = renderHook((props) => useRoomListItemViewModel(props), {
+            initialProps: room,
+            ...withClientContextRenderOptions(room.client),
+        });
+        await waitFor(() => expect(vm.current.messagePreview).toBe("Message look like this"));
+
+        jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue(null);
+        rerender(otherRoom);
+        await waitFor(() => expect(vm.current.messagePreview).toBe(undefined));
+    });
+
+    describe("notification", () => {
+        let notificationState: RoomNotificationState;
+        beforeEach(() => {
+            notificationState = new RoomNotificationState(room, false);
+            jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState);
+        });
+
+        it("should show notification decoration if there is call has participant", () => {
+            jest.spyOn(UseCallModule, "useParticipantCount").mockReturnValue(1);
+
+            const { result: vm } = renderHook(
+                () => useRoomListItemViewModel(room),
+                withClientContextRenderOptions(room.client),
+            );
+            expect(vm.current.showNotificationDecoration).toBe(true);
+        });
+
+        it.each([
+            {
+                label: "hasAnyNotificationOrActivity",
+                mock: () => jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true),
+            },
+            { label: "muted", mock: () => jest.spyOn(notificationState, "muted", "get").mockReturnValue(true) },
+        ])("should show notification decoration if $label=true", ({ mock }) => {
+            mock();
+            const { result: vm } = renderHook(
+                () => useRoomListItemViewModel(room),
+                withClientContextRenderOptions(room.client),
+            );
+            expect(vm.current.showNotificationDecoration).toBe(true);
+        });
+
+        it("should be bold if there is a notification", () => {
+            jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+
+            const { result: vm } = renderHook(
+                () => useRoomListItemViewModel(room),
+                withClientContextRenderOptions(room.client),
+            );
+            expect(vm.current.isBold).toBe(true);
+        });
+
+        it("should recompute notification state when room changes", () => {
+            const newRoom = mkStubRoom("room2", "Room 2", room.client);
+            const newNotificationState = new RoomNotificationState(newRoom, false);
+
+            const { result, rerender } = renderHook((room) => useRoomListItemViewModel(room), {
+                ...withClientContextRenderOptions(room.client),
+                initialProps: room,
+            });
+
+            expect(result.current.showNotificationDecoration).toBe(false);
+
+            jest.spyOn(newNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+            jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(newNotificationState);
+            rerender(newRoom);
+
+            expect(result.current.showNotificationDecoration).toBe(true);
+        });
+    });
+
+    describe("a11yLabel", () => {
+        let notificationState: RoomNotificationState;
+        beforeEach(() => {
+            notificationState = new RoomNotificationState(room, false);
+            jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState);
+        });
+
+        it.each([
+            {
+                label: "unsent message",
+                mock: () => jest.spyOn(notificationState, "isUnsentMessage", "get").mockReturnValue(true),
+                expected: "Open room roomName with an unsent message.",
+            },
+            {
+                label: "invitation",
+                mock: () => jest.spyOn(notificationState, "invited", "get").mockReturnValue(true),
+                expected: "Open room roomName invitation.",
+            },
+            {
+                label: "mention",
+                mock: () => {
+                    jest.spyOn(notificationState, "isMention", "get").mockReturnValue(true);
+                    jest.spyOn(notificationState, "count", "get").mockReturnValue(3);
+                },
+                expected: "Open room roomName with 3 unread messages including mentions.",
+            },
+            {
+                label: "unread",
+                mock: () => {
+                    jest.spyOn(notificationState, "hasUnreadCount", "get").mockReturnValue(true);
+                    jest.spyOn(notificationState, "count", "get").mockReturnValue(3);
+                },
+                expected: "Open room roomName with 3 unread messages.",
+            },
+            {
+                label: "default",
+                expected: "Open room roomName",
+            },
+        ])("should return the $label label", ({ mock, expected }) => {
+            mock?.();
+            const { result: vm } = renderHook(
+                () => useRoomListItemViewModel(room),
+                withClientContextRenderOptions(room.client),
+            );
+            expect(vm.current.a11yLabel).toBe(expected);
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3f4337608e645b9ff49573d16644d8b596e2799f
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx
@@ -0,0 +1,333 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { range } from "lodash";
+import { act, renderHook, waitFor } from "jest-matrix-react";
+import { mocked } from "jest-mock";
+
+import RoomListStoreV3, { LISTS_UPDATE_EVENT } from "../../../../../src/stores/room-list-v3/RoomListStoreV3";
+import { mkStubRoom } from "../../../../test-utils";
+import { useRoomListViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
+import { FilterKey } from "../../../../../src/stores/room-list-v3/skip-list/filters";
+import { hasCreateRoomRights, createRoom } from "../../../../../src/components/viewmodels/roomlist/utils";
+import dispatcher from "../../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../../src/dispatcher/actions";
+import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
+import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
+import { UPDATE_SELECTED_SPACE } from "../../../../../src/stores/spaces";
+
+jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
+    hasCreateRoomRights: jest.fn().mockReturnValue(false),
+    createRoom: jest.fn(),
+}));
+
+describe("RoomListViewModel", () => {
+    function mockAndCreateRooms() {
+        const rooms = range(10).map((i) => mkStubRoom(`foo${i}:matrix.org`, `Foo ${i}`, undefined));
+        const fn = jest
+            .spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace")
+            .mockImplementation(() => [...rooms]);
+        return { rooms, fn };
+    }
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should return a list of rooms", async () => {
+        const { rooms } = mockAndCreateRooms();
+        const { result: vm } = renderHook(() => useRoomListViewModel());
+
+        expect(vm.current.rooms).toHaveLength(10);
+        for (const room of rooms) {
+            expect(vm.current.rooms).toContain(room);
+        }
+    });
+
+    it("should update list of rooms on event from room list store", async () => {
+        const { rooms } = mockAndCreateRooms();
+        const { result: vm } = renderHook(() => useRoomListViewModel());
+
+        const newRoom = mkStubRoom("bar:matrix.org", "Bar", undefined);
+        rooms.push(newRoom);
+        await act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+
+        await waitFor(() => {
+            expect(vm.current.rooms).toContain(newRoom);
+        });
+    });
+
+    describe("Filters", () => {
+        it("should provide list of available filters", () => {
+            mockAndCreateRooms();
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            // should have 6 filters
+            expect(vm.current.primaryFilters).toHaveLength(6);
+            // check the order
+            for (const [i, name] of ["Unreads", "People", "Rooms", "Mentions", "Invites", "Favourites"].entries()) {
+                expect(vm.current.primaryFilters[i].name).toEqual(name);
+                expect(vm.current.primaryFilters[i].active).toEqual(false);
+            }
+        });
+
+        it("should get filtered rooms from RLS on toggle", () => {
+            const { fn } = mockAndCreateRooms();
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            // Let's say we toggle the People toggle
+            const i = vm.current.primaryFilters.findIndex((f) => f.name === "People");
+            act(() => {
+                vm.current.primaryFilters[i].toggle();
+            });
+            expect(fn).toHaveBeenCalledWith([FilterKey.PeopleFilter]);
+            expect(vm.current.primaryFilters[i].active).toEqual(true);
+        });
+
+        it("should change active property on toggle", () => {
+            mockAndCreateRooms();
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            // Let's say we toggle the People filter
+            const i = vm.current.primaryFilters.findIndex((f) => f.name === "People");
+            expect(vm.current.primaryFilters[i].active).toEqual(false);
+            act(() => {
+                vm.current.primaryFilters[i].toggle();
+            });
+            expect(vm.current.primaryFilters[i].active).toEqual(true);
+
+            // Let's say that we toggle the Favourite filter
+            const j = vm.current.primaryFilters.findIndex((f) => f.name === "Favourites");
+            act(() => {
+                vm.current.primaryFilters[j].toggle();
+            });
+            expect(vm.current.primaryFilters[i].active).toEqual(false);
+            expect(vm.current.primaryFilters[j].active).toEqual(true);
+        });
+
+        it("should return the current active primary filter", async () => {
+            // Let's say that the user's preferred sorting is alphabetic
+            mockAndCreateRooms();
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            // Toggle people filter
+            const i = vm.current.primaryFilters.findIndex((f) => f.name === "People");
+            expect(vm.current.primaryFilters[i].active).toEqual(false);
+            act(() => vm.current.primaryFilters[i].toggle());
+
+            // The active primary filter should be the People filter
+            expect(vm.current.activePrimaryFilter).toEqual(vm.current.primaryFilters[i]);
+        });
+
+        it("should remove all filters when active space is changed", async () => {
+            mockAndCreateRooms();
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+
+            // Let's first toggle the People filter
+            const i = vm.current.primaryFilters.findIndex((f) => f.name === "People");
+            act(() => {
+                vm.current.primaryFilters[i].toggle();
+            });
+            expect(vm.current.primaryFilters[i].active).toEqual(true);
+
+            // Simulate a space change
+            await act(() => SpaceStore.instance.emit(UPDATE_SELECTED_SPACE));
+
+            // Primary filer should have been unapplied
+            expect(vm.current.activePrimaryFilter).toEqual(undefined);
+        });
+    });
+
+    describe("Create room and chat", () => {
+        it("should be canCreateRoom=false if hasCreateRoomRights=false", () => {
+            mocked(hasCreateRoomRights).mockReturnValue(false);
+            const { result } = renderHook(() => useRoomListViewModel());
+            expect(result.current.canCreateRoom).toBe(false);
+        });
+
+        it("should be canCreateRoom=true if hasCreateRoomRights=true", () => {
+            mocked(hasCreateRoomRights).mockReturnValue(true);
+            const { result } = renderHook(() => useRoomListViewModel());
+            expect(result.current.canCreateRoom).toBe(true);
+        });
+
+        it("should call createRoom", () => {
+            const { result } = renderHook(() => useRoomListViewModel());
+            result.current.createRoom();
+            expect(mocked(createRoom)).toHaveBeenCalled();
+        });
+
+        it("should dispatch Action.CreateChat", () => {
+            const spy = jest.spyOn(dispatcher, "fire");
+            const { result } = renderHook(() => useRoomListViewModel());
+            result.current.createChatRoom();
+            expect(spy).toHaveBeenCalledWith(Action.CreateChat);
+        });
+    });
+
+    describe("Sticky room and active index", () => {
+        function expectActiveRoom(vm: ReturnType<typeof useRoomListViewModel>, i: number, roomId: string) {
+            expect(vm.activeIndex).toEqual(i);
+            expect(vm.rooms[i].roomId).toEqual(roomId);
+        }
+
+        it("active index is calculated with the last opened room in a space", () => {
+            // Let's say there's two spaces: !space1:matrix.org and !space2:matrix.org
+            // Let's also say that the current active space is !space1:matrix.org
+            let currentSpace = "!space1:matrix.org";
+            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => currentSpace);
+
+            const rooms = range(10).map((i) => mkStubRoom(`foo${i}:matrix.org`, `Foo ${i}`, undefined));
+            // Let's say all the rooms are in space1
+            const roomsInSpace1 = [...rooms];
+            // Let's say all rooms with even index are in space 2
+            const roomsInSpace2 = [...rooms].filter((_, i) => i % 2 === 0);
+            jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockImplementation(() =>
+                currentSpace === "!space1:matrix.org" ? roomsInSpace1 : roomsInSpace2,
+            );
+
+            // Let's say that the room at index 4 is currently active
+            const roomId = rooms[4].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expect(vm.current.activeIndex).toEqual(4);
+
+            // Let's say that space is changed to "!space2:matrix.org"
+            currentSpace = "!space2:matrix.org";
+            // Let's say that room[6] is active in space 2
+            const activeRoomIdInSpace2 = rooms[6].roomId;
+            jest.spyOn(SpaceStore.instance, "getLastSelectedRoomIdForSpace").mockImplementation(
+                () => activeRoomIdInSpace2,
+            );
+            act(() => {
+                RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT);
+            });
+
+            // Active index should be 3 even without the room change event.
+            expectActiveRoom(vm.current, 3, activeRoomIdInSpace2);
+        });
+
+        it("active room and active index are retained on order change", () => {
+            const { rooms } = mockAndCreateRooms();
+
+            // Let's say that the room at index 5 is active
+            const roomId = rooms[5].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expect(vm.current.activeIndex).toEqual(5);
+
+            // Let's say that room at index 9 moves to index 5
+            const room9 = rooms[9];
+            rooms.splice(9, 1);
+            rooms.splice(5, 0, room9);
+            act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+
+            // Active room index should still be 5
+            expectActiveRoom(vm.current, 5, roomId);
+
+            // Let's add 2 new rooms from index 0
+            const newRoom1 = mkStubRoom("bar1:matrix.org", "Bar 1", undefined);
+            const newRoom2 = mkStubRoom("bar2:matrix.org", "Bar 2", undefined);
+            rooms.unshift(newRoom1, newRoom2);
+            act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+
+            // Active room index should still be 5
+            expectActiveRoom(vm.current, 5, roomId);
+        });
+
+        it("active room and active index are updated when another room is opened", () => {
+            const { rooms } = mockAndCreateRooms();
+            const roomId = rooms[5].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expectActiveRoom(vm.current, 5, roomId);
+
+            // Let's say that room at index 9 becomes active
+            const room = rooms[9];
+            act(() => {
+                dispatcher.dispatch(
+                    {
+                        action: Action.ActiveRoomChanged,
+                        oldRoomId: null,
+                        newRoomId: room.roomId,
+                    },
+                    true,
+                );
+            });
+
+            // Active room index should change to reflect new room
+            expectActiveRoom(vm.current, 9, room.roomId);
+        });
+
+        it("active room and active index are updated when active index spills out of rooms array bounds", () => {
+            const { rooms } = mockAndCreateRooms();
+            // Let's say that the room at index 5 is active
+            const roomId = rooms[5].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expectActiveRoom(vm.current, 5, roomId);
+
+            // Let's say that we remove rooms from the start of the array
+            for (let i = 0; i < 4; ++i) {
+                // We should be able to do 4 deletions before we run out of rooms
+                rooms.splice(0, 1);
+                act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+                expectActiveRoom(vm.current, 5, roomId);
+            }
+
+            // If we remove one more room from the start, there's not going to be enough rooms
+            // to maintain the active index.
+            rooms.splice(0, 1);
+            act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+            expectActiveRoom(vm.current, 0, roomId);
+        });
+
+        it("active room and active index are retained when rooms that appear after the active room are deleted", () => {
+            const { rooms } = mockAndCreateRooms();
+            // Let's say that the room at index 5 is active
+            const roomId = rooms[5].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expectActiveRoom(vm.current, 5, roomId);
+
+            // Let's say that we remove rooms from the start of the array
+            for (let i = 0; i < 4; ++i) {
+                // Deleting rooms after index 5 (active) should not update the active index
+                rooms.splice(6, 1);
+                act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+                expectActiveRoom(vm.current, 5, roomId);
+            }
+        });
+
+        it("active room index becomes undefined when active room is deleted", () => {
+            const { rooms } = mockAndCreateRooms();
+            // Let's say that the room at index 5 is active
+            let roomId: string | undefined = rooms[5].roomId;
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expectActiveRoom(vm.current, 5, roomId);
+
+            // Let's remove the active room (i.e room at index 5)
+            rooms.splice(5, 1);
+            roomId = undefined;
+            act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
+            expect(vm.current.activeIndex).toBeUndefined();
+        });
+
+        it("active room index is initially undefined", () => {
+            mockAndCreateRooms();
+
+            // Let's say that there's no active room currently
+            jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => undefined);
+
+            const { result: vm } = renderHook(() => useRoomListViewModel());
+            expect(vm.current.activeIndex).toEqual(undefined);
+        });
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts b/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c82f9aa87d681fdaf8ff374fdbe8467e2bb513fb
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { renderHook } from "jest-matrix-react";
+import { type Room } from "matrix-js-sdk/src/matrix";
+import { waitFor } from "@testing-library/dom";
+
+import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
+import dispatcher from "../../../../../src/dispatcher/dispatcher";
+import { mkStubRoom, stubClient } from "../../../../test-utils";
+import { useRoomListNavigation } from "../../../../../src/components/viewmodels/roomlist/useRoomListNavigation";
+import { Action } from "../../../../../src/dispatcher/actions";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
+import { type RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
+
+describe("useRoomListNavigation", () => {
+    let rooms: Room[];
+
+    beforeEach(() => {
+        const matrixClient = stubClient();
+        rooms = [
+            mkStubRoom("room1", "Room 1", matrixClient),
+            mkStubRoom("room2", "Room 2", matrixClient),
+            mkStubRoom("room3", "Room 3", matrixClient),
+        ];
+
+        DMRoomMap.makeShared(matrixClient);
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+        jest.spyOn(dispatcher, "dispatch");
+    });
+
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should navigate to the next room based on delta", async () => {
+        jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1");
+
+        renderHook(() => useRoomListNavigation(rooms));
+        dispatcher.dispatch({
+            action: Action.ViewRoomDelta,
+            delta: 1,
+            unread: false,
+        });
+
+        await waitFor(() =>
+            expect(dispatcher.dispatch).toHaveBeenCalledWith({
+                action: Action.ViewRoom,
+                room_id: "room2",
+                show_room_tile: true,
+                metricsTrigger: "WebKeyboardShortcut",
+                metricsViaKeyboard: true,
+            }),
+        );
+    });
+
+    it("should navigate to the previous room based on delta", async () => {
+        jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room2");
+
+        renderHook(() => useRoomListNavigation(rooms));
+        dispatcher.dispatch({
+            action: Action.ViewRoomDelta,
+            delta: -1,
+            unread: false,
+        });
+
+        await waitFor(() =>
+            expect(dispatcher.dispatch).toHaveBeenCalledWith({
+                action: Action.ViewRoom,
+                room_id: "room1",
+                show_room_tile: true,
+                metricsTrigger: "WebKeyboardShortcut",
+                metricsViaKeyboard: true,
+            }),
+        );
+    });
+
+    it("should wrap around to the first room when navigating past the last room", async () => {
+        jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room3");
+
+        renderHook(() => useRoomListNavigation(rooms));
+        dispatcher.dispatch({
+            action: Action.ViewRoomDelta,
+            delta: 1,
+            unread: false,
+        });
+
+        await waitFor(() =>
+            expect(dispatcher.dispatch).toHaveBeenCalledWith({
+                action: Action.ViewRoom,
+                room_id: "room1",
+                show_room_tile: true,
+                metricsTrigger: "WebKeyboardShortcut",
+                metricsViaKeyboard: true,
+            }),
+        );
+    });
+
+    it("should wrap around to the last room when navigating before the first room", async () => {
+        jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1");
+
+        renderHook(() => useRoomListNavigation(rooms));
+        dispatcher.dispatch({
+            action: Action.ViewRoomDelta,
+            delta: -1,
+            unread: false,
+        });
+
+        await waitFor(() =>
+            expect(dispatcher.dispatch).toHaveBeenCalledWith({
+                action: Action.ViewRoom,
+                room_id: "room3",
+                show_room_tile: true,
+                metricsTrigger: "WebKeyboardShortcut",
+                metricsViaKeyboard: true,
+            }),
+        );
+    });
+
+    it("should filter rooms to only unread when unread=true", async () => {
+        jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1");
+        jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation(
+            (room) =>
+                ({
+                    isUnread: room.roomId !== "room1",
+                }) as RoomNotificationState,
+        );
+
+        renderHook(() => useRoomListNavigation(rooms));
+
+        dispatcher.dispatch({
+            action: Action.ViewRoomDelta,
+            delta: 1,
+            unread: true,
+        });
+
+        await waitFor(() =>
+            expect(dispatcher.dispatch).toHaveBeenCalledWith({
+                action: Action.ViewRoom,
+                room_id: "room2",
+                show_room_tile: true,
+                metricsTrigger: "WebKeyboardShortcut",
+                metricsViaKeyboard: true,
+            }),
+        );
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/roomlist/utils-test.ts b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..322d2a5cc60d3d771c90a5f1184da05d102286b7
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { mocked } from "jest-mock";
+
+import type { MatrixClient, Room, RoomState } from "matrix-js-sdk/src/matrix";
+import { createTestClient, mkStubRoom } from "../../../../test-utils";
+import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
+import {
+    hasCreateRoomRights,
+    createRoom,
+    hasAccessToNotificationMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
+import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../../src/dispatcher/actions";
+import { showCreateNewRoom } from "../../../../../src/utils/space";
+
+jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
+    shouldShowComponent: jest.fn(),
+}));
+
+jest.mock("../../../../../src/utils/space", () => ({
+    showCreateNewRoom: jest.fn(),
+}));
+
+describe("utils", () => {
+    let matrixClient: MatrixClient;
+    let space: Room;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+        space = mkStubRoom("spaceId", "spaceName", matrixClient);
+    });
+
+    describe("createRoom", () => {
+        it("should fire Action.CreateRoom when createRoom is called without a space", async () => {
+            const spy = jest.spyOn(defaultDispatcher, "fire");
+            await createRoom();
+
+            expect(spy).toHaveBeenCalledWith(Action.CreateRoom);
+        });
+
+        it("should call showCreateNewRoom when createRoom is called in a space", async () => {
+            await createRoom(space);
+            expect(showCreateNewRoom).toHaveBeenCalledWith(space);
+        });
+    });
+
+    describe("hasCreateRoomRights", () => {
+        it("should return false when UIComponent.CreateRooms is disabled", () => {
+            mocked(shouldShowComponent).mockReturnValue(false);
+            expect(hasCreateRoomRights(matrixClient, space)).toBe(false);
+        });
+
+        it("should return true when UIComponent.CreateRooms is enabled and no space", () => {
+            mocked(shouldShowComponent).mockReturnValue(true);
+            expect(hasCreateRoomRights(matrixClient)).toBe(true);
+        });
+
+        it("should return false in space when UIComponent.CreateRooms is enabled and the user doesn't have the rights", () => {
+            mocked(shouldShowComponent).mockReturnValue(true);
+            jest.spyOn(space.getLiveTimeline(), "getState").mockReturnValue({
+                maySendStateEvent: jest.fn().mockReturnValue(true),
+            } as unknown as RoomState);
+
+            expect(hasCreateRoomRights(matrixClient)).toBe(true);
+        });
+    });
+
+    it("hasAccessToNotificationMenu", () => {
+        mocked(shouldShowComponent).mockReturnValue(true);
+        const room = mkStubRoom("roomId", "roomName", matrixClient);
+        const isGuest = false;
+        const isArchived = false;
+
+        expect(hasAccessToNotificationMenu(room, isGuest, isArchived)).toBe(true);
+    });
+});
diff --git a/test/unit-tests/components/viewmodels/settings/encryption/KeyStoragePanelViewModel-test.ts b/test/unit-tests/components/viewmodels/settings/encryption/KeyStoragePanelViewModel-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0132c731792551b479ad9cfbf8ddf13e02fc666
--- /dev/null
+++ b/test/unit-tests/components/viewmodels/settings/encryption/KeyStoragePanelViewModel-test.ts
@@ -0,0 +1,94 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { renderHook } from "jest-matrix-react";
+import { act } from "react";
+import { mocked } from "jest-mock";
+
+import type { MatrixClient } from "matrix-js-sdk/src/matrix";
+import type { KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { useKeyStoragePanelViewModel } from "../../../../../../src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel";
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+
+describe("KeyStoragePanelViewModel", () => {
+    let matrixClient: MatrixClient;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should update the pending value immediately", async () => {
+        const { result } = renderHook(
+            () => useKeyStoragePanelViewModel(),
+            withClientContextRenderOptions(matrixClient),
+        );
+        act(() => {
+            result.current.setEnabled(true);
+        });
+        expect(result.current.isEnabled).toBe(true);
+        expect(result.current.busy).toBe(true);
+    });
+
+    it("should call resetKeyBackup if there is no backup currently", async () => {
+        mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);
+
+        const { result } = renderHook(
+            () => useKeyStoragePanelViewModel(),
+            withClientContextRenderOptions(matrixClient),
+        );
+
+        await result.current.setEnabled(true);
+        expect(mocked(matrixClient.getCrypto()!.resetKeyBackup)).toHaveBeenCalled();
+    });
+
+    it("should not call resetKeyBackup if there is a backup currently", async () => {
+        mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue({} as KeyBackupCheck);
+
+        const { result } = renderHook(
+            () => useKeyStoragePanelViewModel(),
+            withClientContextRenderOptions(matrixClient),
+        );
+
+        await result.current.setEnabled(true);
+        expect(mocked(matrixClient.getCrypto()!.resetKeyBackup)).not.toHaveBeenCalled();
+    });
+
+    it("should set account data flag when enabling", async () => {
+        mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);
+
+        const { result } = renderHook(
+            () => useKeyStoragePanelViewModel(),
+            withClientContextRenderOptions(matrixClient),
+        );
+
+        await result.current.setEnabled(true);
+        expect(mocked(matrixClient.setAccountData)).toHaveBeenCalledWith("m.org.matrix.custom.backup_disabled", {
+            disabled: false,
+        });
+    });
+
+    it("should delete key storage when disabling", async () => {
+        mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue({} as KeyBackupCheck);
+        mocked(matrixClient.getCrypto()!.getKeyBackupInfo).mockResolvedValue({ version: "99" } as KeyBackupInfo);
+
+        const { result } = renderHook(
+            () => useKeyStoragePanelViewModel(),
+            withClientContextRenderOptions(matrixClient),
+        );
+
+        await result.current.setEnabled(false);
+
+        expect(mocked(matrixClient.getCrypto()!.disableKeyStorage)).toHaveBeenCalled();
+        expect(mocked(matrixClient.setAccountData)).toHaveBeenCalledWith("m.org.matrix.custom.backup_disabled", {
+            disabled: true,
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/VerificationShowSas-test.tsx b/test/unit-tests/components/views/VerificationShowSas-test.tsx
index 3e29e2098c9d399766ccaa2184d60fb152258be0..f9312e906e4d2b2e8d8786b21551434d8f736bce 100644
--- a/test/unit-tests/components/views/VerificationShowSas-test.tsx
+++ b/test/unit-tests/components/views/VerificationShowSas-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EmojiMapping } from "matrix-js-sdk/src/crypto-api";
+import { type EmojiMapping } from "matrix-js-sdk/src/crypto-api";
 
 import { tEmoji } from "../../../../src/components/views/verification/VerificationShowSas";
 
diff --git a/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx b/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx
index 6c96aacb8e83829a60d8394909684c750b7bbe26..243fea38b29a1aff03feffbb4b6724b73c9ea9e2 100644
--- a/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx
+++ b/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { mocked } from "jest-mock";
 import { logger } from "matrix-js-sdk/src/logger";
-import { fireEvent, render, RenderResult } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult } from "jest-matrix-react";
 
 import RecordingPlayback, {
     PlaybackLayout,
@@ -18,7 +18,7 @@ import { Playback } from "../../../../../src/audio/Playback";
 import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
 import { createAudioContext } from "../../../../../src/audio/compat";
 import { flushPromises } from "../../../../test-utils";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
 
 jest.mock("../../../../../src/WorkerManager", () => ({
@@ -65,7 +65,7 @@ describe("<RecordingPlayback />", () => {
     beforeEach(() => {
         jest.spyOn(logger, "error").mockRestore();
         mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
-        mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
+        mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer);
         mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
     });
 
diff --git a/test/unit-tests/components/views/audio_messages/SeekBar-test.tsx b/test/unit-tests/components/views/audio_messages/SeekBar-test.tsx
index 95aaa7ada8893069b020de3a8742de4dcdefa6a9..b723ccb1c446f319ea77814b890a685958a98e39 100644
--- a/test/unit-tests/components/views/audio_messages/SeekBar-test.tsx
+++ b/test/unit-tests/components/views/audio_messages/SeekBar-test.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, RefObject } from "react";
+import React, { createRef, type RefObject } from "react";
 import { mocked } from "jest-mock";
-import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
+import { act, fireEvent, render, type RenderResult } from "jest-matrix-react";
 
-import { Playback } from "../../../../../src/audio/Playback";
+import { type Playback } from "../../../../../src/audio/Playback";
 import { createTestPlayback } from "../../../../test-utils/audio";
 import SeekBar from "../../../../../src/components/views/audio_messages/SeekBar";
 
@@ -18,7 +18,7 @@ describe("SeekBar", () => {
     let playback: Playback;
     let renderResult: RenderResult;
     let frameRequestCallback: FrameRequestCallback;
-    let seekBarRef: RefObject<SeekBar>;
+    let seekBarRef: RefObject<SeekBar | null>;
 
     beforeEach(() => {
         seekBarRef = createRef();
diff --git a/test/unit-tests/components/views/auth/AuthFooter-test.tsx b/test/unit-tests/components/views/auth/AuthFooter-test.tsx
index f92a304299a14c7903b2ca8cb00b07bd750d24d0..e1d5260cd0fe4b48f27d6db88157c244a7d78983 100644
--- a/test/unit-tests/components/views/auth/AuthFooter-test.tsx
+++ b/test/unit-tests/components/views/auth/AuthFooter-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { render } from "jest-matrix-react";
 
 import AuthFooter from "../../../../../src/components/views/auth/AuthFooter";
diff --git a/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx b/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx
index 706e4d5d396cee9f48181a7623ab9df53146c8b3..d0cb9ad8b2f6959486e019678079091cec0cc986 100644
--- a/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx
+++ b/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { render } from "jest-matrix-react";
 
 import AuthHeaderLogo from "../../../../../src/components/views/auth/AuthHeaderLogo";
diff --git a/test/unit-tests/components/views/auth/AuthPage-test.tsx b/test/unit-tests/components/views/auth/AuthPage-test.tsx
index 543342661e4931c624ed9573650c6d708d69d08f..1c5b8569468d1fe0b071fa6313b5ed9e81180c70 100644
--- a/test/unit-tests/components/views/auth/AuthPage-test.tsx
+++ b/test/unit-tests/components/views/auth/AuthPage-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { render } from "jest-matrix-react";
 
 import AuthPage from "../../../../../src/components/views/auth/AuthPage";
diff --git a/test/unit-tests/components/views/auth/InteractiveAuthEntryComponents-test.tsx b/test/unit-tests/components/views/auth/InteractiveAuthEntryComponents-test.tsx
index 41408edd1989f38254937056f2d833205c318110..485f8e077b3634ba0253d80af786c8e2e71bf11e 100644
--- a/test/unit-tests/components/views/auth/InteractiveAuthEntryComponents-test.tsx
+++ b/test/unit-tests/components/views/auth/InteractiveAuthEntryComponents-test.tsx
@@ -10,10 +10,12 @@ import React from "react";
 import { render, screen, waitFor, act, fireEvent } from "jest-matrix-react";
 import { AuthType } from "matrix-js-sdk/src/interactive-auth";
 import userEvent from "@testing-library/user-event";
+import { type Policy } from "matrix-js-sdk/src/matrix";
 
 import {
     EmailIdentityAuthEntry,
     MasUnlockCrossSigningAuthEntry,
+    TermsAuthEntry,
 } from "../../../../../src/components/views/auth/InteractiveAuthEntryComponents";
 import { createTestClient } from "../../../../test-utils";
 
@@ -29,7 +31,6 @@ describe("<EmailIdentityAuthEntry/>", () => {
                 submitAuthDict={jest.fn()}
                 fail={jest.fn()}
                 clientSecret="my secret"
-                showContinue={true}
                 inputs={{ emailAddress: "alice@example.xyz" }}
             />,
         );
@@ -71,7 +72,6 @@ describe("<MasUnlockCrossSigningAuthEntry/>", () => {
                 submitAuthDict={jest.fn()}
                 fail={jest.fn()}
                 clientSecret="my secret"
-                showContinue={true}
                 stageParams={{ url: "https://example.com" }}
                 {...props}
             />,
@@ -87,7 +87,7 @@ describe("<MasUnlockCrossSigningAuthEntry/>", () => {
         const spy = jest.spyOn(global.window, "open");
         renderAuth();
 
-        fireEvent.click(screen.getByRole("button", { name: "Go to your account" }));
+        fireEvent.click(screen.getByRole("button", { name: "Continue to account" }));
         expect(spy).toHaveBeenCalledWith("https://example.com", "_blank");
     });
 
@@ -99,3 +99,37 @@ describe("<MasUnlockCrossSigningAuthEntry/>", () => {
         expect(submitAuthDict).toHaveBeenCalledWith({});
     });
 });
+
+describe("<TermsAuthEntry/>", () => {
+    const renderAuth = (policy: Policy, props = {}) => {
+        const matrixClient = createTestClient();
+
+        return render(
+            <TermsAuthEntry
+                matrixClient={matrixClient}
+                loginType={AuthType.Email}
+                onPhaseChange={jest.fn()}
+                submitAuthDict={jest.fn()}
+                fail={jest.fn()}
+                clientSecret="my secret"
+                stageParams={{
+                    policies: {
+                        test_policy: policy,
+                    },
+                }}
+                {...props}
+            />,
+        );
+    };
+
+    test("should render", () => {
+        const { container } = renderAuth({
+            version: "alpha",
+            en: {
+                name: "Test Policy",
+                url: "https://example.com/en",
+            },
+        });
+        expect(container).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/auth/RegistrationToken-test.tsx b/test/unit-tests/components/views/auth/RegistrationToken-test.tsx
index cf9f8268a03425b00e12013387776a8505589f86..1ae42d618888b744f35c9c0b5484790d12013a26 100644
--- a/test/unit-tests/components/views/auth/RegistrationToken-test.tsx
+++ b/test/unit-tests/components/views/auth/RegistrationToken-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult } from "jest-matrix-react";
 
 import InteractiveAuthComponent from "../../../../../src/components/structures/InteractiveAuth";
 import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap b/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap
index 78c95a945a0e742c85df868985a42edbcf4f6e18..17f61abcbbfc86c5daa77a309a85b7dd149097bb 100644
--- a/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap
+++ b/test/unit-tests/components/views/auth/__snapshots__/InteractiveAuthEntryComponents-test.tsx.snap
@@ -35,18 +35,45 @@ exports[`<EmailIdentityAuthEntry/> should render 1`] = `
 
 exports[`<MasUnlockCrossSigningAuthEntry/> should render 1`] = `
 <div>
-  <div>
-    <p
-      class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
     >
-      Reset your identity through your account provider and then come back and click “Retry”.
-    </p>
+      <div
+        class="_content_o77nw_8"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 15q-1.65 0-2.825-1.175T8 11t1.175-2.825T12 7t2.825 1.175T16 11t-1.175 2.825T12 15"
+          />
+          <path
+            d="M19.528 18.583A9.96 9.96 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.98 9.98 0 0 0 12 22a9.98 9.98 0 0 0 7.528-3.417M8.75 16.388q-1.373.332-2.709.95a8 8 0 1 1 11.918 0 14.7 14.7 0 0 0-2.709-.95A13.8 13.8 0 0 0 12 16q-1.65 0-3.25.387"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Go to your account to reset your identity
+      </h2>
+      <span>
+        You're about to go to your matrix.org account to reset your identity. Once you have completed reset on your account, please return here and click Retry.
+      </span>
+    </div>
     <div
-      class="mx_Flex"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+      class="mx_EncryptionCard_buttons"
     >
       <button
-        class="_button_i91xf_17 mx_Dialog_nonDialogButton _has-icon_i91xf_66"
+        class="_button_vczzf_8 mx_Dialog_nonDialogButton _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -61,17 +88,17 @@ exports[`<MasUnlockCrossSigningAuthEntry/> should render 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"
+            d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2"
           />
           <path
-            d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2Z"
+            d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2"
           />
         </svg>
-        Go to your account
+        Continue to account
       </button>
       <button
-        class="_button_i91xf_17 mx_Dialog_nonDialogButton"
-        data-kind="secondary"
+        class="_button_vczzf_8 mx_Dialog_nonDialogButton"
+        data-kind="tertiary"
         data-size="lg"
         role="button"
         tabindex="0"
@@ -82,3 +109,38 @@ exports[`<MasUnlockCrossSigningAuthEntry/> should render 1`] = `
   </div>
 </div>
 `;
+
+exports[`<TermsAuthEntry/> should render 1`] = `
+<div>
+  <div
+    class="mx_InteractiveAuthEntryComponents"
+  >
+    <p>
+      Please review and accept the policies of this homeserver:
+    </p>
+    <label
+      class="mx_InteractiveAuthEntryComponents_termsPolicy"
+    >
+      <input
+        type="checkbox"
+      />
+      <a
+        href="https://example.com/en"
+        rel="noreferrer noopener"
+        target="_blank"
+      >
+        Test Policy
+      </a>
+    </label>
+    <div
+      aria-disabled="true"
+      class="mx_AccessibleButton mx_InteractiveAuthEntryComponents_termsSubmit mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
+      disabled=""
+      role="button"
+      tabindex="0"
+    >
+      Accept
+    </div>
+  </div>
+</div>
+`;
diff --git a/test/unit-tests/components/views/avatars/DecoratedRoomAvatar-test.tsx b/test/unit-tests/components/views/avatars/DecoratedRoomAvatar-test.tsx
index c1c6a2ce9820ba1606fa4acf5edbba241dd6b83b..e41f068d2174709b578fb4f2e9a7950d54055bc7 100644
--- a/test/unit-tests/components/views/avatars/DecoratedRoomAvatar-test.tsx
+++ b/test/unit-tests/components/views/avatars/DecoratedRoomAvatar-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { render, waitFor } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { JoinRule, MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
+import { JoinRule, type MatrixClient, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 import React from "react";
 import userEvent from "@testing-library/user-event";
 
@@ -79,6 +79,7 @@ describe("DecoratedRoomAvatar", () => {
         } as unknown as DMRoomMap;
         jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
         jest.spyOn(DecoratedRoomAvatar.prototype as any, "getPresenceIcon").mockImplementation(() => "ONLINE");
+        jest.spyOn(room, "getMember").mockReturnValue(new RoomMember(room.roomId, DM_USER_ID));
 
         const { container, asFragment } = renderComponent();
 
diff --git a/test/unit-tests/components/views/avatars/MemberAvatar-test.tsx b/test/unit-tests/components/views/avatars/MemberAvatar-test.tsx
index 1239d7e5adbbd890f6d3935f77e11e6cd503c606..2e934dc0ce737b0c6d78dde9f20f55e2ee6931d2 100644
--- a/test/unit-tests/components/views/avatars/MemberAvatar-test.tsx
+++ b/test/unit-tests/components/views/avatars/MemberAvatar-test.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import { getByTestId, render, waitFor } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
-import React, { ComponentProps } from "react";
+import { type MatrixClient, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentProps } from "react";
 
 import MemberAvatar from "../../../../../src/components/views/avatars/MemberAvatar";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx
index e3e8715cfba44cde3edeea8a0ee9b46183871d3c..1c8db390409c2b4c55371cc114636269ea431785 100644
--- a/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx
+++ b/test/unit-tests/components/views/avatars/RoomAvatar-test.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render } from "jest-matrix-react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import RoomAvatar from "../../../../../src/components/views/avatars/RoomAvatar";
@@ -17,6 +17,9 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import { LocalRoom } from "../../../../../src/models/LocalRoom";
 import * as AvatarModule from "../../../../../src/Avatar";
 import { DirectoryMember } from "../../../../../src/utils/direct-messages";
+import { MediaPreviewValue } from "../../../../../src/@types/media_preview";
+import SettingsStore from "../../../../../src/settings/SettingsStore";
+import { SettingLevel } from "../../../../../src/settings/SettingLevel";
 
 describe("RoomAvatar", () => {
     let client: MatrixClient;
@@ -35,6 +38,12 @@ describe("RoomAvatar", () => {
     });
 
     afterAll(() => {
+        SettingsStore.setValue(
+            "mediaPreviewConfig",
+            null,
+            SettingLevel.ACCOUNT,
+            SettingsStore.getDefaultValue("mediaPreviewConfig"),
+        );
         jest.restoreAllMocks();
     });
 
@@ -52,6 +61,7 @@ describe("RoomAvatar", () => {
     it("should render as expected for a DM room", () => {
         const userId = "@dm_user@example.com";
         const room = new Room("!room:example.com", client, client.getSafeUserId());
+        room.getMember = jest.fn().mockImplementation(() => new RoomMember(room.roomId, userId));
         room.name = "DM room";
         mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
         expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
@@ -64,4 +74,31 @@ describe("RoomAvatar", () => {
         localRoom.targets.push(new DirectoryMember({ user_id: userId }));
         expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
     });
+    it("should render an avatar for a room the user is invited to", () => {
+        const room = new Room("!room:example.com", client, client.getSafeUserId());
+        jest.spyOn(room, "getMxcAvatarUrl").mockImplementation(() => "mxc://example.com/foobar");
+        room.name = "test room";
+        room.updateMyMembership("invite");
+        room.currentState.setStateEvents([
+            new MatrixEvent({
+                sender: "@sender:server",
+                room_id: room.roomId,
+                type: EventType.RoomAvatar,
+                state_key: "",
+                content: {
+                    url: "mxc://example.com/foobar",
+                },
+            }),
+        ]);
+        expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
+    });
+    it("should not render an invite avatar if the user has disabled it", () => {
+        SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, {
+            invite_avatars: MediaPreviewValue.Off,
+        });
+        const room = new Room("!room:example.com", client, client.getSafeUserId());
+        room.name = "test room";
+        room.updateMyMembership("invite");
+        expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
+    });
 });
diff --git a/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..020b92227dd5d375e56d85ce8cbca5a822ddbbe1
--- /dev/null
+++ b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import { mocked } from "jest-mock";
+
+import { RoomAvatarView } from "../../../../../src/components/views/avatars/RoomAvatarView";
+import { mkStubRoom, stubClient } from "../../../../test-utils";
+import {
+    type RoomAvatarViewState,
+    useRoomAvatarViewModel,
+} from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+import { Presence } from "../../../../../src/components/views/avatars/WithPresenceIndicator";
+
+jest.mock("../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel", () => ({
+    useRoomAvatarViewModel: jest.fn(),
+}));
+
+describe("<RoomAvatarView />", () => {
+    const matrixClient = stubClient();
+    const room = mkStubRoom("roomId", "roomName", matrixClient);
+
+    DMRoomMap.makeShared(matrixClient);
+    jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+
+    let defaultValue: RoomAvatarViewState;
+
+    beforeEach(() => {
+        defaultValue = {
+            hasDecoration: true,
+            isPublic: true,
+            isVideoRoom: true,
+            presence: null,
+        };
+
+        mocked(useRoomAvatarViewModel).mockReturnValue(defaultValue);
+    });
+
+    it("should not render a decoration", () => {
+        mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: false });
+        const { asFragment } = render(<RoomAvatarView room={room} />);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render a video room decoration", () => {
+        mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: true, isVideoRoom: true });
+        const { asFragment } = render(<RoomAvatarView room={room} />);
+
+        expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render a public room decoration", () => {
+        mocked(useRoomAvatarViewModel).mockReturnValue({
+            ...defaultValue,
+            hasDecoration: true,
+            isPublic: true,
+            isVideoRoom: false,
+        });
+        const { asFragment } = render(<RoomAvatarView room={room} />);
+
+        expect(screen.getByLabelText("This room is public")).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should not render a public room decoration if the room is a video room", () => {
+        mocked(useRoomAvatarViewModel).mockReturnValue({
+            ...defaultValue,
+            hasDecoration: true,
+            isPublic: true,
+            isVideoRoom: true,
+        });
+        render(<RoomAvatarView room={room} />);
+
+        expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument();
+        expect(screen.queryByLabelText("This room is public")).toBeNull();
+    });
+
+    it.each([
+        { presence: Presence.Online, label: "Online" },
+        { presence: Presence.Offline, label: "Offline" },
+        { presence: Presence.Busy, label: "Busy" },
+        { presence: Presence.Away, label: "Away" },
+    ])("should render the $presence presence", ({ presence, label }) => {
+        mocked(useRoomAvatarViewModel).mockReturnValue({
+            ...defaultValue,
+            hasDecoration: true,
+            presence,
+        });
+        const { asFragment } = render(<RoomAvatarView room={room} />);
+
+        expect(screen.getByLabelText(label)).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/avatars/WithPresenceIndicator-test.tsx b/test/unit-tests/components/views/avatars/WithPresenceIndicator-test.tsx
index f5c26aa7efa1ea1982739837a2c10641b0794ef6..61cec3d4fa814fda07f640d72da2a0af4c05fc52 100644
--- a/test/unit-tests/components/views/avatars/WithPresenceIndicator-test.tsx
+++ b/test/unit-tests/components/views/avatars/WithPresenceIndicator-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { render, waitFor } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, Room, RoomMember, User } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, PendingEventOrdering, Room, RoomMember, User } from "matrix-js-sdk/src/matrix";
 import React from "react";
 import userEvent from "@testing-library/user-event";
 
diff --git a/test/unit-tests/components/views/avatars/__snapshots__/DecoratedRoomAvatar-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/DecoratedRoomAvatar-test.tsx.snap
index 41ba8af352364415f5f1aa6ae3ea3c5ac289136b..525068291654f533474d8bc3673495a3b713591d 100644
--- a/test/unit-tests/components/views/avatars/__snapshots__/DecoratedRoomAvatar-test.tsx.snap
+++ b/test/unit-tests/components/views/avatars/__snapshots__/DecoratedRoomAvatar-test.tsx.snap
@@ -6,7 +6,7 @@ exports[`DecoratedRoomAvatar shows an avatar with globe icon and tooltip for pub
     class="mx_DecoratedRoomAvatar mx_DecoratedRoomAvatar_cutout"
   >
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="1"
       data-testid="avatar-img"
       data-type="round"
@@ -16,7 +16,7 @@ exports[`DecoratedRoomAvatar shows an avatar with globe icon and tooltip for pub
       r
     </span>
     <div
-      aria-labelledby=":r0:"
+      aria-labelledby="«r0»"
       class="mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_globe"
       tabindex="0"
     />
@@ -30,7 +30,7 @@ exports[`DecoratedRoomAvatar shows the presence indicator in a DM room that also
     class="mx_DecoratedRoomAvatar mx_DecoratedRoomAvatar_cutout"
   >
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="5"
       data-testid="avatar-img"
       data-type="round"
@@ -40,7 +40,7 @@ exports[`DecoratedRoomAvatar shows the presence indicator in a DM room that also
       r
     </span>
     <div
-      aria-labelledby=":r6:"
+      aria-labelledby="«r6»"
       class="mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_online"
       tabindex="0"
     />
diff --git a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap
index 0430b6f584e2c161c9b625341b87c76e6810feee..f77dcc6948cf59a0bec59c6cbb11e829ff9638b1 100644
--- a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap
+++ b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap
@@ -1,9 +1,48 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`RoomAvatar should not render an invite avatar if the user has disabled it 1`] = `
+<div>
+  <span
+    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+    data-color="6"
+    data-testid="avatar-img"
+    data-type="round"
+    role="presentation"
+    style="--cpd-avatar-size: 36px;"
+  >
+    t
+  </span>
+</div>
+`;
+
+exports[`RoomAvatar should render an avatar for a room the user is invited to 1`] = `
+<div>
+  <span
+    aria-label="Avatar"
+    class="_avatar_1qbcf_8 mx_BaseAvatar"
+    data-color="6"
+    data-testid="avatar-img"
+    data-type="round"
+    style="--cpd-avatar-size: 36px;"
+  >
+    <img
+      alt=""
+      class="_image_1qbcf_41"
+      data-type="round"
+      height="36px"
+      loading="lazy"
+      referrerpolicy="no-referrer"
+      src="http://this.is.a.url/example.com/foobar"
+      width="36px"
+    />
+  </span>
+</div>
+`;
+
 exports[`RoomAvatar should render as expected for a DM room 1`] = `
 <div>
   <span
-    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
     data-color="1"
     data-testid="avatar-img"
     data-type="round"
@@ -18,7 +57,7 @@ exports[`RoomAvatar should render as expected for a DM room 1`] = `
 exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
 <div>
   <span
-    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
     data-color="3"
     data-testid="avatar-img"
     data-type="round"
@@ -33,7 +72,7 @@ exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
 exports[`RoomAvatar should render as expected for a Room 1`] = `
 <div>
   <span
-    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
     data-color="6"
     data-testid="avatar-img"
     data-type="round"
diff --git a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..1a4263a0c9c71b179dfeb4b107a7fa74daebaa87
--- /dev/null
+++ b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap
@@ -0,0 +1,389 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomAvatarView /> should not render a decoration 1`] = `
+<DocumentFragment>
+  <span
+    aria-label="Avatar"
+    class="_avatar_1qbcf_8 mx_BaseAvatar"
+    data-color="1"
+    data-testid="avatar-img"
+    data-type="round"
+    style="--cpd-avatar-size: 32px;"
+  >
+    <img
+      alt=""
+      class="_image_1qbcf_41"
+      data-type="round"
+      height="32px"
+      loading="lazy"
+      referrerpolicy="no-referrer"
+      src="http://this.is.a.url/avatar.url/room.png"
+      width="32px"
+    />
+  </span>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render a public room decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is public"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render a video room decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is a video room"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render the AWAY presence 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon mx_RoomAvatarView_RoomAvatar_presence"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is a video room"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+    <svg
+      aria-label="Away"
+      class="mx_RoomAvatarView_PresenceDecoration"
+      color="var(--cpd-color-icon-quaternary)"
+      fill="currentColor"
+      height="8px"
+      viewBox="0 0 8 8"
+      width="8px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <g
+        clip-path="url(#a)"
+      >
+        <path
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0"
+        />
+      </g>
+      <defs>
+        <clippath
+          id="a"
+        >
+          <path
+            d="M0 0h8v8H0z"
+          />
+        </clippath>
+      </defs>
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render the BUSY presence 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon mx_RoomAvatarView_RoomAvatar_presence"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is a video room"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+    <svg
+      aria-label="Busy"
+      class="mx_RoomAvatarView_PresenceDecoration"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="8px"
+      viewBox="0 0 8 8"
+      width="8px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <g
+        clip-path="url(#a)"
+      >
+        <path
+          clip-rule="evenodd"
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0M5.435 6.048A2.5 2.5 0 0 1 1.687 3.05zm.914-1.19L2.648 1.897a2.5 2.5 0 0 1 3.701 2.961"
+          fill-rule="evenodd"
+        />
+      </g>
+      <defs>
+        <clippath
+          id="a"
+        >
+          <path
+            d="M0 0h8v8H0z"
+          />
+        </clippath>
+      </defs>
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render the OFFLINE presence 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon mx_RoomAvatarView_RoomAvatar_presence"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is a video room"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+    <svg
+      aria-label="Offline"
+      class="mx_RoomAvatarView_PresenceDecoration"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="8px"
+      viewBox="0 0 8 8"
+      width="8px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <g
+        clip-path="url(#a)"
+      >
+        <path
+          clip-rule="evenodd"
+          d="M4 6.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5M4 8a4 4 0 1 0 0-8 4 4 0 0 0 0 8"
+          fill-rule="evenodd"
+        />
+      </g>
+      <defs>
+        <clippath
+          id="a"
+        >
+          <path
+            d="M0 0h8v8H0z"
+          />
+        </clippath>
+      </defs>
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomAvatarView /> should render the ONLINE presence 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomAvatarView"
+  >
+    <span
+      aria-label="Avatar"
+      class="_avatar_1qbcf_8 mx_BaseAvatar mx_RoomAvatarView_RoomAvatar mx_RoomAvatarView_RoomAvatar_icon mx_RoomAvatarView_RoomAvatar_presence"
+      data-color="1"
+      data-testid="avatar-img"
+      data-type="round"
+      style="--cpd-avatar-size: 32px;"
+    >
+      <img
+        alt=""
+        class="_image_1qbcf_41"
+        data-type="round"
+        height="32px"
+        loading="lazy"
+        referrerpolicy="no-referrer"
+        src="http://this.is.a.url/avatar.url/room.png"
+        width="32px"
+      />
+    </span>
+    <svg
+      aria-label="This room is a video room"
+      class="mx_RoomAvatarView_icon"
+      color="var(--cpd-color-icon-tertiary)"
+      fill="currentColor"
+      height="16px"
+      viewBox="0 0 24 24"
+      width="16px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+    <svg
+      aria-label="Online"
+      class="mx_RoomAvatarView_PresenceDecoration"
+      color="var(--cpd-color-icon-accent-primary)"
+      fill="currentColor"
+      height="8px"
+      viewBox="0 0 8 8"
+      width="8px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <g
+        clip-path="url(#a)"
+      >
+        <path
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0"
+        />
+      </g>
+      <defs>
+        <clippath
+          id="a"
+        >
+          <path
+            d="M0 0h8v8H0z"
+          />
+        </clippath>
+      </defs>
+    </svg>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/avatars/__snapshots__/WithPresenceIndicator-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/WithPresenceIndicator-test.tsx.snap
index a66841e0d18eef7184b9460c73d3d1a1ba2420c9..07f947d6d6a22007b033d74bccca06857f016a64 100644
--- a/test/unit-tests/components/views/avatars/__snapshots__/WithPresenceIndicator-test.tsx.snap
+++ b/test/unit-tests/components/views/avatars/__snapshots__/WithPresenceIndicator-test.tsx.snap
@@ -7,7 +7,7 @@ exports[`WithPresenceIndicator renders presence indicator with tooltip for DM ro
   >
     <span />
     <div
-      aria-labelledby=":r0:"
+      aria-labelledby="«r0»"
       class="mx_WithPresenceIndicator_icon mx_WithPresenceIndicator_icon_online"
       style="width: 32px; height: 32px;"
       tabindex="0"
@@ -23,7 +23,7 @@ exports[`WithPresenceIndicator renders presence indicator with tooltip for DM ro
   >
     <span />
     <div
-      aria-labelledby=":r6:"
+      aria-labelledby="«r6»"
       class="mx_WithPresenceIndicator_icon mx_WithPresenceIndicator_icon_offline"
       style="width: 32px; height: 32px;"
       tabindex="0"
@@ -39,7 +39,7 @@ exports[`WithPresenceIndicator renders presence indicator with tooltip for DM ro
   >
     <span />
     <div
-      aria-labelledby=":rc:"
+      aria-labelledby="«rc»"
       class="mx_WithPresenceIndicator_icon mx_WithPresenceIndicator_icon_away"
       style="width: 32px; height: 32px;"
       tabindex="0"
diff --git a/test/unit-tests/components/views/beacon/BeaconListItem-test.tsx b/test/unit-tests/components/views/beacon/BeaconListItem-test.tsx
index f44a1d72db3e1d5f1b7e62cb3859110ef80fe278..7773839223215ea45c0cc78e573ad50d108c35e0 100644
--- a/test/unit-tests/components/views/beacon/BeaconListItem-test.tsx
+++ b/test/unit-tests/components/views/beacon/BeaconListItem-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { act, fireEvent, render } from "jest-matrix-react";
-import { Beacon, RoomMember, MatrixEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import { Beacon, RoomMember, type MatrixEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
 import BeaconListItem from "../../../../../src/components/views/beacon/BeaconListItem";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
diff --git a/test/unit-tests/components/views/beacon/BeaconMarker-test.tsx b/test/unit-tests/components/views/beacon/BeaconMarker-test.tsx
index 625d61b1c3e46df851875defdd0fcc530c8b4f25..8e24c6d8417dc331551a69f7a6f824019341d0ed 100644
--- a/test/unit-tests/components/views/beacon/BeaconMarker-test.tsx
+++ b/test/unit-tests/components/views/beacon/BeaconMarker-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { act, render, screen, waitFor } from "jest-matrix-react";
 import * as maplibregl from "maplibre-gl";
-import { Beacon, Room, RoomMember, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
+import { Beacon, type Room, RoomMember, type MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
 
 import BeaconMarker from "../../../../../src/components/views/beacon/BeaconMarker";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
diff --git a/test/unit-tests/components/views/beacon/BeaconViewDialog-test.tsx b/test/unit-tests/components/views/beacon/BeaconViewDialog-test.tsx
index 2c879468d613b0e3d5f9cfba7749e9d2b0d49b40..5895929640abbf25211c79652ce51a59de65d132 100644
--- a/test/unit-tests/components/views/beacon/BeaconViewDialog-test.tsx
+++ b/test/unit-tests/components/views/beacon/BeaconViewDialog-test.tsx
@@ -7,8 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { act, fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
-import { MatrixClient, MatrixEvent, Room, RoomMember, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
+import { act, fireEvent, render, type RenderResult, waitFor } from "jest-matrix-react";
+import {
+    type MatrixClient,
+    type MatrixEvent,
+    type Room,
+    RoomMember,
+    getBeaconInfoIdentifier,
+} from "matrix-js-sdk/src/matrix";
 import * as maplibregl from "maplibre-gl";
 import { mocked } from "jest-mock";
 
diff --git a/test/unit-tests/components/views/beacon/DialogSidebar-test.tsx b/test/unit-tests/components/views/beacon/DialogSidebar-test.tsx
index ca76d6fd59a810db11dccd34b4c713ac0bcf4599..15a56ed290c5f52aaa7a92469555066fe4ed322f 100644
--- a/test/unit-tests/components/views/beacon/DialogSidebar-test.tsx
+++ b/test/unit-tests/components/views/beacon/DialogSidebar-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { act, fireEvent, render } from "jest-matrix-react";
 
 import DialogSidebar from "../../../../../src/components/views/beacon/DialogSidebar";
diff --git a/test/unit-tests/components/views/beacon/LeftPanelLiveShareWarning-test.tsx b/test/unit-tests/components/views/beacon/LeftPanelLiveShareWarning-test.tsx
index aa83fefb9a9c1b38a81d012212b42ee5dadcf60a..af60e8732496e3ca39059e3c77de378df2c00b89 100644
--- a/test/unit-tests/components/views/beacon/LeftPanelLiveShareWarning-test.tsx
+++ b/test/unit-tests/components/views/beacon/LeftPanelLiveShareWarning-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { mocked } from "jest-mock";
 import { act, fireEvent, render } from "jest-matrix-react";
-import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
+import { Beacon, type BeaconIdentifier } from "matrix-js-sdk/src/matrix";
 
 import LeftPanelLiveShareWarning from "../../../../../src/components/views/beacon/LeftPanelLiveShareWarning";
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../../src/stores/OwnBeaconStore";
diff --git a/test/unit-tests/components/views/beacon/RoomCallBanner-test.tsx b/test/unit-tests/components/views/beacon/RoomCallBanner-test.tsx
index e8fefe0571260b9c033ccaedd50e82d54ef38a76..38cb77eb03f281fc3425dd3bfd4dd9e8dd680aa7 100644
--- a/test/unit-tests/components/views/beacon/RoomCallBanner-test.tsx
+++ b/test/unit-tests/components/views/beacon/RoomCallBanner-test.tsx
@@ -7,10 +7,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room, PendingEventOrdering, MatrixClient, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
-import { ClientWidgetApi, Widget } from "matrix-widget-api";
+import {
+    Room,
+    PendingEventOrdering,
+    type MatrixClient,
+    type RoomMember,
+    RoomStateEvent,
+} from "matrix-js-sdk/src/matrix";
+import { type ClientWidgetApi, Widget } from "matrix-widget-api";
 import { act, cleanup, render, screen } from "jest-matrix-react";
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 
 import {
     mkRoomMember,
diff --git a/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap b/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap
index ad27050370c438e558ab5f207e720209ce1dceab..234b32911d622a0d85120c6800dc97ecb2413975 100644
--- a/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap
+++ b/test/unit-tests/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap
@@ -32,7 +32,7 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
           class="mx_BeaconListItem_interactions"
         >
           <a
-            aria-labelledby=":r0:"
+            aria-labelledby="«r0»"
             data-testid="open-location-in-osm"
             href="https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41"
             rel="noreferrer noopener"
diff --git a/test/unit-tests/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap b/test/unit-tests/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap
index bb916b85f3cd905aea05c39281575ccc4fe095bb..e65ab9d83ec613e9034a1dc20a2a909853291329 100644
--- a/test/unit-tests/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap
+++ b/test/unit-tests/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap
@@ -11,7 +11,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
         class="mx_Marker_border"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="6"
           data-testid="avatar-img"
           data-type="round"
diff --git a/test/unit-tests/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap b/test/unit-tests/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap
index 1bf8cba6bb09b80a474dbba8553f8c5658d2d8c9..7c225cb0149c4d248f8efeb071769c759f5f217d 100644
--- a/test/unit-tests/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap
@@ -17,7 +17,7 @@ exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`]
     xmlns="http://www.w3.org/2000/svg"
   >
     <path
-      d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+      d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
     />
   </svg>
   <span
@@ -41,7 +41,7 @@ exports[`<BeaconViewDialog /> renders own beacon status when user is live sharin
   class="mx_DialogOwnBeaconStatus"
 >
   <span
-    class="_avatar_mcap2_17 mx_BaseAvatar mx_DialogOwnBeaconStatus_avatar _avatar-imageless_mcap2_61"
+    class="_avatar_1qbcf_8 mx_BaseAvatar mx_DialogOwnBeaconStatus_avatar _avatar-imageless_1qbcf_52"
     data-color="6"
     data-testid="avatar-img"
     data-type="round"
diff --git a/test/unit-tests/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap b/test/unit-tests/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap
index 373cb4dcdee34c1a1bfb964f21f9cde4048c6de8..b9364bb6db8a9091bda366561cf262bfb499dc56 100644
--- a/test/unit-tests/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap
+++ b/test/unit-tests/components/views/beacon/__snapshots__/DialogSidebar-test.tsx.snap
@@ -29,7 +29,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
           />
         </svg>
       </div>
@@ -41,7 +41,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
         class="mx_BeaconListItem"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar mx_BeaconListItem_avatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar mx_BeaconListItem_avatar _avatar-imageless_1qbcf_52"
           data-color="1"
           data-testid="avatar-img"
           data-type="round"
@@ -72,7 +72,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
               class="mx_BeaconListItem_interactions"
             >
               <a
-                aria-labelledby=":r8:"
+                aria-labelledby="«r8»"
                 data-testid="open-location-in-osm"
                 href="https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41"
                 rel="noreferrer noopener"
@@ -135,7 +135,7 @@ exports[`<DialogSidebar /> renders sidebar correctly without beacons 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
           />
         </svg>
       </div>
diff --git a/test/unit-tests/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap b/test/unit-tests/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap
index c3e25bc86ac37f8311e0c873a2fef674bb6cd9e1..09500389afc8e65d091904981f2bad4e953aa5e7 100644
--- a/test/unit-tests/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap
+++ b/test/unit-tests/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap
@@ -3,7 +3,7 @@
 exports[`<ShareLatestLocation /> renders share buttons when there is a location 1`] = `
 <DocumentFragment>
   <a
-    aria-labelledby=":r0:"
+    aria-labelledby="«r0»"
     data-testid="open-location-in-osm"
     href="https://www.openstreetmap.org/?mlat=51&mlon=42#map=16/51/42"
     rel="noreferrer noopener"
diff --git a/test/unit-tests/components/views/beta/BetaCard-test.tsx b/test/unit-tests/components/views/beta/BetaCard-test.tsx
index 42efb1cd3489f3bd5bcd8cdffe968bc5d2af042e..06ede990e6d50c16380edb54a353859258e99392 100644
--- a/test/unit-tests/components/views/beta/BetaCard-test.tsx
+++ b/test/unit-tests/components/views/beta/BetaCard-test.tsx
@@ -13,8 +13,8 @@ import { render, screen } from "jest-matrix-react";
 import { shouldShowFeedback } from "../../../../../src/utils/Feedback";
 import BetaCard from "../../../../../src/components/views/beta/BetaCard";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
-import { TranslationKey } from "../../../../../src/languageHandler";
-import { FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
+import { type TranslationKey } from "../../../../../src/languageHandler";
+import { type FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
 
 jest.mock("../../../../../src/utils/Feedback");
 jest.mock("../../../../../src/settings/SettingsStore");
diff --git a/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx
index 1cfc1a03f652fbd99fc979fd65e899c71398f3d6..411cc5d0e498d10fbdf3ae65d79823c6d063d531 100644
--- a/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx
+++ b/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult, screen, waitFor } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult, screen, waitFor } from "jest-matrix-react";
 import {
     EventStatus,
     MatrixEvent,
     Room,
     PendingEventOrdering,
-    BeaconIdentifier,
+    type BeaconIdentifier,
     Beacon,
     getBeaconInfoIdentifier,
     EventType,
@@ -28,7 +28,7 @@ import userEvent from "@testing-library/user-event";
 
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import { canEditContent } from "../../../../../src/utils/EventUtils";
 import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
 import MessageContextMenu from "../../../../../src/components/views/context_menus/MessageContextMenu";
diff --git a/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx
index 10aade5b54b545a8a3ae60470c1eefff6c1747dd..6c71acc09e035b0868707c3bd97d3c983d531fbd 100644
--- a/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx
+++ b/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { fireEvent, getByLabelText, render, screen } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { ReceiptType, MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
+import { ReceiptType, type MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import React from "react";
 import userEvent from "@testing-library/user-event";
@@ -17,7 +17,7 @@ import { sleep } from "matrix-js-sdk/src/utils";
 import { ChevronFace } from "../../../../../src/components/structures/ContextMenu";
 import {
     RoomGeneralContextMenu,
-    RoomGeneralContextMenuProps,
+    type RoomGeneralContextMenuProps,
 } from "../../../../../src/components/views/context_menus/RoomGeneralContextMenu";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/views/context_menus/SpaceContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/SpaceContextMenu-test.tsx
index 8b8a565aa483e5b5dfae7ab43ee5102af9b142db..eab7e4eef9f4540dc32e79c1ddecaca69c394e7b 100644
--- a/test/unit-tests/components/views/context_menus/SpaceContextMenu-test.tsx
+++ b/test/unit-tests/components/views/context_menus/SpaceContextMenu-test.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
-import { Mocked, mocked } from "jest-mock";
-import { prettyDOM, render, RenderResult, screen } from "jest-matrix-react";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+import { type Mocked, mocked } from "jest-mock";
+import { prettyDOM, render, type RenderResult, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 import SpaceContextMenu from "../../../../../src/components/views/context_menus/SpaceContextMenu";
diff --git a/test/unit-tests/components/views/context_menus/ThreadListContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/ThreadListContextMenu-test.tsx
index 0ef43d1b8013192a9e8d578da1f341dd6d5840da..23dd63e9f204b2928fdf51737b09b82639477b8d 100644
--- a/test/unit-tests/components/views/context_menus/ThreadListContextMenu-test.tsx
+++ b/test/unit-tests/components/views/context_menus/ThreadListContextMenu-test.tsx
@@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
 import { getByTestId, render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, PendingEventOrdering, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
 import ThreadListContextMenu, {
-    ThreadListContextMenuProps,
+    type ThreadListContextMenuProps,
 } from "../../../../../src/components/views/context_menus/ThreadListContextMenu";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
diff --git a/test/unit-tests/components/views/context_menus/WidgetContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/WidgetContextMenu-test.tsx
index 37dd19252584685899a8e1ed6f1d518823151467..648e5bff6471a2d89b5fc19f98bc3389ff65c127 100644
--- a/test/unit-tests/components/views/context_menus/WidgetContextMenu-test.tsx
+++ b/test/unit-tests/components/views/context_menus/WidgetContextMenu-test.tsx
@@ -7,19 +7,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type JSX, type ComponentProps } from "react";
 import { screen, render } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { MatrixWidgetType } from "matrix-widget-api";
 import {
-    ApprovalOpts,
-    WidgetInfo,
+    type ApprovalOpts,
+    type WidgetInfo,
     WidgetLifecycle,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
 
 import { WidgetContextMenu } from "../../../../../src/components/views/context_menus/WidgetContextMenu";
-import { IApp } from "../../../../../src/stores/WidgetStore";
+import { type IApp } from "../../../../../src/stores/WidgetStore";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import WidgetUtils from "../../../../../src/utils/WidgetUtils";
 import { ModuleRunner } from "../../../../../src/modules/ModuleRunner";
diff --git a/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx
index cb877d8a11d42e06c21b42d956989939820d9dc4..2d597f8e18f7f869f7fec8bc951fbe34aabcbed8 100644
--- a/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/AccessSecretStorageDialog-test.tsx
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { SecretStorage, MatrixClient } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentProps } from "react";
+import { type SecretStorage, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { act, fireEvent, render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 import { mockPlatformPeg, stubClient } from "../../../../test-utils";
 import AccessSecretStorageDialog from "../../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
 
-const securityKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu";
+const recoveryKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu";
 
 describe("AccessSecretStorageDialog", () => {
     let mockClient: MatrixClient;
@@ -29,11 +29,11 @@ describe("AccessSecretStorageDialog", () => {
         render(<AccessSecretStorageDialog {...defaultProps} {...props} />);
     };
 
-    const enterSecurityKey = (placeholder = "Security Key"): void => {
+    const enterRecoveryKey = (): void => {
         act(() => {
-            fireEvent.change(screen.getByPlaceholderText(placeholder), {
+            fireEvent.change(screen.getByRole("textbox"), {
                 target: {
-                    value: securityKey,
+                    value: recoveryKey,
                 },
             });
             // wait for debounce
@@ -67,30 +67,30 @@ describe("AccessSecretStorageDialog", () => {
         renderComponent({ onFinished, checkPrivateKey });
 
         // check that the input field is focused
-        expect(screen.getByPlaceholderText("Security Key")).toHaveFocus();
+        expect(screen.getByRole("textbox")).toHaveFocus();
 
-        await enterSecurityKey();
+        await enterRecoveryKey();
         await submitDialog();
 
-        expect(screen.getByText("Looks good!")).toBeInTheDocument();
-        expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: securityKey });
-        expect(onFinished).toHaveBeenCalledWith({ recoveryKey: securityKey });
+        expect(screen.getByText("Continue")).not.toHaveAttribute("aria-disabled", "true");
+        expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey });
+        expect(onFinished).toHaveBeenCalledWith({ recoveryKey });
     });
 
-    it("Notifies the user if they input an invalid Security Key", async () => {
+    it("Notifies the user if they input an invalid Recovery Key", async () => {
         const onFinished = jest.fn();
-        const checkPrivateKey = jest.fn().mockResolvedValue(true);
+        const checkPrivateKey = jest.fn().mockResolvedValue(false);
         renderComponent({ onFinished, checkPrivateKey });
 
         jest.spyOn(mockClient.secretStorage, "checkKey").mockImplementation(() => {
             throw new Error("invalid key");
         });
 
-        await enterSecurityKey();
+        await enterRecoveryKey();
         await submitDialog();
 
-        expect(screen.getByText("Continue")).toBeDisabled();
-        expect(screen.getByText("Invalid Security Key")).toBeInTheDocument();
+        expect(screen.getByText("The recovery key you entered is not correct.")).toBeInTheDocument();
+        expect(screen.getByText("Continue")).toHaveAttribute("aria-disabled", "true");
     });
 
     it("Notifies the user if they input an invalid passphrase", async function () {
@@ -110,46 +110,10 @@ describe("AccessSecretStorageDialog", () => {
         const checkPrivateKey = jest.fn().mockResolvedValue(false);
         renderComponent({ checkPrivateKey, keyInfo });
 
-        await enterSecurityKey("Security Phrase");
-        expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
-        await submitDialog();
-
-        await expect(
-            screen.findByText(
-                "👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
-            ),
-        ).resolves.toBeInTheDocument();
-
-        expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
-    });
-
-    it("Can reset secret storage", async () => {
-        jest.spyOn(mockClient.secretStorage, "checkKey").mockResolvedValue(true);
-
-        const onFinished = jest.fn();
-        const checkPrivateKey = jest.fn().mockResolvedValue(true);
-        renderComponent({ onFinished, checkPrivateKey });
-
-        await userEvent.click(screen.getByText("Reset all"), { delay: null });
-
-        // It will prompt the user to confirm resetting
-        expect(screen.getByText("Reset everything")).toBeInTheDocument();
-        await userEvent.click(screen.getByText("Reset"), { delay: null });
-
-        // Then it will prompt the user to create a key/passphrase
-        await screen.findByText("Set up Secure Backup");
-        document.execCommand = jest.fn().mockReturnValue(true);
-        jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
-            privateKey: new Uint8Array(),
-            encodedPrivateKey: securityKey,
-        });
-        screen.getByRole("button", { name: "Continue" }).click();
-
-        await screen.findByText(/Save your Security Key/);
-        screen.getByRole("button", { name: "Copy" }).click();
-        await screen.findByText("Copied!");
-        screen.getByRole("button", { name: "Continue" }).click();
+        await enterRecoveryKey();
+        expect(screen.getByRole("textbox")).toHaveValue(recoveryKey);
 
-        await screen.findByText("Secure Backup successful");
+        await expect(screen.findByText("The recovery key you entered is not correct.")).resolves.toBeInTheDocument();
+        expect(screen.getByText("Continue")).toHaveAttribute("aria-disabled", "true");
     });
 });
diff --git a/test/unit-tests/components/views/dialogs/AskInviteAnywayDialog-test.tsx b/test/unit-tests/components/views/dialogs/AskInviteAnywayDialog-test.tsx
index 713d90a3ef69855ba273bdfcb0807f3ba625ed62..f0e450f9e47479d556e338a7927bcfbc5ce80a6e 100644
--- a/test/unit-tests/components/views/dialogs/AskInviteAnywayDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/AskInviteAnywayDialog-test.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { getByText, render, RenderResult } from "jest-matrix-react";
+import { getByText, render, type RenderResult } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import React from "react";
 
 import AskInviteAnywayDialog, {
-    AskInviteAnywayDialogProps,
+    type AskInviteAnywayDialogProps,
 } from "../../../../../src/components/views/dialogs/AskInviteAnywayDialog";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 
diff --git a/test/unit-tests/components/views/dialogs/BugReportDialog-test.tsx b/test/unit-tests/components/views/dialogs/BugReportDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..955ac64b2d6d88d1aad7859faea0fbe97c978827
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/BugReportDialog-test.tsx
@@ -0,0 +1,132 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { render, waitFor, type RenderResult } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+import fetchMock from "fetch-mock-jest";
+import { type Mocked } from "jest-mock";
+
+import BugReportDialog, {
+    type BugReportDialogProps,
+} from "../../../../../src/components/views/dialogs/BugReportDialog";
+import SdkConfig from "../../../../../src/SdkConfig";
+import { type ConsoleLogger } from "../../../../../src/rageshake/rageshake";
+
+const BUG_REPORT_URL = "https://example.org/submit";
+
+describe("BugReportDialog", () => {
+    const onFinished: jest.Mock<any, any> = jest.fn();
+
+    function renderComponent(props: Partial<BugReportDialogProps> = {}): RenderResult {
+        return render(<BugReportDialog onFinished={onFinished} />);
+    }
+
+    beforeEach(() => {
+        jest.resetAllMocks();
+        SdkConfig.put({
+            bug_report_endpoint_url: BUG_REPORT_URL,
+        });
+
+        const mockConsoleLogger = {
+            flush: jest.fn(),
+            consume: jest.fn(),
+            warn: jest.fn(),
+        } as unknown as Mocked<ConsoleLogger>;
+
+        // @ts-ignore - mock the console logger
+        global.mx_rage_logger = mockConsoleLogger;
+
+        // @ts-ignore
+        mockConsoleLogger.flush.mockReturnValue([
+            {
+                id: "instance-0",
+                line: "line 1",
+            },
+            {
+                id: "instance-1",
+                line: "line 2",
+            },
+        ]);
+    });
+
+    afterEach(() => {
+        SdkConfig.reset();
+        fetchMock.restore();
+    });
+
+    it("can close the bug reporter", async () => {
+        const { getByTestId } = renderComponent();
+        await userEvent.click(getByTestId("dialog-cancel-button"));
+        expect(onFinished).toHaveBeenCalledWith(false);
+    });
+
+    it("can submit a bug report", async () => {
+        const { getByLabelText, getByText } = renderComponent();
+        fetchMock.postOnce(BUG_REPORT_URL, { report_url: "https://exmaple.org/report/url" });
+        await userEvent.type(getByLabelText("GitHub issue"), "https://example.org/some/issue");
+        await userEvent.type(getByLabelText("Notes"), "Additional text");
+        await userEvent.click(getByText("Send logs"));
+        await waitFor(() => expect(getByText("Thank you!")).toBeInTheDocument());
+        expect(onFinished).toHaveBeenCalledWith(false);
+        expect(fetchMock).toHaveFetched(BUG_REPORT_URL);
+    });
+
+    it.each([
+        {
+            errcode: undefined,
+            text: "The rageshake server encountered an unknown error and could not handle the report.",
+        },
+        {
+            errcode: "CUSTOM_ERROR_TYPE",
+            text: "The rageshake server encountered an unknown error and could not handle the report.",
+        },
+        {
+            errcode: "DISALLOWED_APP",
+            text: "Your bug report was rejected. The rageshake server does not support this application.",
+        },
+        {
+            errcode: "REJECTED_BAD_VERSION",
+            text: "Your bug report was rejected as the version you are running is too old.",
+        },
+        {
+            errcode: "REJECTED_UNEXPECTED_RECOVERY_KEY",
+            text: "Your bug report was rejected for safety reasons, as it contained a recovery key.",
+        },
+        {
+            errcode: "REJECTED_CUSTOM_REASON",
+            text: "Your bug report was rejected. The rageshake server rejected the contents of the report due to a policy.",
+        },
+    ])("handles bug report upload errors ($errcode)", async ({ errcode, text }) => {
+        const { getByLabelText, getByText } = renderComponent();
+        fetchMock.postOnce(BUG_REPORT_URL, { status: 400, body: errcode ? { errcode: errcode, error: "blah" } : "" });
+        await userEvent.type(getByLabelText("GitHub issue"), "https://example.org/some/issue");
+        await userEvent.type(getByLabelText("Notes"), "Additional text");
+        await userEvent.click(getByText("Send logs"));
+        expect(onFinished).not.toHaveBeenCalled();
+        expect(fetchMock).toHaveFetched(BUG_REPORT_URL);
+        await waitFor(() => getByText(text));
+    });
+
+    it("should show a policy link when provided", async () => {
+        const { getByLabelText, getByText } = renderComponent();
+        fetchMock.postOnce(BUG_REPORT_URL, {
+            status: 404,
+            body: { errcode: "REJECTED_CUSTOM_REASON", error: "blah", policy_url: "https://example.org/policyurl" },
+        });
+        await userEvent.type(getByLabelText("GitHub issue"), "https://example.org/some/issue");
+        await userEvent.type(getByLabelText("Notes"), "Additional text");
+        await userEvent.click(getByText("Send logs"));
+        expect(onFinished).not.toHaveBeenCalled();
+        expect(fetchMock).toHaveFetched(BUG_REPORT_URL);
+        await waitFor(() => {
+            const learnMoreLink = getByText("Learn more");
+            expect(learnMoreLink).toBeInTheDocument();
+            expect(learnMoreLink.getAttribute("href")).toEqual("https://example.org/policyurl");
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/dialogs/ChangelogDialog-test.tsx b/test/unit-tests/components/views/dialogs/ChangelogDialog-test.tsx
index fd7b82c5903663745f048b0b3bdf3a08f84eb1a5..f240e9716906bb873d517664c27c9609b3033a1c 100644
--- a/test/unit-tests/components/views/dialogs/ChangelogDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ChangelogDialog-test.tsx
@@ -11,7 +11,7 @@ import fetchMock from "fetch-mock-jest";
 import { render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
 
 import ChangelogDialog, {
-    DevelopVersionString,
+    type DevelopVersionString,
     parseVersion,
 } from "../../../../../src/components/views/dialogs/ChangelogDialog";
 
diff --git a/test/unit-tests/components/views/dialogs/ConfirmKeyStorageOffDialog-test.tsx b/test/unit-tests/components/views/dialogs/ConfirmKeyStorageOffDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a27f784c46f53f2835faaa6e4c647272bb045ab7
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/ConfirmKeyStorageOffDialog-test.tsx
@@ -0,0 +1,40 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render } from "jest-matrix-react";
+
+import ConfirmKeyStorageOffDialog from "../../../../../src/components/views/dialogs/ConfirmKeyStorageOffDialog";
+
+describe("ConfirmKeyStorageOffDialog", () => {
+    beforeEach(() => {
+        jest.resetAllMocks();
+    });
+
+    it("renders", () => {
+        const dialog = render(<ConfirmKeyStorageOffDialog onFinished={jest.fn()} />);
+        expect(dialog.asFragment()).toMatchSnapshot();
+    });
+
+    it("calls onFinished with dismissed=true if we dismiss", () => {
+        const onFinished = jest.fn();
+        const dialog = render(<ConfirmKeyStorageOffDialog onFinished={onFinished} />);
+
+        dialog.getByRole("button", { name: "Yes, dismiss" }).click();
+
+        expect(onFinished).toHaveBeenCalledWith(true);
+    });
+
+    it("calls onFinished with dismissed=true if we continue", () => {
+        const onFinished = jest.fn();
+        const dialog = render(<ConfirmKeyStorageOffDialog onFinished={onFinished} />);
+
+        dialog.getByRole("button", { name: "Go to Settings" }).click();
+
+        expect(onFinished).toHaveBeenCalledWith(false);
+    });
+});
diff --git a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx
index 9ba84b58e4ba97acb7e4ed370660ca969051be61..8e16639fef36d31dfac479a12de9f0fdb4c0cf4c 100644
--- a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { screen, act } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
diff --git a/test/unit-tests/components/views/dialogs/ConfirmRejectInviteDialog-test.tsx b/test/unit-tests/components/views/dialogs/ConfirmRejectInviteDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..573cfa76fe0b80de3dcb5c6f5d439a53429696bf
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/ConfirmRejectInviteDialog-test.tsx
@@ -0,0 +1,52 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { render } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+
+import SdkConfig from "../../../../../src/SdkConfig";
+import { DeclineAndBlockInviteDialog } from "../../../../../src/components/views/dialogs/DeclineAndBlockInviteDialog";
+
+describe("ConfirmRejectInviteDialog", () => {
+    const onFinished: jest.Mock<any, any> = jest.fn();
+
+    const MY_ROOM_NAME = "foo";
+
+    beforeEach(() => {
+        jest.resetAllMocks();
+    });
+
+    afterEach(() => {
+        SdkConfig.reset();
+    });
+
+    it("can close the dialog", async () => {
+        const { getByTestId } = render(<DeclineAndBlockInviteDialog onFinished={onFinished} roomName={MY_ROOM_NAME} />);
+        await userEvent.click(getByTestId("dialog-cancel-button"));
+        expect(onFinished).toHaveBeenCalledWith(false, false, false);
+    });
+
+    it("can reject with options selected", async () => {
+        const { container, getByLabelText, getByRole } = render(
+            <DeclineAndBlockInviteDialog onFinished={onFinished} roomName={MY_ROOM_NAME} />,
+        );
+        await userEvent.click(getByRole("switch", { name: "Ignore user" }));
+        await userEvent.click(getByRole("switch", { name: "Report room" }));
+        await userEvent.type(getByLabelText("Reason"), "I want to report this room");
+        expect(container).toMatchSnapshot();
+        await userEvent.click(getByRole("button", { name: "Decline invite" }));
+        expect(onFinished).toHaveBeenCalledWith(true, true, "I want to report this room");
+    });
+    it("can reject without a reason", async () => {
+        const { getByRole } = render(<DeclineAndBlockInviteDialog onFinished={onFinished} roomName={MY_ROOM_NAME} />);
+        await userEvent.click(getByRole("switch", { name: "Ignore user" }));
+        await userEvent.click(getByRole("switch", { name: "Report room" }));
+        await userEvent.click(getByRole("button", { name: "Decline invite" }));
+        expect(onFinished).toHaveBeenCalledWith(true, true, "");
+    });
+});
diff --git a/test/unit-tests/components/views/dialogs/DevtoolsDialog-test.tsx b/test/unit-tests/components/views/dialogs/DevtoolsDialog-test.tsx
index 89dde08393ed8a85639fde2232a25ce8c0885333..34f8daa88a1c51eeb3284663df2af006bf9097ab 100644
--- a/test/unit-tests/components/views/dialogs/DevtoolsDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/DevtoolsDialog-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { getByLabelText, getAllByLabelText, render } from "jest-matrix-react";
-import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
 
 import { stubClient } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/dialogs/ExportDialog-test.tsx b/test/unit-tests/components/views/dialogs/ExportDialog-test.tsx
index 8417ffedb617816749ba90400f680ead8213b88d..6dcb70afd2ef24e26ac94da42a47765f78318df9 100644
--- a/test/unit-tests/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ExportDialog-test.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult, waitFor } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
 import { ExportType, ExportFormat } from "../../../../../src/utils/exportUtils/exportUtils";
diff --git a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx
index 9aa842ee2c35f387a0d193bb9036eff89b9fa8ee..b6ab8a7b38d6b40cdf1f14c3e7d04d4a8bec54a5 100644
--- a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx
@@ -16,7 +16,7 @@ import {
     M_TIMESTAMP,
     M_TEXT,
 } from "matrix-js-sdk/src/matrix";
-import { act, fireEvent, getByTestId, render, RenderResult, screen, waitFor } from "jest-matrix-react";
+import { act, fireEvent, getByTestId, render, type RenderResult, screen, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { sleep } from "matrix-js-sdk/src/utils";
 
diff --git a/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx b/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
index 644b4e0f8b5b52f9bdb23f3cbd0420cdf27235d5..cf9e103ee64dfb44e6e89660a4446201dbfcc5cb 100644
--- a/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
@@ -8,13 +8,13 @@ Please see LICENSE files in the repository root for full details.
 
 import { act, render } from "jest-matrix-react";
 import React from "react";
-import { Mocked } from "jest-mock";
+import { type Mocked } from "jest-mock";
 import {
-    EmojiMapping,
-    ShowSasCallbacks,
-    Verifier,
+    type EmojiMapping,
+    type ShowSasCallbacks,
+    type Verifier,
     VerifierEvent,
-    VerifierEventHandlerMap,
+    type VerifierEventHandlerMap,
 } from "matrix-js-sdk/src/crypto-api";
 import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
 
diff --git a/test/unit-tests/components/views/dialogs/InviteDialog-test.tsx b/test/unit-tests/components/views/dialogs/InviteDialog-test.tsx
index 0a319cc8455fbe4f285fdf73f2313845e86f3b0c..02d0d8dd2ef64527c3db9d994d432709c2e9c128 100644
--- a/test/unit-tests/components/views/dialogs/InviteDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/InviteDialog-test.tsx
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { fireEvent, render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { RoomType, MatrixClient, MatrixError, Room } from "matrix-js-sdk/src/matrix";
+import { RoomType, type MatrixClient, MatrixError, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { sleep } from "matrix-js-sdk/src/utils";
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 
 import InviteDialog from "../../../../../src/components/views/dialogs/InviteDialog";
 import { InviteKind } from "../../../../../src/components/views/dialogs/InviteDialogTypes";
@@ -27,10 +27,10 @@ import {
 } from "../../../../test-utils";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import SdkConfig from "../../../../../src/SdkConfig";
-import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
-import { IConfigOptions } from "../../../../../src/IConfigOptions";
+import { type ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
+import { type IConfigOptions } from "../../../../../src/IConfigOptions";
 import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
-import { IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
+import { type IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
 import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 
diff --git a/test/unit-tests/components/views/dialogs/LogoutDialog-test.tsx b/test/unit-tests/components/views/dialogs/LogoutDialog-test.tsx
index 6c1315d5ac5256589989ee868cd71411fe8da901..cee615380fee5391f06d157e68373eb0f0bc2d8b 100644
--- a/test/unit-tests/components/views/dialogs/LogoutDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/LogoutDialog-test.tsx
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { CryptoApi, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
-import { fireEvent, render, RenderResult, screen } from "jest-matrix-react";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { fireEvent, render, type RenderResult, screen } from "jest-matrix-react";
 
 import { filterConsole, getMockClientWithEventEmitter, mockClientMethodsCrypto } from "../../../../test-utils";
 import LogoutDialog from "../../../../../src/components/views/dialogs/LogoutDialog";
diff --git a/test/unit-tests/components/views/dialogs/MessageEditHistoryDialog-test.tsx b/test/unit-tests/components/views/dialogs/MessageEditHistoryDialog-test.tsx
index 1969867bcd906d2007249af252fbd4d3a0d204d3..2f0b0a497442999e055bfe067f6c1087e4152aaf 100644
--- a/test/unit-tests/components/views/dialogs/MessageEditHistoryDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/MessageEditHistoryDialog-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult, waitForElementToBeRemoved } from "jest-matrix-react";
+import { render, type RenderResult, waitForElementToBeRemoved } from "jest-matrix-react";
 import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import type { MatrixClient } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/components/views/dialogs/ReportRoomDialog-test.tsx b/test/unit-tests/components/views/dialogs/ReportRoomDialog-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2357e341a18e8fdd3ebb8da2add35dd397f0edf0
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/ReportRoomDialog-test.tsx
@@ -0,0 +1,72 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { render } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+
+import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog";
+import SdkConfig from "../../../../../src/SdkConfig";
+import { stubClient } from "../../../../test-utils";
+
+const ROOM_ID = "!foo:bar";
+const REASON = "This room is bad!";
+
+describe("ReportRoomDialog", () => {
+    const onFinished: jest.Mock<any, any> = jest.fn();
+    const reportRoom: jest.Mock<any, any> = jest.fn();
+    beforeEach(() => {
+        jest.resetAllMocks();
+        const client = stubClient();
+        client.reportRoom = reportRoom;
+
+        SdkConfig.put({
+            report_event: {
+                admin_message_md: `
+# You should know
+
+This doesn't actually go **anywhere**.`,
+            },
+        });
+    });
+
+    afterEach(() => {
+        SdkConfig.reset();
+    });
+
+    it("can close the dialog", async () => {
+        const { getByTestId } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
+        await userEvent.click(getByTestId("dialog-cancel-button"));
+        expect(onFinished).toHaveBeenCalledWith(false);
+    });
+
+    it("displays admin message", async () => {
+        const { container } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
+        expect(container).toMatchSnapshot();
+    });
+
+    it("can submit a report without leaving the room", async () => {
+        const { getByLabelText, getByRole } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
+
+        await userEvent.type(getByLabelText("Describe the reason"), REASON);
+        await userEvent.click(getByRole("button", { name: "Send report" }));
+
+        expect(reportRoom).toHaveBeenCalledWith(ROOM_ID, REASON);
+        expect(onFinished).toHaveBeenCalledWith(false);
+    });
+
+    it("can submit a report and leave the room", async () => {
+        const { getByLabelText, getByRole } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
+
+        await userEvent.type(getByLabelText("Describe the reason"), REASON);
+        await userEvent.click(getByRole("switch", { name: "Leave room" }));
+        await userEvent.click(getByRole("button", { name: "Send report" }));
+
+        expect(reportRoom).toHaveBeenCalledWith(ROOM_ID, REASON);
+        expect(onFinished).toHaveBeenCalledWith(true);
+    });
+});
diff --git a/test/unit-tests/components/views/dialogs/ServerPickerDialog-test.tsx b/test/unit-tests/components/views/dialogs/ServerPickerDialog-test.tsx
index b4a4b6e756ba029d21662cceb74a157dd7ce21ec..7dbb08ac2061da161dc98d0621ae71f89dc0a4fe 100644
--- a/test/unit-tests/components/views/dialogs/ServerPickerDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ServerPickerDialog-test.tsx
@@ -13,7 +13,7 @@ import fetchMock from "fetch-mock-jest";
 import ServerPickerDialog from "../../../../../src/components/views/dialogs/ServerPickerDialog";
 import SdkConfig from "../../../../../src/SdkConfig";
 import { flushPromises } from "../../../../test-utils";
-import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
+import { type ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
 
 /** The matrix versions our mock server claims to support */
 const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
diff --git a/test/unit-tests/components/views/dialogs/ShareDialog-test.tsx b/test/unit-tests/components/views/dialogs/ShareDialog-test.tsx
index 93beaec711df88f6dc64bf1678f14c1cb6d838a2..f4c4154cf000eef9a53f60ab134f456bcd005a53 100644
--- a/test/unit-tests/components/views/dialogs/ShareDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/ShareDialog-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 import { render, screen, act } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { waitFor } from "@testing-library/dom";
diff --git a/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx b/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx
index 776aaff17db12035538d6f2aa4e527e4bf459861..1c343a30a707b85bf6df011b80b4034a90a6a6b9 100644
--- a/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx
@@ -10,11 +10,11 @@ import React from "react";
 import { mocked } from "jest-mock";
 import {
     ConnectionError,
-    IProtocol,
-    IPublicRoomsChunkRoom,
+    type IProtocol,
+    type IPublicRoomsChunkRoom,
     JoinRule,
-    MatrixClient,
-    Room,
+    type MatrixClient,
+    type Room,
     RoomMember,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
@@ -353,12 +353,12 @@ describe("Spotlight Dialog", () => {
         });
 
         it("should find Rooms", () => {
-            expect(options).toHaveLength(4);
+            expect(options).toHaveLength(5);
             expect(options[0]!.innerHTML).toContain(testRoom.name);
         });
 
         it("should not find LocalRooms", () => {
-            expect(options).toHaveLength(4);
+            expect(options).toHaveLength(5);
             expect(options[0]!.innerHTML).not.toContain(testLocalRoom.name);
         });
     });
@@ -648,4 +648,20 @@ describe("Spotlight Dialog", () => {
             });
         });
     });
+
+    it("should allow jumping into message search", async () => {
+        const onFinished = jest.fn();
+        render(<SpotlightDialog initialText="search term" onFinished={onFinished} />);
+        jest.advanceTimersByTime(200);
+        await flushPromisesWithFakeTimers();
+
+        fireEvent.click(screen.getByText("Messages"));
+
+        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith(
+            expect.objectContaining({
+                action: Action.FocusMessageSearch,
+                initialText: "search term",
+            }),
+        );
+    });
 });
diff --git a/test/unit-tests/components/views/dialogs/UntrustedDeviceDialog-test.tsx b/test/unit-tests/components/views/dialogs/UntrustedDeviceDialog-test.tsx
index f0a3fdfb8be3fa918a78a89a9d77bc4ab58663d4..a9486ca35ef4de4153fb4ba40d4ee3f9899a2691 100644
--- a/test/unit-tests/components/views/dialogs/UntrustedDeviceDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/UntrustedDeviceDialog-test.tsx
@@ -6,7 +6,7 @@
  */
 
 import React from "react";
-import { Device, MatrixClient, User } from "matrix-js-sdk/src/matrix";
+import { Device, type MatrixClient, User } from "matrix-js-sdk/src/matrix";
 import { render, screen } from "jest-matrix-react";
 
 import { stubClient } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx b/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx
index c6c3eeb1427107b3fef02454710fc80685543167..8ffe9e3de62dd398aa75ca9b1c1dcc82c432dcf8 100644
--- a/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 import { render, screen } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
-import SettingsStore, { CallbackFn } from "../../../../../src/settings/SettingsStore";
+import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
 import SdkConfig from "../../../../../src/SdkConfig";
 import { UserTab } from "../../../../../src/components/views/dialogs/UserTab";
 import UserSettingsDialog from "../../../../../src/components/views/dialogs/UserSettingsDialog";
@@ -27,7 +27,7 @@ import {
 import { UIFeature } from "../../../../../src/settings/UIFeature";
 import { SettingLevel } from "../../../../../src/settings/SettingLevel";
 import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
-import { FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
+import { type FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
 
 mockPlatformPeg({
     supportsSpellCheckSettings: jest.fn().mockReturnValue(false),
@@ -57,9 +57,9 @@ describe("<UserSettingsDialog />", () => {
 
     let sdkContext: SdkContextClass;
     const defaultProps = { onFinished: jest.fn() };
-    const getComponent = (props: Partial<typeof defaultProps & { initialTabId?: UserTab }> = {}): ReactElement => (
-        <UserSettingsDialog sdkContext={sdkContext} {...defaultProps} {...props} />
-    );
+    const getComponent = (
+        props: Partial<typeof defaultProps & { initialTabId?: UserTab; props: Record<string, any> }> = {},
+    ): ReactElement => <UserSettingsDialog sdkContext={sdkContext} {...defaultProps} {...props} />;
 
     beforeEach(() => {
         jest.clearAllMocks();
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmKeyStorageOffDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmKeyStorageOffDialog-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..9e0def25698a5cc9b100456f016195ea93ea162c
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmKeyStorageOffDialog-test.tsx.snap
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ConfirmKeyStorageOffDialog renders 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Are you sure you want to keep key storage turned off?
+      </h2>
+    </div>
+    <span>
+      If you sign out of all your devices you will lose your message history and will need to verify all your existing contacts again. 
+      <br />
+      <a
+        href="https://element.io/help#encryption5"
+        rel="noreferrer noopener"
+        target="_blank"
+      >
+        Learn more 
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2"
+          />
+          <path
+            d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2"
+          />
+        </svg>
+      </a>
+    </span>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        class="_button_vczzf_8"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Go to Settings
+      </button>
+      <button
+        class="_button_vczzf_8"
+        data-kind="secondary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Yes, dismiss
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..c2ca9a568035f62c8d2a2510ad6d04daecdd5a6e
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmRejectInviteDialog-test.tsx.snap
@@ -0,0 +1,151 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ConfirmRejectInviteDialog can reject with options selected 1`] = `
+<div>
+  <div
+    data-focus-guard="true"
+    style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
+    tabindex="0"
+  />
+  <div
+    aria-describedby="mx_Dialog_content"
+    aria-labelledby="mx_BaseDialog_title"
+    class="mx_DeclineAndBlockInviteDialog mx_Dialog_fixedWidth"
+    data-focus-lock-disabled="false"
+    role="dialog"
+  >
+    <div
+      class="mx_Dialog_header"
+    >
+      <h1
+        class="mx_Heading_h3 mx_Dialog_title"
+        id="mx_BaseDialog_title"
+      >
+        Decline invitation
+      </h1>
+    </div>
+    <form
+      class="_root_19upo_16"
+    >
+      <p>
+        Are you sure you want to decline the invitation to join "foo"?
+      </p>
+      <div
+        class="mx_SettingsFlag"
+      >
+        <span
+          class="mx_SettingsFlag_label"
+        >
+          <div
+            id="mx_LabelledToggleSwitch_«r7»"
+          >
+            Ignore user
+          </div>
+          <span
+            class="mx_Caption"
+            id="mx_LabelledToggleSwitch_«r7»_caption"
+          >
+            You will not see any messages or room invites from this user.
+          </span>
+        </span>
+        <div
+          aria-checked="true"
+          aria-describedby="mx_LabelledToggleSwitch_«r7»_caption"
+          aria-disabled="false"
+          aria-labelledby="mx_LabelledToggleSwitch_«r7»"
+          class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
+          role="switch"
+          tabindex="0"
+        >
+          <div
+            class="mx_ToggleSwitch_ball"
+          />
+        </div>
+      </div>
+      <div
+        class="mx_SettingsFlag"
+      >
+        <span
+          class="mx_SettingsFlag_label"
+        >
+          <div
+            id="mx_LabelledToggleSwitch_«r8»"
+          >
+            Report room
+          </div>
+          <span
+            class="mx_Caption"
+            id="mx_LabelledToggleSwitch_«r8»_caption"
+          >
+            Report this room to your account provider.
+          </span>
+        </span>
+        <div
+          aria-checked="true"
+          aria-describedby="mx_LabelledToggleSwitch_«r8»_caption"
+          aria-disabled="false"
+          aria-labelledby="mx_LabelledToggleSwitch_«r8»"
+          class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
+          role="switch"
+          tabindex="0"
+        >
+          <div
+            class="mx_ToggleSwitch_ball"
+          />
+        </div>
+      </div>
+      <div
+        aria-disabled="false"
+        class="_field_19upo_26"
+      >
+        <label
+          class="_label_19upo_59"
+          for="mx_DeclineAndBlockInviteDialog_reason"
+        >
+          Reason
+        </label>
+        <textarea
+          class="mx_RoomReportTextArea"
+          id="mx_DeclineAndBlockInviteDialog_reason"
+          placeholder="Describe the reason for reporting the room."
+          rows="5"
+        >
+          I want to report this room
+        </textarea>
+      </div>
+      <div
+        class="mx_Dialog_buttons"
+      >
+        <span
+          class="mx_Dialog_buttons_row"
+        >
+          <button
+            data-testid="dialog-cancel-button"
+            type="button"
+          >
+            Cancel
+          </button>
+          <button
+            class="mx_Dialog_primary danger"
+            data-testid="dialog-primary-button"
+            type="button"
+          >
+            Decline invite
+          </button>
+        </span>
+      </div>
+    </form>
+    <div
+      aria-label="Close dialog"
+      class="mx_AccessibleButton mx_Dialog_cancelButton"
+      role="button"
+      tabindex="0"
+    />
+  </div>
+  <div
+    data-focus-guard="true"
+    style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
+    tabindex="0"
+  />
+</div>
+`;
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmUserActionDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmUserActionDialog-test.tsx.snap
index bf0fc314430405024428f6412964fb6fe59a043d..d48b62c57dcb712fbdb4a130c73562d6c7ea0f72 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmUserActionDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ConfirmUserActionDialog-test.tsx.snap
@@ -35,7 +35,7 @@ exports[`ConfirmUserActionDialog renders 1`] = `
           class="mx_ConfirmUserActionDialog_avatar"
         >
           <span
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="3"
             data-testid="avatar-img"
             data-type="round"
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap
index 622ed3206521d975ca27083a11c39fcd71eef5a2..2342bab71d1476c2addc63fe4b6ac229221db3cf 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap
@@ -99,6 +99,11 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
         >
           Server info
         </button>
+        <button
+          class="mx_DevTools_button"
+        >
+          End-to-end encryption
+        </button>
       </div>
       <div>
         <h3>
@@ -185,6 +190,31 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
             />
           </div>
         </div>
+        <form
+          class="_root_19upo_16"
+        >
+          <div
+            class="_field_19upo_26"
+          >
+            <label
+              class="_label_19upo_59"
+              for="radix-«r4»"
+            >
+              Element Call URL
+            </label>
+            <div
+              class="_controls_17lij_8"
+            >
+              <input
+                class="_control_sqdq4_10"
+                id="radix-«r4»"
+                name="input"
+                title=""
+                value=""
+              />
+            </div>
+          </div>
+        </form>
       </div>
     </div>
     <div
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap
index 4d2a2d54a486e6dff7b36c75ef626dbe91bc2762..bb11ed96b20b953657b6a69f16d450f44e655740 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ExportDialog-test.tsx.snap
@@ -150,28 +150,53 @@ exports[`<ExportDialog /> renders export dialog 1`] = `
         </span>
       </span>
     </div>
-    <span
-      class="mx_Checkbox mx_ExportDialog_attachments-checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+    <form
+      class="_root_19upo_16"
     >
-      <input
-        id="include-attachments"
-        type="checkbox"
-      />
-      <label
-        for="include-attachments"
+      <div
+        class="_inline-field_19upo_32 mx_ExportDialog_attachments-checkbox"
       >
         <div
-          class="mx_Checkbox_background"
+          class="_inline-field-control_19upo_44"
         >
           <div
-            class="mx_Checkbox_checkmark"
-          />
+            class="_container_1hel1_10"
+          >
+            <input
+              class="_input_1hel1_18"
+              id="include-attachments"
+              type="checkbox"
+            />
+            <div
+              class="_ui_1hel1_19"
+            >
+              <svg
+                aria-hidden="true"
+                fill="currentColor"
+                height="1em"
+                viewBox="0 0 24 24"
+                width="1em"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+            </div>
+          </div>
         </div>
-        <div>
-          Include Attachments
+        <div
+          class="_inline-field-body_19upo_38"
+        >
+          <label
+            class="_label_19upo_59"
+            for="include-attachments"
+          >
+            Include Attachments
+          </label>
         </div>
-      </label>
-    </span>
+      </div>
+    </form>
   </div>
   <div
     class="mx_Dialog_buttons"
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap
index cf01ac63b6b2816e2500d4f67de34e791dd3d08f..627cd1087a113c09631ca8db5a8709600c851b1c 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ManageRestrictedJoinRuleDialog-test.tsx.snap
@@ -59,53 +59,80 @@ exports[`<ManageRestrictedJoinRuleDialog /> should list spaces which are not par
         <h3>
           Other spaces you know
         </h3>
-        <label
+        <div
           class="mx_ManageRestrictedJoinRuleDialog_entry"
         >
-          <div>
-            <div>
-              <span
-                class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-                data-color="1"
-                data-testid="avatar-img"
-                data-type="round"
-                role="presentation"
-                style="--cpd-avatar-size: 20px;"
-              >
-                O
-              </span>
-              <span
-                class="mx_ManageRestrictedJoinRuleDialog_entry_name"
-              >
-                Other Space
-              </span>
-            </div>
-            <div
-              class="mx_ManageRestrictedJoinRuleDialog_entry_description"
-            >
-              0 members
-            </div>
-          </div>
-          <span
-            class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+          <form
+            class="_root_19upo_16"
           >
-            <input
-              id="checkbox_vY7Q4uEh9K"
-              type="checkbox"
-            />
-            <label
-              for="checkbox_vY7Q4uEh9K"
+            <div
+              class="_inline-field_19upo_32"
             >
               <div
-                class="mx_Checkbox_background"
+                class="_inline-field-control_19upo_44"
               >
                 <div
-                  class="mx_Checkbox_checkmark"
-                />
+                  class="_container_1hel1_10"
+                >
+                  <input
+                    aria-describedby="«r5»"
+                    class="_input_1hel1_18"
+                    id="checkbox_vY7Q4uEh9K"
+                    type="checkbox"
+                  />
+                  <div
+                    class="_ui_1hel1_19"
+                  >
+                    <svg
+                      aria-hidden="true"
+                      fill="currentColor"
+                      height="1em"
+                      viewBox="0 0 24 24"
+                      width="1em"
+                      xmlns="http://www.w3.org/2000/svg"
+                    >
+                      <path
+                        d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                      />
+                    </svg>
+                  </div>
+                </div>
+              </div>
+              <div
+                class="_inline-field-body_19upo_38"
+              >
+                <label
+                  class="_label_19upo_59"
+                  for="checkbox_vY7Q4uEh9K"
+                >
+                  <div>
+                    <span
+                      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+                      data-color="1"
+                      data-testid="avatar-img"
+                      data-type="round"
+                      role="none"
+                      style="--cpd-avatar-size: 20px;"
+                    >
+                      O
+                    </span>
+                    <span
+                      class="mx_ManageRestrictedJoinRuleDialog_entry_name"
+                    >
+                      Other Space
+                    </span>
+                  </div>
+                </label>
+                <span
+                  class="_message_19upo_85 _help-message_19upo_91"
+                  id="«r5»"
+                >
+                  0 members
+                </span>
               </div>
-            </label>
-          </span>
-        </label>
+            </div>
+          </form>
+        </div>
       </div>
     </div>
     <div
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ReportRoomDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ReportRoomDialog-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..ec84104044e145885512ca31155a95eac361f107
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ReportRoomDialog-test.tsx.snap
@@ -0,0 +1,128 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ReportRoomDialog displays admin message 1`] = `
+<div>
+  <div
+    data-focus-guard="true"
+    style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
+    tabindex="0"
+  />
+  <div
+    aria-describedby="mx_ReportEventDialog"
+    aria-labelledby="mx_BaseDialog_title"
+    class="mx_ReportRoomDialog mx_Dialog_fixedWidth"
+    data-focus-lock-disabled="false"
+    role="dialog"
+  >
+    <div
+      class="mx_Dialog_header"
+    >
+      <h1
+        class="mx_Heading_h3 mx_Dialog_title"
+        id="mx_BaseDialog_title"
+      >
+        Report room
+      </h1>
+    </div>
+    <form
+      class="_root_19upo_16"
+      id="mx_ReportEventDialog"
+    >
+      <div
+        class="_field_19upo_26"
+      >
+        <label
+          class="_label_19upo_59"
+          for="mx_ReportRoomDialog_reason"
+        >
+          Describe the reason
+        </label>
+        <textarea
+          id="mx_ReportRoomDialog_reason"
+          rows="5"
+        />
+        <span
+          class="_message_19upo_85 _help-message_19upo_91"
+          id="radix-«r8»"
+        >
+          Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them.
+        </span>
+      </div>
+      <p>
+        <h1>
+          You should know
+        </h1>
+        
+
+        <p>
+          This doesn't actually go 
+          <strong>
+            anywhere
+          </strong>
+          .
+        </p>
+        
+
+      </p>
+      <div
+        class="mx_SettingsFlag"
+      >
+        <span
+          class="mx_SettingsFlag_label"
+        >
+          <div
+            id="mx_LabelledToggleSwitch_«r9»"
+          >
+            Leave room
+          </div>
+        </span>
+        <div
+          aria-checked="false"
+          aria-disabled="false"
+          aria-labelledby="mx_LabelledToggleSwitch_«r9»"
+          class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
+          role="switch"
+          tabindex="0"
+        >
+          <div
+            class="mx_ToggleSwitch_ball"
+          />
+        </div>
+      </div>
+      <div
+        class="mx_Dialog_buttons"
+      >
+        <span
+          class="mx_Dialog_buttons_row"
+        >
+          <button
+            data-testid="dialog-cancel-button"
+            type="button"
+          >
+            Cancel
+          </button>
+          <button
+            class="mx_Dialog_primary danger"
+            data-testid="dialog-primary-button"
+            disabled=""
+            type="button"
+          >
+            Send report
+          </button>
+        </span>
+      </div>
+    </form>
+    <div
+      aria-label="Close dialog"
+      class="mx_AccessibleButton mx_Dialog_cancelButton"
+      role="button"
+      tabindex="0"
+    />
+  </div>
+  <div
+    data-focus-guard="true"
+    style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
+    tabindex="0"
+  />
+</div>
+`;
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap
index bdb0037cc412f760e9367e789f86dc1241cae3dc..633dd34fe01fa374ddfc4970b5091a208cbdce48 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ServerPickerDialog-test.tsx.snap
@@ -50,7 +50,7 @@ exports[`<ServerPickerDialog /> should render dialog 1`] = `
           class="mx_StyledRadioButton_content"
         >
           <span
-            aria-labelledby=":r0:"
+            aria-labelledby="«r0»"
             class="mx_Login_underlinedServerName"
             tabindex="0"
           >
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ShareDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ShareDialog-test.tsx.snap
index ab8b8ffb5826686b55ee4fa5a4b1f795e00abb81..12760c8ca4bff017c3f4b2ecec98ba67f54deb42 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/ShareDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/ShareDialog-test.tsx.snap
@@ -35,7 +35,7 @@ exports[`ShareDialog should not render the QR code if disabled 1`] = `
         </span>
       </div>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -50,7 +50,7 @@ exports[`ShareDialog should not render the QR code if disabled 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
@@ -180,7 +180,7 @@ exports[`ShareDialog should not render the socials if disabled 1`] = `
         </span>
       </div>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -195,7 +195,7 @@ exports[`ShareDialog should not render the socials if disabled 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
@@ -267,15 +267,15 @@ exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
       </div>
       <label>
         <div
-          class="_container_1wloq_18"
+          class="_container_1hel1_10"
         >
           <input
             checked=""
-            class="_input_1wloq_26"
+            class="_input_1hel1_18"
             type="checkbox"
           />
           <div
-            class="_ui_1wloq_27"
+            class="_ui_1hel1_19"
           >
             <svg
               aria-hidden="true"
@@ -286,7 +286,7 @@ exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+                d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
               />
             </svg>
           </div>
@@ -294,7 +294,7 @@ exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
         Link to selected message
       </label>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -309,7 +309,7 @@ exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
@@ -440,14 +440,14 @@ exports[`ShareDialog should render a share dialog for a room 1`] = `
       </div>
       <label>
         <div
-          class="_container_1wloq_18"
+          class="_container_1hel1_10"
         >
           <input
-            class="_input_1wloq_26"
+            class="_input_1hel1_18"
             type="checkbox"
           />
           <div
-            class="_ui_1wloq_27"
+            class="_ui_1hel1_19"
           >
             <svg
               aria-hidden="true"
@@ -458,7 +458,7 @@ exports[`ShareDialog should render a share dialog for a room 1`] = `
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+                d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
               />
             </svg>
           </div>
@@ -466,7 +466,7 @@ exports[`ShareDialog should render a share dialog for a room 1`] = `
         Link to most recent message
       </label>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -481,7 +481,7 @@ exports[`ShareDialog should render a share dialog for a room 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
@@ -611,7 +611,7 @@ exports[`ShareDialog should render a share dialog for a room member 1`] = `
         </span>
       </div>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -626,7 +626,7 @@ exports[`ShareDialog should render a share dialog for a room member 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
@@ -756,7 +756,7 @@ exports[`ShareDialog should render a share dialog for an URL 1`] = `
         </span>
       </div>
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -771,7 +771,7 @@ exports[`ShareDialog should render a share dialog for an URL 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+            d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
           />
         </svg>
         Copy link
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap
index b022313c42284fdfec3f97892e7a672c52b34b93..93aa25f3994680b257585da7130d22f1e27b802f 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap
@@ -24,7 +24,7 @@ exports[`<UnpinAllDialog /> should render 1`] = `
       </h1>
     </div>
     <span
-      class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+      class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
     >
       Make sure that you really want to remove all pinned messages. This action can’t be undone.
     </span>
@@ -32,7 +32,7 @@ exports[`<UnpinAllDialog /> should render 1`] = `
       class="mx_UnpinAllDialog_buttons"
     >
       <button
-        class="_button_i91xf_17 _destructive_i91xf_116"
+        class="_button_vczzf_8 _destructive_vczzf_107"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -41,7 +41,7 @@ exports[`<UnpinAllDialog /> should render 1`] = `
         Continue
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="tertiary"
         data-size="lg"
         role="button"
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap
index de47330ddfcedc41366f77c1e030434b3d819c78..3bdd88d373eeacec5f04d7982a707060090a39b6 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap
@@ -18,13 +18,13 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M9.175 13.825C9.958 14.608 10.9 15 12 15s2.042-.392 2.825-1.175C15.608 13.042 16 12.1 16 11s-.392-2.042-1.175-2.825C14.042 7.392 13.1 7 12 7s-2.042.392-2.825 1.175C8.392 8.958 8 9.9 8 11s.392 2.042 1.175 2.825Zm4.237-1.412A1.926 1.926 0 0 1 12 13c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 11c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 9c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412Z"
+        d="M9.175 13.825Q10.35 15 12 15t2.825-1.175T16 11t-1.175-2.825T12 7 9.175 8.175 8 11t1.175 2.825m4.237-1.412A1.93 1.93 0 0 1 12 13q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 11q0-.825.588-1.412A1.93 1.93 0 0 1 12 9q.825 0 1.412.588Q14 10.175 14 11t-.588 1.412"
       />
       <path
-        d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
+        d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0"
       />
       <path
-        d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
+        d="M16.23 18.792a13 13 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0q-.73.18-1.455.455a8 8 0 0 1-1.729-1.454q1.336-.618 2.709-.95A13.8 13.8 0 0 1 12 16q1.65 0 3.25.387 1.373.333 2.709.95a8 8 0 0 1-1.73 1.455"
       />
     </svg>
     <span
@@ -50,7 +50,7 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M3.5 20c-.417 0-.77-.146-1.063-.438A1.447 1.447 0 0 1 2 18.5c0-.417.146-.77.438-1.063A1.446 1.446 0 0 1 3.5 17H4V6c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 4h14a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 20 6H6v11h4.5c.417 0 .77.146 1.063.438.291.291.437.645.437 1.062 0 .417-.146.77-.438 1.063A1.446 1.446 0 0 1 10.5 20h-7ZM15 20a.968.968 0 0 1-.713-.288A.968.968 0 0 1 14 19V9c0-.283.096-.52.287-.713A.968.968 0 0 1 15 8h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v10c0 .283-.096.52-.288.712A.968.968 0 0 1 21 20h-6Zm1-3h4v-7h-4v7Z"
+        d="M3.5 20q-.625 0-1.062-.437A1.45 1.45 0 0 1 2 18.5q0-.625.438-1.062A1.45 1.45 0 0 1 3.5 17H4V6q0-.824.588-1.412A1.93 1.93 0 0 1 6 4h14q.424 0 .712.287Q21 4.576 21 5t-.288.713A.97.97 0 0 1 20 6H6v11h4.5q.624 0 1.063.438.437.437.437 1.062t-.437 1.063A1.45 1.45 0 0 1 10.5 20zM15 20a.97.97 0 0 1-.713-.288A.97.97 0 0 1 14 19V9q0-.424.287-.713A.97.97 0 0 1 15 8h6q.424 0 .712.287Q22 8.576 22 9v10q0 .424-.288.712A.97.97 0 0 1 21 20zm1-3h4v-7h-4z"
       />
     </svg>
     <span
@@ -76,7 +76,7 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 16c1.25 0 2.313-.438 3.188-1.313.874-.874 1.312-1.937 1.312-3.187 0-1.25-.438-2.313-1.313-3.188C14.313 7.439 13.25 7 12 7c-1.25 0-2.312.438-3.187 1.313C7.938 9.187 7.5 10.25 7.5 11.5c0 1.25.438 2.313 1.313 3.188C9.688 15.562 10.75 16 12 16Zm0-1.8c-.75 0-1.387-.262-1.912-.787A2.604 2.604 0 0 1 9.3 11.5c0-.75.263-1.387.787-1.912A2.604 2.604 0 0 1 12 8.8c.75 0 1.387.262 1.912.787.525.526.788 1.163.788 1.913s-.262 1.387-.787 1.912A2.604 2.604 0 0 1 12 14.2Zm0 4.8c-2.317 0-4.433-.613-6.35-1.837-1.917-1.226-3.367-2.88-4.35-4.963a.812.812 0 0 1-.1-.313 2.93 2.93 0 0 1 0-.774.812.812 0 0 1 .1-.313c.983-2.083 2.433-3.738 4.35-4.963C7.567 4.614 9.683 4 12 4c2.317 0 4.433.612 6.35 1.838 1.917 1.224 3.367 2.879 4.35 4.962a.81.81 0 0 1 .1.313 2.925 2.925 0 0 1 0 .774.81.81 0 0 1-.1.313c-.983 2.083-2.433 3.738-4.35 4.963C16.433 18.387 14.317 19 12 19Zm0-2a9.544 9.544 0 0 0 5.188-1.488A9.773 9.773 0 0 0 20.8 11.5a9.773 9.773 0 0 0-3.613-4.013A9.544 9.544 0 0 0 12 6a9.545 9.545 0 0 0-5.187 1.487A9.773 9.773 0 0 0 3.2 11.5a9.773 9.773 0 0 0 3.613 4.012A9.544 9.544 0 0 0 12 17Z"
+        d="M12 16q1.875 0 3.188-1.312Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7 8.813 8.313 7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.787A2.6 2.6 0 0 1 9.3 11.5q0-1.125.787-1.912A2.6 2.6 0 0 1 12 8.8q1.125 0 1.912.787.788.788.788 1.913t-.787 1.912A2.6 2.6 0 0 1 12 14.2m0 4.8q-3.475 0-6.35-1.837Q2.775 15.324 1.3 12.2a.8.8 0 0 1-.1-.312 3 3 0 0 1 0-.775.8.8 0 0 1 .1-.313q1.475-3.125 4.35-4.962Q8.525 4 12 4t6.35 1.838T22.7 10.8a.8.8 0 0 1 .1.313 3 3 0 0 1 0 .774.8.8 0 0 1-.1.313q-1.475 3.125-4.35 4.963Q15.475 19 12 19m0-2a9.54 9.54 0 0 0 5.188-1.488A9.77 9.77 0 0 0 20.8 11.5a9.77 9.77 0 0 0-3.613-4.012A9.54 9.54 0 0 0 12 6a9.55 9.55 0 0 0-5.187 1.487A9.77 9.77 0 0 0 3.2 11.5a9.77 9.77 0 0 0 3.613 4.012A9.54 9.54 0 0 0 12 17"
       />
     </svg>
     <span
@@ -102,7 +102,7 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 3c7 0 7 7 7 7v6l1.293 1.293c.63.63.184 1.707-.707 1.707H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-7 7-7Zm5 7.01v-.022l-.009-.146a6.591 6.591 0 0 0-.073-.607 6.608 6.608 0 0 0-.582-1.84c-.319-.638-.766-1.215-1.398-1.637C14.318 5.344 13.4 5 12 5c-1.4 0-2.317.344-2.937.758-.633.422-1.08.999-1.4 1.636a6.606 6.606 0 0 0-.58 1.841A6.596 6.596 0 0 0 7 9.988v6.84L6.828 17h10.344L17 16.828V10.01ZM12 22a2 2 0 0 1-2-2h4a2 2 0 0 1-2 2Z"
+        d="M12 3c7 0 7 7 7 7v6l1.293 1.293c.63.63.184 1.707-.707 1.707H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-7 7-7m5 7.01v-.022l-.009-.146a7 7 0 0 0-.073-.607 6.6 6.6 0 0 0-.582-1.84c-.319-.638-.766-1.215-1.398-1.637C14.318 5.344 13.4 5 12 5s-2.317.344-2.937.758c-.633.422-1.08.999-1.4 1.636a6.6 6.6 0 0 0-.58 1.841A7 7 0 0 0 7 9.988v6.84L6.828 17h10.344L17 16.828zM12 22a2 2 0 0 1-2-2h4a2 2 0 0 1-2 2"
       />
     </svg>
     <span
@@ -129,7 +129,7 @@ NodeList [
     >
       <path
         clip-rule="evenodd"
-        d="M6.5 2h11a4.5 4.5 0 1 1 0 9h-11a4.5 4.5 0 0 1 0-9Zm0 2h7.258A4.479 4.479 0 0 0 13 6.5c0 .925.28 1.785.758 2.5H6.5a2.5 2.5 0 0 1 0-5ZM15 6.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm-13 11A4.5 4.5 0 0 1 6.5 13h11a4.5 4.5 0 1 1 0 9h-11A4.5 4.5 0 0 1 2 17.5Zm8.242-2.5H17.5a2.5 2.5 0 0 1 0 5h-7.258A4.478 4.478 0 0 0 11 17.5c0-.925-.28-1.785-.758-2.5ZM6.5 15a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5Z"
+        d="M6.5 2h11a4.5 4.5 0 1 1 0 9h-11a4.5 4.5 0 0 1 0-9m0 2h7.258A4.5 4.5 0 0 0 13 6.5c0 .925.28 1.785.758 2.5H6.5a2.5 2.5 0 0 1 0-5M15 6.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0m-13 11A4.5 4.5 0 0 1 6.5 13h11a4.5 4.5 0 1 1 0 9h-11q-.233 0-.46-.023A4.5 4.5 0 0 1 2 17.5m8.242-2.5H17.5a2.5 2.5 0 0 1 0 5h-7.258A4.5 4.5 0 0 0 11 17.5c0-.925-.28-1.785-.758-2.5M6.5 15a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5"
         fill-rule="evenodd"
       />
     </svg>
@@ -156,11 +156,11 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M5.188 8v2h2V8h-2Zm3.875 0v2h2V8h-2Zm3.875 0v2h2V8h-2Zm3.875 0v2h2V8h-2ZM5.188 11.531v2h2v-2h-2Zm3.875 0v2h2v-2h-2Zm3.875 0v2h2v-2h-2Zm3.875 0v2h2v-2h-2ZM9 15a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2H9Z"
+        d="M5.188 8v2h2V8zm3.875 0v2h2V8zm3.875 0v2h2V8zm3.875 0v2h2V8zM5.188 11.531v2h2v-2zm3.875 0v2h2v-2zm3.875 0v2h2v-2zm3.875 0v2h2v-2zM9 15a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2z"
       />
       <path
         clip-rule="evenodd"
-        d="M2 6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6Zm2 0h16v12H4V6Z"
+        d="M2 6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm2 0h16v12H4z"
         fill-rule="evenodd"
       />
     </svg>
@@ -188,7 +188,7 @@ NodeList [
     >
       <path
         clip-rule="evenodd"
-        d="M18 3a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h12Zm-8 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-8V5ZM8 19H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2v14Z"
+        d="M18 3a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4zm-8 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-8zM8 19H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2z"
         fill-rule="evenodd"
       />
     </svg>
@@ -215,7 +215,7 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V10c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 8h1V6c0-1.383.487-2.563 1.463-3.538C9.438 1.487 10.617 1 12 1s2.563.488 3.537 1.462C16.512 3.438 17 4.617 17 6v2h1c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412v10c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm0-2h12V10H6v10ZM9 8h6V6c0-.833-.292-1.542-.875-2.125A2.893 2.893 0 0 0 12 3c-.833 0-1.542.292-2.125.875A2.893 2.893 0 0 0 9 6v2Z"
+        d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zm0-2h12V10H6zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
       />
     </svg>
     <span
@@ -241,7 +241,7 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M7 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 5 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 7 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 7 14Zm0 4c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.117 0 2.13.275 3.037.825A6.212 6.212 0 0 1 12.2 9h8.375a1.033 1.033 0 0 1 .725.3l2 2c.1.1.17.208.212.325.042.117.063.242.063.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-3.175 3.175a.946.946 0 0 1-.3.2c-.117.05-.233.083-.35.1a.832.832 0 0 1-.35-.025.884.884 0 0 1-.325-.175L17.5 15l-1.425 1.075a.945.945 0 0 1-.887.15.859.859 0 0 1-.288-.15L13.375 15H12.2a6.212 6.212 0 0 1-2.162 2.175C9.128 17.725 8.117 18 7 18Zm0-2c.933 0 1.754-.283 2.463-.85A4.032 4.032 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.032 4.032 0 0 0-1.412-2.15C8.754 8.283 7.933 8 7 8c-1.1 0-2.042.392-2.825 1.175C3.392 9.958 3 10.9 3 12s.392 2.042 1.175 2.825C4.958 15.608 5.9 16 7 16Z"
+        d="M7 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 5 12q0-.825.588-1.412A1.93 1.93 0 0 1 7 10q.824 0 1.412.588Q9 11.175 9 12t-.588 1.412A1.93 1.93 0 0 1 7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.676 0 3.037.825A6.2 6.2 0 0 1 12.2 9h8.375q.2 0 .387.075.188.075.338.225l2 2q.15.15.212.325.063.175.063.375t-.062.375a.9.9 0 0 1-.213.325l-3.175 3.175a1 1 0 0 1-.3.2q-.175.075-.35.1a.8.8 0 0 1-.35-.025.9.9 0 0 1-.325-.175L17.5 15l-1.425 1.075a.95.95 0 0 1-.887.15.9.9 0 0 1-.288-.15L13.375 15H12.2a6.2 6.2 0 0 1-2.162 2.175Q8.675 18 7 18m0-2q1.4 0 2.463-.85A4.03 4.03 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.03 4.03 0 0 0-1.412-2.15Q8.4 8 7 8 5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
       />
     </svg>
     <span
@@ -267,15 +267,15 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 5a1 1 0 0 1-1-1V3a1 1 0 1 1 2 0v1a1 1 0 0 1-1 1Zm-7.071-.071a1 1 0 0 1 1.414 0l.707.707A1 1 0 0 1 5.636 7.05l-.707-.707a1 1 0 0 1 0-1.414Z"
+        d="M12 5a1 1 0 0 1-1-1V3a1 1 0 1 1 2 0v1a1 1 0 0 1-1 1m-7.071-.071a1 1 0 0 1 1.414 0l.707.707A1 1 0 0 1 5.636 7.05l-.707-.707a1 1 0 0 1 0-1.414"
       />
       <path
         clip-rule="evenodd"
-        d="M15.734 15.325C15.316 15.795 15 16.371 15 17v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2c0-.63-.316-1.205-.734-1.675a5 5 0 1 1 7.468 0Zm-1.493-1.33a3 3 0 1 0-4.482 0c.433.486.894 1.166 1.112 2.005h2.258c.218-.84.679-1.52 1.112-2.005ZM13 18h-2v1h2v-1Z"
+        d="M15.734 15.325C15.316 15.795 15 16.371 15 17v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2c0-.63-.316-1.205-.734-1.675a5 5 0 1 1 7.468 0m-1.493-1.33a3 3 0 1 0-4.482 0c.433.486.894 1.166 1.112 2.005h2.258c.218-.84.679-1.52 1.112-2.005M13 18h-2v1h2z"
         fill-rule="evenodd"
       />
       <path
-        d="M2 12a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm18-1a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2h-1Zm-3.05-5.364a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707Z"
+        d="M2 12a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1m18-1a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2zm-3.05-5.364a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414z"
       />
     </svg>
     <span
@@ -301,10 +301,10 @@ NodeList [
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439c-.122.126-.24.243-.352.355-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8Zm1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+        d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439q-.183.188-.352.355c-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8m1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0"
       />
       <path
-        d="M8.1 21.212A9.738 9.738 0 0 0 12 22a9.738 9.738 0 0 0 3.9-.788 10.098 10.098 0 0 0 3.175-2.137c.9-.9 1.613-1.958 2.137-3.175A9.738 9.738 0 0 0 22 12a9.738 9.738 0 0 0-.788-3.9 10.099 10.099 0 0 0-2.137-3.175c-.9-.9-1.958-1.612-3.175-2.137A9.738 9.738 0 0 0 12 2a9.738 9.738 0 0 0-3.9.788 10.099 10.099 0 0 0-3.175 2.137c-.9.9-1.612 1.958-2.137 3.175A9.738 9.738 0 0 0 2 12a9.74 9.74 0 0 0 .788 3.9 10.098 10.098 0 0 0 2.137 3.175c.9.9 1.958 1.613 3.175 2.137Zm9.575-3.537C16.125 19.225 14.233 20 12 20c-2.233 0-4.125-.775-5.675-2.325C4.775 16.125 4 14.233 4 12c0-2.233.775-4.125 2.325-5.675C7.875 4.775 9.767 4 12 4c2.233 0 4.125.775 5.675 2.325C19.225 7.875 20 9.767 20 12c0 2.233-.775 4.125-2.325 5.675Z"
+        d="M8.1 21.213A9.7 9.7 0 0 0 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 2.075.788 3.9a10.1 10.1 0 0 0 2.137 3.175q1.35 1.35 3.175 2.137m9.575-3.538Q15.35 20 12 20t-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675"
       />
     </svg>
     <span
diff --git a/test/unit-tests/components/views/dialogs/devtools/Crypto-test.tsx b/test/unit-tests/components/views/dialogs/devtools/Crypto-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..66907eea8b08ab0419e0fcb45b29aa8a7566a189
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/devtools/Crypto-test.tsx
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { render, screen, waitFor } from "jest-matrix-react";
+import { type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+import { Crypto } from "../../../../../../src/components/views/dialogs/devtools/Crypto";
+
+describe("<Crypto />", () => {
+    let matrixClient: MatrixClient;
+    beforeEach(() => {
+        matrixClient = createTestClient();
+    });
+
+    function renderComponent() {
+        return render(<Crypto onBack={jest.fn} />, withClientContextRenderOptions(matrixClient));
+    }
+
+    it("should display message if crypto is not available", async () => {
+        jest.spyOn(matrixClient, "getCrypto").mockReturnValue(undefined);
+        renderComponent();
+        expect(screen.getByText("Cryptographic module is not available")).toBeInTheDocument();
+    });
+
+    describe("<KeyStorage />", () => {
+        it("should display loading spinner while loading", async () => {
+            jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(() => new Promise(() => {}));
+            renderComponent();
+            await waitFor(() => expect(screen.getByLabelText("Loading…")).toBeInTheDocument());
+        });
+
+        it("should display when the key storage data are missing", async () => {
+            renderComponent();
+            await waitFor(() => expect(screen.getByRole("table", { name: "Key Storage" })).toBeInTheDocument());
+            expect(screen.getByRole("table", { name: "Key Storage" })).toMatchSnapshot();
+        });
+
+        it("should display when the key storage data are available", async () => {
+            jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
+                algorithm: "m.megolm_backup.v1",
+                version: "1",
+            } as unknown as KeyBackupInfo);
+            jest.spyOn(matrixClient, "isKeyBackupKeyStored").mockResolvedValue({});
+            jest.spyOn(matrixClient.getCrypto()!, "getSessionBackupPrivateKey").mockResolvedValue(new Uint8Array(32));
+            jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("2");
+            jest.spyOn(matrixClient.secretStorage, "hasKey").mockResolvedValue(true);
+            jest.spyOn(matrixClient.getCrypto()!, "isSecretStorageReady").mockResolvedValue(true);
+
+            renderComponent();
+            await waitFor(() => expect(screen.getByRole("table", { name: "Key Storage" })).toBeInTheDocument());
+            expect(screen.getByRole("table", { name: "Key Storage" })).toMatchSnapshot();
+        });
+    });
+
+    describe("<CrossSigning />", () => {
+        it("should display loading spinner while loading", async () => {
+            jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockImplementation(
+                () => new Promise(() => {}),
+            );
+            renderComponent();
+            await waitFor(() => expect(screen.getByLabelText("Loading…")).toBeInTheDocument());
+        });
+
+        it("should display when the cross-signing data are missing", async () => {
+            renderComponent();
+            await waitFor(() => expect(screen.getByRole("table", { name: "Cross-signing" })).toBeInTheDocument());
+            expect(screen.getByRole("table", { name: "Cross-signing" })).toMatchSnapshot();
+        });
+
+        it("should display when the cross-signing data are available", async () => {
+            jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
+                publicKeysOnDevice: true,
+                privateKeysInSecretStorage: true,
+                privateKeysCachedLocally: {
+                    masterKey: true,
+                    selfSigningKey: true,
+                    userSigningKey: true,
+                },
+            });
+            jest.spyOn(matrixClient.getCrypto()!, "isCrossSigningReady").mockResolvedValue(true);
+
+            renderComponent();
+            await waitFor(() => expect(screen.getByRole("table", { name: "Cross-signing" })).toBeInTheDocument());
+            expect(screen.getByRole("table", { name: "Cross-signing" })).toMatchSnapshot();
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap b/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..c1f3f56902cf34d7e8fd8716364f561beedae046
--- /dev/null
+++ b/test/unit-tests/components/views/dialogs/devtools/__snapshots__/Crypto-test.tsx.snap
@@ -0,0 +1,289 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data are available 1`] = `
+<table
+  aria-label="Cross-signing"
+>
+  <thead>
+    Cross-signing
+  </thead>
+  <tbody>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing status:
+      </th>
+      <td>
+        Cross-signing is ready for use.
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing public keys:
+      </th>
+      <td>
+        in memory
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing private keys:
+      </th>
+      <td>
+        in secret storage
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Master private key:
+      </th>
+      <td>
+        cached locally
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Self signing private key:
+      </th>
+      <td>
+        cached locally
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        User signing private key:
+      </th>
+      <td>
+        cached locally
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
+
+exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data are missing 1`] = `
+<table
+  aria-label="Cross-signing"
+>
+  <thead>
+    Cross-signing
+  </thead>
+  <tbody>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing status:
+      </th>
+      <td>
+        Cross-signing is not set up.
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing public keys:
+      </th>
+      <td>
+        not found
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Cross-signing private keys:
+      </th>
+      <td>
+        not found in storage
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Master private key:
+      </th>
+      <td>
+        not found locally
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Self signing private key:
+      </th>
+      <td>
+        not found locally
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        User signing private key:
+      </th>
+      <td>
+        not found locally
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
+
+exports[`<Crypto /> <KeyStorage /> should display when the key storage data are available 1`] = `
+<table
+  aria-label="Key Storage"
+>
+  <thead>
+    Key Storage
+  </thead>
+  <tbody>
+    <tr>
+      <th
+        scope="row"
+      >
+        Latest backup version on server:
+      </th>
+      <td>
+        1 (Algorithm: m.megolm_backup.v1)
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Backup key stored:
+      </th>
+      <td>
+        in secret storage
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Active backup version:
+      </th>
+      <td>
+        2
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Backup key cached:
+      </th>
+      <td>
+        cached locally, well formed
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Secret storage public key:
+      </th>
+      <td>
+        in account data
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Secret storage:
+      </th>
+      <td>
+        ready
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
+
+exports[`<Crypto /> <KeyStorage /> should display when the key storage data are missing 1`] = `
+<table
+  aria-label="Key Storage"
+>
+  <thead>
+    Key Storage
+  </thead>
+  <tbody>
+    <tr>
+      <th
+        scope="row"
+      >
+        Latest backup version on server:
+      </th>
+      <td>
+        Your keys are not being backed up from this session.
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Backup key stored:
+      </th>
+      <td>
+        not stored
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Active backup version:
+      </th>
+      <td>
+        None
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Backup key cached:
+      </th>
+      <td>
+        not found locally, unexpected type
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Secret storage public key:
+      </th>
+      <td>
+        not found
+      </td>
+    </tr>
+    <tr>
+      <th
+        scope="row"
+      >
+        Secret storage:
+      </th>
+      <td>
+        not ready
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
diff --git a/test/unit-tests/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx b/test/unit-tests/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx
index 1da0bc7be8cd3b523a4d5e0c914f9c4d449cef27..000f6efdb41b63b582b9a47092f16be64756ac0d 100644
--- a/test/unit-tests/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/security/CreateSecretStorageDialog-test.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { render, RenderResult, screen } from "jest-matrix-react";
+import { render, type RenderResult, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import React from "react";
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
 import { sleep } from "matrix-js-sdk/src/utils";
 
 import { filterConsole, flushPromises, stubClient } from "../../../../../test-utils";
@@ -48,7 +48,7 @@ describe("CreateSecretStorageDialog", () => {
         expect(result.container).toMatchSnapshot();
         await userEvent.click(result.getByRole("button", { name: "Continue" }));
 
-        await screen.findByText("Save your Security Key");
+        await screen.findByText("Save your Recovery Key");
         expect(result.container).toMatchSnapshot();
         // Copy the key to enable the continue button
         await userEvent.click(screen.getByRole("button", { name: "Copy" }));
@@ -66,7 +66,7 @@ describe("CreateSecretStorageDialog", () => {
             "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
         );
         await userEvent.click(screen.getByRole("button", { name: "Continue" }));
-        await screen.findByText("Save your Security Key");
+        await screen.findByText("Save your Recovery Key");
         await userEvent.click(screen.getByRole("button", { name: "Copy" }));
         await userEvent.click(screen.getByRole("button", { name: "Continue" }));
 
@@ -108,7 +108,7 @@ describe("CreateSecretStorageDialog", () => {
         });
         result.getByRole("button", { name: "Continue" }).click();
 
-        await result.findByText(/Save your Security Key/);
+        await result.findByText(/Save your Recovery Key/);
         result.getByRole("button", { name: "Copy" }).click();
 
         // Resetting should reset secret storage, cross signing, and key
diff --git a/test/unit-tests/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx b/test/unit-tests/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx
index e4dc75062c0a3f228a1c68742a98d3ea13c34622..004e2e5f8b584325fe5544e50342ed6b11c54ee0 100644
--- a/test/unit-tests/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/security/ExportE2eKeysDialog-test.tsx
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { IMegolmSessionData } from "matrix-js-sdk/src/matrix";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { type IMegolmSessionData } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
 
 import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
 import ExportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog";
diff --git a/test/unit-tests/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx b/test/unit-tests/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx
index f9dcc1773733acd4f290d032de60083097bda952..e45b408df305929fafe17123558977c5d43c1efa 100644
--- a/test/unit-tests/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/security/ImportE2eKeysDialog-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { fireEvent, render, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
 
 import ImportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog";
 import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
diff --git a/test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx b/test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx
index 4fc42f589325a1b4a85068e5049d19376be44f01..e2ba7562f714225dc6ceb5958675af791a8b4fa7 100644
--- a/test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx
@@ -8,8 +8,8 @@
 import React from "react";
 import { screen, render, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
 // Needed to be able to mock decodeRecoveryKey
 // eslint-disable-next-line no-restricted-imports
 import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
@@ -32,7 +32,7 @@ describe("<RestoreKeyBackupDialog />", () => {
 
     it("should render", async () => {
         const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
-        await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
         expect(asFragment()).toMatchSnapshot();
     });
 
@@ -41,19 +41,19 @@ describe("<RestoreKeyBackupDialog />", () => {
             throw new Error("Invalid recovery key");
         });
         const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
-        await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
 
         await userEvent.type(screen.getByRole("textbox"), "invalid key");
-        await waitFor(() => expect(screen.getByText("👎 Not a valid Security Key")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("👎 Not a valid Recovery Key")).toBeInTheDocument());
         expect(asFragment()).toMatchSnapshot();
     });
 
     it("should not raise an error when recovery is valid", async () => {
         const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
-        await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
 
         await userEvent.type(screen.getByRole("textbox"), "valid key");
-        await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("👍 This looks like a valid Recovery Key!")).toBeInTheDocument());
         expect(asFragment()).toMatchSnapshot();
     });
 
@@ -79,7 +79,7 @@ describe("<RestoreKeyBackupDialog />", () => {
         expect(asFragment()).toMatchSnapshot();
     });
 
-    it("should restore key backup when security key is filled by user", async () => {
+    it("should restore key backup when Recovery key is filled by user", async () => {
         jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
             // Reject when trying to restore from cache
             .mockRejectedValueOnce(new Error("key backup not found"))
@@ -87,9 +87,9 @@ describe("<RestoreKeyBackupDialog />", () => {
             .mockResolvedValue(keyBackupRestoreResult);
 
         const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
-        await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());
+        await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
 
-        await userEvent.type(screen.getByRole("textbox"), "my security key");
+        await userEvent.type(screen.getByRole("textbox"), "my recovery key");
         await userEvent.click(screen.getByRole("button", { name: "Next" }));
 
         await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
diff --git a/test/unit-tests/components/views/dialogs/security/__snapshots__/CreateSecretStorageDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/security/__snapshots__/CreateSecretStorageDialog-test.tsx.snap
index a39ce29439d63412c36fa0517ef3b5eacca478c7..39573190e0c289c276ebb8e8824e1d08ecc75321 100644
--- a/test/unit-tests/components/views/dialogs/security/__snapshots__/CreateSecretStorageDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/security/__snapshots__/CreateSecretStorageDialog-test.tsx.snap
@@ -55,10 +55,10 @@ exports[`CreateSecretStorageDialog handles the happy path 1`] = `
                 <span
                   class="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup"
                 />
-                Generate a Security Key
+                Generate a Recovery Key
               </div>
               <div>
-                We'll generate a Security Key for you to store somewhere safe, like a password manager or a safe.
+                We'll generate a Recovery Key for you to store somewhere safe, like a password manager or a safe.
               </div>
             </div>
             <div
@@ -88,7 +88,7 @@ exports[`CreateSecretStorageDialog handles the happy path 1`] = `
                 Enter a Security Phrase
               </div>
               <div>
-                Use a secret phrase only you know, and optionally save a Security Key to use for backup.
+                Use a secret phrase only you know, and optionally save a Recovery Key to use for backup.
               </div>
             </div>
             <div
@@ -148,13 +148,13 @@ exports[`CreateSecretStorageDialog handles the happy path 2`] = `
         class="mx_Heading_h3 mx_Dialog_title mx_CreateSecretStorageDialog_titleWithIcon mx_CreateSecretStorageDialog_secureBackupTitle"
         id="mx_BaseDialog_title"
       >
-        Save your Security Key
+        Save your Recovery Key
       </h1>
     </div>
     <div>
       <div>
         <p>
-          Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.
+          Store your Recovery Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.
         </p>
         <div
           class="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer"
diff --git a/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap
index 1b14530b902476795d22e96fc3551c6f0d023d71..3b5809dddf145a24e647dcad5364da7bbc92a8d7 100644
--- a/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/security/__snapshots__/RestoreKeyBackupDialog-test.tsx.snap
@@ -20,7 +20,7 @@ exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is
         class="mx_Heading_h3 mx_Dialog_title"
         id="mx_BaseDialog_title"
       >
-        Enter Security Key
+        Enter Recovery Key
       </h1>
     </div>
     <div
@@ -36,7 +36,7 @@ exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is
           </span>
         </p>
         <p>
-          Access your secure message history and set up secure messaging by entering your Security Key.
+          Access your secure message history and set up secure messaging by entering your Recovery Key.
         </p>
         <div
           class="mx_RestoreKeyBackupDialog_primaryContainer"
@@ -48,7 +48,7 @@ exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is
           <div
             class="mx_RestoreKeyBackupDialog_keyStatus"
           >
-            👎 Not a valid Security Key
+            👎 Not a valid Recovery Key
           </div>
           <div
             class="mx_Dialog_buttons"
@@ -74,7 +74,7 @@ exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is
           </div>
         </div>
         <span>
-          If you've forgotten your Security Key you can 
+          If you've forgotten your Recovery Key you can 
           <div
             class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
             role="button"
@@ -120,7 +120,7 @@ exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is v
         class="mx_Heading_h3 mx_Dialog_title"
         id="mx_BaseDialog_title"
       >
-        Enter Security Key
+        Enter Recovery Key
       </h1>
     </div>
     <div
@@ -136,7 +136,7 @@ exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is v
           </span>
         </p>
         <p>
-          Access your secure message history and set up secure messaging by entering your Security Key.
+          Access your secure message history and set up secure messaging by entering your Recovery Key.
         </p>
         <div
           class="mx_RestoreKeyBackupDialog_primaryContainer"
@@ -148,7 +148,7 @@ exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is v
           <div
             class="mx_RestoreKeyBackupDialog_keyStatus"
           >
-            👍 This looks like a valid Security Key!
+            👍 This looks like a valid Recovery Key!
           </div>
           <div
             class="mx_Dialog_buttons"
@@ -173,7 +173,7 @@ exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is v
           </div>
         </div>
         <span>
-          If you've forgotten your Security Key you can 
+          If you've forgotten your Recovery Key you can 
           <div
             class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
             role="button"
@@ -219,7 +219,7 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
         class="mx_Heading_h3 mx_Dialog_title"
         id="mx_BaseDialog_title"
       >
-        Enter Security Key
+        Enter Recovery Key
       </h1>
     </div>
     <div
@@ -235,7 +235,7 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
           </span>
         </p>
         <p>
-          Access your secure message history and set up secure messaging by entering your Security Key.
+          Access your secure message history and set up secure messaging by entering your Recovery Key.
         </p>
         <div
           class="mx_RestoreKeyBackupDialog_primaryContainer"
@@ -271,7 +271,7 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
           </div>
         </div>
         <span>
-          If you've forgotten your Security Key you can 
+          If you've forgotten your Recovery Key you can 
           <div
             class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
             role="button"
@@ -297,7 +297,7 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
 </DocumentFragment>
 `;
 
-exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is filled 1`] = `
+exports[`<RestoreKeyBackupDialog /> should restore key backup when Recovery key is filled by user 1`] = `
 <DocumentFragment>
   <div
     data-focus-guard="true"
@@ -362,7 +362,7 @@ exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is
 </DocumentFragment>
 `;
 
-exports[`<RestoreKeyBackupDialog /> should restore key backup when security key is filled by user 1`] = `
+exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is filled 1`] = `
 <DocumentFragment>
   <div
     data-focus-guard="true"
diff --git a/test/unit-tests/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx b/test/unit-tests/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx
index c5cda731bee97821dcac88d34fea7d812a20d735..9e85147c073f967b57ba92276153adadb8cf480b 100644
--- a/test/unit-tests/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx
+++ b/test/unit-tests/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render } from "jest-matrix-react";
-import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
+import { type IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
 
 import { PublicRoomResultDetails } from "../../../../../../src/components/views/dialogs/spotlight/PublicRoomResultDetails";
 
diff --git a/test/unit-tests/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx b/test/unit-tests/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx
index 209ad32727f8e139980f04abee78a05e681517e0..b02d7804852367d29bdbac104cc96370e2f01546 100644
--- a/test/unit-tests/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx
+++ b/test/unit-tests/components/views/dialogs/spotlight/RoomResultContextMenus-test.tsx
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, screen, RenderResult } from "jest-matrix-react";
+import { render, screen, type RenderResult } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { Room, MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
+import { Room, type MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
 
 import { RoomResultContextMenus } from "../../../../../../src/components/views/dialogs/spotlight/RoomResultContextMenus";
 import { filterConsole, stubClient } from "../../../../../test-utils";
diff --git a/test/unit-tests/components/views/elements/AppTile-test.tsx b/test/unit-tests/components/views/elements/AppTile-test.tsx
index 7a2acb3ace47069c54c35b796362a39ad79b33c0..1a21bda494b4ebd20545542e464824fd290c18c3 100644
--- a/test/unit-tests/components/views/elements/AppTile-test.tsx
+++ b/test/unit-tests/components/views/elements/AppTile-test.tsx
@@ -7,14 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { ClientWidgetApi, IWidget, MatrixWidgetType } from "matrix-widget-api";
-import { Optional } from "matrix-events-sdk";
-import { act, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
+import { Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type ClientWidgetApi, type IWidget, MatrixWidgetType } from "matrix-widget-api";
+import { type Optional } from "matrix-events-sdk";
+import { act, render, type RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import {
-    ApprovalOpts,
-    WidgetInfo,
+    type ApprovalOpts,
+    type WidgetInfo,
     WidgetLifecycle,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
 
@@ -29,7 +29,7 @@ import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
 import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
-import WidgetStore, { IApp } from "../../../../../src/stores/WidgetStore";
+import WidgetStore, { type IApp } from "../../../../../src/stores/WidgetStore";
 import ActiveWidgetStore from "../../../../../src/stores/ActiveWidgetStore";
 import AppTile from "../../../../../src/components/views/elements/AppTile";
 import { Container, WidgetLayoutStore } from "../../../../../src/stores/widgets/WidgetLayoutStore";
diff --git a/test/unit-tests/components/views/elements/DesktopCapturerSourcePicker-test.tsx b/test/unit-tests/components/views/elements/DesktopCapturerSourcePicker-test.tsx
index 7fc739fafb5c50c2645bf548e5a21f8763048685..4e5c04605decbcec6518dcbf3aa1191fab03f6e7 100644
--- a/test/unit-tests/components/views/elements/DesktopCapturerSourcePicker-test.tsx
+++ b/test/unit-tests/components/views/elements/DesktopCapturerSourcePicker-test.tsx
@@ -12,7 +12,7 @@ import userEvent from "@testing-library/user-event";
 
 import DesktopCapturerSourcePicker from "../../../../../src/components/views/elements/DesktopCapturerSourcePicker";
 import PlatformPeg from "../../../../../src/PlatformPeg";
-import BasePlatform from "../../../../../src/BasePlatform";
+import type BasePlatform from "../../../../../src/BasePlatform";
 
 const SOURCES = [
     {
diff --git a/test/unit-tests/components/views/elements/EventListSummary-test.tsx b/test/unit-tests/components/views/elements/EventListSummary-test.tsx
index f1140352b85d2254f8e0711c7e6cb82f2dda6ebf..2f2c409db691d7b9101a8a77ab2dbc72be44d26e 100644
--- a/test/unit-tests/components/views/elements/EventListSummary-test.tsx
+++ b/test/unit-tests/components/views/elements/EventListSummary-test.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { render, RenderResult } from "jest-matrix-react";
-import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
-import { KnownMembership, Membership } from "matrix-js-sdk/src/types";
+import React, { type ComponentProps } from "react";
+import { render, type RenderResult } from "jest-matrix-react";
+import { type MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
+import { KnownMembership, type Membership } from "matrix-js-sdk/src/types";
 
 import {
     getMockClientWithEventEmitter,
diff --git a/test/unit-tests/components/views/elements/FilterDropdown-test.tsx b/test/unit-tests/components/views/elements/FilterDropdown-test.tsx
index 112d0ed650c9cf66f818e1befb22afe8815ae598..d84cc7d691ff6444e03036834bd2e257c1b7e838 100644
--- a/test/unit-tests/components/views/elements/FilterDropdown-test.tsx
+++ b/test/unit-tests/components/views/elements/FilterDropdown-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { act, fireEvent, render } from "jest-matrix-react";
-import React from "react";
+import React, { type JSX } from "react";
 
 import { FilterDropdown } from "../../../../../src/components/views/elements/FilterDropdown";
 import { flushPromises, mockPlatformPeg } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/elements/MiniAvatarUploader-test.tsx b/test/unit-tests/components/views/elements/MiniAvatarUploader-test.tsx
index 5b451c44b4e55c8de97a84fe873885734e7138c3..f6d2c07bc97312a2e950b0dca18f02cdd9a66473 100644
--- a/test/unit-tests/components/views/elements/MiniAvatarUploader-test.tsx
+++ b/test/unit-tests/components/views/elements/MiniAvatarUploader-test.tsx
@@ -26,12 +26,12 @@ describe("<MiniAvatarUploader />", () => {
         const setAvatarUrl = jest.fn();
         const user = userEvent.setup();
 
-        const { container, findByText } = render(
+        const { container, findByLabelText } = render(
             <MiniAvatarUploader hasAvatar={false} noAvatarLabel="Upload" setAvatarUrl={setAvatarUrl} isUserAvatar />,
             withClientContextRenderOptions(cli),
         );
 
-        await findByText("Upload");
+        await findByLabelText("Upload");
         await user.upload(container.querySelector("input")!, AVATAR_FILE);
 
         expect(cli.uploadContent).toHaveBeenCalledWith(AVATAR_FILE);
diff --git a/test/unit-tests/components/views/elements/Pill-test.tsx b/test/unit-tests/components/views/elements/Pill-test.tsx
index b2ea507790e5c36d81bc9eac76c1053382b7267c..f3a49d673481efca0e2a224d11ecfd51ea6ef9b2 100644
--- a/test/unit-tests/components/views/elements/Pill-test.tsx
+++ b/test/unit-tests/components/views/elements/Pill-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult, screen } from "jest-matrix-react";
+import { render, type RenderResult, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { mocked, type Mocked } from "jest-mock";
+import { type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 
 import dis from "../../../../../src/dispatcher/dispatcher";
-import { Pill, PillProps, PillType } from "../../../../../src/components/views/elements/Pill";
+import { Pill, type PillProps, PillType } from "../../../../../src/components/views/elements/Pill";
 import {
     filterConsole,
     flushPromises,
@@ -21,11 +21,14 @@ import {
     mkRoomCanonicalAliasEvent,
     mkRoomMemberJoinEvent,
     stubClient,
+    withClientContextRenderOptions,
 } from "../../../../test-utils";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import { Action } from "../../../../../src/dispatcher/actions";
-import { ButtonEvent } from "../../../../../src/components/views/elements/AccessibleButton";
-import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
+import { type ButtonEvent } from "../../../../../src/components/views/elements/AccessibleButton";
+import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
+import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg.ts";
+import { TestSdkContext } from "../../../TestSdkContext.ts";
 
 describe("<Pill>", () => {
     let client: Mocked<MatrixClient>;
@@ -45,6 +48,10 @@ describe("<Pill>", () => {
     let pillParentClickHandler: (e: ButtonEvent) => void;
 
     const renderPill = (props: PillProps): void => {
+        const cli = MatrixClientPeg.safeGet();
+        const mockSdkContext = new TestSdkContext();
+        mockSdkContext.client = cli;
+
         const withDefault = {
             inMessage: true,
             shouldShowPillAvatar: true,
@@ -53,9 +60,12 @@ describe("<Pill>", () => {
         // wrap Pill with a div to allow testing of event bubbling
         renderResult = render(
             // eslint-disable-next-line jsx-a11y/click-events-have-key-events
-            <div onClick={pillParentClickHandler}>
-                <Pill {...withDefault} />
-            </div>,
+            <SDKContext.Provider value={mockSdkContext}>
+                <div onClick={pillParentClickHandler}>
+                    <Pill {...withDefault} />
+                </div>
+            </SDKContext.Provider>,
+            withClientContextRenderOptions(cli),
         );
     };
 
diff --git a/test/unit-tests/components/views/elements/PollCreateDialog-test.tsx b/test/unit-tests/components/views/elements/PollCreateDialog-test.tsx
index cf50181bb8ca87c0d605280366e5d8573d8e2602..a0acf4b0e06aaefcfde3061db3198e64b19f300f 100644
--- a/test/unit-tests/components/views/elements/PollCreateDialog-test.tsx
+++ b/test/unit-tests/components/views/elements/PollCreateDialog-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult } from "jest-matrix-react";
 import {
     Room,
     MatrixEvent,
@@ -17,7 +17,7 @@ import {
     M_TEXT,
 } from "matrix-js-sdk/src/matrix";
 import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
-import { ReplacementEvent } from "matrix-js-sdk/src/types";
+import { type ReplacementEvent } from "matrix-js-sdk/src/types";
 
 import { getMockClientWithEventEmitter } from "../../../../test-utils";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/views/elements/PowerSelector-test.tsx b/test/unit-tests/components/views/elements/PowerSelector-test.tsx
index ef35cf75a0110f22311297a92d27f457b6756956..f98b6c74468b4786bc272916f3ea9ae176eb7991 100644
--- a/test/unit-tests/components/views/elements/PowerSelector-test.tsx
+++ b/test/unit-tests/components/views/elements/PowerSelector-test.tsx
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render, screen } from "jest-matrix-react";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import PowerSelector from "../../../../../src/components/views/elements/PowerSelector";
 
@@ -70,7 +69,7 @@ describe("<PowerSelector />", () => {
     });
 
     it("should reset when onChange promise rejects", async () => {
-        const deferred = defer<void>();
+        const deferred = Promise.withResolvers<void>();
         render(
             <PowerSelector
                 value={25}
diff --git a/test/unit-tests/components/views/elements/SettingsField-test.tsx b/test/unit-tests/components/views/elements/SettingsField-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cc1522c73849e71e25f41d605fd7f8a9dd07e19c
--- /dev/null
+++ b/test/unit-tests/components/views/elements/SettingsField-test.tsx
@@ -0,0 +1,36 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render, screen, waitFor } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import SettingsField from "../../../../../src/components/views/elements/SettingsField";
+import { SettingLevel } from "../../../../../src/settings/SettingLevel.ts";
+
+describe("<SettingsField />", () => {
+    it("should render with the default label", () => {
+        const component = render(<SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} />);
+
+        expect(screen.getByText("Element Call URL")).toBeTruthy();
+        expect(component.asFragment()).toMatchSnapshot();
+    });
+
+    it("should call onChange when saving a change", async () => {
+        const fn = jest.fn();
+        render(<SettingsField settingKey="Developer.elementCallUrl" level={SettingLevel.DEVICE} onChange={fn} />);
+
+        const input = screen.getByRole("textbox");
+        await userEvent.type(input, "https://call.element.dev");
+        expect(input).toHaveValue("https://call.element.dev");
+
+        screen.getByLabelText("Save").click();
+        await waitFor(() => {
+            expect(fn).toHaveBeenCalledWith("https://call.element.dev");
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/elements/StyledRadioGroup-test.tsx b/test/unit-tests/components/views/elements/StyledRadioGroup-test.tsx
index 1f8894457cb85782042171bfabf28c78c9621bf8..be8fc56fda34509ef812443f1fb3b816dceb8786 100644
--- a/test/unit-tests/components/views/elements/StyledRadioGroup-test.tsx
+++ b/test/unit-tests/components/views/elements/StyledRadioGroup-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult } from "jest-matrix-react";
 
 import StyledRadioGroup from "../../../../../src/components/views/elements/StyledRadioGroup";
 
diff --git a/test/unit-tests/components/views/elements/SyntaxHighlight-test.tsx b/test/unit-tests/components/views/elements/SyntaxHighlight-test.tsx
index 5983e7b0f225a81c7ea3c3e78975745c1b125e83..3d442a6f4d4970cb28aceacf059a9b3ce900787f 100644
--- a/test/unit-tests/components/views/elements/SyntaxHighlight-test.tsx
+++ b/test/unit-tests/components/views/elements/SyntaxHighlight-test.tsx
@@ -12,8 +12,10 @@ import SyntaxHighlight from "../../../../../src/components/views/elements/Syntax
 
 describe("<SyntaxHighlight />", () => {
     it("renders", async () => {
-        const { container } = render(<SyntaxHighlight>console.log("Hello, World!");</SyntaxHighlight>);
-        await waitFor(() => expect(container.querySelector(".language-arcade")).toBeTruthy());
+        const { container } = render(
+            <SyntaxHighlight language="javascript">console.log("Hello, World!");</SyntaxHighlight>,
+        );
+        await waitFor(() => expect(container.querySelector(".language-javascript")).toBeTruthy());
         expect(container).toMatchSnapshot();
     });
 
diff --git a/test/unit-tests/components/views/elements/__snapshots__/AppTile-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/AppTile-test.tsx.snap
index f039d9451434a11a270039cbbb78b7ffa4f68d16..ccbdf5d8fbd0fb023dbe6527abf6c416f125f6c7 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/AppTile-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/AppTile-test.tsx.snap
@@ -4,6 +4,7 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
 <DocumentFragment>
   <aside
     class="mx_RightPanel"
+    data-testid="right-panel"
     id="mx_RightPanel"
   >
     <div
@@ -30,15 +31,15 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
           />
         </div>
         <button
-          aria-labelledby=":r0:"
-          class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+          aria-labelledby="«r0»"
+          class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
           data-testid="base-card-close-button"
           role="button"
           style="--cpd-icon-button-size: 28px;"
           tabindex="0"
         >
           <div
-            class="_indicator-icon_133tf_26"
+            class="_indicator-icon_zr2a0_17"
             style="--cpd-icon-button-size: 100%;"
           >
             <svg
@@ -49,7 +50,7 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
               />
             </svg>
           </div>
@@ -101,7 +102,7 @@ exports[`AppTile for a pinned widget should render 1`] = `
         <span>
           <span
             aria-label="Avatar"
-            class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar mx_WidgetAvatar"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -109,7 +110,7 @@ exports[`AppTile for a pinned widget should render 1`] = `
           >
             <img
               alt=""
-              class="_image_mcap2_50"
+              class="_image_1qbcf_41"
               data-type="round"
               height="20px"
               loading="lazy"
@@ -142,7 +143,7 @@ exports[`AppTile for a pinned widget should render 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 11.034a.996.996 0 0 0 .29.702l.005.005c.18.18.43.29.705.29h8a1 1 0 1 0 0-2h-5.586L22 3.446a1 1 0 0 0-1.414-1.415L14 8.617V3.031a1 1 0 1 0-2 0v8.003Zm0 1.963a.997.997 0 0 0-.29-.702l-.005-.004A.997.997 0 0 0 11 12H3a1 1 0 0 0 0 2h5.586L2 20.586A1 1 0 1 0 3.414 22L10 15.414V21a1 1 0 1 0 2 0v-8.003Z"
+              d="M12 11.034a1 1 0 0 0 .29.702l.005.005c.18.18.43.29.705.29h8a1 1 0 0 0 0-2h-5.586L22 3.445a1 1 0 0 0-1.414-1.414L14 8.617V3.031a1 1 0 1 0-2 0zm0 1.963a1 1 0 0 0-.29-.702l-.005-.004A1 1 0 0 0 11 12H3a1 1 0 1 0 0 2h5.586L2 20.586A1 1 0 1 0 3.414 22L10 15.414V21a1 1 0 0 0 2 0z"
             />
           </svg>
         </div>
@@ -161,7 +162,7 @@ exports[`AppTile for a pinned widget should render 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 13a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h12a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13H6Z"
+              d="M6 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13z"
             />
           </svg>
         </div>
@@ -182,7 +183,7 @@ exports[`AppTile for a pinned widget should render 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+              d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
             />
           </svg>
         </div>
@@ -213,7 +214,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
         <span>
           <span
             aria-label="Avatar"
-            class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar mx_WidgetAvatar"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -221,7 +222,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
           >
             <img
               alt=""
-              class="_image_mcap2_50"
+              class="_image_1qbcf_41"
               data-type="round"
               height="20px"
               loading="lazy"
@@ -254,7 +255,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 11.034a.996.996 0 0 0 .29.702l.005.005c.18.18.43.29.705.29h8a1 1 0 1 0 0-2h-5.586L22 3.446a1 1 0 0 0-1.414-1.415L14 8.617V3.031a1 1 0 1 0-2 0v8.003Zm0 1.963a.997.997 0 0 0-.29-.702l-.005-.004A.997.997 0 0 0 11 12H3a1 1 0 0 0 0 2h5.586L2 20.586A1 1 0 1 0 3.414 22L10 15.414V21a1 1 0 1 0 2 0v-8.003Z"
+              d="M12 11.034a1 1 0 0 0 .29.702l.005.005c.18.18.43.29.705.29h8a1 1 0 0 0 0-2h-5.586L22 3.445a1 1 0 0 0-1.414-1.414L14 8.617V3.031a1 1 0 1 0-2 0zm0 1.963a1 1 0 0 0-.29-.702l-.005-.004A1 1 0 0 0 11 12H3a1 1 0 1 0 0 2h5.586L2 20.586A1 1 0 1 0 3.414 22L10 15.414V21a1 1 0 0 0 2 0z"
             />
           </svg>
         </div>
@@ -273,7 +274,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 13a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h12a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13H6Z"
+              d="M6 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13z"
             />
           </svg>
         </div>
@@ -294,7 +295,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+              d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
             />
           </svg>
         </div>
@@ -316,7 +317,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
           </div>
           <div>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="1"
               data-testid="avatar-img"
               data-type="round"
@@ -336,8 +337,8 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
             <span>
               Using this widget may share data 
               <div
-                aria-describedby=":r2j:"
-                aria-labelledby=":r2i:"
+                aria-describedby="«r2j»"
+                aria-labelledby="«r2i»"
                 class="mx_TextWithTooltip_target mx_TextWithTooltip_target--helpIcon"
               >
                 <svg
@@ -349,10 +350,10 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439c-.122.126-.24.243-.352.355-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8Zm1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
+                    d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439q-.183.188-.352.355c-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8m1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0"
                   />
                   <path
-                    d="M8.1 21.212A9.738 9.738 0 0 0 12 22a9.738 9.738 0 0 0 3.9-.788 10.098 10.098 0 0 0 3.175-2.137c.9-.9 1.613-1.958 2.137-3.175A9.738 9.738 0 0 0 22 12a9.738 9.738 0 0 0-.788-3.9 10.099 10.099 0 0 0-2.137-3.175c-.9-.9-1.958-1.612-3.175-2.137A9.738 9.738 0 0 0 12 2a9.738 9.738 0 0 0-3.9.788 10.099 10.099 0 0 0-3.175 2.137c-.9.9-1.612 1.958-2.137 3.175A9.738 9.738 0 0 0 2 12a9.74 9.74 0 0 0 .788 3.9 10.098 10.098 0 0 0 2.137 3.175c.9.9 1.958 1.613 3.175 2.137Zm9.575-3.537C16.125 19.225 14.233 20 12 20c-2.233 0-4.125-.775-5.675-2.325C4.775 16.125 4 14.233 4 12c0-2.233.775-4.125 2.325-5.675C7.875 4.775 9.767 4 12 4c2.233 0 4.125.775 5.675 2.325C19.225 7.875 20 9.767 20 12c0 2.233-.775 4.125-2.325 5.675Z"
+                    d="M8.1 21.213A9.7 9.7 0 0 0 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 2.075.788 3.9a10.1 10.1 0 0 0 2.137 3.175q1.35 1.35 3.175 2.137m9.575-3.538Q15.35 20 12 20t-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675"
                   />
                 </svg>
               </div>
@@ -404,7 +405,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
               <span>
                 <span
                   aria-label="Avatar"
-                  class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
+                  class="_avatar_1qbcf_8 mx_BaseAvatar mx_WidgetAvatar"
                   data-color="1"
                   data-testid="avatar-img"
                   data-type="round"
@@ -412,7 +413,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
                 >
                   <img
                     alt=""
-                    class="_image_mcap2_50"
+                    class="_image_1qbcf_41"
                     data-type="round"
                     height="20px"
                     loading="lazy"
@@ -445,7 +446,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M21 3.997a.996.996 0 0 0-.29-.702l-.005-.004A.997.997 0 0 0 20 3h-8a1 1 0 1 0 0 2h5.586L5 17.586V12a1 1 0 1 0-2 0v8.003a.997.997 0 0 0 .29.702l.005.004c.18.18.43.291.705.291h8a1 1 0 1 0 0-2H6.414L19 6.414V12a1 1 0 1 0 2 0V3.997Z"
+                    d="M21 3.997a1 1 0 0 0-.29-.702l-.005-.004A1 1 0 0 0 20 3h-8a1 1 0 1 0 0 2h5.586L5 17.586V12a1 1 0 1 0-2 0v8.003a1 1 0 0 0 .29.702l.005.004c.18.18.43.291.705.291h8a1 1 0 1 0 0-2H6.414L19 6.414V12a1 1 0 1 0 2 0z"
                   />
                 </svg>
               </div>
@@ -464,7 +465,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M6 13a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h12a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13H6Z"
+                    d="M6 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13z"
                   />
                 </svg>
               </div>
@@ -485,7 +486,7 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                    d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                   />
                 </svg>
               </div>
diff --git a/test/unit-tests/components/views/elements/__snapshots__/FacePile-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/FacePile-test.tsx.snap
index 2956bed06661b5f69bb6ffd6553e90f472343c7b..62187854c35eb210f095b853956d5f235b128d8a 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/FacePile-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/FacePile-test.tsx.snap
@@ -3,16 +3,16 @@
 exports[`<FacePile /> renders with a tooltip 1`] = `
 <DocumentFragment>
   <div
-    aria-labelledby=":r0:"
+    aria-labelledby="«r0»"
     class="mx_AccessibleButton mx_FacePile"
     role="button"
     tabindex="0"
   >
     <div
-      class="_stacked-avatars_mcap2_111"
+      class="_stacked-avatars_1qbcf_102"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="4"
         data-testid="avatar-img"
         data-type="round"
diff --git a/test/unit-tests/components/views/elements/__snapshots__/FilterDropdown-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/FilterDropdown-test.tsx.snap
index fe153adcb985ea53f7c717c60c71a27cd736782b..3f6d46f2fe982af66557c6af82013515f5c2c35c 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/FilterDropdown-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/FilterDropdown-test.tsx.snap
@@ -25,7 +25,7 @@ exports[`<FilterDropdown /> renders dropdown options in menu 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+          d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
         />
       </svg>
       <span
diff --git a/test/unit-tests/components/views/elements/__snapshots__/InfoTooltip-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/InfoTooltip-test.tsx.snap
index 2d2541e3615d4eb71b6e82778a8fd38c4a4d7677..438d3f0ecc5f9011c3ed9232ce1c0c676053fe3c 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/InfoTooltip-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/InfoTooltip-test.tsx.snap
@@ -3,7 +3,7 @@
 exports[`InfoTooltip should show tooltip on hover 1`] = `
 <DocumentFragment>
   <div
-    aria-describedby=":r2:"
+    aria-describedby="«r2»"
     class="mx_InfoTooltip"
     tabindex="0"
   >
diff --git a/test/unit-tests/components/views/elements/__snapshots__/LabelledCheckbox-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/LabelledCheckbox-test.tsx.snap
index dce0a4011a2711709c55153f158843ea52da8dff..944cb235132096c421ffd94b1a8df7b0dacb5797 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/LabelledCheckbox-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/LabelledCheckbox-test.tsx.snap
@@ -2,81 +2,129 @@
 
 exports[`<LabelledCheckbox /> should render with byline of "this is a byline" 1`] = `
 <DocumentFragment>
-  <label
+  <div
     class="mx_LabelledCheckbox"
   >
-    <span
-      class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+    <form
+      class="_root_19upo_16"
     >
-      <input
-        checked=""
-        id="checkbox_vY7Q4uEh9K"
-        type="checkbox"
-      />
-      <label
-        for="checkbox_vY7Q4uEh9K"
+      <div
+        class="_inline-field_19upo_32"
       >
         <div
-          class="mx_Checkbox_background"
+          class="_inline-field-control_19upo_44"
         >
           <div
-            class="mx_Checkbox_checkmark"
-          />
+            class="_container_1hel1_10"
+          >
+            <input
+              aria-describedby="«r4»"
+              checked=""
+              class="_input_1hel1_18"
+              id="checkbox_vY7Q4uEh9K"
+              type="checkbox"
+            />
+            <div
+              class="_ui_1hel1_19"
+            >
+              <svg
+                aria-hidden="true"
+                fill="currentColor"
+                height="1em"
+                viewBox="0 0 24 24"
+                width="1em"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+            </div>
+          </div>
         </div>
-      </label>
-    </span>
-    <div
-      class="mx_LabelledCheckbox_labels"
-    >
-      <span
-        class="mx_LabelledCheckbox_label"
-      >
-        Hello world
-      </span>
-      <span
-        class="mx_LabelledCheckbox_byline"
-      >
-        this is a byline
-      </span>
-    </div>
-  </label>
+        <div
+          class="_inline-field-body_19upo_38"
+        >
+          <label
+            class="_label_19upo_59"
+            for="checkbox_vY7Q4uEh9K"
+          >
+            <span
+              class="mx_LabelledCheckbox_label"
+            >
+              Hello world
+            </span>
+          </label>
+          <span
+            class="_message_19upo_85 _help-message_19upo_91"
+            id="«r4»"
+          >
+            this is a byline
+          </span>
+        </div>
+      </div>
+    </form>
+  </div>
 </DocumentFragment>
 `;
 
 exports[`<LabelledCheckbox /> should render with byline of undefined 1`] = `
 <DocumentFragment>
-  <label
+  <div
     class="mx_LabelledCheckbox"
   >
-    <span
-      class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+    <form
+      class="_root_19upo_16"
     >
-      <input
-        checked=""
-        id="checkbox_vY7Q4uEh9K"
-        type="checkbox"
-      />
-      <label
-        for="checkbox_vY7Q4uEh9K"
+      <div
+        class="_inline-field_19upo_32"
       >
         <div
-          class="mx_Checkbox_background"
+          class="_inline-field-control_19upo_44"
         >
           <div
-            class="mx_Checkbox_checkmark"
-          />
+            class="_container_1hel1_10"
+          >
+            <input
+              checked=""
+              class="_input_1hel1_18"
+              id="checkbox_vY7Q4uEh9K"
+              type="checkbox"
+            />
+            <div
+              class="_ui_1hel1_19"
+            >
+              <svg
+                aria-hidden="true"
+                fill="currentColor"
+                height="1em"
+                viewBox="0 0 24 24"
+                width="1em"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+            </div>
+          </div>
         </div>
-      </label>
-    </span>
-    <div
-      class="mx_LabelledCheckbox_labels"
-    >
-      <span
-        class="mx_LabelledCheckbox_label"
-      >
-        Hello world
-      </span>
-    </div>
-  </label>
+        <div
+          class="_inline-field-body_19upo_38"
+        >
+          <label
+            class="_label_19upo_59"
+            for="checkbox_vY7Q4uEh9K"
+          >
+            <span
+              class="mx_LabelledCheckbox_label"
+            >
+              Hello world
+            </span>
+          </label>
+        </div>
+      </div>
+    </form>
+  </div>
 </DocumentFragment>
 `;
diff --git a/test/unit-tests/components/views/elements/__snapshots__/Pill-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/Pill-test.tsx.snap
index b625f22fbe92773bee8eef3ce5373b5d0a332143..0f6acfd0213777b72bc5d6b066f3f8221021a333 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/Pill-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/Pill-test.tsx.snap
@@ -40,7 +40,7 @@ exports[`<Pill> should render the expected pill for @room 1`] = `
         >
           <span
             aria-hidden="true"
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -71,7 +71,7 @@ exports[`<Pill> should render the expected pill for a known user not in the room
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="5"
           data-testid="avatar-img"
           data-type="round"
@@ -101,7 +101,7 @@ exports[`<Pill> should render the expected pill for a message in another room 1`
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="1"
           data-testid="avatar-img"
           data-type="round"
@@ -131,7 +131,7 @@ exports[`<Pill> should render the expected pill for a message in the same room 1
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="4"
           data-testid="avatar-img"
           data-type="round"
@@ -161,7 +161,7 @@ exports[`<Pill> should render the expected pill for a room alias 1`] = `
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="1"
           data-testid="avatar-img"
           data-type="round"
@@ -191,7 +191,7 @@ exports[`<Pill> should render the expected pill for a space 1`] = `
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="2"
           data-testid="avatar-img"
           data-type="round"
@@ -228,7 +228,7 @@ exports[`<Pill> should render the expected pill for an uknown user not in the ro
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 12c-1.1 0-2.042-.392-2.825-1.175C8.392 10.042 8 9.1 8 8s.392-2.042 1.175-2.825C9.958 4.392 10.9 4 12 4s2.042.392 2.825 1.175C15.608 5.958 16 6.9 16 8s-.392 2.042-1.175 2.825C14.042 11.608 13.1 12 12 12Zm-8 6v-.8c0-.567.146-1.087.438-1.563A2.911 2.911 0 0 1 5.6 14.55a14.843 14.843 0 0 1 3.15-1.163A13.76 13.76 0 0 1 12 13c1.1 0 2.183.13 3.25.387 1.067.259 2.117.646 3.15 1.163.483.25.87.612 1.163 1.087.291.476.437.996.437 1.563v.8c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18Z"
+            d="M12 12q-1.65 0-2.825-1.175T8 8t1.175-2.825T12 4t2.825 1.175T16 8t-1.175 2.825T12 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 12 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q20 16.35 20 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20H6q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18"
           />
         </svg>
         <span
@@ -252,7 +252,7 @@ exports[`<Pill> when rendering a pill for a room should render the expected pill
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="1"
           data-testid="avatar-img"
           data-type="round"
@@ -282,7 +282,7 @@ exports[`<Pill> when rendering a pill for a user in the room should render as ex
       >
         <span
           aria-hidden="true"
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="4"
           data-testid="avatar-img"
           data-type="round"
diff --git a/test/unit-tests/components/views/elements/__snapshots__/RoomFacePile-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/RoomFacePile-test.tsx.snap
index b8ae894619bee40b7bd75f804273871aed3bd529..4b396abded588e736a7c1c777175f8e23896034b 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/RoomFacePile-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/RoomFacePile-test.tsx.snap
@@ -3,17 +3,17 @@
 exports[`<RoomFacePile /> renders 1`] = `
 <DocumentFragment>
   <div
-    aria-describedby=":r1:"
-    aria-labelledby=":r0:"
+    aria-describedby="«r1»"
+    aria-labelledby="«r0»"
     class="mx_AccessibleButton mx_FacePile"
     role="button"
     tabindex="0"
   >
     <div
-      class="_stacked-avatars_mcap2_111"
+      class="_stacked-avatars_1qbcf_102"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="4"
         data-testid="avatar-img"
         data-type="round"
diff --git a/test/unit-tests/components/views/elements/__snapshots__/SettingsField-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/SettingsField-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..3103159bcefae4e60d4cfd072dcaec17da4dad37
--- /dev/null
+++ b/test/unit-tests/components/views/elements/__snapshots__/SettingsField-test.tsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<SettingsField /> should render with the default label 1`] = `
+<DocumentFragment>
+  <form
+    class="_root_19upo_16"
+  >
+    <div
+      class="_field_19upo_26"
+    >
+      <label
+        class="_label_19upo_59"
+        for="radix-«r0»"
+      >
+        Element Call URL
+      </label>
+      <div
+        class="_controls_17lij_8"
+      >
+        <input
+          class="_control_sqdq4_10"
+          id="radix-«r0»"
+          name="input"
+          title=""
+          value=""
+        />
+      </div>
+    </div>
+  </form>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap b/test/unit-tests/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap
index e7ad9c057b0dc51f322ef473a04024b21049acd2..9068995c5906686f48c171ad42f534ecdf715e82 100644
--- a/test/unit-tests/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap
+++ b/test/unit-tests/components/views/elements/__snapshots__/SyntaxHighlight-test.tsx.snap
@@ -3,17 +3,17 @@
 exports[`<SyntaxHighlight /> renders 1`] = `
 <div>
   <pre
-    class="mx_SyntaxHighlight hljs language-arcade"
+    class="mx_SyntaxHighlight hljs language-javascript"
   >
     <code>
       <span
-        class="hljs-built_in"
+        class="hljs-variable language_"
       >
         console
       </span>
       .
       <span
-        class="hljs-built_in"
+        class="hljs-title function_"
       >
         log
       </span>
diff --git a/test/unit-tests/components/views/location/LocationPicker-test.tsx b/test/unit-tests/components/views/location/LocationPicker-test.tsx
index 93acbdf18a9c7884e3e6be78d2408099407020e2..365f6678df1b1a8dcaf49eccac882de1eb6b8798 100644
--- a/test/unit-tests/components/views/location/LocationPicker-test.tsx
+++ b/test/unit-tests/components/views/location/LocationPicker-test.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
+import { act, fireEvent, render, type RenderResult } from "jest-matrix-react";
 import * as maplibregl from "maplibre-gl";
-import { RoomMember, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { RoomMember, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 import { logger } from "matrix-js-sdk/src/logger";
 
diff --git a/test/unit-tests/components/views/location/LocationShareMenu-test.tsx b/test/unit-tests/components/views/location/LocationShareMenu-test.tsx
index 7f95b5564d2b2732d184933c1bad17a0d8c0c04e..c347ac91a9e0c9ffce13b8df1d97e120dcb28c79 100644
--- a/test/unit-tests/components/views/location/LocationShareMenu-test.tsx
+++ b/test/unit-tests/components/views/location/LocationShareMenu-test.tsx
@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { mocked } from "jest-mock";
-import { RoomMember, RelationType, MatrixClient, M_ASSET, LocationAssetType } from "matrix-js-sdk/src/matrix";
+import { RoomMember, RelationType, type MatrixClient, M_ASSET, LocationAssetType } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
-import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
+import { act, fireEvent, render, type RenderResult } from "jest-matrix-react";
 import * as maplibregl from "maplibre-gl";
 
 import LocationShareMenu from "../../../../../src/components/views/location/LocationShareMenu";
diff --git a/test/unit-tests/components/views/location/LocationViewDialog-test.tsx b/test/unit-tests/components/views/location/LocationViewDialog-test.tsx
index 1993215cf8091b45df97e32f3fed2f61a6623db4..8e251e219a68c17526c35bedb939512340b55f87 100644
--- a/test/unit-tests/components/views/location/LocationViewDialog-test.tsx
+++ b/test/unit-tests/components/views/location/LocationViewDialog-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult } from "jest-matrix-react";
+import { render, type RenderResult } from "jest-matrix-react";
 import { RoomMember, LocationAssetType } from "matrix-js-sdk/src/matrix";
 
 import LocationViewDialog from "../../../../../src/components/views/location/LocationViewDialog";
diff --git a/test/unit-tests/components/views/location/MapError-test.tsx b/test/unit-tests/components/views/location/MapError-test.tsx
index 423a4645dcf3ca24d4199c8a71d72ea1eb56e7f3..f150958405f253a7a9371372ff67391aa1dcef45 100644
--- a/test/unit-tests/components/views/location/MapError-test.tsx
+++ b/test/unit-tests/components/views/location/MapError-test.tsx
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult } from "jest-matrix-react";
+import { render, type RenderResult } from "jest-matrix-react";
 
-import { MapError, MapErrorProps } from "../../../../../src/components/views/location/MapError";
+import { MapError, type MapErrorProps } from "../../../../../src/components/views/location/MapError";
 import { LocationShareError } from "../../../../../src/utils/location";
 
 describe("<MapError />", () => {
diff --git a/test/unit-tests/components/views/location/SmartMarker-test.tsx b/test/unit-tests/components/views/location/SmartMarker-test.tsx
index d1280c7a74c5a7f4234b4c12c5afbf7ef3a3703d..6d90aac7ebaae3f0553955f4f24516e0926cd55f 100644
--- a/test/unit-tests/components/views/location/SmartMarker-test.tsx
+++ b/test/unit-tests/components/views/location/SmartMarker-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { render } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 import * as maplibregl from "maplibre-gl";
diff --git a/test/unit-tests/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap
index 6bf73720a57cad5c3d22fe1aa91dc78bcda274ea..3ab56061be7891d4bbe7efd305d164e5e151ec35 100644
--- a/test/unit-tests/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/LocationShareMenu-test.tsx.snap
@@ -26,7 +26,7 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
       class="mx_SettingsFlag_label"
     >
       <div
-        id="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+        id="mx_LabelledToggleSwitch_«r0»"
       >
         Enable live location sharing
       </div>
@@ -34,7 +34,7 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
     <div
       aria-checked="false"
       aria-disabled="false"
-      aria-labelledby="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+      aria-labelledby="mx_LabelledToggleSwitch_«r0»"
       class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
       role="switch"
       tabindex="0"
diff --git a/test/unit-tests/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap
index 36152bc0f4f40c901b7d5b4ca78e8f6fe657d9cd..c18ce7c60277c3ca637c1f8b1775d4ad841a4a65 100644
--- a/test/unit-tests/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/LocationViewDialog-test.tsx.snap
@@ -22,7 +22,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+            d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
           />
         </svg>
       </div>
@@ -47,7 +47,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M11 13H6a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h5V6c0-.283.096-.52.287-.713A.968.968 0 0 1 12 5c.283 0 .52.096.713.287.191.192.287.43.287.713v5h5a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13h-5v5a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 19a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 18v-5Z"
+          d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
         />
       </svg>
     </div>
@@ -67,7 +67,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6 13a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h12a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13H6Z"
+          d="M6 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13z"
         />
       </svg>
     </div>
diff --git a/test/unit-tests/components/views/location/__snapshots__/MapError-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/MapError-test.tsx.snap
index 238097d995b139fb3ec3217e58d23a980edaa3de..f0c4749153c5c674a25873cddfcfc6fb64c03d95 100644
--- a/test/unit-tests/components/views/location/__snapshots__/MapError-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/MapError-test.tsx.snap
@@ -15,7 +15,7 @@ exports[`<MapError /> applies class when isMinimised is truthy 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+        d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
       />
     </svg>
     <h3
@@ -54,7 +54,7 @@ exports[`<MapError /> renders correctly for MapStyleUrlNotConfigured 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+        d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
       />
     </svg>
     <h3
@@ -93,7 +93,7 @@ exports[`<MapError /> renders correctly for MapStyleUrlNotReachable 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+        d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
       />
     </svg>
     <h3
diff --git a/test/unit-tests/components/views/location/__snapshots__/Marker-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/Marker-test.tsx.snap
index 635119d55ccb8dec11a912b3f4fc789d16cfc18f..43d75c65a7618296f181daf30c5e0faf42103121 100644
--- a/test/unit-tests/components/views/location/__snapshots__/Marker-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/Marker-test.tsx.snap
@@ -18,7 +18,7 @@ exports[`<Marker /> renders with location icon when no room member 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+          d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
         />
       </svg>
     </div>
diff --git a/test/unit-tests/components/views/location/__snapshots__/SmartMarker-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/SmartMarker-test.tsx.snap
index f2b3e4cc8be6fb7542c2af2f0ce8ebba06c0967b..f1663bb7518a1afc8251f410d8f573b4e3e49646 100644
--- a/test/unit-tests/components/views/location/__snapshots__/SmartMarker-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/SmartMarker-test.tsx.snap
@@ -18,7 +18,7 @@ exports[`<SmartMarker /> creates a marker on mount 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+            d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
           />
         </svg>
       </div>
@@ -45,7 +45,7 @@ exports[`<SmartMarker /> removes marker on unmount 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+            d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
           />
         </svg>
       </div>
diff --git a/test/unit-tests/components/views/location/__snapshots__/ZoomButtons-test.tsx.snap b/test/unit-tests/components/views/location/__snapshots__/ZoomButtons-test.tsx.snap
index 4aa18b2f1e10641bc0b0b94b800dfb9e9e68d13a..02131a43d73405c7a911fa301142efb0eb5d0d4f 100644
--- a/test/unit-tests/components/views/location/__snapshots__/ZoomButtons-test.tsx.snap
+++ b/test/unit-tests/components/views/location/__snapshots__/ZoomButtons-test.tsx.snap
@@ -21,7 +21,7 @@ exports[`<ZoomButtons /> renders buttons 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M11 13H6a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h5V6c0-.283.096-.52.287-.713A.968.968 0 0 1 12 5c.283 0 .52.096.713.287.191.192.287.43.287.713v5h5a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13h-5v5a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 19a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 18v-5Z"
+          d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
         />
       </svg>
     </div>
@@ -41,7 +41,7 @@ exports[`<ZoomButtons /> renders buttons 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6 13a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h12a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13H6Z"
+          d="M6 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h12q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13z"
         />
       </svg>
     </div>
diff --git a/test/unit-tests/components/views/location/shareLocation-test.ts b/test/unit-tests/components/views/location/shareLocation-test.ts
index 498df43ee1899103632fe20aa6658999e18015b2..6c4d7d3913ab52b3297a12f6bd17b974398a9459 100644
--- a/test/unit-tests/components/views/location/shareLocation-test.ts
+++ b/test/unit-tests/components/views/location/shareLocation-test.ts
@@ -9,16 +9,16 @@ Please see LICENSE files in the repository root for full details.
 import { mocked } from "jest-mock";
 import {
     ContentHelpers,
-    MatrixClient,
-    LegacyLocationEventContent,
-    MLocationEventContent,
+    type MatrixClient,
+    type LegacyLocationEventContent,
+    type MLocationEventContent,
 } from "matrix-js-sdk/src/matrix";
 
 import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
 import {
     LocationShareType,
     shareLocation,
-    ShareLocationFn,
+    type ShareLocationFn,
 } from "../../../../../src/components/views/location/shareLocation";
 
 jest.mock("../../../../../src/utils/local-room", () => ({
diff --git a/test/unit-tests/components/views/messages/CallEvent-test.tsx b/test/unit-tests/components/views/messages/CallEvent-test.tsx
index b092cf84f64b94913003c760d2d363f4f7eb695a..cc8e5a0a8ee153b6cf96388bd9416a500f923f30 100644
--- a/test/unit-tests/components/views/messages/CallEvent-test.tsx
+++ b/test/unit-tests/components/views/messages/CallEvent-test.tsx
@@ -8,11 +8,16 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen, act, cleanup, fireEvent, waitFor } from "jest-matrix-react";
-import { mocked, Mocked } from "jest-mock";
-import { Room, RoomStateEvent, MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
-import { ClientWidgetApi, Widget } from "matrix-widget-api";
+import { mocked, type Mocked } from "jest-mock";
+import {
+    Room,
+    RoomStateEvent,
+    type MatrixClient,
+    PendingEventOrdering,
+    type RoomMember,
+} from "matrix-js-sdk/src/matrix";
+import { type ClientWidgetApi, Widget } from "matrix-widget-api";
 
-import type { RoomMember } from "matrix-js-sdk/src/matrix";
 import {
     useMockedCalls,
     MockedCall,
diff --git a/test/unit-tests/components/views/messages/DateSeparator-test.tsx b/test/unit-tests/components/views/messages/DateSeparator-test.tsx
index 3177fd513da5caf0fe63e2a08157ba10be09c051..190872feeab2bb1a29963913d8491f8efc1e76a6 100644
--- a/test/unit-tests/components/views/messages/DateSeparator-test.tsx
+++ b/test/unit-tests/components/views/messages/DateSeparator-test.tsx
@@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { mocked } from "jest-mock";
 import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
-import { TimestampToEventResponse, ConnectionError, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type TimestampToEventResponse, ConnectionError, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix";
 
 import dispatcher from "../../../../../src/dispatcher/dispatcher";
 import { Action } from "../../../../../src/dispatcher/actions";
-import { ViewRoomPayload } from "../../../../../src/dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../../../src/dispatcher/payloads/ViewRoomPayload";
 import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
 import { formatFullDateNoTime } from "../../../../../src/DateUtils";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
diff --git a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx b/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx
index 5d5e3badc4733731009e0da199acb50bc6f70fdc..ce628da309f5530ef895f8e4f94b1dcf4fed129d 100644
--- a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx
+++ b/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx
@@ -8,7 +8,7 @@
 
 import React from "react";
 import { render } from "jest-matrix-react";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
 import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
 
diff --git a/test/unit-tests/components/views/messages/EncryptionEvent-test.tsx b/test/unit-tests/components/views/messages/EncryptionEvent-test.tsx
index 7ea9de76bc8b10ae129dd841c6076a2b95834801..c91a274c70df192c288efbc130f01abce784e3e4 100644
--- a/test/unit-tests/components/views/messages/EncryptionEvent-test.tsx
+++ b/test/unit-tests/components/views/messages/EncryptionEvent-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { mocked } from "jest-mock";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { render, screen } from "jest-matrix-react";
 import { waitFor } from "@testing-library/dom";
 
diff --git a/test/unit-tests/components/views/messages/HideActionButton-test.tsx b/test/unit-tests/components/views/messages/HideActionButton-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..650afe69e63bad5521b43989aa030dd3f91f53bf
--- /dev/null
+++ b/test/unit-tests/components/views/messages/HideActionButton-test.tsx
@@ -0,0 +1,84 @@
+/*
+Copyright 2024,2025 New Vector Ltd.
+Copyright 2024 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { fireEvent, render, screen } from "jest-matrix-react";
+import { MatrixEvent, type MatrixClient } from "matrix-js-sdk/src/matrix";
+
+import { HideActionButton } from "../../../../../src/components/views/messages/HideActionButton";
+import SettingsStore from "../../../../../src/settings/SettingsStore";
+import { SettingLevel } from "../../../../../src/settings/SettingLevel";
+import type { Settings } from "../../../../../src/settings/Settings";
+import { MediaPreviewValue } from "../../../../../src/@types/media_preview";
+import { getMockClientWithEventEmitter, withClientContextRenderOptions } from "../../../../test-utils";
+import type { MockedObject } from "jest-mock";
+
+function mockSetting(mediaPreviews: MediaPreviewValue, showMediaEventIds: Settings["showMediaEventIds"]["default"]) {
+    jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
+        if (settingName === "mediaPreviewConfig") {
+            return { media_previews: mediaPreviews, invite_avatars: MediaPreviewValue.Off };
+        } else if (settingName === "showMediaEventIds") {
+            return showMediaEventIds;
+        }
+        throw Error(`Unexpected setting ${settingName}`);
+    });
+}
+
+const EVENT_ID = "$foo:bar";
+
+const event = new MatrixEvent({
+    event_id: EVENT_ID,
+    room_id: "!room:id",
+    sender: "@user:id",
+    type: "m.room.message",
+    content: {
+        body: "test",
+        msgtype: "m.image",
+        url: "mxc://matrix.org/1234",
+    },
+});
+
+describe("HideActionButton", () => {
+    let cli: MockedObject<MatrixClient>;
+    beforeEach(() => {
+        cli = getMockClientWithEventEmitter({
+            getRoom: jest.fn(),
+        });
+    });
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+    it("should show button when event is visible by showMediaEventIds setting", async () => {
+        mockSetting(MediaPreviewValue.Off, { [EVENT_ID]: true });
+        render(<HideActionButton mxEvent={event} />, withClientContextRenderOptions(cli));
+        expect(screen.getByRole("button")).toBeVisible();
+    });
+    it("should show button when event is visible by mediaPreviewConfig setting", async () => {
+        mockSetting(MediaPreviewValue.On, {});
+        render(<HideActionButton mxEvent={event} />, withClientContextRenderOptions(cli));
+        expect(screen.getByRole("button")).toBeVisible();
+    });
+    it("should hide button when event is hidden by showMediaEventIds setting", async () => {
+        mockSetting(MediaPreviewValue.Off, { [EVENT_ID]: false });
+        render(<HideActionButton mxEvent={event} />, withClientContextRenderOptions(cli));
+        expect(screen.queryByRole("button")).toBeNull();
+    });
+    it("should hide button when event is hidden by showImages setting", async () => {
+        mockSetting(MediaPreviewValue.Off, {});
+        render(<HideActionButton mxEvent={event} />, withClientContextRenderOptions(cli));
+        expect(screen.queryByRole("button")).toBeNull();
+    });
+    it("should store event as hidden when clicked", async () => {
+        const spy = jest.spyOn(SettingsStore, "setValue");
+        render(<HideActionButton mxEvent={event} />, withClientContextRenderOptions(cli));
+        fireEvent.click(screen.getByRole("button"));
+        expect(spy).toHaveBeenCalledWith("showMediaEventIds", null, SettingLevel.DEVICE, { "$foo:bar": false });
+        // Button should be hidden after the setting is set.
+        expect(screen.queryByRole("button")).toBeNull();
+    });
+});
diff --git a/test/unit-tests/components/views/messages/LegacyCallEvent-test.tsx b/test/unit-tests/components/views/messages/LegacyCallEvent-test.tsx
index f345b58a1c154570912ff20045dc124f29ee12a7..aa8b7172f84de420776f83d017564237999e00f2 100644
--- a/test/unit-tests/components/views/messages/LegacyCallEvent-test.tsx
+++ b/test/unit-tests/components/views/messages/LegacyCallEvent-test.tsx
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { render, screen } from "jest-matrix-react";
 import { CallErrorCode, CallState } from "matrix-js-sdk/src/webrtc/call";
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import LegacyCallEvent from "../../../../../src/components/views/messages/LegacyCallEvent";
-import LegacyCallEventGrouper from "../../../../../src/components/structures/LegacyCallEventGrouper";
+import type LegacyCallEventGrouper from "../../../../../src/components/structures/LegacyCallEventGrouper";
 
 const THEIR_USER_ID = "@them:here";
 
diff --git a/test/unit-tests/components/views/messages/MBeaconBody-test.tsx b/test/unit-tests/components/views/messages/MBeaconBody-test.tsx
index 6827d45df06a0367ed04ea4d5516ecbef2e0ba20..e0b6481307bbe645c14b49bdff18e46526c5c32a 100644
--- a/test/unit-tests/components/views/messages/MBeaconBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MBeaconBody-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { act, fireEvent, render } from "jest-matrix-react";
 import * as maplibregl from "maplibre-gl";
 import {
@@ -17,7 +17,7 @@ import {
     EventType,
     Relations,
     M_BEACON,
-    Room,
+    type Room,
 } from "matrix-js-sdk/src/matrix";
 
 import MBeaconBody from "../../../../../src/components/views/messages/MBeaconBody";
@@ -28,8 +28,8 @@ import {
     makeRoomWithBeacons,
     makeRoomWithStateEvents,
 } from "../../../../test-utils";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
+import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import Modal from "../../../../../src/Modal";
 import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils";
@@ -63,7 +63,6 @@ describe("<MBeaconBody />", () => {
         mxEvent: defaultEvent,
         highlights: [],
         highlightLink: "",
-        onHeightChanged: jest.fn(),
         onMessageAllowed: jest.fn(),
         // we dont use these and they pollute the snapshots
         permalinkCreator: {} as unknown as RoomPermalinkCreator,
diff --git a/test/unit-tests/components/views/messages/MFileBody-test.tsx b/test/unit-tests/components/views/messages/MFileBody-test.tsx
index 6d7f491bbef1140cf5ba8538010f1805e688f02d..e5e56c2efadc561447b5020b1b382de036cba91f 100644
--- a/test/unit-tests/components/views/messages/MFileBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MFileBody-test.tsx
@@ -62,7 +62,6 @@ describe("<MFileBody/>", () => {
     });
 
     const props = {
-        onHeightChanged: jest.fn(),
         onMessageAllowed: jest.fn(),
         permalinkCreator: new RoomPermalinkCreator(new Room(mediaEvent.getRoomId()!, cli, cli.getUserId()!)),
     };
diff --git a/test/unit-tests/components/views/messages/MImageBody-test.tsx b/test/unit-tests/components/views/messages/MImageBody-test.tsx
index f9ba25d7a96416fec813a6c4203fadf0ae3fd843..edda0d3add17cc4a0ccf22a6997319e80ff4bc09 100644
--- a/test/unit-tests/components/views/messages/MImageBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MImageBody-test.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
+import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
 import { EventType, getHttpUriForMxc, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import fetchMock from "fetch-mock-jest";
 import encrypt from "matrix-encrypt-attachment";
@@ -24,9 +24,11 @@ import {
     mockClientMethodsDevice,
     mockClientMethodsServer,
     mockClientMethodsUser,
+    withClientContextRenderOptions,
 } from "../../../../test-utils";
 import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
+import { MediaPreviewValue } from "../../../../../src/@types/media_preview";
 
 jest.mock("matrix-encrypt-attachment", () => ({
     decryptAttachment: jest.fn(),
@@ -41,6 +43,7 @@ describe("<MImageBody/>", () => {
         ...mockClientMethodsDevice(deviceId),
         ...mockClientMethodsCrypto(),
         getRooms: jest.fn().mockReturnValue([]),
+        getRoom: jest.fn(),
         getIgnoredUsers: jest.fn(),
         getVersions: jest.fn().mockResolvedValue({
             unstable_features: {
@@ -57,6 +60,7 @@ describe("<MImageBody/>", () => {
         },
     );
     const encryptedMediaEvent = new MatrixEvent({
+        event_id: "$foo:bar",
         room_id: "!room:server",
         sender: userId,
         type: EventType.RoomMessage,
@@ -73,7 +77,6 @@ describe("<MImageBody/>", () => {
     });
 
     const props = {
-        onHeightChanged: jest.fn(),
         onMessageAllowed: jest.fn(),
         permalinkCreator: new RoomPermalinkCreator(new Room(encryptedMediaEvent.getRoomId()!, cli, cli.getUserId()!)),
     };
@@ -83,6 +86,11 @@ describe("<MImageBody/>", () => {
         fetchMock.mockReset();
     });
 
+    afterEach(() => {
+        SettingsStore.reset();
+        mocked(encrypt.decryptAttachment).mockReset();
+    });
+
     it("should show a thumbnail while image is being downloaded", async () => {
         fetchMock.getOnce(url, { status: 200 });
 
@@ -92,6 +100,7 @@ describe("<MImageBody/>", () => {
                 mxEvent={encryptedMediaEvent}
                 mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
             />,
+            withClientContextRenderOptions(cli),
         );
 
         // thumbnail with dimensions present
@@ -107,6 +116,7 @@ describe("<MImageBody/>", () => {
                 mxEvent={encryptedMediaEvent}
                 mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
             />,
+            withClientContextRenderOptions(cli),
         );
 
         expect(fetchMock).toHaveBeenCalledWith(url);
@@ -124,6 +134,7 @@ describe("<MImageBody/>", () => {
                 mxEvent={encryptedMediaEvent}
                 mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
             />,
+            withClientContextRenderOptions(cli),
         );
 
         await screen.findByText("Error decrypting image");
@@ -131,7 +142,13 @@ describe("<MImageBody/>", () => {
 
     describe("with image previews/thumbnails disabled", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
+            const origFn = SettingsStore.getValue;
+            jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, ...args) => {
+                if (setting === "mediaPreviewConfig") {
+                    return { invite_avatars: MediaPreviewValue.Off, media_previews: MediaPreviewValue.Off };
+                }
+                return origFn(setting, ...args);
+            });
         });
 
         it("should not download image", async () => {
@@ -143,8 +160,11 @@ describe("<MImageBody/>", () => {
                     mxEvent={encryptedMediaEvent}
                     mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
                 />,
+                withClientContextRenderOptions(cli),
             );
 
+            expect(screen.getByText("Show image")).toBeInTheDocument();
+
             expect(fetchMock).not.toHaveFetched(url);
         });
 
@@ -157,17 +177,21 @@ describe("<MImageBody/>", () => {
                     mxEvent={encryptedMediaEvent}
                     mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
                 />,
+                withClientContextRenderOptions(cli),
             );
 
             expect(screen.getByText("Show image")).toBeInTheDocument();
 
             fireEvent.click(screen.getByRole("button"));
 
-            // image fetched after clicking show image
             expect(fetchMock).toHaveFetched(url);
 
-            // spinner while downloading image
-            expect(screen.getByRole("progressbar")).toBeInTheDocument();
+            // Show image is asynchronous since it applies through a settings watcher hook, so
+            // be sure to wait here.
+            await waitFor(() => {
+                // spinner while downloading image
+                expect(screen.getByRole("progressbar")).toBeInTheDocument();
+            });
         });
     });
 
@@ -191,6 +215,7 @@ describe("<MImageBody/>", () => {
 
         const { container } = render(
             <MImageBody {...props} mxEvent={event} mediaEventHelper={new MediaEventHelper(event)} />,
+            withClientContextRenderOptions(cli),
         );
 
         const img = container.querySelector(".mx_MImageBody_thumbnail")!;
@@ -244,6 +269,7 @@ describe("<MImageBody/>", () => {
 
         const { container } = render(
             <MImageBody {...props} mxEvent={event} mediaEventHelper={new MediaEventHelper(event)} />,
+            withClientContextRenderOptions(cli),
         );
 
         // Wait for spinners to go away
@@ -269,6 +295,7 @@ describe("<MImageBody/>", () => {
 
         const { container } = render(
             <MImageBody {...props} mxEvent={event} mediaEventHelper={new MediaEventHelper(event)} />,
+            withClientContextRenderOptions(cli),
         );
 
         const img = container.querySelector(".mx_MImageBody_thumbnail")!;
diff --git a/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx b/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx
index 2f08a8bf9368c41c31a8af910a1e6bc928d573f5..a559fd2b6d5a7e2167b1ef1ded62668bd1ace9f6 100644
--- a/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx
+++ b/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { RenderResult, render } from "jest-matrix-react";
-import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type RenderResult, render } from "jest-matrix-react";
+import { type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import MKeyVerificationRequest from "../../../../../src/components/views/messages/MKeyVerificationRequest";
 import TileErrorBoundary from "../../../../../src/components/views/messages/TileErrorBoundary";
diff --git a/test/unit-tests/components/views/messages/MLocationBody-test.tsx b/test/unit-tests/components/views/messages/MLocationBody-test.tsx
index f0f7bae8a7557daac17e53a01be3a4d7d50894ac..35681700ca2c9f08f5b997a432e88c45f8e3d958 100644
--- a/test/unit-tests/components/views/messages/MLocationBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MLocationBody-test.tsx
@@ -15,8 +15,8 @@ import { sleep } from "matrix-js-sdk/src/utils";
 
 import MLocationBody from "../../../../../src/components/views/messages/MLocationBody";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
+import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import Modal from "../../../../../src/Modal";
 import SdkConfig from "../../../../../src/SdkConfig";
 import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils";
@@ -39,7 +39,6 @@ describe("MLocationBody", () => {
             mxEvent: defaultEvent,
             highlights: [],
             highlightLink: "",
-            onHeightChanged: jest.fn(),
             onMessageAllowed: jest.fn(),
             permalinkCreator: {} as RoomPermalinkCreator,
             mediaEventHelper: {} as MediaEventHelper,
diff --git a/test/unit-tests/components/views/messages/MPollBody-test.tsx b/test/unit-tests/components/views/messages/MPollBody-test.tsx
index 86849a1069e59067c75be270b612e70b4a8fcd99..52b015bfd0348252d6343e1df2d83221f83c2116 100644
--- a/test/unit-tests/components/views/messages/MPollBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MPollBody-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { act, fireEvent, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
+import { act, fireEvent, render, type RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
 import {
     MatrixEvent,
     Relations,
@@ -15,8 +15,8 @@ import {
     M_POLL_KIND_UNDISCLOSED,
     M_POLL_RESPONSE,
     M_POLL_START,
-    PollStartEventContent,
-    PollAnswer,
+    type PollStartEventContent,
+    type PollAnswer,
     M_TEXT,
 } from "matrix-js-sdk/src/matrix";
 
@@ -25,7 +25,7 @@ import MPollBody, {
     findTopAnswer,
     isPollEnded,
 } from "../../../../../src/components/views/messages/MPollBody";
-import { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
+import { type IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
 import {
     flushPromises,
     getMockClientWithEventEmitter,
@@ -34,8 +34,8 @@ import {
     setupRoomWithPollEvents,
 } from "../../../../test-utils";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
+import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import * as languageHandler from "../../../../../src/languageHandler";
 
 const CHECKED = "mx_PollOption_checked";
@@ -909,7 +909,6 @@ function getMPollBodyPropsFromEvent(mxEvent: MatrixEvent): IBodyProps {
         highlightLink: "unused",
         highlights: [],
         mediaEventHelper: {} as unknown as MediaEventHelper,
-        onHeightChanged: () => {},
         onMessageAllowed: () => {},
         permalinkCreator: {} as unknown as RoomPermalinkCreator,
     };
diff --git a/test/unit-tests/components/views/messages/MPollEndBody-test.tsx b/test/unit-tests/components/views/messages/MPollEndBody-test.tsx
index cc27c8a4d960167d907298b3cba2c16fe7f1fbb2..7015e3d1d99d3d9b52b2554f424909ba46a1a9f9 100644
--- a/test/unit-tests/components/views/messages/MPollEndBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MPollEndBody-test.tsx
@@ -8,14 +8,14 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, waitFor } from "jest-matrix-react";
-import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
+import { type EventTimeline, type MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 
-import { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
+import { type IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
 import { MPollEndBody } from "../../../../../src/components/views/messages/MPollEndBody";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
+import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import {
     flushPromises,
     getMockClientWithEventEmitter,
@@ -70,7 +70,6 @@ describe("<MPollEndBody />", () => {
         mxEvent: pollEndEvent,
         highlightLink: "unused",
         mediaEventHelper: {} as unknown as MediaEventHelper,
-        onHeightChanged: () => {},
         onMessageAllowed: () => {},
         permalinkCreator: {} as unknown as RoomPermalinkCreator,
         ref: undefined as any,
diff --git a/test/unit-tests/components/views/messages/MStickerBody-test.tsx b/test/unit-tests/components/views/messages/MStickerBody-test.tsx
index cf94a6d0c8ae49770eb17ccccb933a50a496b3b4..3ae44215d165c61b389f34c8a7aa5ce659e29379 100644
--- a/test/unit-tests/components/views/messages/MStickerBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MStickerBody-test.tsx
@@ -19,6 +19,7 @@ import {
     mockClientMethodsDevice,
     mockClientMethodsServer,
     mockClientMethodsUser,
+    withClientContextRenderOptions,
 } from "../../../../test-utils";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import MStickerBody from "../../../../../src/components/views/messages/MStickerBody";
@@ -31,6 +32,7 @@ describe("<MStickerBody/>", () => {
         ...mockClientMethodsServer(),
         ...mockClientMethodsDevice(deviceId),
         ...mockClientMethodsCrypto(),
+        getRoom: jest.fn(),
         getRooms: jest.fn().mockReturnValue([]),
         getIgnoredUsers: jest.fn(),
         getVersions: jest.fn().mockResolvedValue({
@@ -64,7 +66,6 @@ describe("<MStickerBody/>", () => {
     });
 
     const props = {
-        onHeightChanged: jest.fn(),
         onMessageAllowed: jest.fn(),
         permalinkCreator: new RoomPermalinkCreator(new Room(mediaEvent.getRoomId()!, cli, cli.getUserId()!)),
     };
@@ -77,7 +78,7 @@ describe("<MStickerBody/>", () => {
     it("should show a tooltip on hover", async () => {
         fetchMock.getOnce(url, { status: 200 });
 
-        render(<MStickerBody {...props} mxEvent={mediaEvent} />);
+        render(<MStickerBody {...props} mxEvent={mediaEvent} />, withClientContextRenderOptions(cli));
 
         expect(screen.queryByRole("tooltip")).toBeNull();
         await userEvent.hover(screen.getByRole("img"));
diff --git a/test/unit-tests/components/views/messages/MVideoBody-test.tsx b/test/unit-tests/components/views/messages/MVideoBody-test.tsx
index e445dc1711edb4b718cd020c08b6654648f7772a..1d058a7b0c930fb8ac5d3bd8258e87a6381ab34b 100644
--- a/test/unit-tests/components/views/messages/MVideoBody-test.tsx
+++ b/test/unit-tests/components/views/messages/MVideoBody-test.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,12 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { EventType, getHttpUriForMxc, IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { render, RenderResult } from "jest-matrix-react";
+import { EventType, getHttpUriForMxc, type IContent, type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { fireEvent, render, screen, type RenderResult } from "jest-matrix-react";
 import fetchMock from "fetch-mock-jest";
+import { type MockedObject } from "jest-mock";
 
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { type RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
 import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 import {
     getMockClientWithEventEmitter,
@@ -20,26 +21,32 @@ import {
     mockClientMethodsDevice,
     mockClientMethodsServer,
     mockClientMethodsUser,
+    withClientContextRenderOptions,
 } from "../../../../test-utils";
 import MVideoBody from "../../../../../src/components/views/messages/MVideoBody";
+import type { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
+import SettingsStore from "../../../../../src/settings/SettingsStore";
+import { MediaPreviewValue } from "../../../../../src/@types/media_preview";
+
+// Needed so we don't throw an error about failing to decrypt.
+jest.mock("matrix-encrypt-attachment", () => ({
+    decryptAttachment: jest.fn(),
+}));
 
 describe("MVideoBody", () => {
-    it("does not crash when given a portrait image", () => {
-        // Check for an unreliable crash caused by a fractional-sized
-        // image dimension being used for a CanvasImageData.
-        const { asFragment } = makeMVideoBody(720, 1280);
-        expect(asFragment()).toMatchSnapshot();
-        // If we get here, we did not crash.
-    });
+    const userId = "@user:server";
+    const deviceId = "DEADB33F";
 
-    it("should show poster for encrypted media before downloading it", async () => {
-        const userId = "@user:server";
-        const deviceId = "DEADB33F";
-        const cli = getMockClientWithEventEmitter({
+    const thumbUrl = "https://server/_matrix/media/v3/download/server/encrypted-poster";
+    let cli: MockedObject<MatrixClient>;
+
+    beforeEach(() => {
+        cli = getMockClientWithEventEmitter({
             ...mockClientMethodsUser(userId),
             ...mockClientMethodsServer(),
             ...mockClientMethodsDevice(deviceId),
             ...mockClientMethodsCrypto(),
+            getRoom: jest.fn(),
             getRooms: jest.fn().mockReturnValue([]),
             getIgnoredUsers: jest.fn(),
             getVersions: jest.fn().mockResolvedValue({
@@ -49,40 +56,104 @@ describe("MVideoBody", () => {
                 },
             }),
         });
-        const thumbUrl = "https://server/_matrix/media/v3/download/server/encrypted-poster";
-        fetchMock.getOnce(thumbUrl, { status: 200 });
-
         // eslint-disable-next-line no-restricted-properties
         cli.mxcUrlToHttp.mockImplementation(
             (mxcUrl: string, width?: number, height?: number, resizeMethod?: string, allowDirectLinks?: boolean) => {
                 return getHttpUriForMxc("https://server", mxcUrl, width, height, resizeMethod, allowDirectLinks);
             },
         );
-        const encryptedMediaEvent = new MatrixEvent({
-            room_id: "!room:server",
-            sender: userId,
-            type: EventType.RoomMessage,
-            content: {
-                body: "alt for a test video",
-                info: {
-                    duration: 420,
-                    w: 40,
-                    h: 50,
-                    thumbnail_file: {
-                        url: "mxc://server/encrypted-poster",
-                    },
-                },
-                file: {
-                    url: "mxc://server/encrypted-image",
+        fetchMock.mockReset();
+    });
+
+    const encryptedMediaEvent = new MatrixEvent({
+        room_id: "!room:server",
+        sender: userId,
+        type: EventType.RoomMessage,
+        event_id: "$foo:bar",
+        content: {
+            body: "alt for a test video",
+            info: {
+                duration: 420,
+                w: 40,
+                h: 50,
+                thumbnail_file: {
+                    url: "mxc://server/encrypted-poster",
                 },
             },
-        });
+            file: {
+                url: "mxc://server/encrypted-image",
+            },
+        },
+    });
 
+    it("does not crash when given a portrait image", () => {
+        // Check for an unreliable crash caused by a fractional-sized
+        // image dimension being used for a CanvasImageData.
+        const { asFragment } = makeMVideoBody(720, 1280);
+        expect(asFragment()).toMatchSnapshot();
+        // If we get here, we did not crash.
+    });
+
+    it("should show poster for encrypted media before downloading it", async () => {
+        fetchMock.getOnce(thumbUrl, { status: 200 });
         const { asFragment } = render(
             <MVideoBody mxEvent={encryptedMediaEvent} mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)} />,
+            withClientContextRenderOptions(cli),
         );
         expect(asFragment()).toMatchSnapshot();
     });
+
+    describe("with video previews/thumbnails disabled", () => {
+        beforeEach(() => {
+            const origFn = SettingsStore.getValue;
+            jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, ...args) => {
+                if (setting === "mediaPreviewConfig") {
+                    return { invite_avatars: MediaPreviewValue.Off, media_previews: MediaPreviewValue.Off };
+                }
+                return origFn(setting, ...args);
+            });
+        });
+
+        afterEach(() => {
+            SettingsStore.reset();
+            jest.restoreAllMocks();
+        });
+
+        it("should not download video", async () => {
+            fetchMock.getOnce(thumbUrl, { status: 200 });
+
+            render(
+                <MVideoBody
+                    mxEvent={encryptedMediaEvent}
+                    mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
+                />,
+                withClientContextRenderOptions(cli),
+            );
+
+            expect(screen.getByText("Show video")).toBeInTheDocument();
+
+            expect(fetchMock).not.toHaveFetched(thumbUrl);
+        });
+
+        it("should render video poster after user consent", async () => {
+            fetchMock.getOnce(thumbUrl, { status: 200 });
+
+            render(
+                <MVideoBody
+                    mxEvent={encryptedMediaEvent}
+                    mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
+                />,
+                withClientContextRenderOptions(cli),
+            );
+
+            const placeholderButton = screen.getByRole("button", { name: "Show video" });
+
+            expect(placeholderButton).toBeInTheDocument();
+            fireEvent.click(placeholderButton);
+
+            expect(fetchMock).toHaveFetched(thumbUrl);
+        });
+    });
 });
 
 function makeMVideoBody(w: number, h: number): RenderResult {
@@ -109,11 +180,10 @@ function makeMVideoBody(w: number, h: number): RenderResult {
         content,
     });
 
-    const defaultProps: MVideoBody["props"] = {
+    const defaultProps: IBodyProps = {
         mxEvent: event,
         highlights: [],
         highlightLink: "",
-        onHeightChanged: jest.fn(),
         onMessageAllowed: jest.fn(),
         permalinkCreator: {} as RoomPermalinkCreator,
         mediaEventHelper: { media: { isEncrypted: false } } as MediaEventHelper,
@@ -121,6 +191,7 @@ function makeMVideoBody(w: number, h: number): RenderResult {
 
     const mockClient = getMockClientWithEventEmitter({
         mxcUrlToHttp: jest.fn(),
+        getRoom: jest.fn(),
     });
 
     return render(
diff --git a/test/unit-tests/components/views/messages/MessageActionBar-test.tsx b/test/unit-tests/components/views/messages/MessageActionBar-test.tsx
index 1f28f1d008a99b6fb94cf681f5c81e79721a9f0e..10592635338d8c75d08dcf39c48b2267560a564e 100644
--- a/test/unit-tests/components/views/messages/MessageActionBar-test.tsx
+++ b/test/unit-tests/components/views/messages/MessageActionBar-test.tsx
@@ -30,7 +30,7 @@ import {
 } from "../../../../test-utils";
 import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
 import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import dispatcher from "../../../../../src/dispatcher/dispatcher";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import { Action } from "../../../../../src/dispatcher/actions";
diff --git a/test/unit-tests/components/views/messages/MessageEvent-test.tsx b/test/unit-tests/components/views/messages/MessageEvent-test.tsx
index 63e00134faec6a4548b99d1fcdd2b234c7655b52..a2f788c4abda82d7405497d8066e349f50f2e979 100644
--- a/test/unit-tests/components/views/messages/MessageEvent-test.tsx
+++ b/test/unit-tests/components/views/messages/MessageEvent-test.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult } from "jest-matrix-react";
-import { MatrixClient, MatrixEvent, EventType, Room, MsgType } from "matrix-js-sdk/src/matrix";
+import { render, type RenderResult } from "jest-matrix-react";
+import { type MatrixClient, type MatrixEvent, EventType, type Room, MsgType } from "matrix-js-sdk/src/matrix";
 import fetchMock from "fetch-mock-jest";
 import fs from "fs";
 import path from "path";
@@ -59,13 +59,7 @@ describe("MessageEvent", () => {
     let event: MatrixEvent;
 
     const renderMessageEvent = (): RenderResult => {
-        return render(
-            <MessageEvent
-                mxEvent={event}
-                onHeightChanged={jest.fn()}
-                permalinkCreator={new RoomPermalinkCreator(room)}
-            />,
-        );
+        return render(<MessageEvent mxEvent={event} permalinkCreator={new RoomPermalinkCreator(room)} />);
     };
 
     beforeEach(() => {
diff --git a/test/unit-tests/components/views/messages/ReactionsRowButton-test.tsx b/test/unit-tests/components/views/messages/ReactionsRowButton-test.tsx
index d00237e5d2096432492ee674631c638365a91021..3b4298a61ca82611227ee330a802abcdc090b500 100644
--- a/test/unit-tests/components/views/messages/ReactionsRowButton-test.tsx
+++ b/test/unit-tests/components/views/messages/ReactionsRowButton-test.tsx
@@ -7,12 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { IContent, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type IContent, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { render } from "jest-matrix-react";
 
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { getMockClientWithEventEmitter } from "../../../../test-utils";
-import ReactionsRowButton, { IProps } from "../../../../../src/components/views/messages/ReactionsRowButton";
+import ReactionsRowButton, { type IProps } from "../../../../../src/components/views/messages/ReactionsRowButton";
 
 describe("ReactionsRowButton", () => {
     const userId = "@alice:server";
diff --git a/test/unit-tests/components/views/messages/TextualBody-test.tsx b/test/unit-tests/components/views/messages/TextualBody-test.tsx
index 1e526e346b8077fbd2a90ee5704b7841cb44d5a5..cb3d755f8bcd10170ac3354842af065027ed2e89 100644
--- a/test/unit-tests/components/views/messages/TextualBody-test.tsx
+++ b/test/unit-tests/components/views/messages/TextualBody-test.tsx
@@ -6,19 +6,26 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
-import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { mocked, MockedObject } from "jest-mock";
+import React, { type ComponentProps } from "react";
+import { type MatrixClient, type MatrixEvent, PushRuleKind, type Room } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
 import { render, waitFor } from "jest-matrix-react";
-
-import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../../test-utils";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+
+import {
+    getMockClientWithEventEmitter,
+    mkEvent,
+    mkMessage,
+    mkStubRoom,
+    mockClientPushProcessor,
+} from "../../../../test-utils";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import * as languageHandler from "../../../../../src/languageHandler";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import TextualBody from "../../../../../src/components/views/messages/TextualBody";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
+import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
 
 const room1Id = "!room1:example.com";
 const room2Id = "!room2:example.com";
@@ -54,8 +61,8 @@ describe("<TextualBody />", () => {
         jest.spyOn(global.Math, "random").mockRestore();
     });
 
-    const defaultRoom = mkStubRoom(room1Id, "test room", undefined);
-    const otherRoom = mkStubRoom(room2Id, room2Name, undefined);
+    let defaultRoom: Room;
+    let otherRoom: Room;
     let defaultMatrixClient: MockedObject<MatrixClient>;
 
     const defaultEvent = mkEvent({
@@ -69,6 +76,14 @@ describe("<TextualBody />", () => {
         event: true,
     });
 
+    const defaultProps: ComponentProps<typeof TextualBody> = {
+        mxEvent: defaultEvent,
+        highlights: [] as string[],
+        highlightLink: "",
+        onMessageAllowed: jest.fn(),
+        mediaEventHelper: {} as MediaEventHelper,
+    };
+
     beforeEach(() => {
         defaultMatrixClient = getMockClientWithEventEmitter({
             getRoom: (roomId: string | undefined) => {
@@ -85,6 +100,12 @@ describe("<TextualBody />", () => {
                 throw new Error("MockClient event not found");
             },
         });
+        // @ts-expect-error
+        defaultMatrixClient.pushProcessor = new PushProcessor(defaultMatrixClient);
+
+        defaultRoom = mkStubRoom(room1Id, "test room", defaultMatrixClient);
+        defaultProps.permalinkCreator = new RoomPermalinkCreator(defaultRoom);
+        otherRoom = mkStubRoom(room2Id, room2Name, defaultMatrixClient);
 
         mocked(defaultRoom).findEventById.mockImplementation((eventId: string) => {
             if (eventId === defaultEvent.getId()) return defaultEvent;
@@ -93,16 +114,6 @@ describe("<TextualBody />", () => {
         jest.spyOn(global.Math, "random").mockReturnValue(0.123456);
     });
 
-    const defaultProps = {
-        mxEvent: defaultEvent,
-        highlights: [] as string[],
-        highlightLink: "",
-        onMessageAllowed: jest.fn(),
-        onHeightChanged: jest.fn(),
-        permalinkCreator: new RoomPermalinkCreator(defaultRoom),
-        mediaEventHelper: {} as MediaEventHelper,
-    };
-
     const getComponent = (props = {}, matrixClient: MatrixClient = defaultMatrixClient, renderingFn?: any) =>
         (renderingFn ?? render)(
             <MatrixClientContext.Provider value={matrixClient}>
@@ -177,7 +188,7 @@ describe("<TextualBody />", () => {
             const { container } = getComponent({ mxEvent: ev });
             const content = container.querySelector(".mx_EventTile_body");
             expect(content.innerHTML).toMatchInlineSnapshot(
-                `"Chat with <a href="https://matrix.to/#/@user:example.com" class="linkified" rel="noreferrer noopener">@user:example.com</a>"`,
+                `"Chat with <a href="https://matrix.to/#/@user:example.com" rel="noreferrer noopener" class="linkified">@user:example.com</a>"`,
             );
         });
 
@@ -186,7 +197,7 @@ describe("<TextualBody />", () => {
             const { container } = getComponent({ mxEvent: ev });
             const content = container.querySelector(".mx_EventTile_body");
             expect(content.innerHTML).toMatchInlineSnapshot(
-                `"Chat with <span><bdi><a class="mx_Pill mx_UserPill mx_UserPill_me" href="https://matrix.to/#/@user:example.com"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Member</span></a></bdi></span>"`,
+                `"Chat with <bdi><a class="mx_Pill mx_UserPill mx_UserPill_me" href="https://matrix.to/#/@user:example.com"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/image.png"></span><span class="mx_Pill_text">Member</span></a></bdi>"`,
             );
         });
 
@@ -195,7 +206,7 @@ describe("<TextualBody />", () => {
             const { container } = getComponent({ mxEvent: ev });
             const content = container.querySelector(".mx_EventTile_body");
             expect(content.innerHTML).toMatchInlineSnapshot(
-                `"Visit <a href="https://matrix.to/#/#room:example.com" class="linkified" rel="noreferrer noopener">#room:example.com</a>"`,
+                `"Visit <a href="https://matrix.to/#/#room:example.com" rel="noreferrer noopener" class="linkified">#room:example.com</a>"`,
             );
         });
 
@@ -204,7 +215,7 @@ describe("<TextualBody />", () => {
             const { container } = getComponent({ mxEvent: ev });
             const content = container.querySelector(".mx_EventTile_body");
             expect(content.innerHTML).toMatchInlineSnapshot(
-                `"Visit <span><bdi><a class="mx_Pill mx_RoomPill" href="https://matrix.to/#/#room:example.com"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" class="mx_Pill_LinkIcon mx_BaseAvatar"><path d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"></path></svg><span class="mx_Pill_text">#room:example.com</span></a></bdi></span>"`,
+                `"Visit <bdi><a class="mx_Pill mx_RoomPill" href="https://matrix.to/#/#room:example.com"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" class="mx_Pill_LinkIcon mx_BaseAvatar"><path d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"></path></svg><span class="mx_Pill_text">#room:example.com</span></a></bdi>"`,
             );
         });
 
@@ -228,13 +239,31 @@ describe("<TextualBody />", () => {
             const content = container.querySelector(".mx_EventTile_body");
             expect(content.innerHTML.replace(defaultEvent.getId(), "%event_id%")).toMatchSnapshot();
         });
+
+        it("should pillify a keyword responsible for triggering a notification", () => {
+            const ev = mkRoomTextMessage("foo bar baz");
+            ev.setPushDetails(undefined, {
+                actions: [],
+                pattern: "bar",
+                rule_id: "bar",
+                default: false,
+                enabled: true,
+                kind: PushRuleKind.ContentSpecific,
+            });
+            const { container } = getComponent({ mxEvent: ev });
+            const content = container.querySelector(".mx_EventTile_body");
+            expect(content.innerHTML).toMatchInlineSnapshot(
+                `"foo <bdi><span tabindex="0"><span class="mx_Pill mx_KeywordPill"><span class="mx_Pill_text">bar</span></span></span></bdi> baz"`,
+            );
+        });
     });
 
     describe("renders formatted m.text correctly", () => {
         let matrixClient: MatrixClient;
         beforeEach(() => {
             matrixClient = getMockClientWithEventEmitter({
-                getRoom: () => mkStubRoom(room1Id, "room name", undefined),
+                getRoom: jest.fn(),
+                ...mockClientPushProcessor(),
                 getAccountData: (): MatrixEvent | undefined => undefined,
                 getUserId: () => "@me:my_server",
                 getHomeserverUrl: () => "https://my_server/",
@@ -243,6 +272,7 @@ describe("<TextualBody />", () => {
                 isGuest: () => false,
                 mxcUrlToHttp: (s: string) => s,
             });
+            mocked(matrixClient.getRoom).mockReturnValue(mkStubRoom(room1Id, "room name", matrixClient));
             DMRoomMap.makeShared(defaultMatrixClient);
         });
 
@@ -381,21 +411,21 @@ describe("<TextualBody />", () => {
         beforeEach(() => {
             languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
             matrixClient = getMockClientWithEventEmitter({
-                getRoom: () => mkStubRoom("room_id", "room name", undefined),
+                getRoom: jest.fn(),
+                getUserId: jest.fn(),
+                ...mockClientPushProcessor(),
                 getAccountData: (): MatrixClient | undefined => undefined,
                 getUrlPreview: (url: string) => new Promise(() => {}),
                 isGuest: () => false,
                 mxcUrlToHttp: (s: string) => s,
             });
+            mocked(matrixClient.getRoom).mockReturnValue(mkStubRoom("room_id", "room name", matrixClient));
             DMRoomMap.makeShared(defaultMatrixClient);
         });
 
         it("renders url previews correctly", () => {
             const ev = mkRoomTextMessage("Visit https://matrix.org/");
-            const { container, rerender } = getComponent(
-                { mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() },
-                matrixClient,
-            );
+            const { container, rerender } = getComponent({ mxEvent: ev, showUrlPreview: true }, matrixClient);
 
             expect(container).toHaveTextContent(ev.getContent().body);
             expect(container.querySelector("a")).toHaveAttribute("href", "https://matrix.org/");
@@ -416,11 +446,7 @@ describe("<TextualBody />", () => {
             jest.spyOn(ev, "replacingEventDate").mockReturnValue(new Date(1993, 7, 3));
             ev.makeReplaced(ev2);
 
-            getComponent(
-                { mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn(), replacingEventId: ev.getId() },
-                matrixClient,
-                rerender,
-            );
+            getComponent({ mxEvent: ev, showUrlPreview: true, replacingEventId: ev.getId() }, matrixClient, rerender);
 
             expect(container).toHaveTextContent(ev2.getContent()["m.new_content"].body + "(edited)");
 
@@ -434,13 +460,10 @@ describe("<TextualBody />", () => {
         it("should listen to showUrlPreview change", () => {
             const ev = mkRoomTextMessage("Visit https://matrix.org/");
 
-            const { container, rerender } = getComponent(
-                { mxEvent: ev, showUrlPreview: false, onHeightChanged: jest.fn() },
-                matrixClient,
-            );
+            const { container, rerender } = getComponent({ mxEvent: ev, showUrlPreview: false }, matrixClient);
             expect(container.querySelector(".mx_LinkPreviewGroup")).toBeNull();
 
-            getComponent({ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() }, matrixClient, rerender);
+            getComponent({ mxEvent: ev, showUrlPreview: true }, matrixClient, rerender);
             expect(container.querySelector(".mx_LinkPreviewGroup")).toBeTruthy();
         });
     });
diff --git a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
index a3b9fb205f4f5590eec8c91488c095b91d5940c2..8c67d88bb43928cca86b534c5e57ce2705485d10 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
@@ -35,10 +35,10 @@ exports[`DecryptionFailureBody should handle messages from users who change iden
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Zm0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-.9-.146-1.767-.438-2.6A7.951 7.951 0 0 0 18.3 7.1L7.1 18.3c.7.55 1.467.97 2.3 1.262.833.292 1.7.438 2.6.438Zm-6.3-3.1L16.9 5.7a7.95 7.95 0 0 0-2.3-1.263A7.813 7.813 0 0 0 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .9.146 1.767.438 2.6A7.95 7.95 0 0 0 5.7 16.9Z"
+          d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
         />
       </svg>
-      Sender's verified identity has changed
+      Sender's verified identity was reset
     </span>
   </div>
 </div>
diff --git a/test/unit-tests/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap
index de31628ec3db3f17eefd28855cd976252dc9b54f..14007bd61adb8d88e3deaeaf0c3692f4eec22888 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap
@@ -14,7 +14,7 @@ exports[`<MBeaconBody /> when map display is not configured renders maps unavail
     xmlns="http://www.w3.org/2000/svg"
   >
     <path
-      d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+      d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
     />
   </svg>
   <h3
diff --git a/test/unit-tests/components/views/messages/__snapshots__/MFileBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/MFileBody-test.tsx.snap
index d15b7f454e94053f5324d43fbcb244c3ec02fa24..a7356697e51e7c53b6cba19479ae6d3584e50758 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/MFileBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/MFileBody-test.tsx.snap
@@ -9,7 +9,7 @@ exports[`<MFileBody/> should show a download button in file rendering type 1`] =
       class="mx_MFileBody_download"
     >
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="sm"
         download="alt for a image"
@@ -28,7 +28,7 @@ exports[`<MFileBody/> should show a download button in file rendering type 1`] =
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 15.575c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212l-3.6-3.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7c.183-.183.42-.28.712-.288.292-.008.53.08.713.263L11 12.15V5c0-.283.096-.52.287-.713A.968.968 0 0 1 12 4c.283 0 .52.096.713.287.191.192.287.43.287.713v7.15l1.875-1.875c.183-.183.42-.27.713-.263.291.009.529.105.712.288a.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-3.6 3.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063ZM6 20c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.967.967 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
+            d="M12 15.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-3.6-3.6a.95.95 0 0 1-.275-.7q0-.425.275-.7.274-.275.712-.288t.713.263L11 12.15V5q0-.424.287-.713A.97.97 0 0 1 12 4q.424 0 .713.287Q13 4.576 13 5v7.15l1.875-1.875q.274-.274.713-.263.437.014.712.288a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-3.6 3.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063M6 20q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
           />
         </svg>
         Download
diff --git a/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap
index 3ba381641bca9d27f0865e647ef9f62806ac8800..8b9950c6813d1d7fcd1bb975ca76e5dd67a76210 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap
@@ -41,9 +41,6 @@ exports[`<MImageBody/> should generate a thumbnail if one isn't included for ani
             GIF
           </p>
         </div>
-        <div
-          style="height: 50px; width: 40px;"
-        />
       </div>
     </a>
   </div>
@@ -77,9 +74,6 @@ exports[`<MImageBody/> should show a thumbnail while image is being downloaded 1
       <div
         style="max-height: 50px; max-width: 40px;"
       />
-      <div
-        style="height: 50px; width: 40px;"
-      />
     </div>
   </div>
 </div>
diff --git a/test/unit-tests/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap
index 7b919b53260f908c01d020d3c9d6467a2daa83ec..6470175c3ce9f80cc3b575bd9bdbd060310c7983 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap
@@ -34,7 +34,7 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
     class="mx_MLocationBody"
   >
     <div
-      aria-labelledby=":ri:"
+      aria-labelledby="«ri»"
       class="mx_MLocationBody_map"
     >
       <div
@@ -58,7 +58,7 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
+                  d="M12 21.325a2.1 2.1 0 0 1-.7-.125 1.8 1.8 0 0 1-.625-.375A39 39 0 0 1 7.8 17.9q-1.25-1.425-2.087-2.762-.838-1.338-1.275-2.575Q4 11.325 4 10.2q0-3.75 2.412-5.975T12 2t5.587 2.225T20 10.2q0 1.125-.437 2.363-.438 1.237-1.275 2.574A22 22 0 0 1 16.2 17.9a39 39 0 0 1-2.875 2.925 1.8 1.8 0 0 1-.625.375 2.1 2.1 0 0 1-.7.125M12 12q.825 0 1.412-.588Q14 10.826 14 10t-.588-1.412A1.93 1.93 0 0 0 12 8q-.825 0-1.412.588A1.93 1.93 0 0 0 10 10q0 .825.588 1.412Q11.175 12 12 12"
                 />
               </svg>
             </div>
@@ -76,7 +76,7 @@ exports[`MLocationBody <MLocationBody> without error renders marker correctly fo
     class="mx_MLocationBody"
   >
     <div
-      aria-labelledby=":ru:"
+      aria-labelledby="«ru»"
       class="mx_MLocationBody_map"
     >
       <div
@@ -92,7 +92,7 @@ exports[`MLocationBody <MLocationBody> without error renders marker correctly fo
               class="mx_Marker_border"
             >
               <span
-                class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                 data-color="3"
                 data-testid="avatar-img"
                 data-type="round"
diff --git a/test/unit-tests/components/views/messages/__snapshots__/PinnedMessageBadge-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/PinnedMessageBadge-test.tsx.snap
index 31e192628a8f6fd2bc278db0a488173c584c6d83..043f60f880623c67711f1781582d69803c5d3e65 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/PinnedMessageBadge-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/PinnedMessageBadge-test.tsx.snap
@@ -13,7 +13,7 @@ exports[`PinnedMessageBadge should render 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+        d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
       />
     </svg>
     Pinned message
diff --git a/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap
index 1ea245acf0c56c9138d12ee51009ed87c6613706..ae1c638a9003a9bb9e2a71fb27b455cff012fa50 100644
--- a/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap
+++ b/test/unit-tests/components/views/messages/__snapshots__/TextualBody-test.tsx.snap
@@ -77,40 +77,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for an
   dir="auto"
 >
   Chat with 
-  <span>
-    <bdi>
-      <a
-        class="mx_Pill mx_UserPill"
-        href="https://matrix.to/#/@user:example.com"
+  <bdi>
+    <a
+      class="mx_Pill mx_UserPill"
+      href="https://matrix.to/#/@user:example.com"
+    >
+      <span
+        aria-hidden="true"
+        aria-label="Profile picture"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="2"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 16px;"
       >
-        <span
-          aria-hidden="true"
-          aria-label="Profile picture"
-          class="_avatar_mcap2_17 mx_BaseAvatar"
-          data-color="2"
-          data-testid="avatar-img"
+        <img
+          alt=""
+          class="_image_1qbcf_41"
           data-type="round"
-          style="--cpd-avatar-size: 16px;"
-        >
-          <img
-            alt=""
-            class="_image_mcap2_50"
-            data-type="round"
-            height="16px"
-            loading="lazy"
-            referrerpolicy="no-referrer"
-            src="mxc://avatar.url/image.png"
-            width="16px"
-          />
-        </span>
-        <span
-          class="mx_Pill_text"
-        >
-          Member
-        </span>
-      </a>
-    </bdi>
-  </span>
+          height="16px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="mxc://avatar.url/image.png"
+          width="16px"
+        />
+      </span>
+      <span
+        class="mx_Pill_text"
+      >
+        Member
+      </span>
+    </a>
+  </bdi>
 </div>
 `;
 
@@ -124,40 +122,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for eve
       dir="auto"
     >
       See this message 
-      <span>
-        <bdi>
-          <a
-            class="mx_Pill mx_EventPill"
-            href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com"
+      <bdi>
+        <a
+          class="mx_Pill mx_EventPill"
+          href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/$16085560162aNpaH:example.com?via=example.com"
+        >
+          <span
+            aria-hidden="true"
+            aria-label="Avatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar"
+            data-color="1"
+            data-testid="avatar-img"
+            data-type="round"
+            style="--cpd-avatar-size: 16px;"
           >
-            <span
-              aria-hidden="true"
-              aria-label="Avatar"
-              class="_avatar_mcap2_17 mx_BaseAvatar"
-              data-color="1"
-              data-testid="avatar-img"
+            <img
+              alt=""
+              class="_image_1qbcf_41"
               data-type="round"
-              style="--cpd-avatar-size: 16px;"
-            >
-              <img
-                alt=""
-                class="_image_mcap2_50"
-                data-type="round"
-                height="16px"
-                loading="lazy"
-                referrerpolicy="no-referrer"
-                src="mxc://avatar.url/room.png"
-                width="16px"
-              />
-            </span>
-            <span
-              class="mx_Pill_text"
-            >
-              Message in room name
-            </span>
-          </a>
-        </bdi>
-      </span>
+              height="16px"
+              loading="lazy"
+              referrerpolicy="no-referrer"
+              src="mxc://avatar.url/room.png"
+              width="16px"
+            />
+          </span>
+          <span
+            class="mx_Pill_text"
+          >
+            Message in room name
+          </span>
+        </a>
+      </bdi>
     </div>
   </div>
 </DocumentFragment>
@@ -173,40 +169,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for roo
       dir="auto"
     >
       A 
-      <span>
-        <bdi>
-          <a
-            class="mx_Pill mx_RoomPill"
-            href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com?via=example.com&via=bob.com"
+      <bdi>
+        <a
+          class="mx_Pill mx_RoomPill"
+          href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com?via=example.com&via=bob.com"
+        >
+          <span
+            aria-hidden="true"
+            aria-label="Avatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar"
+            data-color="1"
+            data-testid="avatar-img"
+            data-type="round"
+            style="--cpd-avatar-size: 16px;"
           >
-            <span
-              aria-hidden="true"
-              aria-label="Avatar"
-              class="_avatar_mcap2_17 mx_BaseAvatar"
-              data-color="1"
-              data-testid="avatar-img"
+            <img
+              alt=""
+              class="_image_1qbcf_41"
               data-type="round"
-              style="--cpd-avatar-size: 16px;"
-            >
-              <img
-                alt=""
-                class="_image_mcap2_50"
-                data-type="round"
-                height="16px"
-                loading="lazy"
-                referrerpolicy="no-referrer"
-                src="mxc://avatar.url/room.png"
-                width="16px"
-              />
-            </span>
-            <span
-              class="mx_Pill_text"
-            >
-              room name
-            </span>
-          </a>
-        </bdi>
-      </span>
+              height="16px"
+              loading="lazy"
+              referrerpolicy="no-referrer"
+              src="mxc://avatar.url/room.png"
+              width="16px"
+            />
+          </span>
+          <span
+            class="mx_Pill_text"
+          >
+            room name
+          </span>
+        </a>
+      </bdi>
        with vias
     </div>
   </div>
@@ -287,40 +281,38 @@ exports[`<TextualBody /> renders formatted m.text correctly pills get injected c
   dir="auto"
 >
   Hey 
-  <span>
-    <bdi>
-      <a
-        class="mx_Pill mx_UserPill"
-        href="https://matrix.to/#/@user:server"
+  <bdi>
+    <a
+      class="mx_Pill mx_UserPill"
+      href="https://matrix.to/#/@user:server"
+    >
+      <span
+        aria-hidden="true"
+        aria-label="Profile picture"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="2"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 16px;"
       >
-        <span
-          aria-hidden="true"
-          aria-label="Profile picture"
-          class="_avatar_mcap2_17 mx_BaseAvatar"
-          data-color="2"
-          data-testid="avatar-img"
+        <img
+          alt=""
+          class="_image_1qbcf_41"
           data-type="round"
-          style="--cpd-avatar-size: 16px;"
-        >
-          <img
-            alt=""
-            class="_image_mcap2_50"
-            data-type="round"
-            height="16px"
-            loading="lazy"
-            referrerpolicy="no-referrer"
-            src="mxc://avatar.url/image.png"
-            width="16px"
-          />
-        </span>
-        <span
-          class="mx_Pill_text"
-        >
-          Member
-        </span>
-      </a>
-    </bdi>
-  </span>
+          height="16px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="mxc://avatar.url/image.png"
+          width="16px"
+        />
+      </span>
+      <span
+        class="mx_Pill_text"
+      >
+        Member
+      </span>
+    </a>
+  </bdi>
 </div>
 `;
 
@@ -466,25 +458,21 @@ exports[`<TextualBody /> renders formatted m.text correctly spoilers get injecte
   dir="auto"
 >
   Hey 
-  <span>
-    <button
-      class="mx_EventTile_spoiler"
+  <button
+    class="mx_EventTile_spoiler"
+  >
+    <span
+      class="mx_EventTile_spoiler_reason"
     >
-      <span
-        class="mx_EventTile_spoiler_reason"
-      >
-        (movie)
-      </span>
-       
-      <span
-        class="mx_EventTile_spoiler_content"
-      >
-        <span>
-          the movie was awesome
-        </span>
-      </span>
-    </button>
-  </span>
+      (movie)
+    </span>
+     
+    <span
+      class="mx_EventTile_spoiler_content"
+    >
+      the movie was awesome
+    </span>
+  </button>
 </div>
 `;
 
@@ -522,9 +510,9 @@ exports[`<TextualBody /> renders plain-text m.text correctly linkification get a
 </div>
 `;
 
-exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi></span>"`;
+exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/image.png"></span><span class="mx_Pill_text">Message from Member</span></a></bdi>"`;
 
-exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi></span>"`;
+exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/room.png"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi>"`;
 
 exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = `
 <div
@@ -532,32 +520,30 @@ exports[`<TextualBody /> renders plain-text m.text correctly should pillify a pe
   dir="auto"
 >
   Visit 
-  <span>
-    <bdi>
-      <a
-        class="mx_Pill mx_EventPill"
-        href="https://matrix.to/#/!room1:example.com/!abc123"
+  <bdi>
+    <a
+      class="mx_Pill mx_EventPill"
+      href="https://matrix.to/#/!room1:example.com/!abc123"
+    >
+      <svg
+        class="mx_Pill_LinkIcon mx_BaseAvatar"
+        fill="currentColor"
+        height="1em"
+        viewBox="0 0 24 24"
+        width="1em"
+        xmlns="http://www.w3.org/2000/svg"
       >
-        <svg
-          class="mx_Pill_LinkIcon mx_BaseAvatar"
-          fill="currentColor"
-          height="1em"
-          viewBox="0 0 24 24"
-          width="1em"
-          xmlns="http://www.w3.org/2000/svg"
-        >
-          <path
-            d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
-          />
-        </svg>
-        <span
-          class="mx_Pill_text"
-        >
-          Message
-        </span>
-      </a>
-    </bdi>
-  </span>
+        <path
+          d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
+        />
+      </svg>
+      <span
+        class="mx_Pill_text"
+      >
+        Message
+      </span>
+    </a>
+  </bdi>
 </div>
 `;
 
diff --git a/test/unit-tests/components/views/polls/pollHistory/PollHistory-test.tsx b/test/unit-tests/components/views/polls/pollHistory/PollHistory-test.tsx
index 55dcdda1633ce616cf8a0de3c9bf7076a4706df4..05ad7c3e33af238eea15de605f7b4286645f73fd 100644
--- a/test/unit-tests/components/views/polls/pollHistory/PollHistory-test.tsx
+++ b/test/unit-tests/components/views/polls/pollHistory/PollHistory-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render } from "jest-matrix-react";
-import { Filter, EventTimeline, Room, MatrixEvent, M_POLL_START } from "matrix-js-sdk/src/matrix";
+import { Filter, EventTimeline, Room, type MatrixEvent, M_POLL_START } from "matrix-js-sdk/src/matrix";
 
 import { PollHistory } from "../../../../../../src/components/views/polls/pollHistory/PollHistory";
 import {
diff --git a/test/unit-tests/components/views/polls/pollHistory/PollListItemEnded-test.tsx b/test/unit-tests/components/views/polls/pollHistory/PollListItemEnded-test.tsx
index 492a86f679ab42a0d99a837fb549bb7557b14c34..219c6163270e0c1f91c07803eefcb30022f30a93 100644
--- a/test/unit-tests/components/views/polls/pollHistory/PollListItemEnded-test.tsx
+++ b/test/unit-tests/components/views/polls/pollHistory/PollListItemEnded-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render } from "jest-matrix-react";
-import { MatrixEvent, Poll, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent, type Poll, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
 
 import { PollListItemEnded } from "../../../../../../src/components/views/polls/pollHistory/PollListItemEnded";
 import {
diff --git a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap
index 360eeda061d79f100823612d0b22d5dabb47b782..89c4ff0ba290930c517866db409322dcb9e6e2d0 100644
--- a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap
+++ b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap
@@ -31,7 +31,7 @@ exports[`<PollHistory /> Poll detail navigates back to poll list from detail vie
     xmlns="http://www.w3.org/2000/svg"
   >
     <path
-      d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+      d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
     />
   </svg>
   Active polls
@@ -91,7 +91,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
             tabindex="0"
           >
             <div
-              aria-labelledby=":rc:"
+              aria-labelledby="«ra»"
               class="mx_PollListItem_content"
             >
               <span>
@@ -116,7 +116,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
             tabindex="0"
           >
             <div
-              aria-labelledby=":rh:"
+              aria-labelledby="«rf»"
               class="mx_PollListItem_content"
             >
               <span>
diff --git a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItem-test.tsx.snap b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItem-test.tsx.snap
index 7b65c6e5064c327ffd8654477304652388f78674..3319ae385680cf66a1f13fe4ff4b7fcb86976359 100644
--- a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItem-test.tsx.snap
+++ b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItem-test.tsx.snap
@@ -10,7 +10,7 @@ exports[`<PollListItem /> renders a poll 1`] = `
       tabindex="0"
     >
       <div
-        aria-labelledby=":r0:"
+        aria-labelledby="«r0»"
         class="mx_PollListItem_content"
       >
         <span>
diff --git a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItemEnded-test.tsx.snap b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItemEnded-test.tsx.snap
index 1d8ea3c55ef2fbba0df692ba46b9c67f393df9f8..1ae7a6b9d70be4b2167e708783acd3e4ca051924 100644
--- a/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItemEnded-test.tsx.snap
+++ b/test/unit-tests/components/views/polls/pollHistory/__snapshots__/PollListItemEnded-test.tsx.snap
@@ -10,7 +10,7 @@ exports[`<PollListItemEnded /> renders a poll with no responses 1`] = `
       tabindex="0"
     >
       <div
-        aria-labelledby=":r0:"
+        aria-labelledby="«r0»"
         class="mx_PollListItemEnded_content"
       >
         <div
diff --git a/test/unit-tests/components/views/right_panel/ExtensionsCard-test.tsx b/test/unit-tests/components/views/right_panel/ExtensionsCard-test.tsx
index 2b74276f28a91f9dedbd6979093e32b750f07cf6..e11b4092d74d9d392552ca32c0841e490a067bd8 100644
--- a/test/unit-tests/components/views/right_panel/ExtensionsCard-test.tsx
+++ b/test/unit-tests/components/views/right_panel/ExtensionsCard-test.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 import { render, screen } from "jest-matrix-react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { MatrixWidgetType } from "matrix-widget-api";
 import userEvent from "@testing-library/user-event";
 
 import ExtensionsCard from "../../../../../src/components/views/right_panel/ExtensionsCard";
 import { stubClient } from "../../../../test-utils";
-import { IApp } from "../../../../../src/stores/WidgetStore";
+import { type IApp } from "../../../../../src/stores/WidgetStore";
 import WidgetUtils, { useWidgets } from "../../../../../src/utils/WidgetUtils";
 import { WidgetLayoutStore } from "../../../../../src/stores/widgets/WidgetLayoutStore";
 import { IntegrationManagers } from "../../../../../src/integrations/IntegrationManagers";
diff --git a/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx
index b63b84c7e615eb04be3accb61225222545612fab..7196667d9c60e42139f09eb1bfedfd32b2c3b1fd 100644
--- a/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx
+++ b/test/unit-tests/components/views/right_panel/PinnedMessagesCard-test.tsx
@@ -7,19 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, act, RenderResult, waitForElementToBeRemoved, screen, waitFor } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
+import { render, act, type RenderResult, waitForElementToBeRemoved, screen, waitFor } from "jest-matrix-react";
+import { mocked, type MockedObject } from "jest-mock";
 import {
     MatrixEvent,
     RoomStateEvent,
     Room,
-    IMinimalEvent,
+    type IMinimalEvent,
     EventType,
     RelationType,
     MsgType,
     M_POLL_KIND_DISCLOSED,
     EventTimeline,
-    MatrixClient,
+    type MatrixClient,
 } from "matrix-js-sdk/src/matrix";
 import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
 import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
diff --git a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx b/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx
deleted file mode 100644
index 48ba88d88a6ef7ec859494fe77f3b7071dd521a9..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { render, fireEvent, screen, waitFor } from "jest-matrix-react";
-import { EventType, MatrixEvent, Room, MatrixClient, JoinRule } from "matrix-js-sdk/src/matrix";
-import { KnownMembership } from "matrix-js-sdk/src/types";
-import { mocked, MockedObject } from "jest-mock";
-import userEvent from "@testing-library/user-event";
-
-import DMRoomMap from "../../../../../src/utils/DMRoomMap";
-import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard";
-import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
-import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
-import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
-import * as settingsHooks from "../../../../../src/hooks/useSettings";
-import Modal from "../../../../../src/Modal";
-import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
-import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
-import { flushPromises, stubClient } from "../../../../test-utils";
-import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/PollHistoryDialog";
-import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
-import { _t } from "../../../../../src/languageHandler";
-import { tagRoom } from "../../../../../src/utils/room/tagRoom";
-import { DefaultTagID } from "../../../../../src/stores/room-list/models";
-import { Action } from "../../../../../src/dispatcher/actions";
-import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
-import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
-
-jest.mock("../../../../../src/utils/room/tagRoom");
-
-describe("<RoomSummaryCard />", () => {
-    const userId = "@alice:domain.org";
-
-    const roomId = "!room:domain.org";
-    let mockClient!: MockedObject<MatrixClient>;
-    let room!: Room;
-
-    const getComponent = (props = {}) => {
-        const defaultProps = {
-            room,
-            onClose: jest.fn(),
-            permalinkCreator: new RoomPermalinkCreator(room),
-        };
-
-        return render(<RoomSummaryCard {...defaultProps} {...props} />, {
-            wrapper: ({ children }) => (
-                <MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
-            ),
-        });
-    };
-
-    beforeEach(() => {
-        mockClient = mocked(stubClient());
-        room = new Room(roomId, mockClient, userId);
-        const roomCreateEvent = new MatrixEvent({
-            type: "m.room.create",
-            room_id: roomId,
-            sender: userId,
-            content: {
-                creator: userId,
-                room_version: "5",
-            },
-            state_key: "",
-        });
-        room.currentState.setStateEvents([roomCreateEvent]);
-        room.updateMyMembership(KnownMembership.Join);
-
-        jest.spyOn(Modal, "createDialog");
-        jest.spyOn(RightPanelStore.instance, "pushCard");
-        jest.spyOn(settingsHooks, "useFeatureEnabled").mockReturnValue(false);
-        jest.spyOn(defaultDispatcher, "dispatch");
-        jest.clearAllMocks();
-        DMRoomMap.makeShared(mockClient);
-
-        mockClient.getRoom.mockReturnValue(room);
-        jest.spyOn(room, "isElementVideoRoom").mockRestore();
-        jest.spyOn(room, "isCallRoom").mockRestore();
-    });
-
-    afterEach(() => {
-        jest.restoreAllMocks();
-    });
-
-    it("renders the room summary", () => {
-        const { container } = getComponent();
-        expect(container).toMatchSnapshot();
-    });
-
-    it("renders the room topic in the summary", () => {
-        room.currentState.setStateEvents([
-            new MatrixEvent({
-                type: "m.room.topic",
-                room_id: roomId,
-                sender: userId,
-                content: {
-                    topic: "This is the room's topic.",
-                },
-                state_key: "",
-            }),
-        ]);
-        const { container } = getComponent();
-        expect(container).toMatchSnapshot();
-    });
-
-    it("has button to edit topic", () => {
-        room.currentState.setStateEvents([
-            new MatrixEvent({
-                type: "m.room.topic",
-                room_id: roomId,
-                sender: userId,
-                content: {
-                    topic: "This is the room's topic.",
-                },
-                state_key: "",
-            }),
-        ]);
-        const { container, getByText } = getComponent();
-        expect(getByText("Edit")).toBeInTheDocument();
-        expect(container).toMatchSnapshot();
-    });
-
-    describe("search", () => {
-        it("has the search field", async () => {
-            const onSearchChange = jest.fn();
-            const { getByPlaceholderText } = getComponent({
-                onSearchChange,
-            });
-            expect(getByPlaceholderText("Search messages…")).toBeVisible();
-        });
-
-        it("should focus the search field if Action.FocusMessageSearch is fired", async () => {
-            const onSearchChange = jest.fn();
-            const { getByPlaceholderText } = getComponent({
-                onSearchChange,
-            });
-            expect(getByPlaceholderText("Search messages…")).not.toHaveFocus();
-            defaultDispatcher.fire(Action.FocusMessageSearch);
-            await waitFor(() => {
-                expect(getByPlaceholderText("Search messages…")).toHaveFocus();
-            });
-        });
-
-        it("should focus the search field if focusRoomSearch=true", () => {
-            const onSearchChange = jest.fn();
-            const { getByPlaceholderText } = getComponent({
-                onSearchChange,
-                focusRoomSearch: true,
-            });
-            expect(getByPlaceholderText("Search messages…")).toHaveFocus();
-        });
-
-        it("should cancel search on escape", () => {
-            const onSearchChange = jest.fn();
-            const onSearchCancel = jest.fn();
-            const { getByPlaceholderText } = getComponent({
-                onSearchChange,
-                onSearchCancel,
-                focusRoomSearch: true,
-            });
-            expect(getByPlaceholderText("Search messages…")).toHaveFocus();
-            fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" });
-            expect(onSearchCancel).toHaveBeenCalled();
-        });
-
-        it("should empty search field when the timeline rendering type changes away", async () => {
-            const onSearchChange = jest.fn();
-            const { rerender } = render(
-                <MatrixClientContext.Provider value={mockClient}>
-                    <ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Search } as any)}>
-                        <RoomSummaryCard
-                            room={room}
-                            permalinkCreator={new RoomPermalinkCreator(room)}
-                            onSearchChange={onSearchChange}
-                            focusRoomSearch={true}
-                        />
-                    </ScopedRoomContextProvider>
-                </MatrixClientContext.Provider>,
-            );
-
-            await userEvent.type(screen.getByPlaceholderText("Search messages…"), "test");
-            expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("test");
-
-            rerender(
-                <MatrixClientContext.Provider value={mockClient}>
-                    <ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Room } as any)}>
-                        <RoomSummaryCard
-                            room={room}
-                            permalinkCreator={new RoomPermalinkCreator(room)}
-                            onSearchChange={onSearchChange}
-                        />
-                    </ScopedRoomContextProvider>
-                </MatrixClientContext.Provider>,
-            );
-            expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("");
-        });
-    });
-
-    it("opens room file panel on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText("Files"));
-
-        expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true);
-    });
-
-    it("opens room export dialog on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText(_t("export_chat|title")));
-
-        expect(Modal.createDialog).toHaveBeenCalledWith(ExportDialog, { room });
-    });
-
-    it("opens share room dialog on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText(_t("action|copy_link")));
-
-        expect(Modal.createDialog).toHaveBeenCalledWith(ShareDialog, { target: room });
-    });
-
-    it("opens invite dialog on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText(_t("action|invite")));
-
-        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "view_invite", roomId: room.roomId });
-    });
-
-    it("fires favourite dispatch on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText(_t("room|context_menu|favourite")));
-
-        expect(tagRoom).toHaveBeenCalledWith(room, DefaultTagID.Favourite);
-    });
-
-    it("opens room settings on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText(_t("common|settings")));
-
-        expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "open_room_settings" });
-    });
-
-    it("opens room member list on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText("People"));
-
-        expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true);
-    });
-
-    it("opens room threads list on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText("Threads"));
-
-        expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel }, true);
-    });
-
-    it("opens room pinned messages on button click", () => {
-        const { getByText } = getComponent();
-
-        fireEvent.click(getByText("Pinned messages"));
-
-        expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith(
-            { phase: RightPanelPhases.PinnedMessages },
-            true,
-        );
-    });
-
-    describe("pinning", () => {
-        it("renders pins options", () => {
-            const { getByText } = getComponent();
-
-            expect(getByText("Pinned messages")).toBeInTheDocument();
-        });
-    });
-
-    describe("poll history", () => {
-        it("renders poll history option", () => {
-            const { getByText } = getComponent();
-
-            expect(getByText("Polls")).toBeInTheDocument();
-        });
-
-        it("opens poll history dialog on button click", () => {
-            const permalinkCreator = new RoomPermalinkCreator(room);
-            const { getByText } = getComponent({ permalinkCreator });
-
-            fireEvent.click(getByText("Polls"));
-
-            expect(Modal.createDialog).toHaveBeenCalledWith(PollHistoryDialog, {
-                room,
-                matrixClient: mockClient,
-                permalinkCreator: permalinkCreator,
-            });
-        });
-    });
-
-    describe("video rooms", () => {
-        it("does not render irrelevant options for element video room", () => {
-            jest.spyOn(room, "isElementVideoRoom").mockReturnValue(true);
-            mocked(settingsHooks.useFeatureEnabled).mockImplementation((feature) => feature === "feature_video_rooms");
-            const { queryByText } = getComponent();
-
-            // options not rendered
-            expect(queryByText("Files")).not.toBeInTheDocument();
-            expect(queryByText("Pinned")).not.toBeInTheDocument();
-            expect(queryByText("Export chat")).not.toBeInTheDocument();
-        });
-
-        it("does not render irrelevant options for element call room", () => {
-            jest.spyOn(room, "isCallRoom").mockReturnValue(true);
-            mocked(settingsHooks.useFeatureEnabled).mockImplementation(
-                (feature) => feature === "feature_element_call_video_rooms" || feature === "feature_video_rooms",
-            );
-            const { queryByText } = getComponent();
-
-            // options not rendered
-            expect(queryByText("Files")).not.toBeInTheDocument();
-            expect(queryByText("Pinned")).not.toBeInTheDocument();
-            expect(queryByText("Export chat")).not.toBeInTheDocument();
-        });
-    });
-
-    describe("public room label", () => {
-        beforeEach(() => {
-            jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public);
-        });
-
-        it("does not show public room label for a DM", async () => {
-            mockClient.getAccountData.mockImplementation((eventType) => {
-                if (eventType === EventType.Direct) {
-                    return new MatrixEvent({
-                        type: EventType.Direct,
-                        content: {
-                            "@bob:sesame.st": ["some-room-id"],
-                            // this room is a DM with ernie
-                            "@ernie:sesame.st": ["some-other-room-id", room.roomId],
-                        },
-                    });
-                }
-            });
-            getComponent();
-
-            await flushPromises();
-
-            expect(screen.queryByText("Public room")).not.toBeInTheDocument();
-        });
-
-        it("does not show public room label for non public room", async () => {
-            jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Invite);
-            getComponent();
-
-            await flushPromises();
-
-            expect(screen.queryByText("Public room")).not.toBeInTheDocument();
-        });
-
-        it("shows a public room label for a public room", async () => {
-            jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public);
-            getComponent();
-
-            await flushPromises();
-
-            expect(screen.queryByText("Public room")).toBeInTheDocument();
-        });
-    });
-});
diff --git a/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx b/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0e5c7efffed2ab3f3de4621f426e68a2b53997f0
--- /dev/null
+++ b/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx
@@ -0,0 +1,324 @@
+/*
+Copyright 2024 New Vector Ltd.
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render, fireEvent, screen } from "jest-matrix-react";
+import { Room, type MatrixClient, JoinRule, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import userEvent from "@testing-library/user-event";
+
+import RoomSummaryCardView from "../../../../../src/components/views/right_panel/RoomSummaryCardView";
+import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
+import { flushPromises, stubClient } from "../../../../test-utils";
+import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
+import { _t } from "../../../../../src/languageHandler";
+import {
+    type RoomSummaryCardState,
+    useRoomSummaryCardViewModel,
+} from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel";
+import DMRoomMap from "../../../../../src/utils/DMRoomMap";
+
+// Mock the viewmodel hooks
+jest.mock("../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel", () => ({
+    useRoomSummaryCardViewModel: jest.fn(),
+}));
+
+describe("<RoomSummaryCard />", () => {
+    const userId = "@alice:domain.org";
+
+    const roomId = "!room:domain.org";
+    let mockClient!: MockedObject<MatrixClient>;
+    let room!: Room;
+
+    const getComponent = (props = {}) => {
+        const defaultProps = {
+            room,
+            onClose: jest.fn(),
+            permalinkCreator: new RoomPermalinkCreator(room),
+        };
+
+        return render(<RoomSummaryCardView {...defaultProps} {...props} />, {
+            wrapper: ({ children }) => (
+                <MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
+            ),
+        });
+    };
+
+    // Setup mock view models
+    const vmDefaultValues: RoomSummaryCardState = {
+        isDirectMessage: false,
+        isRoomEncrypted: false,
+        e2eStatus: undefined,
+        isVideoRoom: false,
+        roomJoinRule: JoinRule.Public,
+        alias: "",
+        isFavorite: false,
+        canInviteToState: true,
+        pinCount: 0,
+        searchInputRef: { current: null },
+        onUpdateSearchInput: jest.fn(),
+        onRoomMembersClick: jest.fn(),
+        onRoomThreadsClick: jest.fn(),
+        onRoomFilesClick: jest.fn(),
+        onRoomExtensionsClick: jest.fn(),
+        onRoomPinsClick: jest.fn(),
+        onRoomSettingsClick: jest.fn(),
+        onLeaveRoomClick: jest.fn(),
+        onShareRoomClick: jest.fn(),
+        onRoomExportClick: jest.fn(),
+        onRoomPollHistoryClick: jest.fn(),
+        onReportRoomClick: jest.fn(),
+        onFavoriteToggleClick: jest.fn(),
+        onInviteToRoomClick: jest.fn(),
+    };
+
+    beforeEach(() => {
+        mockClient = mocked(stubClient());
+        room = new Room(roomId, mockClient, userId);
+        mocked(useRoomSummaryCardViewModel).mockReturnValue(vmDefaultValues);
+        DMRoomMap.makeShared(mockClient);
+
+        mockClient.getRoom.mockReturnValue(room);
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("renders the room summary", () => {
+        const { container } = getComponent();
+        expect(container).toMatchSnapshot();
+    });
+
+    it("renders the room topic in the summary", () => {
+        room.currentState.setStateEvents([
+            new MatrixEvent({
+                type: "m.room.topic",
+                room_id: roomId,
+                sender: userId,
+                content: {
+                    topic: "This is the room's topic.",
+                },
+                state_key: "",
+            }),
+        ]);
+        const { container } = getComponent();
+        expect(container).toMatchSnapshot();
+    });
+
+    it("has button to edit topic", () => {
+        room.currentState.setStateEvents([
+            new MatrixEvent({
+                type: "m.room.topic",
+                room_id: roomId,
+                sender: userId,
+                content: {
+                    topic: "This is the room's topic.",
+                },
+                state_key: "",
+            }),
+        ]);
+        const { container, getByText } = getComponent();
+        expect(getByText("Edit")).toBeInTheDocument();
+        expect(container).toMatchSnapshot();
+    });
+
+    describe("search", () => {
+        it("has the search field", async () => {
+            const onSearchChange = jest.fn();
+            const { getByPlaceholderText } = getComponent({
+                onSearchChange,
+            });
+            expect(getByPlaceholderText("Search messages…")).toBeVisible();
+        });
+
+        it("should focus the search field if focusRoomSearch=true", () => {
+            const onSearchChange = jest.fn();
+            const { getByPlaceholderText } = getComponent({
+                onSearchChange,
+                focusRoomSearch: true,
+            });
+            expect(getByPlaceholderText("Search messages…")).toHaveFocus();
+        });
+
+        it("should cancel search on escape", () => {
+            const onSearchChange = jest.fn();
+            const onSearchCancel = jest.fn();
+
+            const { getByPlaceholderText } = getComponent({
+                onSearchChange,
+                onSearchCancel,
+                focusRoomSearch: true,
+            });
+            expect(getByPlaceholderText("Search messages…")).toHaveFocus();
+            fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" });
+            expect(vmDefaultValues.onUpdateSearchInput).toHaveBeenCalled();
+        });
+
+        it("should update the search field value correctly", async () => {
+            const user = userEvent.setup();
+
+            const onSearchChange = jest.fn();
+            const { getByPlaceholderText } = getComponent({
+                onSearchChange,
+            });
+
+            const searchInput = getByPlaceholderText("Search messages…");
+            await user.type(searchInput, "test query");
+
+            expect(onSearchChange).toHaveBeenCalledWith("test query");
+            expect(searchInput).toHaveValue("test query");
+        });
+    });
+
+    it("opens room file panel on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText("Files"));
+
+        expect(vmDefaultValues.onRoomFilesClick).toHaveBeenCalled();
+    });
+
+    it("opens room export dialog on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText(_t("export_chat|title")));
+
+        expect(vmDefaultValues.onRoomExportClick).toHaveBeenCalled();
+    });
+
+    it("opens share room dialog on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText(_t("action|copy_link")));
+
+        expect(vmDefaultValues.onShareRoomClick).toHaveBeenCalled();
+    });
+
+    it("opens invite dialog on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText(_t("action|invite")));
+
+        expect(vmDefaultValues.onInviteToRoomClick).toHaveBeenCalled();
+    });
+
+    it("fires favourite dispatch on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText(_t("room|context_menu|favourite")));
+
+        expect(vmDefaultValues.onFavoriteToggleClick).toHaveBeenCalled();
+    });
+
+    it("opens room settings on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText(_t("common|settings")));
+
+        expect(vmDefaultValues.onRoomSettingsClick).toHaveBeenCalled();
+    });
+
+    it("opens room member list on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText("People"));
+
+        expect(vmDefaultValues.onRoomMembersClick).toHaveBeenCalled();
+    });
+
+    it("opens room threads list on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText("Threads"));
+
+        expect(vmDefaultValues.onRoomThreadsClick).toHaveBeenCalled();
+    });
+
+    it("opens room pinned messages on button click", () => {
+        const { getByText } = getComponent();
+
+        fireEvent.click(getByText("Pinned messages"));
+
+        expect(vmDefaultValues.onRoomPinsClick).toHaveBeenCalled();
+    });
+
+    it("does not render irrelevant options if video room", () => {
+        mocked(useRoomSummaryCardViewModel).mockReturnValue({
+            ...vmDefaultValues,
+            isVideoRoom: true,
+        });
+        const { queryByText } = getComponent();
+
+        // options not rendered
+        expect(queryByText("Files")).not.toBeInTheDocument();
+        expect(queryByText("Pinned")).not.toBeInTheDocument();
+        expect(queryByText("Export chat")).not.toBeInTheDocument();
+    });
+
+    describe("pinning", () => {
+        it("renders pins options", () => {
+            const { getByText } = getComponent();
+
+            expect(getByText("Pinned messages")).toBeInTheDocument();
+        });
+    });
+
+    describe("poll history", () => {
+        it("renders poll history option", () => {
+            const { getByText } = getComponent();
+
+            expect(getByText("Polls")).toBeInTheDocument();
+        });
+
+        it("opens poll history dialog on button click", () => {
+            const permalinkCreator = new RoomPermalinkCreator(room);
+            const { getByText } = getComponent({ permalinkCreator });
+
+            fireEvent.click(getByText("Polls"));
+
+            expect(vmDefaultValues.onRoomPollHistoryClick).toHaveBeenCalled();
+        });
+    });
+
+    describe("public room label", () => {
+        it("does not show public room label for a DM", async () => {
+            mocked(useRoomSummaryCardViewModel).mockReturnValue({
+                ...vmDefaultValues,
+                isDirectMessage: true,
+            });
+
+            getComponent();
+
+            await flushPromises();
+
+            expect(screen.queryByText("Public room")).not.toBeInTheDocument();
+        });
+
+        it("does not show public room label for non public room", async () => {
+            mocked(useRoomSummaryCardViewModel).mockReturnValue({
+                ...vmDefaultValues,
+                isDirectMessage: false,
+                roomJoinRule: JoinRule.Invite,
+            });
+            getComponent();
+
+            await flushPromises();
+
+            expect(screen.queryByText("Public room")).not.toBeInTheDocument();
+        });
+
+        it("shows a public room label for a public room", async () => {
+            getComponent();
+
+            await flushPromises();
+
+            expect(screen.queryByText("Public room")).toBeInTheDocument();
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/right_panel/UserInfo-test.tsx b/test/unit-tests/components/views/right_panel/UserInfo-test.tsx
index db7af71c2421e8e9f81ebfe79f80297ab16242ca..1a58b1e2e0b6d7ee804af9b6e0ef9204af2e069f 100644
--- a/test/unit-tests/components/views/right_panel/UserInfo-test.tsx
+++ b/test/unit-tests/components/views/right_panel/UserInfo-test.tsx
@@ -7,25 +7,30 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, screen, cleanup, act, within, waitForElementToBeRemoved } from "jest-matrix-react";
+import { fireEvent, render, screen, cleanup, act, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { Mocked, mocked } from "jest-mock";
-import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
+import { type Mocked, mocked } from "jest-mock";
+import {
+    type Room,
+    User,
+    type MatrixClient,
+    RoomMember,
+    MatrixEvent,
+    EventType,
+    Device,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { defer } from "matrix-js-sdk/src/utils";
 import { EventEmitter } from "events";
 import {
     UserVerificationStatus,
-    VerificationRequest,
+    type VerificationRequest,
     VerificationPhase as Phase,
     VerificationRequestEvent,
-    CryptoApi,
-    DeviceVerificationStatus,
+    type CryptoApi,
 } from "matrix-js-sdk/src/crypto-api";
 
 import UserInfo, {
     BanToggleButton,
-    DeviceItem,
     disambiguateDevices,
     getPowerLevels,
     isMuted,
@@ -40,9 +45,7 @@ import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPan
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import MultiInviter from "../../../../../src/utils/MultiInviter";
-import * as mockVerification from "../../../../../src/verification";
 import Modal from "../../../../../src/Modal";
-import { E2EStatus } from "../../../../../src/utils/ShieldUtils";
 import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
 import { clearAllModals, flushPromises } from "../../../../test-utils";
 import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
@@ -237,7 +240,10 @@ describe("<UserInfo />", () => {
                 _locale,
                 opts,
             ) {
-                return origDate.call(this, "en-US", opts);
+                return origDate.call(this, "en-US", {
+                    ...opts,
+                    hourCycle: "h12",
+                });
             });
             mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
             mockClient.getExtendedProfileProperty.mockResolvedValue("Europe/London");
@@ -437,20 +443,6 @@ describe("<UserInfo />", () => {
             mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
         });
 
-        it("renders a device list which can be expanded", async () => {
-            renderComponent();
-            await flushPromises();
-
-            // check the button exists with the expected text
-            const devicesButton = screen.getByRole("button", { name: "1 session" });
-
-            // click it
-            await userEvent.click(devicesButton);
-
-            // there should now be a button with the device id which should contain the device name
-            expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
-        });
-
         it("renders <BasicUserInfo />", async () => {
             mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
 
@@ -460,190 +452,9 @@ describe("<UserInfo />", () => {
                 room: mockRoom,
             });
             await flushPromises();
-
-            await expect(screen.findByRole("button", { name: "Verify" })).resolves.toBeInTheDocument();
             expect(container).toMatchSnapshot();
         });
 
-        describe("device dehydration", () => {
-            it("hides a verified dehydrated device (unverified user)", async () => {
-                const device1 = new Device({
-                    deviceId: "d1",
-                    userId: defaultUserId,
-                    displayName: "my device",
-                    algorithms: [],
-                    keys: new Map(),
-                });
-                const device2 = new Device({
-                    deviceId: "d2",
-                    userId: defaultUserId,
-                    displayName: "dehydrated device",
-                    algorithms: [],
-                    keys: new Map(),
-                    dehydrated: true,
-                });
-                const devicesMap = new Map<string, Device>([
-                    [device1.deviceId, device1],
-                    [device2.deviceId, device2],
-                ]);
-                const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
-                mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
-
-                renderComponent({ room: mockRoom });
-                await flushPromises();
-
-                // check the button exists with the expected text (the dehydrated device shouldn't be counted)
-                const devicesButton = screen.getByRole("button", { name: "1 session" });
-
-                // click it
-                await act(() => {
-                    return userEvent.click(devicesButton);
-                });
-
-                // there should now be a button with the non-dehydrated device ID
-                expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
-
-                // but not for the dehydrated device ID
-                expect(screen.queryByRole("button", { name: "dehydrated device" })).not.toBeInTheDocument();
-
-                // there should be a line saying that the user has "Offline device" enabled
-                expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
-            });
-
-            it("hides a verified dehydrated device (verified user)", async () => {
-                const device1 = new Device({
-                    deviceId: "d1",
-                    userId: defaultUserId,
-                    displayName: "my device",
-                    algorithms: [],
-                    keys: new Map(),
-                });
-                const device2 = new Device({
-                    deviceId: "d2",
-                    userId: defaultUserId,
-                    displayName: "dehydrated device",
-                    algorithms: [],
-                    keys: new Map(),
-                    dehydrated: true,
-                });
-                const devicesMap = new Map<string, Device>([
-                    [device1.deviceId, device1],
-                    [device2.deviceId, device2],
-                ]);
-                const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
-                mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
-                mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
-                mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
-                    isVerified: () => true,
-                } as DeviceVerificationStatus);
-
-                renderComponent({ room: mockRoom });
-                await flushPromises();
-
-                // check the button exists with the expected text (the dehydrated device shouldn't be counted)
-                const devicesButton = screen.getByRole("button", { name: "1 verified session" });
-
-                // click it
-                await act(() => {
-                    return userEvent.click(devicesButton);
-                });
-
-                // there should now be a button with the non-dehydrated device ID
-                expect(screen.getByTitle("d1")).toBeInTheDocument();
-
-                // but not for the dehydrated device ID
-                expect(screen.queryByTitle("d2")).not.toBeInTheDocument();
-
-                // there should be a line saying that the user has "Offline device" enabled
-                expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
-            });
-
-            it("shows an unverified dehydrated device", async () => {
-                const device1 = new Device({
-                    deviceId: "d1",
-                    userId: defaultUserId,
-                    displayName: "my device",
-                    algorithms: [],
-                    keys: new Map(),
-                });
-                const device2 = new Device({
-                    deviceId: "d2",
-                    userId: defaultUserId,
-                    displayName: "dehydrated device",
-                    algorithms: [],
-                    keys: new Map(),
-                    dehydrated: true,
-                });
-                const devicesMap = new Map<string, Device>([
-                    [device1.deviceId, device1],
-                    [device2.deviceId, device2],
-                ]);
-                const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
-                mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
-                mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
-
-                renderComponent({ room: mockRoom });
-                await flushPromises();
-
-                // the dehydrated device should be shown as an unverified device, which means
-                // there should now be a button with the device id ...
-                const deviceButton = screen.getByRole("button", { name: "dehydrated device" });
-
-                // ... which should contain the device name
-                expect(within(deviceButton).getByText("dehydrated device")).toBeInTheDocument();
-            });
-
-            it("shows dehydrated devices if there is more than one", async () => {
-                const device1 = new Device({
-                    deviceId: "d1",
-                    userId: defaultUserId,
-                    displayName: "dehydrated device 1",
-                    algorithms: [],
-                    keys: new Map(),
-                    dehydrated: true,
-                });
-                const device2 = new Device({
-                    deviceId: "d2",
-                    userId: defaultUserId,
-                    displayName: "dehydrated device 2",
-                    algorithms: [],
-                    keys: new Map(),
-                    dehydrated: true,
-                });
-                const devicesMap = new Map<string, Device>([
-                    [device1.deviceId, device1],
-                    [device2.deviceId, device2],
-                ]);
-                const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
-                mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
-
-                renderComponent({ room: mockRoom });
-                await flushPromises();
-
-                // check the button exists with the expected text (the dehydrated device shouldn't be counted)
-                const devicesButton = screen.getByRole("button", { name: "2 sessions" });
-
-                // click it
-                await act(() => {
-                    return userEvent.click(devicesButton);
-                });
-
-                // the dehydrated devices should be shown as an unverified device, which means
-                // there should now be a button with the first dehydrated device...
-                const device1Button = screen.getByRole("button", { name: "dehydrated device 1" });
-                expect(device1Button).toBeVisible();
-
-                // ... which should contain the device name
-                expect(within(device1Button).getByText("dehydrated device 1")).toBeInTheDocument();
-                // and a button with the second dehydrated device...
-                const device2Button = screen.getByRole("button", { name: "dehydrated device 2" });
-                expect(device2Button).toBeVisible();
-
-                // ... which should contain the device name
-                expect(within(device2Button).getByText("dehydrated device 2")).toBeInTheDocument();
-            });
-        });
-
         it("should render a deactivate button for users of the same server if we are a server admin", async () => {
             mockClient.isSynapseAdministrator.mockResolvedValue(true);
             mockClient.getDomain.mockReturnValue("example.com");
@@ -655,39 +466,11 @@ describe("<UserInfo />", () => {
 
             await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
             if (screen.queryAllByRole("progressbar").length) {
-                await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
+                await act(() => waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")));
             }
             expect(container).toMatchSnapshot();
         });
     });
-
-    describe("with an encrypted room", () => {
-        beforeEach(() => {
-            jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
-        });
-
-        it("renders unverified user info", async () => {
-            mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
-            renderComponent({ room: mockRoom });
-            await flushPromises();
-
-            const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
-
-            // there should be a "normal" E2E padlock
-            expect(userHeading.getElementsByClassName("mx_E2EIcon_normal")).toHaveLength(1);
-        });
-
-        it("renders verified user info", async () => {
-            mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
-            renderComponent({ room: mockRoom });
-            await flushPromises();
-
-            const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
-
-            // there should be a "verified" E2E padlock
-            expect(userHeading.getElementsByClassName("mx_E2EIcon_verified")).toHaveLength(1);
-        });
-    });
 });
 
 describe("<UserInfoHeader />", () => {
@@ -699,179 +482,51 @@ describe("<UserInfoHeader />", () => {
     };
 
     const renderComponent = (props = {}) => {
+        const device1 = new Device({
+            deviceId: "d1",
+            userId: defaultUserId,
+            displayName: "my device",
+            algorithms: [],
+            keys: new Map(),
+        });
+        const devicesMap = new Map<string, Device>([[device1.deviceId, device1]]);
+        const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
+        mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
+        mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
         const Wrapper = (wrapperProps = {}) => {
             return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
         };
 
-        return render(<UserInfoHeader {...defaultProps} {...props} />, {
+        return render(<UserInfoHeader {...defaultProps} {...props} devices={[device1]} />, {
             wrapper: Wrapper,
         });
     };
 
-    it("does not render an e2e icon in the header if e2eStatus prop is undefined", () => {
-        renderComponent();
-        const header = screen.getByRole("heading", { name: defaultUserId });
-
-        expect(header.getElementsByClassName("mx_E2EIcon")).toHaveLength(0);
-    });
-
-    it("renders an e2e icon in the header if e2eStatus prop is defined", () => {
-        renderComponent({ e2eStatus: E2EStatus.Normal });
-        const header = screen.getByRole("heading");
-
-        expect(header.getElementsByClassName("mx_E2EIcon")).toHaveLength(1);
-    });
-
     it("renders custom user identifiers in the header", () => {
         renderComponent();
-
         expect(screen.getByText("customUserIdentifier")).toBeInTheDocument();
     });
-});
-
-describe("<DeviceItem />", () => {
-    const device = { deviceId: "deviceId", displayName: "deviceName" } as Device;
-    const defaultProps = {
-        userId: defaultUserId,
-        device,
-        isUserVerified: false,
-    };
-
-    const renderComponent = (props = {}) => {
-        const Wrapper = (wrapperProps = {}) => {
-            return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
-        };
-
-        return render(<DeviceItem {...defaultProps} {...props} />, {
-            wrapper: Wrapper,
-        });
-    };
-
-    const setMockDeviceTrust = (isVerified = false, isCrossSigningVerified = false) => {
-        mockCrypto.getDeviceVerificationStatus.mockResolvedValue({
-            isVerified: () => isVerified,
-            crossSigningVerified: isCrossSigningVerified,
-        } as DeviceVerificationStatus);
-    };
-
-    const mockVerifyDevice = jest.spyOn(mockVerification, "verifyDevice");
-
-    beforeEach(() => {
-        setMockDeviceTrust();
-    });
-
-    afterEach(() => {
-        mockCrypto.getDeviceVerificationStatus.mockReset();
-        mockVerifyDevice.mockClear();
-    });
 
-    afterAll(() => {
-        mockVerifyDevice.mockRestore();
-    });
-
-    it("with unverified user and device, displays button without a label", async () => {
-        renderComponent();
-        await flushPromises();
-
-        expect(screen.getByRole("button", { name: device.displayName! })).toBeInTheDocument();
-        expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
-    });
-
-    it("with verified user only, displays button with a 'Not trusted' label", async () => {
-        renderComponent({ isUserVerified: true });
-        await flushPromises();
-
-        const button = screen.getByRole("button", { name: device.displayName });
-        expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
-    });
-
-    it("with verified device only, displays no button without a label", async () => {
-        setMockDeviceTrust(true);
-        renderComponent();
-        await flushPromises();
-
-        expect(screen.getByText(device.displayName!)).toBeInTheDocument();
-        expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
-    });
-
-    it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", async () => {
-        const deferred = defer<DeviceVerificationStatus>();
-        mockCrypto.getDeviceVerificationStatus.mockReturnValue(deferred.promise);
-
-        mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
-        mockClient.getUserId.mockReturnValueOnce(defaultUserId);
-        renderComponent();
-        await flushPromises();
-
-        // set trust to be false for isVerified, true for isCrossSigningVerified
-        deferred.resolve({
-            isVerified: () => false,
-            crossSigningVerified: true,
-        } as DeviceVerificationStatus);
-
-        await expect(screen.findByText(device.displayName!)).resolves.toBeInTheDocument();
-        // expect to see no button in this case
-        expect(screen.queryByRole("button")).not.toBeInTheDocument();
-    });
-
-    it("with verified user and device, displays no button and a 'Trusted' label", async () => {
-        setMockDeviceTrust(true);
-        renderComponent({ isUserVerified: true });
-        await flushPromises();
-
-        expect(screen.queryByRole("button")).not.toBeInTheDocument();
-        expect(screen.getByText(device.displayName!)).toBeInTheDocument();
-        expect(screen.getByText("Trusted")).toBeInTheDocument();
-    });
-
-    it("does not call verifyDevice if client.getUser returns null", async () => {
-        mockClient.getUser.mockReturnValueOnce(null);
-        renderComponent();
-        await flushPromises();
-
-        const button = screen.getByRole("button", { name: device.displayName! });
-        expect(button).toBeInTheDocument();
-        await userEvent.click(button);
-
-        expect(mockVerifyDevice).not.toHaveBeenCalled();
-    });
-
-    it("calls verifyDevice if client.getUser returns an object", async () => {
-        mockClient.getUser.mockReturnValueOnce(defaultUser);
-        // set mock return of isGuest to short circuit verifyDevice call to avoid
-        // even more mocking
-        mockClient.isGuest.mockReturnValueOnce(true);
-        renderComponent();
-        await flushPromises();
-
-        const button = screen.getByRole("button", { name: device.displayName! });
-        expect(button).toBeInTheDocument();
-        await userEvent.click(button);
-
-        expect(mockVerifyDevice).toHaveBeenCalledTimes(1);
-        expect(mockVerifyDevice).toHaveBeenCalledWith(mockClient, defaultUser, device);
-    });
-
-    it("with display name", async () => {
+    it("renders verified badge when user is verified", async () => {
+        mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, false));
         const { container } = renderComponent();
-        await flushPromises();
-
+        await waitFor(() => expect(screen.getByText("Verified")).toBeInTheDocument());
         expect(container).toMatchSnapshot();
     });
 
-    it("without display name", async () => {
-        const device = { deviceId: "deviceId" } as Device;
-        const { container } = renderComponent({ device, userId: defaultUserId });
-        await flushPromises();
-
+    it("renders verify button", async () => {
+        mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
+        mockCrypto.userHasCrossSigningKeys.mockResolvedValue(true);
+        const { container } = renderComponent();
+        await waitFor(() => expect(screen.getByText("Verify User")).toBeInTheDocument());
         expect(container).toMatchSnapshot();
     });
 
-    it("ambiguous display name", async () => {
-        const device = { deviceId: "deviceId", ambiguous: true, displayName: "my display name" };
-        const { container } = renderComponent({ device, userId: defaultUserId });
-        await flushPromises();
-
+    it("renders verification unavailable message", async () => {
+        mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
+        mockCrypto.userHasCrossSigningKeys.mockResolvedValue(false);
+        const { container } = renderComponent();
+        await waitFor(() => expect(screen.getByText("(User verification unavailable)")).toBeInTheDocument());
         expect(container).toMatchSnapshot();
     });
 });
@@ -1058,7 +713,7 @@ describe("<UserOptionsSection />", () => {
     ])(
         "clicking »message« %s should start a DM",
         async (test: string, member: RoomMember | User, expectedAvatarUrl: string | undefined) => {
-            const deferred = defer<string>();
+            const deferred = Promise.withResolvers<string>();
             mocked(startDmOnFirstMessage).mockReturnValue(deferred.promise);
 
             renderComponent({ member });
diff --git a/test/unit-tests/components/views/right_panel/VerificationPanel-test.tsx b/test/unit-tests/components/views/right_panel/VerificationPanel-test.tsx
index 4d1252ac3ad1a70ea703ae8c14709ca66215340f..0fbe248d4440c5e973ba932fd582ec273b9ab693 100644
--- a/test/unit-tests/components/views/right_panel/VerificationPanel-test.tsx
+++ b/test/unit-tests/components/views/right_panel/VerificationPanel-test.tsx
@@ -7,18 +7,18 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { act, render, waitFor } from "jest-matrix-react";
-import React, { ComponentProps } from "react";
-import { User, TypedEventEmitter, Device, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { mocked, Mocked } from "jest-mock";
+import React, { type ComponentProps } from "react";
+import { User, TypedEventEmitter, Device, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { mocked, type Mocked } from "jest-mock";
 import {
-    EmojiMapping,
-    ShowSasCallbacks,
+    type EmojiMapping,
+    type ShowSasCallbacks,
     VerificationPhase as Phase,
-    VerificationRequest,
-    VerificationRequestEvent,
-    Verifier,
+    type VerificationRequest,
+    type VerificationRequestEvent,
+    type Verifier,
     VerifierEvent,
-    VerifierEventHandlerMap,
+    type VerifierEventHandlerMap,
 } from "matrix-js-sdk/src/crypto-api";
 
 import VerificationPanel from "../../../../../src/components/views/right_panel/VerificationPanel";
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/BaseCard-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/BaseCard-test.tsx.snap
index 21ec64e9b35fda8e61605342e9a302992b821283..ef91df63aff8ee454174044d7c6db194167b63bc 100644
--- a/test/unit-tests/components/views/right_panel/__snapshots__/BaseCard-test.tsx.snap
+++ b/test/unit-tests/components/views/right_panel/__snapshots__/BaseCard-test.tsx.snap
@@ -12,22 +12,22 @@ exports[`<BaseCard /> should close when clicking X button 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Heading text
         </p>
       </div>
       <button
-        aria-labelledby=":r0:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r0»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -38,7 +38,7 @@ exports[`<BaseCard /> should close when clicking X button 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/ExtensionsCard-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/ExtensionsCard-test.tsx.snap
index 907087852551bb3fda8a426c0b78200df2af8722..b3a84240f0bdf4214ad79b7fdb9b0b414cf897e0 100644
--- a/test/unit-tests/components/views/right_panel/__snapshots__/ExtensionsCard-test.tsx.snap
+++ b/test/unit-tests/components/views/right_panel/__snapshots__/ExtensionsCard-test.tsx.snap
@@ -12,22 +12,22 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Extensions
         </p>
       </div>
       <button
-        aria-labelledby=":r0:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r0»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -38,7 +38,7 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -49,7 +49,7 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
       tabindex="-1"
     >
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -64,14 +64,14 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M11 13H6a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h5V6c0-.283.096-.52.287-.713A.968.968 0 0 1 12 5c.283 0 .52.096.713.287.191.192.287.43.287.713v5h5a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13h-5v5a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 19a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 18v-5Z"
+            d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
           />
         </svg>
         Add extensions
       </button>
       <div
         class="mx_Flex mx_EmptyState"
-        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <svg
           fill="currentColor"
@@ -81,16 +81,16 @@ exports[`<ExtensionsCard /> should render empty state 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M17.25 11.672a.907.907 0 0 1-.663-.282L12.61 7.413a.907.907 0 0 1-.282-.663c0-.254.094-.475.282-.663l3.977-3.977a.907.907 0 0 1 .663-.282c.254 0 .475.094.663.282l3.977 3.977a.907.907 0 0 1 .282.663.907.907 0 0 1-.282.663l-3.977 3.977a.907.907 0 0 1-.663.282Zm2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475 2.475-2.475ZM4 11a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6a.97.97 0 0 1 .713.288A.968.968 0 0 1 11 4v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4Zm5-2V5H5v4h4Zm5 12a.968.968 0 0 1-.713-.288A.968.968 0 0 1 13 20v-6c0-.283.096-.52.287-.713A.968.968 0 0 1 14 13h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v6c0 .283-.096.52-.288.712A.968.968 0 0 1 20 21h-6Zm5-2v-4h-4v4h4ZM4 21a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20v-6a.97.97 0 0 1 .288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6a.97.97 0 0 1-.287.712A.968.968 0 0 1 10 21H4Zm5-2v-4H5v4h4Z"
+            d="M17.25 11.672a.9.9 0 0 1-.663-.282L12.61 7.413a.9.9 0 0 1-.282-.663q0-.381.282-.663l3.977-3.977a.9.9 0 0 1 .663-.282q.381 0 .663.282l3.977 3.977a.9.9 0 0 1 .282.663.9.9 0 0 1-.282.663l-3.977 3.977a.9.9 0 0 1-.663.282m2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475zM4 11a.97.97 0 0 1-.712-.287A.97.97 0 0 1 3 10V4q0-.424.288-.712A.97.97 0 0 1 4 3h6q.424 0 .713.288Q11 3.575 11 4v6q0 .424-.287.713A.97.97 0 0 1 10 11zm5-2V5H5v4zm5 12a.97.97 0 0 1-.713-.288A.97.97 0 0 1 13 20v-6q0-.424.287-.713A.97.97 0 0 1 14 13h6q.424 0 .712.287.288.288.288.713v6q0 .424-.288.712A.97.97 0 0 1 20 21zm5-2v-4h-4v4zM4 21a.97.97 0 0 1-.712-.288A.97.97 0 0 1 3 20v-6q0-.424.288-.713A.97.97 0 0 1 4 13h6q.424 0 .713.287.287.288.287.713v6q0 .424-.287.712A.97.97 0 0 1 10 21zm5-2v-4H5v4z"
           />
         </svg>
         <p
-          class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+          class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
         >
           Boost productivity with more tools, widgets and bots
         </p>
         <p
-          class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+          class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
         >
           Select “Add extensions” to browse and add extensions to this room
         </p>
@@ -112,22 +112,22 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Extensions
         </p>
       </div>
       <button
-        aria-labelledby=":r6:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r6»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -138,7 +138,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -149,7 +149,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
       tabindex="-1"
     >
       <button
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -164,13 +164,13 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M11 13H6a.967.967 0 0 1-.713-.287A.968.968 0 0 1 5 12c0-.283.096-.52.287-.713A.967.967 0 0 1 6 11h5V6c0-.283.096-.52.287-.713A.968.968 0 0 1 12 5c.283 0 .52.096.713.287.191.192.287.43.287.713v5h5a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 18 13h-5v5a.97.97 0 0 1-.287.712A.968.968 0 0 1 12 19a.968.968 0 0 1-.713-.288A.968.968 0 0 1 11 18v-5Z"
+            d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
           />
         </svg>
         Add extensions
       </button>
       <div
-        class="_separator_144s5_17"
+        class="_separator_7ckbw_8"
         data-kind="primary"
         data-orientation="horizontal"
         role="separator"
@@ -185,7 +185,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
         >
           <span
             aria-label="Avatar"
-            class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar mx_WidgetAvatar"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -193,7 +193,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
           >
             <img
               alt=""
-              class="_image_mcap2_50"
+              class="_image_1qbcf_41"
               data-type="round"
               height="24px"
               loading="lazy"
@@ -203,7 +203,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
             />
           </span>
           <p
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_lineClamp"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_lineClamp"
           >
             Custom Widget
           </p>
@@ -225,7 +225,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
         >
           <span
             aria-label="Avatar"
-            class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar mx_WidgetAvatar"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -233,7 +233,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
           >
             <img
               alt=""
-              class="_image_mcap2_50"
+              class="_image_1qbcf_41"
               data-type="round"
               height="24px"
               loading="lazy"
@@ -243,7 +243,7 @@ exports[`<ExtensionsCard /> should render widgets 1`] = `
             />
           </span>
           <p
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_lineClamp"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_lineClamp"
           >
             Jitsi
           </p>
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap
index 4a4ac6d6d63ac903d2d5638a4404b26de87ba1fe..e0052d818c6123d76b8068bda877e1c156801a4a 100644
--- a/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap
+++ b/test/unit-tests/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap
@@ -12,22 +12,22 @@ exports[`<PinnedMessagesCard /> should show the empty state when there are no pi
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Pinned messages
         </p>
       </div>
       <button
-        aria-labelledby=":re:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«re»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -38,7 +38,7 @@ exports[`<PinnedMessagesCard /> should show the empty state when there are no pi
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -50,7 +50,7 @@ exports[`<PinnedMessagesCard /> should show the empty state when there are no pi
     >
       <div
         class="mx_Flex mx_EmptyState"
-        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <svg
           fill="currentColor"
@@ -61,17 +61,17 @@ exports[`<PinnedMessagesCard /> should show the empty state when there are no pi
         >
           <path
             clip-rule="evenodd"
-            d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857H6.119ZM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744V4Z"
+            d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857zM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744z"
             fill-rule="evenodd"
           />
         </svg>
         <p
-          class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+          class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
         >
           Pin important messages so that they can be easily discovered
         </p>
         <p
-          class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+          class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
         >
           Select a message and choose “Pin” to it include here.
         </p>
@@ -93,22 +93,22 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           2 Pinned messages
         </p>
       </div>
       <button
-        aria-labelledby=":rk:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«rk»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -119,7 +119,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -139,7 +139,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
         >
           <div>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -157,7 +157,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
               class="mx_PinnedEventTile_top"
             >
               <span
-                aria-labelledby=":rq:"
+                aria-labelledby="«rq»"
                 class="mx_PinnedEventTile_sender mx_Username_color3"
               >
                 @alice:example.org
@@ -167,16 +167,16 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
                 aria-expanded="false"
                 aria-haspopup="menu"
                 aria-label="Open menu"
-                class="_icon-button_bh2qc_17"
+                class="_icon-button_m2erp_8"
                 data-state="closed"
-                id="radix-:rv:"
+                id="radix-«rv»"
                 role="button"
                 style="--cpd-icon-button-size: 24px;"
                 tabindex="0"
                 type="button"
               >
                 <div
-                  class="_indicator-icon_133tf_26"
+                  class="_indicator-icon_zr2a0_17"
                   style="--cpd-icon-button-size: 100%;"
                 >
                   <svg
@@ -187,7 +187,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
                     xmlns="http://www.w3.org/2000/svg"
                   >
                     <path
-                      d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                      d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                     />
                   </svg>
                 </div>
@@ -206,7 +206,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
           </div>
         </div>
         <div
-          class="_separator_144s5_17 mx_PinnedMessagesCard_Separator"
+          class="_separator_7ckbw_8 mx_PinnedMessagesCard_Separator"
           data-kind="primary"
           data-orientation="horizontal"
           role="separator"
@@ -217,7 +217,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
         >
           <div>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -235,7 +235,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
               class="mx_PinnedEventTile_top"
             >
               <span
-                aria-labelledby=":r11:"
+                aria-labelledby="«r11»"
                 class="mx_PinnedEventTile_sender mx_Username_color3"
               >
                 @alice:example.org
@@ -245,16 +245,16 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
                 aria-expanded="false"
                 aria-haspopup="menu"
                 aria-label="Open menu"
-                class="_icon-button_bh2qc_17"
+                class="_icon-button_m2erp_8"
                 data-state="closed"
-                id="radix-:r16:"
+                id="radix-«r16»"
                 role="button"
                 style="--cpd-icon-button-size: 24px;"
                 tabindex="0"
                 type="button"
               >
                 <div
-                  class="_indicator-icon_133tf_26"
+                  class="_indicator-icon_zr2a0_17"
                   style="--cpd-icon-button-size: 100%;"
                 >
                   <svg
@@ -265,7 +265,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
                     xmlns="http://www.w3.org/2000/svg"
                   >
                     <path
-                      d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                      d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                     />
                   </svg>
                 </div>
@@ -288,7 +288,7 @@ exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
         class="mx_PinnedMessagesCard_unpin"
       >
         <button
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="tertiary"
           data-size="lg"
           role="button"
@@ -314,22 +314,22 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           2 Pinned messages
         </p>
       </div>
       <button
-        aria-labelledby=":rt2:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«rt2»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -340,7 +340,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -360,7 +360,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
         >
           <div>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -378,7 +378,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
               class="mx_PinnedEventTile_top"
             >
               <span
-                aria-labelledby=":rt8:"
+                aria-labelledby="«rt8»"
                 class="mx_PinnedEventTile_sender mx_Username_color3"
               >
                 @alice:example.org
@@ -388,16 +388,16 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
                 aria-expanded="false"
                 aria-haspopup="menu"
                 aria-label="Open menu"
-                class="_icon-button_bh2qc_17"
+                class="_icon-button_m2erp_8"
                 data-state="closed"
-                id="radix-:rtd:"
+                id="radix-«rtd»"
                 role="button"
                 style="--cpd-icon-button-size: 24px;"
                 tabindex="0"
                 type="button"
               >
                 <div
-                  class="_indicator-icon_133tf_26"
+                  class="_indicator-icon_zr2a0_17"
                   style="--cpd-icon-button-size: 100%;"
                 >
                   <svg
@@ -408,7 +408,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
                     xmlns="http://www.w3.org/2000/svg"
                   >
                     <path
-                      d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                      d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                     />
                   </svg>
                 </div>
@@ -427,7 +427,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
           </div>
         </div>
         <div
-          class="_separator_144s5_17 mx_PinnedMessagesCard_Separator"
+          class="_separator_7ckbw_8 mx_PinnedMessagesCard_Separator"
           data-kind="primary"
           data-orientation="horizontal"
           role="separator"
@@ -438,7 +438,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
         >
           <div>
             <span
-              class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -456,7 +456,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
               class="mx_PinnedEventTile_top"
             >
               <span
-                aria-labelledby=":rtf:"
+                aria-labelledby="«rtf»"
                 class="mx_PinnedEventTile_sender mx_Username_color3"
               >
                 @alice:example.org
@@ -466,16 +466,16 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
                 aria-expanded="false"
                 aria-haspopup="menu"
                 aria-label="Open menu"
-                class="_icon-button_bh2qc_17"
+                class="_icon-button_m2erp_8"
                 data-state="closed"
-                id="radix-:rtk:"
+                id="radix-«rtk»"
                 role="button"
                 style="--cpd-icon-button-size: 24px;"
                 tabindex="0"
                 type="button"
               >
                 <div
-                  class="_indicator-icon_133tf_26"
+                  class="_indicator-icon_zr2a0_17"
                   style="--cpd-icon-button-size: 100%;"
                 >
                   <svg
@@ -486,7 +486,7 @@ exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
                     xmlns="http://www.w3.org/2000/svg"
                   >
                     <path
-                      d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                      d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                     />
                   </svg>
                 </div>
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap
deleted file mode 100644
index a4496312f328d89e7bba00550602bd0c8448279e..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap
+++ /dev/null
@@ -1,1929 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
-<div>
-  <div
-    aria-labelledby="room-summary-panel-tab"
-    class="mx_BaseCard mx_RoomSummaryCard"
-    id="room-summary-panel"
-    role="tabpanel"
-  >
-    <div
-      class="mx_BaseCard_header"
-    >
-      <div
-        class="mx_BaseCard_header_spacer"
-      />
-      <button
-        aria-labelledby=":rm:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
-        data-testid="base-card-close-button"
-        role="button"
-        style="--cpd-icon-button-size: 28px;"
-        tabindex="0"
-      >
-        <div
-          class="_indicator-icon_133tf_26"
-          style="--cpd-icon-button-size: 100%;"
-        >
-          <svg
-            fill="currentColor"
-            height="1em"
-            viewBox="0 0 24 24"
-            width="1em"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
-            />
-          </svg>
-        </div>
-      </button>
-    </div>
-    <div
-      class="mx_AutoHideScrollbar"
-      tabindex="-1"
-    >
-      <header
-        class="mx_RoomSummaryCard_container"
-      >
-        <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-          data-color="1"
-          data-testid="avatar-img"
-          data-type="round"
-          role="presentation"
-          style="--cpd-avatar-size: 80px;"
-        >
-          !
-        </span>
-        <h1
-          class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121 mx_RoomSummaryCard_roomName text-primary"
-          title="!room:domain.org"
-        >
-          !room:domain.org
-        </h1>
-        <div
-          class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_RoomSummaryCard_alias text-secondary"
-          title=""
-        />
-        <section
-          class="mx_Flex mx_RoomSummaryCard_badges"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <span
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50 _badge_1171v_17"
-            data-kind="grey"
-          >
-            <svg
-              fill="currentColor"
-              height="1em"
-              viewBox="0 0 24 24"
-              width="1em"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M6 22c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 4 20V10c0-.55.196-1.02.587-1.412.212-.212.446-.366.703-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 0 1-1.414 1.414l-.896-.896A1.935 1.935 0 0 1 18 22H6Zm14-4.834V10c0-.55-.196-1.02-.587-1.412A1.926 1.926 0 0 0 18 8h-1V6c0-1.383-.488-2.563-1.463-3.538C14.563 1.487 13.383 1 12 1s-2.563.488-3.538 1.462A4.876 4.876 0 0 0 7.243 4.41L9 6.166V6c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 12 3c.833 0 1.542.292 2.125.875S15 5.167 15 6v2h-4.166L20 17.166Z"
-              />
-            </svg>
-            Not encrypted
-          </span>
-        </section>
-        <section
-          class="mx_Flex mx_RoomSummaryCard_topic"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <div
-            class="mx_Box mx_RoomSummaryCard_topic_container mx_Box--flex"
-            style="--mx-box-flex: 1;"
-          >
-            <p
-              class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-            >
-              <span
-                dir="auto"
-              >
-                This is the room's topic.
-              </span>
-            </p>
-            <button
-              class="_icon-button_bh2qc_17 mx_RoomSummaryCard_topic_chevron"
-              role="button"
-              style="--cpd-icon-button-size: 24px;"
-              tabindex="0"
-            >
-              <div
-                class="_indicator-icon_133tf_26"
-                style="--cpd-icon-button-size: 100%;"
-              >
-                <svg
-                  fill="currentColor"
-                  height="1em"
-                  viewBox="0 0 24 24"
-                  width="1em"
-                  xmlns="http://www.w3.org/2000/svg"
-                >
-                  <path
-                    d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
-                  />
-                </svg>
-              </div>
-            </button>
-          </div>
-          <div
-            class="mx_Box mx_RoomSummaryCard_topic_edit mx_Box--flex"
-            style="--mx-box-flex: 1;"
-          >
-            <a
-              class="_link_ue21z_17"
-              data-kind="primary"
-              data-size="medium"
-              rel="noreferrer noopener"
-            >
-              <p
-                class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-              >
-                Edit
-              </p>
-            </a>
-          </div>
-        </section>
-      </header>
-      <div
-        class="_separator_144s5_17"
-        data-kind="primary"
-        data-orientation="horizontal"
-        role="separator"
-      />
-      <div
-        aria-orientation="vertical"
-        role="menubar"
-      >
-        <div
-          aria-checked="false"
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitemcheckbox"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004-4.26-.619ZM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Favourite
-          </span>
-          <div
-            class="_container_qnvru_18"
-          >
-            <input
-              aria-hidden="true"
-              class="_input_qnvru_32"
-              id=":rr:"
-              type="checkbox"
-            />
-            <div
-              class="_ui_qnvru_42"
-            />
-          </div>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M10 12c-1.1 0-2.042-.392-2.825-1.175C6.392 10.042 6 9.1 6 8s.392-2.042 1.175-2.825C7.958 4.392 8.9 4 10 4s2.042.392 2.825 1.175C13.608 5.958 14 6.9 14 8s-.392 2.042-1.175 2.825C12.042 11.608 11.1 12 10 12Zm-8 6v-.8c0-.567.146-1.087.438-1.563A2.911 2.911 0 0 1 3.6 14.55a14.843 14.843 0 0 1 3.15-1.163A13.76 13.76 0 0 1 10 13c1.1 0 2.183.13 3.25.387 1.067.259 2.117.646 3.15 1.163.483.25.87.612 1.163 1.087.291.476.437.996.437 1.563v.8c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 16 20H4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 2 18Zm2 0h12v-.8a.973.973 0 0 0-.5-.85c-.9-.45-1.808-.787-2.725-1.012a11.6 11.6 0 0 0-5.55 0c-.917.225-1.825.562-2.725 1.012a.973.973 0 0 0-.5.85v.8Zm6-8c.55 0 1.02-.196 1.412-.588C11.804 9.021 12 8.55 12 8c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 10 6c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 8 8c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm7 1h2v2c0 .283.096.52.288.713.191.191.429.287.712.287s.52-.096.712-.287A.968.968 0 0 0 21 13v-2h2a.97.97 0 0 0 .712-.287A.968.968 0 0 0 24 10a.967.967 0 0 0-.288-.713A.968.968 0 0 0 23 9h-2V7a.967.967 0 0 0-.288-.713A.968.968 0 0 0 20 6a.968.968 0 0 0-.712.287A.967.967 0 0 0 19 7v2h-2a.968.968 0 0 0-.712.287A.967.967 0 0 0 16 10c0 .283.096.52.288.713A.968.968 0 0 0 17 11Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Invite
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M9.175 13.825C9.958 14.608 10.9 15 12 15s2.042-.392 2.825-1.175C15.608 13.042 16 12.1 16 11s-.392-2.042-1.175-2.825C14.042 7.392 13.1 7 12 7s-2.042.392-2.825 1.175C8.392 8.958 8 9.9 8 11s.392 2.042 1.175 2.825Zm4.237-1.412A1.926 1.926 0 0 1 12 13c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 11c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 9c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412Z"
-            />
-            <path
-              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
-            />
-            <path
-              d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            People
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M7 10a.968.968 0 0 1-.713-.287A.968.968 0 0 1 6 9c0-.283.096-.52.287-.713A.968.968 0 0 1 7 8h10a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 17 10H7Zm0 4a.967.967 0 0 1-.713-.287A.968.968 0 0 1 6 13c0-.283.096-.52.287-.713A.967.967 0 0 1 7 12h6c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 13 14H7Z"
-            />
-            <path
-              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293ZM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Threads
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          aria-expanded="false"
-          aria-haspopup="dialog"
-        >
-          <button
-            class="_item_8j2l6_17 _interactive_8j2l6_35"
-            data-kind="primary"
-            role="menuitem"
-          >
-            <svg
-              aria-hidden="true"
-              class="_icon_8j2l6_43"
-              fill="currentColor"
-              height="24"
-              viewBox="0 0 24 24"
-              width="24"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                clip-rule="evenodd"
-                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857H6.119ZM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744V4Z"
-                fill-rule="evenodd"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-            >
-              Pinned messages
-            </span>
-            <svg
-              aria-hidden="true"
-              class="_nav-hint_8j2l6_59"
-              fill="currentColor"
-              height="24"
-              viewBox="8 0 8 24"
-              width="8"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-            >
-              0
-            </span>
-          </button>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V4c0-.55.196-1.02.588-1.413A1.926 1.926 0 0 1 6 2h7.175a1.975 1.975 0 0 1 1.4.575l4.85 4.85a1.975 1.975 0 0 1 .575 1.4V20c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm7-14V4H6v16h12V9h-4a.968.968 0 0 1-.713-.287A.967.967 0 0 1 13 8Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Files
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M17.25 11.672a.907.907 0 0 1-.663-.282L12.61 7.413a.907.907 0 0 1-.282-.663c0-.254.094-.475.282-.663l3.977-3.977a.907.907 0 0 1 .663-.282c.254 0 .475.094.663.282l3.977 3.977a.907.907 0 0 1 .282.663.907.907 0 0 1-.282.663l-3.977 3.977a.907.907 0 0 1-.663.282Zm2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475 2.475-2.475ZM4 11a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6a.97.97 0 0 1 .713.288A.968.968 0 0 1 11 4v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4Zm5-2V5H5v4h4Zm5 12a.968.968 0 0 1-.713-.288A.968.968 0 0 1 13 20v-6c0-.283.096-.52.287-.713A.968.968 0 0 1 14 13h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v6c0 .283-.096.52-.288.712A.968.968 0 0 1 20 21h-6Zm5-2v-4h-4v4h4ZM4 21a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20v-6a.97.97 0 0 1 .288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6a.97.97 0 0 1-.287.712A.968.968 0 0 1 10 21H4Zm5-2v-4H5v4h4Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Extensions
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Copy link
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 17 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 16 8h-3a.968.968 0 0 0-.713.287A.967.967 0 0 0 12 9c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm0 6a.97.97 0 0 0 .712-.287A.968.968 0 0 0 17 15a.968.968 0 0 0-.288-.713A.968.968 0 0 0 16 14h-3a.968.968 0 0 0-.713.287A.968.968 0 0 0 12 15c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm-7-5c.55 0 1.02-.196 1.412-.588C10.804 10.021 11 9.55 11 9c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 7c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 9c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm0 6c.55 0 1.02-.196 1.412-.587.392-.392.588-.863.588-1.413s-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 13c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 15c0 .55.196 1.02.588 1.413.391.391.862.587 1.412.587Zm-4 4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V5c0-.55.196-1.02.587-1.413A1.926 1.926 0 0 1 5 3h14c.55 0 1.02.196 1.413.587.39.393.587.863.587 1.413v14c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm0-2h14V5H5v14Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Polls
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M5 21c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V6.5c0-.25.042-.475.125-.675.083-.2.192-.392.325-.575l1.4-1.7c.133-.183.3-.32.5-.412C5.55 3.046 5.767 3 6 3h12c.233 0 .45.046.65.138.2.091.367.229.5.412l1.4 1.7c.133.183.242.375.325.575.083.2.125.425.125.675V19c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm.4-15h13.2l-.85-1H6.25L5.4 6ZM5 19h14V8H5v11Zm7-1.425c.133 0 .258-.02.375-.063a.877.877 0 0 0 .325-.212l2.6-2.6a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275l-.9.9V11a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 10a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 11v3.2l-.9-.9a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7l2.6 2.6c.1.1.208.17.325.212.117.042.242.063.375.063Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Export Chat
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Zm-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"
-            />
-            <path
-              d="M11.312 2h1.376A2.312 2.312 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.312 2.312 0 0 1 3.27 0l.973.974a2.312 2.312 0 0 1 0 3.269l-.177.177v.003a.134.134 0 0 0 .01.07.153.153 0 0 0 .047.063l.003.002h.247A2.312 2.312 0 0 1 22 11.312v1.376A2.312 2.312 0 0 1 19.688 15h-.247l-.003.002a.152.152 0 0 0-.047.064.134.134 0 0 0-.01.07v.002l.177.177a2.312 2.312 0 0 1 0 3.27l-.974.973a2.312 2.312 0 0 1-3.269 0l-.177-.177h-.003a.134.134 0 0 0-.07.01.152.152 0 0 0-.063.047l-.002.003v.247A2.312 2.312 0 0 1 12.688 22h-1.376A2.312 2.312 0 0 1 9 19.688v-.247l-.002-.003a.153.153 0 0 0-.064-.047.134.134 0 0 0-.07-.01h-.002l-.177.177a2.312 2.312 0 0 1-3.27 0l-.973-.974a2.312 2.312 0 0 1 0-3.269l.177-.177v-.003a.135.135 0 0 0-.01-.07.152.152 0 0 0-.047-.063L4.559 15h-.247A2.312 2.312 0 0 1 2 12.688v-1.376A2.312 2.312 0 0 1 4.312 9h.247l.003-.002a.153.153 0 0 0 .047-.064.135.135 0 0 0 .01-.07v-.002l-.177-.177a2.312 2.312 0 0 1 0-3.27l.974-.973a2.312 2.312 0 0 1 3.269 0l.177.177h.003a.135.135 0 0 0 .07-.01.153.153 0 0 0 .063-.047L9 4.559v-.247A2.312 2.312 0 0 1 11.312 2ZM11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.312.312 0 0 0-.441 0l-.974.974a.312.312 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.312.312 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.312.312 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.248.418l.182.181c.122.122.32.122.441 0l.973-.973a.312.312 0 0 0 0-.44l-.181-.183c-.627-.627-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.312.312 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.312.312 0 0 0 0-.441l-.973-.974a.312.312 0 0 0-.44 0l-.183.182c-.627.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.312.312 0 0 0 12.688 4h-1.376a.312.312 0 0 0-.312.312Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Settings
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="critical"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M14 13c.283 0 .52-.096.713-.287A.968.968 0 0 0 15 12a.968.968 0 0 0-.287-.713A.968.968 0 0 0 14 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 13 12c0 .283.096.52.287.713.192.191.43.287.713.287Z"
-            />
-            <path
-              d="M10.385 21.788A.998.998 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a.998.998 0 0 1-.857-.182ZM18 5.781l-6-1.5v15.438l6-1.5V5.781ZM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2v2Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Leave room
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-      </div>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`<RoomSummaryCard /> renders the room summary 1`] = `
-<div>
-  <div
-    aria-labelledby="room-summary-panel-tab"
-    class="mx_BaseCard mx_RoomSummaryCard"
-    id="room-summary-panel"
-    role="tabpanel"
-  >
-    <div
-      class="mx_BaseCard_header"
-    >
-      <div
-        class="mx_BaseCard_header_spacer"
-      />
-      <button
-        aria-labelledby=":r0:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
-        data-testid="base-card-close-button"
-        role="button"
-        style="--cpd-icon-button-size: 28px;"
-        tabindex="0"
-      >
-        <div
-          class="_indicator-icon_133tf_26"
-          style="--cpd-icon-button-size: 100%;"
-        >
-          <svg
-            fill="currentColor"
-            height="1em"
-            viewBox="0 0 24 24"
-            width="1em"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
-            />
-          </svg>
-        </div>
-      </button>
-    </div>
-    <div
-      class="mx_AutoHideScrollbar"
-      tabindex="-1"
-    >
-      <header
-        class="mx_RoomSummaryCard_container"
-      >
-        <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-          data-color="1"
-          data-testid="avatar-img"
-          data-type="round"
-          role="presentation"
-          style="--cpd-avatar-size: 80px;"
-        >
-          !
-        </span>
-        <h1
-          class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121 mx_RoomSummaryCard_roomName text-primary"
-          title="!room:domain.org"
-        >
-          !room:domain.org
-        </h1>
-        <div
-          class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_RoomSummaryCard_alias text-secondary"
-          title=""
-        />
-        <section
-          class="mx_Flex mx_RoomSummaryCard_badges"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <span
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50 _badge_1171v_17"
-            data-kind="grey"
-          >
-            <svg
-              fill="currentColor"
-              height="1em"
-              viewBox="0 0 24 24"
-              width="1em"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M6 22c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 4 20V10c0-.55.196-1.02.587-1.412.212-.212.446-.366.703-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 0 1-1.414 1.414l-.896-.896A1.935 1.935 0 0 1 18 22H6Zm14-4.834V10c0-.55-.196-1.02-.587-1.412A1.926 1.926 0 0 0 18 8h-1V6c0-1.383-.488-2.563-1.463-3.538C14.563 1.487 13.383 1 12 1s-2.563.488-3.538 1.462A4.876 4.876 0 0 0 7.243 4.41L9 6.166V6c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 12 3c.833 0 1.542.292 2.125.875S15 5.167 15 6v2h-4.166L20 17.166Z"
-              />
-            </svg>
-            Not encrypted
-          </span>
-        </section>
-        <section
-          class="mx_Flex mx_RoomSummaryCard_topic"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <div
-            class="mx_Box mx_Box--flex"
-            style="--mx-box-flex: 1;"
-          >
-            <a
-              class="_link_ue21z_17"
-              data-kind="primary"
-              data-size="medium"
-              rel="noreferrer noopener"
-            >
-              <p
-                class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-              >
-                Add topic
-              </p>
-            </a>
-          </div>
-        </section>
-      </header>
-      <div
-        class="_separator_144s5_17"
-        data-kind="primary"
-        data-orientation="horizontal"
-        role="separator"
-      />
-      <div
-        aria-orientation="vertical"
-        role="menubar"
-      >
-        <div
-          aria-checked="false"
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitemcheckbox"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004-4.26-.619ZM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Favourite
-          </span>
-          <div
-            class="_container_qnvru_18"
-          >
-            <input
-              aria-hidden="true"
-              class="_input_qnvru_32"
-              id=":r5:"
-              type="checkbox"
-            />
-            <div
-              class="_ui_qnvru_42"
-            />
-          </div>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M10 12c-1.1 0-2.042-.392-2.825-1.175C6.392 10.042 6 9.1 6 8s.392-2.042 1.175-2.825C7.958 4.392 8.9 4 10 4s2.042.392 2.825 1.175C13.608 5.958 14 6.9 14 8s-.392 2.042-1.175 2.825C12.042 11.608 11.1 12 10 12Zm-8 6v-.8c0-.567.146-1.087.438-1.563A2.911 2.911 0 0 1 3.6 14.55a14.843 14.843 0 0 1 3.15-1.163A13.76 13.76 0 0 1 10 13c1.1 0 2.183.13 3.25.387 1.067.259 2.117.646 3.15 1.163.483.25.87.612 1.163 1.087.291.476.437.996.437 1.563v.8c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 16 20H4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 2 18Zm2 0h12v-.8a.973.973 0 0 0-.5-.85c-.9-.45-1.808-.787-2.725-1.012a11.6 11.6 0 0 0-5.55 0c-.917.225-1.825.562-2.725 1.012a.973.973 0 0 0-.5.85v.8Zm6-8c.55 0 1.02-.196 1.412-.588C11.804 9.021 12 8.55 12 8c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 10 6c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 8 8c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm7 1h2v2c0 .283.096.52.288.713.191.191.429.287.712.287s.52-.096.712-.287A.968.968 0 0 0 21 13v-2h2a.97.97 0 0 0 .712-.287A.968.968 0 0 0 24 10a.967.967 0 0 0-.288-.713A.968.968 0 0 0 23 9h-2V7a.967.967 0 0 0-.288-.713A.968.968 0 0 0 20 6a.968.968 0 0 0-.712.287A.967.967 0 0 0 19 7v2h-2a.968.968 0 0 0-.712.287A.967.967 0 0 0 16 10c0 .283.096.52.288.713A.968.968 0 0 0 17 11Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Invite
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M9.175 13.825C9.958 14.608 10.9 15 12 15s2.042-.392 2.825-1.175C15.608 13.042 16 12.1 16 11s-.392-2.042-1.175-2.825C14.042 7.392 13.1 7 12 7s-2.042.392-2.825 1.175C8.392 8.958 8 9.9 8 11s.392 2.042 1.175 2.825Zm4.237-1.412A1.926 1.926 0 0 1 12 13c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 11c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 9c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412Z"
-            />
-            <path
-              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
-            />
-            <path
-              d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            People
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M7 10a.968.968 0 0 1-.713-.287A.968.968 0 0 1 6 9c0-.283.096-.52.287-.713A.968.968 0 0 1 7 8h10a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 17 10H7Zm0 4a.967.967 0 0 1-.713-.287A.968.968 0 0 1 6 13c0-.283.096-.52.287-.713A.967.967 0 0 1 7 12h6c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 13 14H7Z"
-            />
-            <path
-              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293ZM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Threads
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          aria-expanded="false"
-          aria-haspopup="dialog"
-        >
-          <button
-            class="_item_8j2l6_17 _interactive_8j2l6_35"
-            data-kind="primary"
-            role="menuitem"
-          >
-            <svg
-              aria-hidden="true"
-              class="_icon_8j2l6_43"
-              fill="currentColor"
-              height="24"
-              viewBox="0 0 24 24"
-              width="24"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                clip-rule="evenodd"
-                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857H6.119ZM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744V4Z"
-                fill-rule="evenodd"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-            >
-              Pinned messages
-            </span>
-            <svg
-              aria-hidden="true"
-              class="_nav-hint_8j2l6_59"
-              fill="currentColor"
-              height="24"
-              viewBox="8 0 8 24"
-              width="8"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-            >
-              0
-            </span>
-          </button>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V4c0-.55.196-1.02.588-1.413A1.926 1.926 0 0 1 6 2h7.175a1.975 1.975 0 0 1 1.4.575l4.85 4.85a1.975 1.975 0 0 1 .575 1.4V20c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm7-14V4H6v16h12V9h-4a.968.968 0 0 1-.713-.287A.967.967 0 0 1 13 8Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Files
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M17.25 11.672a.907.907 0 0 1-.663-.282L12.61 7.413a.907.907 0 0 1-.282-.663c0-.254.094-.475.282-.663l3.977-3.977a.907.907 0 0 1 .663-.282c.254 0 .475.094.663.282l3.977 3.977a.907.907 0 0 1 .282.663.907.907 0 0 1-.282.663l-3.977 3.977a.907.907 0 0 1-.663.282Zm2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475 2.475-2.475ZM4 11a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6a.97.97 0 0 1 .713.288A.968.968 0 0 1 11 4v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4Zm5-2V5H5v4h4Zm5 12a.968.968 0 0 1-.713-.288A.968.968 0 0 1 13 20v-6c0-.283.096-.52.287-.713A.968.968 0 0 1 14 13h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v6c0 .283-.096.52-.288.712A.968.968 0 0 1 20 21h-6Zm5-2v-4h-4v4h4ZM4 21a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20v-6a.97.97 0 0 1 .288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6a.97.97 0 0 1-.287.712A.968.968 0 0 1 10 21H4Zm5-2v-4H5v4h4Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Extensions
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Copy link
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 17 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 16 8h-3a.968.968 0 0 0-.713.287A.967.967 0 0 0 12 9c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm0 6a.97.97 0 0 0 .712-.287A.968.968 0 0 0 17 15a.968.968 0 0 0-.288-.713A.968.968 0 0 0 16 14h-3a.968.968 0 0 0-.713.287A.968.968 0 0 0 12 15c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm-7-5c.55 0 1.02-.196 1.412-.588C10.804 10.021 11 9.55 11 9c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 7c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 9c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm0 6c.55 0 1.02-.196 1.412-.587.392-.392.588-.863.588-1.413s-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 13c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 15c0 .55.196 1.02.588 1.413.391.391.862.587 1.412.587Zm-4 4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V5c0-.55.196-1.02.587-1.413A1.926 1.926 0 0 1 5 3h14c.55 0 1.02.196 1.413.587.39.393.587.863.587 1.413v14c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm0-2h14V5H5v14Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Polls
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M5 21c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V6.5c0-.25.042-.475.125-.675.083-.2.192-.392.325-.575l1.4-1.7c.133-.183.3-.32.5-.412C5.55 3.046 5.767 3 6 3h12c.233 0 .45.046.65.138.2.091.367.229.5.412l1.4 1.7c.133.183.242.375.325.575.083.2.125.425.125.675V19c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm.4-15h13.2l-.85-1H6.25L5.4 6ZM5 19h14V8H5v11Zm7-1.425c.133 0 .258-.02.375-.063a.877.877 0 0 0 .325-.212l2.6-2.6a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275l-.9.9V11a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 10a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 11v3.2l-.9-.9a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7l2.6 2.6c.1.1.208.17.325.212.117.042.242.063.375.063Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Export Chat
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Zm-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"
-            />
-            <path
-              d="M11.312 2h1.376A2.312 2.312 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.312 2.312 0 0 1 3.27 0l.973.974a2.312 2.312 0 0 1 0 3.269l-.177.177v.003a.134.134 0 0 0 .01.07.153.153 0 0 0 .047.063l.003.002h.247A2.312 2.312 0 0 1 22 11.312v1.376A2.312 2.312 0 0 1 19.688 15h-.247l-.003.002a.152.152 0 0 0-.047.064.134.134 0 0 0-.01.07v.002l.177.177a2.312 2.312 0 0 1 0 3.27l-.974.973a2.312 2.312 0 0 1-3.269 0l-.177-.177h-.003a.134.134 0 0 0-.07.01.152.152 0 0 0-.063.047l-.002.003v.247A2.312 2.312 0 0 1 12.688 22h-1.376A2.312 2.312 0 0 1 9 19.688v-.247l-.002-.003a.153.153 0 0 0-.064-.047.134.134 0 0 0-.07-.01h-.002l-.177.177a2.312 2.312 0 0 1-3.27 0l-.973-.974a2.312 2.312 0 0 1 0-3.269l.177-.177v-.003a.135.135 0 0 0-.01-.07.152.152 0 0 0-.047-.063L4.559 15h-.247A2.312 2.312 0 0 1 2 12.688v-1.376A2.312 2.312 0 0 1 4.312 9h.247l.003-.002a.153.153 0 0 0 .047-.064.135.135 0 0 0 .01-.07v-.002l-.177-.177a2.312 2.312 0 0 1 0-3.27l.974-.973a2.312 2.312 0 0 1 3.269 0l.177.177h.003a.135.135 0 0 0 .07-.01.153.153 0 0 0 .063-.047L9 4.559v-.247A2.312 2.312 0 0 1 11.312 2ZM11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.312.312 0 0 0-.441 0l-.974.974a.312.312 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.312.312 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.312.312 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.248.418l.182.181c.122.122.32.122.441 0l.973-.973a.312.312 0 0 0 0-.44l-.181-.183c-.627-.627-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.312.312 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.312.312 0 0 0 0-.441l-.973-.974a.312.312 0 0 0-.44 0l-.183.182c-.627.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.312.312 0 0 0 12.688 4h-1.376a.312.312 0 0 0-.312.312Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Settings
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="critical"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M14 13c.283 0 .52-.096.713-.287A.968.968 0 0 0 15 12a.968.968 0 0 0-.287-.713A.968.968 0 0 0 14 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 13 12c0 .283.096.52.287.713.192.191.43.287.713.287Z"
-            />
-            <path
-              d="M10.385 21.788A.998.998 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a.998.998 0 0 1-.857-.182ZM18 5.781l-6-1.5v15.438l6-1.5V5.781ZM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2v2Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Leave room
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-      </div>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
-<div>
-  <div
-    aria-labelledby="room-summary-panel-tab"
-    class="mx_BaseCard mx_RoomSummaryCard"
-    id="room-summary-panel"
-    role="tabpanel"
-  >
-    <div
-      class="mx_BaseCard_header"
-    >
-      <div
-        class="mx_BaseCard_header_spacer"
-      />
-      <button
-        aria-labelledby=":rb:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
-        data-testid="base-card-close-button"
-        role="button"
-        style="--cpd-icon-button-size: 28px;"
-        tabindex="0"
-      >
-        <div
-          class="_indicator-icon_133tf_26"
-          style="--cpd-icon-button-size: 100%;"
-        >
-          <svg
-            fill="currentColor"
-            height="1em"
-            viewBox="0 0 24 24"
-            width="1em"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
-            />
-          </svg>
-        </div>
-      </button>
-    </div>
-    <div
-      class="mx_AutoHideScrollbar"
-      tabindex="-1"
-    >
-      <header
-        class="mx_RoomSummaryCard_container"
-      >
-        <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
-          data-color="1"
-          data-testid="avatar-img"
-          data-type="round"
-          role="presentation"
-          style="--cpd-avatar-size: 80px;"
-        >
-          !
-        </span>
-        <h1
-          class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121 mx_RoomSummaryCard_roomName text-primary"
-          title="!room:domain.org"
-        >
-          !room:domain.org
-        </h1>
-        <div
-          class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_RoomSummaryCard_alias text-secondary"
-          title=""
-        />
-        <section
-          class="mx_Flex mx_RoomSummaryCard_badges"
-          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <span
-            class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50 _badge_1171v_17"
-            data-kind="grey"
-          >
-            <svg
-              fill="currentColor"
-              height="1em"
-              viewBox="0 0 24 24"
-              width="1em"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M6 22c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 4 20V10c0-.55.196-1.02.587-1.412.212-.212.446-.366.703-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 0 1-1.414 1.414l-.896-.896A1.935 1.935 0 0 1 18 22H6Zm14-4.834V10c0-.55-.196-1.02-.587-1.412A1.926 1.926 0 0 0 18 8h-1V6c0-1.383-.488-2.563-1.463-3.538C14.563 1.487 13.383 1 12 1s-2.563.488-3.538 1.462A4.876 4.876 0 0 0 7.243 4.41L9 6.166V6c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 12 3c.833 0 1.542.292 2.125.875S15 5.167 15 6v2h-4.166L20 17.166Z"
-              />
-            </svg>
-            Not encrypted
-          </span>
-        </section>
-        <section
-          class="mx_Flex mx_RoomSummaryCard_topic"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
-        >
-          <div
-            class="mx_Box mx_RoomSummaryCard_topic_container mx_Box--flex"
-            style="--mx-box-flex: 1;"
-          >
-            <p
-              class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-            >
-              <span
-                dir="auto"
-              >
-                This is the room's topic.
-              </span>
-            </p>
-            <button
-              class="_icon-button_bh2qc_17 mx_RoomSummaryCard_topic_chevron"
-              role="button"
-              style="--cpd-icon-button-size: 24px;"
-              tabindex="0"
-            >
-              <div
-                class="_indicator-icon_133tf_26"
-                style="--cpd-icon-button-size: 100%;"
-              >
-                <svg
-                  fill="currentColor"
-                  height="1em"
-                  viewBox="0 0 24 24"
-                  width="1em"
-                  xmlns="http://www.w3.org/2000/svg"
-                >
-                  <path
-                    d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
-                  />
-                </svg>
-              </div>
-            </button>
-          </div>
-          <div
-            class="mx_Box mx_RoomSummaryCard_topic_edit mx_Box--flex"
-            style="--mx-box-flex: 1;"
-          >
-            <a
-              class="_link_ue21z_17"
-              data-kind="primary"
-              data-size="medium"
-              rel="noreferrer noopener"
-            >
-              <p
-                class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-              >
-                Edit
-              </p>
-            </a>
-          </div>
-        </section>
-      </header>
-      <div
-        class="_separator_144s5_17"
-        data-kind="primary"
-        data-orientation="horizontal"
-        role="separator"
-      />
-      <div
-        aria-orientation="vertical"
-        role="menubar"
-      >
-        <div
-          aria-checked="false"
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitemcheckbox"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004-4.26-.619ZM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Favourite
-          </span>
-          <div
-            class="_container_qnvru_18"
-          >
-            <input
-              aria-hidden="true"
-              class="_input_qnvru_32"
-              id=":rg:"
-              type="checkbox"
-            />
-            <div
-              class="_ui_qnvru_42"
-            />
-          </div>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M10 12c-1.1 0-2.042-.392-2.825-1.175C6.392 10.042 6 9.1 6 8s.392-2.042 1.175-2.825C7.958 4.392 8.9 4 10 4s2.042.392 2.825 1.175C13.608 5.958 14 6.9 14 8s-.392 2.042-1.175 2.825C12.042 11.608 11.1 12 10 12Zm-8 6v-.8c0-.567.146-1.087.438-1.563A2.911 2.911 0 0 1 3.6 14.55a14.843 14.843 0 0 1 3.15-1.163A13.76 13.76 0 0 1 10 13c1.1 0 2.183.13 3.25.387 1.067.259 2.117.646 3.15 1.163.483.25.87.612 1.163 1.087.291.476.437.996.437 1.563v.8c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 16 20H4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 2 18Zm2 0h12v-.8a.973.973 0 0 0-.5-.85c-.9-.45-1.808-.787-2.725-1.012a11.6 11.6 0 0 0-5.55 0c-.917.225-1.825.562-2.725 1.012a.973.973 0 0 0-.5.85v.8Zm6-8c.55 0 1.02-.196 1.412-.588C11.804 9.021 12 8.55 12 8c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 10 6c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 8 8c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm7 1h2v2c0 .283.096.52.288.713.191.191.429.287.712.287s.52-.096.712-.287A.968.968 0 0 0 21 13v-2h2a.97.97 0 0 0 .712-.287A.968.968 0 0 0 24 10a.967.967 0 0 0-.288-.713A.968.968 0 0 0 23 9h-2V7a.967.967 0 0 0-.288-.713A.968.968 0 0 0 20 6a.968.968 0 0 0-.712.287A.967.967 0 0 0 19 7v2h-2a.968.968 0 0 0-.712.287A.967.967 0 0 0 16 10c0 .283.096.52.288.713A.968.968 0 0 0 17 11Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Invite
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M9.175 13.825C9.958 14.608 10.9 15 12 15s2.042-.392 2.825-1.175C15.608 13.042 16 12.1 16 11s-.392-2.042-1.175-2.825C14.042 7.392 13.1 7 12 7s-2.042.392-2.825 1.175C8.392 8.958 8 9.9 8 11s.392 2.042 1.175 2.825Zm4.237-1.412A1.926 1.926 0 0 1 12 13c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 11c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 9c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412Z"
-            />
-            <path
-              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
-            />
-            <path
-              d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            People
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M7 10a.968.968 0 0 1-.713-.287A.968.968 0 0 1 6 9c0-.283.096-.52.287-.713A.968.968 0 0 1 7 8h10a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 17 10H7Zm0 4a.967.967 0 0 1-.713-.287A.968.968 0 0 1 6 13c0-.283.096-.52.287-.713A.967.967 0 0 1 7 12h6c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 13 14H7Z"
-            />
-            <path
-              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293ZM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Threads
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          aria-expanded="false"
-          aria-haspopup="dialog"
-        >
-          <button
-            class="_item_8j2l6_17 _interactive_8j2l6_35"
-            data-kind="primary"
-            role="menuitem"
-          >
-            <svg
-              aria-hidden="true"
-              class="_icon_8j2l6_43"
-              fill="currentColor"
-              height="24"
-              viewBox="0 0 24 24"
-              width="24"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                clip-rule="evenodd"
-                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857H6.119ZM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744V4Z"
-                fill-rule="evenodd"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-            >
-              Pinned messages
-            </span>
-            <svg
-              aria-hidden="true"
-              class="_nav-hint_8j2l6_59"
-              fill="currentColor"
-              height="24"
-              viewBox="8 0 8 24"
-              width="8"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-              />
-            </svg>
-            <span
-              class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
-            >
-              0
-            </span>
-          </button>
-        </div>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V4c0-.55.196-1.02.588-1.413A1.926 1.926 0 0 1 6 2h7.175a1.975 1.975 0 0 1 1.4.575l4.85 4.85a1.975 1.975 0 0 1 .575 1.4V20c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm7-14V4H6v16h12V9h-4a.968.968 0 0 1-.713-.287A.967.967 0 0 1 13 8Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Files
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M17.25 11.672a.907.907 0 0 1-.663-.282L12.61 7.413a.907.907 0 0 1-.282-.663c0-.254.094-.475.282-.663l3.977-3.977a.907.907 0 0 1 .663-.282c.254 0 .475.094.663.282l3.977 3.977a.907.907 0 0 1 .282.663.907.907 0 0 1-.282.663l-3.977 3.977a.907.907 0 0 1-.663.282Zm2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475 2.475-2.475ZM4 11a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6a.97.97 0 0 1 .713.288A.968.968 0 0 1 11 4v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4Zm5-2V5H5v4h4Zm5 12a.968.968 0 0 1-.713-.288A.968.968 0 0 1 13 20v-6c0-.283.096-.52.287-.713A.968.968 0 0 1 14 13h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v6c0 .283-.096.52-.288.712A.968.968 0 0 1 20 21h-6Zm5-2v-4h-4v4h4ZM4 21a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20v-6a.97.97 0 0 1 .288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6a.97.97 0 0 1-.287.712A.968.968 0 0 1 10 21H4Zm5-2v-4H5v4h4Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Extensions
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Copy link
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 17 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 16 8h-3a.968.968 0 0 0-.713.287A.967.967 0 0 0 12 9c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm0 6a.97.97 0 0 0 .712-.287A.968.968 0 0 0 17 15a.968.968 0 0 0-.288-.713A.968.968 0 0 0 16 14h-3a.968.968 0 0 0-.713.287A.968.968 0 0 0 12 15c0 .283.096.52.287.713.192.191.43.287.713.287h3Zm-7-5c.55 0 1.02-.196 1.412-.588C10.804 10.021 11 9.55 11 9c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 7c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 9c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Zm0 6c.55 0 1.02-.196 1.412-.587.392-.392.588-.863.588-1.413s-.196-1.02-.588-1.412A1.926 1.926 0 0 0 9 13c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 15c0 .55.196 1.02.588 1.413.391.391.862.587 1.412.587Zm-4 4c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V5c0-.55.196-1.02.587-1.413A1.926 1.926 0 0 1 5 3h14c.55 0 1.02.196 1.413.587.39.393.587.863.587 1.413v14c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm0-2h14V5H5v14Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Polls
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M5 21c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V6.5c0-.25.042-.475.125-.675.083-.2.192-.392.325-.575l1.4-1.7c.133-.183.3-.32.5-.412C5.55 3.046 5.767 3 6 3h12c.233 0 .45.046.65.138.2.091.367.229.5.412l1.4 1.7c.133.183.242.375.325.575.083.2.125.425.125.675V19c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm.4-15h13.2l-.85-1H6.25L5.4 6ZM5 19h14V8H5v11Zm7-1.425c.133 0 .258-.02.375-.063a.877.877 0 0 0 .325-.212l2.6-2.6a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275l-.9.9V11a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 10a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 11v3.2l-.9-.9a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7l2.6 2.6c.1.1.208.17.325.212.117.042.242.063.375.063Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Export Chat
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="primary"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Zm-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"
-            />
-            <path
-              d="M11.312 2h1.376A2.312 2.312 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.312 2.312 0 0 1 3.27 0l.973.974a2.312 2.312 0 0 1 0 3.269l-.177.177v.003a.134.134 0 0 0 .01.07.153.153 0 0 0 .047.063l.003.002h.247A2.312 2.312 0 0 1 22 11.312v1.376A2.312 2.312 0 0 1 19.688 15h-.247l-.003.002a.152.152 0 0 0-.047.064.134.134 0 0 0-.01.07v.002l.177.177a2.312 2.312 0 0 1 0 3.27l-.974.973a2.312 2.312 0 0 1-3.269 0l-.177-.177h-.003a.134.134 0 0 0-.07.01.152.152 0 0 0-.063.047l-.002.003v.247A2.312 2.312 0 0 1 12.688 22h-1.376A2.312 2.312 0 0 1 9 19.688v-.247l-.002-.003a.153.153 0 0 0-.064-.047.134.134 0 0 0-.07-.01h-.002l-.177.177a2.312 2.312 0 0 1-3.27 0l-.973-.974a2.312 2.312 0 0 1 0-3.269l.177-.177v-.003a.135.135 0 0 0-.01-.07.152.152 0 0 0-.047-.063L4.559 15h-.247A2.312 2.312 0 0 1 2 12.688v-1.376A2.312 2.312 0 0 1 4.312 9h.247l.003-.002a.153.153 0 0 0 .047-.064.135.135 0 0 0 .01-.07v-.002l-.177-.177a2.312 2.312 0 0 1 0-3.27l.974-.973a2.312 2.312 0 0 1 3.269 0l.177.177h.003a.135.135 0 0 0 .07-.01.153.153 0 0 0 .063-.047L9 4.559v-.247A2.312 2.312 0 0 1 11.312 2ZM11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.312.312 0 0 0-.441 0l-.974.974a.312.312 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.312.312 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.312.312 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.248.418l.182.181c.122.122.32.122.441 0l.973-.973a.312.312 0 0 0 0-.44l-.181-.183c-.627-.627-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.312.312 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.312.312 0 0 0 0-.441l-.973-.974a.312.312 0 0 0-.44 0l-.183.182c-.627.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.312.312 0 0 0 12.688 4h-1.376a.312.312 0 0 0-.312.312Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Settings
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-        <div
-          class="_separator_144s5_17"
-          data-kind="primary"
-          data-orientation="horizontal"
-          role="separator"
-        />
-        <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
-          data-kind="critical"
-          role="menuitem"
-        >
-          <svg
-            aria-hidden="true"
-            class="_icon_8j2l6_43"
-            fill="currentColor"
-            height="24"
-            viewBox="0 0 24 24"
-            width="24"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M14 13c.283 0 .52-.096.713-.287A.968.968 0 0 0 15 12a.968.968 0 0 0-.287-.713A.968.968 0 0 0 14 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 13 12c0 .283.096.52.287.713.192.191.43.287.713.287Z"
-            />
-            <path
-              d="M10.385 21.788A.998.998 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a.998.998 0 0 1-.857-.182ZM18 5.781l-6-1.5v15.438l6-1.5V5.781ZM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2v2Z"
-            />
-          </svg>
-          <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
-          >
-            Leave room
-          </span>
-          <svg
-            aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
-            fill="currentColor"
-            height="24"
-            viewBox="8 0 8 24"
-            width="8"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
-            />
-          </svg>
-        </button>
-      </div>
-    </div>
-  </div>
-</div>
-`;
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..10e9ae776d175a3915843123dc5db1b185882955
--- /dev/null
+++ b/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap
@@ -0,0 +1,2238 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
+<div>
+  <div
+    aria-labelledby="room-summary-panel-tab"
+    class="mx_BaseCard mx_RoomSummaryCard"
+    id="room-summary-panel"
+    role="tabpanel"
+  >
+    <div
+      class="mx_BaseCard_header"
+      data-floating-ui-inert=""
+    >
+      <div
+        class="mx_BaseCard_header_spacer"
+      />
+      <button
+        aria-labelledby="«rq»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+        data-testid="base-card-close-button"
+        role="button"
+        style="--cpd-icon-button-size: 28px;"
+        tabindex="0"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_AutoHideScrollbar"
+      tabindex="-1"
+    >
+      <header
+        class="mx_RoomSummaryCard_container"
+        data-floating-ui-inert=""
+      >
+        <span
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="1"
+          data-testid="avatar-img"
+          data-type="round"
+          role="presentation"
+          style="--cpd-avatar-size: 80px;"
+        >
+          !
+        </span>
+        <h1
+          class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112 mx_RoomSummaryCard_roomName text-primary"
+          title="!room:domain.org"
+        >
+          !room:domain.org
+        </h1>
+        <div
+          class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_RoomSummaryCard_alias text-secondary"
+          title=""
+        />
+        <section
+          class="mx_Flex mx_RoomSummaryCard_badges"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
+              />
+            </svg>
+            Public room
+          </span>
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22q-.825 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412a2 2 0 0 1 .702-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 1 1-1.414 1.414l-.896-.896A1.94 1.94 0 0 1 18 22zm14-4.834V10q0-.825-.587-1.412A1.93 1.93 0 0 0 18 8h-1V6q0-2.075-1.463-3.537Q14.075 1 12 1T8.463 2.463a4.9 4.9 0 0 0-1.22 1.946L9 6.166V6q0-1.25.875-2.125A2.9 2.9 0 0 1 12 3q1.25 0 2.125.875T15 6v2h-4.166z"
+              />
+            </svg>
+            Not encrypted
+          </span>
+        </section>
+        <section
+          class="mx_Flex mx_RoomSummaryCard_topic"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <div
+            class="mx_Box mx_RoomSummaryCard_topic_container mx_Box--flex"
+            style="--mx-box-flex: 1;"
+          >
+            <p
+              class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+            >
+              <span
+                dir="auto"
+              >
+                This is the room's topic.
+              </span>
+            </p>
+            <button
+              class="_icon-button_m2erp_8 mx_RoomSummaryCard_topic_chevron"
+              role="button"
+              style="--cpd-icon-button-size: 24px;"
+              tabindex="0"
+            >
+              <div
+                class="_indicator-icon_zr2a0_17"
+                style="--cpd-icon-button-size: 100%;"
+              >
+                <svg
+                  fill="currentColor"
+                  height="1em"
+                  viewBox="0 0 24 24"
+                  width="1em"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                  />
+                </svg>
+              </div>
+            </button>
+          </div>
+          <div
+            class="mx_Box mx_RoomSummaryCard_topic_edit mx_Box--flex"
+            style="--mx-box-flex: 1;"
+          >
+            <a
+              class="_link_1v5rz_8"
+              data-kind="primary"
+              data-size="medium"
+              rel="noreferrer noopener"
+            >
+              <p
+                class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+              >
+                Edit
+              </p>
+            </a>
+          </div>
+        </section>
+      </header>
+      <div
+        class="_separator_7ckbw_8"
+        data-floating-ui-inert=""
+        data-kind="primary"
+        data-orientation="horizontal"
+        role="separator"
+      />
+      <div
+        aria-orientation="vertical"
+        role="menubar"
+      >
+        <div
+          aria-checked="false"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitemcheckbox"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004zM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Favourite
+          </span>
+          <div
+            class="_container_19o42_10"
+          >
+            <input
+              aria-hidden="true"
+              class="_input_19o42_24"
+              id="«rv»"
+              type="checkbox"
+            />
+            <div
+              class="_ui_19o42_34"
+            />
+          </div>
+        </div>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Invite
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.175 13.825Q10.35 15 12 15t2.825-1.175T16 11t-1.175-2.825T12 7 9.175 8.175 8 11t1.175 2.825m4.237-1.412A1.93 1.93 0 0 1 12 13q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 11q0-.825.588-1.412A1.93 1.93 0 0 1 12 9q.825 0 1.412.588Q14 10.175 14 11t-.588 1.412"
+            />
+            <path
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0"
+            />
+            <path
+              d="M16.23 18.792a13 13 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0q-.73.18-1.455.455a8 8 0 0 1-1.729-1.454q1.336-.618 2.709-.95A13.8 13.8 0 0 1 12 16q1.65 0 3.25.387 1.373.333 2.709.95a8 8 0 0 1-1.73 1.455"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            People
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M7 10a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 9q0-.424.287-.713A.97.97 0 0 1 7 8h10q.424 0 .712.287Q18 8.576 18 9t-.288.713A.97.97 0 0 1 17 10zm0 4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 13q0-.424.287-.713A.97.97 0 0 1 7 12h6q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 13 14z"
+            />
+            <path
+              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6zM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Threads
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          aria-controls="«r12»"
+          aria-describedby="«r12»"
+          aria-expanded="true"
+          aria-haspopup="dialog"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="primary"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                clip-rule="evenodd"
+                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857zM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744z"
+                fill-rule="evenodd"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Pinned messages
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+            >
+              0
+            </span>
+          </button>
+        </div>
+        <span
+          aria-hidden="true"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="-1"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <span
+          aria-owns="«r14»"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V4q0-.824.588-1.412A1.93 1.93 0 0 1 6 2h7.175a1.98 1.98 0 0 1 1.4.575l4.85 4.85q.275.275.425.638.15.361.15.762V20q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zm7-14V4H6v16h12V9h-4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 13 8"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Files
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M17.25 11.672a.9.9 0 0 1-.663-.282L12.61 7.413a.9.9 0 0 1-.282-.663q0-.381.282-.663l3.977-3.977a.9.9 0 0 1 .663-.282q.381 0 .663.282l3.977 3.977a.9.9 0 0 1 .282.663.9.9 0 0 1-.282.663l-3.977 3.977a.9.9 0 0 1-.663.282m2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475zM4 11a.97.97 0 0 1-.712-.287A.97.97 0 0 1 3 10V4q0-.424.288-.712A.97.97 0 0 1 4 3h6q.424 0 .713.288Q11 3.575 11 4v6q0 .424-.287.713A.97.97 0 0 1 10 11zm5-2V5H5v4zm5 12a.97.97 0 0 1-.713-.288A.97.97 0 0 1 13 20v-6q0-.424.287-.713A.97.97 0 0 1 14 13h6q.424 0 .712.287.288.288.288.713v6q0 .424-.288.712A.97.97 0 0 1 20 21zm5-2v-4h-4v4zM4 21a.97.97 0 0 1-.712-.288A.97.97 0 0 1 3 20v-6q0-.424.288-.713A.97.97 0 0 1 4 13h6q.424 0 .713.287.287.288.287.713v6q0 .424-.287.712A.97.97 0 0 1 10 21zm5-2v-4H5v4z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Extensions
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Copy link
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 10q.424 0 .712-.287A.97.97 0 0 0 17 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 8h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 9q0 .424.287.713.288.287.713.287zm0 6q.424 0 .712-.287A.97.97 0 0 0 17 15a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 14h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 15q0 .424.287.713.288.287.713.287zm-7-5q.825 0 1.412-.588Q11 9.826 11 9t-.588-1.412A1.93 1.93 0 0 0 9 7q-.825 0-1.412.588A1.93 1.93 0 0 0 7 9q0 .825.588 1.412Q8.175 11 9 11m0 6q.825 0 1.412-.587Q11 15.825 11 15t-.588-1.412A1.93 1.93 0 0 0 9 13q-.825 0-1.412.588A1.93 1.93 0 0 0 7 15q0 .824.588 1.413Q8.175 17 9 17m-4 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h14q.824 0 1.413.587Q21 4.176 21 5v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm0-2h14V5H5z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Polls
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V6.5q0-.375.125-.675t.325-.575l1.4-1.7q.2-.274.5-.412T6 3h12q.35 0 .65.138.3.137.5.412l1.4 1.7q.2.275.325.575T21 6.5V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm.4-15h13.2l-.85-1H6.25zM5 19h14V8H5zm7-1.425q.2 0 .375-.062a.9.9 0 0 0 .325-.213l2.6-2.6a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275l-.9.9V11a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 10a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 11v3.2l-.9-.9a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7l2.6 2.6q.15.15.325.212.175.063.375.063"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Export Chat
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0m-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0"
+            />
+            <path
+              d="M11.312 2h1.376A2.31 2.31 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.31 2.31 0 0 1 3.27 0l.973.974a2.31 2.31 0 0 1 0 3.269l-.177.177v.003a.13.13 0 0 0 .01.07.15.15 0 0 0 .047.063l.003.002h.247A2.31 2.31 0 0 1 22 11.312v1.376A2.31 2.31 0 0 1 19.688 15h-.247l-.003.002a.15.15 0 0 0-.047.064.13.13 0 0 0-.01.07v.002l.177.177a2.31 2.31 0 0 1 0 3.27l-.974.973a2.31 2.31 0 0 1-3.269 0l-.177-.177h-.003a.13.13 0 0 0-.07.01.15.15 0 0 0-.063.047l-.002.003v.247A2.31 2.31 0 0 1 12.688 22h-1.376A2.31 2.31 0 0 1 9 19.688v-.247l-.002-.003a.15.15 0 0 0-.064-.047.13.13 0 0 0-.07-.01h-.002l-.177.177a2.31 2.31 0 0 1-3.27 0l-.973-.974a2.31 2.31 0 0 1 0-3.269l.177-.177v-.003a.14.14 0 0 0-.01-.07.15.15 0 0 0-.047-.063L4.559 15h-.247A2.31 2.31 0 0 1 2 12.688v-1.376A2.31 2.31 0 0 1 4.312 9h.247l.003-.002a.15.15 0 0 0 .047-.064.14.14 0 0 0 .01-.07v-.002l-.177-.177a2.31 2.31 0 0 1 0-3.27l.974-.973a2.31 2.31 0 0 1 3.269 0l.177.177h.003a.14.14 0 0 0 .07-.01.15.15 0 0 0 .063-.047L9 4.559v-.247A2.31 2.31 0 0 1 11.312 2M11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.31.31 0 0 0-.441 0l-.974.974a.31.31 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.31.31 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.31.31 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.249.418l.181.181c.122.122.32.122.441 0l.973-.973a.31.31 0 0 0 0-.44l-.181-.183c-.627-.626-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.31.31 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.31.31 0 0 0 0-.441l-.973-.974a.31.31 0 0 0-.44 0l-.183.182c-.626.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.31.31 0 0 0 12.688 4h-1.376a.31.31 0 0 0-.312.312"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Settings
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <div
+          class="mx_RoomSummaryCard_bottomOptions"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Report room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+          <button
+            class="mx_RoomSummaryCard_leave _item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
+              />
+              <path
+                d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Leave room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`<RoomSummaryCard /> renders the room summary 1`] = `
+<div>
+  <div
+    aria-labelledby="room-summary-panel-tab"
+    class="mx_BaseCard mx_RoomSummaryCard"
+    id="room-summary-panel"
+    role="tabpanel"
+  >
+    <div
+      class="mx_BaseCard_header"
+      data-floating-ui-inert=""
+    >
+      <div
+        class="mx_BaseCard_header_spacer"
+      />
+      <button
+        aria-labelledby="«r0»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+        data-testid="base-card-close-button"
+        role="button"
+        style="--cpd-icon-button-size: 28px;"
+        tabindex="0"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_AutoHideScrollbar"
+      tabindex="-1"
+    >
+      <header
+        class="mx_RoomSummaryCard_container"
+        data-floating-ui-inert=""
+      >
+        <span
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="1"
+          data-testid="avatar-img"
+          data-type="round"
+          role="presentation"
+          style="--cpd-avatar-size: 80px;"
+        >
+          !
+        </span>
+        <h1
+          class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112 mx_RoomSummaryCard_roomName text-primary"
+          title="!room:domain.org"
+        >
+          !room:domain.org
+        </h1>
+        <div
+          class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_RoomSummaryCard_alias text-secondary"
+          title=""
+        />
+        <section
+          class="mx_Flex mx_RoomSummaryCard_badges"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
+              />
+            </svg>
+            Public room
+          </span>
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22q-.825 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412a2 2 0 0 1 .702-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 1 1-1.414 1.414l-.896-.896A1.94 1.94 0 0 1 18 22zm14-4.834V10q0-.825-.587-1.412A1.93 1.93 0 0 0 18 8h-1V6q0-2.075-1.463-3.537Q14.075 1 12 1T8.463 2.463a4.9 4.9 0 0 0-1.22 1.946L9 6.166V6q0-1.25.875-2.125A2.9 2.9 0 0 1 12 3q1.25 0 2.125.875T15 6v2h-4.166z"
+              />
+            </svg>
+            Not encrypted
+          </span>
+        </section>
+        <section
+          class="mx_Flex mx_RoomSummaryCard_topic"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <div
+            class="mx_Box mx_Box--flex"
+            style="--mx-box-flex: 1;"
+          >
+            <a
+              class="_link_1v5rz_8"
+              data-kind="primary"
+              data-size="medium"
+              rel="noreferrer noopener"
+            >
+              <p
+                class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+              >
+                Add topic
+              </p>
+            </a>
+          </div>
+        </section>
+      </header>
+      <div
+        class="_separator_7ckbw_8"
+        data-floating-ui-inert=""
+        data-kind="primary"
+        data-orientation="horizontal"
+        role="separator"
+      />
+      <div
+        aria-orientation="vertical"
+        role="menubar"
+      >
+        <div
+          aria-checked="false"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitemcheckbox"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004zM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Favourite
+          </span>
+          <div
+            class="_container_19o42_10"
+          >
+            <input
+              aria-hidden="true"
+              class="_input_19o42_24"
+              id="«r5»"
+              type="checkbox"
+            />
+            <div
+              class="_ui_19o42_34"
+            />
+          </div>
+        </div>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Invite
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.175 13.825Q10.35 15 12 15t2.825-1.175T16 11t-1.175-2.825T12 7 9.175 8.175 8 11t1.175 2.825m4.237-1.412A1.93 1.93 0 0 1 12 13q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 11q0-.825.588-1.412A1.93 1.93 0 0 1 12 9q.825 0 1.412.588Q14 10.175 14 11t-.588 1.412"
+            />
+            <path
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0"
+            />
+            <path
+              d="M16.23 18.792a13 13 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0q-.73.18-1.455.455a8 8 0 0 1-1.729-1.454q1.336-.618 2.709-.95A13.8 13.8 0 0 1 12 16q1.65 0 3.25.387 1.373.333 2.709.95a8 8 0 0 1-1.73 1.455"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            People
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M7 10a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 9q0-.424.287-.713A.97.97 0 0 1 7 8h10q.424 0 .712.287Q18 8.576 18 9t-.288.713A.97.97 0 0 1 17 10zm0 4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 13q0-.424.287-.713A.97.97 0 0 1 7 12h6q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 13 14z"
+            />
+            <path
+              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6zM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Threads
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          aria-controls="«r8»"
+          aria-describedby="«r8»"
+          aria-expanded="true"
+          aria-haspopup="dialog"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="primary"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                clip-rule="evenodd"
+                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857zM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744z"
+                fill-rule="evenodd"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Pinned messages
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+            >
+              0
+            </span>
+          </button>
+        </div>
+        <span
+          aria-hidden="true"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="-1"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <span
+          aria-owns="«ra»"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V4q0-.824.588-1.412A1.93 1.93 0 0 1 6 2h7.175a1.98 1.98 0 0 1 1.4.575l4.85 4.85q.275.275.425.638.15.361.15.762V20q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zm7-14V4H6v16h12V9h-4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 13 8"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Files
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M17.25 11.672a.9.9 0 0 1-.663-.282L12.61 7.413a.9.9 0 0 1-.282-.663q0-.381.282-.663l3.977-3.977a.9.9 0 0 1 .663-.282q.381 0 .663.282l3.977 3.977a.9.9 0 0 1 .282.663.9.9 0 0 1-.282.663l-3.977 3.977a.9.9 0 0 1-.663.282m2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475zM4 11a.97.97 0 0 1-.712-.287A.97.97 0 0 1 3 10V4q0-.424.288-.712A.97.97 0 0 1 4 3h6q.424 0 .713.288Q11 3.575 11 4v6q0 .424-.287.713A.97.97 0 0 1 10 11zm5-2V5H5v4zm5 12a.97.97 0 0 1-.713-.288A.97.97 0 0 1 13 20v-6q0-.424.287-.713A.97.97 0 0 1 14 13h6q.424 0 .712.287.288.288.288.713v6q0 .424-.288.712A.97.97 0 0 1 20 21zm5-2v-4h-4v4zM4 21a.97.97 0 0 1-.712-.288A.97.97 0 0 1 3 20v-6q0-.424.288-.713A.97.97 0 0 1 4 13h6q.424 0 .713.287.287.288.287.713v6q0 .424-.287.712A.97.97 0 0 1 10 21zm5-2v-4H5v4z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Extensions
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Copy link
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 10q.424 0 .712-.287A.97.97 0 0 0 17 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 8h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 9q0 .424.287.713.288.287.713.287zm0 6q.424 0 .712-.287A.97.97 0 0 0 17 15a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 14h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 15q0 .424.287.713.288.287.713.287zm-7-5q.825 0 1.412-.588Q11 9.826 11 9t-.588-1.412A1.93 1.93 0 0 0 9 7q-.825 0-1.412.588A1.93 1.93 0 0 0 7 9q0 .825.588 1.412Q8.175 11 9 11m0 6q.825 0 1.412-.587Q11 15.825 11 15t-.588-1.412A1.93 1.93 0 0 0 9 13q-.825 0-1.412.588A1.93 1.93 0 0 0 7 15q0 .824.588 1.413Q8.175 17 9 17m-4 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h14q.824 0 1.413.587Q21 4.176 21 5v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm0-2h14V5H5z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Polls
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V6.5q0-.375.125-.675t.325-.575l1.4-1.7q.2-.274.5-.412T6 3h12q.35 0 .65.138.3.137.5.412l1.4 1.7q.2.275.325.575T21 6.5V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm.4-15h13.2l-.85-1H6.25zM5 19h14V8H5zm7-1.425q.2 0 .375-.062a.9.9 0 0 0 .325-.213l2.6-2.6a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275l-.9.9V11a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 10a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 11v3.2l-.9-.9a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7l2.6 2.6q.15.15.325.212.175.063.375.063"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Export Chat
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0m-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0"
+            />
+            <path
+              d="M11.312 2h1.376A2.31 2.31 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.31 2.31 0 0 1 3.27 0l.973.974a2.31 2.31 0 0 1 0 3.269l-.177.177v.003a.13.13 0 0 0 .01.07.15.15 0 0 0 .047.063l.003.002h.247A2.31 2.31 0 0 1 22 11.312v1.376A2.31 2.31 0 0 1 19.688 15h-.247l-.003.002a.15.15 0 0 0-.047.064.13.13 0 0 0-.01.07v.002l.177.177a2.31 2.31 0 0 1 0 3.27l-.974.973a2.31 2.31 0 0 1-3.269 0l-.177-.177h-.003a.13.13 0 0 0-.07.01.15.15 0 0 0-.063.047l-.002.003v.247A2.31 2.31 0 0 1 12.688 22h-1.376A2.31 2.31 0 0 1 9 19.688v-.247l-.002-.003a.15.15 0 0 0-.064-.047.13.13 0 0 0-.07-.01h-.002l-.177.177a2.31 2.31 0 0 1-3.27 0l-.973-.974a2.31 2.31 0 0 1 0-3.269l.177-.177v-.003a.14.14 0 0 0-.01-.07.15.15 0 0 0-.047-.063L4.559 15h-.247A2.31 2.31 0 0 1 2 12.688v-1.376A2.31 2.31 0 0 1 4.312 9h.247l.003-.002a.15.15 0 0 0 .047-.064.14.14 0 0 0 .01-.07v-.002l-.177-.177a2.31 2.31 0 0 1 0-3.27l.974-.973a2.31 2.31 0 0 1 3.269 0l.177.177h.003a.14.14 0 0 0 .07-.01.15.15 0 0 0 .063-.047L9 4.559v-.247A2.31 2.31 0 0 1 11.312 2M11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.31.31 0 0 0-.441 0l-.974.974a.31.31 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.31.31 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.31.31 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.249.418l.181.181c.122.122.32.122.441 0l.973-.973a.31.31 0 0 0 0-.44l-.181-.183c-.627-.626-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.31.31 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.31.31 0 0 0 0-.441l-.973-.974a.31.31 0 0 0-.44 0l-.183.182c-.626.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.31.31 0 0 0 12.688 4h-1.376a.31.31 0 0 0-.312.312"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Settings
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <div
+          class="mx_RoomSummaryCard_bottomOptions"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Report room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+          <button
+            class="mx_RoomSummaryCard_leave _item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
+              />
+              <path
+                d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Leave room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
+<div>
+  <div
+    aria-labelledby="room-summary-panel-tab"
+    class="mx_BaseCard mx_RoomSummaryCard"
+    id="room-summary-panel"
+    role="tabpanel"
+  >
+    <div
+      class="mx_BaseCard_header"
+      data-floating-ui-inert=""
+    >
+      <div
+        class="mx_BaseCard_header_spacer"
+      />
+      <button
+        aria-labelledby="«rd»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+        data-testid="base-card-close-button"
+        role="button"
+        style="--cpd-icon-button-size: 28px;"
+        tabindex="0"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_AutoHideScrollbar"
+      tabindex="-1"
+    >
+      <header
+        class="mx_RoomSummaryCard_container"
+        data-floating-ui-inert=""
+      >
+        <span
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="1"
+          data-testid="avatar-img"
+          data-type="round"
+          role="presentation"
+          style="--cpd-avatar-size: 80px;"
+        >
+          !
+        </span>
+        <h1
+          class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112 mx_RoomSummaryCard_roomName text-primary"
+          title="!room:domain.org"
+        >
+          !room:domain.org
+        </h1>
+        <div
+          class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_RoomSummaryCard_alias text-secondary"
+          title=""
+        />
+        <section
+          class="mx_Flex mx_RoomSummaryCard_badges"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
+              />
+            </svg>
+            Public room
+          </span>
+          <span
+            class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
+            data-kind="grey"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22q-.825 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412a2 2 0 0 1 .702-.463L1.333 4.167a1 1 0 0 1 1.414-1.414L7 7.006v-.012l13 13v.012l1.247 1.247a1 1 0 1 1-1.414 1.414l-.896-.896A1.94 1.94 0 0 1 18 22zm14-4.834V10q0-.825-.587-1.412A1.93 1.93 0 0 0 18 8h-1V6q0-2.075-1.463-3.537Q14.075 1 12 1T8.463 2.463a4.9 4.9 0 0 0-1.22 1.946L9 6.166V6q0-1.25.875-2.125A2.9 2.9 0 0 1 12 3q1.25 0 2.125.875T15 6v2h-4.166z"
+              />
+            </svg>
+            Not encrypted
+          </span>
+        </section>
+        <section
+          class="mx_Flex mx_RoomSummaryCard_topic"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+        >
+          <div
+            class="mx_Box mx_RoomSummaryCard_topic_container mx_Box--flex"
+            style="--mx-box-flex: 1;"
+          >
+            <p
+              class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+            >
+              <span
+                dir="auto"
+              >
+                This is the room's topic.
+              </span>
+            </p>
+            <button
+              class="_icon-button_m2erp_8 mx_RoomSummaryCard_topic_chevron"
+              role="button"
+              style="--cpd-icon-button-size: 24px;"
+              tabindex="0"
+            >
+              <div
+                class="_indicator-icon_zr2a0_17"
+                style="--cpd-icon-button-size: 100%;"
+              >
+                <svg
+                  fill="currentColor"
+                  height="1em"
+                  viewBox="0 0 24 24"
+                  width="1em"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                  />
+                </svg>
+              </div>
+            </button>
+          </div>
+          <div
+            class="mx_Box mx_RoomSummaryCard_topic_edit mx_Box--flex"
+            style="--mx-box-flex: 1;"
+          >
+            <a
+              class="_link_1v5rz_8"
+              data-kind="primary"
+              data-size="medium"
+              rel="noreferrer noopener"
+            >
+              <p
+                class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+              >
+                Edit
+              </p>
+            </a>
+          </div>
+        </section>
+      </header>
+      <div
+        class="_separator_7ckbw_8"
+        data-floating-ui-inert=""
+        data-kind="primary"
+        data-orientation="horizontal"
+        role="separator"
+      />
+      <div
+        aria-orientation="vertical"
+        role="menubar"
+      >
+        <div
+          aria-checked="false"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitemcheckbox"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M13.905 9.378 12 5.52l-1.905 3.86-4.259.618 3.082 3.004-.727 4.242L12 15.24l3.81 2.003-.728-4.242 3.082-3.004zM8.767 7.55l2.336-4.733a1 1 0 0 1 1.794 0l2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Favourite
+          </span>
+          <div
+            class="_container_19o42_10"
+          >
+            <input
+              aria-hidden="true"
+              class="_input_19o42_24"
+              id="«ri»"
+              type="checkbox"
+            />
+            <div
+              class="_ui_19o42_34"
+            />
+          </div>
+        </div>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Invite
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.175 13.825Q10.35 15 12 15t2.825-1.175T16 11t-1.175-2.825T12 7 9.175 8.175 8 11t1.175 2.825m4.237-1.412A1.93 1.93 0 0 1 12 13q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 11q0-.825.588-1.412A1.93 1.93 0 0 1 12 9q.825 0 1.412.588Q14 10.175 14 11t-.588 1.412"
+            />
+            <path
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0"
+            />
+            <path
+              d="M16.23 18.792a13 13 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0q-.73.18-1.455.455a8 8 0 0 1-1.729-1.454q1.336-.618 2.709-.95A13.8 13.8 0 0 1 12 16q1.65 0 3.25.387 1.373.333 2.709.95a8 8 0 0 1-1.73 1.455"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            People
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M7 10a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 9q0-.424.287-.713A.97.97 0 0 1 7 8h10q.424 0 .712.287Q18 8.576 18 9t-.288.713A.97.97 0 0 1 17 10zm0 4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 13q0-.424.287-.713A.97.97 0 0 1 7 12h6q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 13 14z"
+            />
+            <path
+              d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6zM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Threads
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          aria-controls="«rl»"
+          aria-describedby="«rl»"
+          aria-expanded="true"
+          aria-haspopup="dialog"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="primary"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                clip-rule="evenodd"
+                d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857zM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744z"
+                fill-rule="evenodd"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Pinned messages
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
+            >
+              0
+            </span>
+          </button>
+        </div>
+        <span
+          aria-hidden="true"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="-1"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <span
+          aria-owns="«rn»"
+          data-floating-ui-inert=""
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+        />
+        <span
+          data-floating-ui-focus-guard=""
+          data-type="outside"
+          role="button"
+          style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
+          tabindex="0"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V4q0-.824.588-1.412A1.93 1.93 0 0 1 6 2h7.175a1.98 1.98 0 0 1 1.4.575l4.85 4.85q.275.275.425.638.15.361.15.762V20q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zm7-14V4H6v16h12V9h-4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 13 8"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Files
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M17.25 11.672a.9.9 0 0 1-.663-.282L12.61 7.413a.9.9 0 0 1-.282-.663q0-.381.282-.663l3.977-3.977a.9.9 0 0 1 .663-.282q.381 0 .663.282l3.977 3.977a.9.9 0 0 1 .282.663.9.9 0 0 1-.282.663l-3.977 3.977a.9.9 0 0 1-.663.282m2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475zM4 11a.97.97 0 0 1-.712-.287A.97.97 0 0 1 3 10V4q0-.424.288-.712A.97.97 0 0 1 4 3h6q.424 0 .713.288Q11 3.575 11 4v6q0 .424-.287.713A.97.97 0 0 1 10 11zm5-2V5H5v4zm5 12a.97.97 0 0 1-.713-.288A.97.97 0 0 1 13 20v-6q0-.424.287-.713A.97.97 0 0 1 14 13h6q.424 0 .712.287.288.288.288.713v6q0 .424-.288.712A.97.97 0 0 1 20 21zm5-2v-4h-4v4zM4 21a.97.97 0 0 1-.712-.288A.97.97 0 0 1 3 20v-6q0-.424.288-.713A.97.97 0 0 1 4 13h6q.424 0 .713.287.287.288.287.713v6q0 .424-.287.712A.97.97 0 0 1 10 21zm5-2v-4H5v4z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Extensions
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Copy link
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 10q.424 0 .712-.287A.97.97 0 0 0 17 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 8h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 9q0 .424.287.713.288.287.713.287zm0 6q.424 0 .712-.287A.97.97 0 0 0 17 15a.97.97 0 0 0-.288-.713A.97.97 0 0 0 16 14h-3a.97.97 0 0 0-.713.287A.97.97 0 0 0 12 15q0 .424.287.713.288.287.713.287zm-7-5q.825 0 1.412-.588Q11 9.826 11 9t-.588-1.412A1.93 1.93 0 0 0 9 7q-.825 0-1.412.588A1.93 1.93 0 0 0 7 9q0 .825.588 1.412Q8.175 11 9 11m0 6q.825 0 1.412-.587Q11 15.825 11 15t-.588-1.412A1.93 1.93 0 0 0 9 13q-.825 0-1.412.588A1.93 1.93 0 0 0 7 15q0 .824.588 1.413Q8.175 17 9 17m-4 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h14q.824 0 1.413.587Q21 4.176 21 5v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm0-2h14V5H5z"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Polls
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V6.5q0-.375.125-.675t.325-.575l1.4-1.7q.2-.274.5-.412T6 3h12q.35 0 .65.138.3.137.5.412l1.4 1.7q.2.275.325.575T21 6.5V19q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm.4-15h13.2l-.85-1H6.25zM5 19h14V8H5zm7-1.425q.2 0 .375-.062a.9.9 0 0 0 .325-.213l2.6-2.6a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275l-.9.9V11a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 10a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 11v3.2l-.9-.9a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7l2.6 2.6q.15.15.325.212.175.063.375.063"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Export Chat
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <button
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          role="menuitem"
+        >
+          <svg
+            aria-hidden="true"
+            class="_icon_dyt4i_50"
+            fill="currentColor"
+            height="24"
+            viewBox="0 0 24 24"
+            width="24"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0m-2 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0"
+            />
+            <path
+              d="M11.312 2h1.376A2.31 2.31 0 0 1 15 4.312v.247l.002.003c.01.014.031.033.064.047.03.013.056.013.07.01h.002l.177-.177a2.31 2.31 0 0 1 3.27 0l.973.974a2.31 2.31 0 0 1 0 3.269l-.177.177v.003a.13.13 0 0 0 .01.07.15.15 0 0 0 .047.063l.003.002h.247A2.31 2.31 0 0 1 22 11.312v1.376A2.31 2.31 0 0 1 19.688 15h-.247l-.003.002a.15.15 0 0 0-.047.064.13.13 0 0 0-.01.07v.002l.177.177a2.31 2.31 0 0 1 0 3.27l-.974.973a2.31 2.31 0 0 1-3.269 0l-.177-.177h-.003a.13.13 0 0 0-.07.01.15.15 0 0 0-.063.047l-.002.003v.247A2.31 2.31 0 0 1 12.688 22h-1.376A2.31 2.31 0 0 1 9 19.688v-.247l-.002-.003a.15.15 0 0 0-.064-.047.13.13 0 0 0-.07-.01h-.002l-.177.177a2.31 2.31 0 0 1-3.27 0l-.973-.974a2.31 2.31 0 0 1 0-3.269l.177-.177v-.003a.14.14 0 0 0-.01-.07.15.15 0 0 0-.047-.063L4.559 15h-.247A2.31 2.31 0 0 1 2 12.688v-1.376A2.31 2.31 0 0 1 4.312 9h.247l.003-.002a.15.15 0 0 0 .047-.064.14.14 0 0 0 .01-.07v-.002l-.177-.177a2.31 2.31 0 0 1 0-3.27l.974-.973a2.31 2.31 0 0 1 3.269 0l.177.177h.003a.14.14 0 0 0 .07-.01.15.15 0 0 0 .063-.047L9 4.559v-.247A2.31 2.31 0 0 1 11.312 2M11 4.312v.257c0 .893-.59 1.593-1.299 1.887-.716.297-1.622.21-2.248-.418l-.182-.182a.31.31 0 0 0-.441 0l-.974.974a.31.31 0 0 0 0 .44l.182.183c.627.626.715 1.531.418 2.248C6.162 10.41 5.462 11 4.569 11h-.257a.31.31 0 0 0-.312.312v1.376c0 .172.14.312.312.312h.257c.893 0 1.593.59 1.887 1.299.297.716.21 1.622-.418 2.248l-.182.182a.31.31 0 0 0 0 .441l.974.973a.31.31 0 0 0 .44 0l.183-.181c.626-.627 1.532-.715 2.248-.418.709.294 1.299.994 1.299 1.887v.257c0 .172.14.312.312.312h1.376c.172 0 .312-.14.312-.312v-.257c0-.893.59-1.593 1.299-1.887.716-.297 1.622-.21 2.249.418l.181.181c.122.122.32.122.441 0l.973-.973a.31.31 0 0 0 0-.44l-.181-.183c-.627-.626-.715-1.532-.418-2.248.294-.709.994-1.299 1.887-1.299h.257c.172 0 .312-.14.312-.312v-1.376a.31.31 0 0 0-.312-.312h-.257c-.893 0-1.593-.59-1.887-1.299-.297-.717-.21-1.622.418-2.248l.181-.182a.31.31 0 0 0 0-.441l-.973-.974a.31.31 0 0 0-.44 0l-.183.182c-.626.627-1.532.715-2.248.418C13.59 6.162 13 5.462 13 4.569v-.257A.31.31 0 0 0 12.688 4h-1.376a.31.31 0 0 0-.312.312"
+            />
+          </svg>
+          <span
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+          >
+            Settings
+          </span>
+          <svg
+            aria-hidden="true"
+            class="_nav-hint_dyt4i_59"
+            fill="currentColor"
+            height="24"
+            viewBox="8 0 8 24"
+            width="8"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+            />
+          </svg>
+        </button>
+        <div
+          class="_separator_7ckbw_8"
+          data-floating-ui-inert=""
+          data-kind="primary"
+          data-orientation="horizontal"
+          role="separator"
+        />
+        <div
+          class="mx_RoomSummaryCard_bottomOptions"
+          data-floating-ui-inert=""
+        >
+          <button
+            class="_item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Report room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+          <button
+            class="mx_RoomSummaryCard_leave _item_dyt4i_8 _interactive_dyt4i_26"
+            data-kind="critical"
+            role="menuitem"
+          >
+            <svg
+              aria-hidden="true"
+              class="_icon_dyt4i_50"
+              fill="currentColor"
+              height="24"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
+              />
+              <path
+                d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
+              />
+            </svg>
+            <span
+              class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
+            >
+              Leave room
+            </span>
+            <svg
+              aria-hidden="true"
+              class="_nav-hint_dyt4i_59"
+              fill="currentColor"
+              height="24"
+              viewBox="8 0 8 24"
+              width="8"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+`;
diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap
index 17440813897f47ee261a248ad8bd8f15ce1709b8..654db50e0f4f774ecf6ec6fdec28cbe6a6cbe7c3 100644
--- a/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap
+++ b/test/unit-tests/components/views/right_panel/__snapshots__/UserInfo-test.tsx.snap
@@ -1,74 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`<DeviceItem /> ambiguous display name 1`] = `
-<div>
-  <div
-    aria-label="my display name (deviceId)"
-    class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
-    role="button"
-    tabindex="0"
-  >
-    <div
-      class="mx_E2EIcon mx_E2EIcon_normal"
-    />
-    <div
-      class="mx_UserInfo_device_name"
-    >
-      my display name (deviceId)
-    </div>
-    <div
-      class="mx_UserInfo_device_trusted"
-    />
-  </div>
-</div>
-`;
-
-exports[`<DeviceItem /> with display name 1`] = `
-<div>
-  <div
-    aria-label="deviceName"
-    class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
-    role="button"
-    tabindex="0"
-  >
-    <div
-      class="mx_E2EIcon mx_E2EIcon_normal"
-    />
-    <div
-      class="mx_UserInfo_device_name"
-    >
-      deviceName
-    </div>
-    <div
-      class="mx_UserInfo_device_trusted"
-    />
-  </div>
-</div>
-`;
-
-exports[`<DeviceItem /> without display name 1`] = `
-<div>
-  <div
-    aria-label="deviceId"
-    class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
-    role="button"
-    tabindex="0"
-  >
-    <div
-      class="mx_E2EIcon mx_E2EIcon_normal"
-    />
-    <div
-      class="mx_UserInfo_device_name"
-    >
-      deviceId
-    </div>
-    <div
-      class="mx_UserInfo_device_trusted"
-    />
-  </div>
-</div>
-`;
-
 exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
 <div>
   <div
@@ -81,22 +12,22 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Profile
         </p>
       </div>
       <button
-        aria-labelledby=":r74:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r6i»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -107,7 +38,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -129,7 +60,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             <button
               aria-label="Profile picture"
               aria-live="off"
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -142,19 +73,19 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
         </div>
       </div>
       <div
-        class="mx_UserInfo_container"
+        class="mx_UserInfo_container mx_UserInfo_header"
       >
         <div
           class="mx_Flex mx_UserInfo_profile"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
         >
           <h1
-            class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+            class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
             dir="auto"
           >
             <div
-              class="mx_Flex"
-              style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
+              class="mx_Flex mx_UserInfo_profile_name"
+              style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
             >
               @user:example.com
             </div>
@@ -165,7 +96,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             Unknown
           </div>
           <p
-            class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_UserInfo_profile_mxid"
+            class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
           >
             <div
               class="mx_CopyableText"
@@ -180,58 +111,30 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             </div>
           </p>
         </div>
-      </div>
-      <div
-        class="mx_UserInfo_container"
-      >
-        <h2>
-          Security
-        </h2>
-        <p>
-          Messages in this room are not end-to-end encrypted.
-        </p>
         <div
-          class="mx_UserInfo_container_verifyButton"
+          class="mx_Flex mx_UserInfo_verification"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
         >
-          <div
-            class="mx_AccessibleButton mx_UserInfo_field mx_UserInfo_verifyButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
-            role="button"
-            tabindex="0"
+          <p
+            class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
           >
-            Verify
-          </div>
-        </div>
-        <div
-          class="mx_UserInfo_devices"
-        >
-          <div />
-          <div>
-            <div
-              class="mx_AccessibleButton mx_UserInfo_expand mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
-              role="button"
-              tabindex="0"
-            >
-              <div
-                class="mx_E2EIcon mx_E2EIcon_normal"
-              />
-              <div>
-                1 session
-              </div>
-            </div>
-          </div>
+            (
+            User verification unavailable
+            )
+          </p>
         </div>
       </div>
       <div
         class="mx_UserInfo_container"
       >
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="primary"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -239,17 +142,17 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
+              d="m1.5 21.25 1.45-4.95a10.2 10.2 0 0 1-.712-2.1A10.2 10.2 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22q-1.125 0-2.2-.238a10.2 10.2 0 0 1-2.1-.712L2.75 22.5a.94.94 0 0 1-1-.25.94.94 0 0 1-.25-1m2.45-1.2 3.2-.95a1 1 0 0 1 .275-.062q.15-.013.275-.013.225 0 .438.038.212.036.412.137a7.4 7.4 0 0 0 1.675.6Q11.1 20 12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12q0 .9.2 1.775t.6 1.675q.176.325.188.688t-.088.712z"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Send message
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -257,19 +160,19 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35 _disabled_8j2l6_118"
+          class="_item_dyt4i_8 _interactive_dyt4i_26 _disabled_dyt4i_118"
           data-kind="primary"
           disabled=""
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -277,17 +180,17 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Jump to read receipt
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -295,18 +198,18 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="primary"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -314,17 +217,17 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 16a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 15V7.85L9.125 9.725c-.2.2-.433.3-.7.3-.267 0-.508-.108-.725-.325a.93.93 0 0 1-.288-.712A.977.977 0 0 1 7.7 8.3l3.6-3.6c.1-.1.208-.17.325-.212.117-.042.242-.063.375-.063s.258.02.375.063a.877.877 0 0 1 .325.212l3.6 3.6c.2.2.296.438.287.713a.977.977 0 0 1-.287.687c-.2.2-.438.304-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15c0 .283-.096.52-.287.713A.968.968 0 0 1 12 16Zm-6 4c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.968.968 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
+              d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Share profile
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -332,7 +235,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
@@ -341,13 +244,13 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
         class="mx_UserInfo_container"
       >
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="critical"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -355,17 +258,17 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Zm0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-.9-.146-1.767-.438-2.6A7.951 7.951 0 0 0 18.3 7.1L7.1 18.3c.7.55 1.467.97 2.3 1.262.833.292 1.7.438 2.6.438Zm-6.3-3.1L16.9 5.7a7.95 7.95 0 0 0-2.3-1.263A7.813 7.813 0 0 0 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .9.146 1.767.438 2.6A7.95 7.95 0 0 0 5.7 16.9Z"
+              d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Ignore
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -373,7 +276,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
@@ -395,22 +298,22 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Profile
         </p>
       </div>
       <button
-        aria-labelledby=":r9a:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r6s»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -421,7 +324,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -443,7 +346,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             <button
               aria-label="Profile picture"
               aria-live="off"
-              class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+              class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
               data-color="3"
               data-testid="avatar-img"
               data-type="round"
@@ -456,19 +359,19 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
         </div>
       </div>
       <div
-        class="mx_UserInfo_container"
+        class="mx_UserInfo_container mx_UserInfo_header"
       >
         <div
           class="mx_Flex mx_UserInfo_profile"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
         >
           <h1
-            class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+            class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
             dir="auto"
           >
             <div
-              class="mx_Flex"
-              style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
+              class="mx_Flex mx_UserInfo_profile_name"
+              style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
             >
               @user:example.com
             </div>
@@ -479,7 +382,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             Unknown
           </div>
           <p
-            class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 mx_UserInfo_profile_mxid"
+            class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
           >
             <div
               class="mx_CopyableText"
@@ -494,58 +397,30 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             </div>
           </p>
         </div>
-      </div>
-      <div
-        class="mx_UserInfo_container"
-      >
-        <h2>
-          Security
-        </h2>
-        <p>
-          Messages in this room are not end-to-end encrypted.
-        </p>
         <div
-          class="mx_UserInfo_container_verifyButton"
+          class="mx_Flex mx_UserInfo_verification"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
         >
-          <div
-            class="mx_AccessibleButton mx_UserInfo_field mx_UserInfo_verifyButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
-            role="button"
-            tabindex="0"
+          <p
+            class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
           >
-            Verify
-          </div>
-        </div>
-        <div
-          class="mx_UserInfo_devices"
-        >
-          <div />
-          <div>
-            <div
-              class="mx_AccessibleButton mx_UserInfo_expand mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
-              role="button"
-              tabindex="0"
-            >
-              <div
-                class="mx_E2EIcon mx_E2EIcon_normal"
-              />
-              <div>
-                1 session
-              </div>
-            </div>
-          </div>
+            (
+            User verification unavailable
+            )
+          </p>
         </div>
       </div>
       <div
         class="mx_UserInfo_container"
       >
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="primary"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -553,17 +428,17 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
+              d="m1.5 21.25 1.45-4.95a10.2 10.2 0 0 1-.712-2.1A10.2 10.2 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22q-1.125 0-2.2-.238a10.2 10.2 0 0 1-2.1-.712L2.75 22.5a.94.94 0 0 1-1-.25.94.94 0 0 1-.25-1m2.45-1.2 3.2-.95a1 1 0 0 1 .275-.062q.15-.013.275-.013.225 0 .438.038.212.036.412.137a7.4 7.4 0 0 0 1.675.6Q11.1 20 12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12q0 .9.2 1.775t.6 1.675q.176.325.188.688t-.088.712z"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Send message
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -571,19 +446,19 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35 _disabled_8j2l6_118"
+          class="_item_dyt4i_8 _interactive_dyt4i_26 _disabled_dyt4i_118"
           data-kind="primary"
           disabled=""
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -591,17 +466,17 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Jump to read receipt
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -609,18 +484,18 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="primary"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -628,17 +503,17 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 16a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 15V7.85L9.125 9.725c-.2.2-.433.3-.7.3-.267 0-.508-.108-.725-.325a.93.93 0 0 1-.288-.712A.977.977 0 0 1 7.7 8.3l3.6-3.6c.1-.1.208-.17.325-.212.117-.042.242-.063.375-.063s.258.02.375.063a.877.877 0 0 1 .325.212l3.6 3.6c.2.2.296.438.287.713a.977.977 0 0 1-.287.687c-.2.2-.438.304-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15c0 .283-.096.52-.287.713A.968.968 0 0 1 12 16Zm-6 4c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 18v-2c0-.283.096-.52.287-.713A.968.968 0 0 1 5 15c.283 0 .52.096.713.287.191.192.287.43.287.713v2h12v-2a.97.97 0 0 1 .288-.713A.968.968 0 0 1 19 15a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v2c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 20H6Z"
+              d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Share profile
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -646,7 +521,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
@@ -655,13 +530,13 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
         class="mx_UserInfo_container"
       >
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="critical"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -669,17 +544,17 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
+              d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Deactivate user
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -687,7 +562,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
@@ -696,13 +571,13 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
         class="mx_UserInfo_container"
       >
         <button
-          class="_item_8j2l6_17 _interactive_8j2l6_35"
+          class="_item_dyt4i_8 _interactive_dyt4i_26"
           data-kind="critical"
           role="button"
         >
           <svg
             aria-hidden="true"
-            class="_icon_8j2l6_43"
+            class="_icon_dyt4i_50"
             fill="currentColor"
             height="24"
             viewBox="0 0 24 24"
@@ -710,17 +585,17 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Zm0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-.9-.146-1.767-.438-2.6A7.951 7.951 0 0 0 18.3 7.1L7.1 18.3c.7.55 1.467.97 2.3 1.262.833.292 1.7.438 2.6.438Zm-6.3-3.1L16.9 5.7a7.95 7.95 0 0 0-2.3-1.263A7.813 7.813 0 0 0 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .9.146 1.767.438 2.6A7.95 7.95 0 0 0 5.7 16.9Z"
+              d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
             />
           </svg>
           <span
-            class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+            class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
           >
             Ignore
           </span>
           <svg
             aria-hidden="true"
-            class="_nav-hint_8j2l6_59"
+            class="_nav-hint_dyt4i_59"
             fill="currentColor"
             height="24"
             viewBox="8 0 8 24"
@@ -728,7 +603,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+              d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
             />
           </svg>
         </button>
@@ -737,3 +612,270 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
   </div>
 </div>
 `;
+
+exports[`<UserInfoHeader /> renders verification unavailable message 1`] = `
+<div>
+  <div
+    class="mx_UserInfo_avatar"
+  >
+    <div
+      class="mx_UserInfo_avatar_transition"
+    >
+      <div
+        class="mx_UserInfo_avatar_transition_child"
+      >
+        <button
+          aria-label="Profile picture"
+          aria-live="off"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="3"
+          data-testid="avatar-img"
+          data-type="round"
+          role="button"
+          style="--cpd-avatar-size: 120px;"
+          title="customUserIdentifier"
+        >
+          u
+        </button>
+      </div>
+    </div>
+  </div>
+  <div
+    class="mx_UserInfo_container mx_UserInfo_header"
+  >
+    <div
+      class="mx_Flex mx_UserInfo_profile"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+        dir="auto"
+      >
+        <div
+          class="mx_Flex mx_UserInfo_profile_name"
+          style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+        >
+          @user:example.com
+        </div>
+      </h1>
+      <div
+        class="mx_PresenceLabel mx_UserInfo_profileStatus"
+      >
+        Unknown
+      </div>
+      <p
+        class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
+      >
+        <div
+          class="mx_CopyableText"
+        >
+          customUserIdentifier
+          <div
+            aria-label="Copy"
+            class="mx_AccessibleButton mx_CopyableText_copyButton"
+            role="button"
+            tabindex="0"
+          />
+        </div>
+      </p>
+    </div>
+    <div
+      class="mx_Flex mx_UserInfo_verification"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <p
+        class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
+      >
+        (
+        User verification unavailable
+        )
+      </p>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`<UserInfoHeader /> renders verified badge when user is verified 1`] = `
+<div>
+  <div
+    class="mx_UserInfo_avatar"
+  >
+    <div
+      class="mx_UserInfo_avatar_transition"
+    >
+      <div
+        class="mx_UserInfo_avatar_transition_child"
+      >
+        <button
+          aria-label="Profile picture"
+          aria-live="off"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="3"
+          data-testid="avatar-img"
+          data-type="round"
+          role="button"
+          style="--cpd-avatar-size: 120px;"
+          title="customUserIdentifier"
+        >
+          u
+        </button>
+      </div>
+    </div>
+  </div>
+  <div
+    class="mx_UserInfo_container mx_UserInfo_header"
+  >
+    <div
+      class="mx_Flex mx_UserInfo_profile"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+        dir="auto"
+      >
+        <div
+          class="mx_Flex mx_UserInfo_profile_name"
+          style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+        >
+          @user:example.com
+        </div>
+      </h1>
+      <div
+        class="mx_PresenceLabel mx_UserInfo_profileStatus"
+      >
+        Unknown
+      </div>
+      <p
+        class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
+      >
+        <div
+          class="mx_CopyableText"
+        >
+          customUserIdentifier
+          <div
+            aria-label="Copy"
+            class="mx_AccessibleButton mx_CopyableText_copyButton"
+            role="button"
+            tabindex="0"
+          />
+        </div>
+      </p>
+    </div>
+    <div
+      class="mx_Flex mx_UserInfo_verification"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <span
+        class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8 mx_UserInfo_verified_badge"
+        data-kind="green"
+      >
+        <svg
+          class="mx_UserInfo_verified_icon"
+          fill="currentColor"
+          height="16px"
+          viewBox="0 0 24 24"
+          width="16px"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M8.15 21.75 6.7 19.3l-2.75-.6a.94.94 0 0 1-.6-.387.93.93 0 0 1-.175-.688L3.45 14.8l-1.875-2.15a.93.93 0 0 1-.25-.65q0-.375.25-.65L3.45 9.2l-.275-2.825a.93.93 0 0 1 .175-.687.94.94 0 0 1 .6-.388l2.75-.6 1.45-2.45a.98.98 0 0 1 .55-.437.97.97 0 0 1 .7.037l2.6 1.1 2.6-1.1a.97.97 0 0 1 .7-.038q.35.112.55.438L17.3 4.7l2.75.6q.375.075.6.388.225.312.175.687L20.55 9.2l1.875 2.15q.25.275.25.65t-.25.65L20.55 14.8l.275 2.825a.93.93 0 0 1-.175.688.94.94 0 0 1-.6.387l-2.75.6-1.45 2.45a.98.98 0 0 1-.55.438.97.97 0 0 1-.7-.038l-2.6-1.1-2.6 1.1a.97.97 0 0 1-.7.038.98.98 0 0 1-.55-.438m2.8-9.05L9.5 11.275A.93.93 0 0 0 8.812 11q-.412 0-.712.3a.95.95 0 0 0-.275.7q0 .425.275.7l2.15 2.15q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.287-.7a1.06 1.06 0 0 0-.287-.7 1.02 1.02 0 0 0-.713-.312.93.93 0 0 0-.712.287z"
+          />
+        </svg>
+        <p
+          class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 mx_UserInfo_verified_label"
+        >
+          Verified
+        </p>
+      </span>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`<UserInfoHeader /> renders verify button 1`] = `
+<div>
+  <div
+    class="mx_UserInfo_avatar"
+  >
+    <div
+      class="mx_UserInfo_avatar_transition"
+    >
+      <div
+        class="mx_UserInfo_avatar_transition_child"
+      >
+        <button
+          aria-label="Profile picture"
+          aria-live="off"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+          data-color="3"
+          data-testid="avatar-img"
+          data-type="round"
+          role="button"
+          style="--cpd-avatar-size: 120px;"
+          title="customUserIdentifier"
+        >
+          u
+        </button>
+      </div>
+    </div>
+  </div>
+  <div
+    class="mx_UserInfo_container mx_UserInfo_header"
+  >
+    <div
+      class="mx_Flex mx_UserInfo_profile"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+        dir="auto"
+      >
+        <div
+          class="mx_Flex mx_UserInfo_profile_name"
+          style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+        >
+          @user:example.com
+        </div>
+      </h1>
+      <div
+        class="mx_PresenceLabel mx_UserInfo_profileStatus"
+      >
+        Unknown
+      </div>
+      <p
+        class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
+      >
+        <div
+          class="mx_CopyableText"
+        >
+          customUserIdentifier
+          <div
+            aria-label="Copy"
+            class="mx_AccessibleButton mx_CopyableText_copyButton"
+            role="button"
+            tabindex="0"
+          />
+        </div>
+      </p>
+    </div>
+    <div
+      class="mx_Flex mx_UserInfo_verification"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <div
+        class="mx_UserInfo_container_verifyButton"
+      >
+        <button
+          class="_button_vczzf_8 mx_UserInfo_verify_button"
+          data-kind="tertiary"
+          data-size="sm"
+          role="button"
+          tabindex="0"
+        >
+          Verify User
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+`;
diff --git a/test/unit-tests/components/views/room_settings/RoomProfileSettings-test.tsx b/test/unit-tests/components/views/room_settings/RoomProfileSettings-test.tsx
index a6af82d94b85905620257dab11a170c226f6741b..0acb182dba33e38440310ba4de2acb0251f4a4fa 100644
--- a/test/unit-tests/components/views/room_settings/RoomProfileSettings-test.tsx
+++ b/test/unit-tests/components/views/room_settings/RoomProfileSettings-test.tsx
@@ -9,7 +9,7 @@ import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 
 import { mkStubRoom, stubClient } from "../../../../test-utils";
 import RoomProfileSettings from "../../../../../src/components/views/room_settings/RoomProfileSettings";
diff --git a/test/unit-tests/components/views/room_settings/UrlPreviewSettings-test.tsx b/test/unit-tests/components/views/room_settings/UrlPreviewSettings-test.tsx
index fa4f34aa34b18cfec2db8fb105227b29b7ea5b3c..c392788e387cd16394349041723ad5f16a1dd0ce 100644
--- a/test/unit-tests/components/views/room_settings/UrlPreviewSettings-test.tsx
+++ b/test/unit-tests/components/views/room_settings/UrlPreviewSettings-test.tsx
@@ -6,7 +6,7 @@
  */
 
 import React from "react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { render, screen } from "jest-matrix-react";
 import { waitFor } from "@testing-library/dom";
 
diff --git a/test/unit-tests/components/views/room_settings/__snapshots__/UrlPreviewSettings-test.tsx.snap b/test/unit-tests/components/views/room_settings/__snapshots__/UrlPreviewSettings-test.tsx.snap
index d3a7eb4ad4c7896e105e67634582173bb8f596c1..017f4bc2bbb99635eff84b1184258823fd62b378 100644
--- a/test/unit-tests/components/views/room_settings/__snapshots__/UrlPreviewSettings-test.tsx.snap
+++ b/test/unit-tests/components/views/room_settings/__snapshots__/UrlPreviewSettings-test.tsx.snap
@@ -216,7 +216,7 @@ exports[`UrlPreviewSettings should display the correct preview when the setting
       class="mx_SettingsFieldset_content"
     >
       <svg
-        class="_icon_1ye7b_27"
+        class="_icon_11k6c_18"
         fill="currentColor"
         height="1em"
         style="width: 20px; height: 20px;"
@@ -226,7 +226,7 @@ exports[`UrlPreviewSettings should display the correct preview when the setting
       >
         <path
           clip-rule="evenodd"
-          d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
+          d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2"
           fill-rule="evenodd"
         />
       </svg>
diff --git a/test/unit-tests/components/views/rooms/AppsDrawer-test.tsx b/test/unit-tests/components/views/rooms/AppsDrawer-test.tsx
index 71e69bd91bc7e195effbad870ef96e7b5665d3fa..14c8bde3a2ac3a15f66ca199079616a1e02fd90a 100644
--- a/test/unit-tests/components/views/rooms/AppsDrawer-test.tsx
+++ b/test/unit-tests/components/views/rooms/AppsDrawer-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
 import { render } from "jest-matrix-react";
 
 import { stubClient } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/rooms/BasicMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/BasicMessageComposer-test.tsx
index b8864624e59e7fc009217ca70816477b8cbac735..798f9481144afda30bddc21e7149ea19187e0e7b 100644
--- a/test/unit-tests/components/views/rooms/BasicMessageComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/BasicMessageComposer-test.tsx
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import BasicMessageComposer from "../../../../../src/components/views/rooms/BasicMessageComposer";
 import * as TestUtils from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/rooms/EditMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/EditMessageComposer-test.tsx
index cdc961f73c37905f785340685d03aabb3078743a..86dd8566d1190d914cc685832a2901c5561e80df 100644
--- a/test/unit-tests/components/views/rooms/EditMessageComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/EditMessageComposer-test.tsx
@@ -10,7 +10,7 @@ import React from "react";
 import { fireEvent, render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { Room } from "matrix-js-sdk/src/matrix";
-import { ReplacementEvent, RoomMessageEventContent } from "matrix-js-sdk/src/types";
+import { type ReplacementEvent, type RoomMessageEventContent } from "matrix-js-sdk/src/types";
 
 import EditMessageComposerWithMatrixClient, {
     createEditContent,
@@ -27,9 +27,9 @@ import {
 import DocumentOffset from "../../../../../src/editor/offset";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import Autocompleter, { IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
+import Autocompleter, { type IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
 import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
@@ -432,8 +432,6 @@ describe("<EditMessageComposer/>", () => {
                     user_ids: [
                         // sender of event we replied to
                         originalEvent.getSender()!,
-                        // mentions from this event
-                        "@bob:server.org",
                     ],
                 },
             },
diff --git a/test/unit-tests/components/views/rooms/EventTile-test.tsx b/test/unit-tests/components/views/rooms/EventTile-test.tsx
index 057d93ee514e2faeeac06321dc9a7e19d2a6677b..b04633925a74f56fc149c4218e498bed52230ccc 100644
--- a/test/unit-tests/components/views/rooms/EventTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/EventTile-test.tsx
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 import {
     EventType,
-    IEventDecryptionResult,
-    MatrixClient,
+    type IEventDecryptionResult,
+    type MatrixClient,
     MatrixEvent,
     NotificationCountType,
     PendingEventOrdering,
@@ -20,15 +20,15 @@ import {
     TweakName,
 } from "matrix-js-sdk/src/matrix";
 import {
-    CryptoApi,
+    type CryptoApi,
     DecryptionFailureCode,
-    EventEncryptionInfo,
+    type EventEncryptionInfo,
     EventShieldColour,
     EventShieldReason,
 } from "matrix-js-sdk/src/crypto-api";
 import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
 
-import EventTile, { EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
+import EventTile, { type EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
@@ -37,7 +37,7 @@ import { mkThread } from "../../../../test-utils/threads";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import dis from "../../../../../src/dispatcher/dispatcher";
 import { Action } from "../../../../../src/dispatcher/actions";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import PinningUtils from "../../../../../src/utils/PinningUtils";
 import { Layout } from "../../../../../src/settings/enums/Layout";
 import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
@@ -305,7 +305,7 @@ describe("EventTile", () => {
             [EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
             [EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
             [EventShieldReason.SENT_IN_CLEAR, "Not encrypted"],
-            [EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity has changed"],
+            [EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity was reset"],
         ])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
             mxEvent = await mkEncryptedMatrixEvent({
                 plainContent: { msgtype: "m.text", body: "msg1" },
diff --git a/test/unit-tests/components/views/rooms/EventTile/EventTileThreadToolbar-test.tsx b/test/unit-tests/components/views/rooms/EventTile/EventTileThreadToolbar-test.tsx
index f08dbc4042d0e0f41c5a61798b2e9f8b131239a9..38785d5b457fa5a54659c9709e4093b23cbaa3c3 100644
--- a/test/unit-tests/components/views/rooms/EventTile/EventTileThreadToolbar-test.tsx
+++ b/test/unit-tests/components/views/rooms/EventTile/EventTileThreadToolbar-test.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { getByLabelText, render, RenderResult } from "jest-matrix-react";
+import { getByLabelText, render, type RenderResult } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 
 import { EventTileThreadToolbar } from "../../../../../../src/components/views/rooms/EventTile/EventTileThreadToolbar";
 
diff --git a/test/unit-tests/components/views/rooms/EventTile/__snapshots__/EventTileThreadToolbar-test.tsx.snap b/test/unit-tests/components/views/rooms/EventTile/__snapshots__/EventTileThreadToolbar-test.tsx.snap
index 4597bd83bc35f312bb2eb6d79eaa231dd8b57a1f..acd9d3baae4996c9c06f3d60fddd2b322c520f79 100644
--- a/test/unit-tests/components/views/rooms/EventTile/__snapshots__/EventTileThreadToolbar-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/EventTile/__snapshots__/EventTileThreadToolbar-test.tsx.snap
@@ -30,7 +30,7 @@ exports[`EventTileThreadToolbar renders 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+          d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
         />
       </svg>
     </div>
diff --git a/test/unit-tests/components/views/rooms/ExtraTile-test.tsx b/test/unit-tests/components/views/rooms/ExtraTile-test.tsx
index 6330b5bbc8f35641b8063429e29c14d78cc2d370..a1a957dbce305515db270ec21efe66835d249e38 100644
--- a/test/unit-tests/components/views/rooms/ExtraTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/ExtraTile-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { getByRole, render } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 
 import ExtraTile from "../../../../../src/components/views/rooms/ExtraTile";
 
diff --git a/test/unit-tests/components/views/rooms/RoomList-test.tsx b/test/unit-tests/components/views/rooms/LegacyRoomList-test.tsx
similarity index 96%
rename from test/unit-tests/components/views/rooms/RoomList-test.tsx
rename to test/unit-tests/components/views/rooms/LegacyRoomList-test.tsx
index 4ee68deb216a14fcccf5120f20c8addd1c73c55f..7e9751631bd48ad3cc301bb9f056f8b54df44b66 100644
--- a/test/unit-tests/components/views/rooms/RoomList-test.tsx
+++ b/test/unit-tests/components/views/rooms/LegacyRoomList-test.tsx
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
+import React, { type JSX } from "react";
 import { cleanup, queryByRole, render, screen, within } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { mocked } from "jest-mock";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
-import RoomList from "../../../../../src/components/views/rooms/RoomList";
+import LegacyRoomList from "../../../../../src/components/views/rooms/LegacyRoomList";
 import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
 import { MetaSpace } from "../../../../../src/stores/spaces";
 import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
@@ -26,7 +26,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import RoomListStore from "../../../../../src/stores/room-list/RoomListStore";
-import { ITagMap } from "../../../../../src/stores/room-list/algorithms/models";
+import { type ITagMap } from "../../../../../src/stores/room-list/algorithms/models";
 import { DefaultTagID } from "../../../../../src/stores/room-list/models";
 
 jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
@@ -40,14 +40,14 @@ const getDMRoomsForUserId = jest.fn();
 // @ts-ignore
 DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId };
 
-describe("RoomList", () => {
+describe("LegacyRoomList", () => {
     stubClient();
     const client = MatrixClientPeg.safeGet();
     const store = SpaceStore.instance;
 
-    function getComponent(props: Partial<RoomList["props"]> = {}): JSX.Element {
+    function getComponent(props: Partial<LegacyRoomList["props"]> = {}): JSX.Element {
         return (
-            <RoomList
+            <LegacyRoomList
                 onKeyDown={jest.fn()}
                 onFocus={jest.fn()}
                 onBlur={jest.fn()}
diff --git a/test/unit-tests/components/views/rooms/MessageComposer-test.tsx b/test/unit-tests/components/views/rooms/MessageComposer-test.tsx
index 895b5f09a77512220567547f662d598c0c450379..c12aa7e7e4537ff0c33b0b0c60c7d8eef5d7178e 100644
--- a/test/unit-tests/components/views/rooms/MessageComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/MessageComposer-test.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
-import { EventType, MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
+import React from "react";
+import { EventType, type MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
 import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { initOnce } from "@vector-im/matrix-wysiwyg";
@@ -24,7 +24,7 @@ import {
 import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
 import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
 import { LocalRoom } from "../../../../../src/models/LocalRoom";
diff --git a/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx b/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx
index fffb852ebfe05b317da38c775c8491bca68de434..634a72866d408652b03a2139f8eb98618a1a70fa 100644
--- a/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx
+++ b/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx
@@ -11,7 +11,7 @@ import { render, screen, waitFor } from "jest-matrix-react";
 
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
 import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
diff --git a/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx b/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx
index a5d598e1b4bae25b0ef90c2681097931a188249a..333e0e10206e0d2ff002cbdee0f6ad7d9f4eb1e7 100644
--- a/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx
+++ b/test/unit-tests/components/views/rooms/NewRoomIntro-test.tsx
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen } from "jest-matrix-react";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import { LocalRoom } from "../../../../../src/models/LocalRoom";
 import { filterConsole, mkRoomMemberJoinEvent, mkThirdPartyInviteEvent, stubClient } from "../../../../test-utils";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
-import { IRoomState } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../src/components/structures/RoomView";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import { DirectoryMember } from "../../../../../src/utils/direct-messages";
 import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
diff --git a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c54e16215e359e64921d48c3904a7e32b01ad25b
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+
+import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
+import { NotificationDecoration } from "../../../../../src/components/views/rooms/NotificationDecoration";
+import { createTestClient, mkStubRoom } from "../../../../test-utils";
+
+describe("<NotificationDecoration />", () => {
+    let roomNotificationState: RoomNotificationState;
+    beforeEach(() => {
+        const matrixClient = createTestClient();
+        const room = mkStubRoom("roomId", "roomName", matrixClient);
+        roomNotificationState = new RoomNotificationState(room, false);
+    });
+
+    it("should not render if RoomNotificationState.hasAnyNotificationOrActivity=true", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false);
+        render(<NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />);
+        expect(screen.queryByTestId("notification-decoration")).toBeNull();
+    });
+
+    it("should render the unset message decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "isUnsentMessage", "get").mockReturnValue(true);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the invitation decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "invited", "get").mockReturnValue(true);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the mention decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "isMention", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the notification decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the notification decoration without count", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(0);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the activity decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "isActivityNotification", "get").mockReturnValue(true);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the muted decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(roomNotificationState, "muted", "get").mockReturnValue(true);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={false} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+    it("should render the video decoration", () => {
+        jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false);
+        const { asFragment } = render(
+            <NotificationDecoration notificationState={roomNotificationState} hasVideoCall={true} />,
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/PinnedEventTile-test.tsx b/test/unit-tests/components/views/rooms/PinnedEventTile-test.tsx
index 9031d238a82e295dfee7205b067a56b437d2964a..101c04a07b3939961f941342897990cd44003d0a 100644
--- a/test/unit-tests/components/views/rooms/PinnedEventTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/PinnedEventTile-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
-import { EventTimeline, EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { EventTimeline, EventType, type IEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
 
 import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
diff --git a/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx b/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx
index 66e0e32f386c8503e2956c48aad7d9689e9d5468..b7c0e4a430eb1310f47334c4eaaf0f251b229201 100644
--- a/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx
+++ b/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx
@@ -8,7 +8,7 @@
 
 import { act, screen, render } from "jest-matrix-react";
 import React from "react";
-import { EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type IEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
 
 import * as pinnedEventHooks from "../../../../../src/hooks/usePinnedEvents";
diff --git a/test/unit-tests/components/views/rooms/ReadReceiptGroup-test.tsx b/test/unit-tests/components/views/rooms/ReadReceiptGroup-test.tsx
index ec9884591f001d127b2f94a0891c7e322b499561..0a3cfafb85ea746fef7737f40bba990dea3a0d31 100644
--- a/test/unit-tests/components/views/rooms/ReadReceiptGroup-test.tsx
+++ b/test/unit-tests/components/views/rooms/ReadReceiptGroup-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
 import { RoomMember } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
diff --git a/test/unit-tests/components/views/rooms/ReadReceiptMarker-test.tsx b/test/unit-tests/components/views/rooms/ReadReceiptMarker-test.tsx
index 68b7bc9799f439c35aefdb8c321ec12d9c5b8946..a55636d58134c957fcbab2504dfcbdda61d384bf 100644
--- a/test/unit-tests/components/views/rooms/ReadReceiptMarker-test.tsx
+++ b/test/unit-tests/components/views/rooms/ReadReceiptMarker-test.tsx
@@ -9,7 +9,9 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { render, screen } from "jest-matrix-react";
 
-import ReadReceiptMarker, { IReadReceiptPosition } from "../../../../../src/components/views/rooms/ReadReceiptMarker";
+import ReadReceiptMarker, {
+    type IReadReceiptPosition,
+} from "../../../../../src/components/views/rooms/ReadReceiptMarker";
 
 describe("ReadReceiptMarker", () => {
     afterEach(() => {
diff --git a/test/unit-tests/components/views/rooms/RoomHeader/CallGuestLinkButton-test.tsx b/test/unit-tests/components/views/rooms/RoomHeader/CallGuestLinkButton-test.tsx
index a89058d73014ab565f8bdeb80bba84e2bb29ec14..4ba3cfc41047d14e2272b1e4ab6ded7988b9a66e 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader/CallGuestLinkButton-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomHeader/CallGuestLinkButton-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, getByLabelText, getByText, render, screen, waitFor } from "jest-matrix-react";
-import { EventTimeline, JoinRule, Room } from "matrix-js-sdk/src/matrix";
+import { type EventTimeline, JoinRule, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { SDKContext, SdkContextClass } from "../../../../../../src/contexts/SDKContext";
@@ -46,9 +46,11 @@ describe("<CallGuestLinkButton />", () => {
      */
     const makeRoom = (isVideoRoom = true): Room => {
         const room = new Room(roomId, sdkContext.client!, sdkContext.client!.getSafeUserId());
+        sdkContext.client!.getRoomDirectoryVisibility = jest.fn().mockResolvedValue("public");
         jest.spyOn(room, "isElementVideoRoom").mockReturnValue(isVideoRoom);
         // stub
         jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
+        jest.spyOn(room, "getVersion").mockReturnValue("9");
         return room;
     };
     function mockRoomMembers(room: Room, count: number) {
@@ -221,24 +223,25 @@ describe("<CallGuestLinkButton />", () => {
         });
 
         it("shows ask to join if feature is enabled", () => {
-            const { container } = getComponent(room);
-            expect(getByText(container, "Ask to join")).toBeInTheDocument();
+            getComponent(room);
+            expect(screen.getByRole("radio", { name: "Ask to join ( Recommended )" })).toBeInTheDocument();
         });
-        it("font show ask to join if feature is enabled but cannot invite", () => {
+        it("dont show ask to join if feature is enabled but cannot invite", () => {
             getComponent(room, false);
-            expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
+            expect(screen.queryByRole("radio", { name: "Ask to join ( Recommended )" })).not.toBeInTheDocument();
         });
         it("doesn't show ask to join if feature is disabled", () => {
             jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
             getComponent(room);
-            expect(screen.queryByText("Ask to join")).not.toBeInTheDocument();
+            expect(screen.queryByRole("radio", { name: "Ask to join ( Recommended )" })).not.toBeInTheDocument();
         });
 
         it("sends correct state event on click", async () => {
             const sendStateSpy = jest.spyOn(sdkContext.client!, "sendStateEvent");
+
             let container;
             container = getComponent(room).container;
-            fireEvent.click(getByText(container, "Ask to join"));
+            fireEvent.click(screen.getByRole("radio", { name: "Ask to join ( Recommended )" }));
             expect(sendStateSpy).toHaveBeenCalledWith(
                 "!room:server.org",
                 "m.room.join_rules",
@@ -246,7 +249,7 @@ describe("<CallGuestLinkButton />", () => {
                 "",
             );
             expect(sendStateSpy).toHaveBeenCalledTimes(1);
-            await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
+            await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1), { timeout: 3000 });
             onFinished.mockClear();
             sendStateSpy.mockClear();
 
@@ -260,7 +263,7 @@ describe("<CallGuestLinkButton />", () => {
             );
             expect(sendStateSpy).toHaveBeenCalledTimes(1);
             container = getComponent(room).container;
-            await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
+            await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1), { timeout: 3000 });
             onFinished.mockClear();
             sendStateSpy.mockClear();
 
diff --git a/test/unit-tests/components/views/rooms/RoomHeader-test.tsx b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
similarity index 82%
rename from test/unit-tests/components/views/rooms/RoomHeader-test.tsx
rename to test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
index 633100eec4c235323cd06b29cb1e329d6acedccf..b93a26c4122247caad86084c5ecdfb5b8c575912 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2023 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { CallType, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
 import {
     EventType,
     JoinRule,
@@ -29,35 +29,42 @@ import {
     queryAllByLabelText,
     queryByLabelText,
     render,
-    RenderOptions,
+    type RenderOptions,
     screen,
     waitFor,
 } from "jest-matrix-react";
-import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
 import { mocked } from "jest-mock";
-
-import { filterConsole, stubClient } from "../../../../test-utils";
-import RoomHeader from "../../../../../src/components/views/rooms/RoomHeader";
-import DMRoomMap from "../../../../../src/utils/DMRoomMap";
-import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
-import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
-import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
-import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
-import SettingsStore from "../../../../../src/settings/SettingsStore";
-import SdkConfig from "../../../../../src/SdkConfig";
-import dispatcher from "../../../../../src/dispatcher/dispatcher";
-import { CallStore } from "../../../../../src/stores/CallStore";
-import { Call, ElementCall } from "../../../../../src/models/Call";
-import * as ShieldUtils from "../../../../../src/utils/ShieldUtils";
-import { Container, WidgetLayoutStore } from "../../../../../src/stores/widgets/WidgetLayoutStore";
-import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
-import { _t } from "../../../../../src/languageHandler";
-import * as UseCall from "../../../../../src/hooks/useCall";
-import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
-import WidgetStore, { IApp } from "../../../../../src/stores/WidgetStore";
-import { UIFeature } from "../../../../../src/settings/UIFeature";
-
-jest.mock("../../../../../src/utils/ShieldUtils");
+import userEvent from "@testing-library/user-event";
+
+import { filterConsole, stubClient } from "../../../../../test-utils";
+import RoomHeader from "../../../../../../src/components/views/rooms/RoomHeader/RoomHeader";
+import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
+import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
+import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
+import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
+import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
+import SettingsStore from "../../../../../../src/settings/SettingsStore";
+import SdkConfig from "../../../../../../src/SdkConfig";
+import dispatcher from "../../../../../../src/dispatcher/dispatcher";
+import { CallStore } from "../../../../../../src/stores/CallStore";
+import { type Call, ElementCall } from "../../../../../../src/models/Call";
+import * as ShieldUtils from "../../../../../../src/utils/ShieldUtils";
+import { Container, WidgetLayoutStore } from "../../../../../../src/stores/widgets/WidgetLayoutStore";
+import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
+import { _t } from "../../../../../../src/languageHandler";
+import * as UseCall from "../../../../../../src/hooks/useCall";
+import { SdkContextClass } from "../../../../../../src/contexts/SDKContext";
+import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore";
+import { UIFeature } from "../../../../../../src/settings/UIFeature";
+import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
+
+jest.mock("../../../../../../src/utils/ShieldUtils");
+jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
+    useCurrentPhase: () => {
+        return { currentPhase: "foo", isOpen: false };
+    },
+}));
 
 function getWrapper(): RenderOptions {
     return {
@@ -93,6 +100,7 @@ describe("RoomHeader", () => {
 
     afterEach(() => {
         jest.restoreAllMocks();
+        SettingsStore.reset();
     });
 
     it("renders the room header", () => {
@@ -101,13 +109,15 @@ describe("RoomHeader", () => {
     });
 
     it("opens the room summary", async () => {
+        const user = userEvent.setup();
         const { container } = render(<RoomHeader room={room} />, getWrapper());
 
-        fireEvent.click(getByText(container, ROOM_ID));
+        await user.click(getByText(container, ROOM_ID));
         expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomSummary });
     });
 
     it("shows a face pile for rooms", async () => {
+        const user = userEvent.setup();
         const members = [
             {
                 userId: "@me:example.org",
@@ -156,33 +166,34 @@ describe("RoomHeader", () => {
         const facePile = getByLabelText(document.body, "4 members");
         expect(facePile).toHaveTextContent("4");
 
-        fireEvent.click(facePile);
+        await user.click(facePile);
 
         expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
     });
 
     it("has room info icon that opens the room info panel", async () => {
+        const user = userEvent.setup();
         const { getAllByRole } = render(<RoomHeader room={room} />, getWrapper());
         const infoButton = getAllByRole("button", { name: "Room info" })[1];
-        fireEvent.click(infoButton);
+        await user.click(infoButton);
         expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomSummary });
     });
 
     it("opens the thread panel", async () => {
+        const user = userEvent.setup();
         render(<RoomHeader room={room} />, getWrapper());
 
-        fireEvent.click(getByLabelText(document.body, "Threads"));
+        await user.click(getByLabelText(document.body, "Threads"));
         expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel });
     });
 
     it("opens the notifications panel", async () => {
-        jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
-            if (name === "feature_notifications") return true;
-        });
+        const user = userEvent.setup();
+        SettingsStore.setValue("feature_notifications", null, SettingLevel.DEVICE, true);
 
         render(<RoomHeader room={room} />, getWrapper());
 
-        fireEvent.click(getByLabelText(document.body, "Notifications"));
+        await user.click(getByLabelText(document.body, "Notifications"));
         expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.NotificationPanel });
     });
 
@@ -215,9 +226,48 @@ describe("RoomHeader", () => {
         expect(screen.queryByRole("button", { name: "Voice call" })).not.toBeInTheDocument();
     });
 
+    describe("UIFeature.Voip disabled", () => {
+        beforeEach(() => {
+            SdkConfig.put({
+                setting_defaults: {
+                    [UIFeature.Voip]: false,
+                },
+            });
+        });
+
+        afterEach(() => {
+            SdkConfig.reset();
+            jest.restoreAllMocks();
+        });
+
+        it("should not show call buttons in rooms smaller than 3 members", async () => {
+            mockRoomMembers(room, 2);
+            render(<RoomHeader room={room} />, getWrapper());
+
+            expect(screen.queryByRole("button", { name: "Video call" })).not.toBeInTheDocument();
+            expect(screen.queryByRole("button", { name: "Voice call" })).not.toBeInTheDocument();
+        });
+
+        it("should not show call button in rooms larger than 2 members", async () => {
+            mockRoomMembers(room, 3);
+            render(<RoomHeader room={room} />, getWrapper());
+
+            expect(screen.queryByRole("button", { name: "Video call" })).not.toBeInTheDocument();
+            expect(screen.queryByRole("button", { name: "Voice call" })).not.toBeInTheDocument();
+        });
+    });
+
     describe("UIFeature.Widgets enabled (default)", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets);
+            SdkConfig.put({
+                setting_defaults: {
+                    [UIFeature.Widgets]: true,
+                },
+            });
+        });
+
+        afterEach(() => {
+            SdkConfig.reset();
         });
 
         it("should show call buttons in a room with 2 members", () => {
@@ -237,7 +287,15 @@ describe("RoomHeader", () => {
 
     describe("UIFeature.Widgets disabled", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => false);
+            SdkConfig.put({
+                setting_defaults: {
+                    [UIFeature.Widgets]: false,
+                },
+            });
+        });
+
+        afterEach(() => {
+            SdkConfig.reset();
         });
 
         it("should show call buttons in a room with 2 members", () => {
@@ -257,7 +315,15 @@ describe("RoomHeader", () => {
 
     describe("groups call disabled", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets);
+            SdkConfig.put({
+                setting_defaults: {
+                    [UIFeature.Widgets]: true,
+                },
+            });
+        });
+
+        afterEach(() => {
+            SdkConfig.reset();
         });
 
         it("you can't call if you're alone", () => {
@@ -269,6 +335,7 @@ describe("RoomHeader", () => {
         });
 
         it("you can call when you're two in the room", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 2);
             render(<RoomHeader room={room} />, getWrapper());
 
@@ -279,10 +346,10 @@ describe("RoomHeader", () => {
 
             const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
 
-            fireEvent.click(voiceButton);
+            await user.click(voiceButton);
             expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
 
-            fireEvent.click(videoButton);
+            await user.click(videoButton);
             expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
         });
 
@@ -321,14 +388,26 @@ describe("RoomHeader", () => {
 
     describe("group call enabled", () => {
         beforeEach(() => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation(
-                (feature) => feature === "feature_group_calls" || feature == UIFeature.Widgets,
-            );
+            SdkConfig.put({
+                features: {
+                    feature_group_calls: true,
+                },
+            });
+        });
+
+        afterEach(() => {
+            SdkConfig.reset();
+            jest.restoreAllMocks();
         });
 
         it("renders only the video call element", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 3);
-            jest.spyOn(SdkConfig, "get").mockReturnValue({ use_exclusively: true });
+            SdkConfig.add({
+                element_call: {
+                    use_exclusively: true,
+                },
+            });
             // allow element calls
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
 
@@ -339,14 +418,18 @@ describe("RoomHeader", () => {
             const videoCallButton = screen.getByRole("button", { name: "Video call" });
             expect(videoCallButton).not.toHaveAttribute("aria-disabled", "true");
 
-            const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
+            const dispatcherSpy = jest.spyOn(dispatcher, "dispatch").mockImplementation();
 
-            fireEvent.click(videoCallButton);
+            await user.click(videoCallButton);
             expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ view_call: true }));
         });
 
         it("can't call if there's an ongoing (pinned) call", () => {
-            jest.spyOn(SdkConfig, "get").mockReturnValue({ use_exclusively: true });
+            SdkConfig.add({
+                element_call: {
+                    use_exclusively: true,
+                },
+            });
             // allow element calls
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
             jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true);
@@ -361,9 +444,17 @@ describe("RoomHeader", () => {
             expect(screen.getByRole("button", { name: "Ongoing call" })).toHaveAttribute("aria-disabled", "true");
         });
 
-        it("clicking on ongoing (unpinned) call re-pins it", () => {
+        it("clicking on ongoing (unpinned) call re-pins it", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 3);
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature == UIFeature.Widgets);
+            SdkConfig.add({
+                setting_defaults: {
+                    [UIFeature.Widgets]: true,
+                },
+                features: {
+                    feature_group_calls: false,
+                },
+            });
             // allow calls
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
             jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false);
@@ -381,7 +472,7 @@ describe("RoomHeader", () => {
 
             const videoButton = screen.getByRole("button", { name: "Video call" });
             expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
-            fireEvent.click(videoButton);
+            await user.click(videoButton);
             expect(spy).toHaveBeenCalledWith(room, widget, Container.Top);
         });
 
@@ -413,8 +504,10 @@ describe("RoomHeader", () => {
             jest.spyOn(room.currentState, "maySendStateEvent").mockReturnValue(true);
             jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
             jest.spyOn(room, "canInvite").mockReturnValue(false);
-            const guestSpaUrlMock = jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
-                return { guest_spa_url: "https://guest_spa_url.com", url: "https://spa_url.com" };
+            SdkConfig.add({
+                element_call: {
+                    guest_spa_url: "https://guest_spa_url.com",
+                },
             });
             const { container: containerNoInviteNotPublicCanUpgradeAccess } = render(
                 <RoomHeader room={room} />,
@@ -428,8 +521,10 @@ describe("RoomHeader", () => {
             jest.spyOn(room.currentState, "maySendStateEvent").mockReturnValue(false);
             jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
             jest.spyOn(room, "canInvite").mockReturnValue(false);
-            jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
-                return { guest_spa_url: "https://guest_spa_url.com", url: "https://spa_url.com" };
+            SdkConfig.add({
+                element_call: {
+                    guest_spa_url: "https://guest_spa_url.com",
+                },
             });
             const { container: containerNoInviteNotPublic } = render(<RoomHeader room={room} />, getWrapper());
             expect(queryAllByLabelText(containerNoInviteNotPublic, "There's no one here to call")).toHaveLength(2);
@@ -449,8 +544,9 @@ describe("RoomHeader", () => {
             const { container: containerInvitePublic } = render(<RoomHeader room={room} />, getWrapper());
             expect(queryAllByLabelText(containerInvitePublic, "There's no one here to call")).toHaveLength(0);
 
+            // Clear guest_spa_url
+            SdkConfig.reset();
             // last we can allow everything but without guest_spa_url nothing will work
-            guestSpaUrlMock.mockRestore();
             const { container: containerAllAllowedButNoGuestSpaUrl } = render(<RoomHeader room={room} />, getWrapper());
             expect(
                 queryAllByLabelText(containerAllAllowedButNoGuestSpaUrl, "There's no one here to call"),
@@ -458,6 +554,7 @@ describe("RoomHeader", () => {
         });
 
         it("calls using legacy or jitsi", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 2);
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
                 if (key === "im.vector.modular.widgets") return true;
@@ -471,14 +568,15 @@ describe("RoomHeader", () => {
             expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
 
             const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
-            fireEvent.click(voiceButton);
+            await user.click(voiceButton);
             expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
 
-            fireEvent.click(videoButton);
+            await user.click(videoButton);
             expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
         });
 
         it("calls using legacy or jitsi for large rooms", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 3);
 
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
@@ -492,11 +590,12 @@ describe("RoomHeader", () => {
             expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
 
             const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
-            fireEvent.click(videoButton);
+            await user.click(videoButton);
             expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
         });
 
         it("calls using element call for large rooms", async () => {
+            const user = userEvent.setup();
             mockRoomMembers(room, 3);
 
             jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
@@ -509,8 +608,8 @@ describe("RoomHeader", () => {
             const videoButton = screen.getByRole("button", { name: "Video call" });
             expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
 
-            const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
-            fireEvent.click(videoButton);
+            const dispatcherSpy = jest.spyOn(dispatcher, "dispatch").mockImplementation();
+            await user.click(videoButton);
             expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ view_call: true }));
         });
 
@@ -626,6 +725,10 @@ describe("RoomHeader", () => {
             ]);
         });
 
+        afterEach(() => {
+            SdkConfig.reset();
+        });
+
         it.each([
             [ShieldUtils.E2EStatus.Verified, "Verified"],
             [ShieldUtils.E2EStatus.Warning, "Untrusted"],
@@ -638,6 +741,11 @@ describe("RoomHeader", () => {
         });
 
         it("does not show the face pile for DMs", () => {
+            SdkConfig.put({
+                features: {
+                    feature_notifications: false,
+                },
+            });
             const { asFragment } = render(<RoomHeader room={room} />, getWrapper());
 
             expect(asFragment()).toMatchSnapshot();
@@ -734,7 +842,7 @@ describe("RoomHeader", () => {
 
     describe("ask to join enabled", () => {
         it("does render the RoomKnocksBar", () => {
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature === "feature_ask_to_join");
+            SettingsStore.setValue("feature_ask_to_join", null, SettingLevel.DEVICE, true);
             jest.spyOn(room, "canInvite").mockReturnValue(true);
             jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
             jest.spyOn(room, "getMembersWithMembership").mockReturnValue([new RoomMember(room.roomId, "@foo")]);
@@ -745,10 +853,11 @@ describe("RoomHeader", () => {
     });
 
     it("should open room settings when clicking the room avatar", async () => {
+        const user = userEvent.setup();
         render(<RoomHeader room={room} />, getWrapper());
 
         const dispatcherSpy = jest.spyOn(dispatcher, "dispatch");
-        fireEvent.click(getByLabelText(document.body, "Open room settings"));
+        await user.click(getByLabelText(document.body, "Open room settings"));
         expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ action: "open_room_settings" }));
     });
 });
diff --git a/test/unit-tests/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx b/test/unit-tests/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx
index 9a1466a2617ace7dca202dc6e76d667720d1495a..5ae6560779e3829d10ad12505514845619200c97 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomHeader/VideoRoomChatButton-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MockedObject } from "jest-mock";
+import { type MockedObject } from "jest-mock";
 import { Room } from "matrix-js-sdk/src/matrix";
 import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
 
 import { VideoRoomChatButton } from "../../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
 import { SDKContext, SdkContextClass } from "../../../../../../src/contexts/SDKContext";
-import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
+import type RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
 import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../../test-utils";
 import { RoomNotificationState } from "../../../../../../src/stores/notifications/RoomNotificationState";
 import { NotificationLevel } from "../../../../../../src/stores/notifications/NotificationLevel";
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
similarity index 51%
rename from test/unit-tests/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap
rename to test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
index 3db3fb67fbf82118f9ccd622410ac1eb0d633d5c..a32d00d28285bdbdaa411a1b62d379a4e6fa1fa1 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
@@ -4,12 +4,12 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
 <DocumentFragment>
   <header
     class="mx_Flex mx_RoomHeader light-panel"
-    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
   >
     <button
       aria-label="Open room settings"
       aria-live="off"
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="3"
       data-testid="avatar-img"
       data-type="round"
@@ -30,7 +30,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
       >
         <div
           aria-level="1"
-          class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
+          class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
           dir="auto"
           role="heading"
         >
@@ -43,17 +43,19 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
       </div>
     </button>
     <button
-      aria-labelledby=":r16c:"
-      class="_icon-button_bh2qc_17"
+      aria-disabled="true"
+      aria-label="There's no one here to call"
+      class="_icon-button_m2erp_8"
       role="button"
       style="--cpd-icon-button-size: 32px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
-        style="--cpd-icon-button-size: 100%;"
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
       >
         <svg
+          aria-labelledby="«r1do»"
           fill="currentColor"
           height="1em"
           viewBox="0 0 24 24"
@@ -61,7 +63,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+            d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
           />
         </svg>
       </div>
@@ -69,14 +71,14 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
     <button
       aria-disabled="true"
       aria-label="There's no one here to call"
-      aria-labelledby=":r16h:"
-      class="_icon-button_bh2qc_17"
+      aria-labelledby="«r1dt»"
+      class="_icon-button_m2erp_8"
       role="button"
       style="--cpd-icon-button-size: 32px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
       >
         <svg
@@ -87,24 +89,25 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
+            d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
           />
         </svg>
       </div>
     </button>
     <button
       aria-label="Threads"
-      aria-labelledby=":r16m:"
-      class="_icon-button_bh2qc_17"
+      aria-labelledby="«r1e2»"
+      class="_icon-button_m2erp_8"
       role="button"
       style="--cpd-icon-button-size: 32px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
+          class=""
           fill="currentColor"
           height="1em"
           viewBox="0 0 24 24"
@@ -112,24 +115,25 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+            d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
           />
         </svg>
       </div>
     </button>
     <button
       aria-label="Room info"
-      aria-labelledby=":r16r:"
-      class="_icon-button_bh2qc_17"
+      aria-labelledby="«r1e7»"
+      class="_icon-button_m2erp_8"
       role="button"
       style="--cpd-icon-button-size: 32px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
+          class=""
           fill="currentColor"
           height="1em"
           viewBox="0 0 24 24"
@@ -137,7 +141,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
diff --git a/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/VideoRoomChatButton-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/VideoRoomChatButton-test.tsx.snap
index 83f023e99b21fda1920fd0aa8b51f0b541a0aa48..ec92f780ea06447ca00d0ff4b43b33d002098425 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/VideoRoomChatButton-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/VideoRoomChatButton-test.tsx.snap
@@ -3,19 +3,20 @@
 exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
 <button
   aria-label="Chat"
-  aria-labelledby=":r6:"
-  class="_icon-button_bh2qc_17"
+  aria-labelledby="«r6»"
+  class="_icon-button_m2erp_8"
   data-indicator="default"
   role="button"
   style="--cpd-icon-button-size: 32px;"
   tabindex="0"
 >
   <div
-    class="_indicator-icon_133tf_26"
+    class="_indicator-icon_zr2a0_17"
     data-indicator="default"
     style="--cpd-icon-button-size: 100%;"
   >
     <svg
+      class=""
       fill="currentColor"
       height="1em"
       viewBox="0 0 24 24"
@@ -23,7 +24,7 @@ exports[`<VideoRoomChatButton /> renders button with an unread marker when room
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M2.95 16.3 1.5 21.25a.936.936 0 0 0 .25 1 .936.936 0 0 0 1 .25l4.95-1.45a10.23 10.23 0 0 0 2.1.712c.717.159 1.45.238 2.2.238a9.737 9.737 0 0 0 3.9-.788 10.098 10.098 0 0 0 3.175-2.137c.9-.9 1.613-1.958 2.137-3.175A9.738 9.738 0 0 0 22 12a9.738 9.738 0 0 0-.788-3.9 10.099 10.099 0 0 0-2.137-3.175c-.9-.9-1.958-1.612-3.175-2.137A9.737 9.737 0 0 0 12 2a9.737 9.737 0 0 0-3.9.788 10.099 10.099 0 0 0-3.175 2.137c-.9.9-1.612 1.958-2.137 3.175A9.738 9.738 0 0 0 2 12a10.179 10.179 0 0 0 .95 4.3Z"
+        d="M2.95 16.3 1.5 21.25a.94.94 0 0 0 .25 1 .94.94 0 0 0 1 .25l4.95-1.45a10.2 10.2 0 0 0 2.1.712Q10.875 22 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 1.125.238 2.2.237 1.076.712 2.1"
       />
     </svg>
   </div>
diff --git a/test/unit-tests/components/views/rooms/RoomListHeader-test.tsx b/test/unit-tests/components/views/rooms/RoomListHeader-test.tsx
index ade08b8f956cd7bb3a1e4fff16be2dd2f8078139..a4c4b4795aa16c489d14312e41c87b07acf142b2 100644
--- a/test/unit-tests/components/views/rooms/RoomListHeader-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomListHeader-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { MatrixClient, Room, EventType } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room, EventType } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
-import { act, render, screen, fireEvent, RenderResult } from "jest-matrix-react";
+import { act, render, screen, fireEvent, type RenderResult } from "jest-matrix-react";
 
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 import { MetaSpace } from "../../../../../src/stores/spaces";
-import _RoomListHeader from "../../../../../src/components/views/rooms/RoomListHeader";
+import _RoomListHeader from "../../../../../src/components/views/rooms/LegacyRoomListHeader";
 import * as testUtils from "../../../../test-utils";
 import { stubClient, mkSpace } from "../../../../test-utils";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a8f87a42414704bfabf94bb4e1e45ee8afa07672
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
+import { EmptyRoomList } from "../../../../../../src/components/views/rooms/RoomListPanel/EmptyRoomList";
+import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters";
+
+describe("<EmptyRoomList />", () => {
+    let vm: RoomListViewState;
+
+    beforeEach(() => {
+        vm = {
+            isLoadingRooms: false,
+            rooms: [],
+            primaryFilters: [],
+            createRoom: jest.fn(),
+            createChatRoom: jest.fn(),
+            canCreateRoom: true,
+            activeIndex: undefined,
+        };
+    });
+
+    test("should render the default placeholder when there is no filter", async () => {
+        const user = userEvent.setup();
+
+        const { asFragment } = render(<EmptyRoomList vm={vm} />);
+        expect(screen.getByText("No chats yet")).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+
+        await user.click(screen.getByRole("button", { name: "New message" }));
+        expect(vm.createChatRoom).toHaveBeenCalled();
+
+        await user.click(screen.getByRole("button", { name: "New room" }));
+        expect(vm.createRoom).toHaveBeenCalled();
+    });
+
+    test("should not render the new room button if the user doesn't have the rights to create a room", async () => {
+        const newState = { ...vm, canCreateRoom: false };
+
+        const { asFragment } = render(<EmptyRoomList vm={newState} />);
+        expect(screen.queryByRole("button", { name: "New room" })).toBeNull();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it.each([
+        { key: FilterKey.UnreadFilter, name: "unread", action: "Show all chats" },
+        { key: FilterKey.MentionsFilter, name: "mention", action: "See all activity" },
+        { key: FilterKey.InvitesFilter, name: "invite", action: "See all activity" },
+    ])("should display the empty state for the $name filter", async ({ key, name, action }) => {
+        const user = userEvent.setup();
+        const activePrimaryFilter = {
+            toggle: jest.fn(),
+            active: true,
+            name,
+            key,
+        };
+        const newState = {
+            ...vm,
+            activePrimaryFilter,
+        };
+
+        const { asFragment } = render(<EmptyRoomList vm={newState} />);
+        await user.click(screen.getByRole("button", { name: action }));
+        expect(activePrimaryFilter.toggle).toHaveBeenCalled();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it.each([
+        { key: FilterKey.FavouriteFilter, name: "favourite" },
+        { key: FilterKey.PeopleFilter, name: "people" },
+        { key: FilterKey.RoomsFilter, name: "rooms" },
+    ])("should display empty state for filter $name", ({ name, key }) => {
+        const activePrimaryFilter = {
+            toggle: jest.fn(),
+            active: true,
+            name,
+            key,
+        };
+        const newState = { ...vm, activePrimaryFilter };
+        const { asFragment } = render(<EmptyRoomList vm={newState} />);
+        expect(asFragment()).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5229ff6679a5f73bc20fd5cb954e12e44a7a4af
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { render } from "jest-matrix-react";
+import { fireEvent } from "@testing-library/dom";
+
+import { mkRoom, stubClient } from "../../../../../test-utils";
+import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
+import { RoomList } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomList";
+import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
+import { Landmark, LandmarkNavigation } from "../../../../../../src/accessibility/LandmarkNavigation";
+
+describe("<RoomList />", () => {
+    let matrixClient: MatrixClient;
+    let vm: RoomListViewState;
+
+    beforeEach(() => {
+        // Needed to render the virtualized list in rtl tests
+        // https://github.com/bvaughn/react-virtualized/issues/493#issuecomment-640084107
+        jest.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockReturnValue(1500);
+        jest.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockReturnValue(1500);
+
+        matrixClient = stubClient();
+        const rooms = Array.from({ length: 10 }, (_, i) => mkRoom(matrixClient, `room${i}`));
+        vm = {
+            isLoadingRooms: false,
+            rooms,
+            primaryFilters: [],
+            createRoom: jest.fn(),
+            createChatRoom: jest.fn(),
+            canCreateRoom: true,
+            activeIndex: undefined,
+        };
+
+        // Needed to render a room list cell
+        DMRoomMap.makeShared(matrixClient);
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+    });
+
+    it("should render a room list", () => {
+        const { asFragment } = render(<RoomList vm={vm} />);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it.each([
+        { shortcut: { key: "F6", ctrlKey: true, shiftKey: true }, isPreviousLandmark: true, label: "PreviousLandmark" },
+        { shortcut: { key: "F6", ctrlKey: true }, isPreviousLandmark: false, label: "NextLandmark" },
+    ])("should navigate to the landmark on NextLandmark.$label action", ({ shortcut, isPreviousLandmark }) => {
+        const spyFindLandmark = jest.spyOn(LandmarkNavigation, "findAndFocusNextLandmark").mockReturnValue();
+        const { getByTestId } = render(<RoomList vm={vm} />);
+        const roomList = getByTestId("room-list");
+        fireEvent.keyDown(roomList, shortcut);
+
+        expect(spyFindLandmark).toHaveBeenCalledWith(Landmark.ROOM_LIST, isPreviousLandmark);
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListHeaderView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListHeaderView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4840808fe4aa999f281e4bffe0d6a45450d9c105
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListHeaderView-test.tsx
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { mocked } from "jest-mock";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import {
+    type RoomListHeaderViewState,
+    useRoomListHeaderViewModel,
+} from "../../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel";
+import { RoomListHeaderView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListHeaderView";
+import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter";
+
+jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel", () => ({
+    useRoomListHeaderViewModel: jest.fn(),
+}));
+
+describe("<RoomListHeaderView />", () => {
+    const defaultValue: RoomListHeaderViewState = {
+        title: "title",
+        displayComposeMenu: true,
+        displaySpaceMenu: true,
+        canCreateRoom: true,
+        canCreateVideoRoom: true,
+        canInviteInSpace: true,
+        canAccessSpaceSettings: true,
+        sort: jest.fn(),
+        activeSortOption: SortOption.Activity,
+        shouldShowMessagePreview: false,
+        toggleMessagePreview: jest.fn(),
+        createRoom: jest.fn(),
+        createVideoRoom: jest.fn(),
+        createChatRoom: jest.fn(),
+        openSpaceHome: jest.fn(),
+        inviteInSpace: jest.fn(),
+        openSpacePreferences: jest.fn(),
+        openSpaceSettings: jest.fn(),
+    };
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    it("should render 'room options' button", async () => {
+        mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue);
+        const { asFragment } = render(<RoomListHeaderView />);
+        expect(screen.getByRole("button", { name: "Room Options" })).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    describe("compose menu", () => {
+        it("should display the compose menu", () => {
+            mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue);
+
+            const { asFragment } = render(<RoomListHeaderView />);
+            expect(screen.queryByRole("button", { name: "Add" })).toBeInTheDocument();
+            expect(asFragment()).toMatchSnapshot();
+        });
+
+        it("should not display the compose menu", async () => {
+            const user = userEvent.setup();
+            mocked(useRoomListHeaderViewModel).mockReturnValue({ ...defaultValue, displayComposeMenu: false });
+
+            const { asFragment } = render(<RoomListHeaderView />);
+            expect(screen.queryByRole("button", { name: "Add" })).toBeNull();
+            expect(asFragment()).toMatchSnapshot();
+
+            await user.click(screen.getByRole("button", { name: "New message" }));
+            expect(defaultValue.createChatRoom).toHaveBeenCalled();
+        });
+
+        it("should display all the buttons when the menu is opened", async () => {
+            const user = userEvent.setup();
+            mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue);
+            render(<RoomListHeaderView />);
+            const openMenu = screen.getByRole("button", { name: "Add" });
+            await user.click(openMenu);
+
+            await user.click(screen.getByRole("menuitem", { name: "New message" }));
+            expect(defaultValue.createChatRoom).toHaveBeenCalled();
+
+            await user.click(openMenu);
+            await user.click(screen.getByRole("menuitem", { name: "New room" }));
+            expect(defaultValue.createRoom).toHaveBeenCalled();
+
+            await user.click(openMenu);
+            await user.click(screen.getByRole("menuitem", { name: "New video room" }));
+            expect(defaultValue.createVideoRoom).toHaveBeenCalled();
+        });
+
+        it("should display only the new message button", async () => {
+            const user = userEvent.setup();
+            mocked(useRoomListHeaderViewModel).mockReturnValue({
+                ...defaultValue,
+                canCreateRoom: false,
+                canCreateVideoRoom: false,
+            });
+
+            render(<RoomListHeaderView />);
+            await user.click(screen.getByRole("button", { name: "Add" }));
+
+            expect(screen.queryByRole("menuitem", { name: "New room" })).toBeNull();
+            expect(screen.queryByRole("menuitem", { name: "New video room" })).toBeNull();
+        });
+    });
+
+    describe("space menu", () => {
+        it("should display the space menu", () => {
+            mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue);
+
+            const { asFragment } = render(<RoomListHeaderView />);
+            expect(screen.queryByRole("button", { name: "Open space menu" })).toBeInTheDocument();
+            expect(asFragment()).toMatchSnapshot();
+        });
+
+        it("should not display the space menu", () => {
+            mocked(useRoomListHeaderViewModel).mockReturnValue({ ...defaultValue, displaySpaceMenu: false });
+
+            const { asFragment } = render(<RoomListHeaderView />);
+            expect(screen.queryByRole("button", { name: "Open space menu" })).toBeNull();
+            expect(asFragment()).toMatchSnapshot();
+        });
+
+        it("should display all the buttons when the space menu is opened", async () => {
+            const user = userEvent.setup();
+            mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue);
+            render(<RoomListHeaderView />);
+            const openMenu = screen.getByRole("button", { name: "Open space menu" });
+            await user.click(openMenu);
+
+            await user.click(screen.getByRole("menuitem", { name: "Space home" }));
+            expect(defaultValue.openSpaceHome).toHaveBeenCalled();
+
+            await user.click(openMenu);
+            await user.click(screen.getByRole("menuitem", { name: "Invite" }));
+            expect(defaultValue.inviteInSpace).toHaveBeenCalled();
+
+            await user.click(openMenu);
+            await user.click(screen.getByRole("menuitem", { name: "Preferences" }));
+            expect(defaultValue.openSpacePreferences).toHaveBeenCalled();
+
+            await user.click(openMenu);
+            await user.click(screen.getByRole("menuitem", { name: "Space Settings" }));
+            expect(defaultValue.openSpaceSettings).toHaveBeenCalled();
+        });
+
+        it("should display only the home and preference buttons", async () => {
+            const user = userEvent.setup();
+            mocked(useRoomListHeaderViewModel).mockReturnValue({
+                ...defaultValue,
+                canInviteInSpace: false,
+                canAccessSpaceSettings: false,
+            });
+
+            render(<RoomListHeaderView />);
+            await user.click(screen.getByRole("button", { name: "Add" }));
+
+            expect(screen.queryByRole("menuitem", { name: "Invite" })).toBeNull();
+            expect(screen.queryByRole("menuitem", { name: "Space Setting" })).toBeNull();
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2994d7629ceee799228685159ae419a5f1f35bfd
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { mocked } from "jest-mock";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import {
+    type RoomListItemMenuViewState,
+    useRoomListItemMenuViewModel,
+} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel";
+import type { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { mkRoom, stubClient } from "../../../../../test-utils";
+import { RoomListItemMenuView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemMenuView";
+import { RoomNotifState } from "../../../../../../src/RoomNotifs";
+
+jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel", () => ({
+    useRoomListItemMenuViewModel: jest.fn(),
+}));
+
+describe("<RoomListItemMenuView />", () => {
+    const defaultValue: RoomListItemMenuViewState = {
+        showMoreOptionsMenu: true,
+        showNotificationMenu: true,
+        isFavourite: true,
+        canInvite: true,
+        canMarkAsUnread: true,
+        canMarkAsRead: true,
+        canCopyRoomLink: true,
+        isNotificationAllMessage: true,
+        isNotificationMentionOnly: true,
+        isNotificationAllMessageLoud: true,
+        isNotificationMute: true,
+        copyRoomLink: jest.fn(),
+        markAsUnread: jest.fn(),
+        markAsRead: jest.fn(),
+        leaveRoom: jest.fn(),
+        toggleLowPriority: jest.fn(),
+        toggleFavorite: jest.fn(),
+        invite: jest.fn(),
+        setRoomNotifState: jest.fn(),
+    };
+
+    let matrixClient: MatrixClient;
+    let room: Room;
+
+    beforeEach(() => {
+        mocked(useRoomListItemMenuViewModel).mockReturnValue(defaultValue);
+        matrixClient = stubClient();
+        room = mkRoom(matrixClient, "room1");
+    });
+
+    function renderMenu(setMenuOpen = jest.fn()) {
+        return render(<RoomListItemMenuView room={room} setMenuOpen={setMenuOpen} />);
+    }
+
+    it("should render the more options menu", () => {
+        const { asFragment } = renderMenu();
+        expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should render the notification options menu", () => {
+        const { asFragment } = renderMenu();
+        expect(screen.getByRole("button", { name: "Notification options" })).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should not render the more options menu when showMoreOptionsMenu is false", () => {
+        mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showMoreOptionsMenu: false });
+        renderMenu();
+        expect(screen.queryByRole("button", { name: "More Options" })).toBeNull();
+    });
+
+    it("should not render the notification options menu when showNotificationMenu is false", () => {
+        mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showNotificationMenu: false });
+        renderMenu();
+        expect(screen.queryByRole("button", { name: "Notification options" })).toBeNull();
+    });
+
+    it.each([["More Options"], ["Notification options"]])(
+        "should call setMenuOpen when the menu is opened for %s menu",
+        async (label) => {
+            const user = userEvent.setup();
+            const setMenuOpen = jest.fn();
+            renderMenu(setMenuOpen);
+
+            await user.click(screen.getByRole("button", { name: label }));
+            expect(setMenuOpen).toHaveBeenCalledWith(true);
+        },
+    );
+
+    it("should display all the buttons and have the actions linked for the more options menu", async () => {
+        const user = userEvent.setup();
+        renderMenu();
+
+        const openMenu = screen.getByRole("button", { name: "More Options" });
+        await user.click(openMenu);
+
+        await user.click(screen.getByRole("menuitem", { name: "Mark as read" }));
+        expect(defaultValue.markAsRead).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Mark as unread" }));
+        expect(defaultValue.markAsUnread).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitemcheckbox", { name: "Favourited" }));
+        expect(defaultValue.toggleFavorite).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Low priority" }));
+        expect(defaultValue.toggleLowPriority).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Invite" }));
+        expect(defaultValue.invite).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Copy room link" }));
+        expect(defaultValue.copyRoomLink).toHaveBeenCalled();
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Leave room" }));
+        expect(defaultValue.leaveRoom).toHaveBeenCalled();
+    });
+
+    it("should display all the buttons and have the actions linked for the notification options menu", async () => {
+        const user = userEvent.setup();
+        renderMenu();
+
+        const openMenu = screen.getByRole("button", { name: "Notification options" });
+        await user.click(openMenu);
+
+        await user.click(screen.getByRole("menuitem", { name: "Match default settings" }));
+        expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessages);
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "All messages" }));
+        expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessagesLoud);
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Mentions and keywords" }));
+        expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.MentionsOnly);
+
+        await user.click(openMenu);
+        await user.click(screen.getByRole("menuitem", { name: "Mute room" }));
+        expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute);
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..914d33ee18fbffa938b26bf2a1b3cd3da38c94a4
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+import { render, screen, waitFor } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import { mocked } from "jest-mock";
+
+import { mkRoom, stubClient, withClientContextRenderOptions } from "../../../../../test-utils";
+import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView";
+import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
+import {
+    type RoomListItemViewState,
+    useRoomListItemViewModel,
+} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel";
+import { RoomNotificationState } from "../../../../../../src/stores/notifications/RoomNotificationState";
+
+jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel", () => ({
+    useRoomListItemViewModel: jest.fn(),
+}));
+
+describe("<RoomListItemView />", () => {
+    let defaultValue: RoomListItemViewState;
+    let matrixClient: MatrixClient;
+    let room: Room;
+    beforeEach(() => {
+        matrixClient = stubClient();
+        room = mkRoom(matrixClient, "room1");
+
+        DMRoomMap.makeShared(matrixClient);
+        jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
+
+        const notificationState = new RoomNotificationState(room, false);
+        jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true);
+        jest.spyOn(notificationState, "isNotification", "get").mockReturnValue(true);
+        jest.spyOn(notificationState, "count", "get").mockReturnValue(1);
+
+        defaultValue = {
+            openRoom: jest.fn(),
+            showHoverMenu: false,
+            notificationState,
+            a11yLabel: "Open room room1",
+            isBold: false,
+            isVideoRoom: false,
+            callConnectionState: null,
+            hasParticipantInCall: false,
+            name: room.name,
+            showNotificationDecoration: false,
+            messagePreview: undefined,
+        };
+
+        mocked(useRoomListItemViewModel).mockReturnValue(defaultValue);
+    });
+
+    test("should render a room item", () => {
+        const onClick = jest.fn();
+        const { asFragment } = render(<RoomListItemView room={room} onClick={onClick} isSelected={false} />);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    test("should render a room item with a message preview", () => {
+        defaultValue.messagePreview = "The message looks list this";
+
+        const onClick = jest.fn();
+        const { asFragment } = render(<RoomListItemView room={room} onClick={onClick} isSelected={false} />);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    test("should call openRoom when clicked", async () => {
+        const user = userEvent.setup();
+        render(<RoomListItemView room={room} isSelected={false} />);
+
+        await user.click(screen.getByRole("button", { name: `Open room ${room.name}` }));
+        expect(defaultValue.openRoom).toHaveBeenCalled();
+    });
+
+    test("should hover decoration if hovered", async () => {
+        mocked(useRoomListItemViewModel).mockReturnValue({ ...defaultValue, showHoverMenu: true });
+
+        const user = userEvent.setup();
+        render(<RoomListItemView room={room} isSelected={false} />, withClientContextRenderOptions(matrixClient));
+        const listItem = screen.getByRole("button", { name: `Open room ${room.name}` });
+        expect(screen.queryByRole("button", { name: "More Options" })).toBeNull();
+
+        await user.hover(listItem);
+        await waitFor(() => expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument());
+    });
+
+    test("should hover decoration if focused", async () => {
+        const user = userEvent.setup();
+        render(<RoomListItemView room={room} isSelected={false} />, withClientContextRenderOptions(matrixClient));
+        const listItem = screen.getByRole("button", { name: `Open room ${room.name}` });
+        await user.click(listItem);
+        expect(listItem).toHaveClass("mx_RoomListItemView_hover");
+
+        await user.tab();
+        await waitFor(() => expect(listItem).not.toHaveClass("mx_RoomListItemView_hover"));
+    });
+
+    test("should be selected if isSelected=true", async () => {
+        const { asFragment } = render(<RoomListItemView room={room} isSelected={true} />);
+        expect(screen.queryByRole("button", { name: `Open room ${room.name}` })).toHaveAttribute(
+            "aria-selected",
+            "true",
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    test("should display notification decoration", async () => {
+        mocked(useRoomListItemViewModel).mockReturnValue({
+            ...defaultValue,
+            showNotificationDecoration: true,
+        });
+
+        const { asFragment } = render(<RoomListItemView room={room} isSelected={false} />);
+        expect(screen.getByTestId("notification-decoration")).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    test("should not display notification decoration when hovered", async () => {
+        const user = userEvent.setup();
+
+        mocked(useRoomListItemViewModel).mockReturnValue({
+            ...defaultValue,
+            showNotificationDecoration: true,
+        });
+
+        render(<RoomListItemView room={room} isSelected={false} />);
+        const listItem = screen.getByRole("button", { name: `Open room ${room.name}` });
+        await user.hover(listItem);
+
+        expect(screen.queryByRole("notification-decoration")).toBeNull();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListOptionsMenu-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListOptionsMenu-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c476bda0d1e67cdb0abef932ff9eb5b7efa4e35b
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListOptionsMenu-test.tsx
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import { RoomListOptionsMenu } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListOptionsMenu";
+import { type RoomListHeaderViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel";
+
+describe("<RoomListOptionsMenu />", () => {
+    it("should match snapshot", () => {
+        const vm = {
+            sort: jest.fn(),
+        } as unknown as RoomListHeaderViewState;
+
+        const { asFragment } = render(<RoomListOptionsMenu vm={vm} />);
+
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should show A to Z selected if activeSortOption is Alphabetic", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            sort: jest.fn(),
+            activeSortOption: "Alphabetic",
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        // Open the menu
+        const button = screen.getByRole("button", { name: "Room Options" });
+        await user.click(button);
+
+        expect(screen.getByRole("menuitemradio", { name: "A-Z" })).toBeChecked();
+        expect(screen.getByRole("menuitemradio", { name: "Activity" })).not.toBeChecked();
+    });
+
+    it("should show Activity selected if activeSortOption is Recency", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            sort: jest.fn(),
+            activeSortOption: "Recency",
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        // Open the menu
+        const button = screen.getByRole("button", { name: "Room Options" });
+        await user.click(button);
+
+        expect(screen.getByRole("menuitemradio", { name: "A-Z" })).not.toBeChecked();
+        expect(screen.getByRole("menuitemradio", { name: "Activity" })).toBeChecked();
+    });
+
+    it("should sort A to Z", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            sort: jest.fn(),
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        await user.click(screen.getByRole("button", { name: "Room Options" }));
+
+        await user.click(screen.getByRole("menuitemradio", { name: "A-Z" }));
+
+        expect(vm.sort).toHaveBeenCalledWith("Alphabetic");
+    });
+
+    it("should sort by activity", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            sort: jest.fn(),
+            activeSortOption: "Alphabetic",
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        await user.click(screen.getByRole("button", { name: "Room Options" }));
+
+        await user.click(screen.getByRole("menuitemradio", { name: "Activity" }));
+
+        expect(vm.sort).toHaveBeenCalledWith("Recency");
+    });
+
+    it("should show message previews disabled", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            shouldShowMessagePreview: false,
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        await user.click(screen.getByRole("button", { name: "Room Options" }));
+
+        expect(screen.getByRole("menuitemcheckbox", { name: "Show message previews" })).not.toBeChecked();
+    });
+
+    it("should show message previews enabled", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            shouldShowMessagePreview: true,
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        await user.click(screen.getByRole("button", { name: "Room Options" }));
+
+        expect(screen.getByRole("menuitemcheckbox", { name: "Show message previews" })).toBeChecked();
+    });
+
+    it("should toggle message previews", async () => {
+        const user = userEvent.setup();
+
+        const vm = {
+            toggleMessagePreview: jest.fn(),
+        } as unknown as RoomListHeaderViewState;
+
+        render(<RoomListOptionsMenu vm={vm} />);
+
+        await user.click(screen.getByRole("button", { name: "Room Options" }));
+
+        await user.click(screen.getByRole("menuitemcheckbox", { name: "Show message previews" }));
+
+        expect(vm.toggleMessagePreview).toHaveBeenCalled();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..525a17e1ac805e856b432dd8e702b9e499818336
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPanel-test.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import { mocked } from "jest-mock";
+
+import { RoomListPanel } from "../../../../../../src/components/views/rooms/RoomListPanel";
+import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
+import { MetaSpace } from "../../../../../../src/stores/spaces";
+
+jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
+    shouldShowComponent: jest.fn(),
+}));
+
+describe("<RoomListPanel />", () => {
+    function renderComponent() {
+        return render(<RoomListPanel activeSpace={MetaSpace.Home} />);
+    }
+
+    beforeEach(() => {
+        // By default, we consider shouldShowComponent(UIComponent.FilterContainer) should return true
+        mocked(shouldShowComponent).mockReturnValue(true);
+    });
+
+    it("should render the RoomListSearch component when UIComponent.FilterContainer is at true", () => {
+        const { asFragment } = renderComponent();
+        expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should not render the RoomListSearch component when UIComponent.FilterContainer is at false", () => {
+        mocked(shouldShowComponent).mockReturnValue(false);
+        const { asFragment } = renderComponent();
+
+        expect(screen.queryByRole("button", { name: "Search Ctrl K" })).toBeNull();
+        expect(asFragment()).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fe605ce70faafb5fa38591bca67daaabbf5bfaea
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
+import { RoomListPrimaryFilters } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters";
+import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters";
+
+describe("<RoomListPrimaryFilters />", () => {
+    let vm: RoomListViewState;
+
+    beforeEach(() => {
+        vm = {
+            isLoadingRooms: false,
+            rooms: [],
+            canCreateRoom: true,
+            createRoom: jest.fn(),
+            createChatRoom: jest.fn(),
+            primaryFilters: [
+                { name: "People", active: false, toggle: jest.fn(), key: FilterKey.PeopleFilter },
+                { name: "Rooms", active: true, toggle: jest.fn(), key: FilterKey.RoomsFilter },
+            ],
+            activeIndex: undefined,
+        };
+    });
+
+    it("should render primary filters", async () => {
+        const user = userEvent.setup();
+
+        const { asFragment } = render(<RoomListPrimaryFilters vm={vm} />);
+        expect(screen.getByRole("option", { name: "People" })).toBeInTheDocument();
+        expect(screen.getByRole("option", { name: "Rooms" })).toHaveAttribute("aria-selected", "true");
+        expect(asFragment()).toMatchSnapshot();
+
+        await user.click(screen.getByRole("button", { name: "People" }));
+        expect(vm.primaryFilters[0].toggle).toHaveBeenCalled();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListSearch-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListSearch-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..47ecc69cc34a6f35bdc14053b29b4b127e5c03e1
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListSearch-test.tsx
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import { mocked } from "jest-mock";
+import userEvent from "@testing-library/user-event";
+
+import { RoomListSearch } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSearch";
+import { MetaSpace } from "../../../../../../src/stores/spaces";
+import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
+import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../../../src/dispatcher/actions";
+import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
+
+jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
+    shouldShowComponent: jest.fn(),
+}));
+
+describe("<RoomListSearch />", () => {
+    function renderComponent(activeSpace = MetaSpace.Home) {
+        return render(<RoomListSearch activeSpace={activeSpace} />);
+    }
+
+    beforeEach(() => {
+        // By default, we consider shouldShowComponent(UIComponent.ExploreRooms) should return true
+        mocked(shouldShowComponent).mockReturnValue(true);
+        jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
+    });
+
+    it("should display search and explore buttons", () => {
+        const { asFragment } = renderComponent();
+
+        // The search and explore buttons should be displayed
+        expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
+        expect(screen.getByRole("button", { name: "Explore rooms" })).toBeInTheDocument();
+        // The dial button should not be displayed
+        expect(screen.queryByRole("button", { name: "Open dial pad" })).not.toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should hide the explore button when the active space is not MetaSpace.Home", () => {
+        const { asFragment } = renderComponent(MetaSpace.VideoRooms);
+
+        // The search button should be displayed but not the explore button
+        expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
+        expect(screen.queryByRole("button", { name: "Explore rooms" })).not.toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should hide the explore button when UIComponent.ExploreRooms is disabled", () => {
+        mocked(shouldShowComponent).mockReturnValue(false);
+        const { asFragment } = renderComponent();
+
+        // The search button should be displayed but not the explore button
+        expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
+        expect(screen.queryByRole("button", { name: "Explore rooms" })).not.toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should display the dial button when the PTSN protocol is not supported", () => {
+        jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
+        const { asFragment } = renderComponent();
+
+        // The dial button should be displayed
+        expect(screen.getByRole("button", { name: "Open dial pad" })).toBeInTheDocument();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should open the spotlight when the search button is clicked", async () => {
+        const fireSpy = jest.spyOn(defaultDispatcher, "fire");
+        const user = userEvent.setup();
+        renderComponent();
+
+        // Click on the search button
+        await user.click(screen.getByRole("button", { name: "Search Ctrl K" }));
+
+        // The spotlight should be opened
+        expect(fireSpy).toHaveBeenCalledWith(Action.OpenSpotlight);
+    });
+
+    it("should open the room directory when the explore button is clicked", async () => {
+        const fireSpy = jest.spyOn(defaultDispatcher, "fire");
+        const user = userEvent.setup();
+        renderComponent();
+
+        // Click on the search button
+        await user.click(screen.getByRole("button", { name: "Explore rooms" }));
+
+        // The spotlight should be opened
+        expect(fireSpy).toHaveBeenCalledWith(Action.ViewRoomDirectory);
+    });
+
+    it("should open the dial pad when the dial button is clicked", async () => {
+        jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
+        const fireSpy = jest.spyOn(defaultDispatcher, "fire");
+        const user = userEvent.setup();
+        renderComponent();
+
+        // Click on the search button
+        await user.click(screen.getByRole("button", { name: "Open dial pad" }));
+
+        // The spotlight should be opened
+        expect(fireSpy).toHaveBeenCalledWith(Action.OpenDialPad);
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4ebe6fc4c704056e16c12a5f92e65ee1b3452b13
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import { mocked } from "jest-mock";
+import { render, screen } from "jest-matrix-react";
+import React from "react";
+
+import {
+    type RoomListViewState,
+    useRoomListViewModel,
+} from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
+import { RoomListView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListView";
+import { mkRoom, stubClient } from "../../../../../test-utils";
+
+jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListViewModel", () => ({
+    useRoomListViewModel: jest.fn(),
+}));
+
+describe("<RoomListView />", () => {
+    const defaultValue: RoomListViewState = {
+        isLoadingRooms: false,
+        rooms: [],
+        primaryFilters: [],
+        createRoom: jest.fn(),
+        createChatRoom: jest.fn(),
+        canCreateRoom: true,
+        activeIndex: undefined,
+    };
+    const matrixClient = stubClient();
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    it("should render the loading room list", () => {
+        mocked(useRoomListViewModel).mockReturnValue({
+            ...defaultValue,
+            isLoadingRooms: true,
+        });
+
+        const roomList = render(<RoomListView />);
+        expect(roomList.container.querySelector(".mx_RoomListSkeleton")).not.toBeNull();
+    });
+
+    it("should render an empty room list", () => {
+        mocked(useRoomListViewModel).mockReturnValue(defaultValue);
+
+        render(<RoomListView />);
+        expect(screen.getByText("No chats yet")).toBeInTheDocument();
+    });
+
+    it("should render a room list", () => {
+        mocked(useRoomListViewModel).mockReturnValue({
+            ...defaultValue,
+            rooms: [mkRoom(matrixClient, "testing room")],
+        });
+
+        render(<RoomListView />);
+        expect(screen.getByRole("grid", { name: "Room list" })).toBeInTheDocument();
+    });
+});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..1e6fb38ab1675200282167e3c9a24e26c2a2eae6
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap
@@ -0,0 +1,254 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<EmptyRoomList /> should display empty state for filter favourite 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      You don't have favourite chat yet
+    </span>
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_description"
+    >
+      You can add a chat to your favourites in the chat settings
+    </span>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should display empty state for filter people 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      You don’t have direct chats with anyone yet
+    </span>
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_description"
+    >
+      You can deselect filters in order to see your other chats
+    </span>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should display empty state for filter rooms 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      You’re not in any room yet
+    </span>
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_description"
+    >
+      You can deselect filters in order to see your other chats
+    </span>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should display the empty state for the invite filter 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      You don't have any unread invites
+    </span>
+    <button
+      class="_button_vczzf_8"
+      data-kind="tertiary"
+      data-size="lg"
+      role="button"
+      tabindex="0"
+    >
+      See all activity
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should display the empty state for the mention filter 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      You don't have any unread mentions
+    </span>
+    <button
+      class="_button_vczzf_8"
+      data-kind="tertiary"
+      data-size="lg"
+      role="button"
+      tabindex="0"
+    >
+      See all activity
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should display the empty state for the unread filter 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      Congrats! You don’t have any unread messages
+    </span>
+    <button
+      class="_button_vczzf_8"
+      data-kind="tertiary"
+      data-size="lg"
+      role="button"
+      tabindex="0"
+    >
+      Show all chats
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should not render the new room button if the user doesn't have the rights to create a room 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      No chats yet
+    </span>
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_description"
+    >
+      Get started by messaging someone
+    </span>
+    <div
+      class="mx_Flex mx_EmptyRoomList_DefaultPlaceholder"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        class="_button_vczzf_8 _has-icon_vczzf_57"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
+          />
+        </svg>
+        New message
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EmptyRoomList /> should render the default placeholder when there is no filter 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_EmptyRoomList_GenericPlaceholder"
+    data-testid="empty-room-list"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_title"
+    >
+      No chats yet
+    </span>
+    <span
+      class="mx_EmptyRoomList_GenericPlaceholder_description"
+    >
+      Get started by messaging someone or by creating a room
+    </span>
+    <div
+      class="mx_Flex mx_EmptyRoomList_DefaultPlaceholder"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        class="_button_vczzf_8 _has-icon_vczzf_57"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
+          />
+        </svg>
+        New message
+      </button>
+      <button
+        class="_button_vczzf_8 _has-icon_vczzf_57"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m8.566 17-.944 4.094q-.086.406-.372.656t-.687.25q-.543 0-.887-.469a1.18 1.18 0 0 1-.2-1.031l.801-3.5H3.158q-.572 0-.916-.484a1.27 1.27 0 0 1-.2-1.078 1.12 1.12 0 0 1 1.116-.938H6.85l1.145-5h-3.12q-.57 0-.915-.484a1.27 1.27 0 0 1-.2-1.078A1.12 1.12 0 0 1 4.875 7h3.691l.945-4.094q.085-.406.372-.656.286-.25.686-.25.544 0 .887.469.345.468.2 1.031l-.8 3.5h4.578l.944-4.094q.085-.406.372-.656.286-.25.687-.25.543 0 .887.469t.2 1.031L17.723 7h3.119q.573 0 .916.484.343.485.2 1.079a1.12 1.12 0 0 1-1.116.937H17.15l-1.145 5h3.12q.57 0 .915.484.343.485.2 1.079a1.12 1.12 0 0 1-1.116.937h-3.691l-.944 4.094q-.087.406-.373.656t-.686.25q-.544 0-.887-.469a1.18 1.18 0 0 1-.2-1.031l.8-3.5zm.573-2.5h4.578l1.144-5h-4.578z"
+          />
+        </svg>
+        New room
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..3ab510bfca9a1897695520ac1afcd8c78242e2f7
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap
@@ -0,0 +1,564 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomList /> should render a room list 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_RoomList"
+    data-testid="room-list"
+  >
+    <div
+      style="overflow: visible; height: 0px; width: 0px;"
+    >
+      <div
+        aria-label="Room list"
+        aria-readonly="true"
+        class="ReactVirtualized__Grid ReactVirtualized__List mx_RoomList_List"
+        role="grid"
+        style="box-sizing: border-box; direction: ltr; height: 1500px; position: relative; width: 1500px; will-change: transform; overflow-x: hidden; overflow-y: hidden;"
+        tabindex="-1"
+      >
+        <div
+          class="ReactVirtualized__Grid__innerScrollContainer"
+          role="row"
+          style="width: auto; height: 480px; max-width: 1500px; max-height: 480px; overflow: hidden; position: relative;"
+        >
+          <button
+            aria-label="Open room room0"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 0px; width: 100%;"
+            tabindex="0"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="2"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room0"
+                  >
+                    room0
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room1"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 48px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="3"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room1"
+                  >
+                    room1
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room2"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 96px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="4"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room2"
+                  >
+                    room2
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room3"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 144px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="5"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room3"
+                  >
+                    room3
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room4"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 192px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="6"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room4"
+                  >
+                    room4
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room5"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 240px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="1"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room5"
+                  >
+                    room5
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room6"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 288px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="2"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room6"
+                  >
+                    room6
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room7"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 336px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="3"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room7"
+                  >
+                    room7
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room8"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 384px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="4"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room8"
+                  >
+                    room8
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+          <button
+            aria-label="Open room room9"
+            aria-selected="false"
+            class="mx_RoomListItemView"
+            role="gridcell"
+            style="height: 48px; left: 0px; position: absolute; top: 432px; width: 100%;"
+            tabindex="-1"
+            type="button"
+          >
+            <div
+              class="mx_Flex mx_RoomListItemView_container"
+              style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+            >
+              <span
+                aria-label="Avatar"
+                class="_avatar_1qbcf_8 mx_BaseAvatar"
+                data-color="5"
+                data-testid="avatar-img"
+                data-type="round"
+                style="--cpd-avatar-size: 32px;"
+              >
+                <img
+                  alt=""
+                  class="_image_1qbcf_41"
+                  data-type="round"
+                  height="32px"
+                  loading="lazy"
+                  referrerpolicy="no-referrer"
+                  src="http://this.is.a.url/avatar.url/room.png"
+                  width="32px"
+                />
+              </span>
+              <div
+                class="mx_Flex mx_RoomListItemView_content"
+                style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+              >
+                <div
+                  class="mx_RoomListItemView_text"
+                >
+                  <div
+                    class="mx_RoomListItemView_roomName"
+                    title="room9"
+                  >
+                    room9
+                  </div>
+                  <div
+                    class="mx_RoomListItemView_messagePreview"
+                  />
+                </div>
+              </div>
+            </div>
+          </button>
+        </div>
+      </div>
+    </div>
+    <div
+      class="resize-triggers"
+    >
+      <div
+        class="expand-trigger"
+      >
+        <div
+          style="width: 1501px; height: 1501px;"
+        />
+      </div>
+      <div
+        class="contract-trigger"
+      />
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListHeaderView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListHeaderView-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..8a3caa1587771b533562b9f4f7b6fb653235b5e9
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListHeaderView-test.tsx.snap
@@ -0,0 +1,594 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListHeaderView /> compose menu should display the compose menu 1`] = `
+<DocumentFragment>
+  <header
+    aria-label="Room options"
+    class="mx_Flex mx_RoomListHeaderView"
+    data-testid="room-list-header"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListHeaderView_title"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        title="title"
+      >
+        title
+      </h1>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Open space menu"
+        class="_icon-button_m2erp_8 mx_SpaceMenu_button"
+        data-state="closed"
+        id="radix-«rc»"
+        role="button"
+        style="--cpd-icon-button-size: 20px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_Flex"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Room Options"
+        aria-labelledby="«rg»"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«re»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+            />
+          </svg>
+        </div>
+      </button>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Add"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«rl»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+              fill-rule="evenodd"
+            />
+            <path
+              d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+  </header>
+</DocumentFragment>
+`;
+
+exports[`<RoomListHeaderView /> compose menu should not display the compose menu 1`] = `
+<DocumentFragment>
+  <header
+    aria-label="Room options"
+    class="mx_Flex mx_RoomListHeaderView"
+    data-testid="room-list-header"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListHeaderView_title"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        title="title"
+      >
+        title
+      </h1>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Open space menu"
+        class="_icon-button_m2erp_8 mx_SpaceMenu_button"
+        data-state="closed"
+        id="radix-«ro»"
+        role="button"
+        style="--cpd-icon-button-size: 20px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_Flex"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Room Options"
+        aria-labelledby="«rs»"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«rq»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+            />
+          </svg>
+        </div>
+      </button>
+      <button
+        aria-label="New message"
+        class="_icon-button_m2erp_8"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+              fill-rule="evenodd"
+            />
+            <path
+              d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+  </header>
+</DocumentFragment>
+`;
+
+exports[`<RoomListHeaderView /> should render 'room options' button 1`] = `
+<DocumentFragment>
+  <header
+    aria-label="Room options"
+    class="mx_Flex mx_RoomListHeaderView"
+    data-testid="room-list-header"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListHeaderView_title"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        title="title"
+      >
+        title
+      </h1>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Open space menu"
+        class="_icon-button_m2erp_8 mx_SpaceMenu_button"
+        data-state="closed"
+        id="radix-«r0»"
+        role="button"
+        style="--cpd-icon-button-size: 20px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_Flex"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Room Options"
+        aria-labelledby="«r4»"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r2»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+            />
+          </svg>
+        </div>
+      </button>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Add"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r9»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+              fill-rule="evenodd"
+            />
+            <path
+              d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+  </header>
+</DocumentFragment>
+`;
+
+exports[`<RoomListHeaderView /> space menu should display the space menu 1`] = `
+<DocumentFragment>
+  <header
+    aria-label="Room options"
+    class="mx_Flex mx_RoomListHeaderView"
+    data-testid="room-list-header"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListHeaderView_title"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        title="title"
+      >
+        title
+      </h1>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Open space menu"
+        class="_icon-button_m2erp_8 mx_SpaceMenu_button"
+        data-state="closed"
+        id="radix-«r28»"
+        role="button"
+        style="--cpd-icon-button-size: 20px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+    <div
+      class="mx_Flex"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Room Options"
+        aria-labelledby="«r2c»"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r2a»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+            />
+          </svg>
+        </div>
+      </button>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Add"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r2h»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+              fill-rule="evenodd"
+            />
+            <path
+              d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+  </header>
+</DocumentFragment>
+`;
+
+exports[`<RoomListHeaderView /> space menu should not display the space menu 1`] = `
+<DocumentFragment>
+  <header
+    aria-label="Room options"
+    class="mx_Flex mx_RoomListHeaderView"
+    data-testid="room-list-header"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListHeaderView_title"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+    >
+      <h1
+        title="title"
+      >
+        title
+      </h1>
+    </div>
+    <div
+      class="mx_Flex"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Room Options"
+        aria-labelledby="«r2m»"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r2k»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+            />
+          </svg>
+        </div>
+      </button>
+      <button
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
+        aria-label="Add"
+        class="_icon-button_m2erp_8"
+        data-state="closed"
+        id="radix-«r2r»"
+        role="button"
+        style="--cpd-icon-button-size: 32px;"
+        tabindex="0"
+        type="button"
+      >
+        <div
+          class="_indicator-icon_zr2a0_17"
+          style="--cpd-icon-button-size: 100%;"
+        >
+          <svg
+            color="var(--cpd-color-icon-secondary)"
+            fill="currentColor"
+            height="1em"
+            viewBox="0 0 24 24"
+            width="1em"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+              fill-rule="evenodd"
+            />
+            <path
+              d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+            />
+          </svg>
+        </div>
+      </button>
+    </div>
+  </header>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..7bb17cbd4fb0d7917e2b48a9abc3d58b2d0c4a0e
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap
@@ -0,0 +1,151 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListItemMenuView /> should render the more options menu 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListItemMenuView"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      aria-disabled="false"
+      aria-expanded="false"
+      aria-haspopup="menu"
+      aria-label="More Options"
+      aria-labelledby="«r2»"
+      class="_icon-button_m2erp_8"
+      data-state="closed"
+      id="radix-«r0»"
+      role="button"
+      style="--cpd-icon-button-size: 24px;"
+      tabindex="0"
+      type="button"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
+          />
+        </svg>
+      </div>
+    </button>
+    <button
+      aria-disabled="false"
+      aria-expanded="false"
+      aria-haspopup="menu"
+      aria-label="Notification options"
+      aria-labelledby="«r9»"
+      class="_icon-button_m2erp_8"
+      data-state="closed"
+      id="radix-«r7»"
+      role="button"
+      style="--cpd-icon-button-size: 24px;"
+      tabindex="0"
+      type="button"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m4.917 2.083 17 17a1 1 0 0 1-1.414 1.414L19.006 19H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-2.034 1.096-3.91L3.504 3.498a1 1 0 0 1 1.414-1.414M19 13.35 9.136 3.484C9.93 3.181 10.874 3 12 3c7 0 7 7 7 7z"
+          />
+          <path
+            d="M10 20h4a2 2 0 0 1-4 0"
+          />
+        </svg>
+      </div>
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomListItemMenuView /> should render the notification options menu 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListItemMenuView"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      aria-disabled="false"
+      aria-expanded="false"
+      aria-haspopup="menu"
+      aria-label="More Options"
+      aria-labelledby="«ri»"
+      class="_icon-button_m2erp_8"
+      data-state="closed"
+      id="radix-«rg»"
+      role="button"
+      style="--cpd-icon-button-size: 24px;"
+      tabindex="0"
+      type="button"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
+          />
+        </svg>
+      </div>
+    </button>
+    <button
+      aria-disabled="false"
+      aria-expanded="false"
+      aria-haspopup="menu"
+      aria-label="Notification options"
+      aria-labelledby="«rp»"
+      class="_icon-button_m2erp_8"
+      data-state="closed"
+      id="radix-«rn»"
+      role="button"
+      style="--cpd-icon-button-size: 24px;"
+      tabindex="0"
+      type="button"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m4.917 2.083 17 17a1 1 0 0 1-1.414 1.414L19.006 19H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-2.034 1.096-3.91L3.504 3.498a1 1 0 0 1 1.414-1.414M19 13.35 9.136 3.484C9.93 3.181 10.874 3 12 3c7 0 7 7 7 7z"
+          />
+          <path
+            d="M10 20h4a2 2 0 0 1-4 0"
+          />
+        </svg>
+      </div>
+    </button>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..4340cb420012caec4979e16790b9851e0d13c89b
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap
@@ -0,0 +1,235 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListItemView /> should be selected if isSelected=true 1`] = `
+<DocumentFragment>
+  <button
+    aria-label="Open room room1"
+    aria-selected="true"
+    class="mx_RoomListItemView mx_RoomListItemView_selected"
+    tabindex="-1"
+    type="button"
+  >
+    <div
+      class="mx_Flex mx_RoomListItemView_container"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <span
+        aria-label="Avatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="3"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 32px;"
+      >
+        <img
+          alt=""
+          class="_image_1qbcf_41"
+          data-type="round"
+          height="32px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="http://this.is.a.url/avatar.url/room.png"
+          width="32px"
+        />
+      </span>
+      <div
+        class="mx_Flex mx_RoomListItemView_content"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <div
+          class="mx_RoomListItemView_text"
+        >
+          <div
+            class="mx_RoomListItemView_roomName"
+            title="room1"
+          >
+            room1
+          </div>
+          <div
+            class="mx_RoomListItemView_messagePreview"
+          />
+        </div>
+      </div>
+    </div>
+  </button>
+</DocumentFragment>
+`;
+
+exports[`<RoomListItemView /> should display notification decoration 1`] = `
+<DocumentFragment>
+  <button
+    aria-label="Open room room1"
+    aria-selected="false"
+    class="mx_RoomListItemView"
+    tabindex="-1"
+    type="button"
+  >
+    <div
+      class="mx_Flex mx_RoomListItemView_container"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <span
+        aria-label="Avatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="3"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 32px;"
+      >
+        <img
+          alt=""
+          class="_image_1qbcf_41"
+          data-type="round"
+          height="32px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="http://this.is.a.url/avatar.url/room.png"
+          width="32px"
+        />
+      </span>
+      <div
+        class="mx_Flex mx_RoomListItemView_content"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <div
+          class="mx_RoomListItemView_text"
+        >
+          <div
+            class="mx_RoomListItemView_roomName"
+            title="room1"
+          >
+            room1
+          </div>
+          <div
+            class="mx_RoomListItemView_messagePreview"
+          />
+        </div>
+        <div
+          aria-hidden="true"
+          class="mx_Flex"
+          data-testid="notification-decoration"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+        >
+          <span
+            class="_unread-counter_9mg0k_8"
+          >
+            1
+          </span>
+        </div>
+      </div>
+    </div>
+  </button>
+</DocumentFragment>
+`;
+
+exports[`<RoomListItemView /> should render a room item 1`] = `
+<DocumentFragment>
+  <button
+    aria-label="Open room room1"
+    aria-selected="false"
+    class="mx_RoomListItemView"
+    tabindex="-1"
+    type="button"
+  >
+    <div
+      class="mx_Flex mx_RoomListItemView_container"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <span
+        aria-label="Avatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="3"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 32px;"
+      >
+        <img
+          alt=""
+          class="_image_1qbcf_41"
+          data-type="round"
+          height="32px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="http://this.is.a.url/avatar.url/room.png"
+          width="32px"
+        />
+      </span>
+      <div
+        class="mx_Flex mx_RoomListItemView_content"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <div
+          class="mx_RoomListItemView_text"
+        >
+          <div
+            class="mx_RoomListItemView_roomName"
+            title="room1"
+          >
+            room1
+          </div>
+          <div
+            class="mx_RoomListItemView_messagePreview"
+          />
+        </div>
+      </div>
+    </div>
+  </button>
+</DocumentFragment>
+`;
+
+exports[`<RoomListItemView /> should render a room item with a message preview 1`] = `
+<DocumentFragment>
+  <button
+    aria-label="Open room room1"
+    aria-selected="false"
+    class="mx_RoomListItemView"
+    tabindex="-1"
+    type="button"
+  >
+    <div
+      class="mx_Flex mx_RoomListItemView_container"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <span
+        aria-label="Avatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
+        data-color="3"
+        data-testid="avatar-img"
+        data-type="round"
+        style="--cpd-avatar-size: 32px;"
+      >
+        <img
+          alt=""
+          class="_image_1qbcf_41"
+          data-type="round"
+          height="32px"
+          loading="lazy"
+          referrerpolicy="no-referrer"
+          src="http://this.is.a.url/avatar.url/room.png"
+          width="32px"
+        />
+      </span>
+      <div
+        class="mx_Flex mx_RoomListItemView_content"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <div
+          class="mx_RoomListItemView_text"
+        >
+          <div
+            class="mx_RoomListItemView_roomName"
+            title="room1"
+          >
+            room1
+          </div>
+          <div
+            class="mx_RoomListItemView_messagePreview"
+          >
+            The message looks list this
+          </div>
+        </div>
+      </div>
+    </div>
+  </button>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..aafcede2ad10b3cd63fe1ae57b63f2b6b40d5547
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListOptionsMenu /> should match snapshot 1`] = `
+<DocumentFragment>
+  <button
+    aria-disabled="false"
+    aria-expanded="false"
+    aria-haspopup="menu"
+    aria-label="Room Options"
+    aria-labelledby="«r2»"
+    class="_icon-button_m2erp_8"
+    data-state="closed"
+    id="radix-«r0»"
+    role="button"
+    style="--cpd-icon-button-size: 32px;"
+    tabindex="0"
+    type="button"
+  >
+    <div
+      class="_indicator-icon_zr2a0_17"
+      style="--cpd-icon-button-size: 100%;"
+    >
+      <svg
+        color="var(--cpd-color-icon-secondary)"
+        fill="currentColor"
+        height="1em"
+        viewBox="0 0 24 24"
+        width="1em"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+        />
+      </svg>
+    </div>
+  </button>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..1cfb1708294005d24bc340aa1b001a4e41f11297
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap
@@ -0,0 +1,433 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListPanel /> should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = `
+<DocumentFragment>
+  <section
+    class="mx_Flex mx_RoomListPanel"
+    data-testid="room-list-panel"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <header
+      aria-label="Room options"
+      class="mx_Flex mx_RoomListHeaderView"
+      data-testid="room-list-header"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <div
+        class="mx_Flex mx_RoomListHeaderView_title"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+      >
+        <h1
+          title="Home"
+        >
+          Home
+        </h1>
+      </div>
+      <div
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <button
+          aria-disabled="false"
+          aria-expanded="false"
+          aria-haspopup="menu"
+          aria-label="Room Options"
+          aria-labelledby="«rc»"
+          class="_icon-button_m2erp_8"
+          data-state="closed"
+          id="radix-«ra»"
+          role="button"
+          style="--cpd-icon-button-size: 32px;"
+          tabindex="0"
+          type="button"
+        >
+          <div
+            class="_indicator-icon_zr2a0_17"
+            style="--cpd-icon-button-size: 100%;"
+          >
+            <svg
+              color="var(--cpd-color-icon-secondary)"
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+              />
+            </svg>
+          </div>
+        </button>
+        <button
+          aria-label="New message"
+          class="_icon-button_m2erp_8"
+          role="button"
+          style="--cpd-icon-button-size: 32px;"
+          tabindex="0"
+        >
+          <div
+            class="_indicator-icon_zr2a0_17"
+            style="--cpd-icon-button-size: 100%;"
+          >
+            <svg
+              color="var(--cpd-color-icon-secondary)"
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                clip-rule="evenodd"
+                d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+                fill-rule="evenodd"
+              />
+              <path
+                d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+              />
+            </svg>
+          </div>
+        </button>
+      </div>
+    </header>
+    <ul
+      aria-label="Room list filters"
+      class="mx_Flex mx_RoomListPrimaryFilters"
+      role="listbox"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
+    >
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Unreads
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          People
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Rooms
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Mentions
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Invites
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Favourites
+        </button>
+      </li>
+    </ul>
+    <div
+      class="mx_RoomListSkeleton"
+    />
+  </section>
+</DocumentFragment>
+`;
+
+exports[`<RoomListPanel /> should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = `
+<DocumentFragment>
+  <section
+    class="mx_Flex mx_RoomListPanel"
+    data-testid="room-list-panel"
+    style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: stretch; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="mx_Flex mx_RoomListSearch"
+      role="search"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+    >
+      <button
+        class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
+          />
+        </svg>
+        <span
+          class="mx_Flex"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+        >
+          <span
+            class="mx_RoomListSearch_search_text"
+          >
+            Search
+          </span>
+          <kbd>
+            Ctrl K
+          </kbd>
+        </span>
+      </button>
+      <button
+        aria-label="Explore rooms"
+        class="_button_vczzf_8 mx_RoomListSearch_button _has-icon_vczzf_57 _icon-only_vczzf_50"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
+          />
+        </svg>
+      </button>
+    </div>
+    <header
+      aria-label="Room options"
+      class="mx_Flex mx_RoomListHeaderView"
+      data-testid="room-list-header"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+    >
+      <div
+        class="mx_Flex mx_RoomListHeaderView_title"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+      >
+        <h1
+          title="Home"
+        >
+          Home
+        </h1>
+      </div>
+      <div
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+      >
+        <button
+          aria-disabled="false"
+          aria-expanded="false"
+          aria-haspopup="menu"
+          aria-label="Room Options"
+          aria-labelledby="«r2»"
+          class="_icon-button_m2erp_8"
+          data-state="closed"
+          id="radix-«r0»"
+          role="button"
+          style="--cpd-icon-button-size: 32px;"
+          tabindex="0"
+          type="button"
+        >
+          <div
+            class="_indicator-icon_zr2a0_17"
+            style="--cpd-icon-button-size: 100%;"
+          >
+            <svg
+              color="var(--cpd-color-icon-secondary)"
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M5 7a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2zm3 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2zm2 5a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1"
+              />
+            </svg>
+          </div>
+        </button>
+        <button
+          aria-disabled="false"
+          aria-expanded="false"
+          aria-haspopup="menu"
+          aria-label="Add"
+          class="_icon-button_m2erp_8"
+          data-state="closed"
+          id="radix-«r7»"
+          role="button"
+          style="--cpd-icon-button-size: 32px;"
+          tabindex="0"
+          type="button"
+        >
+          <div
+            class="_indicator-icon_zr2a0_17"
+            style="--cpd-icon-button-size: 100%;"
+          >
+            <svg
+              color="var(--cpd-color-icon-secondary)"
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                clip-rule="evenodd"
+                d="M16.937 2.82a2 2 0 0 1 2.828 0l1.415 1.414a2 2 0 0 1 0 2.829l-7.071 7.07c-.195.196-.42.342-.66.44a1 1 0 0 1-.168.072l-3.993 1.331a1 1 0 0 1-1.265-1.265l1.331-3.992q.03-.09.073-.168m10.338-4.903-6.717 6.718-1.414-1.414 6.717-6.718z"
+                fill-rule="evenodd"
+              />
+              <path
+                d="M3 5a2 2 0 0 1 2-2h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+              />
+            </svg>
+          </div>
+        </button>
+      </div>
+    </header>
+    <ul
+      aria-label="Room list filters"
+      class="mx_Flex mx_RoomListPrimaryFilters"
+      role="listbox"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
+    >
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Unreads
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          People
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Rooms
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Mentions
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Invites
+        </button>
+      </li>
+      <li
+        aria-selected="false"
+        role="option"
+      >
+        <button
+          aria-selected="false"
+          class="_chat-filter_5qdp0_8"
+          role="button"
+          tabindex="0"
+        >
+          Favourites
+        </button>
+      </li>
+    </ul>
+    <div
+      class="mx_RoomListSkeleton"
+    />
+  </section>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..6c705944b86a78a31f3de234662dd35f089c9463
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListPrimaryFilters /> should render primary filters 1`] = `
+<DocumentFragment>
+  <ul
+    aria-label="Room list filters"
+    class="mx_Flex mx_RoomListPrimaryFilters"
+    role="listbox"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: wrap;"
+  >
+    <li
+      aria-selected="false"
+      role="option"
+    >
+      <button
+        aria-selected="false"
+        class="_chat-filter_5qdp0_8"
+        role="button"
+        tabindex="0"
+      >
+        People
+      </button>
+    </li>
+    <li
+      aria-selected="true"
+      role="option"
+    >
+      <button
+        aria-selected="true"
+        class="_chat-filter_5qdp0_8"
+        role="button"
+        tabindex="0"
+      >
+        Rooms
+      </button>
+    </li>
+  </ul>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListSearch-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListSearch-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..e52842ba3a8eadc989e89c7f25d7eca12c9aab89
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListSearch-test.tsx.snap
@@ -0,0 +1,240 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RoomListSearch /> should display search and explore buttons 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListSearch"
+    role="search"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
+        />
+      </svg>
+      <span
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+      >
+        <span
+          class="mx_RoomListSearch_search_text"
+        >
+          Search
+        </span>
+        <kbd>
+          Ctrl K
+        </kbd>
+      </span>
+    </button>
+    <button
+      aria-label="Explore rooms"
+      class="_button_vczzf_8 mx_RoomListSearch_button _has-icon_vczzf_57 _icon-only_vczzf_50"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
+        />
+      </svg>
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomListSearch /> should display the dial button when the PTSN protocol is not supported 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListSearch"
+    role="search"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
+        />
+      </svg>
+      <span
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+      >
+        <span
+          class="mx_RoomListSearch_search_text"
+        >
+          Search
+        </span>
+        <kbd>
+          Ctrl K
+        </kbd>
+      </span>
+    </button>
+    <button
+      aria-label="Open dial pad"
+      class="_button_vczzf_8 mx_RoomListSearch_button _has-icon_vczzf_57 _icon-only_vczzf_50"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
+        />
+      </svg>
+    </button>
+    <button
+      aria-label="Explore rooms"
+      class="_button_vczzf_8 mx_RoomListSearch_button _has-icon_vczzf_57 _icon-only_vczzf_50"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
+        />
+      </svg>
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomListSearch /> should hide the explore button when UIComponent.ExploreRooms is disabled 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListSearch"
+    role="search"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
+        />
+      </svg>
+      <span
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+      >
+        <span
+          class="mx_RoomListSearch_search_text"
+        >
+          Search
+        </span>
+        <kbd>
+          Ctrl K
+        </kbd>
+      </span>
+    </button>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<RoomListSearch /> should hide the explore button when the active space is not MetaSpace.Home 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex mx_RoomListSearch"
+    role="search"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
+  >
+    <button
+      class="_button_vczzf_8 mx_RoomListSearch_search _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
+        />
+      </svg>
+      <span
+        class="mx_Flex"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+      >
+        <span
+          class="mx_RoomListSearch_search_text"
+        >
+          Search
+        </span>
+        <kbd>
+          Ctrl K
+        </kbd>
+      </span>
+    </button>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/RoomPreviewBar-test.tsx b/test/unit-tests/components/views/rooms/RoomPreviewBar-test.tsx
index 112b088709cb4467a2487fd136d7f3505f3c2d70..fb6691af7862bbec66634881efb580e7ae794de9 100644
--- a/test/unit-tests/components/views/rooms/RoomPreviewBar-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomPreviewBar-test.tsx
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { render, fireEvent, RenderResult, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
-import { Room, RoomMember, MatrixError, IContent } from "matrix-js-sdk/src/matrix";
+import React, { type ComponentProps } from "react";
+import { render, fireEvent, type RenderResult, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
+import { Room, type RoomMember, MatrixError, type IContent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { withClientContextRenderOptions, stubClient } from "../../../../test-utils";
@@ -279,37 +279,31 @@ describe("<RoomPreviewBar />", () => {
                 });
 
                 it("renders join and reject action buttons correctly", () => {
-                    const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
+                    const component = getComponent({ inviterName, room, onJoinClick, onDeclineClick: onRejectClick });
                     expect(getActions(component)).toMatchSnapshot();
                 });
 
-                it("renders reject and ignore action buttons when handler is provided", () => {
-                    const onRejectAndIgnoreClick = jest.fn();
+                it("renders join and reject action buttons in reverse order when room can previewed", () => {
+                    // when room is previewed action buttons are rendered left to right, with primary on the right
                     const component = getComponent({
                         inviterName,
                         room,
                         onJoinClick,
-                        onRejectClick,
-                        onRejectAndIgnoreClick,
+                        onDeclineClick: onRejectClick,
+                        canPreview: true,
                     });
                     expect(getActions(component)).toMatchSnapshot();
                 });
 
-                it("renders join and reject action buttons in reverse order when room can previewed", () => {
-                    // when room is previewed action buttons are rendered left to right, with primary on the right
-                    const component = getComponent({ inviterName, room, onJoinClick, onRejectClick, canPreview: true });
-                    expect(getActions(component)).toMatchSnapshot();
-                });
-
                 it("joins room on primary button click", () => {
-                    const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
+                    const component = getComponent({ inviterName, room, onJoinClick, onDeclineClick: onRejectClick });
                     fireEvent.click(getPrimaryActionButton(component)!);
 
                     expect(onJoinClick).toHaveBeenCalled();
                 });
 
                 it("rejects invite on secondary button click", () => {
-                    const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
+                    const component = getComponent({ inviterName, room, onJoinClick, onDeclineClick: onRejectClick });
                     fireEvent.click(getSecondaryActionButton(component)!);
 
                     expect(onRejectClick).toHaveBeenCalled();
@@ -337,18 +331,6 @@ describe("<RoomPreviewBar />", () => {
                     const component = getComponent({ inviterName, room });
                     expect(getMessage(component)).toMatchSnapshot();
                 });
-
-                it("renders join and reject action buttons with correct labels", () => {
-                    const onRejectAndIgnoreClick = jest.fn();
-                    const component = getComponent({
-                        inviterName,
-                        room,
-                        onJoinClick,
-                        onRejectAndIgnoreClick,
-                        onRejectClick,
-                    });
-                    expect(getActions(component)).toMatchSnapshot();
-                });
             });
         });
 
@@ -364,7 +346,7 @@ describe("<RoomPreviewBar />", () => {
                 async () => {
                     const onJoinClick = jest.fn();
                     const onRejectClick = jest.fn();
-                    const component = getComponent({ ...props, onJoinClick, onRejectClick });
+                    const component = getComponent({ ...props, onJoinClick, onDeclineClick: onRejectClick });
                     await waitFor(() => expect(getPrimaryActionButton(component)).toBeTruthy());
                     if (expectSecondaryButton) expect(getSecondaryActionButton(component)).toBeFalsy();
                     fireEvent.click(getPrimaryActionButton(component)!);
diff --git a/test/unit-tests/components/views/rooms/RoomPreviewCard-test.tsx b/test/unit-tests/components/views/rooms/RoomPreviewCard-test.tsx
index b80ce4bb5b6e4eed65c2e6d64eff43bbc34024d3..e12fabeb3fe801a78fb1cc39c980e64cc920ecaa 100644
--- a/test/unit-tests/components/views/rooms/RoomPreviewCard-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomPreviewCard-test.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 import { render, screen, act } from "jest-matrix-react";
 import { PendingEventOrdering, Room, RoomStateEvent, RoomType } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
@@ -44,9 +44,13 @@ describe("RoomPreviewCard", () => {
         client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
 
         enabledFeatures = [];
-        jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any =>
-            enabledFeatures.includes(settingName) ? true : undefined,
-        );
+        const origFn = SettingsStore.getValue;
+        jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any => {
+            if (enabledFeatures.includes(settingName)) {
+                return true;
+            }
+            return origFn(settingName);
+        });
     });
 
     afterEach(() => {
diff --git a/test/unit-tests/components/views/rooms/RoomTile-test.tsx b/test/unit-tests/components/views/rooms/RoomTile-test.tsx
index e5169d9121461b6c4944b96e5b417a2bc870f36f..68b47a037a5720be1d088aa4ec2c7b110e34ce2e 100644
--- a/test/unit-tests/components/views/rooms/RoomTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomTile-test.tsx
@@ -7,13 +7,19 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, screen, act, RenderResult } from "jest-matrix-react";
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent, Thread } from "matrix-js-sdk/src/matrix";
+import { render, screen, act, type RenderResult } from "jest-matrix-react";
+import { mocked, type Mocked } from "jest-mock";
+import {
+    type MatrixClient,
+    PendingEventOrdering,
+    Room,
+    RoomStateEvent,
+    type Thread,
+    type RoomMember,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { Widget } from "matrix-widget-api";
 
-import type { RoomMember } from "matrix-js-sdk/src/matrix";
 import type { ClientWidgetApi } from "matrix-widget-api";
 import {
     stubClient,
@@ -31,7 +37,7 @@ import RoomTile from "../../../../../src/components/views/rooms/RoomTile";
 import { DefaultTagID } from "../../../../../src/stores/room-list/models";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import PlatformPeg from "../../../../../src/PlatformPeg";
-import BasePlatform from "../../../../../src/BasePlatform";
+import type BasePlatform from "../../../../../src/BasePlatform";
 import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
 import { TestSdkContext } from "../../../TestSdkContext";
 import { SDKContext } from "../../../../../src/contexts/SDKContext";
@@ -40,7 +46,6 @@ import { UIComponent } from "../../../../../src/settings/UIFeature";
 import { MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
-import { ConnectionState } from "../../../../../src/models/Call";
 
 jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
     shouldShowComponent: jest.fn(),
@@ -210,41 +215,10 @@ describe("RoomTile", () => {
             it("tracks connection state", async () => {
                 renderRoomTile();
                 screen.getByText("Video");
-
-                let completeWidgetLoading: () => void = () => {};
-                const widgetLoadingCompleted = new Promise<void>((resolve) => (completeWidgetLoading = resolve));
-
-                // Insert an await point in the connection method so we can inspect
-                // the intermediate connecting state
-                let completeConnection: () => void = () => {};
-                const connectionCompleted = new Promise<void>((resolve) => (completeConnection = resolve));
-
-                let completeLobby: () => void = () => {};
-                const lobbyCompleted = new Promise<void>((resolve) => (completeLobby = resolve));
-
-                jest.spyOn(call, "performConnection").mockImplementation(async () => {
-                    call.setConnectionState(ConnectionState.WidgetLoading);
-                    await widgetLoadingCompleted;
-                    call.setConnectionState(ConnectionState.Lobby);
-                    await lobbyCompleted;
-                    call.setConnectionState(ConnectionState.Connecting);
-                    await connectionCompleted;
-                });
-
-                await Promise.all([
-                    (async () => {
-                        await screen.findByText("Loading…");
-                        completeWidgetLoading();
-                        await screen.findByText("Lobby");
-                        completeLobby();
-                        await screen.findByText("Joining…");
-                        completeConnection();
-                        await screen.findByText("Joined");
-                    })(),
-                    call.start(),
-                ]);
-
-                await Promise.all([screen.findByText("Video"), call.disconnect()]);
+                await act(() => call.start());
+                screen.getByText("Joined");
+                await act(() => call.disconnect());
+                screen.getByText("Video");
             });
 
             it("tracks participants", () => {
diff --git a/test/unit-tests/components/views/rooms/SearchResultTile-test.tsx b/test/unit-tests/components/views/rooms/SearchResultTile-test.tsx
index c12aa15eaeb6e6acdb555c8f71c5c69c712aebfd..a605650269a6a8868acc147e8208a626de5228e6 100644
--- a/test/unit-tests/components/views/rooms/SearchResultTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/SearchResultTile-test.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import * as React from "react";
+import React from "react";
 import { MatrixEvent, Room, EventType } from "matrix-js-sdk/src/matrix";
 import { render, type RenderResult } from "jest-matrix-react";
 
-import { stubClient } from "../../../../test-utils";
+import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
 import SearchResultTile from "../../../../../src/components/views/rooms/SearchResultTile";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 
@@ -28,7 +28,10 @@ describe("SearchResultTile", () => {
     });
 
     function renderComponent(props: Partial<Props>): RenderResult {
-        return render(<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />);
+        return render(
+            <SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />,
+            withClientContextRenderOptions(MatrixClientPeg.safeGet()),
+        );
     }
 
     it("Sets up appropriate callEventGrouper for m.call. events", () => {
diff --git a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx
index ad0ec2afdf215918d33b81a316e01ed1cfd836d1..412065353d181b00a94119fe48e3a63e66d33f3b 100644
--- a/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render, waitFor } from "jest-matrix-react";
-import { IContent, MatrixClient, MsgType } from "matrix-js-sdk/src/matrix";
+import { type IContent, type MatrixClient, MsgType } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 import userEvent from "@testing-library/user-event";
 
@@ -26,7 +26,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
 import DocumentOffset from "../../../../../src/editor/offset";
 import { Layout } from "../../../../../src/settings/enums/Layout";
-import { IRoomState, MainSplitContentType } from "../../../../../src/components/structures/RoomView";
+import { type IRoomState, MainSplitContentType } from "../../../../../src/components/structures/RoomView";
 import { mockPlatformPeg } from "../../../../test-utils/platform";
 import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
 import { addTextToComposer } from "../../../../test-utils/composer";
@@ -73,7 +73,6 @@ describe("<SendMessageComposer/>", () => {
         canSelfRedact: false,
         resizing: false,
         narrow: false,
-        activeCall: null,
         msc3946ProcessDynamicPredecessor: false,
         canAskToJoin: false,
         promptAskToJoin: false,
@@ -195,7 +194,7 @@ describe("<SendMessageComposer/>", () => {
                 "m.mentions": { user_ids: ["@bob:test"] },
             });
 
-            // It also adds any other mentioned users, but removes yourself.
+            // It no longer adds any other mentioned users
             replyToEvent = mkEvent({
                 type: "m.room.message",
                 user: "@bob:test",
@@ -206,7 +205,7 @@ describe("<SendMessageComposer/>", () => {
             content = {};
             attachMentions("@alice:test", content, model, replyToEvent);
             expect(content).toEqual({
-                "m.mentions": { user_ids: ["@bob:test", "@charlie:test"] },
+                "m.mentions": { user_ids: ["@bob:test"] },
             });
         });
 
diff --git a/test/unit-tests/components/views/rooms/ThirdPartyMemberInfo-test.tsx b/test/unit-tests/components/views/rooms/ThirdPartyMemberInfo-test.tsx
index 60541cd30a2a620361d0db5f171ea91ae7b252c5..147e412d8fc1c86bd4c9401fa83d0af8d817c098 100644
--- a/test/unit-tests/components/views/rooms/ThirdPartyMemberInfo-test.tsx
+++ b/test/unit-tests/components/views/rooms/ThirdPartyMemberInfo-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen } from "jest-matrix-react";
-import { EventType, IEvent, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { EventType, type IEvent, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 
 import ThirdPartyMemberInfo from "../../../../../src/components/views/rooms/ThirdPartyMemberInfo";
 import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
diff --git a/test/unit-tests/components/views/rooms/UserIdentityWarning-test.tsx b/test/unit-tests/components/views/rooms/UserIdentityWarning-test.tsx
index eef25269125f11468d74ea160f94419f5a6cfc63..4f0c58f7a4993ee57835c972ef15c7e5bb0cb3f6 100644
--- a/test/unit-tests/components/views/rooms/UserIdentityWarning-test.tsx
+++ b/test/unit-tests/components/views/rooms/UserIdentityWarning-test.tsx
@@ -9,12 +9,12 @@ import React from "react";
 import { sleep } from "matrix-js-sdk/src/utils";
 import {
     EventType,
-    MatrixClient,
+    type MatrixClient,
     MatrixEvent,
-    Room,
+    type Room,
     RoomState,
     RoomStateEvent,
-    RoomMember,
+    type RoomMember,
 } from "matrix-js-sdk/src/matrix";
 import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 import { act, render, screen, waitFor } from "jest-matrix-react";
@@ -37,6 +37,50 @@ function mockRoom(): Room {
     return room;
 }
 
+function mockMembershipForRoom(room: Room, users: string[] | [string, "joined" | "invited"][]): void {
+    const encryptToInvited = room.shouldEncryptForInvitedMembers();
+    const members = users
+        .filter((user) => {
+            if (Array.isArray(user)) {
+                return encryptToInvited || user[1] === "joined";
+            } else {
+                return true;
+            }
+        })
+        .map((id) => {
+            if (Array.isArray(id)) {
+                return mockRoomMember(id[0]);
+            } else {
+                return mockRoomMember(id);
+            }
+        });
+
+    jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue(members);
+
+    jest.spyOn(room, "getMember").mockImplementation((userId) => {
+        return members.find((member) => member.userId === userId) ?? null;
+    });
+}
+
+function emitMembershipChange(client: MatrixClient, userId: string, membership: "join" | "leave" | "invite"): void {
+    const sender = membership === "invite" ? "@carol:example.org" : userId;
+    client.emit(
+        RoomStateEvent.Events,
+        new MatrixEvent({
+            event_id: "$event_id",
+            type: EventType.RoomMember,
+            state_key: userId,
+            content: {
+                membership: membership,
+            },
+            room_id: ROOM_ID,
+            sender: sender,
+        }),
+        dummyRoomState(),
+        null,
+    );
+}
+
 function mockRoomMember(userId: string, name?: string): RoomMember {
     return {
         userId,
@@ -97,18 +141,58 @@ describe("UserIdentityWarning", () => {
         jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
             new UserVerificationStatus(false, false, false, true),
         );
-        crypto.pinCurrentUserIdentity = jest.fn();
+        crypto.pinCurrentUserIdentity = jest.fn().mockResolvedValue(undefined);
         renderComponent(client, room);
 
         await waitFor(() =>
-            expect(
-                getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-            ).toBeInTheDocument(),
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
         );
         await userEvent.click(screen.getByRole("button")!);
         await waitFor(() => expect(crypto.pinCurrentUserIdentity).toHaveBeenCalledWith("@alice:example.org"));
     });
 
+    // This tests the basic functionality of the component.  If we have a room
+    // member whose identity is in verification violation, we should display a warning.  When
+    // the "Withdraw verification" button gets pressed, it should call `withdrawVerification`.
+    it("displays a warning when a user's identity is in verification violation", async () => {
+        jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
+            mockRoomMember("@alice:example.org", "Alice"),
+        ]);
+        const crypto = client.getCrypto()!;
+        jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
+            new UserVerificationStatus(false, true, false, true),
+        );
+        crypto.withdrawVerificationRequirement = jest.fn().mockResolvedValue(undefined);
+        renderComponent(client, room);
+
+        await waitFor(() =>
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
+        );
+
+        expect(
+            screen.getByRole("button", {
+                name: "Withdraw verification",
+            }),
+        ).toBeInTheDocument();
+        await userEvent.click(screen.getByRole("button")!);
+        await waitFor(() => expect(crypto.withdrawVerificationRequirement).toHaveBeenCalledWith("@alice:example.org"));
+    });
+
+    it("Should not display a warning if the user was verified and is still verified", async () => {
+        jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
+            mockRoomMember("@alice:example.org", "Alice"),
+        ]);
+        const crypto = client.getCrypto()!;
+        jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
+            new UserVerificationStatus(true, true, false, false),
+        );
+
+        renderComponent(client, room);
+        await sleep(10); // give it some time to finish initialising
+
+        expect(() => getWarningByText("Alice's (@alice:example.org) identity was reset.")).toThrow();
+    });
+
     // We don't display warnings in non-encrypted rooms, but if encryption is
     // enabled, then we should display a warning if there are any users whose
     // identity need accepting.
@@ -124,8 +208,9 @@ describe("UserIdentityWarning", () => {
         );
 
         renderComponent(client, room);
+
         await sleep(10); // give it some time to finish initialising
-        expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
+        expect(() => getWarningByText("Alice's (@alice:example.org) identity was reset.")).toThrow();
 
         // Encryption gets enabled in the room.  We should now warn that Alice's
         // identity changed.
@@ -146,12 +231,55 @@ describe("UserIdentityWarning", () => {
             null,
         );
         await waitFor(() =>
-            expect(
-                getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-            ).toBeInTheDocument(),
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
         );
     });
 
+    describe("Warnings are displayed in consistent order", () => {
+        it("Ensure lexicographic order for prompt", async () => {
+            // members are not returned lexicographic order
+            mockMembershipForRoom(room, ["@b:example.org", "@a:example.org"]);
+
+            const crypto = client.getCrypto()!;
+
+            // All identities needs approval
+            jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
+                new UserVerificationStatus(false, false, false, true),
+            );
+
+            crypto.pinCurrentUserIdentity = jest.fn();
+            renderComponent(client, room);
+
+            await waitFor(() => expect(getWarningByText("@a:example.org's identity was reset.")).toBeInTheDocument());
+        });
+
+        it("Ensure existing prompt stays even if a new violation with lower lexicographic order detected", async () => {
+            mockMembershipForRoom(room, ["@b:example.org"]);
+
+            const crypto = client.getCrypto()!;
+
+            // All identities needs approval
+            jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
+                new UserVerificationStatus(false, false, false, true),
+            );
+
+            crypto.pinCurrentUserIdentity = jest.fn();
+            renderComponent(client, room);
+
+            await waitFor(() => expect(getWarningByText("@b:example.org's identity was reset.")).toBeInTheDocument());
+
+            // Simulate a new member joined with lower lexico order and also in violation
+            mockMembershipForRoom(room, ["@a:example.org", "@b:example.org"]);
+
+            act(() => {
+                emitMembershipChange(client, "@a:example.org", "join");
+            });
+
+            // We should still display the warning for @b:example.org
+            await waitFor(() => expect(getWarningByText("@b:example.org's identity was reset.")).toBeInTheDocument());
+        });
+    });
+
     // When a user's identity needs approval, or has been approved, the display
     // should update appropriately.
     it("updates the display when identity changes", async () => {
@@ -163,35 +291,33 @@ describe("UserIdentityWarning", () => {
         jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
             new UserVerificationStatus(false, false, false, false),
         );
-        renderComponent(client, room);
-        await sleep(10); // give it some time to finish initialising
-        expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
+        await act(async () => {
+            renderComponent(client, room);
+            await sleep(50);
+        });
+
+        expect(() => getWarningByText("Alice's (@alice:example.org) identity was reset.")).toThrow();
 
         // The user changes their identity, so we should show the warning.
         act(() => {
-            client.emit(
-                CryptoEvent.UserTrustStatusChanged,
-                "@alice:example.org",
-                new UserVerificationStatus(false, false, false, true),
-            );
+            const newStatus = new UserVerificationStatus(false, false, false, true);
+            jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(newStatus);
+            client.emit(CryptoEvent.UserTrustStatusChanged, "@alice:example.org", newStatus);
         });
+
         await waitFor(() =>
-            expect(
-                getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-            ).toBeInTheDocument(),
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
         );
 
         // Simulate the user's new identity having been approved, so we no
         // longer show the warning.
         act(() => {
-            client.emit(
-                CryptoEvent.UserTrustStatusChanged,
-                "@alice:example.org",
-                new UserVerificationStatus(false, false, false, false),
-            );
+            const newStatus = new UserVerificationStatus(false, false, false, false);
+            jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(newStatus);
+            client.emit(CryptoEvent.UserTrustStatusChanged, "@alice:example.org", newStatus);
         });
         await waitFor(() =>
-            expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow(),
+            expect(() => getWarningByText("Alice's (@alice:example.org) identity was reset.")).toThrow(),
         );
     });
 
@@ -200,8 +326,7 @@ describe("UserIdentityWarning", () => {
     describe("updates the display when a member joins/leaves", () => {
         it("when invited users can see encrypted messages", async () => {
             // Nobody in the room yet
-            jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
-            jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
+            mockMembershipForRoom(room, []);
             jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(true);
             const crypto = client.getCrypto()!;
             jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
@@ -211,74 +336,38 @@ describe("UserIdentityWarning", () => {
             await sleep(10); // give it some time to finish initialising
 
             // Alice joins.  Her identity needs approval, so we should show a warning.
-            client.emit(
-                RoomStateEvent.Events,
-                new MatrixEvent({
-                    event_id: "$event_id",
-                    type: EventType.RoomMember,
-                    state_key: "@alice:example.org",
-                    content: {
-                        membership: "join",
-                    },
-                    room_id: ROOM_ID,
-                    sender: "@alice:example.org",
-                }),
-                dummyRoomState(),
-                null,
-            );
+            act(() => {
+                mockMembershipForRoom(room, ["@alice:example.org"]);
+                emitMembershipChange(client, "@alice:example.org", "join");
+            });
+
             await waitFor(() =>
-                expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
+                expect(getWarningByText("@alice:example.org's identity was reset.")).toBeInTheDocument(),
             );
 
             // Bob is invited.  His identity needs approval, so we should show a
             // warning for him after Alice's warning is resolved by her leaving.
-            client.emit(
-                RoomStateEvent.Events,
-                new MatrixEvent({
-                    event_id: "$event_id",
-                    type: EventType.RoomMember,
-                    state_key: "@bob:example.org",
-                    content: {
-                        membership: "invite",
-                    },
-                    room_id: ROOM_ID,
-                    sender: "@carol:example.org",
-                }),
-                dummyRoomState(),
-                null,
-            );
+            act(() => {
+                mockMembershipForRoom(room, ["@alice:example.org", "@bob:example.org"]);
+                emitMembershipChange(client, "@bob:example.org", "invite");
+            });
 
             // Alice leaves, so we no longer show her warning, but we will show
             // a warning for Bob.
             act(() => {
-                client.emit(
-                    RoomStateEvent.Events,
-                    new MatrixEvent({
-                        event_id: "$event_id",
-                        type: EventType.RoomMember,
-                        state_key: "@alice:example.org",
-                        content: {
-                            membership: "leave",
-                        },
-                        room_id: ROOM_ID,
-                        sender: "@alice:example.org",
-                    }),
-                    dummyRoomState(),
-                    null,
-                );
+                mockMembershipForRoom(room, ["@bob:example.org"]);
+                emitMembershipChange(client, "@alice:example.org", "leave");
             });
-            await waitFor(() =>
-                expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
-            );
-            await waitFor(() =>
-                expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
-            );
+
+            await waitFor(() => expect(() => getWarningByText("@alice:example.org's identity was reset.")).toThrow());
+            await waitFor(() => expect(getWarningByText("@bob:example.org's identity was reset.")).toBeInTheDocument());
         });
 
         it("when invited users cannot see encrypted messages", async () => {
             // Nobody in the room yet
-            jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
-            jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
+            mockMembershipForRoom(room, []);
+            // jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
+            // jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
             jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
             const crypto = client.getCrypto()!;
             jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
@@ -288,104 +377,57 @@ describe("UserIdentityWarning", () => {
             await sleep(10); // give it some time to finish initialising
 
             // Alice joins.  Her identity needs approval, so we should show a warning.
-            client.emit(
-                RoomStateEvent.Events,
-                new MatrixEvent({
-                    event_id: "$event_id",
-                    type: EventType.RoomMember,
-                    state_key: "@alice:example.org",
-                    content: {
-                        membership: "join",
-                    },
-                    room_id: ROOM_ID,
-                    sender: "@alice:example.org",
-                }),
-                dummyRoomState(),
-                null,
-            );
+            act(() => {
+                mockMembershipForRoom(room, ["@alice:example.org"]);
+                emitMembershipChange(client, "@alice:example.org", "join");
+            });
             await waitFor(() =>
-                expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
+                expect(getWarningByText("@alice:example.org's identity was reset.")).toBeInTheDocument(),
             );
 
             // Bob is invited. His identity needs approval, but we don't encrypt
             // to him, so we won't show a warning. (When Alice leaves, the
             // display won't be updated to show a warningfor Bob.)
-            client.emit(
-                RoomStateEvent.Events,
-                new MatrixEvent({
-                    event_id: "$event_id",
-                    type: EventType.RoomMember,
-                    state_key: "@bob:example.org",
-                    content: {
-                        membership: "invite",
-                    },
-                    room_id: ROOM_ID,
-                    sender: "@carol:example.org",
-                }),
-                dummyRoomState(),
-                null,
-            );
+            act(() => {
+                mockMembershipForRoom(room, [
+                    ["@alice:example.org", "joined"],
+                    ["@bob:example.org", "invited"],
+                ]);
+                emitMembershipChange(client, "@bob:example.org", "invite");
+            });
 
             // Alice leaves, so we no longer show her warning, and we don't show
             // a warning for Bob.
             act(() => {
-                client.emit(
-                    RoomStateEvent.Events,
-                    new MatrixEvent({
-                        event_id: "$event_id",
-                        type: EventType.RoomMember,
-                        state_key: "@alice:example.org",
-                        content: {
-                            membership: "leave",
-                        },
-                        room_id: ROOM_ID,
-                        sender: "@alice:example.org",
-                    }),
-                    dummyRoomState(),
-                    null,
-                );
+                mockMembershipForRoom(room, [["@bob:example.org", "invited"]]);
+                emitMembershipChange(client, "@alice:example.org", "leave");
             });
-            await waitFor(() =>
-                expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
-            );
-            await waitFor(() =>
-                expect(() => getWarningByText("@bob:example.org's identity appears to have changed.")).toThrow(),
-            );
+            await waitFor(() => expect(() => getWarningByText("@alice:example.org's identity was reset.")).toThrow());
+            await waitFor(() => expect(() => getWarningByText("@bob:example.org's identity was reset.")).toThrow());
         });
 
         it("when member leaves immediately after component is loaded", async () => {
+            let hasLeft = false;
             jest.spyOn(room, "getEncryptionTargetMembers").mockImplementation(async () => {
+                if (hasLeft) return [];
                 setTimeout(() => {
-                    // Alice immediately leaves after we get the room
-                    // membership, so we shouldn't show the warning any more
-                    client.emit(
-                        RoomStateEvent.Events,
-                        new MatrixEvent({
-                            event_id: "$event_id",
-                            type: EventType.RoomMember,
-                            state_key: "@alice:example.org",
-                            content: {
-                                membership: "leave",
-                            },
-                            room_id: ROOM_ID,
-                            sender: "@alice:example.org",
-                        }),
-                        dummyRoomState(),
-                        null,
-                    );
+                    emitMembershipChange(client, "@alice:example.org", "leave");
+                    hasLeft = true;
                 });
                 return [mockRoomMember("@alice:example.org")];
             });
-            jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
+
             jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
             const crypto = client.getCrypto()!;
             jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
                 new UserVerificationStatus(false, false, false, true),
             );
-            renderComponent(client, room);
 
-            await sleep(10);
-            expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
+            await act(async () => {
+                renderComponent(client, room);
+                await sleep(10);
+            });
+            expect(() => getWarningByText("@alice:example.org's identity was reset.")).toThrow();
         });
 
         it("when member leaves immediately after joining", async () => {
@@ -433,7 +475,7 @@ describe("UserIdentityWarning", () => {
                 null,
             );
             await sleep(10); // give it some time to finish
-            expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
+            expect(() => getWarningByText("@alice:example.org's identity was reset.")).toThrow();
         });
     });
 
@@ -453,82 +495,61 @@ describe("UserIdentityWarning", () => {
         renderComponent(client, room);
         // We should warn about Alice's identity first.
         await waitFor(() =>
-            expect(
-                getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-            ).toBeInTheDocument(),
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
         );
 
         // Simulate Alice's new identity having been approved, so now we warn
         // about Bob's identity.
         act(() => {
-            client.emit(
-                CryptoEvent.UserTrustStatusChanged,
-                "@alice:example.org",
-                new UserVerificationStatus(false, false, false, false),
-            );
+            const newStatus = new UserVerificationStatus(false, false, false, false);
+            jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async (userId) => {
+                if (userId == "@alice:example.org") {
+                    return newStatus;
+                } else {
+                    return new UserVerificationStatus(false, false, false, true);
+                }
+            });
+            client.emit(CryptoEvent.UserTrustStatusChanged, "@alice:example.org", newStatus);
         });
-        await waitFor(() =>
-            expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
-        );
+        await waitFor(() => expect(getWarningByText("@bob:example.org's identity was reset.")).toBeInTheDocument());
     });
 
-    // If we get an update for a user's verification status while we're fetching
-    // that user's verification status, we should display based on the updated
-    // value.
-    describe("handles races between fetching verification status and receiving updates", () => {
-        // First case: check that if the update says that the user identity
-        // needs approval, but the fetch says it doesn't, we show the warning.
-        it("update says identity needs approval", async () => {
-            jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
-                mockRoomMember("@alice:example.org", "Alice"),
-            ]);
-            jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
-            const crypto = client.getCrypto()!;
-            jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
-                act(() => {
-                    client.emit(
-                        CryptoEvent.UserTrustStatusChanged,
-                        "@alice:example.org",
-                        new UserVerificationStatus(false, false, false, true),
-                    );
-                });
-                return Promise.resolve(new UserVerificationStatus(false, false, false, false));
-            });
-            renderComponent(client, room);
-            await sleep(10); // give it some time to finish initialising
-            await waitFor(() =>
-                expect(
-                    getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-                ).toBeInTheDocument(),
-            );
+    it("displays the next user when the verification requirement is withdrawn", async () => {
+        jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
+            mockRoomMember("@alice:example.org", "Alice"),
+            mockRoomMember("@bob:example.org"),
+        ]);
+        const crypto = client.getCrypto()!;
+        jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async (userId) => {
+            if (userId == "@alice:example.org") {
+                return new UserVerificationStatus(false, true, false, true);
+            } else {
+                return new UserVerificationStatus(false, false, false, true);
+            }
         });
 
-        // Second case: check that if the update says that the user identity
-        // doesn't needs approval, but the fetch says it does, we don't show the
-        // warning.
-        it("update says identity doesn't need approval", async () => {
-            jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
-                mockRoomMember("@alice:example.org", "Alice"),
-            ]);
-            jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
-            const crypto = client.getCrypto()!;
-            jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
-                act(() => {
-                    client.emit(
-                        CryptoEvent.UserTrustStatusChanged,
-                        "@alice:example.org",
-                        new UserVerificationStatus(false, false, false, false),
-                    );
-                });
-                return Promise.resolve(new UserVerificationStatus(false, false, false, true));
+        renderComponent(client, room);
+        // We should warn about Alice's identity first.
+        await waitFor(() =>
+            expect(getWarningByText("Alice's (@alice:example.org) identity was reset.")).toBeInTheDocument(),
+        );
+
+        // Simulate Alice's new identity having been approved, so now we warn
+        // about Bob's identity.
+        act(() => {
+            jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async (userId) => {
+                if (userId == "@alice:example.org") {
+                    return new UserVerificationStatus(false, false, false, false);
+                } else {
+                    return new UserVerificationStatus(false, false, false, true);
+                }
             });
-            renderComponent(client, room);
-            await sleep(10); // give it some time to finish initialising
-            await waitFor(() =>
-                expect(() =>
-                    getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
-                ).toThrow(),
+            client.emit(
+                CryptoEvent.UserTrustStatusChanged,
+                "@alice:example.org",
+                new UserVerificationStatus(false, false, false, false),
             );
         });
+        await waitFor(() => expect(getWarningByText("@bob:example.org's identity was reset.")).toBeInTheDocument());
     });
 });
diff --git a/test/unit-tests/components/views/rooms/VoiceRecordComposerTile-test.tsx b/test/unit-tests/components/views/rooms/VoiceRecordComposerTile-test.tsx
index 06d314b58843f9e4aaed8fb2181c26134524bd6a..b8bfac2c956803a9e347ff7792edaee42fedbdcc 100644
--- a/test/unit-tests/components/views/rooms/VoiceRecordComposerTile-test.tsx
+++ b/test/unit-tests/components/views/rooms/VoiceRecordComposerTile-test.tsx
@@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { createRef, RefObject } from "react";
+import React, { createRef, type RefObject } from "react";
 import { render } from "jest-matrix-react";
-import { MatrixClient, MsgType, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MsgType, type Room } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import VoiceRecordComposerTile from "../../../../../src/components/views/rooms/VoiceRecordComposerTile";
 import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
-import { IUpload, VoiceMessageRecording } from "../../../../../src/audio/VoiceMessageRecording";
+import { type IUpload, type VoiceMessageRecording } from "../../../../../src/audio/VoiceMessageRecording";
 import { VoiceRecordingStore } from "../../../../../src/stores/VoiceRecordingStore";
-import { PlaybackClock } from "../../../../../src/audio/PlaybackClock";
+import { type PlaybackClock } from "../../../../../src/audio/PlaybackClock";
 import { mkEvent } from "../../../../test-utils";
 
 jest.mock("../../../../../src/utils/local-room", () => ({
@@ -34,7 +34,7 @@ jest.mock("../../../../../src/stores/VoiceRecordingStore", () => ({
 }));
 
 describe("<VoiceRecordComposerTile/>", () => {
-    let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>;
+    let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile | null>;
     let mockRecorder: VoiceMessageRecording;
     let mockUpload: IUpload;
     let mockClient: MatrixClient;
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..5d400fba980b5a4733eca9fedc36c7944e3f10f0
--- /dev/null
+++ b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap
@@ -0,0 +1,165 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<NotificationDecoration /> should render the activity decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="_unread_1k06b_8"
+    >
+      <div />
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the invitation decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <svg
+      fill="var(--cpd-color-icon-accent-primary)"
+      height="20px"
+      viewBox="0 0 24 24"
+      width="20px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2m0 5.111a1 1 0 0 0 .514.874l7 3.89a1 1 0 0 0 .972 0l7-3.89a1 1 0 1 0-.972-1.748L12 11.856 5.486 8.237A1 1 0 0 0 4 9.111"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the mention decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <svg
+      fill="var(--cpd-color-icon-accent-primary)"
+      height="20px"
+      viewBox="0 0 24 24"
+      width="20px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M12 4a8 8 0 1 0 0 16 1 1 0 1 1 0 2C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10v1.5a3.5 3.5 0 0 1-6.396 1.966A5 5 0 1 1 17 12v1.5a1.5 1.5 0 0 0 3 0V12a8 8 0 0 0-8-8m3 8a3 3 0 1 0-6 0 3 3 0 0 0 6 0"
+      />
+    </svg>
+    <span
+      class="_unread-counter_9mg0k_8"
+    >
+      1
+    </span>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the muted decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <svg
+      fill="var(--cpd-color-icon-tertiary)"
+      height="20px"
+      viewBox="0 0 24 24"
+      width="20px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="m4.917 2.083 17 17a1 1 0 0 1-1.414 1.414L19.006 19H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-2.034 1.096-3.91L3.504 3.498a1 1 0 0 1 1.414-1.414M19 13.35 9.136 3.484C9.93 3.181 10.874 3 12 3c7 0 7 7 7 7z"
+      />
+      <path
+        d="M10 20h4a2 2 0 0 1-4 0"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the notification decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <span
+      class="_unread-counter_9mg0k_8"
+    >
+      1
+    </span>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the notification decoration without count 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <div
+      class="_unread-counter_9mg0k_8"
+    />
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the unset message decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <svg
+      fill="var(--cpd-color-icon-critical-primary)"
+      height="20px"
+      viewBox="0 0 24 24"
+      width="20px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<NotificationDecoration /> should render the video decoration 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_Flex"
+    data-testid="notification-decoration"
+    style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
+  >
+    <svg
+      fill="var(--cpd-color-icon-accent-primary)"
+      height="20px"
+      viewBox="0 0 24 24"
+      width="20px"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
+      />
+    </svg>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap
index 5e965318f43b97c4d76631dd56d23e87ada17186..84a4da73ef05a9062ac74b3e55bb5b900c19d191 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap
@@ -8,7 +8,7 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
   >
     <div>
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
         data-color="2"
         data-testid="avatar-img"
         data-type="round"
@@ -25,7 +25,7 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
         class="mx_PinnedEventTile_top"
       >
         <span
-          aria-labelledby=":r0:"
+          aria-labelledby="«r0»"
           class="mx_PinnedEventTile_sender mx_Username_color2"
         >
           @alice:server.org
@@ -35,16 +35,16 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
           aria-expanded="false"
           aria-haspopup="menu"
           aria-label="Open menu"
-          class="_icon-button_bh2qc_17"
+          class="_icon-button_m2erp_8"
           data-state="closed"
-          id="radix-:r5:"
+          id="radix-«r5»"
           role="button"
           style="--cpd-icon-button-size: 24px;"
           tabindex="0"
           type="button"
         >
           <div
-            class="_indicator-icon_133tf_26"
+            class="_indicator-icon_zr2a0_17"
             style="--cpd-icon-button-size: 100%;"
           >
             <svg
@@ -55,7 +55,7 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
               />
             </svg>
           </div>
@@ -84,7 +84,7 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
   >
     <div>
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_1qbcf_52"
         data-color="2"
         data-testid="avatar-img"
         data-type="round"
@@ -101,7 +101,7 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
         class="mx_PinnedEventTile_top"
       >
         <span
-          aria-labelledby=":r8:"
+          aria-labelledby="«r8»"
           class="mx_PinnedEventTile_sender mx_Username_color2"
         >
           @alice:server.org
@@ -111,16 +111,16 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
           aria-expanded="false"
           aria-haspopup="menu"
           aria-label="Open menu"
-          class="_icon-button_bh2qc_17"
+          class="_icon-button_m2erp_8"
           data-state="closed"
-          id="radix-:rd:"
+          id="radix-«rd»"
           role="button"
           style="--cpd-icon-button-size: 24px;"
           tabindex="0"
           type="button"
         >
           <div
-            class="_indicator-icon_133tf_26"
+            class="_indicator-icon_zr2a0_17"
             style="--cpd-icon-button-size: 100%;"
           >
             <svg
@@ -131,7 +131,7 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
               />
             </svg>
           </div>
@@ -158,10 +158,10 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M7 10a.968.968 0 0 1-.713-.287A.968.968 0 0 1 6 9c0-.283.096-.52.287-.713A.968.968 0 0 1 7 8h10a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 17 10H7Zm0 4a.967.967 0 0 1-.713-.287A.968.968 0 0 1 6 13c0-.283.096-.52.287-.713A.967.967 0 0 1 7 12h6c.283 0 .52.096.713.287.191.192.287.43.287.713s-.096.52-.287.713A.968.968 0 0 1 13 14H7Z"
+            d="M7 10a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 9q0-.424.287-.713A.97.97 0 0 1 7 8h10q.424 0 .712.287Q18 8.576 18 9t-.288.713A.97.97 0 0 1 17 10zm0 4a.97.97 0 0 1-.713-.287A.97.97 0 0 1 6 13q0-.424.287-.713A.97.97 0 0 1 7 12h6q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 13 14z"
           />
           <path
-            d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293ZM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17Z"
+            d="M3.707 21.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6zM6 17h14V5H4v13.172l.586-.586A2 2 0 0 1 6 17"
           />
         </svg>
         <span>
@@ -181,22 +181,22 @@ exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
 exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
 <div
   aria-label="Open menu"
-  aria-labelledby="radix-:r10:"
+  aria-labelledby="radix-«r10»"
   aria-orientation="vertical"
-  class="_menu_1x5h1_17"
+  class="_menu_19sse_8"
   data-align="start"
   data-orientation="vertical"
   data-radix-menu-content=""
   data-side="right"
   data-state="open"
   dir="ltr"
-  id="radix-:r11:"
+  id="radix-«r11»"
   role="menu"
   style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
   tabindex="-1"
 >
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="primary"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -205,7 +205,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -213,17 +213,17 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 16c1.25 0 2.313-.438 3.188-1.313.874-.874 1.312-1.937 1.312-3.187 0-1.25-.438-2.313-1.313-3.188C14.313 7.439 13.25 7 12 7c-1.25 0-2.312.438-3.187 1.313C7.938 9.187 7.5 10.25 7.5 11.5c0 1.25.438 2.313 1.313 3.188C9.688 15.562 10.75 16 12 16Zm0-1.8c-.75 0-1.387-.262-1.912-.787A2.604 2.604 0 0 1 9.3 11.5c0-.75.263-1.387.787-1.912A2.604 2.604 0 0 1 12 8.8c.75 0 1.387.262 1.912.787.525.526.788 1.163.788 1.913s-.262 1.387-.787 1.912A2.604 2.604 0 0 1 12 14.2Zm0 4.8c-2.317 0-4.433-.613-6.35-1.837-1.917-1.226-3.367-2.88-4.35-4.963a.812.812 0 0 1-.1-.313 2.93 2.93 0 0 1 0-.774.812.812 0 0 1 .1-.313c.983-2.083 2.433-3.738 4.35-4.963C7.567 4.614 9.683 4 12 4c2.317 0 4.433.612 6.35 1.838 1.917 1.224 3.367 2.879 4.35 4.962a.81.81 0 0 1 .1.313 2.925 2.925 0 0 1 0 .774.81.81 0 0 1-.1.313c-.983 2.083-2.433 3.738-4.35 4.963C16.433 18.387 14.317 19 12 19Zm0-2a9.544 9.544 0 0 0 5.188-1.488A9.773 9.773 0 0 0 20.8 11.5a9.773 9.773 0 0 0-3.613-4.013A9.544 9.544 0 0 0 12 6a9.545 9.545 0 0 0-5.187 1.487A9.773 9.773 0 0 0 3.2 11.5a9.773 9.773 0 0 0 3.613 4.012A9.544 9.544 0 0 0 12 17Z"
+        d="M12 16q1.875 0 3.188-1.312Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7 8.813 8.313 7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.787A2.6 2.6 0 0 1 9.3 11.5q0-1.125.787-1.912A2.6 2.6 0 0 1 12 8.8q1.125 0 1.912.787.788.788.788 1.913t-.787 1.912A2.6 2.6 0 0 1 12 14.2m0 4.8q-3.475 0-6.35-1.837Q2.775 15.324 1.3 12.2a.8.8 0 0 1-.1-.312 3 3 0 0 1 0-.775.8.8 0 0 1 .1-.313q1.475-3.125 4.35-4.962Q8.525 4 12 4t6.35 1.838T22.7 10.8a.8.8 0 0 1 .1.313 3 3 0 0 1 0 .774.8.8 0 0 1-.1.313q-1.475 3.125-4.35 4.963Q15.475 19 12 19m0-2a9.54 9.54 0 0 0 5.188-1.488A9.77 9.77 0 0 0 20.8 11.5a9.77 9.77 0 0 0-3.613-4.012A9.54 9.54 0 0 0 12 6a9.55 9.55 0 0 0-5.187 1.487A9.77 9.77 0 0 0 3.2 11.5a9.77 9.77 0 0 0 3.613 4.012A9.54 9.54 0 0 0 12 17"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       View in timeline
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -231,12 +231,12 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="primary"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -245,7 +245,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -254,21 +254,21 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
     >
       <path
         clip-rule="evenodd"
-        d="M5.457 2.083a1 1 0 0 0-1.414 1.414L8.04 7.494v2.25a.5.5 0 0 1-.15.356l-3.7 3.644a.5.5 0 0 0-.15.356v1.4a.5.5 0 0 0 .5.5h6.5v6a1 1 0 0 0 2 0v-6h3.506l4.497 4.497a1 1 0 0 0 1.414-1.414l-17-17ZM14.546 14 10.04 9.494v.25a2.5 2.5 0 0 1-.746 1.781L6.78 14h7.766Z"
+        d="M5.457 2.083a1 1 0 0 0-1.414 1.414L8.04 7.494v2.25a.5.5 0 0 1-.15.356l-3.7 3.644a.5.5 0 0 0-.15.356v1.4a.5.5 0 0 0 .5.5h6.5v6a1 1 0 0 0 2 0v-6h3.506l4.497 4.497a1 1 0 0 0 1.414-1.414zM14.546 14 10.04 9.494v.25a2.5 2.5 0 0 1-.746 1.781L6.78 14z"
         fill-rule="evenodd"
       />
       <path
-        d="M14.04 4v3.85l2.015 2.015a.5.5 0 0 1-.015-.12V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857h-9.73l2 2h3.849Z"
+        d="M14.04 4v3.85l2.015 2.015a.5.5 0 0 1-.015-.12V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857h-9.73l2 2z"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       Unpin
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -276,12 +276,12 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="primary"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -290,7 +290,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -298,17 +298,17 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M14.597 5.708a1.004 1.004 0 0 1 0-1.416.996.996 0 0 1 1.412 0l4.699 4.714c.39.39.39 1.025 0 1.416l-4.7 4.714a.996.996 0 0 1-1.411 0 1.004 1.004 0 0 1 0-1.416l3.043-3.054H8.487C6.599 10.666 5 12.27 5 14.333 5.002 16.396 6.6 18 8.488 18h2.093a1 1 0 1 1 0 2H8.487C5.42 20 3 17.425 3 14.333c0-3.092 2.42-5.666 5.486-5.666h9.059l-2.95-2.959Z"
+        d="M14.597 5.708a1.004 1.004 0 0 1 0-1.416.996.996 0 0 1 1.412 0l4.699 4.714c.39.391.39 1.025 0 1.416l-4.7 4.714a.996.996 0 0 1-1.411 0 1.004 1.004 0 0 1 0-1.416l3.043-3.053H8.487c-1.888 0-3.485 1.604-3.485 3.666C5.002 16.396 6.599 18 8.487 18h2.093a1 1 0 1 1 0 2H8.487c-3.067 0-5.485-2.575-5.485-5.667S5.42 8.667 8.487 8.667h9.059z"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       Forward
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -316,18 +316,18 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
   <div
-    class="_separator_144s5_17"
+    class="_separator_7ckbw_8"
     data-kind="primary"
     data-orientation="horizontal"
     role="separator"
   />
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="critical"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -336,7 +336,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -344,17 +344,17 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
+        d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       Delete
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -362,7 +362,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
@@ -372,22 +372,22 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
 exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`] = `
 <div
   aria-label="Open menu"
-  aria-labelledby="radix-:rl:"
+  aria-labelledby="radix-«rl»"
   aria-orientation="vertical"
-  class="_menu_1x5h1_17"
+  class="_menu_19sse_8"
   data-align="start"
   data-orientation="vertical"
   data-radix-menu-content=""
   data-side="right"
   data-state="open"
   dir="ltr"
-  id="radix-:rm:"
+  id="radix-«rm»"
   role="menu"
   style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
   tabindex="-1"
 >
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="primary"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -396,7 +396,7 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -404,17 +404,17 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M12 16c1.25 0 2.313-.438 3.188-1.313.874-.874 1.312-1.937 1.312-3.187 0-1.25-.438-2.313-1.313-3.188C14.313 7.439 13.25 7 12 7c-1.25 0-2.312.438-3.187 1.313C7.938 9.187 7.5 10.25 7.5 11.5c0 1.25.438 2.313 1.313 3.188C9.688 15.562 10.75 16 12 16Zm0-1.8c-.75 0-1.387-.262-1.912-.787A2.604 2.604 0 0 1 9.3 11.5c0-.75.263-1.387.787-1.912A2.604 2.604 0 0 1 12 8.8c.75 0 1.387.262 1.912.787.525.526.788 1.163.788 1.913s-.262 1.387-.787 1.912A2.604 2.604 0 0 1 12 14.2Zm0 4.8c-2.317 0-4.433-.613-6.35-1.837-1.917-1.226-3.367-2.88-4.35-4.963a.812.812 0 0 1-.1-.313 2.93 2.93 0 0 1 0-.774.812.812 0 0 1 .1-.313c.983-2.083 2.433-3.738 4.35-4.963C7.567 4.614 9.683 4 12 4c2.317 0 4.433.612 6.35 1.838 1.917 1.224 3.367 2.879 4.35 4.962a.81.81 0 0 1 .1.313 2.925 2.925 0 0 1 0 .774.81.81 0 0 1-.1.313c-.983 2.083-2.433 3.738-4.35 4.963C16.433 18.387 14.317 19 12 19Zm0-2a9.544 9.544 0 0 0 5.188-1.488A9.773 9.773 0 0 0 20.8 11.5a9.773 9.773 0 0 0-3.613-4.013A9.544 9.544 0 0 0 12 6a9.545 9.545 0 0 0-5.187 1.487A9.773 9.773 0 0 0 3.2 11.5a9.773 9.773 0 0 0 3.613 4.012A9.544 9.544 0 0 0 12 17Z"
+        d="M12 16q1.875 0 3.188-1.312Q16.5 13.375 16.5 11.5t-1.312-3.187T12 7 8.813 8.313 7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.787A2.6 2.6 0 0 1 9.3 11.5q0-1.125.787-1.912A2.6 2.6 0 0 1 12 8.8q1.125 0 1.912.787.788.788.788 1.913t-.787 1.912A2.6 2.6 0 0 1 12 14.2m0 4.8q-3.475 0-6.35-1.837Q2.775 15.324 1.3 12.2a.8.8 0 0 1-.1-.312 3 3 0 0 1 0-.775.8.8 0 0 1 .1-.313q1.475-3.125 4.35-4.962Q8.525 4 12 4t6.35 1.838T22.7 10.8a.8.8 0 0 1 .1.313 3 3 0 0 1 0 .774.8.8 0 0 1-.1.313q-1.475 3.125-4.35 4.963Q15.475 19 12 19m0-2a9.54 9.54 0 0 0 5.188-1.488A9.77 9.77 0 0 0 20.8 11.5a9.77 9.77 0 0 0-3.613-4.012A9.54 9.54 0 0 0 12 6a9.55 9.55 0 0 0-5.187 1.487A9.77 9.77 0 0 0 3.2 11.5a9.77 9.77 0 0 0 3.613 4.012A9.54 9.54 0 0 0 12 17"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       View in timeline
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -422,12 +422,12 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
   <button
-    class="_item_8j2l6_17 _interactive_8j2l6_35"
+    class="_item_dyt4i_8 _interactive_dyt4i_26"
     data-kind="primary"
     data-orientation="vertical"
     data-radix-collection-item=""
@@ -436,7 +436,7 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
   >
     <svg
       aria-hidden="true"
-      class="_icon_8j2l6_43"
+      class="_icon_dyt4i_50"
       fill="currentColor"
       height="24"
       viewBox="0 0 24 24"
@@ -444,17 +444,17 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M14.597 5.708a1.004 1.004 0 0 1 0-1.416.996.996 0 0 1 1.412 0l4.699 4.714c.39.39.39 1.025 0 1.416l-4.7 4.714a.996.996 0 0 1-1.411 0 1.004 1.004 0 0 1 0-1.416l3.043-3.054H8.487C6.599 10.666 5 12.27 5 14.333 5.002 16.396 6.6 18 8.488 18h2.093a1 1 0 1 1 0 2H8.487C5.42 20 3 17.425 3 14.333c0-3.092 2.42-5.666 5.486-5.666h9.059l-2.95-2.959Z"
+        d="M14.597 5.708a1.004 1.004 0 0 1 0-1.416.996.996 0 0 1 1.412 0l4.699 4.714c.39.391.39 1.025 0 1.416l-4.7 4.714a.996.996 0 0 1-1.411 0 1.004 1.004 0 0 1 0-1.416l3.043-3.053H8.487c-1.888 0-3.485 1.604-3.485 3.666C5.002 16.396 6.599 18 8.487 18h2.093a1 1 0 1 1 0 2H8.487c-3.067 0-5.485-2.575-5.485-5.667S5.42 8.667 8.487 8.667h9.059z"
       />
     </svg>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
     >
       Forward
     </span>
     <svg
       aria-hidden="true"
-      class="_nav-hint_8j2l6_59"
+      class="_nav-hint_dyt4i_59"
       fill="currentColor"
       height="24"
       viewBox="8 0 8 24"
@@ -462,7 +462,7 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
-        d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+        d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
       />
     </svg>
   </button>
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
index 9fdc1404d0f9929702c7a7ca1906812a586cb11c..7b4bedeffa1fc783adcc0d7507aa1dc3a2bf7432 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
@@ -33,7 +33,7 @@ exports[`<PinnedMessageBanner /> should display display a poll event 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
@@ -96,7 +96,7 @@ exports[`<PinnedMessageBanner /> should display the last message when the pinned
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <div
@@ -122,7 +122,7 @@ exports[`<PinnedMessageBanner /> should display the last message when the pinned
       </div>
     </button>
     <button
-      class="_button_i91xf_17 mx_PinnedMessageBanner_actions"
+      class="_button_vczzf_8 mx_PinnedMessageBanner_actions"
       data-kind="tertiary"
       data-size="lg"
       role="button"
@@ -167,7 +167,7 @@ exports[`<PinnedMessageBanner /> should display the m.audio event type 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
@@ -222,7 +222,7 @@ exports[`<PinnedMessageBanner /> should display the m.file event type 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
@@ -277,7 +277,7 @@ exports[`<PinnedMessageBanner /> should display the m.image event type 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
@@ -332,7 +332,7 @@ exports[`<PinnedMessageBanner /> should display the m.video event type 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
@@ -391,7 +391,7 @@ exports[`<PinnedMessageBanner /> should render 2 pinned event 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <div
@@ -417,7 +417,7 @@ exports[`<PinnedMessageBanner /> should render 2 pinned event 1`] = `
       </div>
     </button>
     <button
-      class="_button_i91xf_17 mx_PinnedMessageBanner_actions"
+      class="_button_vczzf_8 mx_PinnedMessageBanner_actions"
       data-kind="tertiary"
       data-size="lg"
       role="button"
@@ -470,7 +470,7 @@ exports[`<PinnedMessageBanner /> should render 4 pinned event 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <div
@@ -496,7 +496,7 @@ exports[`<PinnedMessageBanner /> should render 4 pinned event 1`] = `
       </div>
     </button>
     <button
-      class="_button_i91xf_17 mx_PinnedMessageBanner_actions"
+      class="_button_vczzf_8 mx_PinnedMessageBanner_actions"
       data-kind="tertiary"
       data-size="lg"
       role="button"
@@ -541,7 +541,7 @@ exports[`<PinnedMessageBanner /> should render a single pinned event 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357L5.77 2.857Z"
+            d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
           />
         </svg>
         <span
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/ReadReceiptGroup-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/ReadReceiptGroup-test.tsx.snap
index 60e8f844af72d47d3c6dcc0c98ce39016069ce88..1778426866a867f86d340b49ad04ff7ba8a199ff 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/ReadReceiptGroup-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/ReadReceiptGroup-test.tsx.snap
@@ -2,16 +2,16 @@
 
 exports[`ReadReceiptGroup <ReadReceiptPerson /> should display a tooltip 1`] = `
 <div
-  class="_tooltip_1pslb_17"
+  class="_tooltip_6ode6_8"
   data-floating-ui-focusable=""
-  id=":r6:"
+  id="«r6»"
   role="tooltip"
   style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
   tabindex="-1"
 >
   <svg
     aria-hidden="true"
-    class="_arrow_1pslb_42"
+    class="_arrow_6ode6_33"
     height="10"
     style="position: absolute; pointer-events: none; top: 100%;"
     viewBox="0 0 10 10"
@@ -22,7 +22,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should display a tooltip 1`] = `
       stroke="none"
     />
     <clippath
-      id=":r9:"
+      id="«r9»"
     >
       <rect
         height="10"
@@ -33,13 +33,13 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should display a tooltip 1`] = `
     </clippath>
   </svg>
   <span
-    id=":r4:"
+    id="«r4»"
   >
     Alice
   </span>
   <span
-    class="_caption_1pslb_37 cpd-theme-dark"
-    id=":r5:"
+    class="_caption_6ode6_28 cpd-theme-dark"
+    id="«r5»"
   >
     @alice:example.org
   </span>
@@ -58,7 +58,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
         aria-hidden="true"
         aria-label="Profile picture"
         aria-live="off"
-        class="_avatar_mcap2_17 mx_BaseAvatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -66,7 +66,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
       >
         <img
           alt=""
-          class="_image_mcap2_50"
+          class="_image_1qbcf_41"
           data-type="round"
           height="24px"
           loading="lazy"
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap
index 2c7a831ebee41122270cb75747dc3095ac9f2a72..dc44db8d21f9567429d096ea8632a833ddd4fa67 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap
@@ -23,7 +23,7 @@ exports[`<RoomPreviewBar /> message case AskToJoin renders the corresponding mes
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="6"
       data-testid="avatar-img"
       data-type="round"
@@ -48,7 +48,7 @@ exports[`<RoomPreviewBar /> message case AskToJoin renders the corresponding mes
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="6"
       data-testid="avatar-img"
       data-type="round"
@@ -73,7 +73,7 @@ exports[`<RoomPreviewBar /> message case AskToJoin renders the corresponding mes
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="1"
       data-testid="avatar-img"
       data-type="round"
@@ -252,7 +252,7 @@ exports[`<RoomPreviewBar /> with an invite with an invited email when client has
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="6"
       data-testid="avatar-img"
       data-type="round"
@@ -310,7 +310,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="6"
       data-testid="avatar-img"
       data-type="round"
@@ -339,34 +339,6 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
 </div>
 `;
 
-exports[`<RoomPreviewBar /> with an invite without an invited email for a dm room renders join and reject action buttons with correct labels 1`] = `
-<div
-  class="mx_RoomPreviewBar_actions"
->
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
-    role="button"
-    tabindex="0"
-  >
-    Start chatting
-  </div>
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
-    role="button"
-    tabindex="0"
-  >
-    Reject & Ignore user
-  </div>
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
-    role="button"
-    tabindex="0"
-  >
-    Reject
-  </div>
-</div>
-`;
-
 exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm room renders invite message 1`] = `
 <div
   class="mx_RoomPreviewBar_message"
@@ -376,7 +348,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
   </h3>
   <p>
     <span
-      class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+      class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
       data-color="6"
       data-testid="avatar-img"
       data-type="round"
@@ -421,7 +393,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
     role="button"
     tabindex="0"
   >
-    Reject
+    Decline
   </div>
 </div>
 `;
@@ -435,7 +407,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
     role="button"
     tabindex="0"
   >
-    Reject
+    Decline
   </div>
   <div
     class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
@@ -446,31 +418,3 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
   </div>
 </div>
 `;
-
-exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm room renders reject and ignore action buttons when handler is provided 1`] = `
-<div
-  class="mx_RoomPreviewBar_actions"
->
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
-    role="button"
-    tabindex="0"
-  >
-    Accept
-  </div>
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
-    role="button"
-    tabindex="0"
-  >
-    Reject & Ignore user
-  </div>
-  <div
-    class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
-    role="button"
-    tabindex="0"
-  >
-    Reject
-  </div>
-</div>
-`;
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap
index 0a96c2cc650c5b51fbc340c0918e1a143b441f03..160aa6efb5bf99d61252f8928eb38de266a736ec 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/RoomTile-test.tsx.snap
@@ -14,7 +14,7 @@ exports[`RoomTile when message previews are enabled and there is a message in a
       class="mx_DecoratedRoomAvatar"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -88,7 +88,7 @@ exports[`RoomTile when message previews are enabled and there is a message in th
       class="mx_DecoratedRoomAvatar"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -170,7 +170,7 @@ exports[`RoomTile when message previews are enabled should render a room without
       class="mx_DecoratedRoomAvatar"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
@@ -232,7 +232,7 @@ exports[`RoomTile when message previews are not enabled should render the room 1
       class="mx_DecoratedRoomAvatar"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
         data-color="3"
         data-testid="avatar-img"
         data-type="round"
diff --git a/test/unit-tests/components/views/rooms/__snapshots__/ThirdPartyMemberInfo-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/ThirdPartyMemberInfo-test.tsx.snap
index 3e981a8c8d5a5d921e60e0e52431a17a8db2949f..75d559f3902f54fe36c0d7545c95ed07def7a2e3 100644
--- a/test/unit-tests/components/views/rooms/__snapshots__/ThirdPartyMemberInfo-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/__snapshots__/ThirdPartyMemberInfo-test.tsx.snap
@@ -12,22 +12,22 @@ exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Profile
         </p>
       </div>
       <button
-        aria-labelledby=":r0:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r0»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -38,7 +38,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -50,20 +50,20 @@ exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
     >
       <div
         class="mx_Flex mx_ThirdPartyMemberInfo"
-        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <section
           class="mx_Flex"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
         >
           <span
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
             role="heading"
           >
             bob@bob.com
           </span>
           <span
-            class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+            class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
           >
             Invited by Alice DisplayName
           </span>
@@ -86,22 +86,22 @@ exports[`<ThirdPartyMemberInfo /> should render invite when room in not availabl
         class="mx_BaseCard_header_title"
       >
         <p
-          class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_BaseCard_header_title_heading"
+          class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
           role="heading"
         >
           Profile
         </p>
       </div>
       <button
-        aria-labelledby=":r6:"
-        class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+        aria-labelledby="«r6»"
+        class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
         data-testid="base-card-close-button"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -112,7 +112,7 @@ exports[`<ThirdPartyMemberInfo /> should render invite when room in not availabl
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
             />
           </svg>
         </div>
@@ -124,20 +124,20 @@ exports[`<ThirdPartyMemberInfo /> should render invite when room in not availabl
     >
       <div
         class="mx_Flex mx_ThirdPartyMemberInfo"
-        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <section
           class="mx_Flex"
-          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
         >
           <span
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
             role="heading"
           >
             bob@bob.com
           </span>
           <span
-            class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+            class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
           >
             Invited by Alice DisplayName
           </span>
diff --git a/test/unit-tests/components/views/rooms/memberlist/MemberListHeaderView-test.tsx b/test/unit-tests/components/views/rooms/memberlist/MemberListHeaderView-test.tsx
index 28d4c105a46e017b6202008bb87ec0c1690e9f79..b4ab471cc0a898b659d284ced07e2249b034c527 100644
--- a/test/unit-tests/components/views/rooms/memberlist/MemberListHeaderView-test.tsx
+++ b/test/unit-tests/components/views/rooms/memberlist/MemberListHeaderView-test.tsx
@@ -7,21 +7,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React from "react";
 import { act, fireEvent, screen, waitFor } from "jest-matrix-react";
 import { RoomMember, User, RoomEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { mocked } from "jest-mock";
+import { type JSX } from "react";
 
 import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
 import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
-import { Rendered, renderMemberList } from "./common";
+import { type Rendered, renderMemberList } from "./common";
 
 jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
     shouldShowComponent: jest.fn(),
 }));
 
-type Children = (args: { height: number; width: number }) => React.JSX.Element;
+type Children = (args: { height: number; width: number }) => JSX.Element;
 jest.mock("react-virtualized", () => {
     const ReactVirtualized = jest.requireActual("react-virtualized");
     return {
@@ -62,7 +62,7 @@ describe("MemberListHeaderView", () => {
     });
 
     it("Does not show search box when there's less than 20 members", async () => {
-        expect(screen.queryByPlaceholderText("Filter room members")).toBeNull();
+        expect(screen.queryByPlaceholderText("Search room members")).toBeNull();
     });
 
     it("Shows search box when there's more than 20 members", async () => {
@@ -80,7 +80,7 @@ describe("MemberListHeaderView", () => {
             memberListRoom.currentState.members[newMember.userId] = newMember;
         }
         await reRender();
-        expect(screen.queryByPlaceholderText("Filter room members")).toBeVisible();
+        expect(screen.queryByPlaceholderText("Search room members")).toBeVisible();
     });
 
     describe("Invite button functionality", () => {
diff --git a/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.tsx b/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.tsx
index 518ed4f690daaca39fe43f3847e837716b8bfe32..240ad8a5027593b0e0647b8b9419848886f7035c 100644
--- a/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.tsx
+++ b/test/unit-tests/components/views/rooms/memberlist/MemberListView-test.tsx
@@ -7,18 +7,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { act } from "react";
+import { act } from "react";
 import { waitFor } from "jest-matrix-react";
-import { Room, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type Room, type RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type JSX } from "react";
 
 import { filterConsole } from "../../../../../test-utils";
-import { Rendered, renderMemberList } from "./common";
+import { type Rendered, renderMemberList } from "./common";
 
 jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
     shouldShowComponent: jest.fn(),
 }));
 
-type Children = (args: { height: number; width: number }) => React.JSX.Element;
+type Children = (args: { height: number; width: number }) => JSX.Element;
 jest.mock("react-virtualized", () => {
     const ReactVirtualized = jest.requireActual("react-virtualized");
     return {
diff --git a/test/unit-tests/components/views/rooms/memberlist/MemberTileView-test.tsx b/test/unit-tests/components/views/rooms/memberlist/MemberTileView-test.tsx
index 284483a8397d3e0e3301649df802160c50603f8c..4f06c90b43319f251ea0ed2da7857c075d320766 100644
--- a/test/unit-tests/components/views/rooms/memberlist/MemberTileView-test.tsx
+++ b/test/unit-tests/components/views/rooms/memberlist/MemberTileView-test.tsx
@@ -8,13 +8,13 @@
 
 import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
-import { MatrixClient, RoomMember as SdkRoomMember, Device, Room } from "matrix-js-sdk/src/matrix";
-import { UserVerificationStatus, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
+import { type MatrixClient, RoomMember as SdkRoomMember, type Device, Room } from "matrix-js-sdk/src/matrix";
+import { type UserVerificationStatus, type DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 import { mocked } from "jest-mock";
 import userEvent from "@testing-library/user-event";
 
 import * as TestUtils from "../../../../../test-utils";
-import { RoomMember } from "../../../../../../src/models/rooms/RoomMember";
+import { type RoomMember } from "../../../../../../src/models/rooms/RoomMember";
 import {
     getPending3PidInvites,
     sdkRoomMemberToRoomMember,
diff --git a/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap b/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap
index 6feb72ea62d78b95d28ea306f6f8d7dd7751273c..f4c503c4033e72273a0b1f05da420642a264fa35 100644
--- a/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap
@@ -7,7 +7,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
       aria-label="@userId:matrix.org (power 0)"
       class="mx_AccessibleButton mx_MemberTileView"
       role="button"
-      tabindex="0"
+      tabindex="-1"
     >
       <div
         class="mx_MemberTileView_left"
@@ -16,7 +16,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
           class="mx_MemberTileView_avatar"
         >
           <span
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="2"
             data-testid="avatar-img"
             data-type="round"
@@ -47,7 +47,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
         class="mx_MemberTileView_right"
       >
         <div
-          aria-labelledby=":ri:"
+          aria-labelledby="«ri»"
           class="mx_E2EIconView"
         >
           <svg
@@ -59,7 +59,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M8.15 21.75 6.7 19.3l-2.75-.6a.943.943 0 0 1-.6-.387.928.928 0 0 1-.175-.688L3.45 14.8l-1.875-2.15a.934.934 0 0 1-.25-.65c0-.25.083-.467.25-.65L3.45 9.2l-.275-2.825a.928.928 0 0 1 .175-.688.943.943 0 0 1 .6-.387l2.75-.6 1.45-2.45a.983.983 0 0 1 .55-.438.97.97 0 0 1 .7.038l2.6 1.1 2.6-1.1a.97.97 0 0 1 .7-.038.983.983 0 0 1 .55.438L17.3 4.7l2.75.6c.25.05.45.18.6.388.15.208.208.437.175.687L20.55 9.2l1.875 2.15c.167.183.25.4.25.65s-.083.467-.25.65L20.55 14.8l.275 2.825a.928.928 0 0 1-.175.688.943.943 0 0 1-.6.387l-2.75.6-1.45 2.45a.983.983 0 0 1-.55.438.97.97 0 0 1-.7-.038l-2.6-1.1-2.6 1.1a.97.97 0 0 1-.7.038.983.983 0 0 1-.55-.438Zm2.8-9.05L9.5 11.275A.933.933 0 0 0 8.813 11c-.275 0-.513.1-.713.3a.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7l2.15 2.15c.2.2.433.3.7.3.267 0 .5-.1.7-.3l4.25-4.25c.2-.2.296-.433.287-.7a1.055 1.055 0 0 0-.287-.7 1.02 1.02 0 0 0-.713-.313.93.93 0 0 0-.712.288L10.95 12.7Z"
+              d="M8.15 21.75 6.7 19.3l-2.75-.6a.94.94 0 0 1-.6-.387.93.93 0 0 1-.175-.688L3.45 14.8l-1.875-2.15a.93.93 0 0 1-.25-.65q0-.375.25-.65L3.45 9.2l-.275-2.825a.93.93 0 0 1 .175-.687.94.94 0 0 1 .6-.388l2.75-.6 1.45-2.45a.98.98 0 0 1 .55-.437.97.97 0 0 1 .7.037l2.6 1.1 2.6-1.1a.97.97 0 0 1 .7-.038q.35.112.55.438L17.3 4.7l2.75.6q.375.075.6.388.225.312.175.687L20.55 9.2l1.875 2.15q.25.275.25.65t-.25.65L20.55 14.8l.275 2.825a.93.93 0 0 1-.175.688.94.94 0 0 1-.6.387l-2.75.6-1.45 2.45a.98.98 0 0 1-.55.438.97.97 0 0 1-.7-.038l-2.6-1.1-2.6 1.1a.97.97 0 0 1-.7.038.98.98 0 0 1-.55-.438m2.8-9.05L9.5 11.275A.93.93 0 0 0 8.812 11q-.412 0-.712.3a.95.95 0 0 0-.275.7q0 .425.275.7l2.15 2.15q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.287-.7a1.06 1.06 0 0 0-.287-.7 1.02 1.02 0 0 0-.713-.312.93.93 0 0 0-.712.287z"
             />
           </svg>
         </div>
@@ -76,7 +76,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
       aria-label="@userId:matrix.org (power 0)"
       class="mx_AccessibleButton mx_MemberTileView"
       role="button"
-      tabindex="0"
+      tabindex="-1"
     >
       <div
         class="mx_MemberTileView_left"
@@ -85,7 +85,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
           class="mx_MemberTileView_avatar"
         >
           <span
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="2"
             data-testid="avatar-img"
             data-type="round"
@@ -116,7 +116,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
         class="mx_MemberTileView_right"
       >
         <div
-          aria-labelledby=":r8:"
+          aria-labelledby="«r8»"
           class="mx_E2EIconView"
         >
           <svg
@@ -128,7 +128,7 @@ exports[`MemberTileView RoomMemberTileView should display an warning E2EIcon whe
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
         </div>
@@ -145,7 +145,7 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
       aria-label="@userId:matrix.org (power 0)"
       class="mx_AccessibleButton mx_MemberTileView"
       role="button"
-      tabindex="0"
+      tabindex="-1"
     >
       <div
         class="mx_MemberTileView_left"
@@ -154,7 +154,7 @@ exports[`MemberTileView RoomMemberTileView should not display an E2EIcon when th
           class="mx_MemberTileView_avatar"
         >
           <span
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="2"
             data-testid="avatar-img"
             data-type="round"
@@ -195,7 +195,7 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
     <div
       class="mx_AccessibleButton mx_MemberTileView"
       role="button"
-      tabindex="0"
+      tabindex="-1"
     >
       <div
         class="mx_MemberTileView_left"
@@ -205,7 +205,7 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
         >
           <span
             aria-hidden="true"
-            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="1"
             data-testid="avatar-img"
             data-type="round"
@@ -224,7 +224,29 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
       </div>
       <div
         class="mx_MemberTileView_right"
-      />
+      >
+        <div
+          class="mx_MemberTileView_userLabel"
+        >
+          Invited
+        </div>
+        <div
+          class="mx_Flex mx_InvitedIconView"
+          style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
+        >
+          <svg
+            fill="currentColor"
+            height="16px"
+            viewBox="0 0 24 24"
+            width="16px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2m0 5.111a1 1 0 0 0 .514.874l7 3.89a1 1 0 0 0 .972 0l7-3.89a1 1 0 1 0-.972-1.748L12 11.856 5.486 8.237A1 1 0 0 0 4 9.111"
+            />
+          </svg>
+        </div>
+      </div>
     </div>
   </div>
 </div>
diff --git a/test/unit-tests/components/views/rooms/memberlist/__snapshots__/PresenceIconView-test.tsx.snap b/test/unit-tests/components/views/rooms/memberlist/__snapshots__/PresenceIconView-test.tsx.snap
index edf24f529ea54d88474c003c95ae07eec9220ceb..fa0f72239fe224b5945506726e8849b1656dd22c 100644
--- a/test/unit-tests/components/views/rooms/memberlist/__snapshots__/PresenceIconView-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/memberlist/__snapshots__/PresenceIconView-test.tsx.snap
@@ -18,7 +18,7 @@ exports[`<PresenceIconView/> renders correctly for presence=busy 1`] = `
       >
         <path
           clip-rule="evenodd"
-          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM5.435 6.048A2.5 2.5 0 0 1 1.687 3.05l3.748 2.998Zm.914-1.19L2.648 1.897a2.5 2.5 0 0 1 3.701 2.961Z"
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0M5.435 6.048A2.5 2.5 0 0 1 1.687 3.05zm.914-1.19L2.648 1.897a2.5 2.5 0 0 1 3.701 2.961"
           fill-rule="evenodd"
         />
       </g>
@@ -54,7 +54,7 @@ exports[`<PresenceIconView/> renders correctly for presence=offline 1`] = `
       >
         <path
           clip-rule="evenodd"
-          d="M4 6.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM4 8a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"
+          d="M4 6.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5M4 8a4 4 0 1 0 0-8 4 4 0 0 0 0 8"
           fill-rule="evenodd"
         />
       </g>
@@ -89,7 +89,7 @@ exports[`<PresenceIconView/> renders correctly for presence=online 1`] = `
         clip-path="url(#a)"
       >
         <path
-          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0"
         />
       </g>
       <defs>
@@ -123,7 +123,7 @@ exports[`<PresenceIconView/> renders correctly for presence=unavailable/unreacha
         clip-path="url(#a)"
       >
         <path
-          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0"
         />
       </g>
       <defs>
@@ -157,7 +157,7 @@ exports[`<PresenceIconView/> renders correctly for presence=unavailable/unreacha
         clip-path="url(#a)"
       >
         <path
-          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
+          d="M8 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0"
         />
       </g>
       <defs>
diff --git a/test/unit-tests/components/views/rooms/memberlist/common.tsx b/test/unit-tests/components/views/rooms/memberlist/common.tsx
index 2d602356a7c814cd6192b82685acc687d3684cb4..786c9c875eb7c715dc0087818375fd9cbb512255 100644
--- a/test/unit-tests/components/views/rooms/memberlist/common.tsx
+++ b/test/unit-tests/components/views/rooms/memberlist/common.tsx
@@ -8,8 +8,16 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React, { act } from "react";
-import { render, RenderResult, waitFor } from "jest-matrix-react";
-import { Room, MatrixClient, RoomState, RoomMember, User, EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { render, type RenderResult, waitFor } from "jest-matrix-react";
+import {
+    Room,
+    type MatrixClient,
+    type RoomState,
+    RoomMember,
+    User,
+    EventType,
+    RoomStateEvent,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/EditWysiwygComposer-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/EditWysiwygComposer-test.tsx
index 05c7bc4608fc454bda7b42463e9a82a521582f6e..14905f05ccf80545ed620c7f78be9713be1bd527 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/EditWysiwygComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/EditWysiwygComposer-test.tsx
@@ -19,8 +19,11 @@ import { EditWysiwygComposer } from "../../../../../../src/components/views/room
 import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer";
 import { Emoji } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/Emoji";
 import { ChevronFace } from "../../../../../../src/components/structures/ContextMenu";
-import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
-import { ActionPayload } from "../../../../../../src/dispatcher/payloads";
+import {
+    type ComposerInsertPayload,
+    ComposerType,
+} from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
+import { type ActionPayload } from "../../../../../../src/dispatcher/payloads";
 import * as EmojiButton from "../../../../../../src/components/views/rooms/EmojiButton";
 import { createMocks } from "./utils";
 import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx
index 4cba44bd9c0d37d22a32521523983757a89b0155..d8215a101eb9b359506005c91a8232b60dbd8e6a 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx
@@ -17,7 +17,10 @@ import { Action } from "../../../../../../src/dispatcher/actions";
 import { flushPromises } from "../../../../../test-utils";
 import { SendWysiwygComposer } from "../../../../../../src/components/views/rooms/wysiwyg_composer/";
 import { aboveLeftOf } from "../../../../../../src/components/structures/ContextMenu";
-import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
+import {
+    type ComposerInsertPayload,
+    ComposerType,
+} from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
 import { setSelection } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
 import { createMocks } from "./utils";
 import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/FormattingButtons-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/FormattingButtons-test.tsx
index e0f9e2d8b4b7af74a2010b7bbaa26031a5cc7303..94d207eb91f2094f4232406ce6527fbbcf7d6d9e 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/FormattingButtons-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/FormattingButtons-test.tsx
@@ -9,7 +9,12 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { cleanup, render, screen, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@vector-im/matrix-wysiwyg";
+import {
+    type ActionState,
+    type ActionTypes,
+    type AllActionStates,
+    type FormattingFunctions,
+} from "@vector-im/matrix-wysiwyg";
 
 import { FormattingButtons } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/components/FormattingButtons";
 import * as LinkModal from "../../../../../../../src/components/views/rooms/wysiwyg_composer/components/LinkModal";
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/LinkModal-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/LinkModal-test.tsx
index f864f27de2999580ed6d13adf2a1caa1f7e7a6a2..4ff9b893db3069cebcaceccfcddeb59b228e7530 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/LinkModal-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/LinkModal-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { FormattingFunctions } from "@vector-im/matrix-wysiwyg";
+import { type FormattingFunctions } from "@vector-im/matrix-wysiwyg";
 import { render, screen, waitFor } from "jest-matrix-react";
 import React from "react";
 import userEvent from "@testing-library/user-event";
@@ -14,7 +14,7 @@ import userEvent from "@testing-library/user-event";
 import { LinkModal } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/components/LinkModal";
 import { mockPlatformPeg } from "../../../../../../test-utils";
 import * as selection from "../../../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
-import { SubSelection } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/types";
+import { type SubSelection } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/types";
 
 describe("LinkModal", () => {
     const formattingFunctions = {
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx
index 5886b501b436ff92c3d1048c92e2877ba71c601a..8a3d0af791f0bbb5785a36d793084a0715868ef2 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx
@@ -14,9 +14,9 @@ import { initOnce } from "@vector-im/matrix-wysiwyg";
 import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
 import { WysiwygAutocomplete } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete";
 import { getRoomContext, mkStubRoom, stubClient } from "../../../../../../test-utils";
-import Autocomplete from "../../../../../../../src/components/views/rooms/Autocomplete";
-import Autocompleter, { ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
-import AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
+import type Autocomplete from "../../../../../../../src/components/views/rooms/Autocomplete";
+import Autocompleter, { type ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
+import type AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
 import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
 
 const mockCompletion: ICompletion[] = [
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx
index e86488660e9856321898d8963c51add457497651..2d670dbc6bbb4b00f7fca017082a0137bc0d20f2 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/WysiwygComposer-test.tsx
@@ -24,14 +24,14 @@ import {
     getDefaultContextValue,
 } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/ComposerContext";
 import { createMocks } from "../utils";
-import EditorStateTransfer from "../../../../../../../src/utils/EditorStateTransfer";
-import { SubSelection } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/types";
+import type EditorStateTransfer from "../../../../../../../src/utils/EditorStateTransfer";
+import { type SubSelection } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/types";
 import { setSelection } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
 import { parseEditorStateTransfer } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/hooks/useInitialContent";
-import Autocompleter, { ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
-import AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
+import Autocompleter, { type ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
+import type AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
 import * as Permalinks from "../../../../../../../src/utils/permalinks/Permalinks";
-import { PermalinkParts } from "../../../../../../../src/utils/permalinks/PermalinkConstructor";
+import { type PermalinkParts } from "../../../../../../../src/utils/permalinks/PermalinkConstructor";
 import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
 
 beforeAll(initOnce, 10000);
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/__snapshots__/FormattingButtons-test.tsx.snap b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/__snapshots__/FormattingButtons-test.tsx.snap
index 651654287928ed4ddb3be7664b3ac2b0a407f41f..602c40f615eaa3ca157a403333dd2838b12d9e51 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/components/__snapshots__/FormattingButtons-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/components/__snapshots__/FormattingButtons-test.tsx.snap
@@ -20,7 +20,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.8 19c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 6.8 17V7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 8.8 5h3.525c1.083 0 2.083.333 3 1 .917.667 1.375 1.592 1.375 2.775 0 .85-.192 1.504-.575 1.963-.383.458-.742.787-1.075.987.417.183.88.525 1.387 1.025.509.5.763 1.25.763 2.25 0 1.483-.542 2.52-1.625 3.113-1.083.591-2.1.887-3.05.887H8.8Zm1.025-2.8h2.6c.8 0 1.287-.204 1.462-.612.175-.409.263-.705.263-.888 0-.183-.088-.48-.263-.887-.175-.409-.687-.613-1.537-.613H9.825v3Zm0-5.7h2.325c.55 0 .95-.142 1.2-.425a1.4 1.4 0 0 0 .375-.95c0-.4-.142-.725-.425-.975-.283-.25-.65-.375-1.1-.375H9.825V10.5Z"
+          d="M8.8 19q-.824 0-1.413-.587A1.93 1.93 0 0 1 6.8 17V7q0-.824.587-1.412A1.93 1.93 0 0 1 8.8 5h3.525q1.624 0 3 1T16.7 8.775q0 1.275-.575 1.963-.575.687-1.075.987.626.275 1.387 1.025.763.75.763 2.25 0 2.224-1.625 3.113-1.625.887-3.05.887zm1.025-2.8h2.6q1.2 0 1.462-.612.263-.614.263-.888 0-.275-.263-.887-.262-.613-1.537-.613H9.825zm0-5.7h2.325q.825 0 1.2-.425a1.4 1.4 0 0 0 .375-.95q0-.6-.425-.975t-1.1-.375H9.825z"
         />
       </svg>
     </button>
@@ -39,7 +39,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6.25 19c-.35 0-.646-.12-.888-.363A1.207 1.207 0 0 1 5 17.75c0-.35.12-.646.362-.887.242-.242.538-.363.888-.363H9l3-9H9.25c-.35 0-.646-.12-.887-.362A1.207 1.207 0 0 1 8 6.25c0-.35.12-.646.363-.888A1.21 1.21 0 0 1 9.25 5h7.5c.35 0 .646.12.887.362.242.242.363.538.363.888s-.12.646-.363.888a1.207 1.207 0 0 1-.887.362H14.5l-3 9h2.25c.35 0 .646.12.887.363.242.241.363.537.363.887s-.12.646-.363.887a1.207 1.207 0 0 1-.887.363h-7.5Z"
+          d="M6.25 19q-.525 0-.888-.363A1.2 1.2 0 0 1 5 17.75q0-.525.362-.887.363-.363.888-.363H9l3-9H9.25q-.525 0-.887-.362A1.2 1.2 0 0 1 8 6.25q0-.525.363-.888Q8.725 5 9.25 5h7.5q.525 0 .887.362.363.363.363.888t-.363.888a1.2 1.2 0 0 1-.887.362H14.5l-3 9h2.25q.525 0 .887.363.363.362.363.887t-.363.887a1.2 1.2 0 0 1-.887.363z"
         />
       </svg>
     </button>
@@ -58,7 +58,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6 21a.967.967 0 0 1-.713-.288A.968.968 0 0 1 5 20a.97.97 0 0 1 .287-.712A.967.967 0 0 1 6 19h12c.283 0 .52.096.712.288.192.191.288.429.288.712s-.096.52-.288.712A.968.968 0 0 1 18 21H6Zm6-4c-1.683 0-2.992-.525-3.925-1.575-.933-1.05-1.4-2.442-1.4-4.175V4.275c0-.35.13-.65.388-.9A1.27 1.27 0 0 1 7.974 3c.35 0 .65.125.9.375s.375.55.375.9V11.4c0 .933.233 1.692.7 2.275.467.583 1.15.875 2.05.875.9 0 1.583-.292 2.05-.875.467-.583.7-1.342.7-2.275V4.275c0-.35.13-.65.387-.9A1.27 1.27 0 0 1 16.05 3c.35 0 .65.125.9.375s.375.55.375.9v6.975c0 1.733-.467 3.125-1.4 4.175C14.992 16.475 13.683 17 12 17Z"
+          d="M6 21a.97.97 0 0 1-.713-.288A.97.97 0 0 1 5 20q0-.424.287-.712A.97.97 0 0 1 6 19h12q.424 0 .712.288.288.287.288.712 0 .424-.288.712A.97.97 0 0 1 18 21zm6-4q-2.525 0-3.925-1.575t-1.4-4.175V4.275q0-.525.388-.9A1.27 1.27 0 0 1 7.975 3q.525 0 .9.375t.375.9V11.4q0 1.4.7 2.275t2.05.875 2.05-.875.7-2.275V4.275q0-.525.387-.9A1.27 1.27 0 0 1 16.05 3q.525 0 .9.375t.375.9v6.975q0 2.6-1.4 4.175T12 17"
         />
       </svg>
     </button>
@@ -77,7 +77,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12.15 20c-1.267 0-2.392-.375-3.375-1.125-.983-.75-1.692-1.775-2.125-3.075l2.2-.95c.233.8.638 1.458 1.213 1.975.574.517 1.287.775 2.137.775.7 0 1.333-.167 1.9-.5.567-.333.85-.867.85-1.6 0-.3-.058-.575-.175-.825A2.362 2.362 0 0 0 14.3 14h2.8a4.279 4.279 0 0 1 .25 1.5c0 1.433-.513 2.542-1.538 3.325C14.788 19.608 13.567 20 12.15 20ZM3 12a.967.967 0 0 1-.712-.287A.968.968 0 0 1 2 11c0-.283.096-.52.288-.712A.967.967 0 0 1 3 10h18c.283 0 .52.096.712.288.192.191.288.429.288.712s-.096.52-.288.713A.968.968 0 0 1 21 12H3Zm9.05-8.15c1.1 0 2.063.27 2.887.813.825.541 1.463 1.37 1.913 2.487l-2.2.975a2.987 2.987 0 0 0-.838-1.3c-.408-.383-.979-.575-1.712-.575-.683 0-1.25.154-1.7.463-.45.308-.7.737-.75 1.287h-2.4c.033-1.15.487-2.13 1.363-2.937.875-.809 2.02-1.213 3.437-1.213Z"
+          d="M12.15 20q-1.9 0-3.375-1.125T6.65 15.8l2.2-.95q.35 1.2 1.213 1.975.861.775 2.137.775 1.05 0 1.9-.5t.85-1.6q0-.45-.175-.825A2.4 2.4 0 0 0 14.3 14h2.8a4.3 4.3 0 0 1 .25 1.5q0 2.15-1.538 3.325Q14.277 20 12.15 20M3 12a.97.97 0 0 1-.712-.287A.97.97 0 0 1 2 11q0-.424.288-.713A.97.97 0 0 1 3 10h18q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 21 12zm9.05-8.15q1.65 0 2.887.812T16.85 7.15l-2.2.975a3 3 0 0 0-.838-1.3Q13.2 6.25 12.1 6.25q-1.025 0-1.7.462-.675.463-.75 1.288h-2.4q.05-1.725 1.363-2.938Q9.925 3.85 12.05 3.85"
         />
       </svg>
     </button>
@@ -96,7 +96,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M4.5 7.5a1.45 1.45 0 0 1-1.06-.44A1.444 1.444 0 0 1 3 6c0-.412.147-.766.44-1.06A1.45 1.45 0 0 1 4.5 4.5c.412 0 .766.147 1.06.44.293.294.44.647.44 1.06 0 .412-.147.766-.44 1.06-.294.293-.647.44-1.06.44Zm4.787 11.212c.192.192.43.288.713.288h10c.283 0 .52-.096.712-.288A.968.968 0 0 0 21 18a.968.968 0 0 0-.288-.712A.968.968 0 0 0 20 17H10a.967.967 0 0 0-.713.288A.968.968 0 0 0 9 18c0 .283.096.52.287.712Zm0-5.999c.192.191.43.287.713.287h10a.97.97 0 0 0 .712-.287A.968.968 0 0 0 21 12a.968.968 0 0 0-.288-.713A.968.968 0 0 0 20 11H10a.967.967 0 0 0-.713.287A.968.968 0 0 0 9 12c0 .283.096.52.287.713Zm0-6c.192.191.43.287.713.287h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 21 6a.967.967 0 0 0-.288-.713A.968.968 0 0 0 20 5H10a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 6c0 .283.096.52.287.713ZM3.44 19.06c.294.293.648.44 1.06.44a1.45 1.45 0 0 0 1.06-.44c.293-.294.44-.647.44-1.06 0-.413-.147-.766-.44-1.06a1.445 1.445 0 0 0-1.06-.44 1.45 1.45 0 0 0-1.06.44c-.293.294-.44.647-.44 1.06 0 .413.147.766.44 1.06ZM4.5 13.5a1.45 1.45 0 0 1-1.06-.44A1.445 1.445 0 0 1 3 12c0-.412.147-.766.44-1.06a1.45 1.45 0 0 1 1.06-.44c.412 0 .766.147 1.06.44.293.294.44.648.44 1.06 0 .412-.147.766-.44 1.06-.294.293-.647.44-1.06.44Z"
+          d="M4.5 7.5q-.618 0-1.06-.44A1.44 1.44 0 0 1 3 6q0-.618.44-1.06.442-.44 1.06-.44t1.06.44Q6 5.383 6 6t-.44 1.06q-.44.44-1.06.44m4.788 11.213Q9.575 19 10 19h10q.424 0 .712-.288A.97.97 0 0 0 21 18a.97.97 0 0 0-.288-.712A.97.97 0 0 0 20 17H10a.97.97 0 0 0-.713.288A.97.97 0 0 0 9 18q0 .424.287.712m.001-5.999Q9.575 13 10 13h10q.424 0 .712-.287A.97.97 0 0 0 21 12a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 11H10a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 12q0 .424.287.713m.001-6Q9.575 7 10 7h10q.424 0 .712-.287A.97.97 0 0 0 21 6a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 5H10a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 6q0 .424.287.713M3.44 19.06q.442.44 1.06.44t1.06-.44Q6 18.62 6 18t-.44-1.06a1.45 1.45 0 0 0-1.06-.44q-.618 0-1.06.44Q3 17.38 3 18t.44 1.06M4.5 13.5q-.618 0-1.06-.44A1.45 1.45 0 0 1 3 12q0-.619.44-1.06.442-.44 1.06-.44t1.06.44Q6 11.383 6 12t-.44 1.06q-.44.44-1.06.44"
         />
       </svg>
     </button>
@@ -115,7 +115,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M9 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1ZM5.604 5.089A.75.75 0 0 1 6 5.75v4.5a.75.75 0 0 1-1.5 0V7.151l-.334.223a.75.75 0 0 1-.832-1.248l1.5-1a.75.75 0 0 1 .77-.037ZM5 13a2.02 2.02 0 0 0-1.139.321 1.846 1.846 0 0 0-.626.719 2.286 2.286 0 0 0-.234.921v.023l-.001.01v.005l.75.001H3a.75.75 0 0 0 1.5.01V15a.789.789 0 0 1 .077-.29.35.35 0 0 1 .116-.14c.04-.027.126-.07.307-.07s.267.043.307.07a.35.35 0 0 1 .116.14.788.788 0 0 1 .076.29v.008a.532.532 0 0 1-.14.352l-2.161 2.351a.748.748 0 0 0-.198.523v.016c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5h-.82l1.034-1.124C6.809 16 7 15.51 7 15h-.75H7v-.039l-.004-.068a2.285 2.285 0 0 0-.231-.853 1.846 1.846 0 0 0-.626-.719A2.02 2.02 0 0 0 5 13Zm-.5 2.003V15v.01-.008Z"
+          d="M9 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H10a1 1 0 0 1-1-1M5.604 5.089A.75.75 0 0 1 6 5.75v4.5a.75.75 0 0 1-1.5 0V7.151l-.334.223a.75.75 0 0 1-.832-1.248l1.5-1a.75.75 0 0 1 .77-.037M5 13a2 2 0 0 0-1.139.321 1.85 1.85 0 0 0-.626.719 2.3 2.3 0 0 0-.234.921v.023l-.001.01v.005l.75.001H3a.75.75 0 0 0 1.5.01V15l.01-.072a.8.8 0 0 1 .067-.218.35.35 0 0 1 .116-.14c.04-.027.126-.07.307-.07s.267.043.307.07a.35.35 0 0 1 .116.14.8.8 0 0 1 .076.29v.008a.53.53 0 0 1-.14.352l-2.161 2.351a.75.75 0 0 0-.198.523v.016c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5h-.82l1.034-1.124C6.809 16 7 15.51 7 15h-.75H7v-.039l-.004-.068a2.3 2.3 0 0 0-.231-.853 1.85 1.85 0 0 0-.626-.719A2 2 0 0 0 5 13m-.5 2.003V15v.01z"
         />
       </svg>
     </button>
@@ -134,7 +134,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M4.719 4.34c.094-.642-.366-1.236-1.028-1.328-.663-.092-1.276.354-1.371.996l-.808 5.478c-.094.642.366 1.237 1.028 1.328.663.092 1.276-.354 1.371-.996l.808-5.478Zm12.115 10.174c.095-.642-.366-1.237-1.028-1.328-.662-.092-1.276.354-1.37.996l-.809 5.478c-.094.642.366 1.236 1.028 1.328.663.092 1.277-.354 1.371-.996l.808-5.478ZM9.318 3.009c.665.077 1.138.662 1.058 1.306l-.022.175a220.467 220.467 0 0 1-.266 2.006c-.161 1.171-.368 2.579-.535 3.386-.13.636-.769 1.049-1.425.921-.656-.127-1.082-.745-.95-1.381.148-.72.345-2.052.509-3.237a190.652 190.652 0 0 0 .262-1.981l.021-.17c.08-.644.684-1.103 1.348-1.025Zm13.17 11.505c.094-.642-.366-1.237-1.028-1.328-.663-.092-1.276.354-1.371.996l-.808 5.478c-.094.642.366 1.236 1.028 1.328.663.092 1.276-.354 1.371-.996l.808-5.478Z"
+          d="M4.719 4.34c.094-.642-.366-1.236-1.028-1.328-.663-.092-1.276.354-1.371.996l-.808 5.478c-.094.642.366 1.237 1.028 1.328.663.092 1.276-.354 1.371-.996zm12.115 10.174c.095-.642-.366-1.237-1.028-1.328-.662-.092-1.276.354-1.37.996l-.809 5.478c-.094.642.366 1.236 1.028 1.328.663.092 1.277-.354 1.371-.996zM9.318 3.009c.665.077 1.138.662 1.058 1.306l-.022.175a221 221 0 0 1-.266 2.006c-.161 1.171-.368 2.579-.535 3.386-.13.636-.769 1.049-1.425.921s-1.082-.745-.95-1.381c.148-.72.345-2.052.509-3.237a191 191 0 0 0 .262-1.981l.021-.17c.08-.644.684-1.103 1.348-1.025m13.17 11.505c.094-.642-.366-1.237-1.028-1.328-.663-.092-1.276.354-1.371.996l-.808 5.478c-.094.642.366 1.236 1.028 1.328.663.092 1.276-.354 1.371-.996z"
         />
       </svg>
     </button>
@@ -153,7 +153,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M14.958 5.62a1 1 0 0 0-1.916-.574l-4 13.333a1 1 0 0 0 1.916.575l4-13.333ZM5.974 7.232a1 1 0 0 0-1.409.128l-3.333 4a1 1 0 0 0 0 1.28l3.333 4a1 1 0 1 0 1.537-1.28L3.302 12l2.8-3.36a1 1 0 0 0-.128-1.408Zm12.052 0a1 1 0 0 1 1.409.128l3.333 4a1 1 0 0 1 0 1.28l-3.333 4a1 1 0 1 1-1.537-1.28l2.8-3.36-2.8-3.36a1 1 0 0 1 .128-1.408Z"
+          d="M14.958 5.62a1 1 0 0 0-1.916-.574l-4 13.333a1 1 0 0 0 1.916.575zM5.974 7.232a1 1 0 0 0-1.409.128l-3.333 4a1 1 0 0 0 0 1.28l3.333 4a1 1 0 1 0 1.537-1.28L3.302 12l2.8-3.36a1 1 0 0 0-.128-1.408m12.053 0a1 1 0 0 1 1.408.128l3.333 4a1 1 0 0 1 0 1.28l-3.333 4a1 1 0 1 1-1.537-1.28l2.8-3.36-2.8-3.36a1 1 0 0 1 .128-1.408"
         />
       </svg>
     </button>
@@ -172,7 +172,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="m8.825 12 1.475-1.475c.2-.2.3-.433.3-.7 0-.267-.1-.5-.3-.7-.2-.2-.438-.3-.713-.3-.274 0-.512.1-.712.3L6.7 11.3c-.1.1-.17.208-.213.325a1.107 1.107 0 0 0-.062.375c0 .133.02.258.063.375a.877.877 0 0 0 .212.325l2.175 2.175c.2.2.438.3.713.3.275 0 .512-.1.712-.3.2-.2.3-.433.3-.7 0-.267-.1-.5-.3-.7L8.825 12Zm6.35 0L13.7 13.475c-.2.2-.3.433-.3.7 0 .267.1.5.3.7.2.2.438.3.713.3.274 0 .512-.1.712-.3L17.3 12.7c.1-.1.17-.208.212-.325.042-.117.063-.242.063-.375s-.02-.258-.063-.375a.877.877 0 0 0-.212-.325l-2.175-2.175a.999.999 0 0 0-1.425 0c-.2.2-.3.433-.3.7 0 .267.1.5.3.7L15.175 12ZM5 21c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 3 19V5c0-.55.196-1.02.587-1.413A1.926 1.926 0 0 1 5 3h14c.55 0 1.02.196 1.413.587.39.393.587.863.587 1.413v14c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 19 21H5Zm0-2h14V5H5v14Z"
+          d="m8.825 12 1.475-1.475q.3-.3.3-.7t-.3-.7-.713-.3-.712.3L6.7 11.3q-.15.15-.213.325a1.1 1.1 0 0 0-.062.375q0 .2.063.375a.9.9 0 0 0 .212.325l2.175 2.175q.3.3.713.3.412 0 .712-.3t.3-.7-.3-.7zm6.35 0L13.7 13.475q-.3.3-.3.7t.3.7.713.3.712-.3L17.3 12.7q.15-.15.212-.325.063-.175.063-.375t-.062-.375a.9.9 0 0 0-.213-.325l-2.175-2.175a1 1 0 0 0-1.425 0q-.3.3-.3.7t.3.7zM5 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 3 19V5q0-.824.587-1.412A1.93 1.93 0 0 1 5 3h14q.824 0 1.413.587Q21 4.176 21 5v14q0 .824-.587 1.413A1.93 1.93 0 0 1 19 21zm0-2h14V5H5z"
         />
       </svg>
     </button>
@@ -191,7 +191,7 @@ exports[`FormattingButtons renders in german 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
+          d="M12 19.071q-1.467 1.467-3.536 1.467-2.067 0-3.535-1.467t-1.467-3.535q0-2.07 1.467-3.536L7.05 9.879q.3-.3.707-.3t.707.3.301.707-.3.707l-2.122 2.121a2.9 2.9 0 0 0-.884 2.122q0 1.237.884 2.12.884.885 2.121.885t2.122-.884l2.121-2.121q.3-.3.707-.3t.707.3.3.707q0 .405-.3.707zm-1.414-4.243q-.3.3-.707.301a.97.97 0 0 1-.707-.3q-.3-.3-.301-.708 0-.405.3-.707l4.243-4.242q.3-.3.707-.3t.707.3.3.707-.3.707zm6.364-.707q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.301-.707 0-.405.3-.707l2.122-2.121q.884-.885.884-2.121 0-1.238-.884-2.122a2.9 2.9 0 0 0-2.121-.884q-1.237 0-2.122.884l-2.121 2.122q-.3.3-.707.3a.97.97 0 0 1-.707-.3q-.3-.3-.3-.708 0-.405.3-.707L12 4.93q1.467-1.467 3.536-1.467t3.535 1.467 1.467 3.536T19.071 12z"
         />
       </svg>
     </button>
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/useSuggestion-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/useSuggestion-test.tsx
index 06762b2c8f95c7597d9833d19d04f3e69f13c6e6..203f82cc66d388ad9d542e1542dbd30775c618af 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/useSuggestion-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/useSuggestion-test.tsx
@@ -5,10 +5,9 @@ Copyright 2023 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
-import React from "react";
-
+import type React from "react";
 import {
-    Suggestion,
+    type Suggestion,
     findSuggestionInText,
     getMappedSuggestion,
     processCommand,
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/utils-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/utils-test.tsx
index c691756200cc30b4831680cacad694bac2a743ff..f47c8075fd9787961fe3bbaa6062c682a85d4999 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/utils-test.tsx
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/hooks/utils-test.tsx
@@ -5,13 +5,13 @@ Copyright 2023 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
-import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type IEventRelation, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { waitFor } from "jest-matrix-react";
 
 import { TimelineRenderingType } from "../../../../../../../src/contexts/RoomContext";
 import { mkStubRoom, stubClient } from "../../../../../../test-utils";
 import ContentMessages from "../../../../../../../src/ContentMessages";
-import { IRoomState } from "../../../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../../../src/components/structures/RoomView";
 import {
     handleClipboardEvent,
     isEventToHandleAsClipboardEvent,
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils.ts b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils.ts
index cb72c90fb3b81b21d81d209b3b7551bd6ddc8062..3a9e18fd0a9043ed684d6327c25e5e9bbfdcc39d 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils.ts
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventTimeline, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type EventTimeline, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { getRoomContext, mkEvent, mkStubRoom, stubClient } from "../../../../../test-utils";
-import { IRoomState } from "../../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../../src/components/structures/RoomView";
 import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer";
 
 export function createMocks(eventContent = "Replying <strong>to</strong> this new content") {
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts
index 292314f87f9daea39445dce4045f78d21edd3223..bc572412c552e212721f33ed9d1ea5d55903859a 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import { mocked } from "jest-mock";
 import React from "react";
 
-import { ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
+import { type ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
 import {
     buildQuery,
     getRoomFromCompletion,
diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts
index 867947bef9136f26a127e1b55fb9a0f1cd818db8..23f46244db5c7a132c3c41fa95202dd3a2e6e1ed 100644
--- a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts
+++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventStatus, IEventRelation, MsgType } from "matrix-js-sdk/src/matrix";
+import { EventStatus, type IEventRelation, MsgType } from "matrix-js-sdk/src/matrix";
 
-import { IRoomState } from "../../../../../../../src/components/structures/RoomView";
+import { type IRoomState } from "../../../../../../../src/components/structures/RoomView";
 import {
     editMessage,
     sendMessage,
diff --git a/test/unit-tests/components/views/settings/AddPrivilegedUsers-test.tsx b/test/unit-tests/components/views/settings/AddPrivilegedUsers-test.tsx
index 875b5d9f193b741bce9af55f4cd554441382788f..2088bccc17e0fab7322515c20c2fd430d3fa1099 100644
--- a/test/unit-tests/components/views/settings/AddPrivilegedUsers-test.tsx
+++ b/test/unit-tests/components/views/settings/AddPrivilegedUsers-test.tsx
@@ -19,7 +19,7 @@ import {
     hasLowerOrEqualLevelThanDefaultLevel,
 } from "../../../../../src/components/views/settings/AddPrivilegedUsers";
 import UserProvider from "../../../../../src/autocomplete/UserProvider";
-import { ICompletion } from "../../../../../src/autocomplete/Autocompleter";
+import { type ICompletion } from "../../../../../src/autocomplete/Autocompleter";
 
 jest.mock("../../../../../src/autocomplete/UserProvider");
 jest.mock("../../../../../src/stores/WidgetStore");
@@ -84,12 +84,12 @@ describe("<AddPrivilegedUsers />", () => {
         // Find some suggestions and select them.
         const autocompleteInput = getByTestId("autocomplete-input");
 
-        act(() => {
+        await act(async () => {
             fireEvent.focus(autocompleteInput);
             fireEvent.change(autocompleteInput, { target: { value: "u" } });
+            await waitFor(() => expect(provider.mock.instances[0].getCompletions).toHaveBeenCalledTimes(1));
         });
 
-        await waitFor(() => expect(provider.mock.instances[0].getCompletions).toHaveBeenCalledTimes(1));
         const matchOne = getByTestId("autocomplete-suggestion-item-@user_1:host.local");
         const matchTwo = getByTestId("autocomplete-suggestion-item-@user_2:host.local");
 
diff --git a/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx b/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx
index dacd2c52a5ab8740ebe1d65f74e08820439a1187..6d3d3de692da52cbc8b8383629886b4d5c406692 100644
--- a/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx
+++ b/test/unit-tests/components/views/settings/AddRemoveThreepids-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { render, screen, waitFor, cleanup } from "jest-matrix-react";
-import { MatrixClient, MatrixError, ThreepidMedium } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MatrixError, ThreepidMedium } from "matrix-js-sdk/src/matrix";
 import React from "react";
 import userEvent from "@testing-library/user-event";
 import { mocked } from "jest-mock";
@@ -172,7 +172,7 @@ describe("AddRemoveThreepids", () => {
 
         const countryDropdown = await screen.findByRole("button", { name: /Country Dropdown/ });
         await userEvent.click(countryDropdown);
-        const gbOption = screen.getByRole("option", { name: "🇬🇧 United Kingdom (+44)" });
+        const gbOption = screen.getByText("United Kingdom (+44)");
         await userEvent.click(gbOption);
 
         const input = screen.getByRole("textbox", { name: "Phone Number" });
@@ -511,7 +511,7 @@ describe("AddRemoveThreepids", () => {
 
         const countryDropdown = screen.getByRole("button", { name: /Country Dropdown/ });
         await userEvent.click(countryDropdown);
-        const gbOption = screen.getByRole("option", { name: "🇬🇧 United Kingdom (+44)" });
+        const gbOption = screen.getByText("United Kingdom (+44)");
         await userEvent.click(gbOption);
 
         const input = screen.getByRole("textbox", { name: "Phone Number" });
diff --git a/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx b/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx
deleted file mode 100644
index ee7c642587840650f2cfb9ec4b8eef70a52e4866..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/settings/CrossSigningPanel-test.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { render, screen } from "jest-matrix-react";
-import { Mocked, mocked } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-
-import CrossSigningPanel from "../../../../../src/components/views/settings/CrossSigningPanel";
-import {
-    flushPromises,
-    getMockClientWithEventEmitter,
-    mockClientMethodsCrypto,
-    mockClientMethodsUser,
-} from "../../../../test-utils";
-import Modal from "../../../../../src/Modal";
-import ConfirmDestroyCrossSigningDialog from "../../../../../src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog";
-
-describe("<CrossSigningPanel />", () => {
-    const userId = "@alice:server.org";
-    let mockClient: Mocked<MatrixClient>;
-    const getComponent = () => render(<CrossSigningPanel />);
-
-    beforeEach(() => {
-        mockClient = getMockClientWithEventEmitter({
-            ...mockClientMethodsUser(userId),
-            ...mockClientMethodsCrypto(),
-            doesServerSupportUnstableFeature: jest.fn(),
-        });
-
-        mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
-    });
-
-    afterEach(() => {
-        jest.restoreAllMocks();
-    });
-
-    it("should render a spinner while loading", () => {
-        getComponent();
-
-        expect(screen.getByRole("progressbar")).toBeInTheDocument();
-    });
-
-    it("should render when homeserver does not support cross-signing", async () => {
-        mockClient.doesServerSupportUnstableFeature.mockResolvedValue(false);
-
-        getComponent();
-        await flushPromises();
-
-        expect(screen.getByText("Your homeserver does not support cross-signing.")).toBeInTheDocument();
-    });
-
-    describe("when cross signing is ready", () => {
-        it("should render when keys are not backed up", async () => {
-            getComponent();
-            await flushPromises();
-
-            expect(screen.getByTestId("summarised-status").innerHTML).toEqual(
-                "⚠️ Cross-signing is ready but keys are not backed up.",
-            );
-            expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
-        });
-
-        it("should render when keys are backed up", async () => {
-            mocked(mockClient.getCrypto()!.getCrossSigningStatus).mockResolvedValue({
-                publicKeysOnDevice: true,
-                privateKeysInSecretStorage: true,
-                privateKeysCachedLocally: {
-                    masterKey: true,
-                    selfSigningKey: true,
-                    userSigningKey: true,
-                },
-            });
-            getComponent();
-            await flushPromises();
-
-            expect(screen.getByTestId("summarised-status").innerHTML).toEqual("✅ Cross-signing is ready for use.");
-            expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
-        });
-
-        it("should allow reset of cross-signing", async () => {
-            mockClient.getCrypto()!.bootstrapCrossSigning = jest.fn().mockResolvedValue(undefined);
-            getComponent();
-            await flushPromises();
-
-            const modalSpy = jest.spyOn(Modal, "createDialog");
-
-            screen.getByRole("button", { name: "Reset" }).click();
-            expect(modalSpy).toHaveBeenCalledWith(ConfirmDestroyCrossSigningDialog, expect.any(Object));
-            modalSpy.mock.lastCall![1]!.onFinished(true);
-            expect(mockClient.getCrypto()!.bootstrapCrossSigning).toHaveBeenCalledWith(
-                expect.objectContaining({ setupNewCrossSigning: true }),
-            );
-        });
-    });
-
-    describe("when cross signing is not ready", () => {
-        beforeEach(() => {
-            mocked(mockClient.getCrypto()!.isCrossSigningReady).mockResolvedValue(false);
-        });
-
-        it("should render when keys are not backed up", async () => {
-            getComponent();
-            await flushPromises();
-
-            expect(screen.getByTestId("summarised-status").innerHTML).toEqual("Cross-signing is not set up.");
-        });
-
-        it("should render when keys are backed up", async () => {
-            mocked(mockClient.getCrypto()!.getCrossSigningStatus).mockResolvedValue({
-                publicKeysOnDevice: true,
-                privateKeysInSecretStorage: true,
-                privateKeysCachedLocally: {
-                    masterKey: true,
-                    selfSigningKey: true,
-                    userSigningKey: true,
-                },
-            });
-            getComponent();
-            await flushPromises();
-
-            expect(screen.getByTestId("summarised-status").innerHTML).toEqual(
-                "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
-            );
-            expect(screen.getByText("Cross-signing private keys:").parentElement!).toMatchSnapshot();
-        });
-    });
-});
diff --git a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx b/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx
deleted file mode 100644
index 3d368734731b83891e47e14ea13a76fe5a5deaa9..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { render, waitFor, screen, fireEvent } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { mocked } from "jest-mock";
-
-import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
-import * as TestUtils from "../../../../test-utils";
-import CryptographyPanel from "../../../../../src/components/views/settings/CryptographyPanel";
-import { withClientContextRenderOptions } from "../../../../test-utils";
-
-describe("CryptographyPanel", () => {
-    it("shows the session ID and key", async () => {
-        const sessionId = "ABCDEFGHIJ";
-        const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl";
-        const sessionKeyFormatted = "<strong>AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl</strong>";
-
-        TestUtils.stubClient();
-        const client: MatrixClient = MatrixClientPeg.safeGet();
-        client.deviceId = sessionId;
-
-        mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" });
-
-        // When we render the CryptographyPanel
-        const rendered = render(<CryptographyPanel />, withClientContextRenderOptions(client));
-
-        // Then it displays info about the user's session
-        const codes = rendered.container.querySelectorAll("code");
-        expect(codes.length).toEqual(2);
-        expect(codes[0].innerHTML).toEqual(sessionId);
-
-        // Initially a placeholder
-        expect(codes[1].innerHTML).toEqual("<strong>...</strong>");
-
-        // Then the actual key
-        await waitFor(() => expect(codes[1].innerHTML).toEqual(sessionKeyFormatted));
-    });
-
-    it("handles errors fetching session key", async () => {
-        const sessionId = "ABCDEFGHIJ";
-
-        TestUtils.stubClient();
-        const client: MatrixClient = MatrixClientPeg.safeGet();
-        client.deviceId = sessionId;
-
-        mocked(client.getCrypto()!.getOwnDeviceKeys).mockRejectedValue(new Error("bleh"));
-
-        // When we render the CryptographyPanel
-        const rendered = render(<CryptographyPanel />, withClientContextRenderOptions(client));
-
-        // Then it displays info about the user's session
-        const codes = rendered.container.querySelectorAll("code");
-
-        // Initially a placeholder
-        expect(codes[1].innerHTML).toEqual("<strong>...</strong>");
-
-        // Then "not supported key
-        await waitFor(() => expect(codes[1].innerHTML).toEqual("<strong>&lt;not supported&gt;</strong>"));
-    });
-
-    it("should open the export e2e keys dialog on click", async () => {
-        const sessionId = "ABCDEFGHIJ";
-        const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl";
-
-        TestUtils.stubClient();
-        const client: MatrixClient = MatrixClientPeg.safeGet();
-        client.deviceId = sessionId;
-
-        mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" });
-
-        render(<CryptographyPanel />, withClientContextRenderOptions(client));
-        fireEvent.click(await screen.findByRole("button", { name: "Export E2E room keys" }));
-        await expect(screen.findByRole("heading", { name: "Export room keys" })).resolves.toBeInTheDocument();
-    });
-
-    it("should open the import e2e keys dialog on click", async () => {
-        const sessionId = "ABCDEFGHIJ";
-        const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl";
-
-        TestUtils.stubClient();
-        const client: MatrixClient = MatrixClientPeg.safeGet();
-        client.deviceId = sessionId;
-
-        mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" });
-
-        render(<CryptographyPanel />, withClientContextRenderOptions(client));
-        fireEvent.click(await screen.findByRole("button", { name: "Import E2E room keys" }));
-        await expect(screen.findByRole("heading", { name: "Import room keys" })).resolves.toBeInTheDocument();
-    });
-});
diff --git a/test/unit-tests/components/views/settings/EventIndexPanel-test.tsx b/test/unit-tests/components/views/settings/EventIndexPanel-test.tsx
index c154a3c2a81c07239e90e87e9004bcf5b9ce7766..738ff95c76b493feff369ca465663c2e60cda559 100644
--- a/test/unit-tests/components/views/settings/EventIndexPanel-test.tsx
+++ b/test/unit-tests/components/views/settings/EventIndexPanel-test.tsx
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { fireEvent, render, screen, within } from "jest-matrix-react";
-import { defer, IDeferred } from "matrix-js-sdk/src/utils";
 
 import EventIndexPanel from "../../../../../src/components/views/settings/EventIndexPanel";
 import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg";
@@ -140,9 +139,9 @@ describe("<EventIndexPanel />", () => {
         });
         it("enables event indexing on enable button click", async () => {
             jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
-            let deferredInitEventIndex: IDeferred<boolean> | undefined;
+            let deferredInitEventIndex: PromiseWithResolvers<boolean> | undefined;
             jest.spyOn(EventIndexPeg, "initEventIndex").mockImplementation(() => {
-                deferredInitEventIndex = defer<boolean>();
+                deferredInitEventIndex = Promise.withResolvers<boolean>();
                 return deferredInitEventIndex.promise;
             });
 
diff --git a/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx b/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx
index 87cef697868e734636e627b92374b85667640133..57e5dd39b37ece5011d5ab8585f72a27aaa9775a 100644
--- a/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx
+++ b/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx
@@ -10,8 +10,8 @@ import React from "react";
 import { act, fireEvent, render, screen, waitFor, within } from "jest-matrix-react";
 import {
     EventType,
-    GuestAccess,
-    HistoryVisibility,
+    type GuestAccess,
+    type HistoryVisibility,
     JoinRule,
     MatrixEvent,
     Room,
@@ -21,7 +21,6 @@ import {
     Visibility,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { defer, IDeferred } from "matrix-js-sdk/src/utils";
 
 import {
     clearAllModals,
@@ -30,7 +29,9 @@ import {
     mockClientMethodsUser,
 } from "../../../../test-utils";
 import { filterBoolean } from "../../../../../src/utils/arrays";
-import JoinRuleSettings, { JoinRuleSettingsProps } from "../../../../../src/components/views/settings/JoinRuleSettings";
+import JoinRuleSettings, {
+    type JoinRuleSettingsProps,
+} from "../../../../../src/components/views/settings/JoinRuleSettings";
 import { PreferredRoomVersions } from "../../../../../src/utils/PreferredRoomVersions";
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
@@ -160,7 +161,7 @@ describe("<JoinRuleSettings />", () => {
             });
 
             it(`upgrades room when changing join rule to ${joinRule}`, async () => {
-                const deferredInvites: IDeferred<any>[] = [];
+                const deferredInvites: PromiseWithResolvers<any>[] = [];
                 // room that doesn't support the join rule
                 const room = new Room(roomId, client, userId);
                 const parentSpace = new Room("!parentSpace:server.org", client, userId);
@@ -183,7 +184,7 @@ describe("<JoinRuleSettings />", () => {
                 // resolve invites by hand
                 // flushPromises is too blunt to test reliably
                 client.invite.mockImplementation(() => {
-                    const p = defer<{}>();
+                    const p = Promise.withResolvers<{}>();
                     deferredInvites.push(p);
                     return p.promise;
                 });
diff --git a/test/unit-tests/components/views/settings/Notifications-test.tsx b/test/unit-tests/components/views/settings/Notifications-test.tsx
index c4b893920df6752863072722531a6849eba0e598..1566013aba65ad6df6169850cf578052d4564aa2 100644
--- a/test/unit-tests/components/views/settings/Notifications-test.tsx
+++ b/test/unit-tests/components/views/settings/Notifications-test.tsx
@@ -8,22 +8,22 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import {
-    IPushRule,
-    IPushRules,
+    type IPushRule,
+    type IPushRules,
     RuleId,
-    IPusher,
+    type IPusher,
     LOCAL_NOTIFICATION_SETTINGS_PREFIX,
     MatrixEvent,
     Room,
     PushRuleActionName,
     TweakName,
     ConditionKind,
-    IPushRuleCondition,
+    type IPushRuleCondition,
     PushRuleKind,
-    IThreepid,
+    type IThreepid,
     ThreepidMedium,
 } from "matrix-js-sdk/src/matrix";
-import { randomString } from "matrix-js-sdk/src/randomstring";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
 import {
     act,
     fireEvent,
@@ -36,6 +36,7 @@ import {
 } from "jest-matrix-react";
 import { mocked } from "jest-mock";
 import userEvent from "@testing-library/user-event";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
 
 import Notifications from "../../../../../src/components/views/settings/Notifications";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
@@ -287,7 +288,7 @@ describe("<Notifications />", () => {
 
     beforeEach(async () => {
         let i = 0;
-        mocked(randomString).mockImplementation(() => {
+        mocked(secureRandomString).mockImplementation(() => {
             return "testid_" + i++;
         });
 
@@ -301,6 +302,8 @@ describe("<Notifications />", () => {
         mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
         mockClient.addPushRule.mockClear();
         mockClient.deletePushRule.mockClear();
+        // @ts-expect-error
+        mockClient.pushProcessor = new PushProcessor(mockClient);
 
         userEvent.setup();
 
diff --git a/test/unit-tests/components/views/settings/PowerLevelSelector-test.tsx b/test/unit-tests/components/views/settings/PowerLevelSelector-test.tsx
index d94587f0f38f3ac2151670df63dacc8079590bca..2b9b3e62fac31d40db9b6fa664042358caba76de 100644
--- a/test/unit-tests/components/views/settings/PowerLevelSelector-test.tsx
+++ b/test/unit-tests/components/views/settings/PowerLevelSelector-test.tsx
@@ -7,7 +7,7 @@
  */
 
 import { render, screen } from "jest-matrix-react";
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import userEvent from "@testing-library/user-event";
 
 import { PowerLevelSelector } from "../../../../../src/components/views/settings/PowerLevelSelector";
@@ -61,7 +61,12 @@ describe("PowerLevelSelector", () => {
 
     it("should be able to change the power level of the current user", async () => {
         const onClick = jest.fn();
-        renderPLS({ onClick });
+        const userLevels = {
+            [currentUser]: 100,
+            "@alice:server.org": 100,
+            "@bob:server.org": 0,
+        };
+        renderPLS({ userLevels, onClick });
 
         // Until the power level is changed, the apply button should be disabled
         // compound button is using aria-disabled instead of the disabled attribute, we can't toBeDisabled on it
@@ -107,4 +112,58 @@ describe("PowerLevelSelector", () => {
 
         expect(screen.getByText("empty label")).toBeInTheDocument();
     });
+
+    it("should display modal warning if user is last admin", async () => {
+        const onClick = jest.fn();
+
+        renderPLS({ onClick });
+
+        // Until the power level is changed, the apply button should be disabled
+        // compound button is using aria-disabled instead of the disabled attribute, we can't toBeDisabled on it
+        expect(screen.getByRole("button", { name: "Apply" })).toHaveAttribute("aria-disabled", "true");
+
+        const select = screen.getByRole("combobox", { name: currentUser });
+        // Sanity check
+        expect(select).toHaveValue("100");
+
+        // Change current user power level to 50
+        await userEvent.selectOptions(select, "50");
+
+        // modal should appear because only admin in the room
+        expect(screen.findByText("WARNING")).toBeTruthy();
+
+        await userEvent.click(screen.getByRole("button", { name: "Continue" }));
+
+        expect(select).toHaveValue("50");
+        // After the user level changes, the apply button should be enabled
+        expect(screen.getByRole("button", { name: "Apply" })).toHaveAttribute("aria-disabled", "false");
+
+        // Click on Apply should call onClick with the new power level
+        await userEvent.click(screen.getByRole("button", { name: "Apply" }));
+        expect(onClick).toHaveBeenCalledWith(50, currentUser);
+    });
+
+    it("should display modal warning if user is last admin and return to initial value if user cancel", async () => {
+        const onClick = jest.fn();
+
+        renderPLS({ onClick });
+
+        // Until the power level is changed, the apply button should be disabled
+        // compound button is using aria-disabled instead of the disabled attribute, we can't toBeDisabled on it
+        expect(screen.getByRole("button", { name: "Apply" })).toHaveAttribute("aria-disabled", "true");
+
+        const select = screen.getByRole("combobox", { name: currentUser });
+        // Sanity check
+        expect(select).toHaveValue("100");
+
+        // Change current user power level to 50
+        await userEvent.selectOptions(select, "50");
+
+        // modal should appear because only admin in the room
+        expect(screen.findByText("WARNING")).toBeTruthy();
+
+        await userEvent.click(screen.getByRole("button", { name: "Cancel" }));
+        // the power level should be back to initial value
+        expect(select).toHaveValue("100");
+    });
 });
diff --git a/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx b/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx
deleted file mode 100644
index 78d59c07f17b75257b91332e5a6541655a2f6232..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/settings/SecureBackupPanel-test.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { fireEvent, render, screen, within } from "jest-matrix-react";
-import { mocked } from "jest-mock";
-
-import {
-    flushPromises,
-    getMockClientWithEventEmitter,
-    mockClientMethodsCrypto,
-    mockClientMethodsUser,
-} from "../../../../test-utils";
-import SecureBackupPanel from "../../../../../src/components/views/settings/SecureBackupPanel";
-import { accessSecretStorage } from "../../../../../src/SecurityManager";
-
-jest.mock("../../../../../src/SecurityManager", () => ({
-    accessSecretStorage: jest.fn(),
-}));
-
-describe("<SecureBackupPanel />", () => {
-    const userId = "@alice:server.org";
-    const client = getMockClientWithEventEmitter({
-        ...mockClientMethodsUser(userId),
-        ...mockClientMethodsCrypto(),
-        getClientWellKnown: jest.fn(),
-    });
-
-    const getComponent = () => render(<SecureBackupPanel />);
-
-    beforeEach(() => {
-        jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
-            version: "1",
-            algorithm: "test",
-            auth_data: {
-                public_key: "1234",
-            },
-        });
-        Object.assign(client.getCrypto()!, {
-            isKeyBackupTrusted: jest.fn().mockResolvedValue({
-                trusted: false,
-                matchesDecryptionKey: false,
-            }),
-            getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
-            deleteKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
-        });
-
-        mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
-
-        mocked(accessSecretStorage).mockClear().mockResolvedValue();
-    });
-
-    it("displays a loader while checking keybackup", async () => {
-        getComponent();
-        expect(screen.getByRole("progressbar")).toBeInTheDocument();
-        await flushPromises();
-        expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
-    });
-
-    it("handles error fetching backup", async () => {
-        // getKeyBackupInfo can fail for various reasons
-        jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
-            throw new Error("beep beep");
-        });
-        const renderResult = getComponent();
-        await renderResult.findByText("Unable to load key backup status");
-        expect(renderResult.container).toMatchSnapshot();
-    });
-
-    it("handles absence of backup", async () => {
-        jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
-        getComponent();
-        // flush getKeyBackupInfo promise
-        await flushPromises();
-        expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument();
-    });
-
-    it("suggests connecting session to key backup when backup exists", async () => {
-        const { container } = getComponent();
-        // flush checkKeyBackup promise
-        await flushPromises();
-
-        expect(container).toMatchSnapshot();
-    });
-
-    it("displays when session is connected to key backup", async () => {
-        mocked(client.getCrypto()!).getActiveSessionBackupVersion.mockResolvedValue("1");
-        getComponent();
-        // flush checkKeyBackup promise
-        await flushPromises();
-
-        expect(screen.getByText("✅ This session is backing up your keys.")).toBeInTheDocument();
-    });
-
-    it("asks for confirmation before deleting a backup", async () => {
-        getComponent();
-        // flush checkKeyBackup promise
-        await flushPromises();
-
-        fireEvent.click(screen.getByText("Delete Backup"));
-
-        const dialog = await screen.findByRole("dialog");
-
-        expect(
-            within(dialog).getByText(
-                "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
-            ),
-        ).toBeInTheDocument();
-
-        fireEvent.click(within(dialog).getByText("Cancel"));
-
-        expect(client.getCrypto()!.deleteKeyBackupVersion).not.toHaveBeenCalled();
-    });
-
-    it("deletes backup after confirmation", async () => {
-        jest.spyOn(client.getCrypto()!, "getKeyBackupInfo")
-            .mockResolvedValueOnce({
-                version: "1",
-                algorithm: "test",
-                auth_data: {
-                    public_key: "1234",
-                },
-            })
-            .mockResolvedValue(null);
-        getComponent();
-
-        fireEvent.click(await screen.findByText("Delete Backup"));
-
-        const dialog = await screen.findByRole("dialog");
-
-        expect(
-            within(dialog).getByText(
-                "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
-            ),
-        ).toBeInTheDocument();
-
-        fireEvent.click(within(dialog).getByTestId("dialog-primary-button"));
-
-        expect(client.getCrypto()!.deleteKeyBackupVersion).toHaveBeenCalledWith("1");
-
-        // delete request
-        await flushPromises();
-        // refresh backup info
-        await flushPromises();
-    });
-
-    it("resets secret storage", async () => {
-        mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(true);
-        getComponent();
-        // flush checkKeyBackup promise
-        await flushPromises();
-
-        jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockClear();
-        mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();
-
-        fireEvent.click(screen.getByText("Reset"));
-
-        // enter loading state
-        expect(accessSecretStorage).toHaveBeenCalled();
-        await flushPromises();
-
-        // backup status refreshed
-        expect(client.getCrypto()!.getKeyBackupInfo).toHaveBeenCalled();
-        expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
-    });
-});
diff --git a/test/unit-tests/components/views/settings/SetIdServer-test.tsx b/test/unit-tests/components/views/settings/SetIdServer-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cf8fb45f0d7c3747ce9db23ad6e3ca0f39c03de9
--- /dev/null
+++ b/test/unit-tests/components/views/settings/SetIdServer-test.tsx
@@ -0,0 +1,101 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render, waitFor } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock-jest";
+
+import SetIdServer from "../../../../../src/components/views/settings/SetIdServer";
+import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
+import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsServer } from "../../../../test-utils";
+
+describe("<SetIdServer />", () => {
+    const userId = "@alice:server.org";
+
+    const mockClient = getMockClientWithEventEmitter({
+        ...mockClientMethodsUser(userId),
+        ...mockClientMethodsServer(),
+        getOpenIdToken: jest.fn().mockResolvedValue("a_token"),
+        getTerms: jest.fn(),
+        setAccountData: jest.fn(),
+    });
+
+    const getComponent = () => (
+        <MatrixClientContext.Provider value={mockClient}>
+            <SetIdServer missingTerms={false} />
+        </MatrixClientContext.Provider>
+    );
+
+    afterAll(() => {
+        jest.resetAllMocks();
+    });
+
+    it("renders expected fields", () => {
+        const { asFragment } = render(getComponent());
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should allow setting an identity server", async () => {
+        const { getByLabelText, getByRole, findByRole } = render(getComponent());
+
+        fetchMock.get("https://identity.example.org/_matrix/identity/v2", {
+            body: {},
+        });
+        fetchMock.get("https://identity.example.org/_matrix/identity/v2/account", {
+            body: { user_id: userId },
+        });
+        fetchMock.post("https://identity.example.org/_matrix/identity/v2/account/register", {
+            body: { token: "foobar" },
+        });
+
+        const identServerField = getByLabelText("Enter a new identity server");
+        await userEvent.type(identServerField, "https://identity.example.org");
+        await userEvent.click(getByRole("button", { name: "Change" }));
+        await userEvent.click(await findByRole("button", { name: "Continue" }));
+    });
+
+    it("should clear input on cancel", async () => {
+        const { getByLabelText, findByRole } = render(getComponent());
+        const identServerField = getByLabelText("Enter a new identity server");
+        await userEvent.type(identServerField, "https://identity.example.org");
+        await userEvent.click(await findByRole("button", { name: "Reset" }));
+        expect((identServerField as HTMLInputElement).value).toEqual("");
+    });
+
+    it("should show error when an error occurs", async () => {
+        const { getByLabelText, getByRole, getByText } = render(getComponent());
+
+        fetchMock.get("https://invalid.example.org/_matrix/identity/v2", {
+            body: {},
+            status: 404,
+        });
+        fetchMock.get("https://invalid.example.org/_matrix/identity/v2/account", {
+            body: {},
+            status: 404,
+        });
+        fetchMock.post("https://invalid.example.org/_matrix/identity/v2/account/register", {
+            body: {},
+            status: 404,
+        });
+
+        const identServerField = getByLabelText("Enter a new identity server");
+        await userEvent.type(identServerField, "https://invalid.example.org");
+        await userEvent.click(getByRole("button", { name: "Change" }));
+
+        await waitFor(
+            () => {
+                expect(getByText("Not a valid identity server (status code 404)")).toBeVisible();
+            },
+            { timeout: 3000 },
+        );
+
+        // Check the error vanishes when the input is edited.
+        await userEvent.type(identServerField, "https://identity2.example.org");
+        expect(() => getByText("Not a valid identity server (status code 404)")).toThrow();
+    });
+});
diff --git a/test/unit-tests/components/views/settings/ThemeChoicePanel-test.tsx b/test/unit-tests/components/views/settings/ThemeChoicePanel-test.tsx
index 354fd44b9df703668cf7a65b6fba88691d61cc26..f1dbc9a2e0d6f750deb5c3d34408d087c87e6723 100644
--- a/test/unit-tests/components/views/settings/ThemeChoicePanel-test.tsx
+++ b/test/unit-tests/components/views/settings/ThemeChoicePanel-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { act, render, screen, waitFor } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import userEvent from "@testing-library/user-event";
 import fetchMock from "fetch-mock-jest";
 
diff --git a/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx b/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx
index 0c80cd6f5d017c3d51dc9726499b6c7873319653..9a5b8043ac48e9c94def692d6a9951f77bd1ba81 100644
--- a/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx
+++ b/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ChangeEvent } from "react";
+import React, { type ChangeEvent } from "react";
 import { act, render, screen } from "jest-matrix-react";
-import { MatrixClient, UploadResponse } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type UploadResponse } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 import userEvent from "@testing-library/user-event";
 import { TooltipProvider } from "@vector-im/compound-web";
 
 import UserProfileSettings from "../../../../../src/components/views/settings/UserProfileSettings";
 import { mkStubRoom, stubClient } from "../../../../test-utils";
-import { ToastContext, ToastRack } from "../../../../../src/contexts/ToastContext";
+import { ToastContext, type ToastRack } from "../../../../../src/contexts/ToastContext";
 import { OwnProfileStore } from "../../../../../src/stores/OwnProfileStore";
 import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
 import dis from "../../../../../src/dispatcher/dispatcher";
diff --git a/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap
deleted file mode 100644
index d484ba4a3be0381f55771b7110ee3fda93e92409..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/settings/__snapshots__/CrossSigningPanel-test.tsx.snap
+++ /dev/null
@@ -1,40 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`<CrossSigningPanel /> when cross signing is not ready should render when keys are backed up 1`] = `
-<tr>
-  <th
-    scope="row"
-  >
-    Cross-signing private keys:
-  </th>
-  <td>
-    in secret storage
-  </td>
-</tr>
-`;
-
-exports[`<CrossSigningPanel /> when cross signing is ready should render when keys are backed up 1`] = `
-<tr>
-  <th
-    scope="row"
-  >
-    Cross-signing private keys:
-  </th>
-  <td>
-    in secret storage
-  </td>
-</tr>
-`;
-
-exports[`<CrossSigningPanel /> when cross signing is ready should render when keys are not backed up 1`] = `
-<tr>
-  <th
-    scope="row"
-  >
-    Cross-signing private keys:
-  </th>
-  <td>
-    not found in storage
-  </td>
-</tr>
-`;
diff --git a/test/unit-tests/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap
index 2aa08adb943632cffef8c657b711057fed7e4ef6..6c99c3fbeb301306f17ddb41e7a87e3632311421 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap
@@ -19,33 +19,33 @@ exports[`<LayoutSwitcher /> should render 1`] = `
       class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
     >
       <form
-        class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
+        class="_root_19upo_16 mx_LayoutSwitcher_LayoutSelector"
       >
         <div
-          class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+          class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
         >
           <label
             aria-label="Modern"
-            class="_label_ssths_67"
-            for="radix-:r0:"
+            class="_label_19upo_59"
+            for="radix-«r0»"
           >
             <div
               class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
             >
               <div
-                class="_container_1vw5h_18"
+                class="_container_1e0uz_10"
               >
                 <input
                   checked=""
-                  class="_input_1vw5h_26"
-                  id="radix-:r0:"
+                  class="_input_1e0uz_18"
+                  id="radix-«r0»"
                   name="layout"
                   title=""
                   type="radio"
                   value="group"
                 />
                 <div
-                  class="_ui_1vw5h_27"
+                  class="_ui_1e0uz_19"
                 />
               </div>
               <span>
@@ -84,7 +84,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                   class="mx_EventTile_avatar"
                 >
                   <span
-                    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                     data-color="2"
                     data-testid="avatar-img"
                     data-type="round"
@@ -138,7 +138,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                          d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                         />
                       </svg>
                     </div>
@@ -149,29 +149,29 @@ exports[`<LayoutSwitcher /> should render 1`] = `
           </label>
         </div>
         <div
-          class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+          class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
         >
           <label
             aria-label="Message bubbles"
-            class="_label_ssths_67"
-            for="radix-:r9:"
+            class="_label_19upo_59"
+            for="radix-«r9»"
           >
             <div
               class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
             >
               <div
-                class="_container_1vw5h_18"
+                class="_container_1e0uz_10"
               >
                 <input
-                  class="_input_1vw5h_26"
-                  id="radix-:r9:"
+                  class="_input_1e0uz_18"
+                  id="radix-«r9»"
                   name="layout"
                   title=""
                   type="radio"
                   value="bubble"
                 />
                 <div
-                  class="_ui_1vw5h_27"
+                  class="_ui_1e0uz_19"
                 />
               </div>
               <span>
@@ -210,7 +210,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                   class="mx_EventTile_avatar"
                 >
                   <span
-                    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                     data-color="2"
                     data-testid="avatar-img"
                     data-type="round"
@@ -264,7 +264,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                          d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                         />
                       </svg>
                     </div>
@@ -275,29 +275,29 @@ exports[`<LayoutSwitcher /> should render 1`] = `
           </label>
         </div>
         <div
-          class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+          class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
         >
           <label
             aria-label="IRC (experimental)"
-            class="_label_ssths_67"
-            for="radix-:ri:"
+            class="_label_19upo_59"
+            for="radix-«ri»"
           >
             <div
               class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
             >
               <div
-                class="_container_1vw5h_18"
+                class="_container_1e0uz_10"
               >
                 <input
-                  class="_input_1vw5h_26"
-                  id="radix-:ri:"
+                  class="_input_1e0uz_18"
+                  id="radix-«ri»"
                   name="layout"
                   title=""
                   type="radio"
                   value="irc"
                 />
                 <div
-                  class="_ui_1vw5h_27"
+                  class="_ui_1e0uz_19"
                 />
               </div>
               <span>
@@ -336,7 +336,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                   class="mx_EventTile_avatar"
                 >
                   <span
-                    class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                    class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                     data-color="2"
                     data-testid="avatar-img"
                     data-type="round"
@@ -390,7 +390,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                          d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                         />
                       </svg>
                     </div>
@@ -402,42 +402,42 @@ exports[`<LayoutSwitcher /> should render 1`] = `
         </div>
       </form>
       <form
-        class="_root_ssths_24"
+        class="_root_19upo_16"
       >
         <div
-          class="_inline-field_ssths_40"
+          class="_inline-field_19upo_32"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_qnvru_18"
+              class="_container_19o42_10"
             >
               <input
-                aria-describedby="radix-:rs:"
-                class="_input_qnvru_32"
-                id="radix-:rr:"
+                aria-describedby="radix-«rs»"
+                class="_input_19o42_24"
+                id="radix-«rr»"
                 name="compactLayout"
                 title=""
                 type="checkbox"
               />
               <div
-                class="_ui_qnvru_42"
+                class="_ui_19o42_34"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:rr:"
+              class="_label_19upo_59"
+              for="radix-«rr»"
             >
               Show compact text and messages
             </label>
             <span
-              class="_message_ssths_93 _help-message_ssths_99"
-              id="radix-:rs:"
+              class="_message_19upo_85 _help-message_19upo_91"
+              id="radix-«rs»"
             >
               Modern layout must be selected to use this feature.
             </span>
@@ -446,7 +446,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
       </form>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
diff --git a/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap
index 25d06ffc2341e6bb23588c644d0d561fce098ecc..ae8a4aa46ed9dd318700e0104f40eb494600fab6 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/Notifications-test.tsx.snap
@@ -10,22 +10,22 @@ exports[`<Notifications /> main notification switches renders only enable notifi
       class="mx_SettingsFlag_label"
     >
       <div
-        id="mx_LabelledToggleSwitch_testid_0"
+        id="mx_LabelledToggleSwitch_«r0»"
       >
         Enable notifications for this account
       </div>
       <span
         class="mx_Caption"
-        id="mx_LabelledToggleSwitch_testid_0_caption"
+        id="mx_LabelledToggleSwitch_«r0»_caption"
       >
         Turn off to disable notifications on all your devices and sessions
       </span>
     </span>
     <div
       aria-checked="false"
-      aria-describedby="mx_LabelledToggleSwitch_testid_0_caption"
+      aria-describedby="mx_LabelledToggleSwitch_«r0»_caption"
       aria-disabled="false"
-      aria-labelledby="mx_LabelledToggleSwitch_testid_0"
+      aria-labelledby="mx_LabelledToggleSwitch_«r0»"
       class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
       role="switch"
       tabindex="0"
@@ -41,7 +41,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
     >
       <label
         class="mx_SettingsFlag_label"
-        for="mx_SettingsFlag_testid_1"
+        for="mx_SettingsFlag_testid_0"
       >
         <span
           class="mx_SettingsFlag_labelText"
@@ -54,7 +54,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
         aria-disabled="false"
         aria-label="Show all activity in the room list (dots or number of unread messages)"
         class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-        id="mx_SettingsFlag_testid_1"
+        id="mx_SettingsFlag_testid_0"
         role="switch"
         tabindex="0"
       >
@@ -68,7 +68,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
     >
       <label
         class="mx_SettingsFlag_label"
-        for="mx_SettingsFlag_testid_2"
+        for="mx_SettingsFlag_testid_1"
       >
         <span
           class="mx_SettingsFlag_labelText"
@@ -81,7 +81,7 @@ exports[`<Notifications /> main notification switches renders only enable notifi
         aria-disabled="false"
         aria-label="Only show notifications in the thread activity centre"
         class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-        id="mx_SettingsFlag_testid_2"
+        id="mx_SettingsFlag_testid_1"
         role="switch"
         tabindex="0"
       >
diff --git a/test/unit-tests/components/views/settings/__snapshots__/PowerLevelSelector-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/PowerLevelSelector-test.tsx.snap
index 7ba419a4e24570de8d4a556fd979fad961dca929..812dc08b6a780c4340d84b279fcdf9f726aecb23 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/PowerLevelSelector-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/PowerLevelSelector-test.tsx.snap
@@ -60,7 +60,7 @@ exports[`PowerLevelSelector should display only the current user 1`] = `
     <button
       aria-disabled="true"
       aria-label="Apply"
-      class="_button_i91xf_17 mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
+      class="_button_vczzf_8 mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
       data-kind="primary"
       data-size="sm"
       role="button"
@@ -222,7 +222,7 @@ exports[`PowerLevelSelector should render 1`] = `
     <button
       aria-disabled="true"
       aria-label="Apply"
-      class="_button_i91xf_17 mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
+      class="_button_vczzf_8 mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
       data-kind="primary"
       data-size="sm"
       role="button"
diff --git a/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap
deleted file mode 100644
index 1e920c24841188dd241bfbe3742a30ce15787a89..0000000000000000000000000000000000000000
--- a/test/unit-tests/components/views/settings/__snapshots__/SecureBackupPanel-test.tsx.snap
+++ /dev/null
@@ -1,190 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`<SecureBackupPanel /> handles error fetching backup 1`] = `
-<div>
-  <div
-    class="mx_SettingsSubsection_text"
-  >
-    Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
-  </div>
-  <div
-    class="mx_SettingsSubsection_text"
-  >
-    Unable to load key backup status
-  </div>
-  <details>
-    <summary
-      class="mx_SecureBackupPanel_advanced"
-    >
-      Advanced
-    </summary>
-    <table
-      class="mx_SecureBackupPanel_statusList"
-    >
-      <tr>
-        <th
-          scope="row"
-        >
-          Backup key stored:
-        </th>
-        <td>
-          not stored
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Backup key cached:
-        </th>
-        <td>
-          not found locally
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Secret storage public key:
-        </th>
-        <td>
-          not found
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Secret storage:
-        </th>
-        <td>
-          not ready
-        </td>
-      </tr>
-    </table>
-  </details>
-</div>
-`;
-
-exports[`<SecureBackupPanel /> suggests connecting session to key backup when backup exists 1`] = `
-<div>
-  <div
-    class="mx_SettingsSubsection_text"
-  >
-    Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
-  </div>
-  <div
-    class="mx_SettingsSubsection_text"
-  >
-    <span>
-      This session is 
-      <strong>
-        not backing up your keys
-      </strong>
-      , but you do have an existing backup you can restore from and add to going forward.
-    </span>
-  </div>
-  <div
-    class="mx_SettingsSubsection_text"
-  >
-    Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.
-  </div>
-  <details>
-    <summary
-      class="mx_SecureBackupPanel_advanced"
-    >
-      Advanced
-    </summary>
-    <table
-      class="mx_SecureBackupPanel_statusList"
-    >
-      <tr>
-        <th
-          scope="row"
-        >
-          Backup key stored:
-        </th>
-        <td>
-          not stored
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Backup key cached:
-        </th>
-        <td>
-          not found locally
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Secret storage public key:
-        </th>
-        <td>
-          not found
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Secret storage:
-        </th>
-        <td>
-          not ready
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Latest backup version on server:
-        </th>
-        <td>
-          1
-           (
-          Algorithm:
-           
-          <code>
-            test
-          </code>
-          )
-        </td>
-      </tr>
-      <tr>
-        <th
-          scope="row"
-        >
-          Active backup version:
-        </th>
-        <td>
-          None
-        </td>
-      </tr>
-    </table>
-    <div />
-  </details>
-  <div
-    class="mx_SecureBackupPanel_buttonRow"
-  >
-    <div
-      class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
-      role="button"
-      tabindex="0"
-    >
-      Connect this session to Key Backup
-    </div>
-    <div
-      class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_outline"
-      role="button"
-      tabindex="0"
-    >
-      Delete Backup
-    </div>
-  </div>
-</div>
-`;
diff --git a/test/unit-tests/components/views/settings/__snapshots__/SetIdServer-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SetIdServer-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..3a6dfcd5d6d8850b12abdb463510b88fe06be7e4
--- /dev/null
+++ b/test/unit-tests/components/views/settings/__snapshots__/SetIdServer-test.tsx.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<SetIdServer /> renders expected fields 1`] = `
+<DocumentFragment>
+  <fieldset
+    class="mx_SettingsFieldset"
+  >
+    <legend
+      class="mx_SettingsFieldset_legend"
+    >
+      Identity server
+    </legend>
+    <div
+      class="mx_SettingsFieldset_description"
+    >
+      <div
+        class="mx_SettingsSubsection_text"
+      >
+        You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.
+      </div>
+    </div>
+    <div
+      class="mx_SettingsFieldset_content"
+    >
+      <form
+        class="_root_19upo_16 mx_IdentityServerPicker"
+      >
+        <div
+          class="_field_19upo_26"
+        >
+          <label
+            class="_label_19upo_59"
+            for="radix-«r0»"
+          >
+            Enter a new identity server
+          </label>
+          <div
+            class="_controls_17lij_8"
+          >
+            <input
+              class="_control_sqdq4_10"
+              id="radix-«r0»"
+              name="input"
+              placeholder=""
+              title=""
+              value=""
+            />
+          </div>
+        </div>
+      </form>
+    </div>
+  </fieldset>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap
index 9dab61f8b23d21853fcd026e5419092d8890c95e..025afc3c97c3d48152b23b9d39afc1e92150c640 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap
@@ -1,10 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`SetIntegrationManager should render manage integrations sections 1`] = `
-<label
+<div
   class="mx_SetIntegrationManager"
   data-testid="mx_SetIntegrationManager"
-  for="toggle_integration"
 >
   <div
     class="mx_SettingsFlag"
@@ -23,18 +22,6 @@ exports[`SetIntegrationManager should render manage integrations sections 1`] =
         (scalar.vector.im)
       </h4>
     </div>
-    <div
-      aria-checked="false"
-      aria-disabled="false"
-      class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
-      id="toggle_integration"
-      role="switch"
-      tabindex="0"
-    >
-      <div
-        class="mx_ToggleSwitch_ball"
-      />
-    </div>
   </div>
   <div
     class="mx_SettingsSubsection_text"
@@ -52,5 +39,40 @@ exports[`SetIntegrationManager should render manage integrations sections 1`] =
   >
     Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
   </div>
-</label>
+  <form
+    class="_root_19upo_16"
+  >
+    <div
+      class="_inline-field_19upo_32"
+    >
+      <div
+        class="_inline-field-control_19upo_44"
+      >
+        <div
+          class="_container_19o42_10"
+        >
+          <input
+            class="_input_19o42_24"
+            id="mx_SetIntegrationManager_Toggle"
+            role="switch"
+            type="checkbox"
+          />
+          <div
+            class="_ui_19o42_34"
+          />
+        </div>
+      </div>
+      <div
+        class="_inline-field-body_19upo_38"
+      >
+        <label
+          class="_label_19upo_59"
+          for="mx_SetIntegrationManager_Toggle"
+        >
+          Enable the integration manager
+        </label>
+      </div>
+    </div>
+  </form>
+</div>
 `;
diff --git a/test/unit-tests/components/views/settings/__snapshots__/SettingsHeader-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SettingsHeader-test.tsx.snap
index 4098a55ed415bf7c1f1c3403a6e32ae36f13b515..676233259f0cf52de614c359c2528e956b44682d 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/SettingsHeader-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/SettingsHeader-test.tsx.snap
@@ -3,7 +3,7 @@
 exports[`<SettingsHeader /> should render the component 1`] = `
 <DocumentFragment>
   <h2
-    class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+    class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
   >
     Settings Header 
   </h2>
@@ -13,7 +13,7 @@ exports[`<SettingsHeader /> should render the component 1`] = `
 exports[`<SettingsHeader /> should render the component with the recommended tag 1`] = `
 <DocumentFragment>
   <h2
-    class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+    class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
   >
     Settings Header 
     <span>
diff --git a/test/unit-tests/components/views/settings/__snapshots__/SettingsSubheader-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/SettingsSubheader-test.tsx.snap
index 23c9ce087d7cb37b6c326ac287829375e3bc33f7..f6539735b3f936a3c13543d1286fc480b088249d 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/SettingsSubheader-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/SettingsSubheader-test.tsx.snap
@@ -16,7 +16,7 @@ exports[`<SettingsSubheader /> should display a check icon when in success 1`] =
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="m10.6 13.8-2.15-2.15a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7L9.9 15.9c.2.2.433.3.7.3.267 0 .5-.1.7-.3l5.65-5.65a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275L10.6 13.8ZM12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+          d="m10.6 13.8-2.15-2.15a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275zM12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
         />
       </svg>
       Success!
@@ -42,7 +42,7 @@ exports[`<SettingsSubheader /> should display a label 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="m10.6 13.8-2.15-2.15a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7L9.9 15.9c.2.2.433.3.7.3.267 0 .5-.1.7-.3l5.65-5.65a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275L10.6 13.8ZM12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+          d="m10.6 13.8-2.15-2.15a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275zM12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
         />
       </svg>
       Success!
@@ -67,7 +67,7 @@ exports[`<SettingsSubheader /> should display an error icon when in error 1`] =
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+          d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
         />
       </svg>
       Error!
diff --git a/test/unit-tests/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap b/test/unit-tests/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap
index 6cfb0c7f29657af3e99745128b00ae166d33a7ac..64b9312ae8f71abb5f6f45459e31caab36d552fa 100644
--- a/test/unit-tests/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap
@@ -19,35 +19,35 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
       class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
     >
       <form
-        class="_root_ssths_24"
+        class="_root_19upo_16"
       >
         <div
-          class="_inline-field_ssths_40"
+          class="_inline-field_19upo_32"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_qnvru_18"
+              class="_container_19o42_10"
             >
               <input
-                class="_input_qnvru_32"
-                id="radix-:r28:"
+                class="_input_19o42_24"
+                id="radix-«r28»"
                 name="systemTheme"
                 title=""
                 type="checkbox"
               />
               <div
-                class="_ui_qnvru_42"
+                class="_ui_19o42_34"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:r28:"
+              class="_label_19upo_59"
+              for="radix-«r28»"
             >
               Match system theme
             </label>
@@ -55,136 +55,136 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
         </div>
       </form>
       <form
-        class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
+        class="_root_19upo_16 mx_ThemeChoicePanel_ThemeSelectors"
       >
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
                 checked=""
-                class="_input_1vw5h_26"
-                id="radix-:r29:"
+                class="_input_1e0uz_18"
+                id="radix-«r29»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r29:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r29»"
             >
               Light
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r2a:"
+                class="_input_1e0uz_18"
+                id="radix-«r2a»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="dark"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r2a:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r2a»"
             >
               Dark
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r2b:"
+                class="_input_1e0uz_18"
+                id="radix-«r2b»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light-high-contrast"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r2b:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r2b»"
             >
               High contrast
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r2c:"
+                class="_input_1e0uz_18"
+                id="radix-«r2c»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="custom-Alice theme"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r2c:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r2c»"
             >
               Alice theme
             </label>
@@ -195,32 +195,32 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
         class="mx_ThemeChoicePanel_CustomTheme"
       >
         <form
-          class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
+          class="_root_19upo_16 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
         >
           <div
-            class="_field_ssths_34"
+            class="_field_19upo_26"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:r2d:"
+              class="_label_19upo_59"
+              for="radix-«r2d»"
             >
               Add custom theme
             </label>
             <div
-              class="_controls_1h4nb_17"
+              class="_controls_17lij_8"
             >
               <input
-                aria-describedby="radix-:r2e:"
-                class="_control_9gon8_18"
-                id="radix-:r2d:"
+                aria-describedby="radix-«r2e»"
+                class="_control_sqdq4_10"
+                id="radix-«r2d»"
                 name="input"
                 title=""
                 value=""
               />
             </div>
             <span
-              class="_message_ssths_93 _help-message_ssths_99"
-              id="radix-:r2e:"
+              class="_message_19upo_85 _help-message_19upo_91"
+              id="radix-«r2e»"
             >
               Enter the URL of a custom theme you want to apply.
             </span>
@@ -240,14 +240,14 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
             </span>
             <button
               aria-label="Delete"
-              aria-labelledby=":r2f:"
-              class="_icon-button_bh2qc_17 _destructive_bh2qc_83"
+              aria-labelledby="«r2f»"
+              class="_icon-button_m2erp_8 _destructive_m2erp_74"
               role="button"
               style="--cpd-icon-button-size: 32px;"
               tabindex="0"
             >
               <div
-                class="_indicator-icon_133tf_26"
+                class="_indicator-icon_zr2a0_17"
                 style="--cpd-icon-button-size: 100%;"
               >
                 <svg
@@ -258,7 +258,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
+                    d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
                   />
                 </svg>
               </div>
@@ -268,7 +268,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
       </div>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
@@ -296,35 +296,35 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
       class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
     >
       <form
-        class="_root_ssths_24"
+        class="_root_19upo_16"
       >
         <div
-          class="_inline-field_ssths_40"
+          class="_inline-field_19upo_32"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_qnvru_18"
+              class="_container_19o42_10"
             >
               <input
-                class="_input_qnvru_32"
-                id="radix-:r10:"
+                class="_input_19o42_24"
+                id="radix-«r10»"
                 name="systemTheme"
                 title=""
                 type="checkbox"
               />
               <div
-                class="_ui_qnvru_42"
+                class="_ui_19o42_34"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:r10:"
+              class="_label_19upo_59"
+              for="radix-«r10»"
             >
               Match system theme
             </label>
@@ -332,136 +332,136 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
         </div>
       </form>
       <form
-        class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
+        class="_root_19upo_16 mx_ThemeChoicePanel_ThemeSelectors"
       >
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
                 checked=""
-                class="_input_1vw5h_26"
-                id="radix-:r11:"
+                class="_input_1e0uz_18"
+                id="radix-«r11»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r11:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r11»"
             >
               Light
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r12:"
+                class="_input_1e0uz_18"
+                id="radix-«r12»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="dark"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r12:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r12»"
             >
               Dark
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r13:"
+                class="_input_1e0uz_18"
+                id="radix-«r13»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light-high-contrast"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r13:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r13»"
             >
               High contrast
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r14:"
+                class="_input_1e0uz_18"
+                id="radix-«r14»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="custom-Alice theme"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r14:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r14»"
             >
               Alice theme
             </label>
@@ -472,32 +472,32 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
         class="mx_ThemeChoicePanel_CustomTheme"
       >
         <form
-          class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
+          class="_root_19upo_16 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
         >
           <div
-            class="_field_ssths_34"
+            class="_field_19upo_26"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:r15:"
+              class="_label_19upo_59"
+              for="radix-«r15»"
             >
               Add custom theme
             </label>
             <div
-              class="_controls_1h4nb_17"
+              class="_controls_17lij_8"
             >
               <input
-                aria-describedby="radix-:r16:"
-                class="_control_9gon8_18"
-                id="radix-:r15:"
+                aria-describedby="radix-«r16»"
+                class="_control_sqdq4_10"
+                id="radix-«r15»"
                 name="input"
                 title=""
                 value=""
               />
             </div>
             <span
-              class="_message_ssths_93 _help-message_ssths_99"
-              id="radix-:r16:"
+              class="_message_19upo_85 _help-message_19upo_91"
+              id="radix-«r16»"
             >
               Enter the URL of a custom theme you want to apply.
             </span>
@@ -517,14 +517,14 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
             </span>
             <button
               aria-label="Delete"
-              aria-labelledby=":r17:"
-              class="_icon-button_bh2qc_17 _destructive_bh2qc_83"
+              aria-labelledby="«r17»"
+              class="_icon-button_m2erp_8 _destructive_m2erp_74"
               role="button"
               style="--cpd-icon-button-size: 32px;"
               tabindex="0"
             >
               <div
-                class="_indicator-icon_133tf_26"
+                class="_indicator-icon_zr2a0_17"
                 style="--cpd-icon-button-size: 100%;"
               >
                 <svg
@@ -535,7 +535,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
                   xmlns="http://www.w3.org/2000/svg"
                 >
                   <path
-                    d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
+                    d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
                   />
                 </svg>
               </div>
@@ -545,7 +545,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
       </div>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
@@ -573,35 +573,35 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
       class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
     >
       <form
-        class="_root_ssths_24"
+        class="_root_19upo_16"
       >
         <div
-          class="_inline-field_ssths_40"
+          class="_inline-field_19upo_32"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_qnvru_18"
+              class="_container_19o42_10"
             >
               <input
-                class="_input_qnvru_32"
-                id="radix-:r0:"
+                class="_input_19o42_24"
+                id="radix-«r0»"
                 name="systemTheme"
                 title=""
                 type="checkbox"
               />
               <div
-                class="_ui_qnvru_42"
+                class="_ui_19o42_34"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67"
-              for="radix-:r0:"
+              class="_label_19upo_59"
+              for="radix-«r0»"
             >
               Match system theme
             </label>
@@ -609,103 +609,103 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
         </div>
       </form>
       <form
-        class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
+        class="_root_19upo_16 mx_ThemeChoicePanel_ThemeSelectors"
       >
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
                 checked=""
-                class="_input_1vw5h_26"
-                id="radix-:r1:"
+                class="_input_1e0uz_18"
+                id="radix-«r1»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r1:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r1»"
             >
               Light
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r2:"
+                class="_input_1e0uz_18"
+                id="radix-«r2»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="dark"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r2:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r2»"
             >
               Dark
             </label>
           </div>
         </div>
         <div
-          class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
+          class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
         >
           <div
-            class="_inline-field-control_ssths_52"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="_container_1vw5h_18"
+              class="_container_1e0uz_10"
             >
               <input
-                class="_input_1vw5h_26"
-                id="radix-:r3:"
+                class="_input_1e0uz_18"
+                id="radix-«r3»"
                 name="themeSelector"
                 title=""
                 type="radio"
                 value="light-high-contrast"
               />
               <div
-                class="_ui_1vw5h_27"
+                class="_ui_1e0uz_19"
               />
             </div>
           </div>
           <div
-            class="_inline-field-body_ssths_46"
+            class="_inline-field-body_19upo_38"
           >
             <label
-              class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-              for="radix-:r3:"
+              class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+              for="radix-«r3»"
             >
               High contrast
             </label>
@@ -714,7 +714,7 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
       </form>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
diff --git a/test/unit-tests/components/views/settings/devices/DeviceDetailHeading-test.tsx b/test/unit-tests/components/views/settings/devices/DeviceDetailHeading-test.tsx
index d963acf76b1cb18a9cea4966f8b315a936064816..807a91843d308abd6a0901db46da39544bf82a30 100644
--- a/test/unit-tests/components/views/settings/devices/DeviceDetailHeading-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/DeviceDetailHeading-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult } from "jest-matrix-react";
 
 import { DeviceDetailHeading } from "../../../../../../src/components/views/settings/devices/DeviceDetailHeading";
 import { flushPromisesWithFakeTimers } from "../../../../../test-utils";
diff --git a/test/unit-tests/components/views/settings/devices/DeviceDetails-test.tsx b/test/unit-tests/components/views/settings/devices/DeviceDetails-test.tsx
index 765621eee5f43c4605c08e8d5b6311a70d176357..58179edae89ce6a9eff741a9aebf29a57d1493e4 100644
--- a/test/unit-tests/components/views/settings/devices/DeviceDetails-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/DeviceDetails-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { fireEvent, render } from "jest-matrix-react";
 import { PUSHER_ENABLED } from "matrix-js-sdk/src/matrix";
 
diff --git a/test/unit-tests/components/views/settings/devices/DeviceTile-test.tsx b/test/unit-tests/components/views/settings/devices/DeviceTile-test.tsx
index a868b45f275c2af47b9ba9ce35fa6ef8205314cf..a87561d872aec730891ba7584d2a9820fdecb2f8 100644
--- a/test/unit-tests/components/views/settings/devices/DeviceTile-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/DeviceTile-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render } from "jest-matrix-react";
-import { IMyDevice } from "matrix-js-sdk/src/matrix";
+import { type IMyDevice } from "matrix-js-sdk/src/matrix";
 
 import DeviceTile from "../../../../../../src/components/views/settings/devices/DeviceTile";
 import { DeviceType } from "../../../../../../src/utils/device/parseUserAgent";
diff --git a/test/unit-tests/components/views/settings/devices/DeviceVerificationStatusCard-test.tsx b/test/unit-tests/components/views/settings/devices/DeviceVerificationStatusCard-test.tsx
index 8ffb9889e67868824626df1894cf7f6a3a1c8ad9..4059c9a6a4094c44fc3e37f26b5a18685d5a157d 100644
--- a/test/unit-tests/components/views/settings/devices/DeviceVerificationStatusCard-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/DeviceVerificationStatusCard-test.tsx
@@ -11,9 +11,9 @@ import React from "react";
 
 import {
     DeviceVerificationStatusCard,
-    DeviceVerificationStatusCardProps,
+    type DeviceVerificationStatusCardProps,
 } from "../../../../../../src/components/views/settings/devices/DeviceVerificationStatusCard";
-import { ExtendedDevice } from "../../../../../../src/components/views/settings/devices/types";
+import { type ExtendedDevice } from "../../../../../../src/components/views/settings/devices/types";
 import { DeviceType } from "../../../../../../src/utils/device/parseUserAgent";
 
 describe("<DeviceVerificationStatusCard />", () => {
diff --git a/test/unit-tests/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/unit-tests/components/views/settings/devices/FilteredDeviceList-test.tsx
index 3318a13e6ad0433b86388d364e33eb75adce718d..2992b95a5dbbbfae8cd5772fddca1ccf86c0da7a 100644
--- a/test/unit-tests/components/views/settings/devices/FilteredDeviceList-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/FilteredDeviceList-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { act, fireEvent, render } from "jest-matrix-react";
 
 import { FilteredDeviceList } from "../../../../../../src/components/views/settings/devices/FilteredDeviceList";
diff --git a/test/unit-tests/components/views/settings/devices/LoginWithQR-test.tsx b/test/unit-tests/components/views/settings/devices/LoginWithQR-test.tsx
index 7b50ae02787af9915eb0e210710a8339591503e6..d9e9b3e391ffed68da49d308b2df40968103ed09 100644
--- a/test/unit-tests/components/views/settings/devices/LoginWithQR-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/LoginWithQR-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { cleanup, render, waitFor } from "jest-matrix-react";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import React from "react";
 import {
     ClientRendezvousFailureReason,
@@ -15,7 +15,7 @@ import {
     MSC4108SignInWithQR,
     RendezvousError,
 } from "matrix-js-sdk/src/rendezvous";
-import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { HTTPError, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import LoginWithQR from "../../../../../../src/components/views/auth/LoginWithQR";
 import { Click, Mode, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
diff --git a/test/unit-tests/components/views/settings/devices/LoginWithQRFlow-test.tsx b/test/unit-tests/components/views/settings/devices/LoginWithQRFlow-test.tsx
index 9120ff4f66e99e99e8b13fed5504d8b03ec431f7..9d9c5763bece063a14f707cbff4fb9bad9bf63f2 100644
--- a/test/unit-tests/components/views/settings/devices/LoginWithQRFlow-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/LoginWithQRFlow-test.tsx
@@ -11,7 +11,7 @@ import React from "react";
 import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
 
 import LoginWithQRFlow from "../../../../../../src/components/views/auth/LoginWithQRFlow";
-import { LoginWithQRFailureReason, FailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
+import { LoginWithQRFailureReason, type FailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
 import { Click, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
 
 describe("<LoginWithQRFlow />", () => {
diff --git a/test/unit-tests/components/views/settings/devices/LoginWithQRSection-test.tsx b/test/unit-tests/components/views/settings/devices/LoginWithQRSection-test.tsx
index ebbc35988949a15a16c8fbb8cdbc008592bb1cd8..c9511fb188172545c179f56a5bca9cd9cdb50a8a 100644
--- a/test/unit-tests/components/views/settings/devices/LoginWithQRSection-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/LoginWithQRSection-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { render } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { IClientWellKnown, IServerVersions, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IClientWellKnown, type IServerVersions, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import React from "react";
 import fetchMock from "fetch-mock-jest";
 
diff --git a/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
index a3465ab58824d60cd74630c01dd366d6b754790d..a6f1e06c29313a69de4ff1308c5c6b0bea5e0428 100644
--- a/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
@@ -155,7 +155,7 @@ exports[`<CurrentDeviceSection /> handles when device is falsy 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+            d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
           />
         </svg>
       </div>
@@ -199,7 +199,7 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+            d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
           />
         </svg>
       </div>
@@ -272,7 +272,7 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+                d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
               />
             </svg>
           </div>
@@ -361,7 +361,7 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+            d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
           />
         </svg>
       </div>
@@ -434,7 +434,7 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
               xmlns="http://www.w3.org/2000/svg"
             >
               <path
-                d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+                d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
               />
             </svg>
           </div>
diff --git a/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap b/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap
index 5032bd2a643de0ff56012430ab949b2af11ac79d..2c2e0a05b0844f21d75fc2dcafb7c72354e562fa 100644
--- a/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/devices/__snapshots__/DeviceExpandDetailsButton-test.tsx.snap
@@ -18,7 +18,7 @@ exports[`<DeviceExpandDetailsButton /> renders when expanded 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+          d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
         />
       </svg>
     </div>
@@ -44,7 +44,7 @@ exports[`<DeviceExpandDetailsButton /> renders when not expanded 1`] = `
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+          d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
         />
       </svg>
     </div>
diff --git a/test/unit-tests/components/views/settings/devices/__snapshots__/FilteredDeviceListHeader-test.tsx.snap b/test/unit-tests/components/views/settings/devices/__snapshots__/FilteredDeviceListHeader-test.tsx.snap
index d92286d23a3420a66e9ff92331a7903575902b7e..5d99112dd5f20674aac847d7883020b95fc0f6c0 100644
--- a/test/unit-tests/components/views/settings/devices/__snapshots__/FilteredDeviceListHeader-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/devices/__snapshots__/FilteredDeviceListHeader-test.tsx.snap
@@ -9,29 +9,50 @@ exports[`<FilteredDeviceListHeader /> renders correctly when all devices are sel
     <span
       tabindex="0"
     >
-      <span
-        class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+      <form
+        class="_root_19upo_16"
       >
-        <input
-          aria-label="Deselect all"
-          aria-labelledby=":r6:"
-          checked=""
-          data-testid="device-select-all-checkbox"
-          id="device-select-all-checkbox"
-          type="checkbox"
-        />
-        <label
-          for="device-select-all-checkbox"
+        <div
+          class="_inline-field_19upo_32"
         >
           <div
-            class="mx_Checkbox_background"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="mx_Checkbox_checkmark"
-            />
+              class="_container_1hel1_10"
+            >
+              <input
+                aria-label="Deselect all"
+                aria-labelledby="«r9»"
+                checked=""
+                class="_input_1hel1_18"
+                data-testid="device-select-all-checkbox"
+                id="device-select-all-checkbox"
+                type="checkbox"
+              />
+              <div
+                class="_ui_1hel1_19"
+              >
+                <svg
+                  aria-hidden="true"
+                  fill="currentColor"
+                  height="1em"
+                  viewBox="0 0 24 24"
+                  width="1em"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                  />
+                </svg>
+              </div>
+            </div>
           </div>
-        </label>
-      </span>
+          <div
+            class="_inline-field-body_19upo_38"
+          />
+        </div>
+      </form>
     </span>
     <span
       class="mx_FilteredDeviceListHeader_label"
@@ -54,28 +75,49 @@ exports[`<FilteredDeviceListHeader /> renders correctly when no devices are sele
     <span
       tabindex="0"
     >
-      <span
-        class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+      <form
+        class="_root_19upo_16"
       >
-        <input
-          aria-label="Select all"
-          aria-labelledby=":r0:"
-          data-testid="device-select-all-checkbox"
-          id="device-select-all-checkbox"
-          type="checkbox"
-        />
-        <label
-          for="device-select-all-checkbox"
+        <div
+          class="_inline-field_19upo_32"
         >
           <div
-            class="mx_Checkbox_background"
+            class="_inline-field-control_19upo_44"
           >
             <div
-              class="mx_Checkbox_checkmark"
-            />
+              class="_container_1hel1_10"
+            >
+              <input
+                aria-label="Select all"
+                aria-labelledby="«r0»"
+                class="_input_1hel1_18"
+                data-testid="device-select-all-checkbox"
+                id="device-select-all-checkbox"
+                type="checkbox"
+              />
+              <div
+                class="_ui_1hel1_19"
+              >
+                <svg
+                  aria-hidden="true"
+                  fill="currentColor"
+                  height="1em"
+                  viewBox="0 0 24 24"
+                  width="1em"
+                  xmlns="http://www.w3.org/2000/svg"
+                >
+                  <path
+                    d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                  />
+                </svg>
+              </div>
+            </div>
           </div>
-        </label>
-      </span>
+          <div
+            class="_inline-field-body_19upo_38"
+          />
+        </div>
+      </form>
     </span>
     <span
       class="mx_FilteredDeviceListHeader_label"
diff --git a/test/unit-tests/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap b/test/unit-tests/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap
index 280ec75b042a77b90a6d8acc62d8dce6179976aa..8df6a41d906c771e20c06033edefe87f6558371e 100644
--- a/test/unit-tests/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/devices/__snapshots__/LoginWithQRFlow-test.tsx.snap
@@ -20,12 +20,12 @@ exports[`<LoginWithQRFlow /> errors renders authorization_expired 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         The sign in was not completed in time
       </h1>
@@ -62,12 +62,12 @@ exports[`<LoginWithQRFlow /> errors renders check_code_mismatch 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -104,12 +104,12 @@ exports[`<LoginWithQRFlow /> errors renders device_already_exists 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -146,12 +146,12 @@ exports[`<LoginWithQRFlow /> errors renders device_not_found 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -188,12 +188,12 @@ exports[`<LoginWithQRFlow /> errors renders etag_missing 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -230,12 +230,12 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         The sign in was not completed in time
       </h1>
@@ -276,7 +276,7 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
@@ -303,19 +303,19 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
         >
           <path
             clip-rule="evenodd"
-            d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 5V5h4v4H5Zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 5v-4h4v4H5Zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-6Zm1 2v4h4V5h-4Z"
+            d="M3 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm2 5V5h4v4zm-2 5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm2 5v-4h4v4zm9-16a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm1 2v4h4V5z"
             fill-rule="evenodd"
           />
           <path
-            d="M15 16v-3h-2v3h2Z"
+            d="M15 16v-3h-2v3z"
           />
           <path
-            d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2v3Z"
+            d="M17 16h-2v2h-2v3h2v-3h2v2h4v-2h-2v-5h-2z"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         QR code not supported
       </h1>
@@ -352,18 +352,18 @@ exports[`<LoginWithQRFlow /> errors renders insecure_channel_detected 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Connection not secure
       </h1>
       A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.
       <h2
-        class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+        class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
         data-testid="cancellation-message"
       >
         Now what?
@@ -407,12 +407,12 @@ exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -449,12 +449,12 @@ exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] =
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m10.6 13.8-2.15-2.15a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7.95.95 0 0 0 .275.7L9.9 15.9c.2.2.433.3.7.3.267 0 .5-.1.7-.3l5.65-5.65a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275L10.6 13.8ZM12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="m10.6 13.8-2.15-2.15a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275zM12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Your other device is already signed in
       </h1>
@@ -491,12 +491,12 @@ exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -533,12 +533,12 @@ exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -575,12 +575,12 @@ exports[`<LoginWithQRFlow /> errors renders unexpected_message_received 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -617,12 +617,12 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Something went wrong!
       </h1>
@@ -659,12 +659,12 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_protocol 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Other device not compatible
       </h1>
@@ -701,12 +701,12 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Sign in request cancelled
       </h1>
@@ -743,12 +743,12 @@ exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
           />
         </svg>
       </div>
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Sign in declined
       </h1>
@@ -789,7 +789,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
@@ -805,7 +805,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
       class="mx_LoginWithQR_main"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Scan the QR code with another device
       </h1>
@@ -860,12 +860,12 @@ exports[`<LoginWithQRFlow /> renders check code confirmation 1`] = `
       class="mx_LoginWithQR_main"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Enter the number shown on your other device
       </h1>
       <p
-        class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
+        class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
       >
         This will verify that the connection to your other device is secure.
       </p>
@@ -875,11 +875,11 @@ exports[`<LoginWithQRFlow /> renders check code confirmation 1`] = `
         2-digit code
       </label>
       <div
-        class="_container_9zyti_18 mx_LoginWithQR_checkCode_input mx_no_textinput"
+        class="_container_43om7_10 mx_LoginWithQR_checkCode_input mx_no_textinput"
       >
         <input
           autocomplete="one-time-code"
-          class="_control_9zyti_33"
+          class="_control_43om7_25"
           id="mx_LoginWithQR_checkCode"
           inputmode="numeric"
           maxlength="2"
@@ -889,11 +889,11 @@ exports[`<LoginWithQRFlow /> renders check code confirmation 1`] = `
         />
         <div
           aria-hidden="true"
-          class="_digit_9zyti_57"
+          class="_digit_43om7_49"
         />
         <div
           aria-hidden="true"
-          class="_digit_9zyti_57"
+          class="_digit_43om7_49"
         />
       </div>
       <div
@@ -948,7 +948,7 @@ exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
@@ -1012,7 +1012,7 @@ exports[`<LoginWithQRFlow /> renders spinner while signing in 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
@@ -1088,7 +1088,7 @@ exports[`<LoginWithQRFlow /> renders spinner while verifying 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
@@ -1155,7 +1155,7 @@ exports[`<LoginWithQRFlow /> renders spinner whilst QR generating 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
diff --git a/test/unit-tests/components/views/settings/devices/__snapshots__/SelectableDeviceTile-test.tsx.snap b/test/unit-tests/components/views/settings/devices/__snapshots__/SelectableDeviceTile-test.tsx.snap
index 37e93804d5469a6b3309b14acef9482c97288179..d7d96b2d54fda0c14883970b601aeb4532cdc342 100644
--- a/test/unit-tests/components/views/settings/devices/__snapshots__/SelectableDeviceTile-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/devices/__snapshots__/SelectableDeviceTile-test.tsx.snap
@@ -3,6 +3,7 @@
 exports[`<SelectableDeviceTile /> renders selected tile 1`] = `
 <input
   checked=""
+  class="_input_1hel1_18"
   data-testid="device-tile-checkbox-my-device"
   id="device-tile-checkbox-my-device"
   type="checkbox"
@@ -14,88 +15,113 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
   <div
     class="mx_SelectableDeviceTile"
   >
-    <span
-      class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+    <form
+      class="_root_19upo_16"
     >
-      <input
-        data-testid="device-tile-checkbox-my-device"
-        id="device-tile-checkbox-my-device"
-        type="checkbox"
-      />
-      <label
-        for="device-tile-checkbox-my-device"
+      <div
+        class="_inline-field_19upo_32 mx_SelectableDeviceTile_checkbox"
       >
         <div
-          class="mx_Checkbox_background"
+          class="_inline-field-control_19upo_44"
         >
           <div
-            class="mx_Checkbox_checkmark"
-          />
+            class="_container_1hel1_10"
+          >
+            <input
+              class="_input_1hel1_18"
+              data-testid="device-tile-checkbox-my-device"
+              id="device-tile-checkbox-my-device"
+              type="checkbox"
+            />
+            <div
+              class="_ui_1hel1_19"
+            >
+              <svg
+                aria-hidden="true"
+                fill="currentColor"
+                height="1em"
+                viewBox="0 0 24 24"
+                width="1em"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+            </div>
+          </div>
         </div>
-        <div>
-          <div
-            class="mx_DeviceTile mx_DeviceTile_interactive"
-            data-testid="device-tile-my-device"
+        <div
+          class="_inline-field-body_19upo_38"
+        >
+          <label
+            class="_label_19upo_59"
+            for="device-tile-checkbox-my-device"
           >
             <div
-              class="mx_DeviceTypeIcon"
+              class="mx_DeviceTile mx_DeviceTile_interactive"
+              data-testid="device-tile-my-device"
             >
               <div
-                class="mx_DeviceTypeIcon_deviceIconWrapper"
+                class="mx_DeviceTypeIcon"
               >
                 <div
-                  aria-label="Unknown session type"
-                  class="mx_DeviceTypeIcon_deviceIcon"
+                  class="mx_DeviceTypeIcon_deviceIconWrapper"
+                >
+                  <div
+                    aria-label="Unknown session type"
+                    class="mx_DeviceTypeIcon_deviceIcon"
+                    role="img"
+                  />
+                </div>
+                <div
+                  aria-label="Unverified"
+                  class="mx_DeviceTypeIcon_verificationIcon unverified"
                   role="img"
                 />
               </div>
               <div
-                aria-label="Unverified"
-                class="mx_DeviceTypeIcon_verificationIcon unverified"
-                role="img"
-              />
-            </div>
-            <div
-              class="mx_DeviceTile_info"
-            >
-              <h4
-                class="mx_Heading_h4"
-              >
-                My Device
-              </h4>
-              <div
-                class="mx_DeviceTile_metadata"
+                class="mx_DeviceTile_info"
               >
-                <span
-                  data-testid="device-metadata-isVerified"
-                >
-                  Unverified
-                </span>
-                 · 
-                <span
-                  data-testid="device-metadata-lastSeenIp"
+                <h4
+                  class="mx_Heading_h4"
                 >
-                  123.456.789
-                </span>
-                 · 
-                <span
-                  data-testid="device-metadata-deviceId"
+                  My Device
+                </h4>
+                <div
+                  class="mx_DeviceTile_metadata"
                 >
-                  my-device
-                </span>
+                  <span
+                    data-testid="device-metadata-isVerified"
+                  >
+                    Unverified
+                  </span>
+                   · 
+                  <span
+                    data-testid="device-metadata-lastSeenIp"
+                  >
+                    123.456.789
+                  </span>
+                   · 
+                  <span
+                    data-testid="device-metadata-deviceId"
+                  >
+                    my-device
+                  </span>
+                </div>
               </div>
-            </div>
-            <div
-              class="mx_DeviceTile_actions"
-            >
-              <div>
-                test
+              <div
+                class="mx_DeviceTile_actions"
+              >
+                <div>
+                  test
+                </div>
               </div>
             </div>
-          </div>
+          </label>
         </div>
-      </label>
-    </span>
+      </div>
+    </form>
   </div>
 </div>
 `;
diff --git a/test/unit-tests/components/views/settings/devices/deleteDevices-test.tsx b/test/unit-tests/components/views/settings/devices/deleteDevices-test.tsx
index 9aeaa5afcd5dbd189e22d35bf48b32a78a12f88e..603d06d4a629293e36faf7e97b3280c67eb7d1ef 100644
--- a/test/unit-tests/components/views/settings/devices/deleteDevices-test.tsx
+++ b/test/unit-tests/components/views/settings/devices/deleteDevices-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixError, UIAFlow } from "matrix-js-sdk/src/matrix";
+import { MatrixError, type UIAFlow } from "matrix-js-sdk/src/matrix";
 
 import { deleteDevicesWithInteractiveAuth } from "../../../../../../src/components/views/settings/devices/deleteDevices";
 import Modal from "../../../../../../src/Modal";
@@ -35,7 +35,7 @@ describe("deleteDevices()", () => {
         await deleteDevicesWithInteractiveAuth(mockClient, deviceIds, onFinished);
 
         expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(deviceIds, undefined);
-        expect(onFinished).toHaveBeenCalledWith(true, undefined);
+        expect(onFinished).toHaveBeenCalledWith(true);
 
         // didnt open modal
         expect(modalSpy).not.toHaveBeenCalled();
diff --git a/test/unit-tests/components/views/settings/discovery/DiscoverySettings-test.tsx b/test/unit-tests/components/views/settings/discovery/DiscoverySettings-test.tsx
index 404735f63aafcbe1a2002197023a503d7c8c364c..999029d7c75d4d98892687a4b56835eb032a79be 100644
--- a/test/unit-tests/components/views/settings/discovery/DiscoverySettings-test.tsx
+++ b/test/unit-tests/components/views/settings/discovery/DiscoverySettings-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { act, render, screen } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MatrixEvent, type Terms, ThreepidMedium } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 import userEvent from "@testing-library/user-event";
 
@@ -26,6 +26,18 @@ jest.mock("../../../../../../src/IdentityAuthClient", () =>
     })),
 );
 
+const sampleTerms = {
+    policies: {
+        terms: { version: "alpha", en: { name: "No ball games", url: "https://foobar" } },
+    },
+} satisfies Terms;
+
+const invalidTerms = {
+    policies: {
+        terms: { version: "invalid" },
+    },
+} satisfies Terms;
+
 describe("DiscoverySettings", () => {
     let client: MatrixClient;
 
@@ -51,20 +63,17 @@ describe("DiscoverySettings", () => {
 
     it("displays alert if an identity server needs terms accepting", async () => {
         mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
-        mocked(client).getTerms.mockResolvedValue({
-            ["policies"]: { en: "No ball games" },
-        });
+        mocked(client).getTerms.mockResolvedValue(sampleTerms);
 
         render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
 
-        await expect(await screen.findByText("Let people find you")).toBeInTheDocument();
+        expect(await screen.findByText("Let people find you")).toBeInTheDocument();
+        expect(screen.getByRole("link")).toHaveAttribute("href", "https://foobar");
     });
 
     it("button to accept terms is disabled if checkbox not checked", async () => {
         mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
-        mocked(client).getTerms.mockResolvedValue({
-            ["policies"]: { en: "No ball games" },
-        });
+        mocked(client).getTerms.mockResolvedValue(sampleTerms);
 
         render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
 
@@ -93,4 +102,40 @@ describe("DiscoverySettings", () => {
 
         expect(client.getThreePids).toHaveBeenCalled();
     });
+
+    it("should not disable share button if terms accepted", async () => {
+        mocked(client).getThreePids.mockResolvedValue({
+            threepids: [
+                {
+                    medium: ThreepidMedium.Email,
+                    address: "test@email.com",
+                    bound: false,
+                    added_at: 123,
+                    validated_at: 234,
+                },
+            ],
+        });
+        mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
+        mocked(client).getTerms.mockResolvedValue(sampleTerms);
+        mocked(client).getAccountData.mockReturnValue(
+            new MatrixEvent({
+                content: { accepted: [sampleTerms.policies["terms"]["en"].url] },
+            }),
+        );
+
+        render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
+
+        const shareButton = await screen.findByRole("button", { name: "Share" });
+        expect(shareButton).not.toHaveAttribute("aria-disabled", "true");
+    });
+
+    it("should not show invalid terms", async () => {
+        mocked(client).getIdentityServerUrl.mockReturnValue("https://example.com");
+        mocked(client).getTerms.mockResolvedValue(invalidTerms);
+
+        render(<DiscoverySettings />, { wrapper: DiscoveryWrapper });
+
+        expect(await screen.findByText("Let people find you")).toBeInTheDocument();
+        expect(screen.queryByRole("link")).not.toBeInTheDocument();
+    });
 });
diff --git a/test/unit-tests/components/views/settings/encryption/AdvancedPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/AdvancedPanel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ad98fa4e4384687a70ee7202160115f728612872
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/AdvancedPanel-test.tsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { render, screen, waitFor } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+import { AdvancedPanel } from "../../../../../../src/components/views/settings/encryption/AdvancedPanel";
+import SettingsStore from "../../../../../../src/settings/SettingsStore";
+import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
+
+describe("<AdvancedPanel />", () => {
+    let matrixClient: MatrixClient;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+    });
+
+    async function renderAdvancedPanel(onResetIdentityClick = jest.fn()) {
+        const renderResult = render(
+            <AdvancedPanel onResetIdentityClick={onResetIdentityClick} />,
+            withClientContextRenderOptions(matrixClient),
+        );
+        // Wait for the device keys to be displayed
+        await waitFor(() => expect(screen.getByText("ed25519")).toBeInTheDocument());
+        return renderResult;
+    }
+
+    describe("<EncryptionDetails />", () => {
+        it("should display a spinner when loading the device keys", async () => {
+            jest.spyOn(matrixClient.getCrypto()!, "getOwnDeviceKeys").mockImplementation(() => new Promise(() => {}));
+            render(<AdvancedPanel onResetIdentityClick={jest.fn()} />, withClientContextRenderOptions(matrixClient));
+
+            expect(screen.getByTestId("encryptionDetails")).toMatchSnapshot();
+        });
+
+        it("should display the device keys", async () => {
+            await renderAdvancedPanel();
+
+            // session id
+            expect(screen.getByText("ABCDEFGHI")).toBeInTheDocument();
+            // session key
+            expect(screen.getByText("ed25519")).toBeInTheDocument();
+            expect(screen.getByTestId("encryptionDetails")).toMatchSnapshot();
+        });
+
+        it("should call the onResetIdentityClick callback when the reset cryptographic identity button is clicked", async () => {
+            const user = userEvent.setup();
+
+            const onResetIdentityClick = jest.fn();
+            await renderAdvancedPanel(onResetIdentityClick);
+
+            const resetIdentityButton = screen.getByRole("button", { name: "Reset cryptographic identity" });
+            await user.click(resetIdentityButton);
+
+            expect(onResetIdentityClick).toHaveBeenCalled();
+        });
+    });
+
+    describe("<OtherSettings />", () => {
+        it("should display the blacklist of unverified devices settings", async () => {
+            const user = userEvent.setup();
+
+            jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
+            jest.spyOn(SettingsStore, "canSetValue").mockReturnValue(true);
+            jest.spyOn(SettingsStore, "setValue");
+
+            await renderAdvancedPanel();
+
+            expect(screen.getByTestId("otherSettings")).toMatchSnapshot();
+            const checkbox = screen.getByRole("checkbox", {
+                name: "In encrypted rooms, only send messages to verified users",
+            });
+            expect(checkbox).toBeChecked();
+
+            await user.click(checkbox);
+            expect(SettingsStore.setValue).toHaveBeenCalledWith(
+                "blacklistUnverifiedDevices",
+                null,
+                SettingLevel.DEVICE,
+                false,
+            );
+        });
+
+        it("should not display the section when the user can not set the value", async () => {
+            jest.spyOn(SettingsStore, "canSetValue").mockReturnValue(false);
+            jest.spyOn(SettingsStore, "setValue");
+
+            await renderAdvancedPanel();
+            expect(screen.queryByTestId("otherSettings")).toBeNull();
+        });
+    });
+});
diff --git a/test/unit-tests/components/views/settings/encryption/ChangeRecoveryKey-test.tsx b/test/unit-tests/components/views/settings/encryption/ChangeRecoveryKey-test.tsx
index 8929916a0eb30981e7017daa47d40c9b932afbc4..706efb2b90c3ba846d37a4caff85dfc3b8ec6ce3 100644
--- a/test/unit-tests/components/views/settings/encryption/ChangeRecoveryKey-test.tsx
+++ b/test/unit-tests/components/views/settings/encryption/ChangeRecoveryKey-test.tsx
@@ -7,8 +7,9 @@
 
 import React from "react";
 import { render, screen, waitFor } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
+import { mocked } from "jest-mock";
 
 import { ChangeRecoveryKey } from "../../../../../../src/components/views/settings/encryption/ChangeRecoveryKey";
 import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
@@ -18,6 +19,10 @@ jest.mock("../../../../../../src/utils/strings", () => ({
     copyPlaintext: jest.fn(),
 }));
 
+afterEach(() => {
+    jest.restoreAllMocks();
+});
+
 describe("<ChangeRecoveryKey />", () => {
     let matrixClient: MatrixClient;
 
@@ -36,7 +41,7 @@ describe("<ChangeRecoveryKey />", () => {
         );
     }
 
-    describe("flow to setup a recovery key", () => {
+    describe("flow to set up a recovery key", () => {
         it("should display information about the recovery key", async () => {
             const user = userEvent.setup();
 
@@ -107,6 +112,33 @@ describe("<ChangeRecoveryKey />", () => {
             await user.click(finishButton);
             expect(onFinish).toHaveBeenCalledWith();
         });
+
+        it("should display errors from bootstrapSecretStorage", async () => {
+            const consoleErrorSpy = jest.spyOn(console, "error").mockReturnValue(undefined);
+            mocked(matrixClient.getCrypto()!).bootstrapSecretStorage.mockRejectedValue(new Error("can't bootstrap"));
+
+            const user = userEvent.setup();
+            renderComponent(false);
+
+            // Display the recovery key to save
+            await waitFor(() => user.click(screen.getByRole("button", { name: "Continue" })));
+            // Display the form to confirm the recovery key
+            await waitFor(() => user.click(screen.getByRole("button", { name: "Continue" })));
+
+            await waitFor(() => expect(screen.getByText("Enter your recovery key to confirm")).toBeInTheDocument());
+
+            const finishButton = screen.getByRole("button", { name: "Finish set up" });
+            const input = screen.getByRole("textbox");
+            await userEvent.type(input, "encoded private key");
+            await user.click(finishButton);
+
+            await screen.findByText("Failed to set up secret storage");
+            await screen.findByText("Error: can't bootstrap");
+            expect(consoleErrorSpy).toHaveBeenCalledWith(
+                "Failed to set up secret storage:",
+                new Error("can't bootstrap"),
+            );
+        });
     });
 
     describe("flow to change the recovery key", () => {
diff --git a/test/unit-tests/components/views/settings/encryption/DeleteKeyStoragePanel-test.tsx b/test/unit-tests/components/views/settings/encryption/DeleteKeyStoragePanel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8daca481baf190f996fdaf4973eece1d5ce2146a
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/DeleteKeyStoragePanel-test.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen, waitFor } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import { mocked } from "jest-mock";
+
+import type { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+import { DeleteKeyStoragePanel } from "../../../../../../src/components/views/settings/encryption/DeleteKeyStoragePanel";
+import { useKeyStoragePanelViewModel } from "../../../../../../src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel";
+
+jest.mock("../../../../../../src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel", () => ({
+    useKeyStoragePanelViewModel: jest
+        .fn()
+        .mockReturnValue({ setEnabled: jest.fn(), isEnabled: true, loading: false, busy: false }),
+}));
+
+describe("<DeleteKeyStoragePanel />", () => {
+    let matrixClient: MatrixClient;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should match snapshot", async () => {
+        const { asFragment } = render(
+            <DeleteKeyStoragePanel onFinish={() => {}} />,
+            withClientContextRenderOptions(matrixClient),
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should call onFinished when cancel pressed", async () => {
+        const user = userEvent.setup();
+
+        const onFinish = jest.fn();
+        render(<DeleteKeyStoragePanel onFinish={onFinish} />, withClientContextRenderOptions(matrixClient));
+
+        await user.click(screen.getByRole("button", { name: "Cancel" }));
+        expect(onFinish).toHaveBeenCalled();
+    });
+
+    it("should call disable key storage when confirm pressed", async () => {
+        const setEnabled = jest.fn();
+
+        mocked(useKeyStoragePanelViewModel).mockReturnValue({
+            setEnabled,
+            isEnabled: true,
+            loading: false,
+            busy: false,
+        });
+
+        const user = userEvent.setup();
+
+        const onFinish = jest.fn();
+        render(<DeleteKeyStoragePanel onFinish={onFinish} />, withClientContextRenderOptions(matrixClient));
+
+        await user.click(screen.getByRole("button", { name: "Delete key storage" }));
+
+        expect(setEnabled).toHaveBeenCalledWith(false);
+    });
+
+    it("should wait with button disabled while setEnabled runs", async () => {
+        const setEnabledDefer = Promise.withResolvers<void>();
+
+        mocked(useKeyStoragePanelViewModel).mockReturnValue({
+            setEnabled: jest.fn().mockReturnValue(setEnabledDefer.promise),
+            isEnabled: true,
+            loading: false,
+            busy: false,
+        });
+
+        const user = userEvent.setup();
+
+        const onFinish = jest.fn();
+        render(<DeleteKeyStoragePanel onFinish={onFinish} />, withClientContextRenderOptions(matrixClient));
+
+        await user.click(screen.getByRole("button", { name: "Delete key storage" }));
+
+        expect(onFinish).not.toHaveBeenCalled();
+        expect(screen.getByRole("button", { name: "Delete key storage" })).toHaveAttribute("aria-disabled", "true");
+        setEnabledDefer.resolve();
+        await waitFor(() => expect(onFinish).toHaveBeenCalled());
+    });
+});
diff --git a/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx b/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx
index d51fcb840bf1414a9465297f59b1c5b9cdeb9a09..e6d9618a4334a30944706a6dcfa0882f7cfe139b 100644
--- a/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx
+++ b/test/unit-tests/components/views/settings/encryption/EncryptionCard-test.tsx
@@ -7,13 +7,14 @@
 
 import React from "react";
 import { render } from "jest-matrix-react";
+import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
 
 import { EncryptionCard } from "../../../../../../src/components/views/settings/encryption/EncryptionCard";
 
 describe("<EncryptionCard />", () => {
     it("should render", () => {
         const { asFragment } = render(
-            <EncryptionCard title="My title" description="My description">
+            <EncryptionCard Icon={KeyIcon} title="My title" description="My description">
                 Encryption card children
             </EncryptionCard>,
         );
diff --git a/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx
index 6ef79876c7aa6279c910d66a9152aa492a685d70..f0a129e6a1a5c8adbf4942e3d5bb9066eb8634b3 100644
--- a/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx
+++ b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx
@@ -6,26 +6,19 @@
  */
 
 import React from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { render, screen } from "jest-matrix-react";
 import { waitFor } from "@testing-library/dom";
 import userEvent from "@testing-library/user-event";
-import { mocked } from "jest-mock";
 
 import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
 import { RecoveryPanel } from "../../../../../../src/components/views/settings/encryption/RecoveryPanel";
-import { accessSecretStorage } from "../../../../../../src/SecurityManager";
-
-jest.mock("../../../../../../src/SecurityManager", () => ({
-    accessSecretStorage: jest.fn(),
-}));
 
 describe("<RecoveryPanel />", () => {
     let matrixClient: MatrixClient;
 
     beforeEach(() => {
         matrixClient = createTestClient();
-        mocked(accessSecretStorage).mockClear().mockResolvedValue();
     });
 
     function renderRecoverPanel(onChangeRecoveryKeyClick = jest.fn()) {
@@ -56,18 +49,6 @@ describe("<RecoveryPanel />", () => {
         expect(onChangeRecoveryKeyClick).toHaveBeenCalledWith(true);
     });
 
-    it("should ask to enter the recovery key when secrets are not cached", async () => {
-        jest.spyOn(matrixClient.secretStorage, "getDefaultKeyId").mockResolvedValue("default key");
-        const user = userEvent.setup();
-        const { asFragment } = renderRecoverPanel();
-
-        await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" }));
-        expect(asFragment()).toMatchSnapshot();
-
-        await user.click(screen.getByRole("button", { name: "Enter recovery key" }));
-        expect(accessSecretStorage).toHaveBeenCalled();
-    });
-
     it("should allow to change the recovery key when everything is good", async () => {
         jest.spyOn(matrixClient.secretStorage, "getDefaultKeyId").mockResolvedValue("default key");
         jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
diff --git a/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx b/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..36e35dbe83253c3f5e87f2b8ba332fb7afe41e58
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/RecoveryPanelOutOfSync-test.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+import { mocked } from "jest-mock";
+
+import { RecoveryPanelOutOfSync } from "../../../../../../src/components/views/settings/encryption/RecoveryPanelOutOfSync";
+import { accessSecretStorage } from "../../../../../../src/SecurityManager";
+
+jest.mock("../../../../../../src/SecurityManager", () => ({
+    accessSecretStorage: jest.fn(),
+}));
+
+describe("<RecoveyPanelOutOfSync />", () => {
+    function renderComponent(onFinish = jest.fn(), onForgotRecoveryKey = jest.fn()) {
+        return render(<RecoveryPanelOutOfSync onFinish={onFinish} onForgotRecoveryKey={onForgotRecoveryKey} />);
+    }
+
+    it("should render", () => {
+        const { asFragment } = renderComponent();
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should call onForgotRecoveryKey when the 'Forgot recovery key?' is clicked", async () => {
+        const user = userEvent.setup();
+
+        const onForgotRecoveryKey = jest.fn();
+        renderComponent(jest.fn(), onForgotRecoveryKey);
+
+        await user.click(screen.getByRole("button", { name: "Forgot recovery key?" }));
+        expect(onForgotRecoveryKey).toHaveBeenCalled();
+    });
+
+    it("should access to 4S and call onFinish when 'Enter recovery key' is clicked", async () => {
+        const user = userEvent.setup();
+        mocked(accessSecretStorage).mockClear().mockResolvedValue();
+
+        const onFinish = jest.fn();
+        renderComponent(onFinish);
+
+        await user.click(screen.getByRole("button", { name: "Enter recovery key" }));
+        expect(accessSecretStorage).toHaveBeenCalled();
+        expect(onFinish).toHaveBeenCalled();
+    });
+});
diff --git a/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0275b441429b7d14fc818a1ccfce5d6158b790fb
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React from "react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { sleep } from "matrix-js-sdk/src/utils";
+import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
+
+import { ResetIdentityPanel } from "../../../../../../src/components/views/settings/encryption/ResetIdentityPanel";
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+
+describe("<ResetIdentityPanel />", () => {
+    let matrixClient: MatrixClient;
+
+    beforeEach(() => {
+        matrixClient = createTestClient();
+    });
+
+    it("should reset the encryption when the continue button is clicked", async () => {
+        const user = userEvent.setup();
+
+        const onReset = jest.fn();
+        const { asFragment } = render(
+            <ResetIdentityPanel variant="compromised" onReset={onReset} onCancelClick={jest.fn()} />,
+            withClientContextRenderOptions(matrixClient),
+        );
+        expect(asFragment()).toMatchSnapshot();
+
+        // We need to pause the reset so that we can check that it's providing
+        // feedback to the user that something is happening.
+        const { promise: resetEncryptionPromise, resolve: resolveResetEncryption } = Promise.withResolvers<void>();
+        jest.spyOn(matrixClient.getCrypto()!, "resetEncryption").mockReturnValue(resetEncryptionPromise);
+
+        const continueButton = screen.getByRole("button", { name: "Continue" });
+        await user.click(continueButton);
+        expect(asFragment()).toMatchSnapshot();
+        resolveResetEncryption!();
+        await sleep(0);
+
+        expect(matrixClient.getCrypto()!.resetEncryption).toHaveBeenCalled();
+        expect(onReset).toHaveBeenCalled();
+    });
+
+    it("should display the 'forgot recovery key' variant correctly", async () => {
+        const onReset = jest.fn();
+        const { asFragment } = render(
+            <ResetIdentityPanel variant="forgot" onReset={onReset} onCancelClick={jest.fn()} />,
+            withClientContextRenderOptions(matrixClient),
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should display the 'sync failed' variant correctly", async () => {
+        const onReset = jest.fn();
+        const { asFragment } = render(
+            <ResetIdentityPanel variant="sync_failed" onReset={onReset} onCancelClick={jest.fn()} />,
+            withClientContextRenderOptions(matrixClient),
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+});
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/AdvancedPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/AdvancedPanel-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..6fc4a568417630785396a75dbba16fe04e50dc44
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/AdvancedPanel-test.tsx.snap
@@ -0,0 +1,253 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<AdvancedPanel /> <EncryptionDetails /> should display a spinner when loading the device keys 1`] = `
+<div
+  class="mx_EncryptionDetails"
+  data-testid="encryptionDetails"
+>
+  <div
+    class="mx_EncryptionDetails_session"
+  >
+    <h3
+      class="mx_EncryptionDetails_session_title"
+    >
+      Encryption details
+    </h3>
+    <div>
+      <span>
+        Session ID:
+      </span>
+      <span
+        data-testid="deviceId"
+      >
+        ABCDEFGHI
+      </span>
+    </div>
+    <div>
+      <span>
+        Session key:
+      </span>
+      <span
+        data-testid="sessionKey"
+      >
+        <svg
+          aria-label="Loading…"
+          class="_icon_11k6c_18"
+          fill="currentColor"
+          height="1em"
+          style="width: 20px; height: 20px;"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            clip-rule="evenodd"
+            d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2"
+            fill-rule="evenodd"
+          />
+        </svg>
+      </span>
+    </div>
+  </div>
+  <div
+    class="mx_EncryptionDetails_buttons"
+  >
+    <button
+      class="_button_vczzf_8 _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
+        />
+      </svg>
+      Export keys
+    </button>
+    <button
+      class="_button_vczzf_8 _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 15.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-3.6-3.6a.95.95 0 0 1-.275-.7q0-.425.275-.7.274-.275.712-.288t.713.263L11 12.15V5q0-.424.287-.713A.97.97 0 0 1 12 4q.424 0 .713.287Q13 4.576 13 5v7.15l1.875-1.875q.274-.274.713-.263.437.014.712.288a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-3.6 3.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063M6 20q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
+        />
+      </svg>
+      Import keys
+    </button>
+  </div>
+  <button
+    class="_button_vczzf_8 _destructive_vczzf_107"
+    data-kind="tertiary"
+    data-size="sm"
+    role="button"
+    tabindex="0"
+  >
+    Reset cryptographic identity
+  </button>
+</div>
+`;
+
+exports[`<AdvancedPanel /> <EncryptionDetails /> should display the device keys 1`] = `
+<div
+  class="mx_EncryptionDetails"
+  data-testid="encryptionDetails"
+>
+  <div
+    class="mx_EncryptionDetails_session"
+  >
+    <h3
+      class="mx_EncryptionDetails_session_title"
+    >
+      Encryption details
+    </h3>
+    <div>
+      <span>
+        Session ID:
+      </span>
+      <span
+        data-testid="deviceId"
+      >
+        ABCDEFGHI
+      </span>
+    </div>
+    <div>
+      <span>
+        Session key:
+      </span>
+      <span
+        data-testid="sessionKey"
+      >
+        ed25519
+      </span>
+    </div>
+  </div>
+  <div
+    class="mx_EncryptionDetails_buttons"
+  >
+    <button
+      class="_button_vczzf_8 _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
+        />
+      </svg>
+      Export keys
+    </button>
+    <button
+      class="_button_vczzf_8 _has-icon_vczzf_57"
+      data-kind="secondary"
+      data-size="sm"
+      role="button"
+      tabindex="0"
+    >
+      <svg
+        aria-hidden="true"
+        fill="currentColor"
+        height="20"
+        viewBox="0 0 24 24"
+        width="20"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M12 15.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-3.6-3.6a.95.95 0 0 1-.275-.7q0-.425.275-.7.274-.275.712-.288t.713.263L11 12.15V5q0-.424.287-.713A.97.97 0 0 1 12 4q.424 0 .713.287Q13 4.576 13 5v7.15l1.875-1.875q.274-.274.713-.263.437.014.712.288a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-3.6 3.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063M6 20q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
+        />
+      </svg>
+      Import keys
+    </button>
+  </div>
+  <button
+    class="_button_vczzf_8 _destructive_vczzf_107"
+    data-kind="tertiary"
+    data-size="sm"
+    role="button"
+    tabindex="0"
+  >
+    Reset cryptographic identity
+  </button>
+</div>
+`;
+
+exports[`<AdvancedPanel /> <OtherSettings /> should display the blacklist of unverified devices settings 1`] = `
+<form
+  class="_root_19upo_16 mx_OtherSettings"
+  data-testid="otherSettings"
+>
+  <h3
+    class="mx_OtherSettings_title"
+  >
+    Other people’s devices
+  </h3>
+  <div
+    class="_inline-field_19upo_32"
+  >
+    <div
+      class="_inline-field-control_19upo_44"
+    >
+      <div
+        class="_container_19o42_10"
+      >
+        <input
+          aria-describedby="radix-«r7»"
+          checked=""
+          class="_input_19o42_24"
+          id="radix-«r6»"
+          name="neverSendEncrypted"
+          title=""
+          type="checkbox"
+        />
+        <div
+          class="_ui_19o42_34"
+        />
+      </div>
+    </div>
+    <div
+      class="_inline-field-body_19upo_38"
+    >
+      <label
+        class="_label_19upo_59"
+        for="radix-«r6»"
+      >
+        In encrypted rooms, only send messages to verified users
+      </label>
+      <span
+        class="_message_19upo_85 _help-message_19upo_91"
+        id="radix-«r7»"
+      >
+        Warning: users who have not explicitly verified with you (e.g. using emoji) will not receive your encrypted messages. Also, unverified devices of verified users will not receive your encrypted messages.
+      </span>
+    </div>
+  </div>
+</form>
+`;
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/ChangeRecoveryKey-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/ChangeRecoveryKey-test.tsx.snap
index 0719c6cc5317b700d028d048254707a8faf6e095..c9fa65a64ec7c052fce6aa9db8cf9d26e81e9af3 100644
--- a/test/unit-tests/components/views/settings/encryption/__snapshots__/ChangeRecoveryKey-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/ChangeRecoveryKey-test.tsx.snap
@@ -3,17 +3,17 @@
 exports[`<ChangeRecoveryKey /> flow to change the recovery key should display the recovery key 1`] = `
 <DocumentFragment>
   <nav
-    class="_breadcrumb_ikpbb_17"
+    class="_breadcrumb_1xygz_8"
   >
     <button
       aria-label="Back"
-      class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
       role="button"
       style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
@@ -24,17 +24,17 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
     </button>
     <ol
-      class="_pages_ikpbb_26"
+      class="_pages_1xygz_17"
     >
       <li>
         <a
-          class="_link_ue21z_17"
+          class="_link_1v5rz_8"
           data-kind="primary"
           data-size="small"
           rel="noreferrer noopener"
@@ -47,7 +47,7 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
       <li>
         <span
           aria-current="page"
-          class="_last-page_ikpbb_39"
+          class="_last-page_1xygz_30"
         >
           Change recovery key
         </span>
@@ -61,7 +61,7 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -72,12 +72,12 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Change recovery key?
       </h2>
@@ -89,32 +89,32 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
       class="mx_KeyPanel"
     >
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60"
       >
         Recovery key
       </span>
       <div>
         <span
-          class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 mx_KeyPanel_key"
+          class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 mx_KeyPanel_key"
           data-testid="recoveryKey"
         >
           encoded private key
         </span>
         <span
-          class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
+          class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
         >
           Do not share this with anyone!
         </span>
       </div>
       <button
         aria-label="Copy"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -125,20 +125,20 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0V5Z"
+              d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0z"
             />
             <path
-              d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2v-9Zm2 0v9h9v-9h-9Z"
+              d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
             />
           </svg>
         </div>
       </button>
     </div>
     <div
-      class="mx_ChangeRecoveryKey_footer"
+      class="mx_EncryptionCard_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -147,7 +147,7 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
         Continue
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="tertiary"
         data-size="lg"
         role="button"
@@ -160,20 +160,20 @@ exports[`<ChangeRecoveryKey /> flow to change the recovery key should display th
 </DocumentFragment>
 `;
 
-exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user to enter the recovery key 1`] = `
+exports[`<ChangeRecoveryKey /> flow to set up a recovery key should ask the user to enter the recovery key 1`] = `
 <DocumentFragment>
   <nav
-    class="_breadcrumb_ikpbb_17"
+    class="_breadcrumb_1xygz_8"
   >
     <button
       aria-label="Back"
-      class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
       role="button"
       style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
@@ -184,17 +184,17 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
     </button>
     <ol
-      class="_pages_ikpbb_26"
+      class="_pages_1xygz_17"
     >
       <li>
         <a
-          class="_link_ue21z_17"
+          class="_link_1v5rz_8"
           data-kind="primary"
           data-size="small"
           rel="noreferrer noopener"
@@ -207,7 +207,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       <li>
         <span
           aria-current="page"
-          class="_last-page_ikpbb_39"
+          class="_last-page_1xygz_30"
         >
           Set up recovery
         </span>
@@ -221,7 +221,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -232,12 +232,12 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Enter your recovery key to confirm
       </h2>
@@ -246,31 +246,31 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       </span>
     </div>
     <form
-      class="_root_ssths_24 mx_KeyForm"
+      class="_root_19upo_16 mx_KeyForm"
     >
       <div
-        class="_field_ssths_34"
+        class="_field_19upo_26"
       >
         <label
-          class="_label_ssths_67"
-          for="radix-:r0:"
+          class="_label_19upo_59"
+          for="radix-«r0»"
         >
           Enter recovery key
         </label>
         <input
-          class="_control_9gon8_18"
-          id="radix-:r0:"
+          class="_control_sqdq4_10"
+          id="radix-«r0»"
           name="recoveryKey"
           required=""
           title=""
         />
       </div>
       <div
-        class="mx_ChangeRecoveryKey_footer"
+        class="mx_EncryptionCard_buttons"
       >
         <button
           aria-disabled="true"
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="primary"
           data-size="lg"
           role="button"
@@ -279,7 +279,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           Finish set up
         </button>
         <button
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="tertiary"
           data-size="lg"
           role="button"
@@ -293,20 +293,20 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
 </DocumentFragment>
 `;
 
-exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user to enter the recovery key 2`] = `
+exports[`<ChangeRecoveryKey /> flow to set up a recovery key should ask the user to enter the recovery key 2`] = `
 <DocumentFragment>
   <nav
-    class="_breadcrumb_ikpbb_17"
+    class="_breadcrumb_1xygz_8"
   >
     <button
       aria-label="Back"
-      class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
       role="button"
       style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
@@ -317,17 +317,17 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
     </button>
     <ol
-      class="_pages_ikpbb_26"
+      class="_pages_1xygz_17"
     >
       <li>
         <a
-          class="_link_ue21z_17"
+          class="_link_1v5rz_8"
           data-kind="primary"
           data-size="small"
           rel="noreferrer noopener"
@@ -340,7 +340,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       <li>
         <span
           aria-current="page"
-          class="_last-page_ikpbb_39"
+          class="_last-page_1xygz_30"
         >
           Set up recovery
         </span>
@@ -354,7 +354,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -365,12 +365,12 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Enter your recovery key to confirm
       </h2>
@@ -379,32 +379,32 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
       </span>
     </div>
     <form
-      class="_root_ssths_24 mx_KeyForm"
+      class="_root_19upo_16 mx_KeyForm"
     >
       <div
-        class="_field_ssths_34"
+        class="_field_19upo_26"
         data-invalid="true"
       >
         <label
-          class="_label_ssths_67"
+          class="_label_19upo_59"
           data-invalid="true"
-          for="radix-:r0:"
+          for="radix-«r0»"
         >
           Enter recovery key
         </label>
         <input
-          aria-describedby="radix-:r1:"
+          aria-describedby="radix-«r1»"
           aria-invalid="true"
-          class="_control_9gon8_18"
+          class="_control_sqdq4_10"
           data-invalid="true"
-          id="radix-:r0:"
+          id="radix-«r0»"
           name="recoveryKey"
           required=""
           title=""
         />
         <span
-          class="_message_ssths_93 _error-message_ssths_103"
-          id="radix-:r1:"
+          class="_message_19upo_85 _error-message_19upo_95"
+          id="radix-«r1»"
         >
           <svg
             fill="currentColor"
@@ -414,18 +414,18 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
             />
           </svg>
           The recovery key you entered is not correct.
         </span>
       </div>
       <div
-        class="mx_ChangeRecoveryKey_footer"
+        class="mx_EncryptionCard_buttons"
       >
         <button
           aria-disabled="true"
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="primary"
           data-size="lg"
           role="button"
@@ -434,7 +434,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
           Finish set up
         </button>
         <button
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="tertiary"
           data-size="lg"
           role="button"
@@ -448,20 +448,20 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should ask the user
 </DocumentFragment>
 `;
 
-exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display information about the recovery key 1`] = `
+exports[`<ChangeRecoveryKey /> flow to set up a recovery key should display information about the recovery key 1`] = `
 <DocumentFragment>
   <nav
-    class="_breadcrumb_ikpbb_17"
+    class="_breadcrumb_1xygz_8"
   >
     <button
       aria-label="Back"
-      class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
       role="button"
       style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
@@ -472,17 +472,17 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
     </button>
     <ol
-      class="_pages_ikpbb_26"
+      class="_pages_1xygz_17"
     >
       <li>
         <a
-          class="_link_ue21z_17"
+          class="_link_1v5rz_8"
           data-kind="primary"
           data-size="small"
           rel="noreferrer noopener"
@@ -495,7 +495,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
       <li>
         <span
           aria-current="page"
-          class="_last-page_ikpbb_39"
+          class="_last-page_1xygz_30"
         >
           Set up recovery
         </span>
@@ -509,7 +509,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -520,12 +520,12 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Set up recovery
       </h2>
@@ -534,15 +534,15 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
       </span>
     </div>
     <span
-      class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_InformationPanel_description"
+      class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_InformationPanel_description"
     >
       After clicking continue, we’ll generate a recovery key for you.
     </span>
     <div
-      class="mx_ChangeRecoveryKey_footer"
+      class="mx_EncryptionCard_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -551,7 +551,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
         Continue
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="tertiary"
         data-size="lg"
         role="button"
@@ -564,20 +564,20 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display infor
 </DocumentFragment>
 `;
 
-exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the recovery key 1`] = `
+exports[`<ChangeRecoveryKey /> flow to set up a recovery key should display the recovery key 1`] = `
 <DocumentFragment>
   <nav
-    class="_breadcrumb_ikpbb_17"
+    class="_breadcrumb_1xygz_8"
   >
     <button
       aria-label="Back"
-      class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
       role="button"
       style="--cpd-icon-button-size: 28px;"
       tabindex="0"
     >
       <div
-        class="_indicator-icon_133tf_26"
+        class="_indicator-icon_zr2a0_17"
         style="--cpd-icon-button-size: 100%;"
       >
         <svg
@@ -588,17 +588,17 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="m13.3 17.3-4.6-4.6a.877.877 0 0 1-.213-.325A1.106 1.106 0 0 1 8.425 12c0-.133.02-.258.062-.375A.878.878 0 0 1 8.7 11.3l4.6-4.6a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7L10.8 12l3.9 3.9a.949.949 0 0 1 .275.7.948.948 0 0 1-.275.7.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
           />
         </svg>
       </div>
     </button>
     <ol
-      class="_pages_ikpbb_26"
+      class="_pages_1xygz_17"
     >
       <li>
         <a
-          class="_link_ue21z_17"
+          class="_link_1v5rz_8"
           data-kind="primary"
           data-size="small"
           rel="noreferrer noopener"
@@ -611,7 +611,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
       <li>
         <span
           aria-current="page"
-          class="_last-page_ikpbb_39"
+          class="_last-page_1xygz_30"
         >
           Set up recovery
         </span>
@@ -625,7 +625,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -636,12 +636,12 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         Save your recovery key somewhere safe
       </h2>
@@ -653,32 +653,32 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
       class="mx_KeyPanel"
     >
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60"
       >
         Recovery key
       </span>
       <div>
         <span
-          class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 mx_KeyPanel_key"
+          class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 mx_KeyPanel_key"
           data-testid="recoveryKey"
         >
           encoded private key
         </span>
         <span
-          class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
+          class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
         >
           Do not share this with anyone!
         </span>
       </div>
       <button
         aria-label="Copy"
-        class="_icon-button_bh2qc_17"
+        class="_icon-button_m2erp_8"
         role="button"
         style="--cpd-icon-button-size: 28px;"
         tabindex="0"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -689,20 +689,20 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0V5Z"
+              d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0z"
             />
             <path
-              d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2v-9Zm2 0v9h9v-9h-9Z"
+              d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
             />
           </svg>
         </div>
       </button>
     </div>
     <div
-      class="mx_ChangeRecoveryKey_footer"
+      class="mx_EncryptionCard_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="lg"
         role="button"
@@ -711,7 +711,7 @@ exports[`<ChangeRecoveryKey /> flow to setup a recovery key should display the r
         Continue
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="tertiary"
         data-size="lg"
         role="button"
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/DeleteKeyStoragePanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/DeleteKeyStoragePanel-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..52541c468b92eb65df2de70487ca870c04a138a3
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/DeleteKeyStoragePanel-test.tsx.snap
@@ -0,0 +1,156 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<DeleteKeyStoragePanel /> should match snapshot 1`] = `
+<DocumentFragment>
+  <nav
+    class="_breadcrumb_1xygz_8"
+  >
+    <button
+      aria-label="Back"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+      role="button"
+      style="--cpd-icon-button-size: 28px;"
+      tabindex="0"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+          />
+        </svg>
+      </div>
+    </button>
+    <ol
+      class="_pages_1xygz_17"
+    >
+      <li>
+        <a
+          class="_link_1v5rz_8"
+          data-kind="primary"
+          data-size="small"
+          rel="noreferrer noopener"
+          role="button"
+          tabindex="0"
+        >
+          Encryption
+        </a>
+      </li>
+      <li>
+        <span
+          aria-current="page"
+          class="_last-page_1xygz_30"
+        >
+          Delete key storage
+        </span>
+      </li>
+    </ol>
+  </nav>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Are you sure you want to turn off key storage and delete it?
+      </h2>
+    </div>
+    <div
+      class="mx_Flex mx_EncryptionCard_emphasisedContent"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      Deleting key storage will remove your cryptographic identity and message keys from the server and turn off the following security features:
+      <ul
+        class="_visual-list_15wzx_8"
+      >
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-destructive_1ma3e_26"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
+            />
+          </svg>
+          You will not have encrypted message history on new devices
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-destructive_1ma3e_26"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
+            />
+          </svg>
+          You will lose access to your encrypted messages if you are signed out of Element everywhere
+        </li>
+      </ul>
+    </div>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        aria-disabled="false"
+        class="_button_vczzf_8 _destructive_vczzf_107"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Delete key storage
+      </button>
+      <button
+        class="_button_vczzf_8"
+        data-kind="tertiary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Cancel
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/EncryptionCard-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/EncryptionCard-test.tsx.snap
index e523e57c09099d4f91a03741f38fbad08dd7afdf..39aedb263775375d88e4c32c6bbc20fd4c365216 100644
--- a/test/unit-tests/components/views/settings/encryption/__snapshots__/EncryptionCard-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/EncryptionCard-test.tsx.snap
@@ -9,7 +9,7 @@ exports[`<EncryptionCard /> should render 1`] = `
       class="mx_EncryptionCard_header"
     >
       <div
-        class="_content_md016_17"
+        class="_content_o77nw_8"
         data-size="large"
       >
         <svg
@@ -20,12 +20,12 @@ exports[`<EncryptionCard /> should render 1`] = `
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M10.475 16.9A5.863 5.863 0 0 1 7 18c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.35 0 2.53.383 3.537 1.15 1.009.767 1.713 1.717 2.113 2.85h7.95a1.033 1.033 0 0 1 .725.3l1.025 1.025a.99.99 0 0 1 .2.288c.05.108.075.229.075.362a1.066 1.066 0 0 1-.25.7l-2.25 2.575a.973.973 0 0 1-1.038.313 1.033 1.033 0 0 1-.337-.188L17 14l-1.3 1.3c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063c-.133 0-.258-.02-.375-.063a.877.877 0 0 1-.325-.212L13 14h-.35a5.81 5.81 0 0 1-2.175 2.9Zm-4.887-3.487c.391.39.862.587 1.412.587.55 0 1.02-.196 1.412-.588C8.804 13.021 9 12.55 9 12c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 7 10c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 5 12c0 .55.196 1.02.588 1.412Z"
+            d="M10.475 16.9A5.86 5.86 0 0 1 7 18q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q2.026 0 3.537 1.15Q12.05 8.3 12.65 10h7.95q.2 0 .387.075.189.075.338.225l1.025 1.025a1 1 0 0 1 .2.288q.075.162.075.362t-.062.375a1.1 1.1 0 0 1-.188.325l-2.25 2.575a.97.97 0 0 1-1.038.313 1 1 0 0 1-.337-.188L17 14l-1.3 1.3q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L13 14h-.35a5.8 5.8 0 0 1-2.175 2.9m-4.887-3.487Q6.175 14 7 14q.824 0 1.412-.588Q9 12.826 9 12t-.588-1.412A1.93 1.93 0 0 0 7 10q-.824 0-1.412.588A1.93 1.93 0 0 0 5 12q0 .825.588 1.412"
           />
         </svg>
       </div>
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
       >
         My title
       </h2>
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap
index 5030eb02cc98a8adf44c7f5bf459c415a7170491..fda6b29aa1a2d3cdc8c99709b4007afb3793a0c7 100644
--- a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap
@@ -4,19 +4,20 @@ exports[`<RecoveryPanel /> should allow to change the recovery key when everythi
 <DocumentFragment>
   <div
     class="mx_SettingsSection mx_SettingsSection_newUi"
+    data-testid="recoveryPanel"
   >
     <div
       class="mx_SettingsSection_header"
     >
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
       >
         Recovery 
       </h2>
       Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
     </div>
     <button
-      class="_button_i91xf_17 _has-icon_i91xf_66"
+      class="_button_vczzf_8 _has-icon_vczzf_57"
       data-kind="secondary"
       data-size="sm"
       role="button"
@@ -31,7 +32,7 @@ exports[`<RecoveryPanel /> should allow to change the recovery key when everythi
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M7 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 5 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 7 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 7 14Zm0 4c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.117 0 2.13.275 3.037.825A6.212 6.212 0 0 1 12.2 9h8.375a1.033 1.033 0 0 1 .725.3l2 2c.1.1.17.208.212.325.042.117.063.242.063.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-3.175 3.175a.946.946 0 0 1-.3.2c-.117.05-.233.083-.35.1a.832.832 0 0 1-.35-.025.884.884 0 0 1-.325-.175L17.5 15l-1.425 1.075a.945.945 0 0 1-.887.15.859.859 0 0 1-.288-.15L13.375 15H12.2a6.212 6.212 0 0 1-2.162 2.175C9.128 17.725 8.117 18 7 18Zm0-2c.933 0 1.754-.283 2.463-.85A4.032 4.032 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.032 4.032 0 0 0-1.412-2.15C8.754 8.283 7.933 8 7 8c-1.1 0-2.042.392-2.825 1.175C3.392 9.958 3 10.9 3 12s.392 2.042 1.175 2.825C4.958 15.608 5.9 16 7 16Z"
+          d="M7 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 5 12q0-.825.588-1.412A1.93 1.93 0 0 1 7 10q.824 0 1.412.588Q9 11.175 9 12t-.588 1.412A1.93 1.93 0 0 1 7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.676 0 3.037.825A6.2 6.2 0 0 1 12.2 9h8.375q.2 0 .387.075.188.075.338.225l2 2q.15.15.212.325.063.175.063.375t-.062.375a.9.9 0 0 1-.213.325l-3.175 3.175a1 1 0 0 1-.3.2q-.175.075-.35.1a.8.8 0 0 1-.35-.025.9.9 0 0 1-.325-.175L17.5 15l-1.425 1.075a.95.95 0 0 1-.887.15.9.9 0 0 1-.288-.15L13.375 15H12.2a6.2 6.2 0 0 1-2.162 2.175Q8.675 18 7 18m0-2q1.4 0 2.463-.85A4.03 4.03 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.03 4.03 0 0 0-1.412-2.15Q8.4 8 7 8 5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
         />
       </svg>
       Change recovery key
@@ -40,76 +41,17 @@ exports[`<RecoveryPanel /> should allow to change the recovery key when everythi
 </DocumentFragment>
 `;
 
-exports[`<RecoveryPanel /> should ask to enter the recovery key when secrets are not cached 1`] = `
-<DocumentFragment>
-  <div
-    class="mx_SettingsSection mx_SettingsSection_newUi"
-  >
-    <div
-      class="mx_SettingsSection_header"
-    >
-      <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
-      >
-        Recovery 
-      </h2>
-      <div
-        class="mx_SettingsSubheader"
-      >
-        Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
-        <span
-          class="mx_SettingsSubheader_error"
-        >
-          <svg
-            fill="currentColor"
-            height="20px"
-            viewBox="0 0 24 24"
-            width="20px"
-            xmlns="http://www.w3.org/2000/svg"
-          >
-            <path
-              d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
-            />
-          </svg>
-          Your key storage is out of sync. Click the button below to fix the problem.
-        </span>
-      </div>
-    </div>
-    <button
-      class="_button_i91xf_17 _has-icon_i91xf_66"
-      data-kind="primary"
-      data-size="sm"
-      role="button"
-      tabindex="0"
-    >
-      <svg
-        aria-hidden="true"
-        fill="currentColor"
-        height="20"
-        viewBox="0 0 24 24"
-        width="20"
-        xmlns="http://www.w3.org/2000/svg"
-      >
-        <path
-          d="M7 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 5 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 7 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 7 14Zm0 4c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.117 0 2.13.275 3.037.825A6.212 6.212 0 0 1 12.2 9h8.375a1.033 1.033 0 0 1 .725.3l2 2c.1.1.17.208.212.325.042.117.063.242.063.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-3.175 3.175a.946.946 0 0 1-.3.2c-.117.05-.233.083-.35.1a.832.832 0 0 1-.35-.025.884.884 0 0 1-.325-.175L17.5 15l-1.425 1.075a.945.945 0 0 1-.887.15.859.859 0 0 1-.288-.15L13.375 15H12.2a6.212 6.212 0 0 1-2.162 2.175C9.128 17.725 8.117 18 7 18Zm0-2c.933 0 1.754-.283 2.463-.85A4.032 4.032 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.032 4.032 0 0 0-1.412-2.15C8.754 8.283 7.933 8 7 8c-1.1 0-2.042.392-2.825 1.175C3.392 9.958 3 10.9 3 12s.392 2.042 1.175 2.825C4.958 15.608 5.9 16 7 16Z"
-        />
-      </svg>
-      Enter recovery key
-    </button>
-  </div>
-</DocumentFragment>
-`;
-
 exports[`<RecoveryPanel /> should ask to set up a recovery key when there is no recovery key 1`] = `
 <DocumentFragment>
   <div
     class="mx_SettingsSection mx_SettingsSection_newUi"
+    data-testid="recoveryPanel"
   >
     <div
       class="mx_SettingsSection_header"
     >
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
       >
         Recovery 
         <span>
@@ -119,7 +61,7 @@ exports[`<RecoveryPanel /> should ask to set up a recovery key when there is no
       Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
     </div>
     <button
-      class="_button_i91xf_17 _has-icon_i91xf_66"
+      class="_button_vczzf_8 _has-icon_vczzf_57"
       data-kind="primary"
       data-size="sm"
       role="button"
@@ -134,7 +76,7 @@ exports[`<RecoveryPanel /> should ask to set up a recovery key when there is no
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M7 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 5 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 7 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 7 14Zm0 4c-1.667 0-3.083-.583-4.25-1.75C1.583 15.083 1 13.667 1 12c0-1.667.583-3.083 1.75-4.25C3.917 6.583 5.333 6 7 6c1.117 0 2.13.275 3.037.825A6.212 6.212 0 0 1 12.2 9h8.375a1.033 1.033 0 0 1 .725.3l2 2c.1.1.17.208.212.325.042.117.063.242.063.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-3.175 3.175a.946.946 0 0 1-.3.2c-.117.05-.233.083-.35.1a.832.832 0 0 1-.35-.025.884.884 0 0 1-.325-.175L17.5 15l-1.425 1.075a.945.945 0 0 1-.887.15.859.859 0 0 1-.288-.15L13.375 15H12.2a6.212 6.212 0 0 1-2.162 2.175C9.128 17.725 8.117 18 7 18Zm0-2c.933 0 1.754-.283 2.463-.85A4.032 4.032 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.032 4.032 0 0 0-1.412-2.15C8.754 8.283 7.933 8 7 8c-1.1 0-2.042.392-2.825 1.175C3.392 9.958 3 10.9 3 12s.392 2.042 1.175 2.825C4.958 15.608 5.9 16 7 16Z"
+          d="M7 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 5 12q0-.825.588-1.412A1.93 1.93 0 0 1 7 10q.824 0 1.412.588Q9 11.175 9 12t-.588 1.412A1.93 1.93 0 0 1 7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.676 0 3.037.825A6.2 6.2 0 0 1 12.2 9h8.375q.2 0 .387.075.188.075.338.225l2 2q.15.15.212.325.063.175.063.375t-.062.375a.9.9 0 0 1-.213.325l-3.175 3.175a1 1 0 0 1-.3.2q-.175.075-.35.1a.8.8 0 0 1-.35-.025.9.9 0 0 1-.325-.175L17.5 15l-1.425 1.075a.95.95 0 0 1-.887.15.9.9 0 0 1-.288-.15L13.375 15H12.2a6.2 6.2 0 0 1-2.162 2.175Q8.675 18 7 18m0-2q1.4 0 2.463-.85A4.03 4.03 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.03 4.03 0 0 0-1.412-2.15Q8.4 8 7 8 5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
         />
       </svg>
       Set up recovery
@@ -147,12 +89,13 @@ exports[`<RecoveryPanel /> should be in loading state when checking the recovery
 <DocumentFragment>
   <div
     class="mx_SettingsSection mx_SettingsSection_newUi"
+    data-testid="recoveryPanel"
   >
     <div
       class="mx_SettingsSection_header"
     >
       <h2
-        class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
       >
         Recovery 
       </h2>
@@ -160,7 +103,7 @@ exports[`<RecoveryPanel /> should be in loading state when checking the recovery
     </div>
     <svg
       aria-label="Loading…"
-      class="_icon_1ye7b_27"
+      class="_icon_11k6c_18"
       fill="currentColor"
       height="1em"
       style="width: 20px; height: 20px;"
@@ -170,7 +113,7 @@ exports[`<RecoveryPanel /> should be in loading state when checking the recovery
     >
       <path
         clip-rule="evenodd"
-        d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
+        d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2"
         fill-rule="evenodd"
       />
     </svg>
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..edf7de5af9649c5e16d0c1f762013ed31ff00cea
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanelOutOfSync-test.tsx.snap
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<RecoveyPanelOutOfSync /> should render 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_SettingsSection mx_SettingsSection_newUi"
+    data-testid="recoveryPanel"
+  >
+    <div
+      class="mx_SettingsSection_header"
+    >
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
+      >
+        Recovery 
+      </h2>
+      <div
+        class="mx_SettingsSubheader"
+      >
+        Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+        <span
+          class="mx_SettingsSubheader_error"
+        >
+          <svg
+            fill="currentColor"
+            height="20px"
+            viewBox="0 0 24 24"
+            width="20px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+            />
+          </svg>
+          Your key storage is out of sync. Click one of the buttons below to fix the problem.
+        </span>
+      </div>
+    </div>
+    <div
+      class="mx_RecoveryPanelOutOfSync"
+    >
+      <button
+        class="_button_vczzf_8"
+        data-kind="secondary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        Forgot recovery key?
+      </button>
+      <button
+        class="_button_vczzf_8 _has-icon_vczzf_57"
+        data-kind="primary"
+        data-size="sm"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          aria-hidden="true"
+          fill="currentColor"
+          height="20"
+          viewBox="0 0 24 24"
+          width="20"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M7 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 5 12q0-.825.588-1.412A1.93 1.93 0 0 1 7 10q.824 0 1.412.588Q9 11.175 9 12t-.588 1.412A1.93 1.93 0 0 1 7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.676 0 3.037.825A6.2 6.2 0 0 1 12.2 9h8.375q.2 0 .387.075.188.075.338.225l2 2q.15.15.212.325.063.175.063.375t-.062.375a.9.9 0 0 1-.213.325l-3.175 3.175a1 1 0 0 1-.3.2q-.175.075-.35.1a.8.8 0 0 1-.35-.025.9.9 0 0 1-.325-.175L17.5 15l-1.425 1.075a.95.95 0 0 1-.887.15.9.9 0 0 1-.288-.15L13.375 15H12.2a6.2 6.2 0 0 1-2.162 2.175Q8.675 18 7 18m0-2q1.4 0 2.463-.85A4.03 4.03 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.03 4.03 0 0 0-1.412-2.15Q8.4 8 7 8 5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
+          />
+        </svg>
+        Enter recovery key
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..894abf4842b6033a9879dacce3339b2085b60641
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/ResetIdentityPanel-test.tsx.snap
@@ -0,0 +1,751 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<ResetIdentityPanel /> should display the 'forgot recovery key' variant correctly 1`] = `
+<DocumentFragment>
+  <nav
+    class="_breadcrumb_1xygz_8"
+  >
+    <button
+      aria-label="Back"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+      role="button"
+      style="--cpd-icon-button-size: 28px;"
+      tabindex="0"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+          />
+        </svg>
+      </div>
+    </button>
+    <ol
+      class="_pages_1xygz_17"
+    >
+      <li>
+        <a
+          class="_link_1v5rz_8"
+          data-kind="primary"
+          data-size="small"
+          rel="noreferrer noopener"
+          role="button"
+          tabindex="0"
+        >
+          Encryption
+        </a>
+      </li>
+      <li>
+        <span
+          aria-current="page"
+          class="_last-page_1xygz_30"
+        >
+          Reset encryption
+        </span>
+      </li>
+    </ol>
+  </nav>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Forgot your recovery key? You’ll need to reset your identity.
+      </h2>
+    </div>
+    <div
+      class="mx_Flex mx_EncryptionCard_emphasisedContent"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <ul
+        class="_visual-list_15wzx_8"
+      >
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-success_1ma3e_22"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+          Your account details, contacts, preferences, and chat list will be kept
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will lose any message history that’s stored only on the server
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will need to verify all your existing devices and contacts again
+        </li>
+      </ul>
+    </div>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        aria-disabled="false"
+        class="_button_vczzf_8 _destructive_vczzf_107"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Continue
+      </button>
+      <button
+        class="_button_vczzf_8"
+        data-kind="tertiary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Cancel
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<ResetIdentityPanel /> should display the 'sync failed' variant correctly 1`] = `
+<DocumentFragment>
+  <nav
+    class="_breadcrumb_1xygz_8"
+  >
+    <button
+      aria-label="Back"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+      role="button"
+      style="--cpd-icon-button-size: 28px;"
+      tabindex="0"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+          />
+        </svg>
+      </div>
+    </button>
+    <ol
+      class="_pages_1xygz_17"
+    >
+      <li>
+        <a
+          class="_link_1v5rz_8"
+          data-kind="primary"
+          data-size="small"
+          rel="noreferrer noopener"
+          role="button"
+          tabindex="0"
+        >
+          Encryption
+        </a>
+      </li>
+      <li>
+        <span
+          aria-current="page"
+          class="_last-page_1xygz_30"
+        >
+          Reset encryption
+        </span>
+      </li>
+    </ol>
+  </nav>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Failed to sync key storage. You need to reset your identity.
+      </h2>
+    </div>
+    <div
+      class="mx_Flex mx_EncryptionCard_emphasisedContent"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <ul
+        class="_visual-list_15wzx_8"
+      >
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-success_1ma3e_22"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+          Your account details, contacts, preferences, and chat list will be kept
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will lose any message history that’s stored only on the server
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will need to verify all your existing devices and contacts again
+        </li>
+      </ul>
+    </div>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        aria-disabled="false"
+        class="_button_vczzf_8 _destructive_vczzf_107"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Continue
+      </button>
+      <button
+        class="_button_vczzf_8"
+        data-kind="tertiary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Cancel
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<ResetIdentityPanel /> should reset the encryption when the continue button is clicked 1`] = `
+<DocumentFragment>
+  <nav
+    class="_breadcrumb_1xygz_8"
+  >
+    <button
+      aria-label="Back"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+      role="button"
+      style="--cpd-icon-button-size: 28px;"
+      tabindex="0"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+          />
+        </svg>
+      </div>
+    </button>
+    <ol
+      class="_pages_1xygz_17"
+    >
+      <li>
+        <a
+          class="_link_1v5rz_8"
+          data-kind="primary"
+          data-size="small"
+          rel="noreferrer noopener"
+          role="button"
+          tabindex="0"
+        >
+          Encryption
+        </a>
+      </li>
+      <li>
+        <span
+          aria-current="page"
+          class="_last-page_1xygz_30"
+        >
+          Reset encryption
+        </span>
+      </li>
+    </ol>
+  </nav>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Are you sure you want to reset your identity?
+      </h2>
+    </div>
+    <div
+      class="mx_Flex mx_EncryptionCard_emphasisedContent"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <ul
+        class="_visual-list_15wzx_8"
+      >
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-success_1ma3e_22"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+          Your account details, contacts, preferences, and chat list will be kept
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will lose any message history that’s stored only on the server
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will need to verify all your existing devices and contacts again
+        </li>
+      </ul>
+      <span>
+        Only do this if you believe your account has been compromised.
+      </span>
+    </div>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        aria-disabled="false"
+        class="_button_vczzf_8 _destructive_vczzf_107"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Continue
+      </button>
+      <button
+        class="_button_vczzf_8"
+        data-kind="tertiary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        Cancel
+      </button>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<ResetIdentityPanel /> should reset the encryption when the continue button is clicked 2`] = `
+<DocumentFragment>
+  <nav
+    class="_breadcrumb_1xygz_8"
+  >
+    <button
+      aria-label="Back"
+      class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+      role="button"
+      style="--cpd-icon-button-size: 28px;"
+      tabindex="0"
+    >
+      <div
+        class="_indicator-icon_zr2a0_17"
+        style="--cpd-icon-button-size: 100%;"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+          />
+        </svg>
+      </div>
+    </button>
+    <ol
+      class="_pages_1xygz_17"
+    >
+      <li>
+        <a
+          class="_link_1v5rz_8"
+          data-kind="primary"
+          data-size="small"
+          rel="noreferrer noopener"
+          role="button"
+          tabindex="0"
+        >
+          Encryption
+        </a>
+      </li>
+      <li>
+        <span
+          aria-current="page"
+          class="_last-page_1xygz_30"
+        >
+          Reset encryption
+        </span>
+      </li>
+    </ol>
+  </nav>
+  <div
+    class="mx_EncryptionCard"
+  >
+    <div
+      class="mx_EncryptionCard_header"
+    >
+      <div
+        class="_content_o77nw_8 _destructive_o77nw_34"
+        data-size="large"
+      >
+        <svg
+          fill="currentColor"
+          height="1em"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+          />
+        </svg>
+      </div>
+      <h2
+        class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+      >
+        Are you sure you want to reset your identity?
+      </h2>
+    </div>
+    <div
+      class="mx_Flex mx_EncryptionCard_emphasisedContent"
+      style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+    >
+      <ul
+        class="_visual-list_15wzx_8"
+      >
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-success_1ma3e_22"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+            />
+          </svg>
+          Your account details, contacts, preferences, and chat list will be kept
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will lose any message history that’s stored only on the server
+        </li>
+        <li
+          class="_visual-list-item_1ma3e_8"
+        >
+          <svg
+            aria-hidden="true"
+            class="_visual-list-item-icon_1ma3e_17"
+            fill="currentColor"
+            height="24px"
+            viewBox="0 0 24 24"
+            width="24px"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+            />
+            <path
+              clip-rule="evenodd"
+              d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+              fill-rule="evenodd"
+            />
+          </svg>
+          You will need to verify all your existing devices and contacts again
+        </li>
+      </ul>
+      <span>
+        Only do this if you believe your account has been compromised.
+      </span>
+    </div>
+    <div
+      class="mx_EncryptionCard_buttons"
+    >
+      <button
+        aria-disabled="true"
+        class="_button_vczzf_8 _destructive_vczzf_107"
+        data-kind="primary"
+        data-size="lg"
+        role="button"
+        tabindex="0"
+      >
+        <svg
+          class="_icon_11k6c_18"
+          fill="currentColor"
+          height="1em"
+          style="width: 20px; height: 20px;"
+          viewBox="0 0 24 24"
+          width="1em"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            clip-rule="evenodd"
+            d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2"
+            fill-rule="evenodd"
+          />
+        </svg>
+         Reset in progress...
+      </button>
+      <div
+        class="mx_Flex mx_EncryptionCard_emphasisedContent"
+        style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+      >
+        <span
+          class="mx_ResetIdentityPanel_warning"
+        >
+          Do not close this window until the reset is finished
+        </span>
+      </div>
+    </div>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/components/views/settings/notifications/Notifications2-test.tsx b/test/unit-tests/components/views/settings/notifications/Notifications2-test.tsx
index 24441689a240c59672243bc583782f5951ef6761..d33b5c4e26253983cb7f0fd1427d80754c6208c4 100644
--- a/test/unit-tests/components/views/settings/notifications/Notifications2-test.tsx
+++ b/test/unit-tests/components/views/settings/notifications/Notifications2-test.tsx
@@ -10,8 +10,8 @@ import { act, findByRole, getByRole, queryByRole, render, waitFor } from "jest-m
 import userEvent from "@testing-library/user-event";
 import {
     ThreepidMedium,
-    IPushRules,
-    MatrixClient,
+    type IPushRules,
+    type MatrixClient,
     NotificationCountType,
     PushRuleKind,
     Room,
@@ -39,8 +39,7 @@ const labelActivityStatus = "New room activity, upgrades and status messages occ
 const labelActivityBots = "Messages sent by bots";
 const labelMentionUser = "Notify when someone mentions using @displayname or @mxid";
 const labelMentionRoom = "Notify when someone mentions using @room";
-const labelMentionKeyword =
-    "Notify when someone uses a keyword" + "Enter keywords here, or use for spelling variations or nicknames";
+const labelMentionKeyword = "Notify when someone uses a keyword";
 const labelResetDefault = "Reset to default settings";
 
 const keywords = ["justjann3", "justj4nn3", "justj4nne", "Janne", "J4nne", "Jann3", "jann3", "j4nne", "janne"];
diff --git a/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap b/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap
index a0651f139118762a211ca1c9a34b1ea71deab5d3..6ec9599fe1e5fa0953c91f049c602eeb307e8105 100644
--- a/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap
@@ -21,7 +21,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+                id="mx_LabelledToggleSwitch_«r17»"
               >
                 Enable notifications for this account
               </div>
@@ -29,7 +29,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             <div
               aria-checked="true"
               aria-disabled="true"
-              aria-labelledby="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+              aria-labelledby="mx_LabelledToggleSwitch_«r17»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
               role="switch"
               tabindex="0"
@@ -46,7 +46,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_QgU2PomxwKpa"
+                id="mx_LabelledToggleSwitch_«r18»"
               >
                 Enable desktop notifications for this session
               </div>
@@ -54,7 +54,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             <div
               aria-checked="false"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_QgU2PomxwKpa"
+              aria-labelledby="mx_LabelledToggleSwitch_«r18»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -71,7 +71,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_6hpi3YEetmBG"
+                id="mx_LabelledToggleSwitch_«r19»"
               >
                 Show message preview in desktop notification
               </div>
@@ -79,7 +79,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             <div
               aria-checked="false"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_6hpi3YEetmBG"
+              aria-labelledby="mx_LabelledToggleSwitch_«r19»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -96,7 +96,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_4yVCeEefiPqp"
+                id="mx_LabelledToggleSwitch_«r1a»"
               >
                 Enable audible notifications for this session
               </div>
@@ -104,7 +104,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             <div
               aria-checked="true"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_4yVCeEefiPqp"
+              aria-labelledby="mx_LabelledToggleSwitch_«r1a»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -232,108 +232,177 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_MRMwbPDmfG"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_MRMwbPDmfG"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_vY7Q4uEh9K"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  People
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_vY7Q4uEh9K"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        People
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_tmGQvdMWe9"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_tmGQvdMWe9"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_38QgU2Pomx"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Mentions and Keywords
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_38QgU2Pomx"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Mentions and Keywords
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_54DVIAu5Cs"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_54DVIAu5Cs"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_wKpa6hpi3Y"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Audio and Video calls
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_wKpa6hpi3Y"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Audio and Video calls
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
           </div>
         </div>
         <div
@@ -351,107 +420,176 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_iHRD7nyrA2"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_iHRD7nyrA2"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_EetmBG4yVC"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Invited to a room
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_EetmBG4yVC"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Invited to a room
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  disabled=""
-                  id="checkbox_ohjWVJIPau"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_ohjWVJIPau"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_eEefiPqpMR"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  New room activity, upgrades and status messages occur
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_eEefiPqpMR"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        New room activity, upgrades and status messages occur
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_y1OmnTidX4"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_y1OmnTidX4"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_MwbPDmfGtm"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Messages sent by bots
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_MwbPDmfGtm"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Messages sent by bots
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
           </div>
         </div>
         <div
@@ -490,113 +628,184 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_ePDS0OpWwA"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_ePDS0OpWwA"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_GQvdMWe954"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone mentions using @room
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_GQvdMWe954"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone mentions using @room
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_HG75JNTNkN"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_HG75JNTNkN"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_DVIAu5CsiH"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone mentions using @displayname or @userId:matrix.org
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_DVIAu5CsiH"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone mentions using @displayname or @userId:matrix.org
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_U64raTLcRs"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_U64raTLcRs"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r24»"
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_RD7nyrA2oh"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone uses a keyword
-                </span>
-                <span
-                  class="mx_LabelledCheckbox_byline"
-                >
-                  Enter keywords here, or use for spelling variations or nicknames
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_RD7nyrA2oh"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone uses a keyword
+                      </span>
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r24»"
+                    >
+                      Enter keywords here, or use for spelling variations or nicknames
+                    </span>
+                  </div>
+                </div>
+              </form>
+            </div>
             <div
               class="mx_TagComposer mx_TagComposer_disabled"
             >
@@ -641,7 +850,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             >
               <label
                 class="mx_SettingsFlag_label"
-                for="mx_SettingsFlag_QRlYy75nfv5b"
+                for="mx_SettingsFlag_jWVJIPauy1Om"
               >
                 <span
                   class="mx_SettingsFlag_labelText"
@@ -654,7 +863,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
                 aria-disabled="false"
                 aria-label="Show all activity in the room list (dots or number of unread messages)"
                 class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-                id="mx_SettingsFlag_QRlYy75nfv5b"
+                id="mx_SettingsFlag_jWVJIPauy1Om"
                 role="switch"
                 tabindex="0"
               >
@@ -668,7 +877,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
             >
               <label
                 class="mx_SettingsFlag_label"
-                for="mx_SettingsFlag_OEPN1su1JYVt"
+                for="mx_SettingsFlag_nTidX4ePDS0O"
               >
                 <span
                   class="mx_SettingsFlag_labelText"
@@ -681,7 +890,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
                 aria-disabled="false"
                 aria-label="Only show notifications in the thread activity centre"
                 class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-                id="mx_SettingsFlag_OEPN1su1JYVt"
+                id="mx_SettingsFlag_nTidX4ePDS0O"
                 role="switch"
                 tabindex="0"
               >
@@ -789,7 +998,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+                id="mx_LabelledToggleSwitch_«r0»"
               >
                 Enable notifications for this account
               </div>
@@ -797,7 +1006,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             <div
               aria-checked="true"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
+              aria-labelledby="mx_LabelledToggleSwitch_«r0»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -814,7 +1023,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_QgU2PomxwKpa"
+                id="mx_LabelledToggleSwitch_«r1»"
               >
                 Enable desktop notifications for this session
               </div>
@@ -822,7 +1031,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             <div
               aria-checked="false"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_QgU2PomxwKpa"
+              aria-labelledby="mx_LabelledToggleSwitch_«r1»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -839,7 +1048,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_6hpi3YEetmBG"
+                id="mx_LabelledToggleSwitch_«r2»"
               >
                 Show message preview in desktop notification
               </div>
@@ -847,7 +1056,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             <div
               aria-checked="false"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_6hpi3YEetmBG"
+              aria-labelledby="mx_LabelledToggleSwitch_«r2»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -864,7 +1073,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
               class="mx_SettingsFlag_label"
             >
               <div
-                id="mx_LabelledToggleSwitch_4yVCeEefiPqp"
+                id="mx_LabelledToggleSwitch_«r3»"
               >
                 Enable audible notifications for this session
               </div>
@@ -872,7 +1081,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             <div
               aria-checked="true"
               aria-disabled="false"
-              aria-labelledby="mx_LabelledToggleSwitch_4yVCeEefiPqp"
+              aria-labelledby="mx_LabelledToggleSwitch_«r3»"
               class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
               role="switch"
               tabindex="0"
@@ -997,105 +1206,174 @@ exports[`<Notifications /> matches the snapshot 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_MRMwbPDmfG"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_MRMwbPDmfG"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_pWwAHG75JN"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  People
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_pWwAHG75JN"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        People
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_tmGQvdMWe9"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_tmGQvdMWe9"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_TNkNU64raT"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Mentions and Keywords
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_TNkNU64raT"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Mentions and Keywords
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_54DVIAu5Cs"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_54DVIAu5Cs"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_LcRsQRlYy7"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Audio and Video calls
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_LcRsQRlYy7"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Audio and Video calls
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
           </div>
         </div>
         <div
@@ -1113,104 +1391,173 @@ exports[`<Notifications /> matches the snapshot 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_iHRD7nyrA2"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_iHRD7nyrA2"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_5nfv5bOEPN"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Invited to a room
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_5nfv5bOEPN"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Invited to a room
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_ohjWVJIPau"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_ohjWVJIPau"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        class="_input_1hel1_18"
+                        id="checkbox_1su1JYVtOy"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  New room activity, upgrades and status messages occur
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_1su1JYVtOy"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        New room activity, upgrades and status messages occur
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_y1OmnTidX4"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_y1OmnTidX4"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_R5kbu3pEwu"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Messages sent by bots
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_R5kbu3pEwu"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Messages sent by bots
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
           </div>
         </div>
         <div
@@ -1249,110 +1596,181 @@ exports[`<Notifications /> matches the snapshot 1`] = `
           <div
             class="mx_SettingsSubsection_content"
           >
-            <label
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_ePDS0OpWwA"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_ePDS0OpWwA"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_Ln9EnnYuxf"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone mentions using @room
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_Ln9EnnYuxf"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone mentions using @room
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_HG75JNTNkN"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_HG75JNTNkN"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_FEpOsztWhQ"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone mentions using @displayname or @userId:matrix.org
-                </span>
-              </div>
-            </label>
-            <label
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_FEpOsztWhQ"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone mentions using @displayname or @userId:matrix.org
+                      </span>
+                    </label>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
               class="mx_LabelledCheckbox"
             >
-              <span
-                class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  id="checkbox_U64raTLcRs"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_U64raTLcRs"
+                <div
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rt»"
+                        checked=""
+                        class="_input_1hel1_18"
+                        id="checkbox_kBerF1ejc4"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                </label>
-              </span>
-              <div
-                class="mx_LabelledCheckbox_labels"
-              >
-                <span
-                  class="mx_LabelledCheckbox_label"
-                >
-                  Notify when someone uses a keyword
-                </span>
-                <span
-                  class="mx_LabelledCheckbox_byline"
-                >
-                  Enter keywords here, or use for spelling variations or nicknames
-                </span>
-              </div>
-            </label>
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_kBerF1ejc4"
+                    >
+                      <span
+                        class="mx_LabelledCheckbox_label"
+                      >
+                        Notify when someone uses a keyword
+                      </span>
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rt»"
+                    >
+                      Enter keywords here, or use for spelling variations or nicknames
+                    </span>
+                  </div>
+                </div>
+              </form>
+            </div>
             <div
               class="mx_TagComposer"
             >
@@ -1407,7 +1825,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1431,7 +1849,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1455,7 +1873,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1479,7 +1897,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1503,7 +1921,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1527,7 +1945,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1551,7 +1969,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1575,7 +1993,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1599,7 +2017,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <path
-                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+                        d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
                       />
                     </svg>
                   </div>
@@ -1611,7 +2029,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             >
               <label
                 class="mx_SettingsFlag_label"
-                for="mx_SettingsFlag_QRlYy75nfv5b"
+                for="mx_SettingsFlag_jWVJIPauy1Om"
               >
                 <span
                   class="mx_SettingsFlag_labelText"
@@ -1624,7 +2042,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                 aria-disabled="false"
                 aria-label="Show all activity in the room list (dots or number of unread messages)"
                 class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-                id="mx_SettingsFlag_QRlYy75nfv5b"
+                id="mx_SettingsFlag_jWVJIPauy1Om"
                 role="switch"
                 tabindex="0"
               >
@@ -1638,7 +2056,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             >
               <label
                 class="mx_SettingsFlag_label"
-                for="mx_SettingsFlag_OEPN1su1JYVt"
+                for="mx_SettingsFlag_nTidX4ePDS0O"
               >
                 <span
                   class="mx_SettingsFlag_labelText"
@@ -1651,7 +2069,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
                 aria-disabled="false"
                 aria-label="Only show notifications in the thread activity centre"
                 class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-                id="mx_SettingsFlag_OEPN1su1JYVt"
+                id="mx_SettingsFlag_nTidX4ePDS0O"
                 role="switch"
                 tabindex="0"
               >
@@ -1704,38 +2122,61 @@ exports[`<Notifications /> matches the snapshot 1`] = `
             <div
               class="mx_SettingsIndent"
             >
-              <label
+              <div
                 class="mx_LabelledCheckbox"
               >
-                <span
-                  class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                <form
+                  class="_root_19upo_16"
                 >
-                  <input
-                    id="checkbox_OyR5kbu3pE"
-                    type="checkbox"
-                  />
-                  <label
-                    for="checkbox_OyR5kbu3pE"
+                  <div
+                    class="_inline-field_19upo_32"
                   >
                     <div
-                      class="mx_Checkbox_background"
+                      class="_inline-field-control_19upo_44"
                     >
                       <div
-                        class="mx_Checkbox_checkmark"
-                      />
+                        class="_container_1hel1_10"
+                      >
+                        <input
+                          class="_input_1hel1_18"
+                          id="checkbox_GFes1UFzOK"
+                          type="checkbox"
+                        />
+                        <div
+                          class="_ui_1hel1_19"
+                        >
+                          <svg
+                            aria-hidden="true"
+                            fill="currentColor"
+                            height="1em"
+                            viewBox="0 0 24 24"
+                            width="1em"
+                            xmlns="http://www.w3.org/2000/svg"
+                          >
+                            <path
+                              d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                            />
+                          </svg>
+                        </div>
+                      </div>
                     </div>
-                  </label>
-                </span>
-                <div
-                  class="mx_LabelledCheckbox_labels"
-                >
-                  <span
-                    class="mx_LabelledCheckbox_label"
-                  >
-                    test@example.tld
-                  </span>
-                </div>
-              </label>
+                    <div
+                      class="_inline-field-body_19upo_38"
+                    >
+                      <label
+                        class="_label_19upo_59"
+                        for="checkbox_GFes1UFzOK"
+                      >
+                        <span
+                          class="mx_LabelledCheckbox_label"
+                        >
+                          test@example.tld
+                        </span>
+                      </label>
+                    </div>
+                  </div>
+                </form>
+              </div>
             </div>
           </div>
         </div>
diff --git a/test/unit-tests/components/views/settings/tabs/SettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/SettingsTab-test.tsx
index ff4d781e482c241a95cc165affdbb6adeccbb30d..72f7a85b3c797113d6143b3ba133cfadd80d3c6b 100644
--- a/test/unit-tests/components/views/settings/tabs/SettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/SettingsTab-test.tsx
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ReactElement } from "react";
+import React, { type ReactElement } from "react";
 import { render } from "jest-matrix-react";
 
-import SettingsTab, { SettingsTabProps } from "../../../../../../src/components/views/settings/tabs/SettingsTab";
+import SettingsTab, { type SettingsTabProps } from "../../../../../../src/components/views/settings/tabs/SettingsTab";
 
 describe("<SettingsTab />", () => {
     const getComponent = (props: SettingsTabProps): ReactElement => <SettingsTab {...props} />;
diff --git a/test/unit-tests/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx
index b19424f19231eaa31984b0471c4a941a5a3dc7ff..b587c036268da2ba8af52f9d30b8c3ddf580c670 100644
--- a/test/unit-tests/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult, screen } from "jest-matrix-react";
-import { MatrixClient, Room, EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { fireEvent, render, type RenderResult, screen } from "jest-matrix-react";
+import { type MatrixClient, type Room, EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import AdvancedRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/AdvancedRoomSettingsTab";
diff --git a/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx
index 47e9565329508fa638273d5e7f88cd840daa5a3d..f4efc39e1f92860261f6607e22dc39b4f846c524 100644
--- a/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult, screen } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { render, type RenderResult, screen } from "jest-matrix-react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import userEvent from "@testing-library/user-event";
 
 import NotificationSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/NotificationSettingsTab";
 import { mkStubRoom, stubClient } from "../../../../../../test-utils";
 import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
 import { EchoChamber } from "../../../../../../../src/stores/local-echo/EchoChamber";
-import { RoomEchoChamber } from "../../../../../../../src/stores/local-echo/RoomEchoChamber";
+import { type RoomEchoChamber } from "../../../../../../../src/stores/local-echo/RoomEchoChamber";
 import SettingsStore from "../../../../../../../src/settings/SettingsStore";
 import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
 
@@ -37,6 +37,10 @@ describe("NotificatinSettingsTab", () => {
         NotificationSettingsTab.contextType = React.createContext<MatrixClient>(cli);
     });
 
+    afterEach(() => {
+        SettingsStore.reset();
+    });
+
     it("should prevent »Settings« link click from bubbling up to radio buttons", async () => {
         const tab = renderTab();
 
diff --git a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx
index 6d3d6881126bd98fb7f4dce0a77bb29b05c798e2..afd84ab33a93b7253fe809043bd1e75822352035 100644
--- a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx
@@ -7,11 +7,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, getByRole, render, RenderResult, screen, waitFor } from "jest-matrix-react";
-import { MatrixClient, EventType, MatrixEvent, Room, RoomMember, ISendEventResponse } from "matrix-js-sdk/src/matrix";
+import { fireEvent, getByRole, render, type RenderResult, screen, waitFor } from "jest-matrix-react";
+import {
+    type MatrixClient,
+    EventType,
+    MatrixEvent,
+    Room,
+    RoomMember,
+    type ISendEventResponse,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { mocked } from "jest-mock";
-import { defer } from "matrix-js-sdk/src/utils";
 import userEvent from "@testing-library/user-event";
 
 import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
@@ -202,7 +208,7 @@ describe("RolesRoomSettingsTab", () => {
     });
 
     it("should roll back power level change on error", async () => {
-        const deferred = defer<ISendEventResponse>();
+        const deferred = Promise.withResolvers<ISendEventResponse>();
         mocked(cli.sendStateEvent).mockReturnValue(deferred.promise);
         mocked(cli.getRoom).mockReturnValue(room);
         // @ts-ignore - mocked doesn't support overloads properly
@@ -217,6 +223,9 @@ describe("RolesRoomSettingsTab", () => {
                     content: {
                         users: {
                             [cli.getUserId()!]: 100,
+                            // needs at least one remaning admin in the room if we want to demote our user
+                            // otherwise another modal will be displayed
+                            ["@admin:server"]: 100,
                         },
                     },
                 });
diff --git a/test/unit-tests/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx
index 8e244999ade12228b679ad7a85d87242f2f73291..4184e76360cb48c471e4bc307ded4f6a2d3a4237 100644
--- a/test/unit-tests/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
-import { MatrixClient, Room, MatrixEvent, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
+import { fireEvent, render, type RenderResult, waitFor } from "jest-matrix-react";
+import { type MatrixClient, type Room, type MatrixEvent, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
 
 import { mkStubRoom, stubClient } from "../../../../../../test-utils";
 import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/components/views/settings/tabs/room/__snapshots__/PeopleRoomSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/room/__snapshots__/PeopleRoomSettingsTab-test.tsx.snap
index 7478ce1b16ee39b2559a9470cdd943028f1d6b5d..f46355bbd04ba36aecbadf70bc60fd6f4c4d224f 100644
--- a/test/unit-tests/components/views/settings/tabs/room/__snapshots__/PeopleRoomSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/room/__snapshots__/PeopleRoomSettingsTab-test.tsx.snap
@@ -17,7 +17,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
     >
       <span
         aria-label="Profile picture"
-        class="_avatar_mcap2_17 mx_BaseAvatar mx_PeopleRoomSettingsTab_avatar"
+        class="_avatar_1qbcf_8 mx_BaseAvatar mx_PeopleRoomSettingsTab_avatar"
         data-color="4"
         data-testid="avatar-img"
         data-type="round"
@@ -26,7 +26,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
       >
         <img
           alt=""
-          class="_image_mcap2_50"
+          class="_image_1qbcf_41"
           data-type="round"
           height="42px"
           loading="lazy"
@@ -80,7 +80,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
           />
         </svg>
       </div>
@@ -98,7 +98,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
           />
         </svg>
       </div>
@@ -123,7 +123,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1`
       class="mx_PeopleRoomSettingsTab_knock"
     >
       <span
-        class="_avatar_mcap2_17 mx_BaseAvatar mx_PeopleRoomSettingsTab_avatar _avatar-imageless_mcap2_61"
+        class="_avatar_1qbcf_8 mx_BaseAvatar mx_PeopleRoomSettingsTab_avatar _avatar-imageless_1qbcf_52"
         data-color="4"
         data-testid="avatar-img"
         data-type="round"
@@ -161,7 +161,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1`
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
+            d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
           />
         </svg>
       </div>
@@ -179,7 +179,7 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1`
           xmlns="http://www.w3.org/2000/svg"
         >
           <path
-            d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
           />
         </svg>
       </div>
diff --git a/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx
index 18ad320b02ed93b4d3e5c456b615c76105ce893f..b12ba3a4abe50d3caf92e8f6f3e566e15251bc87 100644
--- a/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
 
 import { fireEvent, render, screen, within } from "jest-matrix-react";
 import React from "react";
-import { MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import userEvent from "@testing-library/user-event";
-import { MockedObject } from "jest-mock";
+import { type MockedObject } from "jest-mock";
 
 import AccountUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/AccountUserSettingsTab";
 import { SdkContextClass, SDKContext } from "../../../../../../../src/contexts/SDKContext";
@@ -24,7 +24,7 @@ import {
     flushPromises,
 } from "../../../../../../test-utils";
 import { UIFeature } from "../../../../../../../src/settings/UIFeature";
-import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
+import { type OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
 import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
 import Modal from "../../../../../../../src/Modal";
 
@@ -153,7 +153,8 @@ describe("<AccountUserSettingsTab />", () => {
                 (settingName) => settingName === UIFeature.Deactivate,
             );
 
-            const createDialogFn = jest.fn();
+            const finishedDeferred = Promise.withResolvers<[boolean]>();
+            const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
             jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
 
             render(getComponent());
@@ -167,14 +168,16 @@ describe("<AccountUserSettingsTab />", () => {
                 (settingName) => settingName === UIFeature.Deactivate,
             );
 
-            const createDialogFn = jest.fn();
+            const finishedDeferred = Promise.withResolvers<[boolean]>();
+            const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
             jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
 
             render(getComponent());
 
             await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
 
-            createDialogFn.mock.calls[0][1].onFinished(true);
+            finishedDeferred.resolve([true]);
+            await flushPromises();
 
             expect(defaultProps.closeSettingsFn).toHaveBeenCalled();
         });
@@ -183,14 +186,16 @@ describe("<AccountUserSettingsTab />", () => {
                 (settingName) => settingName === UIFeature.Deactivate,
             );
 
-            const createDialogFn = jest.fn();
+            const finishedDeferred = Promise.withResolvers<[boolean]>();
+            const createDialogFn = jest.fn().mockReturnValue({ finished: finishedDeferred.promise });
             jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
 
             render(getComponent());
 
             await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
 
-            createDialogFn.mock.calls[0][1].onFinished(false);
+            finishedDeferred.resolve([false]);
+            await flushPromises();
 
             expect(defaultProps.closeSettingsFn).not.toHaveBeenCalled();
         });
diff --git a/test/unit-tests/components/views/settings/tabs/user/AppearanceUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/AppearanceUserSettingsTab-test.tsx
index 81ea635609873c3a3f4e41c1f80bfbb910e3b059..69f06d455cce556736723e404862801a3c2ca51f 100644
--- a/test/unit-tests/components/views/settings/tabs/user/AppearanceUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/AppearanceUserSettingsTab-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { render } from "jest-matrix-react";
 import React from "react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import AppearanceUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/AppearanceUserSettingsTab";
 import { withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
diff --git a/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx
index 49ce14042162c70b91459a3e3fd9542ea170eb83..018ec25ef359d999bddc7f2b143ffd5d994ad6e2 100644
--- a/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx
@@ -6,12 +6,16 @@
  */
 
 import React from "react";
-import { render, screen } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { act, render, screen } from "jest-matrix-react";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { waitFor } from "@testing-library/dom";
 import userEvent from "@testing-library/user-event";
+import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
 
-import { EncryptionUserSettingsTab } from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab";
+import {
+    EncryptionUserSettingsTab,
+    type State,
+} from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab";
 import { createTestClient, withClientContextRenderOptions } from "../../../../../../test-utils";
 import Modal from "../../../../../../../src/Modal";
 
@@ -35,8 +39,8 @@ describe("<EncryptionUserSettingsTab />", () => {
         });
     });
 
-    function renderComponent() {
-        return render(<EncryptionUserSettingsTab />, withClientContextRenderOptions(matrixClient));
+    function renderComponent(props: { initialState?: State } = {}) {
+        return render(<EncryptionUserSettingsTab {...props} />, withClientContextRenderOptions(matrixClient));
     }
 
     it("should display a loading state when the encryption state is computed", () => {
@@ -58,17 +62,51 @@ describe("<EncryptionUserSettingsTab />", () => {
         );
         expect(asFragment()).toMatchSnapshot();
 
-        const spy = jest.spyOn(Modal, "createDialog").mockReturnValue({} as any);
+        const spy = jest.spyOn(Modal, "createDialog").mockReturnValue({ finished: new Promise(() => {}) } as any);
         await user.click(screen.getByText("Verify this device"));
         expect(spy).toHaveBeenCalled();
     });
 
-    it("should display the recovery panel when the encryption is set up", async () => {
+    it("should display the recovery panel when key storage is enabled", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
         renderComponent();
         await waitFor(() => expect(screen.getByText("Recovery")).toBeInTheDocument());
     });
 
+    it("should not display the recovery panel when key storage is not enabled", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue(null);
+        renderComponent();
+        await expect(screen.queryByText("Recovery")).not.toBeInTheDocument();
+    });
+
+    it("should display the recovery out of sync panel when secrets are not cached", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+        // Secrets are not cached
+        jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
+            privateKeysInSecretStorage: true,
+            publicKeysOnDevice: true,
+            privateKeysCachedLocally: {
+                masterKey: false,
+                selfSigningKey: true,
+                userSigningKey: true,
+            },
+        });
+
+        const user = userEvent.setup();
+        const { asFragment } = renderComponent();
+
+        await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" }));
+        expect(asFragment()).toMatchSnapshot();
+
+        await user.click(screen.getByRole("button", { name: "Forgot recovery key?" }));
+        expect(
+            screen.getByRole("heading", { name: "Forgot your recovery key? You’ll need to reset your identity." }),
+        ).toBeVisible();
+    });
+
     it("should display the change recovery key panel when the user clicks on the change recovery button", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
         const user = userEvent.setup();
 
         const { asFragment } = renderComponent();
@@ -82,6 +120,7 @@ describe("<EncryptionUserSettingsTab />", () => {
     });
 
     it("should display the set up recovery key when the user clicks on the set up recovery key button", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
         jest.spyOn(matrixClient.secretStorage, "getDefaultKeyId").mockResolvedValue(null);
         const user = userEvent.setup();
 
@@ -94,4 +133,91 @@ describe("<EncryptionUserSettingsTab />", () => {
         await waitFor(() => expect(screen.getByText("Set up recovery")).toBeInTheDocument());
         expect(asFragment()).toMatchSnapshot();
     });
+
+    it("should display the reset identity panel when the user clicks on the reset cryptographic identity panel", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+
+        const user = userEvent.setup();
+
+        const { asFragment } = renderComponent();
+        await waitFor(() => {
+            const button = screen.getByRole("button", { name: "Reset cryptographic identity" });
+            expect(button).toBeInTheDocument();
+            user.click(button);
+        });
+        await waitFor(() =>
+            expect(screen.getByText("Are you sure you want to reset your identity?")).toBeInTheDocument(),
+        );
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should enter 'Forgot recovery' flow when initialState is set to 'reset_identity_forgot'", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+
+        renderComponent({ initialState: "reset_identity_forgot" });
+
+        expect(
+            await screen.findByRole("heading", {
+                name: "Forgot your recovery key? You’ll need to reset your identity.",
+            }),
+        ).toBeVisible();
+    });
+
+    it("should do 'Failed to sync' reset flow when initialState is set to 'reset_identity_sync_failed'", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+
+        renderComponent({ initialState: "reset_identity_sync_failed" });
+
+        expect(
+            await screen.findByRole("heading", {
+                name: "Failed to sync key storage. You need to reset your identity.",
+            }),
+        ).toBeVisible();
+    });
+
+    it("should update when key backup status event is fired", async () => {
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+
+        renderComponent();
+
+        await expect(await screen.findByRole("heading", { name: "Recovery" })).toBeVisible();
+
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue(null);
+
+        act(() => {
+            matrixClient.emit(CryptoEvent.KeyBackupStatus, false);
+        });
+
+        await waitFor(() => {
+            expect(screen.queryByRole("heading", { name: "Recovery" })).toBeNull();
+        });
+    });
+
+    it("should re-check the encryption state and displays the correct panel when the user clicks cancel the reset identity flow", async () => {
+        const user = userEvent.setup();
+
+        jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
+
+        // Secrets are not cached
+        jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
+            privateKeysInSecretStorage: true,
+            publicKeysOnDevice: true,
+            privateKeysCachedLocally: {
+                masterKey: false,
+                selfSigningKey: true,
+                userSigningKey: true,
+            },
+        });
+
+        renderComponent({ initialState: "reset_identity_forgot" });
+
+        expect(
+            screen.getByRole("heading", { name: "Forgot your recovery key? You’ll need to reset your identity." }),
+        ).toBeVisible();
+
+        await user.click(screen.getByRole("button", { name: "Back" }));
+        await waitFor(() =>
+            screen.getByText("Your key storage is out of sync. Click one of the buttons below to fix the problem."),
+        );
+    });
 });
diff --git a/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx
index d9d7cb48b1989cdfa6f3f2d6fd0717fa4ca2d9ba..17bc426a405f00a18532c84d1a8393241f11a9e1 100644
--- a/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx
@@ -14,10 +14,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
 import SdkConfig from "../../../../../../../src/SdkConfig";
 
 describe("<LabsUserSettingsTab />", () => {
-    const defaultProps = {
-        closeSettingsFn: jest.fn(),
-    };
-    const getComponent = () => <LabsUserSettingsTab {...defaultProps} />;
+    const getComponent = () => <LabsUserSettingsTab />;
 
     const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
 
diff --git a/test/unit-tests/components/views/settings/tabs/user/MediaPreviewAccountSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/MediaPreviewAccountSettingsTab-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e5946c070d3fb23da80d5ba79ac17ade3682fb8d
--- /dev/null
+++ b/test/unit-tests/components/views/settings/tabs/user/MediaPreviewAccountSettingsTab-test.tsx
@@ -0,0 +1,99 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { render } from "jest-matrix-react";
+import React from "react";
+import userEvent from "@testing-library/user-event";
+import { type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+
+import { MediaPreviewAccountSettings } from "../../../../../../../src/components/views/settings/tabs/user/MediaPreviewAccountSettings";
+import {
+    getMockClientWithEventEmitter,
+    mockClientMethodsServer,
+    mockClientMethodsUser,
+} from "../../../../../../test-utils";
+import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController";
+import MatrixClientBackedSettingsHandler from "../../../../../../../src/settings/handlers/MatrixClientBackedSettingsHandler";
+import type { MockedObject } from "jest-mock";
+import {
+    MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+    type MediaPreviewConfig,
+    MediaPreviewValue,
+} from "../../../../../../../src/@types/media_preview";
+import MediaPreviewConfigController from "../../../../../../../src/settings/controllers/MediaPreviewConfigController";
+
+describe("MediaPreviewAccountSettings", () => {
+    let client: MockedObject<MatrixClient>;
+    beforeEach(() => {
+        client = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
+            ...mockClientMethodsUser(),
+            getRoom: jest.fn(),
+            setAccountData: jest.fn(),
+            isVersionSupported: jest.fn().mockResolvedValue(true),
+        });
+        MatrixClientBackedController.matrixClient = client;
+        MatrixClientBackedSettingsHandler.matrixClient = client;
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should render", () => {
+        const { getByLabelText } = render(<MediaPreviewAccountSettings />);
+        // Defaults
+        expect(getByLabelText("Hide avatars of room and inviter")).not.toBeChecked();
+        expect(getByLabelText("Always hide")).not.toBeChecked();
+        expect(getByLabelText("In private rooms")).not.toBeChecked();
+        expect(getByLabelText("Always show")).toBeChecked();
+    });
+
+    it("should be able to toggle hide avatar", async () => {
+        const { getByLabelText } = render(<MediaPreviewAccountSettings />);
+        // Defaults
+        const element = getByLabelText("Hide avatars of room and inviter");
+        await userEvent.click(element);
+        expect(client.setAccountData).toHaveBeenCalledWith(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, {
+            invite_avatars: MediaPreviewValue.Off,
+            media_previews: MediaPreviewValue.On,
+        });
+        // Ensure we don't double set the account data.
+        expect(client.setAccountData).toHaveBeenCalledTimes(1);
+    });
+
+    // Skip the default.
+    it.each([
+        ["Always hide", MediaPreviewValue.Off],
+        ["In private rooms", MediaPreviewValue.Private],
+        ["Always show", MediaPreviewValue.On],
+    ])("should be able to toggle media preview option %s", async (key, value) => {
+        if (value === MediaPreviewConfigController.default.media_previews) {
+            // This is the default, so switch away first.
+            client.getAccountData.mockImplementation((type) => {
+                if (type === MEDIA_PREVIEW_ACCOUNT_DATA_TYPE) {
+                    return new MatrixEvent({
+                        content: {
+                            media_previews: MediaPreviewValue.Off,
+                        } satisfies Partial<MediaPreviewConfig>,
+                    });
+                }
+                return undefined;
+            });
+        }
+        const { getByLabelText } = render(<MediaPreviewAccountSettings />);
+
+        const element = getByLabelText(key);
+        await userEvent.click(element);
+        expect(client.setAccountData).toHaveBeenCalledWith(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, {
+            invite_avatars: MediaPreviewValue.On,
+            media_previews: value,
+        });
+        // Ensure we don't double set the account data.
+        expect(client.setAccountData).toHaveBeenCalledTimes(1);
+    });
+});
diff --git a/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx
index edecef24bb80b6544d18932e5c984e1c89024e52..0918b7809f4e61d39f2dd14737fb9540dbf814d1 100644
--- a/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { fireEvent, render, RenderResult, screen, waitFor } from "jest-matrix-react";
+import { fireEvent, render, type RenderResult, screen, waitFor } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 
 import PreferencesUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/PreferencesUserSettingsTab";
@@ -17,7 +17,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
 import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
 import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController";
 import PlatformPeg from "../../../../../../../src/PlatformPeg";
-import { SettingKey } from "../../../../../../../src/settings/Settings.tsx";
+import { type SettingKey } from "../../../../../../../src/settings/Settings.tsx";
 
 describe("PreferencesUserSettingsTab", () => {
     beforeEach(() => {
diff --git a/test/unit-tests/components/views/settings/tabs/user/SecurityUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/SecurityUserSettingsTab-test.tsx
index 65c1bf1d48085b8d74f1d1dfb529fe00369e75fb..454be8adaa275bd01de38c89e54ecf8ec5b72a84 100644
--- a/test/unit-tests/components/views/settings/tabs/user/SecurityUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/SecurityUserSettingsTab-test.tsx
@@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 import { render } from "jest-matrix-react";
-import React from "react";
+import React, { act } from "react";
+import userEvent from "@testing-library/user-event";
 
 import SecurityUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/SecurityUserSettingsTab";
 import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
@@ -19,12 +20,16 @@ import {
     mockPlatformPeg,
 } from "../../../../../../test-utils";
 import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
+import defaultDispatcher from "../../../../../../../src/dispatcher/dispatcher";
 
 describe("<SecurityUserSettingsTab />", () => {
     const defaultProps = {
         closeSettingsFn: jest.fn(),
     };
 
+    const getIgnoredUsers = jest.fn();
+    const setIgnoredUsers = jest.fn();
+
     const userId = "@alice:server.org";
     const deviceId = "alices-device";
     const mockClient = getMockClientWithEventEmitter({
@@ -33,7 +38,9 @@ describe("<SecurityUserSettingsTab />", () => {
         ...mockClientMethodsDevice(deviceId),
         ...mockClientMethodsCrypto(),
         getRooms: jest.fn().mockReturnValue([]),
-        getIgnoredUsers: jest.fn(),
+        getPushers: jest.fn().mockReturnValue([]),
+        getIgnoredUsers,
+        setIgnoredUsers,
     });
 
     const sdkContext = new SdkContextClass();
@@ -57,4 +64,29 @@ describe("<SecurityUserSettingsTab />", () => {
 
         expect(container).toMatchSnapshot();
     });
+
+    it("renders ignored users", () => {
+        getIgnoredUsers.mockReturnValue(["@bob:example.org"]);
+        const { getByRole } = render(getComponent());
+        const ignoredUsers = getByRole("list", { name: "Ignored users" });
+
+        expect(ignoredUsers).toMatchSnapshot();
+    });
+
+    it("allows unignoring a user", async () => {
+        getIgnoredUsers.mockReturnValue(["@bob:example.org"]);
+        const { getByText, getByRole } = render(getComponent());
+        await userEvent.click(getByRole("button", { name: "Unignore" }));
+        expect(setIgnoredUsers).toHaveBeenCalledWith([]);
+        await act(() => {
+            getIgnoredUsers.mockReturnValue([]);
+            defaultDispatcher.dispatch(
+                {
+                    action: "ignore_state_changed",
+                },
+                true,
+            );
+        });
+        expect(getByText("You have no ignored users.")).toBeVisible();
+    });
 });
diff --git a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx
index 030f769de212a53a782f0b27c95dcaadeb4ce1e5..709d4e1714e15947884b483b926e67e841e01be8 100644
--- a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx
@@ -11,29 +11,29 @@ import {
     act,
     fireEvent,
     render,
-    RenderResult,
+    type RenderResult,
     screen,
     waitFor,
     waitForElementToBeRemoved,
     within,
 } from "jest-matrix-react";
 import { logger } from "matrix-js-sdk/src/logger";
-import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
-import { defer, sleep } from "matrix-js-sdk/src/utils";
+import { type CryptoApi, DeviceVerificationStatus, type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
+import { sleep } from "matrix-js-sdk/src/utils";
 import {
     ClientEvent,
     Device,
-    IMyDevice,
+    type IMyDevice,
     LOCAL_NOTIFICATION_SETTINGS_PREFIX,
     MatrixEvent,
     PUSHER_DEVICE_ID,
     PUSHER_ENABLED,
-    IAuthData,
+    type IAuthData,
     GET_LOGIN_TOKEN_CAPABILITY,
     MatrixError,
-    MatrixClient,
+    type MatrixClient,
 } from "matrix-js-sdk/src/matrix";
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import fetchMock from "fetch-mock-jest";
 
 import {
@@ -41,6 +41,7 @@ import {
     flushPromises,
     getMockClientWithEventEmitter,
     mkPusher,
+    mockClientMethodsCrypto,
     mockClientMethodsServer,
     mockClientMethodsUser,
     mockPlatformPeg,
@@ -50,14 +51,14 @@ import Modal from "../../../../../../../src/Modal";
 import LogoutDialog from "../../../../../../../src/components/views/dialogs/LogoutDialog";
 import {
     DeviceSecurityVariation,
-    ExtendedDevice,
+    type ExtendedDevice,
 } from "../../../../../../../src/components/views/settings/devices/types";
 import { INACTIVE_DEVICE_AGE_MS } from "../../../../../../../src/components/views/settings/devices/filter";
 import SettingsStore from "../../../../../../../src/settings/SettingsStore";
 import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation";
 import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
-import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
-import { mockOpenIdConfiguration } from "../../../../../../test-utils/oidc";
+import { type OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
+import { makeDelegatedAuthConfig } from "../../../../../../test-utils/oidc";
 import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
 
 mockPlatformPeg();
@@ -124,10 +125,11 @@ describe("<SessionManagerTab />", () => {
 
     const mockCrypto = mocked({
         getDeviceVerificationStatus: jest.fn(),
-        getUserDeviceInfo: jest.fn(),
+        getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
         requestDeviceVerification: jest.fn().mockResolvedValue(mockVerificationRequest),
         supportsSecretsForQrLogin: jest.fn().mockReturnValue(false),
         isCrossSigningReady: jest.fn().mockReturnValue(true),
+        getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
     } as unknown as CryptoApi);
 
     let mockClient!: MockedObject<MatrixClient>;
@@ -203,6 +205,7 @@ describe("<SessionManagerTab />", () => {
         mockClient = getMockClientWithEventEmitter({
             ...mockClientMethodsUser(aliceId),
             ...mockClientMethodsServer(),
+            ...mockClientMethodsCrypto(),
             getCrypto: jest.fn().mockReturnValue(mockCrypto),
             getDevices: jest.fn(),
             getDeviceId: jest.fn().mockReturnValue(deviceId),
@@ -215,7 +218,7 @@ describe("<SessionManagerTab />", () => {
             getPushers: jest.fn(),
             setPusher: jest.fn(),
             setLocalNotificationSettings: jest.fn(),
-            getAuthIssuer: jest.fn().mockReturnValue(new Promise(() => {})),
+            getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)),
         });
         jest.clearAllMocks();
         jest.spyOn(logger, "error").mockRestore();
@@ -263,6 +266,7 @@ describe("<SessionManagerTab />", () => {
     });
 
     afterAll(() => {
+        // @ts-expect-error
         window.location = realWindowLocation;
     });
 
@@ -627,9 +631,10 @@ describe("<SessionManagerTab />", () => {
             // click verify button from current session section
             fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`));
 
-            const { onFinished: modalOnFinished } = modalSpy.mock.calls[0][1] as any;
-            // simulate modal completing process
-            await modalOnFinished();
+            // close the modal
+            const { close: closeModal } = modalSpy.mock.results[0].value;
+            closeModal();
+            await flushPromises();
 
             // cancelled in case it was a failure exit from modal
             expect(mockVerificationRequest.cancel).toHaveBeenCalled();
@@ -894,7 +899,7 @@ describe("<SessionManagerTab />", () => {
             });
 
             it("deletes a device when interactive auth is not required", async () => {
-                const deferredDeleteMultipleDevices = defer<{}>();
+                const deferredDeleteMultipleDevices = Promise.withResolvers<{}>();
                 mockClient.deleteMultipleDevices.mockReturnValue(deferredDeleteMultipleDevices.promise);
                 mockClient.getDevices.mockResolvedValue({
                     devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
@@ -1103,9 +1108,10 @@ describe("<SessionManagerTab />", () => {
                 // get a handle for resolving the delete call
                 // because promise flushing after the confirm modal is resolving this too
                 // and we want to test the loading state here
-                const resolveDeleteRequest = defer<IAuthData>();
-                mockClient.deleteMultipleDevices.mockImplementation(() => {
-                    return resolveDeleteRequest.promise;
+                const resolveDeleteRequest = Promise.withResolvers<IAuthData>();
+                mockClient.deleteMultipleDevices.mockImplementation(async () => {
+                    await resolveDeleteRequest.promise;
+                    return {};
                 });
 
                 const { getByTestId } = render(getComponent());
@@ -1615,7 +1621,6 @@ describe("<SessionManagerTab />", () => {
     describe("MSC4108 QR code login", () => {
         const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
         const issuer = "https://issuer.org";
-        const openIdConfiguration = mockOpenIdConfiguration(issuer);
 
         beforeEach(() => {
             settingsValueSpy.mockClear().mockReturnValue(true);
@@ -1631,16 +1636,16 @@ describe("<SessionManagerTab />", () => {
                     enabled: true,
                 },
             });
-            mockClient.getAuthIssuer.mockResolvedValue({ issuer });
-            mockCrypto.exportSecretsBundle = jest.fn();
-            fetchMock.mock(`${issuer}/.well-known/openid-configuration`, {
-                ...openIdConfiguration,
+            const delegatedAuthConfig = makeDelegatedAuthConfig(issuer);
+            mockClient.getAuthMetadata.mockResolvedValue({
+                ...delegatedAuthConfig,
                 grant_types_supported: [
-                    ...openIdConfiguration.grant_types_supported,
+                    ...delegatedAuthConfig.grant_types_supported,
                     "urn:ietf:params:oauth:grant-type:device_code",
                 ],
             });
-            fetchMock.mock(openIdConfiguration.jwks_uri!, {
+            mockCrypto.exportSecretsBundle = jest.fn();
+            fetchMock.mock(delegatedAuthConfig.jwks_uri!, {
                 status: 200,
                 headers: {
                     "Content-Type": "application/json",
diff --git a/test/unit-tests/components/views/settings/tabs/user/VoiceUserSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/VoiceUserSettingsTab-test.tsx
index 1c8ff6f26780584cb9975eb13978c7c494373576..d4519d429a881c721e55211e271bb7c3932d2c62 100644
--- a/test/unit-tests/components/views/settings/tabs/user/VoiceUserSettingsTab-test.tsx
+++ b/test/unit-tests/components/views/settings/tabs/user/VoiceUserSettingsTab-test.tsx
@@ -12,7 +12,10 @@ import { fireEvent, render, screen } from "jest-matrix-react";
 import { logger } from "matrix-js-sdk/src/logger";
 
 import VoiceUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/VoiceUserSettingsTab";
-import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../../../src/MediaDeviceHandler";
+import MediaDeviceHandler, {
+    type IMediaDevices,
+    MediaDeviceKindEnum,
+} from "../../../../../../../src/MediaDeviceHandler";
 import { flushPromises } from "../../../../../../test-utils";
 
 jest.mock("../../../../../../../src/MediaDeviceHandler");
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap
index 0f0e1930884928f83ed7fc2c8f073848d6f4c1ef..6c0407a9e8e3b25a2ed51823b1dc562c8c4a0216 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap
@@ -32,105 +32,105 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
               class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
             >
               <form
-                class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
+                class="_root_19upo_16 mx_ThemeChoicePanel_ThemeSelectors"
               >
                 <div
-                  class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
+                  class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
                 >
                   <div
-                    class="_inline-field-control_ssths_52"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="_container_1vw5h_18"
+                      class="_container_1e0uz_10"
                     >
                       <input
-                        class="_input_1vw5h_26"
+                        class="_input_1e0uz_18"
                         disabled=""
-                        id="radix-:r0:"
+                        id="radix-«r0»"
                         name="themeSelector"
                         title=""
                         type="radio"
                         value="light"
                       />
                       <div
-                        class="_ui_1vw5h_27"
+                        class="_ui_1e0uz_19"
                       />
                     </div>
                   </div>
                   <div
-                    class="_inline-field-body_ssths_46"
+                    class="_inline-field-body_19upo_38"
                   >
                     <label
-                      class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-                      for="radix-:r0:"
+                      class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+                      for="radix-«r0»"
                     >
                       Light
                     </label>
                   </div>
                 </div>
                 <div
-                  class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
+                  class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
                 >
                   <div
-                    class="_inline-field-control_ssths_52"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="_container_1vw5h_18"
+                      class="_container_1e0uz_10"
                     >
                       <input
-                        class="_input_1vw5h_26"
+                        class="_input_1e0uz_18"
                         disabled=""
-                        id="radix-:r1:"
+                        id="radix-«r1»"
                         name="themeSelector"
                         title=""
                         type="radio"
                         value="dark"
                       />
                       <div
-                        class="_ui_1vw5h_27"
+                        class="_ui_1e0uz_19"
                       />
                     </div>
                   </div>
                   <div
-                    class="_inline-field-body_ssths_46"
+                    class="_inline-field-body_19upo_38"
                   >
                     <label
-                      class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-                      for="radix-:r1:"
+                      class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+                      for="radix-«r1»"
                     >
                       Dark
                     </label>
                   </div>
                 </div>
                 <div
-                  class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
+                  class="_inline-field_19upo_32 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
                 >
                   <div
-                    class="_inline-field-control_ssths_52"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="_container_1vw5h_18"
+                      class="_container_1e0uz_10"
                     >
                       <input
-                        class="_input_1vw5h_26"
+                        class="_input_1e0uz_18"
                         disabled=""
-                        id="radix-:r2:"
+                        id="radix-«r2»"
                         name="themeSelector"
                         title=""
                         type="radio"
                         value="light-high-contrast"
                       />
                       <div
-                        class="_ui_1vw5h_27"
+                        class="_ui_1e0uz_19"
                       />
                     </div>
                   </div>
                   <div
-                    class="_inline-field-body_ssths_46"
+                    class="_inline-field-body_19upo_38"
                   >
                     <label
-                      class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
-                      for="radix-:r2:"
+                      class="_label_19upo_59 mx_ThemeChoicePanel_themeSelector_Label"
+                      for="radix-«r2»"
                     >
                       High contrast
                     </label>
@@ -139,7 +139,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
               </form>
             </div>
             <div
-              class="_separator_144s5_17"
+              class="_separator_7ckbw_8"
               data-kind="primary"
               data-orientation="horizontal"
               role="separator"
@@ -162,33 +162,33 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
               class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
             >
               <form
-                class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
+                class="_root_19upo_16 mx_LayoutSwitcher_LayoutSelector"
               >
                 <div
-                  class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+                  class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
                 >
                   <label
                     aria-label="Modern"
-                    class="_label_ssths_67"
-                    for="radix-:r3:"
+                    class="_label_19upo_59"
+                    for="radix-«r3»"
                   >
                     <div
                       class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
                     >
                       <div
-                        class="_container_1vw5h_18"
+                        class="_container_1e0uz_10"
                       >
                         <input
                           checked=""
-                          class="_input_1vw5h_26"
-                          id="radix-:r3:"
+                          class="_input_1e0uz_18"
+                          id="radix-«r3»"
                           name="layout"
                           title=""
                           type="radio"
                           value="group"
                         />
                         <div
-                          class="_ui_1vw5h_27"
+                          class="_ui_1e0uz_19"
                         />
                       </div>
                       <span>
@@ -227,7 +227,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                           class="mx_EventTile_avatar"
                         >
                           <span
-                            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                             data-color="2"
                             data-testid="avatar-img"
                             data-type="round"
@@ -281,7 +281,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                                 xmlns="http://www.w3.org/2000/svg"
                               >
                                 <path
-                                  d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                                  d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                                 />
                               </svg>
                             </div>
@@ -292,29 +292,29 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                   </label>
                 </div>
                 <div
-                  class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+                  class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
                 >
                   <label
                     aria-label="Message bubbles"
-                    class="_label_ssths_67"
-                    for="radix-:rc:"
+                    class="_label_19upo_59"
+                    for="radix-«rc»"
                   >
                     <div
                       class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
                     >
                       <div
-                        class="_container_1vw5h_18"
+                        class="_container_1e0uz_10"
                       >
                         <input
-                          class="_input_1vw5h_26"
-                          id="radix-:rc:"
+                          class="_input_1e0uz_18"
+                          id="radix-«rc»"
                           name="layout"
                           title=""
                           type="radio"
                           value="bubble"
                         />
                         <div
-                          class="_ui_1vw5h_27"
+                          class="_ui_1e0uz_19"
                         />
                       </div>
                       <span>
@@ -353,7 +353,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                           class="mx_EventTile_avatar"
                         >
                           <span
-                            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                             data-color="2"
                             data-testid="avatar-img"
                             data-type="round"
@@ -407,7 +407,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                                 xmlns="http://www.w3.org/2000/svg"
                               >
                                 <path
-                                  d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                                  d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                                 />
                               </svg>
                             </div>
@@ -418,29 +418,29 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                   </label>
                 </div>
                 <div
-                  class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
+                  class="_field_19upo_26 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
                 >
                   <label
                     aria-label="IRC (experimental)"
-                    class="_label_ssths_67"
-                    for="radix-:rl:"
+                    class="_label_19upo_59"
+                    for="radix-«rl»"
                   >
                     <div
                       class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
                     >
                       <div
-                        class="_container_1vw5h_18"
+                        class="_container_1e0uz_10"
                       >
                         <input
-                          class="_input_1vw5h_26"
-                          id="radix-:rl:"
+                          class="_input_1e0uz_18"
+                          id="radix-«rl»"
                           name="layout"
                           title=""
                           type="radio"
                           value="irc"
                         />
                         <div
-                          class="_ui_1vw5h_27"
+                          class="_ui_1e0uz_19"
                         />
                       </div>
                       <span>
@@ -479,7 +479,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                           class="mx_EventTile_avatar"
                         >
                           <span
-                            class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+                            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
                             data-color="2"
                             data-testid="avatar-img"
                             data-type="round"
@@ -533,7 +533,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                                 xmlns="http://www.w3.org/2000/svg"
                               >
                                 <path
-                                  d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+                                  d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
                                 />
                               </svg>
                             </div>
@@ -545,42 +545,42 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
                 </div>
               </form>
               <form
-                class="_root_ssths_24"
+                class="_root_19upo_16"
               >
                 <div
-                  class="_inline-field_ssths_40"
+                  class="_inline-field_19upo_32"
                 >
                   <div
-                    class="_inline-field-control_ssths_52"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="_container_qnvru_18"
+                      class="_container_19o42_10"
                     >
                       <input
-                        aria-describedby="radix-:rv:"
-                        class="_input_qnvru_32"
-                        id="radix-:ru:"
+                        aria-describedby="radix-«rv»"
+                        class="_input_19o42_24"
+                        id="radix-«ru»"
                         name="compactLayout"
                         title=""
                         type="checkbox"
                       />
                       <div
-                        class="_ui_qnvru_42"
+                        class="_ui_19o42_34"
                       />
                     </div>
                   </div>
                   <div
-                    class="_inline-field-body_ssths_46"
+                    class="_inline-field-body_19upo_38"
                   >
                     <label
-                      class="_label_ssths_67"
-                      for="radix-:ru:"
+                      class="_label_19upo_59"
+                      for="radix-«ru»"
                     >
                       Show compact text and messages
                     </label>
                     <span
-                      class="_message_ssths_93 _help-message_ssths_99"
-                      id="radix-:rv:"
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="radix-«rv»"
                     >
                       Modern layout must be selected to use this feature.
                     </span>
@@ -589,7 +589,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
               </form>
             </div>
             <div
-              class="_separator_144s5_17"
+              class="_separator_7ckbw_8"
               data-kind="primary"
               data-orientation="horizontal"
               role="separator"
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap
index 71ec4deb5924948dddae6508892e30837798aba2..bff7a9ab57cf2bdad83753cd7b23daaafc408fe7 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/EncryptionUserSettingsTab-test.tsx.snap
@@ -16,7 +16,7 @@ exports[`<EncryptionUserSettingsTab /> should display a verify button when the e
           class="mx_SettingsSection_header"
         >
           <h2
-            class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 mx_SettingsHeader"
+            class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
           >
             Device not verified 
           </h2>
@@ -34,7 +34,7 @@ exports[`<EncryptionUserSettingsTab /> should display a verify button when the e
                 xmlns="http://www.w3.org/2000/svg"
               >
                 <path
-                  d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 15a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 16c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-4c.283 0 .52-.096.713-.287A.968.968 0 0 0 13 12V8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8v4c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 9a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
+                  d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
                 />
               </svg>
               You need to verify this device in order to view your encryption settings.
@@ -42,7 +42,7 @@ exports[`<EncryptionUserSettingsTab /> should display a verify button when the e
           </div>
         </div>
         <button
-          class="_button_i91xf_17 _has-icon_i91xf_66"
+          class="_button_vczzf_8 _has-icon_vczzf_57"
           data-kind="primary"
           data-size="sm"
           role="button"
@@ -57,7 +57,7 @@ exports[`<EncryptionUserSettingsTab /> should display a verify button when the e
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 18c-.55 0-1.02-.196-1.413-.587A1.926 1.926 0 0 1 2 16V5c0-.55.196-1.02.587-1.413A1.926 1.926 0 0 1 4 3h16c.55 0 1.02.196 1.413.587.39.393.587.863.587 1.413v11c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 20 18H4Zm0-2h16V5H4v11Zm-2 5a.967.967 0 0 1-.712-.288A.968.968 0 0 1 1 20c0-.283.096-.52.288-.712A.967.967 0 0 1 2 19h20c.283 0 .52.096.712.288.192.191.288.429.288.712s-.096.52-.288.712A.968.968 0 0 1 22 21H2Z"
+              d="M4 18q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 16V5q0-.824.587-1.412A1.93 1.93 0 0 1 4 3h16q.824 0 1.413.587Q22 4.176 22 5v11q0 .824-.587 1.413A1.93 1.93 0 0 1 20 18zm0-2h16V5H4zm-2 5a.97.97 0 0 1-.712-.288A.97.97 0 0 1 1 20q0-.424.288-.712A.97.97 0 0 1 2 19h20q.424 0 .712.288.288.287.288.712 0 .424-.288.712A.97.97 0 0 1 22 21z"
             />
           </svg>
           Verify this device
@@ -81,6 +81,283 @@ exports[`<EncryptionUserSettingsTab /> should display the change recovery key pa
 </DocumentFragment>
 `;
 
+exports[`<EncryptionUserSettingsTab /> should display the recovery out of sync panel when secrets are not cached 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_SettingsTab mx_EncryptionUserSettingsTab"
+    data-testid="encryptionTab"
+  >
+    <div
+      class="mx_SettingsTab_sections"
+    >
+      <div
+        class="mx_SettingsSection mx_SettingsSection_newUi"
+        data-testid="recoveryPanel"
+      >
+        <div
+          class="mx_SettingsSection_header"
+        >
+          <h2
+            class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93 mx_SettingsHeader"
+          >
+            Recovery 
+          </h2>
+          <div
+            class="mx_SettingsSubheader"
+          >
+            Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+            <span
+              class="mx_SettingsSubheader_error"
+            >
+              <svg
+                fill="currentColor"
+                height="20px"
+                viewBox="0 0 24 24"
+                width="20px"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+                />
+              </svg>
+              Your key storage is out of sync. Click one of the buttons below to fix the problem.
+            </span>
+          </div>
+        </div>
+        <div
+          class="mx_RecoveryPanelOutOfSync"
+        >
+          <button
+            class="_button_vczzf_8"
+            data-kind="secondary"
+            data-size="sm"
+            role="button"
+            tabindex="0"
+          >
+            Forgot recovery key?
+          </button>
+          <button
+            class="_button_vczzf_8 _has-icon_vczzf_57"
+            data-kind="primary"
+            data-size="sm"
+            role="button"
+            tabindex="0"
+          >
+            <svg
+              aria-hidden="true"
+              fill="currentColor"
+              height="20"
+              viewBox="0 0 24 24"
+              width="20"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M7 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 5 12q0-.825.588-1.412A1.93 1.93 0 0 1 7 10q.824 0 1.412.588Q9 11.175 9 12t-.588 1.412A1.93 1.93 0 0 1 7 14m0 4q-2.5 0-4.25-1.75T1 12t1.75-4.25T7 6q1.676 0 3.037.825A6.2 6.2 0 0 1 12.2 9h8.375q.2 0 .387.075.188.075.338.225l2 2q.15.15.212.325.063.175.063.375t-.062.375a.9.9 0 0 1-.213.325l-3.175 3.175a1 1 0 0 1-.3.2q-.175.075-.35.1a.8.8 0 0 1-.35-.025.9.9 0 0 1-.325-.175L17.5 15l-1.425 1.075a.95.95 0 0 1-.887.15.9.9 0 0 1-.288-.15L13.375 15H12.2a6.2 6.2 0 0 1-2.162 2.175Q8.675 18 7 18m0-2q1.4 0 2.463-.85A4.03 4.03 0 0 0 10.875 13H14l1.45 1.025L17.5 12.5l1.775 1.375L21.15 12l-1-1h-9.275a4.03 4.03 0 0 0-1.412-2.15Q8.4 8 7 8 5.35 8 4.175 9.175T3 12t1.175 2.825T7 16"
+              />
+            </svg>
+            Enter recovery key
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`<EncryptionUserSettingsTab /> should display the reset identity panel when the user clicks on the reset cryptographic identity panel 1`] = `
+<DocumentFragment>
+  <div
+    class="mx_SettingsTab mx_EncryptionUserSettingsTab"
+    data-testid="encryptionTab"
+  >
+    <div
+      class="mx_SettingsTab_sections"
+    >
+      <nav
+        class="_breadcrumb_1xygz_8"
+      >
+        <button
+          aria-label="Back"
+          class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
+          role="button"
+          style="--cpd-icon-button-size: 28px;"
+          tabindex="0"
+        >
+          <div
+            class="_indicator-icon_zr2a0_17"
+            style="--cpd-icon-button-size: 100%;"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="m13.3 17.3-4.6-4.6a.9.9 0 0 1-.213-.325A1.1 1.1 0 0 1 8.425 12q0-.2.062-.375A.9.9 0 0 1 8.7 11.3l4.6-4.6a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7L10.8 12l3.9 3.9a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
+              />
+            </svg>
+          </div>
+        </button>
+        <ol
+          class="_pages_1xygz_17"
+        >
+          <li>
+            <a
+              class="_link_1v5rz_8"
+              data-kind="primary"
+              data-size="small"
+              rel="noreferrer noopener"
+              role="button"
+              tabindex="0"
+            >
+              Encryption
+            </a>
+          </li>
+          <li>
+            <span
+              aria-current="page"
+              class="_last-page_1xygz_30"
+            >
+              Reset encryption
+            </span>
+          </li>
+        </ol>
+      </nav>
+      <div
+        class="mx_EncryptionCard"
+      >
+        <div
+          class="mx_EncryptionCard_header"
+        >
+          <div
+            class="_content_o77nw_8 _destructive_o77nw_34"
+            data-size="large"
+          >
+            <svg
+              fill="currentColor"
+              height="1em"
+              viewBox="0 0 24 24"
+              width="1em"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
+              />
+            </svg>
+          </div>
+          <h2
+            class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
+          >
+            Are you sure you want to reset your identity?
+          </h2>
+        </div>
+        <div
+          class="mx_Flex mx_EncryptionCard_emphasisedContent"
+          style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: normal; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
+        >
+          <ul
+            class="_visual-list_15wzx_8"
+          >
+            <li
+              class="_visual-list-item_1ma3e_8"
+            >
+              <svg
+                aria-hidden="true"
+                class="_visual-list-item-icon_1ma3e_17 _visual-list-item-icon-success_1ma3e_22"
+                fill="currentColor"
+                height="24px"
+                viewBox="0 0 24 24"
+                width="24px"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+              Your account details, contacts, preferences, and chat list will be kept
+            </li>
+            <li
+              class="_visual-list-item_1ma3e_8"
+            >
+              <svg
+                aria-hidden="true"
+                class="_visual-list-item-icon_1ma3e_17"
+                fill="currentColor"
+                height="24px"
+                viewBox="0 0 24 24"
+                width="24px"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+                />
+                <path
+                  clip-rule="evenodd"
+                  d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+                  fill-rule="evenodd"
+                />
+              </svg>
+              You will lose any message history that’s stored only on the server
+            </li>
+            <li
+              class="_visual-list-item_1ma3e_8"
+            >
+              <svg
+                aria-hidden="true"
+                class="_visual-list-item-icon_1ma3e_17"
+                fill="currentColor"
+                height="24px"
+                viewBox="0 0 24 24"
+                width="24px"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
+                />
+                <path
+                  clip-rule="evenodd"
+                  d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
+                  fill-rule="evenodd"
+                />
+              </svg>
+              You will need to verify all your existing devices and contacts again
+            </li>
+          </ul>
+          <span>
+            Only do this if you believe your account has been compromised.
+          </span>
+        </div>
+        <div
+          class="mx_EncryptionCard_buttons"
+        >
+          <button
+            aria-disabled="false"
+            class="_button_vczzf_8 _destructive_vczzf_107"
+            data-kind="primary"
+            data-size="lg"
+            role="button"
+            tabindex="0"
+          >
+            Continue
+          </button>
+          <button
+            class="_button_vczzf_8"
+            data-kind="tertiary"
+            data-size="lg"
+            role="button"
+            tabindex="0"
+          >
+            Cancel
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</DocumentFragment>
+`;
+
 exports[`<EncryptionUserSettingsTab /> should display the set up recovery key when the user clicks on the set up recovery key button 1`] = `
 <DocumentFragment>
   <div
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap
index ddbbe2e000a2c23b5908b97324cec32ad75bbc5f..3e3662bd6851e1d075abfec2e9841c7a5916c94b 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap
@@ -827,33 +827,6 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   />
                 </div>
               </div>
-              <div
-                class="mx_SettingsFlag"
-              >
-                <label
-                  class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_NIiWzqsApP1c"
-                >
-                  <span
-                    class="mx_SettingsFlag_labelText"
-                  >
-                    Show previews/thumbnails for images
-                  </span>
-                </label>
-                <div
-                  aria-checked="true"
-                  aria-disabled="true"
-                  aria-label="Show previews/thumbnails for images"
-                  class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_NIiWzqsApP1c"
-                  role="switch"
-                  tabindex="0"
-                >
-                  <div
-                    class="mx_ToggleSwitch_ball"
-                  />
-                </div>
-              </div>
             </div>
           </div>
           <div
@@ -876,7 +849,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_q1SIAPqLMVXh"
+                  for="mx_SettingsFlag_NIiWzqsApP1c"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -889,7 +862,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show typing notifications"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_q1SIAPqLMVXh"
+                  id="mx_SettingsFlag_NIiWzqsApP1c"
                   role="switch"
                   tabindex="0"
                 >
@@ -903,7 +876,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_dXFDGgBsKXay"
+                  for="mx_SettingsFlag_q1SIAPqLMVXh"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -916,7 +889,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show a placeholder for removed messages"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_dXFDGgBsKXay"
+                  id="mx_SettingsFlag_q1SIAPqLMVXh"
                   role="switch"
                   tabindex="0"
                 >
@@ -930,7 +903,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_7Az0xw4Bs4Tt"
+                  for="mx_SettingsFlag_dXFDGgBsKXay"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -943,7 +916,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show read receipts sent by other users"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_7Az0xw4Bs4Tt"
+                  id="mx_SettingsFlag_dXFDGgBsKXay"
                   role="switch"
                   tabindex="0"
                 >
@@ -957,7 +930,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_8jmzPIlPoBCv"
+                  for="mx_SettingsFlag_7Az0xw4Bs4Tt"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -970,7 +943,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show join/leave messages (invites/removes/bans unaffected)"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_8jmzPIlPoBCv"
+                  id="mx_SettingsFlag_7Az0xw4Bs4Tt"
                   role="switch"
                   tabindex="0"
                 >
@@ -984,7 +957,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_enFRaTjdsFou"
+                  for="mx_SettingsFlag_8jmzPIlPoBCv"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -997,7 +970,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show display name changes"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_enFRaTjdsFou"
+                  id="mx_SettingsFlag_8jmzPIlPoBCv"
                   role="switch"
                   tabindex="0"
                 >
@@ -1011,7 +984,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_bfwnd5rz4XNX"
+                  for="mx_SettingsFlag_enFRaTjdsFou"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1024,7 +997,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show chat effects (animations when receiving e.g. confetti)"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_bfwnd5rz4XNX"
+                  id="mx_SettingsFlag_enFRaTjdsFou"
                   role="switch"
                   tabindex="0"
                 >
@@ -1038,7 +1011,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_gs5uWEzYzZrS"
+                  for="mx_SettingsFlag_bfwnd5rz4XNX"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1051,7 +1024,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show profile picture changes"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_gs5uWEzYzZrS"
+                  id="mx_SettingsFlag_bfwnd5rz4XNX"
                   role="switch"
                   tabindex="0"
                 >
@@ -1065,7 +1038,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_qWg7OgID1yRR"
+                  for="mx_SettingsFlag_gs5uWEzYzZrS"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1078,7 +1051,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show avatars in user, room and event mentions"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_qWg7OgID1yRR"
+                  id="mx_SettingsFlag_gs5uWEzYzZrS"
                   role="switch"
                   tabindex="0"
                 >
@@ -1092,7 +1065,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_pOPewl7rtMbV"
+                  for="mx_SettingsFlag_qWg7OgID1yRR"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1105,7 +1078,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Enable big emoji in chat"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_pOPewl7rtMbV"
+                  id="mx_SettingsFlag_qWg7OgID1yRR"
                   role="switch"
                   tabindex="0"
                 >
@@ -1119,7 +1092,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_cmt3PZSyNp3v"
+                  for="mx_SettingsFlag_pOPewl7rtMbV"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1132,7 +1105,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Jump to the bottom of the timeline when you send a message"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_cmt3PZSyNp3v"
+                  id="mx_SettingsFlag_pOPewl7rtMbV"
                   role="switch"
                   tabindex="0"
                 >
@@ -1146,7 +1119,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_dJJz3lHUv9XX"
+                  for="mx_SettingsFlag_cmt3PZSyNp3v"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1159,7 +1132,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show current profile picture and name for users in message history"
                   class="mx_AccessibleButton mx_ToggleSwitch"
-                  id="mx_SettingsFlag_dJJz3lHUv9XX"
+                  id="mx_SettingsFlag_cmt3PZSyNp3v"
                   role="switch"
                   tabindex="0"
                 >
@@ -1170,6 +1143,168 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               </div>
             </div>
           </div>
+          <div
+            class="mx_SettingsSubsection mx_SettingsSubsection_newUi"
+          >
+            <div
+              class="mx_SettingsSubsectionHeading"
+            >
+              <h3
+                class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
+              >
+                Moderation and safety
+              </h3>
+            </div>
+            <div
+              class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
+            >
+              <form
+                class="_root_19upo_16 mx_MediaPreviewAccountSetting_Form"
+              >
+                <div
+                  class="mx_SettingsFlag mx_MediaPreviewAccountSetting_ToggleSwitch"
+                >
+                  <span
+                    class="mx_SettingsFlag_label"
+                  >
+                    <div
+                      id="mx_LabelledToggleSwitch_«r8»"
+                    >
+                      Hide avatars of room and inviter
+                    </div>
+                  </span>
+                  <div
+                    aria-checked="false"
+                    aria-disabled="false"
+                    aria-labelledby="mx_LabelledToggleSwitch_«r8»"
+                    class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
+                    role="switch"
+                    tabindex="0"
+                  >
+                    <div
+                      class="mx_ToggleSwitch_ball"
+                    />
+                  </div>
+                </div>
+                <div
+                  aria-label="Show media in timeline"
+                  class="_field_19upo_26"
+                  id="mx_media_previews"
+                  role="radiogroup"
+                >
+                  <label
+                    class="_label_19upo_59"
+                    for="radix-«r9»"
+                  >
+                    Show media in timeline
+                  </label>
+                  <span
+                    class="_message_19upo_85 _help-message_19upo_91 mx_MediaPreviewAccountSetting_RadioHelp"
+                    id="radix-«ra»"
+                  >
+                    A hidden media can always be shown by tapping on it
+                  </span>
+                  <div
+                    class="_inline-field_19upo_32 mx_MediaPreviewAccountSetting_Radio"
+                  >
+                    <div
+                      class="_inline-field-control_19upo_44"
+                    >
+                      <div
+                        class="_container_1e0uz_10"
+                      >
+                        <input
+                          class="_input_1e0uz_18"
+                          id="mx_media_previews_off"
+                          type="radio"
+                        />
+                        <div
+                          class="_ui_1e0uz_19"
+                        />
+                      </div>
+                    </div>
+                    <div
+                      class="_inline-field-body_19upo_38"
+                    >
+                      <label
+                        class="_label_19upo_59"
+                        for="mx_media_previews_off"
+                      >
+                        Always hide
+                      </label>
+                    </div>
+                  </div>
+                  <div
+                    class="_inline-field_19upo_32 mx_MediaPreviewAccountSetting_Radio"
+                  >
+                    <div
+                      class="_inline-field-control_19upo_44"
+                    >
+                      <div
+                        class="_container_1e0uz_10"
+                      >
+                        <input
+                          class="_input_1e0uz_18"
+                          id="mx_media_previews_private"
+                          type="radio"
+                        />
+                        <div
+                          class="_ui_1e0uz_19"
+                        />
+                      </div>
+                    </div>
+                    <div
+                      class="_inline-field-body_19upo_38"
+                    >
+                      <label
+                        class="_label_19upo_59"
+                        for="mx_media_previews_private"
+                      >
+                        In private rooms
+                      </label>
+                    </div>
+                  </div>
+                  <div
+                    class="_inline-field_19upo_32 mx_MediaPreviewAccountSetting_Radio"
+                  >
+                    <div
+                      class="_inline-field-control_19upo_44"
+                    >
+                      <div
+                        class="_container_1e0uz_10"
+                      >
+                        <input
+                          checked=""
+                          class="_input_1e0uz_18"
+                          id="mx_media_previews_on"
+                          type="radio"
+                        />
+                        <div
+                          class="_ui_1e0uz_19"
+                        />
+                      </div>
+                    </div>
+                    <div
+                      class="_inline-field-body_19upo_38"
+                    >
+                      <label
+                        class="_label_19upo_59"
+                        for="mx_media_previews_on"
+                      >
+                        Always show
+                      </label>
+                    </div>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div
+              class="_separator_7ckbw_8"
+              data-kind="primary"
+              data-orientation="horizontal"
+              role="separator"
+            />
+          </div>
           <div
             class="mx_SettingsSubsection"
           >
@@ -1190,7 +1325,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_SBSSOZDRlzlA"
+                  for="mx_SettingsFlag_dJJz3lHUv9XX"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1203,7 +1338,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Show NSFW content"
                   class="mx_AccessibleButton mx_ToggleSwitch"
-                  id="mx_SettingsFlag_SBSSOZDRlzlA"
+                  id="mx_SettingsFlag_dJJz3lHUv9XX"
                   role="switch"
                   tabindex="0"
                 >
@@ -1234,7 +1369,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
               >
                 <label
                   class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_FLEpLCb0jpp6"
+                  for="mx_SettingsFlag_SBSSOZDRlzlA"
                 >
                   <span
                     class="mx_SettingsFlag_labelText"
@@ -1247,7 +1382,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
                   aria-disabled="true"
                   aria-label="Prompt before sending invites to potentially invalid matrix IDs"
                   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
-                  id="mx_SettingsFlag_FLEpLCb0jpp6"
+                  id="mx_SettingsFlag_SBSSOZDRlzlA"
                   role="switch"
                   tabindex="0"
                 >
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap
index 6be4e41deebe7e4c04d1ddab53e38cbbcb1ea066..ffa91ae7b11c9b11e5dd08c8f4f6e638498ada99 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SecurityUserSettingsTab-test.tsx.snap
@@ -1,5 +1,31 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`<SecurityUserSettingsTab /> renders ignored users 1`] = `
+<ul
+  aria-label="Ignored users"
+  class="mx_SecurityUserSettingsTab_ignoredUsers"
+>
+  <li
+    aria-label="@bob:example.org"
+    class="mx_SecurityUserSettingsTab_ignoredUser"
+  >
+    <div
+      aria-describedby="mx_SecurityUserSettingsTab_ignoredUser_@bob:example.org"
+      class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_sm"
+      role="button"
+      tabindex="0"
+    >
+      Unignore
+    </div>
+    <span
+      id="mx_SecurityUserSettingsTab_ignoredUser_@bob:example.org"
+    >
+      @bob:example.org
+    </span>
+  </li>
+</ul>
+`;
+
 exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
 <div>
   <div
@@ -8,10 +34,9 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
     <div
       class="mx_SettingsTab_sections"
     >
-      <label
+      <div
         class="mx_SetIntegrationManager"
         data-testid="mx_SetIntegrationManager"
-        for="toggle_integration"
       >
         <div
           class="mx_SettingsFlag"
@@ -30,18 +55,6 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
               (scalar.vector.im)
             </h4>
           </div>
-          <div
-            aria-checked="true"
-            aria-disabled="false"
-            class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
-            id="toggle_integration"
-            role="switch"
-            tabindex="0"
-          >
-            <div
-              class="mx_ToggleSwitch_ball"
-            />
-          </div>
         </div>
         <div
           class="mx_SettingsSubsection_text"
@@ -59,102 +72,54 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
         >
           Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
         </div>
-      </label>
-      <div
-        class="mx_SettingsSection"
-      >
-        <h2
-          class="mx_Heading_h3"
-        >
-          Encryption
-        </h2>
-        <div
-          class="mx_SettingsSection_subSections"
+        <form
+          class="_root_19upo_16"
         >
           <div
-            class="mx_SettingsSubsection"
+            class="_inline-field_19upo_32"
           >
             <div
-              class="mx_SettingsSubsectionHeading"
-            >
-              <h3
-                class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
-              >
-                Secure Backup
-              </h3>
-            </div>
-            <div
-              class="mx_SettingsSubsection_content"
+              class="_inline-field-control_19upo_44"
             >
               <div
-                class="mx_SettingsSubsection_text"
-              >
-                Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
-              </div>
-              <div
-                class="mx_Spinner"
+                class="_container_19o42_10"
               >
+                <input
+                  checked=""
+                  class="_input_19o42_24"
+                  id="mx_SetIntegrationManager_Toggle"
+                  role="switch"
+                  type="checkbox"
+                />
                 <div
-                  aria-label="Loading…"
-                  class="mx_Spinner_icon"
-                  data-testid="spinner"
-                  role="progressbar"
-                  style="width: 32px; height: 32px;"
+                  class="_ui_19o42_34"
                 />
               </div>
-              <details>
-                <summary
-                  class="mx_SecureBackupPanel_advanced"
-                >
-                  Advanced
-                </summary>
-                <table
-                  class="mx_SecureBackupPanel_statusList"
-                >
-                  <tr>
-                    <th
-                      scope="row"
-                    >
-                      Backup key stored:
-                    </th>
-                    <td>
-                      not stored
-                    </td>
-                  </tr>
-                  <tr>
-                    <th
-                      scope="row"
-                    >
-                      Backup key cached:
-                    </th>
-                    <td>
-                      not found locally
-                    </td>
-                  </tr>
-                  <tr>
-                    <th
-                      scope="row"
-                    >
-                      Secret storage public key:
-                    </th>
-                    <td>
-                      not found
-                    </td>
-                  </tr>
-                  <tr>
-                    <th
-                      scope="row"
-                    >
-                      Secret storage:
-                    </th>
-                    <td>
-                      not ready
-                    </td>
-                  </tr>
-                </table>
-              </details>
+            </div>
+            <div
+              class="_inline-field-body_19upo_38"
+            >
+              <label
+                class="_label_19upo_59"
+                for="mx_SetIntegrationManager_Toggle"
+              >
+                Enable the integration manager
+              </label>
             </div>
           </div>
+        </form>
+      </div>
+      <div
+        class="mx_SettingsSection"
+      >
+        <h2
+          class="mx_Heading_h3"
+        >
+          Encryption
+        </h2>
+        <div
+          class="mx_SettingsSection_subSections"
+        >
           <div
             class="mx_SettingsSubsection"
           >
@@ -191,8 +156,22 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
               </div>
             </div>
           </div>
+        </div>
+      </div>
+      <div
+        class="mx_SettingsSection"
+      >
+        <h2
+          class="mx_Heading_h3"
+        >
+          Privacy
+        </h2>
+        <div
+          class="mx_SettingsSection_subSections"
+        >
           <div
             class="mx_SettingsSubsection"
+            data-testid="discoverySection"
           >
             <div
               class="mx_SettingsSubsectionHeading"
@@ -200,192 +179,60 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
               <h3
                 class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
               >
-                Cross-signing
-              </h3>
-            </div>
-            <div
-              class="mx_SettingsSubsection_content"
-            >
-              <div
-                class="mx_Spinner"
-              >
-                <div
-                  aria-label="Loading…"
-                  class="mx_Spinner_icon"
-                  data-testid="spinner"
-                  role="progressbar"
-                  style="width: 32px; height: 32px;"
-                />
-              </div>
-              <details>
-                <summary
-                  class="mx_CrossSigningPanel_advanced"
-                >
-                  Advanced
-                </summary>
-                <table
-                  class="mx_CrossSigningPanel_statusList"
-                >
-                  <tbody>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Cross-signing public keys:
-                      </th>
-                      <td>
-                        not found
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Cross-signing private keys:
-                      </th>
-                      <td>
-                        not found in storage
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Master private key:
-                      </th>
-                      <td>
-                        not found locally
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Self signing private key:
-                      </th>
-                      <td>
-                        not found locally
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        User signing private key:
-                      </th>
-                      <td>
-                        not found locally
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Homeserver feature support:
-                      </th>
-                      <td>
-                        not found
-                      </td>
-                    </tr>
-                  </tbody>
-                </table>
-              </details>
-            </div>
-          </div>
-          <div
-            class="mx_SettingsSubsection"
-          >
-            <div
-              class="mx_SettingsSubsectionHeading"
-            >
-              <h3
-                class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
-              >
-                Cryptography
+                How to find you
               </h3>
             </div>
             <div
-              class="mx_SettingsSubsection_content"
+              class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
             >
-              <div
-                class="mx_SettingsSubsection_text"
+              <fieldset
+                class="mx_SettingsFieldset"
               >
-                <table
-                  class="mx_CryptographyPanel_sessionInfo"
+                <legend
+                  class="mx_SettingsFieldset_legend"
                 >
-                  <tbody>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Session ID:
-                      </th>
-                      <td>
-                        <code />
-                      </td>
-                    </tr>
-                    <tr>
-                      <th
-                        scope="row"
-                      >
-                        Session key:
-                      </th>
-                      <td>
-                        <code>
-                          <strong>
-                            ...
-                          </strong>
-                        </code>
-                      </td>
-                    </tr>
-                  </tbody>
-                </table>
-              </div>
-              <div
-                class="mx_CryptographyPanel_importExportButtons"
-              >
+                  Identity server
+                </legend>
                 <div
-                  class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
-                  role="button"
-                  tabindex="0"
+                  class="mx_SettingsFieldset_description"
                 >
-                  Export E2E room keys
+                  <div
+                    class="mx_SettingsSubsection_text"
+                  >
+                    You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.
+                  </div>
                 </div>
                 <div
-                  class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
-                  role="button"
-                  tabindex="0"
-                >
-                  Import E2E room keys
-                </div>
-              </div>
-              <div
-                class="mx_SettingsFlag"
-              >
-                <label
-                  class="mx_SettingsFlag_label"
-                  for="mx_SettingsFlag_vY7Q4uEh9K38"
+                  class="mx_SettingsFieldset_content"
                 >
-                  <span
-                    class="mx_SettingsFlag_labelText"
+                  <form
+                    class="_root_19upo_16 mx_IdentityServerPicker"
                   >
-                    Never send encrypted messages to unverified sessions from this session
-                  </span>
-                </label>
-                <div
-                  aria-checked="false"
-                  aria-disabled="false"
-                  aria-label="Never send encrypted messages to unverified sessions from this session"
-                  class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
-                  id="mx_SettingsFlag_vY7Q4uEh9K38"
-                  role="switch"
-                  tabindex="0"
-                >
-                  <div
-                    class="mx_ToggleSwitch_ball"
-                  />
+                    <div
+                      class="_field_19upo_26"
+                    >
+                      <label
+                        class="_label_19upo_59"
+                        for="radix-«r1»"
+                      >
+                        Enter a new identity server
+                      </label>
+                      <div
+                        class="_controls_17lij_8"
+                      >
+                        <input
+                          class="_control_sqdq4_10"
+                          id="radix-«r1»"
+                          name="input"
+                          placeholder=""
+                          title=""
+                          value=""
+                        />
+                      </div>
+                    </div>
+                  </form>
                 </div>
-              </div>
+              </fieldset>
             </div>
           </div>
         </div>
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
index 72f94d29c69e21723f75a6d3c364775992da32eb..c417e391565a144897b70aa02e8d114e61d8bbf0 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
@@ -99,7 +99,7 @@ exports[`<SessionManagerTab /> current session section renders current session s
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+          d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
         />
       </svg>
     </div>
@@ -172,7 +172,7 @@ exports[`<SessionManagerTab /> current session section renders current session s
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
             />
           </svg>
         </div>
@@ -247,7 +247,7 @@ exports[`<SessionManagerTab /> current session section renders current session s
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M6 14c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 4 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 6 14Zm6 0c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 12c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 10c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412A1.926 1.926 0 0 1 12 14Zm6 0c-.55 0-1.02-.196-1.413-.588A1.926 1.926 0 0 1 16 12c0-.55.196-1.02.587-1.412A1.926 1.926 0 0 1 18 10c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412 0 .55-.196 1.02-.587 1.412A1.926 1.926 0 0 1 18 14Z"
+          d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
         />
       </svg>
     </div>
@@ -320,7 +320,7 @@ exports[`<SessionManagerTab /> current session section renders current session s
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M12 14.95c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212l-4.6-4.6a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l3.9 3.9 3.9-3.9a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275.948.948 0 0 1 .275.7.948.948 0 0 1-.275.7l-4.6 4.6c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
+              d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
             />
           </svg>
         </div>
@@ -383,28 +383,49 @@ exports[`<SessionManagerTab /> goes to filtered list from security recommendatio
   <span
     tabindex="0"
   >
-    <span
-      class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+    <form
+      class="_root_19upo_16"
     >
-      <input
-        aria-label="Select all"
-        aria-labelledby=":r3s:"
-        data-testid="device-select-all-checkbox"
-        id="device-select-all-checkbox"
-        type="checkbox"
-      />
-      <label
-        for="device-select-all-checkbox"
+      <div
+        class="_inline-field_19upo_32"
       >
         <div
-          class="mx_Checkbox_background"
+          class="_inline-field-control_19upo_44"
         >
           <div
-            class="mx_Checkbox_checkmark"
-          />
+            class="_container_1hel1_10"
+          >
+            <input
+              aria-label="Select all"
+              aria-labelledby="«r4q»"
+              class="_input_1hel1_18"
+              data-testid="device-select-all-checkbox"
+              id="device-select-all-checkbox"
+              type="checkbox"
+            />
+            <div
+              class="_ui_1hel1_19"
+            >
+              <svg
+                aria-hidden="true"
+                fill="currentColor"
+                height="1em"
+                viewBox="0 0 24 24"
+                width="1em"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                />
+              </svg>
+            </div>
+          </div>
         </div>
-      </label>
-    </span>
+        <div
+          class="_inline-field-body_19upo_38"
+        />
+      </div>
+    </form>
   </span>
   <span
     class="mx_FilteredDeviceListHeader_label"
diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SidebarUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SidebarUserSettingsTab-test.tsx.snap
index cd87bbb16539930d26f677e941f21d3a07846b40..0557761e2b5702ae547b8b7168a1ca02a93326d4 100644
--- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SidebarUserSettingsTab-test.tsx.snap
+++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/SidebarUserSettingsTab-test.tsx.snap
@@ -38,30 +38,53 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
             <div
               class="mx_SettingsSubsection_content"
             >
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_vY7Q4uEh9K"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_vY7Q4uEh9K"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r1»"
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_vY7Q4uEh9K"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_vY7Q4uEh9K"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -69,73 +92,120 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="m12.971 3.54 7 3.889A2 2 0 0 1 21 9.177V19a2 2 0 0 1-2 2h-4v-9H9v9H5a2 2 0 0 1-2-2V9.177a2 2 0 0 1 1.029-1.748l7-3.89a2 2 0 0 1 1.942 0Z"
+                          d="m12.971 3.54 7 3.889A2 2 0 0 1 21 9.177V19a2 2 0 0 1-2 2h-4v-9H9v9H5a2 2 0 0 1-2-2V9.177a2 2 0 0 1 1.029-1.748l7-3.89a2 2 0 0 1 1.942 0"
                         />
                       </svg>
                       Home
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r1»"
                     >
                       Home is useful for getting an overview of everything.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
-                  id="checkbox_38QgU2Pomx"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_38QgU2Pomx"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r5»"
+                        class="_input_1hel1_18"
+                        data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
+                        id="checkbox_38QgU2Pomx"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_38QgU2Pomx"
                     >
                       Show all rooms
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r5»"
                     >
                       Show all your rooms in Home, even if they're in a space.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_wKpa6hpi3Y"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_wKpa6hpi3Y"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r9»"
+                        class="_input_1hel1_18"
+                        id="checkbox_wKpa6hpi3Y"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_wKpa6hpi3Y"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -143,41 +213,65 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0Z"
+                          d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0"
                         />
                       </svg>
                       Favourites
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r9»"
                     >
                       Group all your favourite rooms and people in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_EetmBG4yVC"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_EetmBG4yVC"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rd»"
+                        class="_input_1hel1_18"
+                        id="checkbox_EetmBG4yVC"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_EetmBG4yVC"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -185,76 +279,122 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M12 15c-1.1 0-2.042-.392-2.825-1.175C8.392 13.042 8 12.1 8 11s.392-2.042 1.175-2.825C9.958 7.392 10.9 7 12 7s2.042.392 2.825 1.175C15.608 8.958 16 9.9 16 11s-.392 2.042-1.175 2.825C14.042 14.608 13.1 15 12 15Z"
+                          d="M12 15q-1.65 0-2.825-1.175T8 11t1.175-2.825T12 7t2.825 1.175T16 11t-1.175 2.825T12 15"
                         />
                         <path
-                          d="M19.528 18.583A9.962 9.962 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.976 9.976 0 0 0 12 22a9.976 9.976 0 0 0 7.528-3.417ZM8.75 16.388c-.915.221-1.818.538-2.709.95a8 8 0 1 1 11.918 0 14.679 14.679 0 0 0-2.709-.95A13.76 13.76 0 0 0 12 16c-1.1 0-2.183.13-3.25.387Z"
+                          d="M19.528 18.583A9.96 9.96 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.98 9.98 0 0 0 12 22a9.98 9.98 0 0 0 7.528-3.417M8.75 16.388q-1.373.332-2.709.95a8 8 0 1 1 11.918 0 14.7 14.7 0 0 0-2.709-.95A13.8 13.8 0 0 0 12 16q-1.65 0-3.25.387"
                         />
                       </svg>
                       People
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rd»"
                     >
                       Group all your people in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_eEefiPqpMR"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_eEefiPqpMR"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rh»"
+                        class="_input_1hel1_18"
+                        id="checkbox_eEefiPqpMR"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_eEefiPqpMR"
                     >
-                      <div />
                       Rooms outside of a space
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rh»"
                     >
                       Group all your rooms that aren't part of a space in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_MwbPDmfGtm"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_MwbPDmfGtm"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rl»"
+                        class="_input_1hel1_18"
+                        id="checkbox_MwbPDmfGtm"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_MwbPDmfGtm"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -262,19 +402,20 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+                          d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
                         />
                       </svg>
                       Video rooms and conferences
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rl»"
                     >
                       Group all private video rooms and conferences. In conferences you can invite people outside of matrix.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
+                </div>
+              </form>
             </div>
           </div>
         </div>
@@ -322,30 +463,53 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
             <div
               class="mx_SettingsSubsection_content"
             >
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  checked=""
-                  disabled=""
-                  id="checkbox_vY7Q4uEh9K"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_vY7Q4uEh9K"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rp»"
+                        checked=""
+                        class="_input_1hel1_18"
+                        disabled=""
+                        id="checkbox_vY7Q4uEh9K"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_vY7Q4uEh9K"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -353,73 +517,120 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="m12.971 3.54 7 3.889A2 2 0 0 1 21 9.177V19a2 2 0 0 1-2 2h-4v-9H9v9H5a2 2 0 0 1-2-2V9.177a2 2 0 0 1 1.029-1.748l7-3.89a2 2 0 0 1 1.942 0Z"
+                          d="m12.971 3.54 7 3.889A2 2 0 0 1 21 9.177V19a2 2 0 0 1-2 2h-4v-9H9v9H5a2 2 0 0 1-2-2V9.177a2 2 0 0 1 1.029-1.748l7-3.89a2 2 0 0 1 1.942 0"
                         />
                       </svg>
                       Home
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rp»"
                     >
                       Home is useful for getting an overview of everything.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
-                  id="checkbox_38QgU2Pomx"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_38QgU2Pomx"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«rt»"
+                        class="_input_1hel1_18"
+                        data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
+                        id="checkbox_38QgU2Pomx"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_38QgU2Pomx"
                     >
                       Show all rooms
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«rt»"
                     >
                       Show all your rooms in Home, even if they're in a space.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_wKpa6hpi3Y"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_wKpa6hpi3Y"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r11»"
+                        class="_input_1hel1_18"
+                        id="checkbox_wKpa6hpi3Y"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_wKpa6hpi3Y"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -427,41 +638,65 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0Z"
+                          d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0"
                         />
                       </svg>
                       Favourites
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r11»"
                     >
                       Group all your favourite rooms and people in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_EetmBG4yVC"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_EetmBG4yVC"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r15»"
+                        class="_input_1hel1_18"
+                        id="checkbox_EetmBG4yVC"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_EetmBG4yVC"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -469,76 +704,122 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M12 15c-1.1 0-2.042-.392-2.825-1.175C8.392 13.042 8 12.1 8 11s.392-2.042 1.175-2.825C9.958 7.392 10.9 7 12 7s2.042.392 2.825 1.175C15.608 8.958 16 9.9 16 11s-.392 2.042-1.175 2.825C14.042 14.608 13.1 15 12 15Z"
+                          d="M12 15q-1.65 0-2.825-1.175T8 11t1.175-2.825T12 7t2.825 1.175T16 11t-1.175 2.825T12 15"
                         />
                         <path
-                          d="M19.528 18.583A9.962 9.962 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.976 9.976 0 0 0 12 22a9.976 9.976 0 0 0 7.528-3.417ZM8.75 16.388c-.915.221-1.818.538-2.709.95a8 8 0 1 1 11.918 0 14.679 14.679 0 0 0-2.709-.95A13.76 13.76 0 0 0 12 16c-1.1 0-2.183.13-3.25.387Z"
+                          d="M19.528 18.583A9.96 9.96 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.98 9.98 0 0 0 12 22a9.98 9.98 0 0 0 7.528-3.417M8.75 16.388q-1.373.332-2.709.95a8 8 0 1 1 11.918 0 14.7 14.7 0 0 0-2.709-.95A13.8 13.8 0 0 0 12 16q-1.65 0-3.25.387"
                         />
                       </svg>
                       People
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r15»"
                     >
                       Group all your people in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_eEefiPqpMR"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_eEefiPqpMR"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r19»"
+                        class="_input_1hel1_18"
+                        id="checkbox_eEefiPqpMR"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_eEefiPqpMR"
                     >
-                      <div />
                       Rooms outside of a space
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r19»"
                     >
                       Group all your rooms that aren't part of a space in one place.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
-              <span
-                class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
+                </div>
+              </form>
+              <form
+                class="_root_19upo_16"
               >
-                <input
-                  id="checkbox_MwbPDmfGtm"
-                  type="checkbox"
-                />
-                <label
-                  for="checkbox_MwbPDmfGtm"
+                <div
+                  class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
                 >
                   <div
-                    class="mx_Checkbox_background"
+                    class="_inline-field-control_19upo_44"
                   >
                     <div
-                      class="mx_Checkbox_checkmark"
-                    />
+                      class="_container_1hel1_10"
+                    >
+                      <input
+                        aria-describedby="«r1d»"
+                        class="_input_1hel1_18"
+                        id="checkbox_MwbPDmfGtm"
+                        type="checkbox"
+                      />
+                      <div
+                        class="_ui_1hel1_19"
+                      >
+                        <svg
+                          aria-hidden="true"
+                          fill="currentColor"
+                          height="1em"
+                          viewBox="0 0 24 24"
+                          width="1em"
+                          xmlns="http://www.w3.org/2000/svg"
+                        >
+                          <path
+                            d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
+                          />
+                        </svg>
+                      </div>
+                    </div>
                   </div>
-                  <div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                  <div
+                    class="_inline-field-body_19upo_38"
+                  >
+                    <label
+                      class="_label_19upo_59"
+                      for="checkbox_MwbPDmfGtm"
                     >
                       <svg
+                        class="mx_SidebarUserSettingsTab_icon"
                         fill="currentColor"
                         height="1em"
                         viewBox="0 0 24 24"
@@ -546,19 +827,20 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
                         xmlns="http://www.w3.org/2000/svg"
                       >
                         <path
-                          d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
+                          d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
                         />
                       </svg>
                       Video rooms and conferences
-                    </div>
-                    <div
-                      class="mx_SettingsSubsection_text"
+                    </label>
+                    <span
+                      class="_message_19upo_85 _help-message_19upo_91"
+                      id="«r1d»"
                     >
                       Group all private video rooms and conferences.
-                    </div>
+                    </span>
                   </div>
-                </label>
-              </span>
+                </div>
+              </form>
             </div>
           </div>
         </div>
diff --git a/test/unit-tests/components/views/spaces/AddExistingToSpaceDialog-test.tsx b/test/unit-tests/components/views/spaces/AddExistingToSpaceDialog-test.tsx
index c06847e700556af1056782aaec1e48a5cb859a0f..c450f607d763569bde867259cdfff7d7b4734475 100644
--- a/test/unit-tests/components/views/spaces/AddExistingToSpaceDialog-test.tsx
+++ b/test/unit-tests/components/views/spaces/AddExistingToSpaceDialog-test.tsx
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { render } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import React from "react";
 
 import AddExistingToSpaceDialog from "../../../../../src/components/views/dialogs/AddExistingToSpaceDialog";
diff --git a/test/unit-tests/components/views/spaces/SpacePanel-test.tsx b/test/unit-tests/components/views/spaces/SpacePanel-test.tsx
index 3331de933414b5ea5f7e037b421caa0c984850ce..496b160130e142faa086c9d4ec537ce890b2fc27 100644
--- a/test/unit-tests/components/views/spaces/SpacePanel-test.tsx
+++ b/test/unit-tests/components/views/spaces/SpacePanel-test.tsx
@@ -9,17 +9,17 @@ Please see LICENSE files in the repository root for full details.
 import React from "react";
 import { render, screen, fireEvent, act, cleanup } from "jest-matrix-react";
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
-import { MetaSpace, SpaceKey } from "../../../../../src/stores/spaces";
+import { MetaSpace, type SpaceKey } from "../../../../../src/stores/spaces";
 import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
 import { UIComponent } from "../../../../../src/settings/UIFeature";
 import { mkStubRoom, wrapInMatrixClientContext, wrapInSdkContext } from "../../../../test-utils";
 import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
-import { SpaceNotificationState } from "../../../../../src/stores/notifications/SpaceNotificationState";
+import { type SpaceNotificationState } from "../../../../../src/stores/notifications/SpaceNotificationState";
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import UnwrappedSpacePanel from "../../../../../src/components/views/spaces/SpacePanel";
 
diff --git a/test/unit-tests/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx b/test/unit-tests/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx
index f16651abd936e499249648c4fd9c147aa5413ceb..a8b4dd895c42d5c21629ee6cd5bf63e7cdeb7389 100644
--- a/test/unit-tests/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx
+++ b/test/unit-tests/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx
@@ -8,9 +8,16 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { mocked } from "jest-mock";
-import { randomString } from "matrix-js-sdk/src/randomstring";
-import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
-import { EventType, MatrixClient, Room, GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/matrix";
+import { secureRandomString } from "matrix-js-sdk/src/randomstring";
+import { act, fireEvent, render, type RenderResult } from "jest-matrix-react";
+import {
+    EventType,
+    type MatrixClient,
+    type Room,
+    GuestAccess,
+    HistoryVisibility,
+    JoinRule,
+} from "matrix-js-sdk/src/matrix";
 
 import _SpaceSettingsVisibilityTab from "../../../../../src/components/views/spaces/SpaceSettingsVisibilityTab";
 import {
@@ -92,7 +99,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
 
     beforeEach(() => {
         let i = 0;
-        mocked(randomString).mockImplementation(() => {
+        mocked(secureRandomString).mockImplementation(() => {
             return "testid_" + i++;
         });
 
diff --git a/test/unit-tests/components/views/spaces/SpaceTreeLevel-test.tsx b/test/unit-tests/components/views/spaces/SpaceTreeLevel-test.tsx
index 98dc9723abb3a5045abf18bc271c827eba9fc30e..c46b9681a9956a536de3d7896594fc107e7d73b9 100644
--- a/test/unit-tests/components/views/spaces/SpaceTreeLevel-test.tsx
+++ b/test/unit-tests/components/views/spaces/SpaceTreeLevel-test.tsx
@@ -15,7 +15,7 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
 import { Action } from "../../../../../src/dispatcher/actions";
 import { SpaceButton } from "../../../../../src/components/views/spaces/SpaceTreeLevel";
-import { MetaSpace, SpaceKey } from "../../../../../src/stores/spaces";
+import { MetaSpace, type SpaceKey } from "../../../../../src/stores/spaces";
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 import { StaticNotificationState } from "../../../../../src/stores/notifications/StaticNotificationState";
 import { NotificationLevel } from "../../../../../src/stores/notifications/NotificationLevel";
diff --git a/test/unit-tests/components/views/spaces/ThreadsActivityCentre-test.tsx b/test/unit-tests/components/views/spaces/ThreadsActivityCentre-test.tsx
index ecbe550d600c969dd5ba688830a1cb214320c074..ed03dc601268dcd4aebffb8f89e1c4612844e0ad 100644
--- a/test/unit-tests/components/views/spaces/ThreadsActivityCentre-test.tsx
+++ b/test/unit-tests/components/views/spaces/ThreadsActivityCentre-test.tsx
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import React, { ComponentProps } from "react";
+import React, { type ComponentProps } from "react";
 import { getByText, render, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
 import { NotificationCountType, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap b/test/unit-tests/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap
index 8a40342a6b5a2f0bb47be011abeff3c060fce33b..c3d3134e68976d4143361f5e0b59e06ae277b085 100644
--- a/test/unit-tests/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/spaces/__snapshots__/AddExistingToSpaceDialog-test.tsx.snap
@@ -26,7 +26,7 @@ exports[`<AddExistingToSpaceDialog /> looks as expected 1`] = `
         >
           <span
             aria-label="Avatar"
-            class="_avatar_mcap2_17 mx_BaseAvatar"
+            class="_avatar_1qbcf_8 mx_BaseAvatar"
             data-color="2"
             data-testid="avatar-img"
             data-type="square"
@@ -34,7 +34,7 @@ exports[`<AddExistingToSpaceDialog /> looks as expected 1`] = `
           >
             <img
               alt=""
-              class="_image_mcap2_50"
+              class="_image_1qbcf_41"
               data-type="square"
               height="40px"
               loading="lazy"
diff --git a/test/unit-tests/components/views/spaces/__snapshots__/SpacePanel-test.tsx.snap b/test/unit-tests/components/views/spaces/__snapshots__/SpacePanel-test.tsx.snap
index 6aee8d43c40292c0326191410c7ac1f6d6cb8c73..5086a8aafcb6450b82d3db66b2a8d747b8724425 100644
--- a/test/unit-tests/components/views/spaces/__snapshots__/SpacePanel-test.tsx.snap
+++ b/test/unit-tests/components/views/spaces/__snapshots__/SpacePanel-test.tsx.snap
@@ -8,7 +8,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
   >
     <div
       class="mx_UserMenu"
-      data-floating-ui-inert=""
     >
       <div
         aria-expanded="false"
@@ -22,7 +21,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
           class="mx_UserMenu_userAvatar"
         >
           <span
-            class="_avatar_mcap2_17 mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar _avatar-imageless_mcap2_61"
+            class="_avatar_1qbcf_8 mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar _avatar-imageless_1qbcf_52"
             data-color="5"
             data-testid="avatar-img"
             data-type="round"
@@ -43,7 +42,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
     <ul
       aria-label="Spaces"
       class="mx_AutoHideScrollbar mx_SpaceTreeLevel"
-      data-floating-ui-inert=""
       data-rbd-droppable-context-id="0"
       data-rbd-droppable-id="top-level-spaces"
       role="tree"
@@ -231,20 +229,21 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
       class="mx_ThreadsActivityCentre_container"
     >
       <button
-        aria-controls=":r12:"
-        aria-describedby=":r12:"
-        aria-expanded="true"
-        aria-haspopup="dialog"
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
         aria-label="Threads"
-        aria-labelledby=":r14:"
-        class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
-        data-floating-ui-inert=""
+        aria-labelledby="«r12»"
+        class="_icon-button_m2erp_8 mx_ThreadsActivityCentreButton"
+        data-state="closed"
+        id="radix-«r10»"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
+        type="button"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -256,42 +255,16 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
-      <span
-        aria-hidden="true"
-        data-floating-ui-inert=""
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="-1"
-      />
-      <span
-        data-floating-ui-focus-guard=""
-        data-type="outside"
-        role="button"
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="0"
-      />
-      <span
-        aria-owns=":r19:"
-        data-floating-ui-inert=""
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-      />
-      <span
-        data-floating-ui-focus-guard=""
-        data-type="outside"
-        role="button"
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="0"
-      />
     </div>
     <div
       aria-expanded="false"
       aria-label="Quick settings"
       class="mx_AccessibleButton mx_QuickSettingsButton"
-      data-floating-ui-inert=""
       role="button"
       tabindex="0"
     />
diff --git a/test/unit-tests/components/views/spaces/__snapshots__/SpaceSettingsVisibilityTab-test.tsx.snap b/test/unit-tests/components/views/spaces/__snapshots__/SpaceSettingsVisibilityTab-test.tsx.snap
index 16528bce192bd371449130548e765b12aaa2d21e..9e9ab218bc647d6f4adba52a433e90e04d1f3f3d 100644
--- a/test/unit-tests/components/views/spaces/__snapshots__/SpaceSettingsVisibilityTab-test.tsx.snap
+++ b/test/unit-tests/components/views/spaces/__snapshots__/SpaceSettingsVisibilityTab-test.tsx.snap
@@ -4,7 +4,7 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space Access renders guest
 <div
   aria-checked="true"
   aria-disabled="false"
-  aria-labelledby="mx_LabelledToggleSwitch_testid_1"
+  aria-labelledby="mx_LabelledToggleSwitch_«rb»"
   class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
   role="switch"
   tabindex="0"
@@ -122,7 +122,7 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
                     class="mx_SettingsFlag_label"
                   >
                     <div
-                      id="mx_LabelledToggleSwitch_testid_0"
+                      id="mx_LabelledToggleSwitch_«r0»"
                     >
                       Preview Space
                     </div>
@@ -130,7 +130,7 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
                   <div
                     aria-checked="true"
                     aria-disabled="false"
-                    aria-labelledby="mx_LabelledToggleSwitch_testid_0"
+                    aria-labelledby="mx_LabelledToggleSwitch_«r0»"
                     class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
                     role="switch"
                     tabindex="0"
diff --git a/test/unit-tests/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap b/test/unit-tests/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap
index b383932993fe04a08094f07cfaff5e9c2e1db458..41eefaf2772e1cad75fb7e7201ef1fa2d2da914a 100644
--- a/test/unit-tests/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap
+++ b/test/unit-tests/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap
@@ -2,23 +2,23 @@
 
 exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] = `
 <div
-  aria-labelledby="radix-:r33:"
+  aria-labelledby="radix-«r2f»"
   aria-orientation="vertical"
-  class="_menu_1x5h1_17"
+  class="_menu_19sse_8"
   data-align="start"
   data-orientation="vertical"
   data-radix-menu-content=""
   data-side="top"
   data-state="open"
   dir="ltr"
-  id="radix-:r34:"
+  id="radix-«r2g»"
   role="menu"
   style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
   tabindex="-1"
 >
   <h3
-    class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
-    id=":r3b:"
+    class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 _menu-title_1sgvx_8 _title_19sse_74"
+    id="«r2n»"
   >
     Threads activity
   </h3>
@@ -26,7 +26,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
     class="mx_ThreadsActivityCentre_rows"
   >
     <button
-      class="mx_ThreadsActivityCentreRow _item_8j2l6_17 _interactive_8j2l6_35"
+      class="mx_ThreadsActivityCentreRow _item_dyt4i_8 _interactive_dyt4i_26"
       data-kind="primary"
       data-orientation="vertical"
       data-radix-collection-item=""
@@ -34,10 +34,10 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
       tabindex="-1"
     >
       <div
-        class="_icon_8j2l6_43"
+        class="_icon_dyt4i_50"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="3"
           data-testid="avatar-img"
           data-type="round"
@@ -48,13 +48,13 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
         </span>
       </div>
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
       >
         This is a real highlight
       </span>
       <svg
         aria-hidden="true"
-        class="_nav-hint_8j2l6_59"
+        class="_nav-hint_dyt4i_59"
         fill="currentColor"
         height="24"
         viewBox="8 0 8 24"
@@ -62,7 +62,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+          d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
         />
       </svg>
       <div
@@ -74,7 +74,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
       </div>
     </button>
     <button
-      class="mx_ThreadsActivityCentreRow _item_8j2l6_17 _interactive_8j2l6_35"
+      class="mx_ThreadsActivityCentreRow _item_dyt4i_8 _interactive_dyt4i_26"
       data-kind="primary"
       data-orientation="vertical"
       data-radix-collection-item=""
@@ -82,10 +82,10 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
       tabindex="-1"
     >
       <div
-        class="_icon_8j2l6_43"
+        class="_icon_dyt4i_50"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="2"
           data-testid="avatar-img"
           data-type="round"
@@ -96,13 +96,13 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
         </span>
       </div>
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
       >
         A notification
       </span>
       <svg
         aria-hidden="true"
-        class="_nav-hint_8j2l6_59"
+        class="_nav-hint_dyt4i_59"
         fill="currentColor"
         height="24"
         viewBox="8 0 8 24"
@@ -110,7 +110,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+          d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
         />
       </svg>
       <div
@@ -145,22 +145,22 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
       class="mx_ThreadsActivityCentre_container"
     >
       <button
-        aria-controls="radix-:r1d:"
+        aria-controls="radix-«rp»"
         aria-disabled="false"
         aria-expanded="true"
         aria-haspopup="menu"
         aria-label="Threads"
-        aria-labelledby=":r1e:"
-        class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
+        aria-labelledby="«rq»"
+        class="_icon-button_m2erp_8 mx_ThreadsActivityCentreButton"
         data-state="open"
-        id="radix-:r1c:"
+        id="radix-«ro»"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
         type="button"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -172,7 +172,7 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
@@ -183,17 +183,17 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
     aria-hidden="true"
     data-aria-hidden="true"
     data-floating-ui-portal=""
-    id=":r1i:"
+    id="«ru»"
   >
     <div
-      class="_tooltip_1pslb_17 _invisible_1pslb_30"
+      class="_tooltip_6ode6_8 _invisible_6ode6_21"
       data-floating-ui-focusable=""
       style="position: absolute; left: 0px; top: 0px; transform: translate(6px, 5px);"
       tabindex="-1"
     >
       <svg
         aria-hidden="true"
-        class="_arrow_1pslb_42"
+        class="_arrow_6ode6_33"
         height="10"
         style="position: absolute; pointer-events: none; right: calc(100% - 0px); transform: rotate(90deg); top: -1px;"
         viewBox="0 0 10 10"
@@ -204,7 +204,7 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
           stroke="none"
         />
         <clippath
-          id=":r1j:"
+          id="«rv»"
         >
           <rect
             height="10"
@@ -215,7 +215,7 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
         </clippath>
       </svg>
       <span
-        id=":r1e:"
+        id="«rq»"
       >
         Threads
       </span>
@@ -224,26 +224,26 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
   <div
     data-radix-popper-content-wrapper=""
     dir="ltr"
-    style="position: fixed; left: 0px; top: 0px; transform: translate(0px, -8px); min-width: max-content; --radix-popper-available-width: 0px; --radix-popper-available-height: -8px; --radix-popper-anchor-width: 0px; --radix-popper-anchor-height: 0px; --radix-popper-transform-origin: 0% 0px;"
+    style="position: fixed; left: 0px; top: 0px; transform: translate(0px, -8px); min-width: max-content; --radix-popper-transform-origin: 0% 0px; --radix-popper-available-width: 0px; --radix-popper-available-height: -8px; --radix-popper-anchor-width: 0px; --radix-popper-anchor-height: 0px;"
   >
     <div
-      aria-labelledby="radix-:r1c:"
+      aria-labelledby="radix-«ro»"
       aria-orientation="vertical"
-      class="_menu_1x5h1_17"
+      class="_menu_19sse_8"
       data-align="start"
       data-orientation="vertical"
       data-radix-menu-content=""
       data-side="top"
       data-state="open"
       dir="ltr"
-      id="radix-:r1d:"
+      id="radix-«rp»"
       role="menu"
       style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
       tabindex="-1"
     >
       <h3
-        class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
-        id=":r1k:"
+        class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 _menu-title_1sgvx_8 _title_19sse_74"
+        id="«r10»"
       >
         Threads activity
       </h3>
@@ -270,23 +270,23 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
 
 exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
 <div
-  aria-labelledby="radix-:r3n:"
+  aria-labelledby="radix-«r33»"
   aria-orientation="vertical"
-  class="_menu_1x5h1_17"
+  class="_menu_19sse_8"
   data-align="start"
   data-orientation="vertical"
   data-radix-menu-content=""
   data-side="top"
   data-state="open"
   dir="ltr"
-  id="radix-:r3o:"
+  id="radix-«r34»"
   role="menu"
   style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
   tabindex="-1"
 >
   <h3
-    class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
-    id=":r3v:"
+    class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 _menu-title_1sgvx_8 _title_19sse_74"
+    id="«r3b»"
   >
     Threads activity
   </h3>
@@ -304,23 +304,23 @@ exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
 
 exports[`ThreadsActivityCentre should order the room with the same notification level by most recent 1`] = `
 <div
-  aria-labelledby="radix-:r40:"
+  aria-labelledby="radix-«r3c»"
   aria-orientation="vertical"
-  class="_menu_1x5h1_17"
+  class="_menu_19sse_8"
   data-align="start"
   data-orientation="vertical"
   data-radix-menu-content=""
   data-side="top"
   data-state="open"
   dir="ltr"
-  id="radix-:r41:"
+  id="radix-«r3d»"
   role="menu"
   style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
   tabindex="-1"
 >
   <h3
-    class="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _title_1x5h1_83"
-    id=":r48:"
+    class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 _menu-title_1sgvx_8 _title_19sse_74"
+    id="«r3k»"
   >
     Threads activity
   </h3>
@@ -328,7 +328,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
     class="mx_ThreadsActivityCentre_rows"
   >
     <button
-      class="mx_ThreadsActivityCentreRow _item_8j2l6_17 _interactive_8j2l6_35"
+      class="mx_ThreadsActivityCentreRow _item_dyt4i_8 _interactive_dyt4i_26"
       data-kind="primary"
       data-orientation="vertical"
       data-radix-collection-item=""
@@ -336,10 +336,10 @@ exports[`ThreadsActivityCentre should order the room with the same notification
       tabindex="-1"
     >
       <div
-        class="_icon_8j2l6_43"
+        class="_icon_dyt4i_50"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="5"
           data-testid="avatar-img"
           data-type="round"
@@ -350,13 +350,13 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         </span>
       </div>
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
       >
         This is a third real highlight
       </span>
       <svg
         aria-hidden="true"
-        class="_nav-hint_8j2l6_59"
+        class="_nav-hint_dyt4i_59"
         fill="currentColor"
         height="24"
         viewBox="8 0 8 24"
@@ -364,7 +364,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+          d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
         />
       </svg>
       <div
@@ -376,7 +376,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
       </div>
     </button>
     <button
-      class="mx_ThreadsActivityCentreRow _item_8j2l6_17 _interactive_8j2l6_35"
+      class="mx_ThreadsActivityCentreRow _item_dyt4i_8 _interactive_dyt4i_26"
       data-kind="primary"
       data-orientation="vertical"
       data-radix-collection-item=""
@@ -384,10 +384,10 @@ exports[`ThreadsActivityCentre should order the room with the same notification
       tabindex="-1"
     >
       <div
-        class="_icon_8j2l6_43"
+        class="_icon_dyt4i_50"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="3"
           data-testid="avatar-img"
           data-type="round"
@@ -398,13 +398,13 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         </span>
       </div>
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
       >
         This is a real highlight
       </span>
       <svg
         aria-hidden="true"
-        class="_nav-hint_8j2l6_59"
+        class="_nav-hint_dyt4i_59"
         fill="currentColor"
         height="24"
         viewBox="8 0 8 24"
@@ -412,7 +412,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+          d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
         />
       </svg>
       <div
@@ -424,7 +424,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
       </div>
     </button>
     <button
-      class="mx_ThreadsActivityCentreRow _item_8j2l6_17 _interactive_8j2l6_35"
+      class="mx_ThreadsActivityCentreRow _item_dyt4i_8 _interactive_dyt4i_26"
       data-kind="primary"
       data-orientation="vertical"
       data-radix-collection-item=""
@@ -432,10 +432,10 @@ exports[`ThreadsActivityCentre should order the room with the same notification
       tabindex="-1"
     >
       <div
-        class="_icon_8j2l6_43"
+        class="_icon_dyt4i_50"
       >
         <span
-          class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
+          class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
           data-color="4"
           data-testid="avatar-img"
           data-type="round"
@@ -446,13 +446,13 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         </span>
       </div>
       <span
-        class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 _label_8j2l6_52"
+        class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_dyt4i_34"
       >
         This is a second real highlight
       </span>
       <svg
         aria-hidden="true"
-        class="_nav-hint_8j2l6_59"
+        class="_nav-hint_dyt4i_59"
         fill="currentColor"
         height="24"
         viewBox="8 0 8 24"
@@ -460,7 +460,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
         xmlns="http://www.w3.org/2000/svg"
       >
         <path
-          d="M8.7 17.3a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7l3.9-3.9-3.9-3.9a.948.948 0 0 1-.275-.7.95.95 0 0 1 .275-.7.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275l4.6 4.6c.1.1.17.208.213.325.041.117.062.242.062.375s-.02.258-.063.375a.877.877 0 0 1-.212.325l-4.6 4.6a.948.948 0 0 1-.7.275.948.948 0 0 1-.7-.275Z"
+          d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
         />
       </svg>
       <div
@@ -482,20 +482,21 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
       class="mx_ThreadsActivityCentre_container"
     >
       <button
-        aria-controls=":ra:"
-        aria-describedby=":ra:"
-        aria-expanded="true"
-        aria-haspopup="dialog"
+        aria-disabled="false"
+        aria-expanded="false"
+        aria-haspopup="menu"
         aria-label="Threads"
-        aria-labelledby=":rc:"
-        class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
-        data-floating-ui-inert=""
+        aria-labelledby="«ra»"
+        class="_icon-button_m2erp_8 mx_ThreadsActivityCentreButton"
+        data-state="closed"
+        id="radix-«r8»"
         role="button"
         style="--cpd-icon-button-size: 32px;"
         tabindex="0"
+        type="button"
       >
         <div
-          class="_indicator-icon_133tf_26"
+          class="_indicator-icon_zr2a0_17"
           style="--cpd-icon-button-size: 100%;"
         >
           <svg
@@ -507,52 +508,26 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
+              d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
             />
           </svg>
         </div>
       </button>
-      <span
-        aria-hidden="true"
-        data-floating-ui-inert=""
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="-1"
-      />
-      <span
-        data-floating-ui-focus-guard=""
-        data-type="outside"
-        role="button"
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="0"
-      />
-      <span
-        aria-owns=":rh:"
-        data-floating-ui-inert=""
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-      />
-      <span
-        data-floating-ui-focus-guard=""
-        data-type="outside"
-        role="button"
-        style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-        tabindex="0"
-      />
     </div>
   </div>
   <div
-    data-floating-ui-inert=""
     data-floating-ui-portal=""
-    id=":rg:"
+    id="«re»"
   >
     <div
-      class="_tooltip_1pslb_17 _invisible_1pslb_30"
+      class="_tooltip_6ode6_8 _invisible_6ode6_21"
       data-floating-ui-focusable=""
       style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
       tabindex="-1"
     >
       <svg
         aria-hidden="true"
-        class="_arrow_1pslb_42"
+        class="_arrow_6ode6_33"
         height="10"
         style="position: absolute; pointer-events: none; right: calc(100% - 0px); transform: rotate(90deg);"
         viewBox="0 0 10 10"
@@ -563,7 +538,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
           stroke="none"
         />
         <clippath
-          id=":ri:"
+          id="«rf»"
         >
           <rect
             height="10"
@@ -574,85 +549,11 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
         </clippath>
       </svg>
       <span
-        id=":rc:"
+        id="«ra»"
       >
         Threads
       </span>
     </div>
   </div>
-  <div
-    data-floating-ui-portal=""
-    id=":rh:"
-  >
-    <span
-      data-floating-ui-focus-guard=""
-      data-type="inside"
-      role="button"
-      style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-      tabindex="0"
-    />
-    <div
-      aria-describedby=":r9:"
-      aria-labelledby=":r8:"
-      class="_content_1oa1y_17"
-      data-floating-ui-focusable=""
-      id=":ra:"
-      role="dialog"
-      style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
-      tabindex="-1"
-    >
-      <svg
-        aria-hidden="true"
-        class="_arrow_1oa1y_62"
-        height="20"
-        style="position: absolute; pointer-events: none; right: calc(100% - 0px); transform: rotate(90deg);"
-        viewBox="0 0 20 20"
-        width="20"
-      >
-        <path
-          d="M0,0 H20 L10,12 Q10,12 10,12 Z"
-          stroke="none"
-        />
-        <clippath
-          id=":rj:"
-        >
-          <rect
-            height="20"
-            width="20"
-            x="0"
-            y="0"
-          />
-        </clippath>
-      </svg>
-      <h3
-        class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 _header_1oa1y_46"
-        id=":r8:"
-      >
-        Threads Activity Centre
-      </h3>
-      <span
-        class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _description_1oa1y_52"
-        id=":r9:"
-      >
-        Threads notifications have moved, find them here from now on.
-      </span>
-      <button
-        class="_button_i91xf_17 _button_1oa1y_57"
-        data-kind="secondary"
-        data-size="sm"
-        role="button"
-        tabindex="0"
-      >
-        OK
-      </button>
-    </div>
-    <span
-      data-floating-ui-focus-guard=""
-      data-type="inside"
-      role="button"
-      style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
-      tabindex="0"
-    />
-  </div>
 </body>
 `;
diff --git a/test/unit-tests/components/views/spaces/useUnreadThreadRooms-test.tsx b/test/unit-tests/components/views/spaces/useUnreadThreadRooms-test.tsx
index f8e32289e9a741a5a0bfcefbec85bbe1077fee49..d2c5a74247e18708a466248d516702678d6201c0 100644
--- a/test/unit-tests/components/views/spaces/useUnreadThreadRooms-test.tsx
+++ b/test/unit-tests/components/views/spaces/useUnreadThreadRooms-test.tsx
@@ -8,7 +8,7 @@
 
 import React from "react";
 import {
-    MatrixClient,
+    type MatrixClient,
     MatrixEventEvent,
     NotificationCountType,
     PendingEventOrdering,
diff --git a/test/unit-tests/components/views/toasts/GenericToast-test.tsx b/test/unit-tests/components/views/toasts/GenericToast-test.tsx
index 5947dcdbc6cc71aa6af72f2cd216e167580f1415..e40e9141771244d8efa89b11706be897390fa247 100644
--- a/test/unit-tests/components/views/toasts/GenericToast-test.tsx
+++ b/test/unit-tests/components/views/toasts/GenericToast-test.tsx
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { render, RenderResult } from "jest-matrix-react";
-import React, { ComponentProps } from "react";
+import { render, type RenderResult } from "jest-matrix-react";
+import React, { type ComponentProps } from "react";
 
 import GenericToast from "../../../../../src/components/views/toasts/GenericToast";
 
diff --git a/test/unit-tests/components/views/toasts/VerificationRequestToast-test.tsx b/test/unit-tests/components/views/toasts/VerificationRequestToast-test.tsx
index 1f22da8d753284740d20f8b83de1bf141a329f5f..f813e89286829381b9ae4a407202801c78660be0 100644
--- a/test/unit-tests/components/views/toasts/VerificationRequestToast-test.tsx
+++ b/test/unit-tests/components/views/toasts/VerificationRequestToast-test.tsx
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import React, { ComponentProps } from "react";
-import { mocked, Mocked } from "jest-mock";
-import { render, RenderResult } from "jest-matrix-react";
-import { TypedEventEmitter, IMyDevice, MatrixClient, Device } from "matrix-js-sdk/src/matrix";
-import { VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
+import React, { type ComponentProps } from "react";
+import { mocked, type Mocked } from "jest-mock";
+import { render, type RenderResult } from "jest-matrix-react";
+import { TypedEventEmitter, type IMyDevice, type MatrixClient, Device } from "matrix-js-sdk/src/matrix";
+import { type VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
 
 import VerificationRequestToast from "../../../../../src/components/views/toasts/VerificationRequestToast";
 import {
diff --git a/test/unit-tests/components/views/toasts/__snapshots__/GenericToast-test.tsx.snap b/test/unit-tests/components/views/toasts/__snapshots__/GenericToast-test.tsx.snap
index ca732870b175f47ec788255c8da7c9dd0b67bcb1..dff4803f41ca40deb86ccd2f2f82d631e612693a 100644
--- a/test/unit-tests/components/views/toasts/__snapshots__/GenericToast-test.tsx.snap
+++ b/test/unit-tests/components/views/toasts/__snapshots__/GenericToast-test.tsx.snap
@@ -15,7 +15,7 @@ exports[`GenericToast should render as expected with detail content 1`] = `
       class="mx_Toast_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -24,7 +24,7 @@ exports[`GenericToast should render as expected with detail content 1`] = `
         Reject
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="sm"
         role="button"
@@ -57,7 +57,7 @@ exports[`GenericToast should render as expected without detail content 1`] = `
       class="mx_Toast_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -66,7 +66,7 @@ exports[`GenericToast should render as expected without detail content 1`] = `
         Reject
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="sm"
         role="button"
diff --git a/test/unit-tests/components/views/toasts/__snapshots__/VerificationRequestToast-test.tsx.snap b/test/unit-tests/components/views/toasts/__snapshots__/VerificationRequestToast-test.tsx.snap
index 05ef32f54b8a4aff8bc3a7e5d882cb68b2b423f6..6250f932d46cd1521fcea009caedeb204b67716f 100644
--- a/test/unit-tests/components/views/toasts/__snapshots__/VerificationRequestToast-test.tsx.snap
+++ b/test/unit-tests/components/views/toasts/__snapshots__/VerificationRequestToast-test.tsx.snap
@@ -13,7 +13,7 @@ exports[`VerificationRequestToast should render a cross-user verification 1`] =
       class="mx_Toast_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -22,7 +22,7 @@ exports[`VerificationRequestToast should render a cross-user verification 1`] =
         Ignore
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="sm"
         role="button"
@@ -53,7 +53,7 @@ exports[`VerificationRequestToast should render a self-verification 1`] = `
       class="mx_Toast_buttons"
     >
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="secondary"
         data-size="sm"
         role="button"
@@ -62,7 +62,7 @@ exports[`VerificationRequestToast should render a self-verification 1`] = `
         Ignore
       </button>
       <button
-        class="_button_i91xf_17"
+        class="_button_vczzf_8"
         data-kind="primary"
         data-size="sm"
         role="button"
diff --git a/test/unit-tests/components/views/voip/CallView-test.tsx b/test/unit-tests/components/views/voip/CallView-test.tsx
index cb1cc6ffb1041562bbcc6d47f327abe37abff50a..ca6a50b4184083e53cc6eef50430d06ce6bb8785 100644
--- a/test/unit-tests/components/views/voip/CallView-test.tsx
+++ b/test/unit-tests/components/views/voip/CallView-test.tsx
@@ -7,13 +7,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { zip } from "lodash";
-import { render, screen, act, fireEvent, waitFor, cleanup } from "jest-matrix-react";
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import { render, screen, act, cleanup } from "jest-matrix-react";
+import { mocked, type Mocked } from "jest-mock";
+import {
+    type MatrixClient,
+    PendingEventOrdering,
+    Room,
+    RoomStateEvent,
+    type RoomMember,
+} from "matrix-js-sdk/src/matrix";
 import { Widget } from "matrix-widget-api";
 
-import type { RoomMember } from "matrix-js-sdk/src/matrix";
 import type { ClientWidgetApi } from "matrix-widget-api";
 import {
     stubClient,
@@ -28,7 +32,6 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
 import { CallView as _CallView } from "../../../../../src/components/views/voip/CallView";
 import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
 import { CallStore } from "../../../../../src/stores/CallStore";
-import { Call, ConnectionState } from "../../../../../src/models/Call";
 
 const CallView = wrapInMatrixClientContext(_CallView);
 
@@ -39,6 +42,8 @@ describe("CallView", () => {
     let client: Mocked<MatrixClient>;
     let room: Room;
     let alice: RoomMember;
+    let call: MockedCall;
+    let widget: Widget;
 
     beforeEach(() => {
         useMockMediaDevices();
@@ -58,112 +63,43 @@ describe("CallView", () => {
 
         setupAsyncStoreWithClient(CallStore.instance, client);
         setupAsyncStoreWithClient(WidgetMessagingStore.instance, client);
+
+        MockedCall.create(room, "1");
+        const maybeCall = CallStore.instance.getCall(room.roomId);
+        if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
+        call = maybeCall;
+
+        widget = new Widget(call.widget);
+        WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
+            stop: () => {},
+        } as unknown as ClientWidgetApi);
     });
 
     afterEach(() => {
+        cleanup(); // Unmount before we do any cleanup that might update the component
+        call.destroy();
+        WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
         client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
     });
 
-    const renderView = async (skipLobby = false): Promise<void> => {
-        render(<CallView room={room} resizing={false} waitForCall={false} skipLobby={skipLobby} />);
+    const renderView = async (skipLobby = false, role: string | undefined = undefined): Promise<void> => {
+        render(<CallView room={room} resizing={false} skipLobby={skipLobby} role={role} onClose={() => {}} />);
         await act(() => Promise.resolve()); // Let effects settle
     };
 
-    describe("with an existing call", () => {
-        let call: MockedCall;
-        let widget: Widget;
-
-        beforeEach(() => {
-            MockedCall.create(room, "1");
-            const maybeCall = CallStore.instance.getCall(room.roomId);
-            if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
-            call = maybeCall;
-
-            widget = new Widget(call.widget);
-            WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
-                stop: () => {},
-            } as unknown as ClientWidgetApi);
-        });
-
-        afterEach(() => {
-            cleanup(); // Unmount before we do any cleanup that might update the component
-            call.destroy();
-            WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
-        });
-
-        it("calls clean on mount", async () => {
-            const cleanSpy = jest.spyOn(call, "clean");
-            await renderView();
-            expect(cleanSpy).toHaveBeenCalled();
-        });
-
-        /**
-         * TODO: Fix I do not understand this test
-         */
-        it.skip("tracks participants", async () => {
-            const bob = mkRoomMember(room.roomId, "@bob:example.org");
-            const carol = mkRoomMember(room.roomId, "@carol:example.org");
-
-            const expectAvatars = (userIds: string[]) => {
-                const avatars = screen.queryAllByRole("button", { name: "Profile picture" });
-                expect(userIds.length).toBe(avatars.length);
-
-                for (const [userId, avatar] of zip(userIds, avatars)) {
-                    fireEvent.focus(avatar!);
-                    screen.getAllByRole("tooltip", { name: userId });
-                }
-            };
-
-            await renderView();
-            expect(screen.queryByLabelText(/joined/)).toBe(null);
-            expectAvatars([]);
-
-            act(() => {
-                call.participants = new Map([[alice, new Set(["a"])]]);
-            });
-            screen.getByText("1 person joined");
-            expectAvatars([alice.userId]);
-
-            act(() => {
-                call.participants = new Map([
-                    [alice, new Set(["a"])],
-                    [bob, new Set(["b1", "b2"])],
-                    [carol, new Set(["c"])],
-                ]);
-            });
-            screen.getByText("4 people joined");
-            expectAvatars([alice.userId, bob.userId, bob.userId, carol.userId]);
-
-            act(() => {
-                call.participants = new Map();
-            });
-            expect(screen.queryByLabelText(/joined/)).toBe(null);
-            expectAvatars([]);
-        });
+    it("accepts an accessibility role", async () => {
+        await renderView(undefined, "main");
+        screen.getByRole("main");
+    });
 
-        it("automatically connects to the call when skipLobby is true", async () => {
-            const connectSpy = jest.spyOn(call, "start");
-            await renderView(true);
-            await waitFor(() => expect(connectSpy).toHaveBeenCalled(), { interval: 1 });
-        });
+    it("calls clean on mount", async () => {
+        const cleanSpy = jest.spyOn(call, "clean");
+        await renderView();
+        expect(cleanSpy).toHaveBeenCalled();
     });
 
-    describe("without an existing call", () => {
-        it("creates and connects to a new call when the join button is pressed", async () => {
-            expect(Call.get(room)).toBeNull();
-            await renderView(true);
-            await waitFor(() => expect(CallStore.instance.getCall(room.roomId)).not.toBeNull());
-            const call = CallStore.instance.getCall(room.roomId)!;
-
-            const widget = new Widget(call.widget);
-            WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
-                stop: () => {},
-            } as unknown as ClientWidgetApi);
-            await waitFor(() => expect(call.connectionState).toBe(ConnectionState.Connected));
-
-            cleanup(); // Unmount before we do any cleanup that might update the component
-            call.destroy();
-            WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
-        });
+    it("updates the call's skipLobby parameter", async () => {
+        await renderView(true);
+        expect(call.widget.data?.skipLobby).toBe(true);
     });
 });
diff --git a/test/unit-tests/components/views/voip/LegacyCallView-test.tsx b/test/unit-tests/components/views/voip/LegacyCallView-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ee1f4fe3266651059054a069caf1bd726f046f5f
--- /dev/null
+++ b/test/unit-tests/components/views/voip/LegacyCallView-test.tsx
@@ -0,0 +1,40 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render } from "jest-matrix-react";
+import { type MatrixCall } from "matrix-js-sdk/src/matrix";
+
+import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
+import { stubClient } from "../../../../test-utils";
+
+describe("LegacyCallView", () => {
+    it("should exit full screen on unmount", () => {
+        const element = document.createElement("div");
+        // @ts-expect-error
+        document.fullscreenElement = element;
+        document.exitFullscreen = jest.fn();
+
+        stubClient();
+
+        const call = {
+            on: jest.fn(),
+            removeListener: jest.fn(),
+            getFeeds: jest.fn().mockReturnValue([]),
+            isLocalOnHold: jest.fn().mockReturnValue(false),
+            isRemoteOnHold: jest.fn().mockReturnValue(false),
+            isMicrophoneMuted: jest.fn().mockReturnValue(false),
+            isLocalVideoMuted: jest.fn().mockReturnValue(false),
+            isScreensharing: jest.fn().mockReturnValue(false),
+        } as unknown as MatrixCall;
+
+        const { unmount } = render(<LegacyCallView call={call} />);
+        expect(document.exitFullscreen).not.toHaveBeenCalled();
+        unmount();
+        expect(document.exitFullscreen).toHaveBeenCalled();
+    });
+});
diff --git a/test/unit-tests/components/views/voip/VideoFeed-test.tsx b/test/unit-tests/components/views/voip/VideoFeed-test.tsx
index d9a396d6640b25afe6601179323b7a473691a47d..bcb1632a3847ebb29e4cdf72437ed9c5056f5ec0 100644
--- a/test/unit-tests/components/views/voip/VideoFeed-test.tsx
+++ b/test/unit-tests/components/views/voip/VideoFeed-test.tsx
@@ -8,14 +8,14 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen } from "jest-matrix-react";
-import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
-import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
+import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import * as AvatarModule from "../../../../../src/Avatar";
 import VideoFeed from "../../../../../src/components/views/voip/VideoFeed";
 import { stubClient, useMockedCalls } from "../../../../test-utils";
-import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
+import type LegacyCallHandler from "../../../../../src/LegacyCallHandler";
 import DMRoomMap from "../../../../../src/utils/DMRoomMap";
 
 const FAKE_AVATAR_URL = "http://fakeurl.dummy/fake.png";
diff --git a/test/unit-tests/contexts/SdkContext-test.ts b/test/unit-tests/contexts/SdkContext-test.ts
index 0ccf638aa292b75e2e28dda83effca8780708389..edb7f92592ad5bf2d464f5bf15a50a8afbb0939b 100644
--- a/test/unit-tests/contexts/SdkContext-test.ts
+++ b/test/unit-tests/contexts/SdkContext-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { SdkContextClass } from "../../../src/contexts/SDKContext";
 import { OidcClientStore } from "../../../src/stores/oidc/OidcClientStore";
diff --git a/test/unit-tests/createRoom-test.ts b/test/unit-tests/createRoom-test.ts
index f3dc57fed4c2eeb28fb8ca94e40317e5cb44798a..c75bebc0fb486a8b36e249c864807d0a36ef6bd5 100644
--- a/test/unit-tests/createRoom-test.ts
+++ b/test/unit-tests/createRoom-test.ts
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, Device, Preset, RoomType } from "matrix-js-sdk/src/matrix";
-import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
+import { mocked, type Mocked } from "jest-mock";
+import { type MatrixClient, type Device, Preset, RoomType } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
 import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
 
 import { stubClient, setupAsyncStoreWithClient, mockPlatformPeg, getMockClientWithEventEmitter } from "../test-utils";
diff --git a/test/unit-tests/dispatcher/dispatcher-test.ts b/test/unit-tests/dispatcher/dispatcher-test.ts
index 711fad2b5e7bbe1b4dc8801782fc6a489be7e2b3..4b8fc31d286b7136982576d052c0e654fa8470db 100644
--- a/test/unit-tests/dispatcher/dispatcher-test.ts
+++ b/test/unit-tests/dispatcher/dispatcher-test.ts
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { defer } from "matrix-js-sdk/src/utils";
-
 import defaultDispatcher from "../../../src/dispatcher/dispatcher";
 import { Action } from "../../../src/dispatcher/actions";
 import { AsyncActionPayload } from "../../../src/dispatcher/payloads";
@@ -20,8 +18,8 @@ describe("MatrixDispatcher", () => {
     });
 
     it("should execute callbacks in registered order", async () => {
-        const deferred1 = defer<number>();
-        const deferred2 = defer<number>();
+        const deferred1 = Promise.withResolvers<number>();
+        const deferred2 = Promise.withResolvers<number>();
 
         const fn1 = jest.fn(() => deferred1.resolve(1));
         const fn2 = jest.fn(() => deferred2.resolve(2));
@@ -36,8 +34,8 @@ describe("MatrixDispatcher", () => {
     });
 
     it("should skip the queue for the given callback", async () => {
-        const deferred1 = defer<number>();
-        const deferred2 = defer<number>();
+        const deferred1 = Promise.withResolvers<number>();
+        const deferred2 = Promise.withResolvers<number>();
 
         const fn1 = jest.fn(() => deferred1.resolve(1));
         const fn2 = jest.fn(() => deferred2.resolve(2));
diff --git a/test/unit-tests/editor/deserialize-test.ts b/test/unit-tests/editor/deserialize-test.ts
index b5b8dc1b9f97a0990e9f299926fe0e40c629650b..e178ab8fba0f3531ec8934a2f6ff5a9b4df74bfb 100644
--- a/test/unit-tests/editor/deserialize-test.ts
+++ b/test/unit-tests/editor/deserialize-test.ts
@@ -5,10 +5,10 @@ Copyright 2019 The Matrix.org Foundation C.I.C.
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { parseEvent } from "../../../src/editor/deserialize";
-import { Part } from "../../../src/editor/parts";
+import { type Part } from "../../../src/editor/parts";
 import { createPartCreator } from "./mock";
 
 const FOUR_SPACES = " ".repeat(4);
diff --git a/test/unit-tests/editor/history-test.ts b/test/unit-tests/editor/history-test.ts
index 2ae284d0d8f13ca5b1687bdf222cf22840cf4e2c..7ff89162475cf41922f02bd606cbd5c7c582ffe2 100644
--- a/test/unit-tests/editor/history-test.ts
+++ b/test/unit-tests/editor/history-test.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import HistoryManager, { IHistory, MAX_STEP_LENGTH } from "../../../src/editor/history";
-import EditorModel from "../../../src/editor/model";
+import HistoryManager, { type IHistory, MAX_STEP_LENGTH } from "../../../src/editor/history";
+import type EditorModel from "../../../src/editor/model";
 import DocumentPosition from "../../../src/editor/position";
 
 describe("editor/history", function () {
diff --git a/test/unit-tests/editor/mock.ts b/test/unit-tests/editor/mock.ts
index e49a4558a4812a8ea5d56b1fa61ddd1a976cd0f2..e7bbe1c8ba6970f608cd9c4494da5d0245c37b09 100644
--- a/test/unit-tests/editor/mock.ts
+++ b/test/unit-tests/editor/mock.ts
@@ -6,11 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type Room, type MatrixClient, type RoomMember } from "matrix-js-sdk/src/matrix";
 
-import AutocompleteWrapperModel, { UpdateCallback } from "../../../src/editor/autocomplete";
-import { Caret } from "../../../src/editor/caret";
-import { PillPart, Part, PartCreator } from "../../../src/editor/parts";
+import { type UpdateCallback } from "../../../src/editor/autocomplete";
+import type AutocompleteWrapperModel from "../../../src/editor/autocomplete";
+import { type Caret } from "../../../src/editor/caret";
+import { type PillPart, type Part, PartCreator } from "../../../src/editor/parts";
 import DocumentPosition from "../../../src/editor/position";
 
 export class MockAutoComplete {
diff --git a/test/unit-tests/editor/model-test.ts b/test/unit-tests/editor/model-test.ts
index 5d548ce7fccd26fcaa2df5917e32dea5e20aacd9..e1bac728845ea0fe13c75d4bb52449d96f56f456 100644
--- a/test/unit-tests/editor/model-test.ts
+++ b/test/unit-tests/editor/model-test.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import EditorModel from "../../../src/editor/model";
-import { createPartCreator, createRenderer, MockAutoComplete } from "./mock";
+import { createPartCreator, createRenderer, type MockAutoComplete } from "./mock";
 import DocumentOffset from "../../../src/editor/offset";
-import { PillPart } from "../../../src/editor/parts";
-import DocumentPosition from "../../../src/editor/position";
+import { type PillPart } from "../../../src/editor/parts";
+import type DocumentPosition from "../../../src/editor/position";
 
 describe("editor/model", function () {
     describe("plain text manipulation", function () {
diff --git a/test/unit-tests/editor/operations-test.ts b/test/unit-tests/editor/operations-test.ts
index d0dc1e837dba21c5d04cbd05d89d3ee8b9d5d4d3..90e98b161c32f1d2614cc0b0d52ba28e7fed8b91 100644
--- a/test/unit-tests/editor/operations-test.ts
+++ b/test/unit-tests/editor/operations-test.ts
@@ -17,7 +17,7 @@ import {
 } from "../../../src/editor/operations";
 import { Formatting } from "../../../src/components/views/rooms/MessageComposerFormatBar";
 import { longestBacktickSequence } from "../../../src/editor/deserialize";
-import DocumentPosition from "../../../src/editor/position";
+import type DocumentPosition from "../../../src/editor/position";
 
 const SERIALIZED_NEWLINE = { text: "\n", type: "newline" };
 
diff --git a/test/unit-tests/editor/roundtrip-test.ts b/test/unit-tests/editor/roundtrip-test.ts
index dfce37472f44fd970a28ab383ddbbd3e874e9497..08c80a849c234ea3824d7a593c430b88452ff7d0 100644
--- a/test/unit-tests/editor/roundtrip-test.ts
+++ b/test/unit-tests/editor/roundtrip-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { parseEvent } from "../../../src/editor/deserialize";
 import EditorModel from "../../../src/editor/model";
diff --git a/test/unit-tests/editor/serialize-test.ts b/test/unit-tests/editor/serialize-test.ts
index 3162f1464ab15c54f5d91ba9458dccf9cdf26fdc..915b78bdfa8e29d9f1052b10631bacc5cd6f9820 100644
--- a/test/unit-tests/editor/serialize-test.ts
+++ b/test/unit-tests/editor/serialize-test.ts
@@ -11,7 +11,7 @@ import { mocked } from "jest-mock";
 import EditorModel from "../../../src/editor/model";
 import { htmlSerializeFromMdIfNeeded, htmlSerializeIfNeeded } from "../../../src/editor/serialize";
 import { createPartCreator } from "./mock";
-import { IConfigOptions } from "../../../src/IConfigOptions";
+import { type IConfigOptions } from "../../../src/IConfigOptions";
 import SettingsStore from "../../../src/settings/SettingsStore";
 import SdkConfig from "../../../src/SdkConfig";
 
diff --git a/test/unit-tests/events/EventTileFactory-test.ts b/test/unit-tests/events/EventTileFactory-test.ts
index eee94791fc3a4dd570aac3a0c24fa14e112055b2..6a4b5fa355a55f041f4baab098bd23cae13fe0e4 100644
--- a/test/unit-tests/events/EventTileFactory-test.ts
+++ b/test/unit-tests/events/EventTileFactory-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
 
 import {
     JSONEventFactory,
diff --git a/test/unit-tests/events/RelationsHelper-test.ts b/test/unit-tests/events/RelationsHelper-test.ts
index fa693a895383c8aed518e7ad3a5d41a0a3b154da..c8992fc9227ba1060fbe0005f4ad6f49fd022c1e 100644
--- a/test/unit-tests/events/RelationsHelper-test.ts
+++ b/test/unit-tests/events/RelationsHelper-test.ts
@@ -7,7 +7,14 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, MatrixEvent, MatrixEventEvent, RelationType, Room } from "matrix-js-sdk/src/matrix";
+import {
+    EventType,
+    type MatrixClient,
+    type MatrixEvent,
+    MatrixEventEvent,
+    RelationType,
+    Room,
+} from "matrix-js-sdk/src/matrix";
 
 import { RelationsHelper, RelationsHelperEvent } from "../../../src/events/RelationsHelper";
 import { mkEvent, stubClient } from "../../test-utils";
diff --git a/test/unit-tests/hooks/room/useRoomThreadNotifications-test.tsx b/test/unit-tests/hooks/room/useRoomThreadNotifications-test.tsx
index 26698af0582e107aa126ba2e4cd9bdde919845af..bf1c5de37ac61289eb397240f8e2c23b140cbbdf 100644
--- a/test/unit-tests/hooks/room/useRoomThreadNotifications-test.tsx
+++ b/test/unit-tests/hooks/room/useRoomThreadNotifications-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { renderHook } from "jest-matrix-react";
-import { MatrixClient, NotificationCountType, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, NotificationCountType, Room } from "matrix-js-sdk/src/matrix";
 
 import { useRoomThreadNotifications } from "../../../../src/hooks/room/useRoomThreadNotifications";
 import { stubClient } from "../../../test-utils";
diff --git a/test/unit-tests/hooks/useLatestResult-test.tsx b/test/unit-tests/hooks/useLatestResult-test.tsx
index 5a70d2148cb2b358845e4adf19097c5f6fedfc08..74848b0b9d391da70f19385a497293c1a45c8c65 100644
--- a/test/unit-tests/hooks/useLatestResult-test.tsx
+++ b/test/unit-tests/hooks/useLatestResult-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { renderHook, RenderHookResult } from "jest-matrix-react";
+import { renderHook, type RenderHookResult } from "jest-matrix-react";
 
 import { useLatestResult } from "../../../src/hooks/useLatestResult";
 
diff --git a/test/unit-tests/hooks/useMediaVisible-test.tsx b/test/unit-tests/hooks/useMediaVisible-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d8f71b764a8d97a6a775a64e2b04c8f1e49827ec
--- /dev/null
+++ b/test/unit-tests/hooks/useMediaVisible-test.tsx
@@ -0,0 +1,97 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { act, renderHook, waitFor } from "jest-matrix-react";
+import { JoinRule, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
+
+import { useMediaVisible } from "../../../src/hooks/useMediaVisible";
+import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../test-utils";
+import { type MediaPreviewConfig, MediaPreviewValue } from "../../../src/@types/media_preview";
+import MediaPreviewConfigController from "../../../src/settings/controllers/MediaPreviewConfigController";
+import SettingsStore from "../../../src/settings/SettingsStore";
+
+const EVENT_ID = "$fibble:example.org";
+const ROOM_ID = "!foobar:example.org";
+
+describe("useMediaVisible", () => {
+    let matrixClient: MatrixClient;
+    let room: Room;
+    const mediaPreviewConfig: MediaPreviewConfig = MediaPreviewConfigController.default;
+
+    function render() {
+        return renderHook(() => useMediaVisible(EVENT_ID, ROOM_ID), withClientContextRenderOptions(matrixClient));
+    }
+    beforeEach(() => {
+        matrixClient = createTestClient();
+        room = mkStubRoom(ROOM_ID, undefined, matrixClient);
+        matrixClient.getRoom = jest.fn().mockReturnValue(room);
+        const origFn = SettingsStore.getValue;
+        jest.spyOn(SettingsStore, "getValue").mockImplementation((setting, ...args) => {
+            if (setting === "mediaPreviewConfig") {
+                return mediaPreviewConfig;
+            }
+            return origFn(setting, ...args);
+        });
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("should display media by default", async () => {
+        const { result } = render();
+        expect(result.current[0]).toEqual(true);
+    });
+
+    it("should hide media when media previews are Off", async () => {
+        mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
+        const { result } = render();
+        expect(result.current[0]).toEqual(false);
+    });
+
+    it.each([[JoinRule.Invite], [JoinRule.Knock], [JoinRule.Restricted]])(
+        "should display media when media previews are Private and the join rule is %s",
+        async (rule) => {
+            mediaPreviewConfig.media_previews = MediaPreviewValue.Private;
+            room.currentState.getJoinRule = jest.fn().mockReturnValue(rule);
+            const { result } = render();
+            expect(result.current[0]).toEqual(true);
+        },
+    );
+
+    it.each([[JoinRule.Public], ["anything_else"]])(
+        "should hide media when media previews are Private and the join rule is %s",
+        async (rule) => {
+            mediaPreviewConfig.media_previews = MediaPreviewValue.Private;
+            room.currentState.getJoinRule = jest.fn().mockReturnValue(rule);
+            const { result } = render();
+            expect(result.current[0]).toEqual(false);
+        },
+    );
+
+    it("should hide media after function is called", async () => {
+        const { result } = render();
+        expect(result.current[0]).toEqual(true);
+        act(() => {
+            result.current[1](false);
+        });
+        await waitFor(() => {
+            expect(result.current[0]).toEqual(false);
+        });
+    });
+    it("should show media after function is called", async () => {
+        mediaPreviewConfig.media_previews = MediaPreviewValue.Off;
+        const { result } = render();
+        expect(result.current[0]).toEqual(false);
+        act(() => {
+            result.current[1](true);
+        });
+        await waitFor(() => {
+            expect(result.current[0]).toEqual(true);
+        });
+    });
+});
diff --git a/test/unit-tests/hooks/useNotificationSettings-test.tsx b/test/unit-tests/hooks/useNotificationSettings-test.tsx
index b05c868a125cad3bf0e4cbc2d9df3659c540d5cb..f8d2047e9b2d95d5bc9dc32bd83713bf988cdda8 100644
--- a/test/unit-tests/hooks/useNotificationSettings-test.tsx
+++ b/test/unit-tests/hooks/useNotificationSettings-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { waitFor, renderHook } from "jest-matrix-react";
-import { IPushRules, MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IPushRules, type MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
 
 import { useNotificationSettings } from "../../../src/hooks/useNotificationSettings";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
 import {
     DefaultNotificationSettings,
-    NotificationSettings,
+    type NotificationSettings,
 } from "../../../src/models/notificationsettings/NotificationSettings";
 import { StandardActions } from "../../../src/notifications/StandardActions";
 import { RoomNotifState } from "../../../src/RoomNotifs";
diff --git a/test/unit-tests/hooks/useProfileInfo-test.tsx b/test/unit-tests/hooks/useProfileInfo-test.tsx
index e42dcb185833063c9117a7a5588781595bded413..41e026f1691ce7b2703e85cfcfcc2c5cb6f915f1 100644
--- a/test/unit-tests/hooks/useProfileInfo-test.tsx
+++ b/test/unit-tests/hooks/useProfileInfo-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { waitFor, renderHook, act } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type EmptyObject, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useProfileInfo } from "../../../src/hooks/useProfileInfo";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
@@ -40,9 +40,10 @@ describe("useProfileInfo", () => {
             result.current.search({ query });
         });
 
-        await waitFor(() => expect(result.current.ready).toBe(true));
-
-        expect(result.current.profile?.display_name).toBe(query);
+        await waitFor(() => {
+            expect(result.current.ready).toBe(true);
+            expect(result.current.profile?.display_name).toBe(query);
+        });
     });
 
     it("should work with empty queries", async () => {
@@ -93,7 +94,7 @@ describe("useProfileInfo", () => {
     });
 
     it("should be able to handle an empty result", async () => {
-        cli.getProfileInfo = () => null as unknown as Promise<{}>;
+        cli.getProfileInfo = () => null as unknown as Promise<EmptyObject>;
         const query = "@user:home.server";
 
         const { result } = render();
diff --git a/test/unit-tests/hooks/usePublicRoomDirectory-test.tsx b/test/unit-tests/hooks/usePublicRoomDirectory-test.tsx
index 980c8b2aa12ab76d1da6c5b5a7c04da5ca7a77fd..b5f9a4d2f5879a5d4013e4872cd9db5fdf1f0d8e 100644
--- a/test/unit-tests/hooks/usePublicRoomDirectory-test.tsx
+++ b/test/unit-tests/hooks/usePublicRoomDirectory-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { waitFor, renderHook, act } from "jest-matrix-react";
-import { IRoomDirectoryOptions, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type IRoomDirectoryOptions, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { usePublicRoomDirectory } from "../../../src/hooks/usePublicRoomDirectory";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
@@ -27,17 +27,15 @@ describe("usePublicRoomDirectory", () => {
         cli.getDomain = () => "matrix.org";
         cli.getThirdpartyProtocols = () => Promise.resolve({});
         cli.publicRooms = ({ filter }: IRoomDirectoryOptions) => {
-            const chunk = filter?.generic_search_term
-                ? [
-                      {
-                          room_id: "hello world!",
-                          name: filter.generic_search_term,
-                          world_readable: true,
-                          guest_can_join: true,
-                          num_joined_members: 1,
-                      },
-                  ]
-                : [];
+            const chunk = [
+                {
+                    room_id: "hello world!",
+                    name: filter?.generic_search_term ?? "", // If the query is "" no filter is applied(an is undefined here), in keeping with the pattern let's call the room ""
+                    world_readable: true,
+                    guest_can_join: true,
+                    num_joined_members: 1,
+                },
+            ];
             return Promise.resolve({
                 chunk,
                 total_room_count_estimate: 1,
@@ -67,7 +65,7 @@ describe("usePublicRoomDirectory", () => {
     });
 
     it("should work with empty queries", async () => {
-        const query = "ROOM NAME";
+        const query = "";
         const { result } = render();
 
         act(() => {
@@ -79,9 +77,8 @@ describe("usePublicRoomDirectory", () => {
 
         await waitFor(() => {
             expect(result.current.ready).toBe(true);
+            expect(result.current.publicRooms[0].name).toEqual(query);
         });
-
-        expect(result.current.publicRooms[0].name).toEqual(query);
     });
 
     it("should recover from a server exception", async () => {
diff --git a/test/unit-tests/hooks/useRoomMembers-test.tsx b/test/unit-tests/hooks/useRoomMembers-test.tsx
index df572ead0886d3a189ebdf7a6c44d9273a31822e..dc5bf0e318b1a185c4c4466d7fca5367a813af7c 100644
--- a/test/unit-tests/hooks/useRoomMembers-test.tsx
+++ b/test/unit-tests/hooks/useRoomMembers-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { waitFor, renderHook, act } from "jest-matrix-react";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/hooks/useSlidingSyncRoomSearch-test.tsx b/test/unit-tests/hooks/useSlidingSyncRoomSearch-test.tsx
deleted file mode 100644
index f8c33ac16b62a407969842eee44731737213b353..0000000000000000000000000000000000000000
--- a/test/unit-tests/hooks/useSlidingSyncRoomSearch-test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import { waitFor, renderHook, act } from "jest-matrix-react";
-import { mocked } from "jest-mock";
-import { SlidingSync } from "matrix-js-sdk/src/sliding-sync";
-import { Room } from "matrix-js-sdk/src/matrix";
-
-import { useSlidingSyncRoomSearch } from "../../../src/hooks/useSlidingSyncRoomSearch";
-import { MockEventEmitter, stubClient } from "../../test-utils";
-import { SlidingSyncManager } from "../../../src/SlidingSyncManager";
-
-describe("useSlidingSyncRoomSearch", () => {
-    afterAll(() => {
-        jest.restoreAllMocks();
-    });
-
-    it("should display rooms when searching", async () => {
-        const client = stubClient();
-        const roomA = new Room("!a:localhost", client, client.getUserId()!);
-        const roomB = new Room("!b:localhost", client, client.getUserId()!);
-        const slidingSync = mocked(
-            new MockEventEmitter({
-                getListData: jest.fn(),
-            }) as unknown as SlidingSync,
-        );
-        jest.spyOn(SlidingSyncManager.instance, "ensureListRegistered").mockResolvedValue({
-            ranges: [[0, 9]],
-        });
-        SlidingSyncManager.instance.slidingSync = slidingSync;
-        mocked(slidingSync.getListData).mockReturnValue({
-            joinedCount: 2,
-            roomIndexToRoomId: {
-                0: roomA.roomId,
-                1: roomB.roomId,
-            },
-        });
-        mocked(client.getRoom).mockImplementation((roomId) => {
-            switch (roomId) {
-                case roomA.roomId:
-                    return roomA;
-                case roomB.roomId:
-                    return roomB;
-                default:
-                    return null;
-            }
-        });
-
-        // first check that everything is empty
-        const { result } = renderHook(() => useSlidingSyncRoomSearch());
-        const query = {
-            limit: 10,
-            query: "foo",
-        };
-        expect(result.current.loading).toBe(false);
-        expect(result.current.rooms).toEqual([]);
-
-        // run the query
-        act(() => {
-            result.current.search(query);
-        });
-
-        // wait for loading to finish
-        await waitFor(() => {
-            expect(result.current.loading).toBe(false);
-        });
-
-        // now we expect there to be rooms
-        expect(result.current.rooms).toEqual([roomA, roomB]);
-
-        // run the query again
-        act(() => {
-            result.current.search(query);
-        });
-        await waitFor(() => {
-            expect(result.current.loading).toBe(false);
-        });
-    });
-});
diff --git a/test/unit-tests/hooks/useUserDirectory-test.tsx b/test/unit-tests/hooks/useUserDirectory-test.tsx
index a2a8f62e31e686d060253a186c3fa75968eb38b3..002b9377b41027e3decbca88e15cb997732396ae 100644
--- a/test/unit-tests/hooks/useUserDirectory-test.tsx
+++ b/test/unit-tests/hooks/useUserDirectory-test.tsx
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { waitFor, renderHook, act } from "jest-matrix-react";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { useUserDirectory } from "../../../src/hooks/useUserDirectory";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
@@ -45,9 +45,11 @@ describe("useUserDirectory", () => {
         act(() => {
             result.current.search({ limit: 1, query });
         });
-        await waitFor(() => expect(result.current.ready).toBe(true));
+        await waitFor(() => {
+            expect(result.current.ready).toBe(true);
+            expect(result.current.loading).toBe(false);
+        });
 
-        expect(result.current.loading).toBe(false);
         expect(result.current.users[0].name).toBe(query);
     });
 
diff --git a/test/unit-tests/images/static-logo-extended-file-format.webp b/test/unit-tests/images/static-logo-extended-file-format.webp
new file mode 100644
index 0000000000000000000000000000000000000000..bb4364374b4e2a665986842946b280c6b1b5c6be
Binary files /dev/null and b/test/unit-tests/images/static-logo-extended-file-format.webp differ
diff --git a/test/unit-tests/integrations/IntegrationManagers-test.ts b/test/unit-tests/integrations/IntegrationManagers-test.ts
index e094926e4effea9c0a9fb196c0b4c8da8146eee3..88da290bb916d852ffd8680c9e226fe650e31c8c 100644
--- a/test/unit-tests/integrations/IntegrationManagers-test.ts
+++ b/test/unit-tests/integrations/IntegrationManagers-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import { IntegrationManagers } from "../../../src/integrations/IntegrationManagers";
diff --git a/test/unit-tests/languageHandler-test.tsx b/test/unit-tests/languageHandler-test.tsx
index 1969c857cc13263425a7cf3dfb7394aef04763d0..c69eefd85c7096f0535786fe32cf0aaa6db37f46 100644
--- a/test/unit-tests/languageHandler-test.tsx
+++ b/test/unit-tests/languageHandler-test.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import fetchMock from "fetch-mock-jest";
-import { Translation } from "matrix-web-i18n";
-import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api";
+import { type Translation } from "matrix-web-i18n";
+import { type TranslationStringsObject } from "@matrix-org/react-sdk-module-api";
 
 import SdkConfig from "../../src/SdkConfig";
 import {
@@ -21,11 +21,12 @@ import {
     setLanguage,
     setMissingEntryGenerator,
     substitute,
-    TranslatedString,
+    type TranslatedString,
     UserFriendlyError,
-    TranslationKey,
-    IVariables,
-    Tags,
+    type TranslationKey,
+    type IVariables,
+    type Tags,
+    getLanguagesFromBrowser,
 } from "../../src/languageHandler";
 import { stubClient } from "../test-utils";
 import { setupLanguageMock } from "../setup/setupLanguage";
@@ -198,6 +199,29 @@ describe("languageHandler", () => {
             setupLanguageMock(); // restore language mock
         });
     });
+
+    describe("getLanguagesFromBrowser", () => {
+        beforeEach(() => {
+            jest.restoreAllMocks();
+        });
+
+        it("should return navigator.languages if available", () => {
+            jest.spyOn(window.navigator, "languages", "get").mockReturnValue(["en", "de"]);
+            expect(getLanguagesFromBrowser()).toEqual(["en", "de"]);
+        });
+
+        it("should return navigator.language if available", () => {
+            jest.spyOn(window.navigator, "languages", "get").mockReturnValue([]);
+            jest.spyOn(window.navigator, "language", "get").mockReturnValue("de");
+            expect(getLanguagesFromBrowser()).toEqual(["de"]);
+        });
+
+        it("should return 'en' otherwise", () => {
+            jest.spyOn(window.navigator, "languages", "get").mockReturnValue([]);
+            jest.spyOn(window.navigator, "language", "get").mockReturnValue(undefined as any);
+            expect(getLanguagesFromBrowser()).toEqual(["en"]);
+        });
+    });
 });
 
 describe("languageHandler JSX", function () {
diff --git a/test/unit-tests/linkify-matrix-test.ts b/test/unit-tests/linkify-matrix-test.ts
index 70a6e18b3bb719e67f87932b2c8cfc104e7472bf..26c2a809d0b4637aa7eba7bba0d4699ced6f5893 100644
--- a/test/unit-tests/linkify-matrix-test.ts
+++ b/test/unit-tests/linkify-matrix-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventListeners } from "linkifyjs";
+import { type EventListeners } from "linkifyjs";
 
 import { linkify, Type, options } from "../../src/linkify-matrix";
 import dispatcher from "../../src/dispatcher/dispatcher";
diff --git a/test/unit-tests/models/Call-test.ts b/test/unit-tests/models/Call-test.ts
index 0da3e60ad876ff2f3d4ee892ec0221d77ecf5f2e..de5c42fc9c3dcdc969d3ed1c5e1b7d00bbf50f57 100644
--- a/test/unit-tests/models/Call-test.ts
+++ b/test/unit-tests/models/Call-test.ts
@@ -16,23 +16,24 @@ import {
     MatrixEvent,
     RoomStateEvent,
     PendingEventOrdering,
-    IContent,
+    type IContent,
+    type MatrixClient,
+    type IMyDevice,
+    type RoomMember,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { Widget } from "matrix-widget-api";
 import {
-    CallMembership,
+    type CallMembership,
     MatrixRTCSessionManagerEvents,
     MatrixRTCSession,
     MatrixRTCSessionEvent,
 } from "matrix-js-sdk/src/matrixrtc";
 
 import type { Mocked } from "jest-mock";
-import type { MatrixClient, IMyDevice, RoomMember } from "matrix-js-sdk/src/matrix";
 import type { ClientWidgetApi } from "matrix-widget-api";
 import {
-    JitsiCallMemberContent,
-    Layout,
+    type JitsiCallMemberContent,
     Call,
     CallEvent,
     ConnectionState,
@@ -47,8 +48,9 @@ import { WidgetMessagingStore } from "../../../src/stores/widgets/WidgetMessagin
 import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../../src/stores/ActiveWidgetStore";
 import { ElementWidgetActions } from "../../../src/stores/widgets/ElementWidgetActions";
 import SettingsStore from "../../../src/settings/SettingsStore";
-import { PosthogAnalytics } from "../../../src/PosthogAnalytics";
-import { SettingKey } from "../../../src/settings/Settings.tsx";
+import { Anonymity, PosthogAnalytics } from "../../../src/PosthogAnalytics";
+import { type SettingKey } from "../../../src/settings/Settings.tsx";
+import SdkConfig from "../../../src/SdkConfig.ts";
 
 jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
     [MediaDeviceKindEnum.AudioInput]: [
@@ -233,16 +235,16 @@ describe("JitsiCall", () => {
 
             ({ widget, messaging, audioMutedSpy, videoMutedSpy } = setUpWidget(call));
 
-            mocked(messaging.transport).send.mockImplementation(async (action: string): Promise<any> => {
+            mocked(messaging.transport).send.mockImplementation(async (action, data): Promise<any> => {
                 if (action === ElementWidgetActions.JoinCall) {
                     messaging.emit(
                         `action:${ElementWidgetActions.JoinCall}`,
-                        new CustomEvent("widgetapirequest", { detail: {} }),
+                        new CustomEvent("widgetapirequest", { detail: { data } }),
                     );
                 } else if (action === ElementWidgetActions.HangupCall) {
                     messaging.emit(
                         `action:${ElementWidgetActions.HangupCall}`,
-                        new CustomEvent("widgetapirequest", { detail: {} }),
+                        new CustomEvent("widgetapirequest", { detail: { data } }),
                     );
                 }
                 return {};
@@ -284,8 +286,6 @@ describe("JitsiCall", () => {
             expect(call.connectionState).toBe(ConnectionState.Disconnected);
 
             const connect = call.start();
-            expect(call.connectionState).toBe(ConnectionState.WidgetLoading);
-
             WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, messaging);
             await connect;
             expect(call.connectionState).toBe(ConnectionState.Connected);
@@ -308,7 +308,6 @@ describe("JitsiCall", () => {
             expect(call.connectionState).toBe(ConnectionState.Disconnected);
 
             const connect = call.start();
-            expect(call.connectionState).toBe(ConnectionState.WidgetLoading);
             async function runTimers() {
                 jest.advanceTimersByTime(500);
                 jest.advanceTimersByTime(1000);
@@ -354,18 +353,10 @@ describe("JitsiCall", () => {
 
             call.on(CallEvent.ConnectionState, callback);
 
-            messaging.emit(
-                `action:${ElementWidgetActions.HangupCall}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
+            messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
+            messaging.emit(`action:${ElementWidgetActions.Close}`, new CustomEvent("widgetapirequest", {}));
             await waitFor(() => {
                 expect(callback).toHaveBeenNthCalledWith(1, ConnectionState.Disconnected, ConnectionState.Connected);
-                expect(callback).toHaveBeenNthCalledWith(
-                    2,
-                    ConnectionState.WidgetLoading,
-                    ConnectionState.Disconnected,
-                );
-                expect(callback).toHaveBeenNthCalledWith(3, ConnectionState.Connecting, ConnectionState.WidgetLoading);
             });
             // in video rooms we expect the call to immediately reconnect
             call.off(CallEvent.ConnectionState, callback);
@@ -495,10 +486,7 @@ describe("JitsiCall", () => {
             await call.start();
             await call.disconnect();
             expect(onConnectionState.mock.calls).toEqual([
-                [ConnectionState.WidgetLoading, ConnectionState.Disconnected],
-                [ConnectionState.Connecting, ConnectionState.WidgetLoading],
-                [ConnectionState.Lobby, ConnectionState.Connecting],
-                [ConnectionState.Connected, ConnectionState.Lobby],
+                [ConnectionState.Connected, ConnectionState.Disconnected],
                 [ConnectionState.Disconnecting, ConnectionState.Connected],
                 [ConnectionState.Disconnected, ConnectionState.Disconnecting],
             ]);
@@ -632,7 +620,7 @@ describe("ElementCall", () => {
         jest.spyOn(room, "getJoinedMembers").mockReturnValue(memberIds.map((id) => ({ userId: id }) as RoomMember));
     }
 
-    const callConnectProcedure: (call: ElementCall) => Promise<void> = async (call) => {
+    const callConnectProcedure = async (call: ElementCall, startWidget = true): Promise<void> => {
         async function sessionConnect() {
             await new Promise<void>((r) => {
                 setTimeout(() => r(), 400);
@@ -651,9 +639,7 @@ describe("ElementCall", () => {
             jest.advanceTimersByTime(500);
         }
         sessionConnect();
-        const promise = call.start();
-        runTimers();
-        await promise;
+        await Promise.all([...(startWidget ? [call.start()] : []), runTimers()]);
     };
     const callDisconnectionProcedure: (call: ElementCall) => Promise<void> = async (call) => {
         async function sessionDisconnect() {
@@ -678,9 +664,11 @@ describe("ElementCall", () => {
     beforeEach(() => {
         jest.useFakeTimers();
         ({ client, room, alice } = setUpClientRoomAndStores());
+        SdkConfig.reset();
     });
 
     afterEach(() => {
+        jest.runOnlyPendingTimers();
         jest.useRealTimers();
         cleanUpClientRoomAndStores(client, room);
     });
@@ -691,11 +679,28 @@ describe("ElementCall", () => {
         });
 
         it("finds calls", async () => {
-            await ElementCall.create(room);
+            ElementCall.create(room);
             expect(Call.get(room)).toBeInstanceOf(ElementCall);
             Call.get(room)?.destroy();
         });
 
+        it("should use element call URL from developer settings if present", async () => {
+            const originalGetValue = SettingsStore.getValue;
+            SettingsStore.getValue = (name: SettingKey, roomId: string | null = null, excludeDefault = false): any => {
+                if (name === "Developer.elementCallUrl") {
+                    return "https://call.element.dev";
+                }
+                return excludeDefault
+                    ? originalGetValue(name, roomId, excludeDefault)
+                    : originalGetValue(name, roomId, excludeDefault);
+            };
+            await ElementCall.create(room);
+            const call = ElementCall.get(room);
+            expect(call?.widget.url.startsWith("https://call.element.dev/")).toBeTruthy();
+            SettingsStore.getValue = originalGetValue;
+            call?.destroy();
+        });
+
         it("finds ongoing calls that are created by the session manager", async () => {
             // There is an existing session created by another user in this room.
             client.matrixRTC.getRoomSession.mockReturnValue({
@@ -726,7 +731,7 @@ describe("ElementCall", () => {
             };
             document.documentElement.style.fontSize = "12px";
 
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
 
@@ -739,7 +744,7 @@ describe("ElementCall", () => {
 
         it("passes ICE fallback preference through widget URL", async () => {
             // Test with the preference set to false
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call1 = Call.get(room);
             if (!(call1 instanceof ElementCall)) throw new Error("Failed to create call");
 
@@ -771,19 +776,29 @@ describe("ElementCall", () => {
             SettingsStore.getValue = originalGetValue;
         });
 
-        it("passes analyticsID through widget URL", async () => {
+        it("passes analyticsID and posthog params through widget URL", async () => {
+            SdkConfig.put({
+                posthog: {
+                    api_host: "https://posthog",
+                    project_api_key: "DEADBEEF",
+                },
+            });
+            jest.spyOn(PosthogAnalytics.instance, "getAnonymity").mockReturnValue(Anonymity.Pseudonymous);
             client.getAccountData.mockImplementation((eventType: string) => {
                 if (eventType === PosthogAnalytics.ANALYTICS_EVENT_TYPE) {
                     return new MatrixEvent({ content: { id: "123456789987654321", pseudonymousAnalyticsOptIn: true } });
                 }
                 return undefined;
             });
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
 
             const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
             expect(urlParams.get("analyticsID")).toBe("123456789987654321");
+            expect(urlParams.get("posthogUserId")).toBe("123456789987654321");
+            expect(urlParams.get("posthogApiHost")).toBe("https://posthog");
+            expect(urlParams.get("posthogApiKey")).toBe("DEADBEEF");
             call.destroy();
         });
 
@@ -796,12 +811,12 @@ describe("ElementCall", () => {
                 }
                 return undefined;
             });
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
 
             const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
-            expect(urlParams.get("analyticsID")).toBe("");
+            expect(urlParams.get("analyticsID")).toBeFalsy();
             call.destroy();
         });
 
@@ -818,7 +833,7 @@ describe("ElementCall", () => {
                             : originalGetValue(name, roomId, excludeDefault);
                 }
             };
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
 
@@ -835,12 +850,12 @@ describe("ElementCall", () => {
                 }
                 return undefined;
             });
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
 
             const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
-            expect(urlParams.get("analyticsID")).toBe("");
+            expect(urlParams.get("analyticsID")).toBeFalsy();
         });
     });
 
@@ -855,7 +870,7 @@ describe("ElementCall", () => {
             jest.useFakeTimers();
             jest.setSystemTime(0);
 
-            await ElementCall.create(room, true);
+            ElementCall.create(room, true);
             const maybeCall = ElementCall.get(room);
             if (maybeCall === null) throw new Error("Failed to create call");
             call = maybeCall;
@@ -874,21 +889,11 @@ describe("ElementCall", () => {
             expect(call.connectionState).toBe(ConnectionState.Disconnected);
 
             const connect = callConnectProcedure(call);
-
-            expect(call.connectionState).toBe(ConnectionState.WidgetLoading);
-
             WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, messaging);
             await connect;
             expect(call.connectionState).toBe(ConnectionState.Connected);
         });
 
-        it("fails to connect if the widget returns an error", async () => {
-            // we only send a JoinCall action if the widget is preloading
-            call.widget.data = { ...call.widget, preload: true };
-            mocked(messaging.transport).send.mockRejectedValue(new Error("never!!1! >:("));
-            await expect(call.start()).rejects.toBeDefined();
-        });
-
         it("fails to disconnect if the widget returns an error", async () => {
             await callConnectProcedure(call);
             mocked(messaging.transport).send.mockRejectedValue(new Error("never!!1! >:("));
@@ -901,10 +906,8 @@ describe("ElementCall", () => {
             await callConnectProcedure(call);
             expect(call.connectionState).toBe(ConnectionState.Connected);
 
-            messaging.emit(
-                `action:${ElementWidgetActions.HangupCall}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
+            messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
+            messaging.emit(`action:${ElementWidgetActions.Close}`, new CustomEvent("widgetapirequest", {}));
             await waitFor(() => expect(call.connectionState).toBe(ConnectionState.Disconnected), { interval: 5 });
         });
 
@@ -937,33 +940,6 @@ describe("ElementCall", () => {
             expect(call.connectionState).toBe(ConnectionState.Disconnected);
         });
 
-        it("tracks layout", async () => {
-            await callConnectProcedure(call);
-            expect(call.layout).toBe(Layout.Tile);
-
-            messaging.emit(
-                `action:${ElementWidgetActions.SpotlightLayout}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
-            expect(call.layout).toBe(Layout.Spotlight);
-
-            messaging.emit(
-                `action:${ElementWidgetActions.TileLayout}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
-            expect(call.layout).toBe(Layout.Tile);
-        });
-
-        it("sets layout", async () => {
-            await callConnectProcedure(call);
-
-            await call.setLayout(Layout.Spotlight);
-            expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
-
-            await call.setLayout(Layout.Tile);
-            expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {});
-        });
-
         it("acknowledges mute_device widget action", async () => {
             await callConnectProcedure(call);
             const preventDefault = jest.fn();
@@ -984,9 +960,7 @@ describe("ElementCall", () => {
             await callConnectProcedure(call);
             await callDisconnectionProcedure(call);
             expect(onConnectionState.mock.calls).toEqual([
-                [ConnectionState.WidgetLoading, ConnectionState.Disconnected],
-                [ConnectionState.Connecting, ConnectionState.WidgetLoading],
-                [ConnectionState.Connected, ConnectionState.Connecting],
+                [ConnectionState.Connected, ConnectionState.Disconnected],
                 [ConnectionState.Disconnecting, ConnectionState.Connected],
                 [ConnectionState.Disconnected, ConnectionState.Disconnecting],
             ]);
@@ -1005,24 +979,6 @@ describe("ElementCall", () => {
             call.off(CallEvent.Participants, onParticipants);
         });
 
-        it("emits events when layout changes", async () => {
-            await callConnectProcedure(call);
-            const onLayout = jest.fn();
-            call.on(CallEvent.Layout, onLayout);
-
-            messaging.emit(
-                `action:${ElementWidgetActions.SpotlightLayout}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
-            messaging.emit(
-                `action:${ElementWidgetActions.TileLayout}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
-            expect(onLayout.mock.calls).toEqual([[Layout.Spotlight], [Layout.Tile]]);
-
-            call.off(CallEvent.Layout, onLayout);
-        });
-
         it("ends the call immediately if the session ended", async () => {
             await callConnectProcedure(call);
             const onDestroy = jest.fn();
@@ -1066,7 +1022,7 @@ describe("ElementCall", () => {
 
         it("sends notify event on connect in a room with more than two members", async () => {
             const sendEventSpy = jest.spyOn(room.client, "sendEvent");
-            await ElementCall.create(room);
+            ElementCall.create(room);
             await callConnectProcedure(Call.get(room) as ElementCall);
             expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
                 "application": "m.call",
@@ -1079,7 +1035,7 @@ describe("ElementCall", () => {
             setRoomMembers(["@user:example.com", "@user2:example.com"]);
 
             const sendEventSpy = jest.spyOn(room.client, "sendEvent");
-            await ElementCall.create(room);
+            ElementCall.create(room);
             await callConnectProcedure(Call.get(room) as ElementCall);
             expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
                 "application": "m.call",
@@ -1103,7 +1059,7 @@ describe("ElementCall", () => {
 
             jest.spyOn(room, "getType").mockReturnValue(RoomType.UnstableCall);
 
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const maybeCall = ElementCall.get(room);
             if (maybeCall === null) throw new Error("Failed to create call");
             call = maybeCall;
@@ -1142,7 +1098,7 @@ describe("ElementCall", () => {
                 return roomSession;
             });
 
-            await ElementCall.create(room);
+            ElementCall.create(room);
             const call = Call.get(room);
             if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
             expect(call.session).toBe(roomSession);
@@ -1161,12 +1117,12 @@ describe("ElementCall", () => {
             await callConnectProcedure(call);
             expect(call.connectionState).toBe(ConnectionState.Connected);
 
-            messaging.emit(
-                `action:${ElementWidgetActions.HangupCall}`,
-                new CustomEvent("widgetapirequest", { detail: {} }),
-            );
-            // We want the call to be connecting after the hangup.
-            waitFor(() => expect(call.connectionState).toBe(ConnectionState.Connecting), { interval: 5 });
+            messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
+            messaging.emit(`action:${ElementWidgetActions.Close}`, new CustomEvent("widgetapirequest", {}));
+            // We should now be able to reconnect without manually starting the widget
+            expect(call.connectionState).toBe(ConnectionState.Disconnected);
+            await callConnectProcedure(call, false);
+            await waitFor(() => expect(call.connectionState).toBe(ConnectionState.Connected), { interval: 5 });
         });
     });
     describe("create call", () => {
@@ -1178,7 +1134,7 @@ describe("ElementCall", () => {
                 { application: "m.call", callId: "" } as unknown as CallMembership,
             ]);
             const sendEventSpy = jest.spyOn(room.client, "sendEvent");
-            await ElementCall.create(room);
+            ElementCall.create(room);
             expect(sendEventSpy).not.toHaveBeenCalled();
         });
     });
diff --git a/test/unit-tests/models/LocalRoom-test.ts b/test/unit-tests/models/LocalRoom-test.ts
index b5398c00ae535f4ffefc6d05fc4e9b7ffca681c7..64e3f51240147d42821f5292d1c8bd2d4d6474b9 100644
--- a/test/unit-tests/models/LocalRoom-test.ts
+++ b/test/unit-tests/models/LocalRoom-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../../../src/models/LocalRoom";
 import { createTestClient } from "../../test-utils";
diff --git a/test/unit-tests/models/notificationsettings/NotificationSettings-test.ts b/test/unit-tests/models/notificationsettings/NotificationSettings-test.ts
index 68bc6a5a4c89c2831fd6a1f60f765115b1809c17..a51540129b35754111993cef070bcd73c248b034 100644
--- a/test/unit-tests/models/notificationsettings/NotificationSettings-test.ts
+++ b/test/unit-tests/models/notificationsettings/NotificationSettings-test.ts
@@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
+import { type IPushRules, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
 
 import {
     DefaultNotificationSettings,
-    NotificationSettings,
+    type NotificationSettings,
 } from "../../../../src/models/notificationsettings/NotificationSettings";
 import { reconcileNotificationSettings } from "../../../../src/models/notificationsettings/reconcileNotificationSettings";
 import { toNotificationSettings } from "../../../../src/models/notificationsettings/toNotificationSettings";
diff --git a/test/unit-tests/modules/MockModule.ts b/test/unit-tests/modules/MockModule.ts
index f699f68dea53ddcea9f62f4d0019cf8ec11f954f..981cafa6e002353c63835c511648e412acde7aee 100644
--- a/test/unit-tests/modules/MockModule.ts
+++ b/test/unit-tests/modules/MockModule.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
-import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
-import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
-import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
-import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
+import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
+import { type AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
+import { type ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
+import { type ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
 
 import { ModuleRunner } from "../../../src/modules/ModuleRunner";
 
diff --git a/test/unit-tests/modules/ModuleRunner-test.ts b/test/unit-tests/modules/ModuleRunner-test.ts
index 69a3baf9f0bdb9595898bf5616e9d35e0302c0b5..bc581105b57c75888938f967f26fb9d5ac27d1e5 100644
--- a/test/unit-tests/modules/ModuleRunner-test.ts
+++ b/test/unit-tests/modules/ModuleRunner-test.ts
@@ -6,10 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import {
+    type RoomPreviewOpts,
+    RoomViewLifecycle,
+} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
 
 import {
-    MockModule,
+    type MockModule,
     registerMockModule,
     registerMockModuleWithCryptoSetupExtension,
     registerMockModuleWithExperimentalExtension,
diff --git a/test/unit-tests/modules/ProxiedModuleApi-test.tsx b/test/unit-tests/modules/ProxiedModuleApi-test.tsx
index 34c3c534dd6d400ad76aa4290908f6f14dcc4423..4a325050ecbee2583dad33330dbfadc772fb169e 100644
--- a/test/unit-tests/modules/ProxiedModuleApi-test.tsx
+++ b/test/unit-tests/modules/ProxiedModuleApi-test.tsx
@@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
-import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
-import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
+import { type TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
+import { type AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
+import { DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
 import { screen, within } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { Mocked } from "jest-mock";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Mocked } from "jest-mock";
 
 import { ProxiedModuleApi } from "../../../src/modules/ProxiedModuleApi";
 import { getMockClientWithEventEmitter, mkRoom, stubClient } from "../../test-utils";
@@ -22,7 +22,7 @@ import { ModuleRunner } from "../../../src/modules/ModuleRunner";
 import { registerMockModule } from "./MockModule";
 import defaultDispatcher from "../../../src/dispatcher/dispatcher";
 import { Action } from "../../../src/dispatcher/actions";
-import WidgetStore, { IApp } from "../../../src/stores/WidgetStore";
+import WidgetStore, { type IApp } from "../../../src/stores/WidgetStore";
 import { Container, WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore";
 
 describe("ProxiedApiModule", () => {
diff --git a/test/unit-tests/notifications/ContentRules-test.ts b/test/unit-tests/notifications/ContentRules-test.ts
index 16bd082156eeeb910b97bd5eb99e605ad8633107..7f900b1d08f889547d267d8e53c220bc4a04218a 100644
--- a/test/unit-tests/notifications/ContentRules-test.ts
+++ b/test/unit-tests/notifications/ContentRules-test.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { TweakName, PushRuleActionName, TweakHighlight, TweakSound } from "matrix-js-sdk/src/matrix";
+import { TweakName, PushRuleActionName, type TweakHighlight, type TweakSound } from "matrix-js-sdk/src/matrix";
 
 import { ContentRules, PushRuleVectorState } from "../../../src/notifications";
 
diff --git a/test/unit-tests/notifications/PushRuleVectorState-test.ts b/test/unit-tests/notifications/PushRuleVectorState-test.ts
index d495752a68fa9e2047469f48d6198dcbbad4ed08..8179bde13eabb8e36e17c0ca663732ecb71e7f36 100644
--- a/test/unit-tests/notifications/PushRuleVectorState-test.ts
+++ b/test/unit-tests/notifications/PushRuleVectorState-test.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { PushRuleActionName, TweakHighlight, TweakName, TweakSound } from "matrix-js-sdk/src/matrix";
+import { PushRuleActionName, type TweakHighlight, TweakName, type TweakSound } from "matrix-js-sdk/src/matrix";
 
 import { PushRuleVectorState } from "../../../src/notifications";
 
diff --git a/test/unit-tests/renderer/__snapshots__/link-tooltip-test.tsx.snap b/test/unit-tests/renderer/__snapshots__/link-tooltip-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..9abf48919cbaa62816e7b26a38dca8cfbb9810ba
--- /dev/null
+++ b/test/unit-tests/renderer/__snapshots__/link-tooltip-test.tsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`link-tooltip does nothing for empty element 1`] = `
+<DocumentFragment>
+  <div />
+</DocumentFragment>
+`;
+
+exports[`link-tooltip wraps single anchor 1`] = `
+<DocumentFragment>
+  
+            
+  <div>
+    
+                
+    <span
+      aria-labelledby="«r0»"
+      tabindex="0"
+    >
+      <a
+        href="/foo"
+      >
+        click
+      </a>
+    </span>
+    
+            
+  </div>
+  
+        
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/renderer/__snapshots__/pill-test.tsx.snap b/test/unit-tests/renderer/__snapshots__/pill-test.tsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..8154467c7e79d37a7b6215bc32fcf94317a8dc81
--- /dev/null
+++ b/test/unit-tests/renderer/__snapshots__/pill-test.tsx.snap
@@ -0,0 +1,103 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`keyword pills should do nothing for empty element 1`] = `
+<DocumentFragment>
+  <div />
+</DocumentFragment>
+`;
+
+exports[`keyword pills should pillify 1`] = `
+<DocumentFragment>
+  <div>
+    Foo 
+    <bdi>
+      <span
+        tabindex="0"
+      >
+        <span
+          class="mx_Pill mx_KeywordPill"
+        >
+          <span
+            class="mx_Pill_text"
+          >
+            TeST
+          </span>
+        </span>
+      </span>
+    </bdi>
+     Bar
+  </div>
+</DocumentFragment>
+`;
+
+exports[`mention pills should do nothing for empty element 1`] = `
+<DocumentFragment>
+  <div />
+</DocumentFragment>
+`;
+
+exports[`mention pills should pillify @room 1`] = `
+<DocumentFragment>
+  <div>
+    <bdi>
+      <span
+        tabindex="0"
+      >
+        <span
+          class="mx_Pill mx_AtRoomPill"
+        >
+          <span
+            aria-hidden="true"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="4"
+            data-testid="avatar-img"
+            data-type="round"
+            role="presentation"
+            style="--cpd-avatar-size: 16px;"
+          >
+            !
+          </span>
+          <span
+            class="mx_Pill_text"
+          >
+            @room
+          </span>
+        </span>
+      </span>
+    </bdi>
+  </div>
+</DocumentFragment>
+`;
+
+exports[`mention pills should pillify @room in an intentional mentions world 1`] = `
+<DocumentFragment>
+  <div>
+    <bdi>
+      <span
+        tabindex="0"
+      >
+        <span
+          class="mx_Pill mx_AtRoomPill"
+        >
+          <span
+            aria-hidden="true"
+            class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
+            data-color="4"
+            data-testid="avatar-img"
+            data-type="round"
+            role="presentation"
+            style="--cpd-avatar-size: 16px;"
+          >
+            !
+          </span>
+          <span
+            class="mx_Pill_text"
+          >
+            @room
+          </span>
+        </span>
+      </span>
+    </bdi>
+  </div>
+</DocumentFragment>
+`;
diff --git a/test/unit-tests/renderer/link-tooltip-test.tsx b/test/unit-tests/renderer/link-tooltip-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..63f9f174b51f8f5c6e2600d7cdb7b06d3631d4dd
--- /dev/null
+++ b/test/unit-tests/renderer/link-tooltip-test.tsx
@@ -0,0 +1,47 @@
+/*
+Copyright 2024-2025 New Vector Ltd.
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { screen, fireEvent, render, type RenderResult } from "jest-matrix-react";
+import parse from "html-react-parser";
+
+import { ambiguousLinkTooltipRenderer, combineRenderers } from "../../../src/renderer";
+import PlatformPeg from "../../../src/PlatformPeg";
+import type BasePlatform from "../../../src/BasePlatform";
+
+describe("link-tooltip", () => {
+    jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
+
+    function renderTooltips(input: string): RenderResult {
+        return render(
+            <>
+                {parse(input, {
+                    replace: combineRenderers(ambiguousLinkTooltipRenderer)({ isHtml: true }),
+                })}
+            </>,
+        );
+    }
+
+    it("does nothing for empty element", () => {
+        const { asFragment } = renderTooltips("<div></div>");
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("wraps single anchor", () => {
+        const { container, asFragment } = renderTooltips(`
+            <div>
+                <a href="/foo">click</a>
+            </div>
+        `);
+        expect(asFragment()).toMatchSnapshot();
+        const anchor = container.querySelector("a")!;
+        expect(anchor.getAttribute("href")).toEqual("/foo");
+        fireEvent.focus(anchor.parentElement!);
+        expect(screen.getByLabelText("http://localhost/foo")).toBe(anchor.parentElement!);
+    });
+});
diff --git a/test/unit-tests/renderer/pill-test.tsx b/test/unit-tests/renderer/pill-test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f3f3e36f9b6c98a4842f709281e876ed4ea88642
--- /dev/null
+++ b/test/unit-tests/renderer/pill-test.tsx
@@ -0,0 +1,228 @@
+/*
+Copyright 2024-2025 New Vector Ltd.
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { render, type RenderResult } from "jest-matrix-react";
+import {
+    MatrixEvent,
+    ConditionKind,
+    EventType,
+    PushRuleActionName,
+    Room,
+    TweakName,
+    type MatrixClient,
+} from "matrix-js-sdk/src/matrix";
+import { mocked } from "jest-mock";
+import parse from "html-react-parser";
+import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
+
+import { keywordPillRenderer, mentionPillRenderer, combineRenderers } from "../../../src/renderer";
+import { stubClient, withClientContextRenderOptions } from "../../test-utils";
+import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
+import DMRoomMap from "../../../src/utils/DMRoomMap";
+
+describe("mention pills", () => {
+    let cli: MatrixClient;
+    let room: Room;
+    const roomId = "!room:id";
+    const event = new MatrixEvent({
+        room_id: roomId,
+        type: EventType.RoomMessage,
+        content: {
+            body: "@room",
+        },
+    });
+
+    beforeEach(() => {
+        stubClient();
+        cli = MatrixClientPeg.safeGet();
+        // @ts-expect-error
+        cli.pushProcessor = new PushProcessor(cli);
+        room = new Room(roomId, cli, cli.getUserId()!);
+        room.currentState.mayTriggerNotifOfType = jest.fn().mockReturnValue(true);
+        (cli.getRoom as jest.Mock).mockReturnValue(room);
+        cli.pushRules!.global = {
+            override: [
+                {
+                    rule_id: ".m.rule.roomnotif",
+                    default: true,
+                    enabled: true,
+                    conditions: [
+                        {
+                            kind: ConditionKind.EventMatch,
+                            key: "content.body",
+                            pattern: "@room",
+                        },
+                    ],
+                    actions: [
+                        PushRuleActionName.Notify,
+                        {
+                            set_tweak: TweakName.Highlight,
+                            value: true,
+                        },
+                    ],
+                },
+                {
+                    rule_id: ".m.rule.is_room_mention",
+                    default: true,
+                    enabled: true,
+                    conditions: [
+                        {
+                            kind: ConditionKind.EventPropertyIs,
+                            key: "content.m\\.mentions.room",
+                            value: true,
+                        },
+                        {
+                            kind: ConditionKind.SenderNotificationPermission,
+                            key: "room",
+                        },
+                    ],
+                    actions: [
+                        PushRuleActionName.Notify,
+                        {
+                            set_tweak: TweakName.Highlight,
+                        },
+                    ],
+                },
+            ],
+        };
+
+        DMRoomMap.makeShared(cli);
+    });
+
+    function renderPills(input: string, mxEvent?: MatrixEvent): RenderResult {
+        return render(
+            <>
+                {parse(input, {
+                    replace: combineRenderers(mentionPillRenderer)({
+                        mxEvent: mxEvent ?? event,
+                        room,
+                        isHtml: true,
+                    }),
+                })}
+            </>,
+            withClientContextRenderOptions(cli),
+        );
+    }
+
+    it("should do nothing for empty element", () => {
+        const input = "<div></div>";
+        const { asFragment } = renderPills(input);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should pillify @room", () => {
+        const input = "<div>@room</div>";
+        const { container, asFragment } = renderPills(input);
+        expect(asFragment()).toMatchSnapshot();
+        expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
+    });
+
+    it("should pillify @room in an intentional mentions world", () => {
+        mocked(MatrixClientPeg.safeGet().supportsIntentionalMentions).mockReturnValue(true);
+        const { container, asFragment } = renderPills(
+            "<div>@room</div>",
+            new MatrixEvent({
+                room_id: roomId,
+                type: EventType.RoomMessage,
+                content: {
+                    "body": "@room",
+                    "m.mentions": {
+                        room: true,
+                    },
+                },
+            }),
+        );
+        expect(asFragment()).toMatchSnapshot();
+        expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
+    });
+});
+
+describe("keyword pills", () => {
+    let cli: MatrixClient;
+    const keywordRegexpPattern = /(test)/i;
+
+    beforeEach(() => {
+        stubClient();
+        cli = MatrixClientPeg.safeGet();
+        cli.pushRules!.global = {
+            override: [
+                {
+                    rule_id: ".m.rule.roomnotif",
+                    default: true,
+                    enabled: true,
+                    conditions: [
+                        {
+                            kind: ConditionKind.EventMatch,
+                            key: "content.body",
+                            pattern: "@room",
+                        },
+                    ],
+                    actions: [
+                        PushRuleActionName.Notify,
+                        {
+                            set_tweak: TweakName.Highlight,
+                            value: true,
+                        },
+                    ],
+                },
+                {
+                    rule_id: ".m.rule.is_room_mention",
+                    default: true,
+                    enabled: true,
+                    conditions: [
+                        {
+                            kind: ConditionKind.EventPropertyIs,
+                            key: "content.m\\.mentions.room",
+                            value: true,
+                        },
+                        {
+                            kind: ConditionKind.SenderNotificationPermission,
+                            key: "room",
+                        },
+                    ],
+                    actions: [
+                        PushRuleActionName.Notify,
+                        {
+                            set_tweak: TweakName.Highlight,
+                        },
+                    ],
+                },
+            ],
+        };
+
+        DMRoomMap.makeShared(cli);
+    });
+
+    function renderPills(input: string): RenderResult {
+        return render(
+            <>
+                {parse(input, {
+                    replace: combineRenderers(keywordPillRenderer)({
+                        isHtml: true,
+                        keywordRegexpPattern,
+                    }),
+                })}
+            </>,
+            withClientContextRenderOptions(cli),
+        );
+    }
+
+    it("should do nothing for empty element", () => {
+        const input = "<div></div>";
+        const { asFragment } = renderPills(input);
+        expect(asFragment()).toMatchSnapshot();
+    });
+
+    it("should pillify", () => {
+        const input = "<div>Foo TeST Bar</div>";
+        const { container, asFragment } = renderPills(input);
+        expect(asFragment()).toMatchSnapshot();
+        expect(container.querySelector(".mx_Pill.mx_KeywordPill")?.textContent).toBe("TeST");
+    });
+});
diff --git a/test/unit-tests/settings/SettingsStore-test.ts b/test/unit-tests/settings/SettingsStore-test.ts
index 74da89bde24c091b01cee3976fcb212ab02f5c33..e2f7d845d20ec7998923456f42ce94cc5037ba02 100644
--- a/test/unit-tests/settings/SettingsStore-test.ts
+++ b/test/unit-tests/settings/SettingsStore-test.ts
@@ -1,19 +1,21 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientEvent, MatrixClient, Room, SyncState } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient, type Room, SyncState } from "matrix-js-sdk/src/matrix";
+import { waitFor } from "jest-matrix-react";
 
-import BasePlatform from "../../../src/BasePlatform";
+import type BasePlatform from "../../../src/BasePlatform";
 import SdkConfig from "../../../src/SdkConfig";
 import { SettingLevel } from "../../../src/settings/SettingLevel";
 import SettingsStore from "../../../src/settings/SettingsStore";
 import { mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
-import { SettingKey } from "../../../src/settings/Settings.tsx";
+import { type SettingKey } from "../../../src/settings/Settings.tsx";
+import MatrixClientBackedController from "../../../src/settings/controllers/MatrixClientBackedController.ts";
 
 const TEST_DATA = [
     {
@@ -139,5 +141,61 @@ describe("SettingsStore", () => {
 
             expect(room.getAccountData).not.toHaveBeenCalled();
         });
+
+        describe("Migrate media preview configuration", () => {
+            beforeEach(() => {
+                MatrixClientBackedController.matrixClient = client;
+                client.getAccountData = jest.fn().mockImplementation((type) => {
+                    if (type === "im.vector.web.settings") {
+                        return {
+                            getContent: jest.fn().mockReturnValue({
+                                showImages: false,
+                                showAvatarsOnInvites: false,
+                            }),
+                        };
+                    } else {
+                        return undefined;
+                    }
+                });
+            });
+
+            it("migrates media preview configuration immediately", async () => {
+                client.setAccountData = jest.fn();
+                SettingsStore.runMigrations(false);
+                expect(client.setAccountData).toHaveBeenCalledWith("io.element.msc4278.media_preview_config", {
+                    invite_avatars: "off",
+                    media_previews: "off",
+                });
+            });
+            it("migrates media preview configuration once client is ready", async () => {
+                client.setAccountData = jest.fn();
+                const mockInitialSync = (client.isInitialSyncComplete = jest.fn().mockReturnValue(false));
+                SettingsStore.runMigrations(false);
+                mockInitialSync.mockReturnValue(true);
+                client.emit(ClientEvent.Sync, SyncState.Prepared, null);
+                // Update is asynchronous
+                waitFor(() => {
+                    expect(client.setAccountData).toHaveBeenCalledWith("io.element.msc4278.media_preview_config", {
+                        invite_avatars: "off",
+                        media_previews: "off",
+                    });
+                });
+            });
+
+            it("does not migrate media preview configuration if the session is fresh", async () => {
+                client.setAccountData = jest.fn();
+                SettingsStore.runMigrations(true);
+                client.emit(ClientEvent.Sync, SyncState.Prepared, null);
+                expect(client.setAccountData).not.toHaveBeenCalled();
+            });
+
+            it("does not migrate media preview configuration if the account data is already set", async () => {
+                client.setAccountData = jest.fn();
+                client.getAccountData = jest.fn().mockReturnValue({});
+                SettingsStore.runMigrations(false);
+                client.emit(ClientEvent.Sync, SyncState.Prepared, null);
+                expect(client.setAccountData).not.toHaveBeenCalled();
+            });
+        });
     });
 });
diff --git a/test/unit-tests/settings/controllers/IncompatibleController-test.ts b/test/unit-tests/settings/controllers/IncompatibleController-test.ts
index 3025a8142e8072ccbeac3e242b512b72b66dc7ef..43dec5fc44b8da81d3537e5e41606c5bedd72ca4 100644
--- a/test/unit-tests/settings/controllers/IncompatibleController-test.ts
+++ b/test/unit-tests/settings/controllers/IncompatibleController-test.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import IncompatibleController from "../../../../src/settings/controllers/IncompatibleController";
 import { SettingLevel } from "../../../../src/settings/SettingLevel";
 import SettingsStore from "../../../../src/settings/SettingsStore";
-import { FeatureSettingKey } from "../../../../src/settings/Settings.tsx";
+import { type FeatureSettingKey } from "../../../../src/settings/Settings.tsx";
 
 describe("IncompatibleController", () => {
     const settingsGetValueSpy = jest.spyOn(SettingsStore, "getValue");
diff --git a/test/unit-tests/settings/controllers/MediaPreviewConfigController-test.ts b/test/unit-tests/settings/controllers/MediaPreviewConfigController-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ec93afb7991113ae3998580c1d21cb23924056c9
--- /dev/null
+++ b/test/unit-tests/settings/controllers/MediaPreviewConfigController-test.ts
@@ -0,0 +1,164 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { MatrixEvent } from "matrix-js-sdk/src/matrix";
+
+import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
+import MediaPreviewConfigController from "../../../../src/settings/controllers/MediaPreviewConfigController";
+import { SettingLevel } from "../../../../src/settings/SettingLevel";
+import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils";
+import { MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, MediaPreviewValue } from "../../../../src/@types/media_preview";
+
+describe("MediaPreviewConfigController", () => {
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    const ROOM_ID = "!room:example.org";
+
+    it("gets the default settings when none are specified.", () => {
+        const controller = new MediaPreviewConfigController();
+
+        MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
+            getAccountData: jest.fn().mockReturnValue(null),
+        });
+
+        const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+        expect(value).toEqual(MediaPreviewConfigController.default);
+    });
+
+    it("gets the default settings when the setting is empty.", () => {
+        const controller = new MediaPreviewConfigController();
+
+        MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
+            getAccountData: jest
+                .fn()
+                .mockReturnValue(new MatrixEvent({ type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, content: {} })),
+        });
+
+        const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+        expect(value).toEqual(MediaPreviewConfigController.default);
+    });
+
+    it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the global level", (key) => {
+        const controller = new MediaPreviewConfigController();
+
+        MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
+            getAccountData: jest.fn().mockReturnValue(
+                new MatrixEvent({
+                    type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+                    content: {
+                        [key]: MediaPreviewValue.Off,
+                    },
+                }),
+            ),
+            getRoom: jest.fn().mockReturnValue({
+                getAccountData: jest.fn().mockReturnValue(null),
+            }),
+        });
+
+        const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+        expect(globalValue[key]).toEqual(MediaPreviewValue.Off);
+
+        // Should follow the global value.
+        const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
+        expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
+    });
+
+    it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the room level", (key) => {
+        const controller = new MediaPreviewConfigController();
+
+        MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
+            getAccountData: jest.fn().mockReturnValue(null),
+            getRoom: jest.fn().mockReturnValue({
+                getAccountData: jest.fn().mockReturnValue(
+                    new MatrixEvent({
+                        type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+                        content: {
+                            [key]: MediaPreviewValue.Off,
+                        },
+                    }),
+                ),
+            }),
+        });
+
+        const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+        expect(globalValue[key]).toEqual(MediaPreviewValue.On);
+
+        // Should follow the global value.
+        const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
+        expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
+    });
+
+    it.each([["media_previews"], ["invite_avatars"]])(
+        "uses defaults when an invalid value is set on the global level",
+        (key) => {
+            const controller = new MediaPreviewConfigController();
+
+            MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+                ...mockClientMethodsServer(),
+                getAccountData: jest.fn().mockReturnValue(
+                    new MatrixEvent({
+                        type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+                        content: {
+                            [key]: "bibble",
+                        },
+                    }),
+                ),
+                getRoom: jest.fn().mockReturnValue({
+                    getAccountData: jest.fn().mockReturnValue(null),
+                }),
+            });
+
+            const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+            expect(globalValue[key]).toEqual(MediaPreviewValue.On);
+
+            // Should follow the global value.
+            const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
+            expect(roomValue[key]).toEqual(MediaPreviewValue.On);
+        },
+    );
+    it.each([["media_previews"], ["invite_avatars"]])(
+        "uses global value when an invalid value is set on the room level",
+        (key) => {
+            const controller = new MediaPreviewConfigController();
+
+            MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
+                ...mockClientMethodsServer(),
+                getAccountData: jest.fn().mockReturnValue(
+                    new MatrixEvent({
+                        type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+                        content: {
+                            [key]: MediaPreviewValue.Off,
+                        },
+                    }),
+                ),
+                getRoom: jest.fn().mockReturnValue({
+                    getAccountData: jest.fn().mockReturnValue(
+                        new MatrixEvent({
+                            type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
+                            content: {
+                                [key]: "bibble",
+                            },
+                        }),
+                    ),
+                }),
+            });
+
+            const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
+            expect(globalValue[key]).toEqual(MediaPreviewValue.Off);
+
+            // Should follow the global value.
+            const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
+            expect(roomValue[key]).toEqual(MediaPreviewValue.Off);
+        },
+    );
+});
diff --git a/test/unit-tests/settings/controllers/ServerSupportUnstableFeatureController-test.ts b/test/unit-tests/settings/controllers/ServerSupportUnstableFeatureController-test.ts
index 8dd74c4de5ad6e461908cfb0f1dc04e9ea15045c..cab82f8d32387f8655f8ac713b735f5135b1aba1 100644
--- a/test/unit-tests/settings/controllers/ServerSupportUnstableFeatureController-test.ts
+++ b/test/unit-tests/settings/controllers/ServerSupportUnstableFeatureController-test.ts
@@ -6,16 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { defer } from "matrix-js-sdk/src/utils";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
 import { SettingLevel } from "../../../../src/settings/SettingLevel";
-import { FeatureSettingKey, LabGroup, SETTINGS } from "../../../../src/settings/Settings";
+import { type FeatureSettingKey, LabGroup, SETTINGS } from "../../../../src/settings/Settings";
 import { stubClient } from "../../../test-utils";
 import { WatchManager } from "../../../../src/settings/WatchManager";
 import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
-import { TranslationKey } from "../../../../src/languageHandler";
+import { type TranslationKey } from "../../../../src/languageHandler";
 
 describe("ServerSupportUnstableFeatureController", () => {
     const watchers = new WatchManager();
@@ -34,7 +33,7 @@ describe("ServerSupportUnstableFeatureController", () => {
             controller,
         };
 
-        const deferred = defer<any>();
+        const deferred = Promise.withResolvers<any>();
         watchers.watchSetting(setting, null, deferred.resolve);
         MatrixClientBackedController.matrixClient = cli;
         await deferred.promise;
diff --git a/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts b/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts
index 7b86e08517f2b9a9743bf98d24eabec58e6ed361..d7580adf1caf164e5801b9673d6618f1c5cdfc85 100644
--- a/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts
+++ b/test/unit-tests/settings/handlers/DeviceSettingsHandler-test.ts
@@ -11,7 +11,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import DeviceSettingsHandler from "../../../../src/settings/handlers/DeviceSettingsHandler";
-import { CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
+import { type CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
 import { stubClient } from "../../../test-utils/test-utils";
 
 describe("DeviceSettingsHandler", () => {
diff --git a/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts b/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts
index 8c3b1a74356a72526faa1c6bfb59468f9d470c62..1852acaf17069ce437ff92d4265a0eef93f944d9 100644
--- a/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts
+++ b/test/unit-tests/settings/handlers/RoomDeviceSettingsHandler-test.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import RoomDeviceSettingsHandler from "../../../../src/settings/handlers/RoomDeviceSettingsHandler";
 import { SettingLevel } from "../../../../src/settings/SettingLevel";
-import { CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
+import { type CallbackFn, WatchManager } from "../../../../src/settings/WatchManager";
 
 describe("RoomDeviceSettingsHandler", () => {
     const roomId = "!room:example.com";
diff --git a/test/unit-tests/settings/watchers/ThemeWatcher-test.tsx b/test/unit-tests/settings/watchers/ThemeWatcher-test.tsx
index 3e094e0711dabeb3270c7dc36e70b47ccaf6174d..3b98e504dda2367636f9d3b9d24a49f9d1ce079f 100644
--- a/test/unit-tests/settings/watchers/ThemeWatcher-test.tsx
+++ b/test/unit-tests/settings/watchers/ThemeWatcher-test.tsx
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import SettingsStore from "../../../../src/settings/SettingsStore";
 import ThemeWatcher from "../../../../src/settings/watchers/ThemeWatcher";
-import { SettingLevel } from "../../../../src/settings/SettingLevel";
-import { SettingKey, Settings } from "../../../../src/settings/Settings.tsx";
+import { type SettingLevel } from "../../../../src/settings/SettingLevel";
+import { type SettingKey, type Settings } from "../../../../src/settings/Settings.tsx";
 
 function makeMatchMedia(values: any) {
     class FakeMediaQueryList {
diff --git a/test/unit-tests/stores/AutoRageshakeStore-test.ts b/test/unit-tests/stores/AutoRageshakeStore-test.ts
index cbfd40b7f4d08a9b02ebe60ba78a0ab6cae06d67..e7f15233f40f239b7061767b5dc0883aca6a1bb2 100644
--- a/test/unit-tests/stores/AutoRageshakeStore-test.ts
+++ b/test/unit-tests/stores/AutoRageshakeStore-test.ts
@@ -10,8 +10,8 @@ import { mocked } from "jest-mock";
 import {
     ClientEvent,
     EventType,
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     MatrixEventEvent,
     SyncState,
 } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/stores/BreadcrumbsStore-test.ts b/test/unit-tests/stores/BreadcrumbsStore-test.ts
index a73a2f60954403c646fdecf54384bb941b9a4dfd..66cd88f41e6cf03a7d9e7a14d5f1246be50d30ec 100644
--- a/test/unit-tests/stores/BreadcrumbsStore-test.ts
+++ b/test/unit-tests/stores/BreadcrumbsStore-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import { createTestClient, flushPromises, setupAsyncStoreWithClient } from "../../test-utils";
 import SettingsStore from "../../../src/settings/SettingsStore";
diff --git a/test/unit-tests/stores/InitialCryptoSetupStore-test.ts b/test/unit-tests/stores/InitialCryptoSetupStore-test.ts
index 32f63edd688c8b3b918961ce74d61e57b05a15d7..46c3f3c1cb8e7f3c9acccde973f36a1e263445f5 100644
--- a/test/unit-tests/stores/InitialCryptoSetupStore-test.ts
+++ b/test/unit-tests/stores/InitialCryptoSetupStore-test.ts
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { waitFor } from "jest-matrix-react";
 import { sleep } from "matrix-js-sdk/src/utils";
 
diff --git a/test/unit-tests/stores/MemberListStore-test.ts b/test/unit-tests/stores/MemberListStore-test.ts
index c61bb904255901abaeea6ffabc0a26601cb5fb19..9139dde85d76c20cf66131fb1727823f57353468 100644
--- a/test/unit-tests/stores/MemberListStore-test.ts
+++ b/test/unit-tests/stores/MemberListStore-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type IContent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import SdkConfig from "../../../src/SdkConfig";
@@ -161,7 +161,7 @@ describe("MemberListStore", () => {
     describe("sliding sync", () => {
         beforeEach(() => {
             jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
-                return settingName === "feature_sliding_sync"; // this is enabled, everything else is disabled.
+                return settingName === "feature_simplified_sliding_sync"; // this is enabled, everything else is disabled.
             });
             client.members = jest.fn();
         });
diff --git a/test/unit-tests/stores/OwnBeaconStore-test.ts b/test/unit-tests/stores/OwnBeaconStore-test.ts
index bd35d037655d72de4662c45d1d977694930d09b9..383488466a69784d56a522e5ee617c7fc503e4bd 100644
--- a/test/unit-tests/stores/OwnBeaconStore-test.ts
+++ b/test/unit-tests/stores/OwnBeaconStore-test.ts
@@ -11,7 +11,7 @@ import {
     Beacon,
     BeaconEvent,
     getBeaconInfoIdentifier,
-    MatrixEvent,
+    type MatrixEvent,
     RoomStateEvent,
     RoomMember,
     ContentHelpers,
@@ -19,7 +19,7 @@ import {
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
-import { Mocked } from "jest-mock";
+import { type Mocked } from "jest-mock";
 
 import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../src/stores/OwnBeaconStore";
 import {
diff --git a/test/unit-tests/stores/OwnProfileStore-test.ts b/test/unit-tests/stores/OwnProfileStore-test.ts
index 57234091d04978c805f5583726c16e791aac73e8..63d84521fe5b89781d1269be3d4f17f2c3125f7e 100644
--- a/test/unit-tests/stores/OwnProfileStore-test.ts
+++ b/test/unit-tests/stores/OwnProfileStore-test.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
-import { MockedObject, mocked } from "jest-mock";
+import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
+import { type MockedObject, mocked } from "jest-mock";
 
 import { stubClient } from "../../test-utils";
 import { OwnProfileStore } from "../../../src/stores/OwnProfileStore";
diff --git a/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx b/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx
index 61cfa93f42c01a1d35477efe0e2901b226c5e6f3..a902616673616e06f09e7500cdb645caa38ba13c 100644
--- a/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx
+++ b/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx
@@ -8,9 +8,9 @@
 
 import { mocked } from "jest-mock";
 
-import SettingsStore, { CallbackFn } from "../../../src/settings/SettingsStore";
-import { Feature, ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore";
-import { SettingLevel } from "../../../src/settings/SettingLevel";
+import SettingsStore, { type CallbackFn } from "../../../src/settings/SettingsStore";
+import { type Feature, ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore";
+import { type SettingLevel } from "../../../src/settings/SettingLevel";
 
 jest.mock("../../../src/settings/SettingsStore");
 
@@ -85,7 +85,8 @@ describe("ReleaseAnnouncementStore", () => {
         expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBeNull();
     });
 
-    it("should return the next feature when the next release announcement is called", async () => {
+    // We only have a single release announcement currently
+    it.skip("should return the next feature when the next release announcement is called", async () => {
         // Sanity check
         expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBe("threadsActivityCentre");
 
@@ -109,12 +110,12 @@ describe("ReleaseAnnouncementStore", () => {
 
     it("should listen to release announcement data changes in the store", async () => {
         const secondStore = new ReleaseAnnouncementStore();
-        expect(secondStore.getReleaseAnnouncement()).toBe("threadsActivityCentre");
+        expect(secondStore.getReleaseAnnouncement()).toBe("pinningMessageList");
 
         const promise = listenReleaseAnnouncementChanged();
         await secondStore.nextReleaseAnnouncement();
 
-        expect(await promise).toBe("pinningMessageList");
-        expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBe("pinningMessageList");
+        expect(await promise).toBe(null);
+        expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBe(null);
     });
 });
diff --git a/test/unit-tests/stores/RoomNotificationStateStore-test.ts b/test/unit-tests/stores/RoomNotificationStateStore-test.ts
index 7787b28b68982cd04d4d2d2181e948d1c2a4030c..4855ca1098ffcf491472b9864cc6e810c8b4946c 100644
--- a/test/unit-tests/stores/RoomNotificationStateStore-test.ts
+++ b/test/unit-tests/stores/RoomNotificationStateStore-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { ClientEvent, MatrixClient, Room, SyncState } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient, Room, SyncState } from "matrix-js-sdk/src/matrix";
 
 import { createTestClient, setupAsyncStoreWithClient } from "../../test-utils";
 import {
diff --git a/test/unit-tests/stores/RoomViewStore-test.ts b/test/unit-tests/stores/RoomViewStore-test.ts
index dc18d68cbdd7579c756ad2117b0b234ae53e02d3..71bc57160de36bb2abec837d9482bb3af796da39 100644
--- a/test/unit-tests/stores/RoomViewStore-test.ts
+++ b/test/unit-tests/stores/RoomViewStore-test.ts
@@ -9,27 +9,40 @@ Please see LICENSE files in the repository root for full details.
 import { mocked } from "jest-mock";
 import { MatrixError, Room } from "matrix-js-sdk/src/matrix";
 import { sleep } from "matrix-js-sdk/src/utils";
-import { RoomViewLifecycle, ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import {
+    RoomViewLifecycle,
+    type ViewRoomOpts,
+} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
+import EventEmitter from "events";
 
 import { RoomViewStore } from "../../../src/stores/RoomViewStore";
 import { Action } from "../../../src/dispatcher/actions";
-import { getMockClientWithEventEmitter, untilDispatch, untilEmission } from "../../test-utils";
+import {
+    getMockClientWithEventEmitter,
+    setupAsyncStoreWithClient,
+    untilDispatch,
+    untilEmission,
+} from "../../test-utils";
 import SettingsStore from "../../../src/settings/SettingsStore";
 import { SlidingSyncManager } from "../../../src/SlidingSyncManager";
 import { PosthogAnalytics } from "../../../src/PosthogAnalytics";
 import { TimelineRenderingType } from "../../../src/contexts/RoomContext";
 import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
 import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
-import { ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/ActiveRoomChangedPayload";
+import { type ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/ActiveRoomChangedPayload";
 import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore";
 import { TestSdkContext } from "../TestSdkContext";
-import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
 import Modal from "../../../src/Modal";
 import ErrorDialog from "../../../src/components/views/dialogs/ErrorDialog";
-import { CancelAskToJoinPayload } from "../../../src/dispatcher/payloads/CancelAskToJoinPayload";
-import { JoinRoomErrorPayload } from "../../../src/dispatcher/payloads/JoinRoomErrorPayload";
-import { SubmitAskToJoinPayload } from "../../../src/dispatcher/payloads/SubmitAskToJoinPayload";
+import { type CancelAskToJoinPayload } from "../../../src/dispatcher/payloads/CancelAskToJoinPayload";
+import { type JoinRoomErrorPayload } from "../../../src/dispatcher/payloads/JoinRoomErrorPayload";
+import { type SubmitAskToJoinPayload } from "../../../src/dispatcher/payloads/SubmitAskToJoinPayload";
 import { ModuleRunner } from "../../../src/modules/ModuleRunner";
+import { type IApp } from "../../../src/utils/WidgetUtils-types";
+import { CallStore } from "../../../src/stores/CallStore";
+import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
+import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../src/MediaDeviceHandler";
 
 jest.mock("../../../src/Modal");
 
@@ -57,6 +70,12 @@ jest.mock("../../../src/audio/VoiceRecording", () => ({
     }),
 }));
 
+jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
+    [MediaDeviceKindEnum.AudioInput]: [],
+    [MediaDeviceKindEnum.VideoInput]: [],
+    [MediaDeviceKindEnum.AudioOutput]: [],
+});
+
 jest.mock("../../../src/utils/DMRoomMap", () => {
     const mock = {
         getUserIdForRoomId: jest.fn(),
@@ -69,7 +88,21 @@ jest.mock("../../../src/utils/DMRoomMap", () => {
     };
 });
 
-jest.mock("../../../src/stores/WidgetStore");
+jest.mock("../../../src/stores/WidgetStore", () => {
+    // This mock needs to use a real EventEmitter; require is the only way to import that in a hoisted block
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    const EventEmitter = require("events");
+    const apps: IApp[] = [];
+    const instance = new (class extends EventEmitter {
+        getApps() {
+            return apps;
+        }
+        addVirtualWidget(app: IApp) {
+            apps.push(app);
+        }
+    })();
+    return { instance };
+});
 jest.mock("../../../src/stores/widgets/WidgetLayoutStore");
 
 describe("RoomViewStore", function () {
@@ -79,10 +112,12 @@ describe("RoomViewStore", function () {
     // we need to change the alias to ensure cache misses as the cache exists
     // through all tests.
     let alias = "#somealias2:aser.ver";
+    const getRooms = jest.fn();
     const mockClient = getMockClientWithEventEmitter({
         joinRoom: jest.fn(),
         getRoom: jest.fn(),
         getRoomIdForAlias: jest.fn(),
+        getRooms,
         isGuest: jest.fn(),
         getUserId: jest.fn().mockReturnValue(userId),
         getSafeUserId: jest.fn().mockReturnValue(userId),
@@ -94,9 +129,18 @@ describe("RoomViewStore", function () {
         knockRoom: jest.fn(),
         leave: jest.fn(),
         setRoomAccountData: jest.fn(),
+        getAccountData: jest.fn(),
+        matrixRTC: new (class extends EventEmitter {
+            getRoomSession() {
+                return new (class extends EventEmitter {
+                    memberships = [];
+                })();
+            }
+        })(),
     });
     const room = new Room(roomId, mockClient, userId);
     const room2 = new Room(roomId2, mockClient, userId);
+    getRooms.mockReturnValue([room, room2]);
 
     const viewCall = async (): Promise<void> => {
         dis.dispatch<ViewRoomPayload>({
@@ -152,6 +196,8 @@ describe("RoomViewStore", function () {
         stores.client = mockClient;
         stores._SlidingSyncManager = slidingSyncManager;
         stores._PosthogAnalytics = new MockPosthogAnalytics();
+        // @ts-expect-error
+        MockPosthogAnalytics.instance = stores._PosthogAnalytics;
         stores._SpaceStore = new MockSpaceStore();
         roomViewStore = new RoomViewStore(dis, stores);
         stores._RoomViewStore = roomViewStore;
@@ -298,6 +344,7 @@ describe("RoomViewStore", function () {
     });
 
     it("when viewing a call without a broadcast, it should not raise an error", async () => {
+        await setupAsyncStoreWithClient(CallStore.instance, MatrixClientPeg.safeGet());
         await viewCall();
     });
 
@@ -338,43 +385,35 @@ describe("RoomViewStore", function () {
     describe("Sliding Sync", function () {
         beforeEach(() => {
             jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
-                return settingName === "feature_sliding_sync"; // this is enabled, everything else is disabled.
+                return settingName === "feature_simplified_sliding_sync"; // this is enabled, everything else is disabled.
             });
         });
 
         it("subscribes to the room", async () => {
-            const setRoomVisible = jest
-                .spyOn(slidingSyncManager, "setRoomVisible")
-                .mockReturnValue(Promise.resolve(""));
+            const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(Promise.resolve());
             const subscribedRoomId = "!sub1:localhost";
             dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId });
             await untilDispatch(Action.ActiveRoomChanged, dis);
             expect(roomViewStore.getRoomId()).toBe(subscribedRoomId);
-            expect(setRoomVisible).toHaveBeenCalledWith(subscribedRoomId, true);
+            expect(setRoomVisible).toHaveBeenCalledWith(subscribedRoomId);
         });
 
-        // Regression test for an in-the-wild bug where rooms would rapidly switch forever in sliding sync mode
+        // Previously a regression test for an in-the-wild bug where rooms would rapidly switch forever in sliding sync mode
+        // although that was before the complexity was removed with similified mode. I've removed the complexity but kept the
+        // test anyway.
         it("doesn't get stuck in a loop if you view rooms quickly", async () => {
-            const setRoomVisible = jest
-                .spyOn(slidingSyncManager, "setRoomVisible")
-                .mockReturnValue(Promise.resolve(""));
+            const setRoomVisible = jest.spyOn(slidingSyncManager, "setRoomVisible").mockReturnValue(Promise.resolve());
             const subscribedRoomId = "!sub1:localhost";
             const subscribedRoomId2 = "!sub2:localhost";
             dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId }, true);
             dis.dispatch({ action: Action.ViewRoom, room_id: subscribedRoomId2 }, true);
             await untilDispatch(Action.ActiveRoomChanged, dis);
-            // sub(1) then unsub(1) sub(2), unsub(1)
-            const wantCalls = [
-                [subscribedRoomId, true],
-                [subscribedRoomId, false],
-                [subscribedRoomId2, true],
-                [subscribedRoomId, false],
-            ];
+            // should view 1, then 2
+            const wantCalls = [[subscribedRoomId], [subscribedRoomId2]];
             expect(setRoomVisible).toHaveBeenCalledTimes(wantCalls.length);
             wantCalls.forEach((v, i) => {
                 try {
                     expect(setRoomVisible.mock.calls[i][0]).toEqual(v[0]);
-                    expect(setRoomVisible.mock.calls[i][1]).toEqual(v[1]);
                 } catch {
                     throw new Error(`i=${i} got ${setRoomVisible.mock.calls[i]} want ${v}`);
                 }
diff --git a/test/unit-tests/stores/SetupEncryptionStore-test.ts b/test/unit-tests/stores/SetupEncryptionStore-test.ts
index 0609dbe66fb55715566c2fa3226df1955bb2848b..468a6439eec747dd9842b8a7856cb07d899c3b39 100644
--- a/test/unit-tests/stores/SetupEncryptionStore-test.ts
+++ b/test/unit-tests/stores/SetupEncryptionStore-test.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, Device } from "matrix-js-sdk/src/matrix";
-import { SecretStorageKeyDescriptionAesV1, ServerSideSecretStorage } from "matrix-js-sdk/src/secret-storage";
-import { BootstrapCrossSigningOpts, CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
+import { mocked, type Mocked } from "jest-mock";
+import { type MatrixClient, Device } from "matrix-js-sdk/src/matrix";
+import { type SecretStorageKeyDescriptionAesV1, type ServerSideSecretStorage } from "matrix-js-sdk/src/secret-storage";
+import { type CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 
 import { accessSecretStorage } from "../../../src/SecurityManager";
 import { SetupEncryptionStore } from "../../../src/stores/SetupEncryptionStore";
@@ -152,21 +152,4 @@ describe("SetupEncryptionStore", () => {
             await dehydrationPromise;
         });
     });
-
-    it("resetConfirm should work with a cached account password", async () => {
-        const makeRequest = jest.fn();
-        mockCrypto.bootstrapCrossSigning.mockImplementation(async (opts: BootstrapCrossSigningOpts) => {
-            await opts?.authUploadDeviceSigningKeys?.(makeRequest);
-        });
-        mocked(accessSecretStorage).mockImplementation(async (func?: () => Promise<void>) => {
-            await func!();
-        });
-
-        await setupEncryptionStore.resetConfirm();
-
-        expect(mocked(accessSecretStorage)).toHaveBeenCalledWith(expect.any(Function), {
-            forceReset: true,
-            resetCrossSigning: true,
-        });
-    });
 });
diff --git a/test/unit-tests/stores/SpaceStore-test.ts b/test/unit-tests/stores/SpaceStore-test.ts
index 27ec09b856e16d5f80bdb2510670136d07d239b4..6b6b6c1bc1509aaf7a49b8d27eca6825b5daf1f0 100644
--- a/test/unit-tests/stores/SpaceStore-test.ts
+++ b/test/unit-tests/stores/SpaceStore-test.ts
@@ -6,21 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventEmitter } from "events";
+import { type EventEmitter } from "events";
 import { mocked } from "jest-mock";
 import {
     EventType,
     RoomMember,
     RoomStateEvent,
     ClientEvent,
-    MatrixEvent,
-    Room,
+    type MatrixEvent,
+    type Room,
     RoomEvent,
     JoinRule,
-    RoomState,
+    type RoomState,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
-import { defer } from "matrix-js-sdk/src/utils";
 
 import SpaceStore from "../../../src/stores/spaces/SpaceStore";
 import {
@@ -141,6 +140,8 @@ describe("SpaceStore", () => {
     });
 
     afterEach(async () => {
+        // Disable the new room list feature flag
+        await SettingsStore.setValue("feature_new_room_list", null, SettingLevel.DEVICE, false);
         await testUtils.resetAsyncStoreWithClient(store);
     });
 
@@ -1008,7 +1009,7 @@ describe("SpaceStore", () => {
 
         await run();
 
-        const deferred = defer<boolean>();
+        const deferred = Promise.withResolvers<boolean>();
         space.loadMembersIfNeeded.mockImplementation(() => {
             const event = mkEvent({
                 event: true,
@@ -1391,6 +1392,15 @@ describe("SpaceStore", () => {
         removeListener();
     });
 
+    it("Favourites and People meta spaces should not be returned when the feature_new_room_list labs flag is enabled", async () => {
+        // Enable the new room list
+        await SettingsStore.setValue("feature_new_room_list", null, SettingLevel.DEVICE, true);
+
+        await run();
+        // Favourites and People meta spaces should not be returned
+        expect(SpaceStore.instance.enabledMetaSpaces).toStrictEqual([MetaSpace.Home, MetaSpace.Orphans]);
+    });
+
     describe("when feature_dynamic_room_predecessors is not enabled", () => {
         beforeAll(() => {
             jest.spyOn(SettingsStore, "getValue").mockImplementation(
diff --git a/test/unit-tests/stores/ToastStore-test.ts b/test/unit-tests/stores/ToastStore-test.ts
index 8332cc823da904e9d4f8c97b0570f2b3480d61c0..612721a1c585263da0030c74e412817109f6c9af 100644
--- a/test/unit-tests/stores/ToastStore-test.ts
+++ b/test/unit-tests/stores/ToastStore-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import GenericToast from "../../../src/components/views/toasts/GenericToast";
-import ToastStore, { IToast } from "../../../src/stores/ToastStore";
+import ToastStore, { type IToast } from "../../../src/stores/ToastStore";
 
 describe("ToastStore", () => {
     const makeToast = (priority: number, key?: string): IToast<typeof GenericToast> => ({
diff --git a/test/unit-tests/stores/TypingStore-test.ts b/test/unit-tests/stores/TypingStore-test.ts
index af055a32f91f44b7779ff4b2d60313a6aa933ca7..3cf5e17ad1fe952762097d84d750384bd8ed079c 100644
--- a/test/unit-tests/stores/TypingStore-test.ts
+++ b/test/unit-tests/stores/TypingStore-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import TypingStore from "../../../src/stores/TypingStore";
 import { LOCAL_ROOM_ID_PREFIX } from "../../../src/models/LocalRoom";
diff --git a/test/unit-tests/stores/UserProfilesStore-test.ts b/test/unit-tests/stores/UserProfilesStore-test.ts
index 8dec46e93ba2a24c03ed60e8b8d001a989b00da1..ea0d06f4b107d4f09d9d49c8200336c16bf5cd8e 100644
--- a/test/unit-tests/stores/UserProfilesStore-test.ts
+++ b/test/unit-tests/stores/UserProfilesStore-test.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 import {
-    IMatrixProfile,
-    MatrixClient,
+    type IMatrixProfile,
+    type MatrixClient,
     MatrixError,
-    MatrixEvent,
+    type MatrixEvent,
     Room,
     RoomMemberEvent,
 } from "matrix-js-sdk/src/matrix";
diff --git a/test/unit-tests/stores/VoiceRecordingStore-test.ts b/test/unit-tests/stores/VoiceRecordingStore-test.ts
index 8b0ba3d0653af216179c1a3e46456484731655f6..e1d0bf6336bdc6762b3852c8d00c0bd254b7ac68 100644
--- a/test/unit-tests/stores/VoiceRecordingStore-test.ts
+++ b/test/unit-tests/stores/VoiceRecordingStore-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { VoiceRecordingStore } from "../../../src/stores/VoiceRecordingStore";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/stores/WidgetLayoutStore-test.ts b/test/unit-tests/stores/WidgetLayoutStore-test.ts
index 5697cac3311a4d963fb0432e3795f2c547f4da24..1b5748f3a848bbb49595c293bc36fe1355c77d78 100644
--- a/test/unit-tests/stores/WidgetLayoutStore-test.ts
+++ b/test/unit-tests/stores/WidgetLayoutStore-test.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
-import WidgetStore, { IApp } from "../../../src/stores/WidgetStore";
+import WidgetStore, { type IApp } from "../../../src/stores/WidgetStore";
 import { Container, WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore";
 import { stubClient } from "../../test-utils";
 import defaultDispatcher from "../../../src/dispatcher/dispatcher";
diff --git a/test/unit-tests/stores/notifications/RoomNotificationState-test.ts b/test/unit-tests/stores/notifications/RoomNotificationState-test.ts
index 88847b42e85b6903ffe27e78792981eb44580ef8..91d13809ec612a202836ec39346731fc73acdd62 100644
--- a/test/unit-tests/stores/notifications/RoomNotificationState-test.ts
+++ b/test/unit-tests/stores/notifications/RoomNotificationState-test.ts
@@ -24,6 +24,9 @@ import { RoomNotificationState } from "../../../../src/stores/notifications/Room
 import { NotificationStateEvents } from "../../../../src/stores/notifications/NotificationState";
 import { NotificationLevel } from "../../../../src/stores/notifications/NotificationLevel";
 import { createMessageEventContent } from "../../../test-utils/events";
+import SettingsStore from "../../../../src/settings/SettingsStore";
+import * as RoomStatusBarModule from "../../../../src/components/structures/RoomStatusBar";
+import * as UnreadModule from "../../../../src/Unread";
 
 describe("RoomNotificationState", () => {
     let room: Room;
@@ -36,6 +39,10 @@ describe("RoomNotificationState", () => {
         });
     });
 
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
     function addThread(room: Room): void {
         const threadId = "thread_id";
         jest.spyOn(room, "eventShouldLiveIn").mockReturnValue({
@@ -200,4 +207,85 @@ describe("RoomNotificationState", () => {
         expect(roomNotifState.level).toBe(NotificationLevel.Activity);
         expect(roomNotifState.symbol).toBe(null);
     });
+
+    describe("computed attributes", () => {
+        beforeEach(() => {
+            jest.spyOn(RoomStatusBarModule, "getUnsentMessages").mockReturnValue([]);
+            jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(false);
+        });
+
+        it("should has invited at true", () => {
+            room.updateMyMembership(KnownMembership.Invite);
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.invited).toBe(true);
+        });
+
+        it("should has isUnsetMessage at true", () => {
+            jest.spyOn(RoomStatusBarModule, "getUnsentMessages").mockReturnValue([{} as MatrixEvent]);
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.isUnsentMessage).toBe(true);
+        });
+
+        it("should has isMention at false if the notification is invitation, an unset message or a knock", () => {
+            setUnreads(room, 0, 2);
+
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.isMention).toBe(true);
+
+            room.updateMyMembership(KnownMembership.Invite);
+            expect(roomNotifState.isMention).toBe(false);
+
+            jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
+            room.updateMyMembership(KnownMembership.Knock);
+            expect(roomNotifState.isMention).toBe(false);
+
+            jest.spyOn(RoomStatusBarModule, "getUnsentMessages").mockReturnValue([{} as MatrixEvent]);
+            room.updateMyMembership(KnownMembership.Join);
+            expect(roomNotifState.isMention).toBe(false);
+        });
+
+        it("should has isNotification at true", () => {
+            setUnreads(room, 1, 0);
+
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.isNotification).toBe(true);
+        });
+
+        it("should has isActivityNotification at true", () => {
+            jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(true);
+
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.isActivityNotification).toBe(true);
+        });
+
+        it("should has hasAnyNotificationOrActivity at true", () => {
+            // Hidebold is disabled
+            jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
+            // Unread message, generate activity notification
+            jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(true);
+            // Highlight notification
+            setUnreads(room, 0, 1);
+
+            // There is one highlight notification
+            const roomNotifState = new RoomNotificationState(room, false);
+            expect(roomNotifState.hasAnyNotificationOrActivity).toBe(true);
+
+            // Activity notification
+            setUnreads(room, 0, 0);
+            // Trigger update
+            room.updateMyMembership(KnownMembership.Join);
+            // hidebold is disabled and we have an activity notification
+            expect(roomNotifState.hasAnyNotificationOrActivity).toBe(true);
+
+            // hidebold is enabled and we have an activity notification
+            jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
+            room.updateMyMembership(KnownMembership.Join);
+            expect(roomNotifState.hasAnyNotificationOrActivity).toBe(false);
+
+            // No unread
+            jest.spyOn(UnreadModule, "doesRoomHaveUnreadMessages").mockReturnValue(false);
+            room.updateMyMembership(KnownMembership.Join);
+            expect(roomNotifState.hasAnyNotificationOrActivity).toBe(false);
+        });
+    });
 });
diff --git a/test/unit-tests/stores/oidc/OidcClientStore-test.ts b/test/unit-tests/stores/oidc/OidcClientStore-test.ts
index 8de1d9dad10529f99eb37f2f48c6d81096e6c3d9..164f90f53199a298db6904c6aa4a337f24740a87 100644
--- a/test/unit-tests/stores/oidc/OidcClientStore-test.ts
+++ b/test/unit-tests/stores/oidc/OidcClientStore-test.ts
@@ -15,7 +15,7 @@ import { OidcError } from "matrix-js-sdk/src/oidc/error";
 
 import { OidcClientStore } from "../../../../src/stores/oidc/OidcClientStore";
 import { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils";
-import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
+import { makeDelegatedAuthConfig } from "../../../test-utils/oidc";
 
 jest.mock("matrix-js-sdk/src/matrix", () => ({
     ...jest.requireActual("matrix-js-sdk/src/matrix"),
@@ -24,28 +24,30 @@ jest.mock("matrix-js-sdk/src/matrix", () => ({
 
 describe("OidcClientStore", () => {
     const clientId = "test-client-id";
-    const metadata = mockOpenIdConfiguration();
-    const account = metadata.issuer + "account";
+    const authConfig = makeDelegatedAuthConfig();
+    const account = authConfig.issuer + "account";
 
     const mockClient = getMockClientWithEventEmitter({
-        getAuthIssuer: jest.fn(),
+        getAuthMetadata: jest.fn(),
     });
 
     beforeEach(() => {
         localStorage.clear();
         localStorage.setItem("mx_oidc_client_id", clientId);
-        localStorage.setItem("mx_oidc_token_issuer", metadata.issuer);
-
-        mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
-            metadata,
-            accountManagementEndpoint: account,
-            authorizationEndpoint: "authorization-endpoint",
-            tokenEndpoint: "token-endpoint",
-        });
+        localStorage.setItem("mx_oidc_token_issuer", authConfig.issuer);
+
+        mocked(discoverAndValidateOIDCIssuerWellKnown)
+            .mockClear()
+            .mockResolvedValue({
+                ...authConfig,
+                account_management_uri: account,
+                authorization_endpoint: "authorization-endpoint",
+                token_endpoint: "token-endpoint",
+            });
         jest.spyOn(logger, "error").mockClear();
 
-        fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata);
-        fetchMock.get(`${metadata.issuer}jwks`, { keys: [] });
+        fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig);
+        fetchMock.get(`${authConfig.issuer}jwks`, { keys: [] });
         mockPlatformPeg();
     });
 
@@ -116,7 +118,7 @@ describe("OidcClientStore", () => {
             const client = await store.getOidcClient();
 
             expect(client?.settings.client_id).toEqual(clientId);
-            expect(client?.settings.authority).toEqual(metadata.issuer);
+            expect(client?.settings.authority).toEqual(authConfig.issuer);
         });
 
         it("should set account management endpoint when configured", async () => {
@@ -129,17 +131,19 @@ describe("OidcClientStore", () => {
         });
 
         it("should set account management endpoint to issuer when not configured", async () => {
-            mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({
-                metadata,
-                accountManagementEndpoint: undefined,
-                authorizationEndpoint: "authorization-endpoint",
-                tokenEndpoint: "token-endpoint",
-            });
+            mocked(discoverAndValidateOIDCIssuerWellKnown)
+                .mockClear()
+                .mockResolvedValue({
+                    ...authConfig,
+                    account_management_uri: undefined,
+                    authorization_endpoint: "authorization-endpoint",
+                    token_endpoint: "token-endpoint",
+                });
             const store = new OidcClientStore(mockClient);
 
             await store.readyPromise;
 
-            expect(store.accountManagementEndpoint).toEqual(metadata.issuer);
+            expect(store.accountManagementEndpoint).toEqual(authConfig.issuer);
         });
 
         it("should reuse initialised oidc client", async () => {
@@ -175,7 +179,7 @@ describe("OidcClientStore", () => {
 
             fetchMock.resetHistory();
             fetchMock.post(
-                metadata.revocation_endpoint,
+                authConfig.revocation_endpoint,
                 {
                     status: 200,
                 },
@@ -197,7 +201,7 @@ describe("OidcClientStore", () => {
 
             await store.revokeTokens(accessToken, refreshToken);
 
-            expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
+            expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
             expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
             expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(refreshToken, "refresh_token");
         });
@@ -206,14 +210,14 @@ describe("OidcClientStore", () => {
             // fail once, then succeed
             fetchMock
                 .postOnce(
-                    metadata.revocation_endpoint,
+                    authConfig.revocation_endpoint,
                     {
                         status: 404,
                     },
                     { overwriteRoutes: true, sendAsJson: true },
                 )
                 .post(
-                    metadata.revocation_endpoint,
+                    authConfig.revocation_endpoint,
                     {
                         status: 200,
                     },
@@ -226,7 +230,7 @@ describe("OidcClientStore", () => {
                 "Failed to revoke tokens",
             );
 
-            expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint);
+            expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint);
             expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token");
         });
     });
@@ -237,7 +241,10 @@ describe("OidcClientStore", () => {
         });
 
         it("should resolve account management endpoint", async () => {
-            mockClient.getAuthIssuer.mockResolvedValue({ issuer: metadata.issuer });
+            mockClient.getAuthMetadata.mockResolvedValue({
+                ...authConfig,
+                account_management_uri: account,
+            });
             const store = new OidcClientStore(mockClient);
             await store.readyPromise;
             expect(store.accountManagementEndpoint).toBe(account);
diff --git a/test/unit-tests/stores/right-panel/RightPanelStore-test.ts b/test/unit-tests/stores/right-panel/RightPanelStore-test.ts
index 1897649cee1c97f18d16271561bf5659ca33da70..bed26a1cac1f70465f75bec6c5e2b6aa979a8124 100644
--- a/test/unit-tests/stores/right-panel/RightPanelStore-test.ts
+++ b/test/unit-tests/stores/right-panel/RightPanelStore-test.ts
@@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, MockedObject } from "jest-mock";
-import { MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
+import { mocked, type MockedObject } from "jest-mock";
+import { type MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
 
 import { stubClient } from "../../../test-utils";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
 import { Action } from "../../../../src/dispatcher/actions";
 import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
-import { ActiveRoomChangedPayload } from "../../../../src/dispatcher/payloads/ActiveRoomChangedPayload";
+import { type ActiveRoomChangedPayload } from "../../../../src/dispatcher/payloads/ActiveRoomChangedPayload";
 import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore";
 import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
 import SettingsStore from "../../../../src/settings/SettingsStore";
@@ -114,11 +114,14 @@ describe("RightPanelStore", () => {
             expect(store.isOpenForRoom("!1:example.org")).toEqual(true);
             expect(store.currentCardForRoom("!1:example.org").phase).toEqual(RightPanelPhases.RoomSummary);
         });
-        it("overwrites history if changing the phase", async () => {
+        it("history is generated for certain phases", async () => {
             await viewRoom("!1:example.org");
-            store.setCard({ phase: RightPanelPhases.RoomSummary }, true, "!1:example.org");
+            // Setting the memberlist card should also generate a history with room summary card
             store.setCard({ phase: RightPanelPhases.MemberList }, true, "!1:example.org");
-            expect(store.roomPhaseHistory).toEqual([{ phase: RightPanelPhases.MemberList, state: {} }]);
+            expect(store.roomPhaseHistory).toEqual([
+                { phase: RightPanelPhases.RoomSummary, state: {} },
+                { phase: RightPanelPhases.MemberList, state: {} },
+            ]);
         });
     });
 
diff --git a/test/unit-tests/stores/right-panel/action-handlers/View3pidInvite-test.ts b/test/unit-tests/stores/right-panel/action-handlers/View3pidInvite-test.ts
index 757c1b93b48db4b61b04d37b8ba269873cd4c9ac..3230101cb71a4a78208de776481c6f48032fb591 100644
--- a/test/unit-tests/stores/right-panel/action-handlers/View3pidInvite-test.ts
+++ b/test/unit-tests/stores/right-panel/action-handlers/View3pidInvite-test.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MockedObject } from "jest-mock";
+import { type MockedObject } from "jest-mock";
 import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
 
 import { Action } from "../../../../../src/dispatcher/actions";
 import { onView3pidInvite } from "../../../../../src/stores/right-panel/action-handlers";
-import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
+import type RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
 import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
 
 describe("onView3pidInvite()", () => {
diff --git a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c0cdfc091f75e1da74b094e4aa40aff48082e5f
--- /dev/null
+++ b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts
@@ -0,0 +1,801 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { EventType, KnownMembership, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { logger } from "matrix-js-sdk/src/logger";
+
+import type { MatrixClient } from "matrix-js-sdk/src/matrix";
+import type { RoomNotificationState } from "../../../../src/stores/notifications/RoomNotificationState";
+import { LISTS_UPDATE_EVENT, RoomListStoreV3Class } from "../../../../src/stores/room-list-v3/RoomListStoreV3";
+import { AsyncStoreWithClient } from "../../../../src/stores/AsyncStoreWithClient";
+import { RecencySorter } from "../../../../src/stores/room-list-v3/skip-list/sorters/RecencySorter";
+import { mkEvent, mkMessage, mkSpace, mkStubRoom, stubClient, upsertRoomStateEvents } from "../../../test-utils";
+import { getMockedRooms } from "./skip-list/getMockedRooms";
+import { AlphabeticSorter } from "../../../../src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter";
+import dispatcher from "../../../../src/dispatcher/dispatcher";
+import SpaceStore from "../../../../src/stores/spaces/SpaceStore";
+import { MetaSpace, UPDATE_SELECTED_SPACE } from "../../../../src/stores/spaces";
+import { DefaultTagID } from "../../../../src/stores/room-list/models";
+import { FilterKey } from "../../../../src/stores/room-list-v3/skip-list/filters";
+import { RoomNotificationStateStore } from "../../../../src/stores/notifications/RoomNotificationStateStore";
+import DMRoomMap from "../../../../src/utils/DMRoomMap";
+import { SortingAlgorithm } from "../../../../src/stores/room-list-v3/skip-list/sorters";
+import SettingsStore from "../../../../src/settings/SettingsStore";
+import * as utils from "../../../../src/utils/notifications";
+import * as roomMute from "../../../../src/stores/room-list/utils/roomMute";
+import { Action } from "../../../../src/dispatcher/actions";
+
+describe("RoomListStoreV3", () => {
+    async function getRoomListStore() {
+        const client = stubClient();
+        const rooms = getMockedRooms(client);
+        client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
+        jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client);
+        const store = new RoomListStoreV3Class(dispatcher);
+        await store.start();
+        return { client, rooms, store, dispatcher };
+    }
+
+    beforeEach(() => {
+        jest.spyOn(SpaceStore.instance, "isRoomInSpace").mockImplementation((space) => space === MetaSpace.Home);
+        jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => MetaSpace.Home);
+        jest.spyOn(SpaceStore.instance, "storeReadyPromise", "get").mockImplementation(() => Promise.resolve());
+        jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
+            const state = {
+                isUnread: false,
+            } as unknown as RoomNotificationState;
+            return state;
+        });
+        jest.spyOn(DMRoomMap, "shared").mockImplementation((() => {
+            return {
+                getUserIdForRoomId: (id) => "",
+            };
+        }) as () => DMRoomMap);
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    it("Provides an unsorted list of rooms", async () => {
+        const { store, rooms } = await getRoomListStore();
+        expect(store.getRooms()).toEqual(rooms);
+    });
+
+    it("Provides a sorted list of rooms", async () => {
+        const { store, rooms, client } = await getRoomListStore();
+        const sorter = new RecencySorter(client.getSafeUserId());
+        const sortedRooms = sorter.sort(rooms);
+        expect(store.getSortedRooms()).toEqual(sortedRooms);
+    });
+
+    it("Provides a way to resort", async () => {
+        const { store, rooms, client } = await getRoomListStore();
+
+        // List is sorted by recency, sort by alphabetical now
+        store.resort(SortingAlgorithm.Alphabetic);
+        let sortedRooms = new AlphabeticSorter().sort(rooms);
+        expect(store.getSortedRooms()).toEqual(sortedRooms);
+        expect(store.activeSortAlgorithm).toEqual(SortingAlgorithm.Alphabetic);
+
+        // Go back to recency sorting
+        store.resort(SortingAlgorithm.Recency);
+        sortedRooms = new RecencySorter(client.getSafeUserId()).sort(rooms);
+        expect(store.getSortedRooms()).toEqual(sortedRooms);
+        expect(store.activeSortAlgorithm).toEqual(SortingAlgorithm.Recency);
+    });
+
+    it("Uses preferred sorter on startup", async () => {
+        jest.spyOn(SettingsStore, "getValue").mockImplementation(() => {
+            return SortingAlgorithm.Alphabetic;
+        });
+        const { store } = await getRoomListStore();
+        expect(store.activeSortAlgorithm).toEqual(SortingAlgorithm.Alphabetic);
+    });
+
+    describe("Updates", () => {
+        it("Room is re-inserted on timeline event", async () => {
+            const { store, rooms, dispatcher } = await getRoomListStore();
+
+            // Let's pretend like a new timeline event came on the room in 37th index.
+            const room = rooms[37];
+            const event = mkMessage({ room: room.roomId, user: `@foo${3}:matrix.org`, ts: 1000, event: true });
+            room.timeline.push(event);
+
+            const payload = {
+                action: "MatrixActions.Room.timeline",
+                event,
+                isLiveEvent: true,
+                isLiveUnfilteredRoomTimelineEvent: true,
+                room,
+            };
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(payload, true);
+
+            expect(fn).toHaveBeenCalled();
+            expect(store.getSortedRooms()[0].roomId).toEqual(room.roomId);
+        });
+
+        it("Forgotten room is removed", async () => {
+            const { store, rooms, dispatcher } = await getRoomListStore();
+            const room = rooms[37];
+
+            // Room at index 37 should be in the store now
+            expect(store.getSortedRooms().map((r) => r.roomId)).toContain(room.roomId);
+
+            // Forget room at index 37
+            const payload = {
+                action: Action.AfterForgetRoom,
+                room: room,
+            };
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(payload, true);
+
+            // Room at index 37 should no longer be in the store
+            expect(fn).toHaveBeenCalled();
+            expect(store.getSortedRooms().map((r) => r.roomId)).not.toContain(room.roomId);
+        });
+
+        it.each([KnownMembership.Join, KnownMembership.Invite])(
+            "Room is removed when membership changes to leave",
+            async (membership) => {
+                const { store, rooms, dispatcher } = await getRoomListStore();
+
+                // Let's say the user leaves room at index 37
+                const room = rooms[37];
+
+                const payload = {
+                    action: "MatrixActions.Room.myMembership",
+                    oldMembership: membership,
+                    membership: KnownMembership.Leave,
+                    room,
+                };
+
+                const fn = jest.fn();
+                store.on(LISTS_UPDATE_EVENT, fn);
+                dispatcher.dispatch(payload, true);
+
+                expect(fn).toHaveBeenCalled();
+                expect(store.getSortedRooms()).not.toContain(room);
+            },
+        );
+
+        it("Room is not removed when user is kicked", async () => {
+            const { store, rooms, dispatcher, client } = await getRoomListStore();
+
+            // Let's say the user gets kicked out of room at index 37
+            const room = rooms[37];
+            const mockMember = room.getMember(client.getSafeUserId())!;
+            mockMember.isKicked = () => true;
+            room.getMember = () => mockMember;
+
+            const payload = {
+                action: "MatrixActions.Room.myMembership",
+                oldMembership: KnownMembership.Join,
+                membership: KnownMembership.Leave,
+                room,
+            };
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(payload, true);
+
+            expect(fn).toHaveBeenCalled();
+            expect(store.getSortedRooms()).toContain(room);
+        });
+
+        it("Predecessor room is removed on room upgrade", async () => {
+            const { store, rooms, client, dispatcher } = await getRoomListStore();
+            // Let's say that !foo32:matrix.org is being upgraded
+            const oldRoom = rooms[32];
+            // Create a new room with a predecessor event that points to oldRoom
+            const newRoom = new Room("!foonew:matrix.org", client, client.getSafeUserId(), {});
+            const createWithPredecessor = new MatrixEvent({
+                type: EventType.RoomCreate,
+                sender: "@foo:foo.org",
+                room_id: newRoom.roomId,
+                content: {
+                    predecessor: { room_id: oldRoom.roomId, event_id: "tombstone_event_id" },
+                },
+                event_id: "$create",
+                state_key: "",
+            });
+            upsertRoomStateEvents(newRoom, [createWithPredecessor]);
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(
+                {
+                    action: "MatrixActions.Room.myMembership",
+                    oldMembership: KnownMembership.Invite,
+                    membership: KnownMembership.Join,
+                    room: newRoom,
+                },
+                true,
+            );
+
+            expect(fn).toHaveBeenCalled();
+            const roomIds = store.getSortedRooms().map((r) => r.roomId);
+            expect(roomIds).not.toContain(oldRoom.roomId);
+            expect(roomIds).toContain(newRoom.roomId);
+        });
+
+        it("Rooms are re-inserted on m.direct event", async () => {
+            const { store, dispatcher, client } = await getRoomListStore();
+
+            // Let's mock the client to return new rooms with the name "My DM Room"
+            client.getRoom = (roomId: string) => mkStubRoom(roomId, "My DM Room", client);
+
+            // Let's create a m.direct event that we can dispatch
+            const content = {
+                "@bar1:matrix.org": ["!foo1:matrix.org", "!foo2:matrix.org"],
+                "@bar2:matrix.org": ["!foo3:matrix.org", "!foo4:matrix.org"],
+                "@bar3:matrix.org": ["!foo5:matrix.org"],
+            };
+            const event = mkEvent({
+                event: true,
+                content,
+                user: "@foo:matrix.org",
+                type: EventType.Direct,
+            });
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+
+            // Do the actual dispatch
+            dispatcher.dispatch(
+                {
+                    action: "MatrixActions.accountData",
+                    event_type: EventType.Direct,
+                    event,
+                },
+                true,
+            );
+
+            // Ensure only one emit occurs
+            expect(fn).toHaveBeenCalledTimes(1);
+
+            /*
+             When the dispatched event is processed by the room-list, the associated
+             rooms will be fetched via client.getRoom and will be re-inserted into the
+             skip list. We can confirm that this happened by checking if all the dm rooms
+             have the same name ("My DM Room") since we've mocked the client to return such rooms.
+             */
+            const ids = [
+                "!foo1:matrix.org",
+                "!foo2:matrix.org",
+                "!foo3:matrix.org",
+                "!foo4:matrix.org",
+                "!foo5:matrix.org",
+            ];
+            const rooms = store.getSortedRooms().filter((r) => ids.includes(r.roomId));
+            rooms.forEach((room) => expect(room.name).toBe("My DM Room"));
+        });
+
+        it("Room is re-inserted on tag change", async () => {
+            const { store, rooms, dispatcher } = await getRoomListStore();
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(
+                {
+                    action: "MatrixActions.Room.tags",
+                    room: rooms[10],
+                },
+                true,
+            );
+            expect(fn).toHaveBeenCalled();
+        });
+
+        it("Room is re-inserted on decryption", async () => {
+            const { store, rooms, client, dispatcher } = await getRoomListStore();
+            jest.spyOn(client, "getRoom").mockImplementation(() => rooms[10]);
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+            dispatcher.dispatch(
+                {
+                    action: "MatrixActions.Event.decrypted",
+                    event: { getRoomId: () => rooms[10].roomId },
+                },
+                true,
+            );
+            expect(fn).toHaveBeenCalled();
+        });
+
+        it("Logs a warning if room couldn't be found from room-id on decryption action", async () => {
+            const { store, client, dispatcher } = await getRoomListStore();
+            jest.spyOn(client, "getRoom").mockImplementation(() => null);
+            const warnSpy = jest.spyOn(logger, "warn");
+
+            const fn = jest.fn();
+            store.on(LISTS_UPDATE_EVENT, fn);
+
+            // Dispatch a decrypted action but the room does not exist.
+            dispatcher.dispatch(
+                {
+                    action: "MatrixActions.Event.decrypted",
+                    event: {
+                        getRoomId: () => "!doesnotexist:matrix.org",
+                        getId: () => "some-id",
+                    },
+                },
+                true,
+            );
+
+            expect(warnSpy).toHaveBeenCalled();
+            expect(fn).not.toHaveBeenCalled();
+        });
+
+        describe("Update from read receipt", () => {
+            function getReadReceiptEvent(userId: string) {
+                const content = {
+                    some_id: {
+                        "m.read": {
+                            [userId]: {
+                                ts: 5000,
+                            },
+                        },
+                    },
+                };
+                const event = mkEvent({
+                    event: true,
+                    content,
+                    user: "@foo:matrix.org",
+                    type: EventType.Receipt,
+                });
+                return event;
+            }
+
+            it("Room is re-inserted on read receipt from our user", async () => {
+                const { store, rooms, client, dispatcher } = await getRoomListStore();
+                const event = getReadReceiptEvent(client.getSafeUserId());
+                const fn = jest.fn();
+                store.on(LISTS_UPDATE_EVENT, fn);
+                dispatcher.dispatch(
+                    {
+                        action: "MatrixActions.Room.receipt",
+                        room: rooms[10],
+                        event,
+                    },
+                    true,
+                );
+                expect(fn).toHaveBeenCalled();
+            });
+
+            it("Read receipt from other users do not cause room to be re-inserted", async () => {
+                const { store, rooms, dispatcher } = await getRoomListStore();
+                const event = getReadReceiptEvent("@foobar:matrix.org");
+                const fn = jest.fn();
+                store.on(LISTS_UPDATE_EVENT, fn);
+                dispatcher.dispatch(
+                    {
+                        action: "MatrixActions.Room.receipt",
+                        room: rooms[10],
+                        event,
+                    },
+                    true,
+                );
+                expect(fn).not.toHaveBeenCalled();
+            });
+        });
+
+        /**
+         * Create a space and add it to rooms
+         * @param rooms An array of rooms to which the new space is added.
+         * @param inSpaceIndices  A list of indices from which rooms are added to the space.
+         */
+        function createSpace(rooms: Room[], inSpaceIndices: number[], client: MatrixClient) {
+            const roomIds = inSpaceIndices.map((i) => rooms[i].roomId);
+            const spaceRoom = mkSpace(client, "!space1:matrix.org", [], roomIds);
+            rooms.push(spaceRoom);
+            return { spaceRoom, roomIds };
+        }
+
+        function setupMocks(spaceRoom: Room, roomIds: string[]) {
+            jest.spyOn(SpaceStore.instance, "isRoomInSpace").mockImplementation((space, id) => {
+                if (space === MetaSpace.Home && !roomIds.includes(id)) return true;
+                if (space === spaceRoom.roomId && roomIds.includes(id)) return true;
+                return false;
+            });
+            jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => spaceRoom.roomId);
+        }
+
+        function getClientAndRooms() {
+            const client = stubClient();
+            const rooms = getMockedRooms(client);
+            client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
+            jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client);
+            return { client, rooms };
+        }
+
+        describe("Spaces", () => {
+            it("Newly created space is not added by the store", async () => {
+                const { client, rooms } = getClientAndRooms();
+                const infoSpy = jest.spyOn(logger, "info");
+
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Create a space and let the store know about it
+                const { spaceRoom } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+                dispatcher.dispatch(
+                    {
+                        action: "MatrixActions.Room.myMembership",
+                        oldMembership: KnownMembership.Leave,
+                        membership: KnownMembership.Invite,
+                        room: spaceRoom,
+                    },
+                    true,
+                );
+
+                // Space room should not be added
+                expect(store.getSortedRooms()).not.toContain(spaceRoom);
+                expect(infoSpy).toHaveBeenCalledWith(
+                    expect.stringContaining("RoomListStoreV3: Refusing to add new room"),
+                );
+            });
+
+            it("Filtering by spaces work", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Mock the space store
+                jest.spyOn(SpaceStore.instance, "isRoomInSpace").mockImplementation((space, id) => {
+                    if (space === MetaSpace.Home && !roomIds.includes(id)) return true;
+                    if (space === spaceRoom.roomId && roomIds.includes(id)) return true;
+                    return false;
+                });
+
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+                const fn = jest.fn();
+                store.on(LISTS_UPDATE_EVENT, fn);
+
+                // The rooms which belong to the space should not be shown
+                const result = store.getSortedRoomsInActiveSpace().map((r) => r.roomId);
+                for (const id of roomIds) {
+                    expect(result).not.toContain(id);
+                }
+
+                // Lets switch to the space
+                jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => spaceRoom.roomId);
+                SpaceStore.instance.emit(UPDATE_SELECTED_SPACE);
+                expect(fn).toHaveBeenCalled();
+                const result2 = store.getSortedRoomsInActiveSpace().map((r) => r.roomId);
+                for (const id of roomIds) {
+                    expect(result2).toContain(id);
+                }
+            });
+        });
+
+        describe("Filters", () => {
+            it("filters by both space and favourite", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say that 8, 27 an 75 are favourite rooms
+                [8, 27, 75].forEach((i) => {
+                    rooms[i].tags[DefaultTagID.Favourite] = {};
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Sorted, filtered rooms should be 8, 27 and 75
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.FavouriteFilter]);
+                expect(result).toHaveLength(3);
+                for (const i of [8, 27, 75]) {
+                    expect(result).toContain(rooms[i]);
+                }
+            });
+
+            it("filters are recalculated on room update", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say that 8, 27 an 75 are favourite rooms
+                [8, 27, 75].forEach((i) => {
+                    rooms[i].tags[DefaultTagID.Favourite] = {};
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Let's say 27 got unfavourited
+                const fn = jest.fn();
+                store.on(LISTS_UPDATE_EVENT, fn);
+                rooms[27].tags = {};
+                dispatcher.dispatch(
+                    {
+                        action: "MatrixActions.Room.tags",
+                        room: rooms[27],
+                    },
+                    true,
+                );
+                expect(fn).toHaveBeenCalled();
+
+                // Sorted, filtered rooms should be 27 and 75
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.FavouriteFilter]);
+                expect(result).toHaveLength(2);
+                for (const i of [8, 75]) {
+                    expect(result).toContain(rooms[i]);
+                }
+            });
+
+            it("supports filtering unread rooms", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say 8, 27 are unread
+                jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
+                    const state = {
+                        hasUnreadCount: [rooms[8], rooms[27]].includes(room),
+                    } as unknown as RoomNotificationState;
+                    return state;
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Should only give us rooms at index 8 and 27
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.UnreadFilter]);
+                expect(result).toHaveLength(2);
+                for (const i of [8, 27]) {
+                    expect(result).toContain(rooms[i]);
+                }
+            });
+
+            it("unread filter matches rooms that are marked as unread", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Since there's no unread yet, we expect zero results
+                let result = store.getSortedRoomsInActiveSpace([FilterKey.UnreadFilter]);
+                expect(result).toHaveLength(0);
+
+                // Mock so that room at index 8 is marked as unread
+                jest.spyOn(utils, "getMarkedUnreadState").mockImplementation((room) => room.roomId === rooms[8].roomId);
+                dispatcher.dispatch(
+                    {
+                        action: "MatrixActions.Room.accountData",
+                        room: rooms[8],
+                        event_type: utils.MARKED_UNREAD_TYPE_STABLE,
+                    },
+                    true,
+                );
+
+                // Now we expect room at index 8 to show as unread
+                result = store.getSortedRoomsInActiveSpace([FilterKey.UnreadFilter]);
+                expect(result).toHaveLength(1);
+                expect(result).toContain(rooms[8]);
+            });
+
+            it("supports filtering by people and rooms", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say 8, 27 are dms
+                const ids = [8, 27].map((i) => rooms[i].roomId);
+                jest.spyOn(DMRoomMap, "shared").mockImplementation((() => {
+                    return {
+                        getUserIdForRoomId: (id) => (ids.includes(id) ? "@myuser:matrix.org" : ""),
+                    };
+                }) as () => DMRoomMap);
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Should only give us rooms at index 8 and 27
+                const peopleRooms = store.getSortedRoomsInActiveSpace([FilterKey.PeopleFilter]);
+                expect(peopleRooms).toHaveLength(2);
+                for (const i of [8, 27]) {
+                    expect(peopleRooms).toContain(rooms[i]);
+                }
+
+                // Rest are normal rooms
+                const nonDms = store.getSortedRoomsInActiveSpace([FilterKey.RoomsFilter]);
+                expect(nonDms).toHaveLength(3);
+                for (const i of [6, 13, 75]) {
+                    expect(nonDms).toContain(rooms[i]);
+                }
+            });
+
+            it("supports filtering invited rooms", async () => {
+                const { client, rooms } = getClientAndRooms();
+
+                // Let's add 5 rooms that we are invited to
+                const invitedRooms = getMockedRooms(client, 5);
+                for (const room of invitedRooms) {
+                    room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Invite);
+                }
+
+                rooms.push(...invitedRooms);
+
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 100, 101, 102, 103, 104], client);
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.InvitesFilter]);
+                expect(result).toHaveLength(5);
+                for (const room of invitedRooms) {
+                    expect(result).toContain(room);
+                }
+            });
+
+            it("supports filtering by mentions", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say 8, 27 have mentions
+                jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
+                    const state = {
+                        isMention: [rooms[8], rooms[27]].includes(room),
+                    } as unknown as RoomNotificationState;
+                    return state;
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Should only give us rooms at index 8 and 27
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.MentionsFilter]);
+                expect(result).toHaveLength(2);
+                for (const i of [8, 27]) {
+                    expect(result).toContain(rooms[i]);
+                }
+            });
+
+            it("supports filtering low priority rooms", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say that 8, 27 an 75 are low priority rooms
+                [8, 27, 75].forEach((i) => {
+                    rooms[i].tags[DefaultTagID.LowPriority] = {};
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Sorted, filtered rooms should be 8, 27 and 75
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.LowPriorityFilter]);
+                expect(result).toHaveLength(3);
+                for (const i of [8, 27, 75]) {
+                    expect(result).toContain(rooms[i]);
+                }
+            });
+
+            it("supports multiple filters", async () => {
+                const { client, rooms } = getClientAndRooms();
+                // Let's choose 5 rooms to put in space
+                const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client);
+
+                // Let's say that 8 is a favourite room
+                rooms[8].tags[DefaultTagID.Favourite] = {};
+
+                // Let's say 8, 27 are unread
+                jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
+                    const state = {
+                        hasUnreadCount: [rooms[8], rooms[27]].includes(room),
+                    } as unknown as RoomNotificationState;
+                    return state;
+                });
+
+                setupMocks(spaceRoom, roomIds);
+                const store = new RoomListStoreV3Class(dispatcher);
+                await store.start();
+
+                // Should give us only room at 8 since that's the only room which matches both filters
+                const result = store.getSortedRoomsInActiveSpace([FilterKey.UnreadFilter, FilterKey.FavouriteFilter]);
+                expect(result).toHaveLength(1);
+                expect(result).toContain(rooms[8]);
+            });
+        });
+    });
+
+    describe("Muted rooms", () => {
+        async function getRoomListStoreWithMutedRooms() {
+            const client = stubClient();
+            const rooms = getMockedRooms(client);
+
+            // Let's say that rooms 34, 84, 64, 14, 57 are muted
+            const mutedIndices = [34, 84, 64, 14, 57];
+            const mutedRooms = mutedIndices.map((i) => rooms[i]);
+            jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
+                const state = {
+                    muted: mutedRooms.includes(room),
+                } as unknown as RoomNotificationState;
+                return state;
+            });
+
+            client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
+            jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client);
+            const store = new RoomListStoreV3Class(dispatcher);
+            await store.start();
+            return { client, rooms, mutedIndices, mutedRooms, store, dispatcher };
+        }
+
+        it("Muted rooms are sorted to the bottom of the list", async () => {
+            const { store, mutedRooms, client } = await getRoomListStoreWithMutedRooms();
+            const lastFiveRooms = store.getSortedRooms().slice(95);
+            const expectedRooms = new RecencySorter(client.getSafeUserId()).sort(mutedRooms);
+            // We expect the muted rooms to be at the bottom sorted by recency
+            expect(lastFiveRooms).toEqual(expectedRooms);
+        });
+
+        it("Muted rooms are sorted within themselves", async () => {
+            const { store, rooms } = await getRoomListStoreWithMutedRooms();
+
+            // Let's say that rooms 14 and 34 get new messages in that order
+            let ts = 1000;
+            for (const room of [rooms[14], rooms[34]]) {
+                const event = mkMessage({ room: room.roomId, user: `@foo${3}:matrix.org`, ts: 1000, event: true });
+                room.timeline.push(event);
+
+                const payload = {
+                    action: "MatrixActions.Room.timeline",
+                    event,
+                    isLiveEvent: true,
+                    isLiveUnfilteredRoomTimelineEvent: true,
+                    room,
+                };
+                dispatcher.dispatch(payload, true);
+                ts = ts + 1;
+            }
+
+            const lastFiveRooms = store.getSortedRooms().slice(95);
+            // The order previously would  have been 84, 64, 57, 34, 14
+            // Expected new order is 34, 14, 84, 64, 57
+            const expectedRooms = [rooms[34], rooms[14], rooms[84], rooms[64], rooms[57]];
+            expect(lastFiveRooms).toEqual(expectedRooms);
+        });
+
+        it("Muted room is correctly sorted when unmuted", async () => {
+            const { store, mutedRooms, rooms, client } = await getRoomListStoreWithMutedRooms();
+
+            // Let's say that muted room 64 becomes un-muted.
+            const unmutedRoom = rooms[64];
+            jest.spyOn(roomMute, "getChangedOverrideRoomMutePushRules").mockImplementation(() => [unmutedRoom.roomId]);
+            client.getRoom = jest.fn().mockReturnValue(unmutedRoom);
+            const payload = {
+                action: "MatrixActions.accountData",
+                event_type: EventType.PushRules,
+            };
+            mutedRooms.splice(2, 1);
+            dispatcher.dispatch(payload, true);
+
+            const lastFiveRooms = store.getSortedRooms().slice(95);
+            // We expect room at index 64 to no longer be at the bottom
+            expect(lastFiveRooms).not.toContain(unmutedRoom);
+            // Room 64 should go to index 34 since we're sorting by recency
+            expect(store.getSortedRooms()[34]).toEqual(unmutedRoom);
+        });
+    });
+});
diff --git a/test/unit-tests/stores/room-list-v3/skip-list/RoomSkipList-test.ts b/test/unit-tests/stores/room-list-v3/skip-list/RoomSkipList-test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..671bfface85666233566ab265d533899d6fcf6e1
--- /dev/null
+++ b/test/unit-tests/stores/room-list-v3/skip-list/RoomSkipList-test.ts
@@ -0,0 +1,151 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { shuffle } from "lodash";
+
+import type { Room } from "matrix-js-sdk/src/matrix";
+import type { Sorter } from "../../../../../src/stores/room-list-v3/skip-list/sorters";
+import type { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
+import { mkMessage, stubClient } from "../../../../test-utils";
+import { RoomSkipList } from "../../../../../src/stores/room-list-v3/skip-list/RoomSkipList";
+import { RecencySorter } from "../../../../../src/stores/room-list-v3/skip-list/sorters/RecencySorter";
+import { AlphabeticSorter } from "../../../../../src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter";
+import { getMockedRooms } from "./getMockedRooms";
+import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
+import { MetaSpace } from "../../../../../src/stores/spaces";
+import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
+
+describe("RoomSkipList", () => {
+    function generateSkipList(roomCount?: number): {
+        skipList: RoomSkipList;
+        rooms: Room[];
+        totalRooms: number;
+        sorter: Sorter;
+    } {
+        const client = stubClient();
+        const sorter = new RecencySorter(client.getSafeUserId());
+        const skipList = new RoomSkipList(sorter);
+        const rooms = getMockedRooms(client, roomCount);
+        skipList.seed(rooms);
+        return { skipList, rooms, totalRooms: rooms.length, sorter };
+    }
+
+    beforeEach(() => {
+        jest.spyOn(SpaceStore.instance, "isRoomInSpace").mockImplementation((space) => space === MetaSpace.Home);
+        jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => MetaSpace.Home);
+        jest.spyOn(SpaceStore.instance, "storeReadyPromise", "get").mockImplementation(() => Promise.resolve());
+        jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation(() => {
+            const state = {
+                mute: false,
+            } as unknown as RoomNotificationState;
+            return state;
+        });
+    });
+
+    it("Rooms are in sorted order after initial seed", () => {
+        const { skipList, totalRooms } = generateSkipList();
+        expect(skipList.size).toEqual(totalRooms);
+        const sortedRooms = [...skipList];
+        for (let i = 0; i < totalRooms; ++i) {
+            expect(sortedRooms[i].roomId).toEqual(`!foo${totalRooms - i - 1}:matrix.org`);
+        }
+    });
+
+    it("Tolerates multiple, repeated inserts of existing rooms", () => {
+        const { skipList, rooms, totalRooms } = generateSkipList();
+        // Let's choose 5 rooms from the list
+        const toInsert = [23, 76, 2, 90, 66].map((i) => rooms[i]);
+        for (const room of toInsert) {
+            // Insert this room 10 times
+            for (let i = 0; i < 10; ++i) {
+                skipList.reInsertRoom(room);
+            }
+        }
+        // Sorting order should be the same as before
+        const sortedRooms = [...skipList];
+        for (let i = 0; i < totalRooms; ++i) {
+            expect(sortedRooms[i].roomId).toEqual(`!foo${totalRooms - i - 1}:matrix.org`);
+        }
+    });
+
+    it("Sorting order is maintained when rooms are inserted", () => {
+        const { skipList, rooms, totalRooms } = generateSkipList();
+        // To simulate the worst case, let's say the order gets reversed one by one
+        for (let i = 0; i < rooms.length; ++i) {
+            const room = rooms[i];
+            const event = mkMessage({
+                room: room.roomId,
+                user: `@foo${i}:matrix.org`,
+                ts: totalRooms - i,
+                event: true,
+            });
+            room.timeline.push(event);
+            skipList.reInsertRoom(room);
+            expect(skipList.size).toEqual(rooms.length);
+        }
+        const sortedRooms = [...skipList];
+        for (let i = 0; i < totalRooms; ++i) {
+            expect(sortedRooms[i].roomId).toEqual(`!foo${i}:matrix.org`);
+        }
+    });
+
+    it("Throws error when same room is added via addNewRoom", () => {
+        const { skipList, rooms } = generateSkipList();
+        const room = rooms[5];
+        expect(() => skipList.addNewRoom(room)).toThrow("Can't add room to skiplist");
+    });
+
+    it("Re-sort works when sorter is swapped", () => {
+        const { skipList, rooms, sorter } = generateSkipList();
+        const sortedByRecency = [...rooms].sort((a, b) => sorter.comparator(a, b));
+        expect(sortedByRecency).toEqual([...skipList]);
+        // Now switch over to alphabetic sorter
+        const newSorter = new AlphabeticSorter();
+        skipList.useNewSorter(newSorter, rooms);
+        const sortedByAlphabet = [...rooms].sort((a, b) => newSorter.comparator(a, b));
+        expect(sortedByAlphabet).toEqual([...skipList]);
+    });
+
+    describe("Empty skip list functionality", () => {
+        it("Insertions into empty skip list works", () => {
+            // Create an empty skip list
+            const client = stubClient();
+            const sorter = new RecencySorter(client.getSafeUserId());
+            const roomSkipList = new RoomSkipList(sorter);
+            expect(roomSkipList.size).toEqual(0);
+            roomSkipList.seed([]);
+            expect(roomSkipList.size).toEqual(0);
+
+            // Create some rooms
+            const totalRooms = 10;
+            const rooms = getMockedRooms(client, totalRooms);
+
+            // Shuffle and insert the rooms
+            for (const room of shuffle(rooms)) {
+                roomSkipList.addNewRoom(room);
+            }
+
+            expect(roomSkipList.size).toEqual(totalRooms);
+            const sortedRooms = [...roomSkipList];
+            for (let i = 0; i < totalRooms; ++i) {
+                expect(sortedRooms[i].roomId).toEqual(`!foo${totalRooms - i - 1}:matrix.org`);
+            }
+        });
+
+        it("Tolerates deletions until skip list is empty", () => {
+            const { skipList, rooms } = generateSkipList(10);
+            const sorted = [...skipList];
+            for (const room of shuffle(rooms)) {
+                skipList.removeRoom(room);
+                const i = sorted.findIndex((r) => r.roomId === room.roomId);
+                sorted.splice(i, 1);
+                expect([...skipList]).toEqual(sorted);
+            }
+            expect(skipList.size).toEqual(0);
+        });
+    });
+});
diff --git a/test/unit-tests/stores/room-list-v3/skip-list/getMockedRooms.ts b/test/unit-tests/stores/room-list-v3/skip-list/getMockedRooms.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d895ba944bd81d0376c14d5a7d60dd78eb6a1332
--- /dev/null
+++ b/test/unit-tests/stores/room-list-v3/skip-list/getMockedRooms.ts
@@ -0,0 +1,21 @@
+/*
+Copyright 2025 New Vector Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import type { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { mkMessage, mkStubRoom } from "../../../../test-utils";
+
+export function getMockedRooms(client: MatrixClient, roomCount: number = 100): Room[] {
+    const rooms: Room[] = [];
+    for (let i = 0; i < roomCount; ++i) {
+        const roomId = `!foo${i}:matrix.org`;
+        const room = mkStubRoom(roomId, `Foo Room ${i}`, client);
+        const event = mkMessage({ room: roomId, user: `@foo${i}:matrix.org`, ts: i + 1, event: true });
+        room.timeline.push(event);
+        rooms.push(room);
+    }
+    return rooms;
+}
diff --git a/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts b/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts
index 864fa6c2b47d77cb8788c141bca57d14f809daae..c6d1d9d56cb54d0632b824fd32082afc6e8da5d1 100644
--- a/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts
+++ b/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked, mocked } from "jest-mock";
+import { type Mocked, mocked } from "jest-mock";
 import {
     EventStatus,
     EventTimeline,
     EventType,
-    MatrixClient,
-    MatrixEvent,
+    type MatrixClient,
+    type MatrixEvent,
     PendingEventOrdering,
     RelationType,
     Room,
diff --git a/test/unit-tests/stores/room-list/RoomListStore-test.ts b/test/unit-tests/stores/room-list/RoomListStore-test.ts
index a84ea341bc941e8dd09608fa3c0c9451f99b779e..d1ab2e71832c521f102483a76eb8180b80baf18a 100644
--- a/test/unit-tests/stores/room-list/RoomListStore-test.ts
+++ b/test/unit-tests/stores/room-list/RoomListStore-test.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import {
     ConditionKind,
     EventType,
-    IPushRule,
+    type IPushRule,
     JoinRule,
     MatrixEvent,
     PendingEventOrdering,
@@ -19,9 +19,9 @@ import {
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { mocked } from "jest-mock";
 
-import defaultDispatcher, { MatrixDispatcher } from "../../../../src/dispatcher/dispatcher";
+import defaultDispatcher, { type MatrixDispatcher } from "../../../../src/dispatcher/dispatcher";
 import { SettingLevel } from "../../../../src/settings/SettingLevel";
-import SettingsStore, { CallbackFn } from "../../../../src/settings/SettingsStore";
+import SettingsStore, { type CallbackFn } from "../../../../src/settings/SettingsStore";
 import { ListAlgorithm, SortAlgorithm } from "../../../../src/stores/room-list/algorithms/models";
 import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause } from "../../../../src/stores/room-list/models";
 import RoomListStore, { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore";
diff --git a/test/unit-tests/stores/room-list/SlidingRoomListStore-test.ts b/test/unit-tests/stores/room-list/SlidingRoomListStore-test.ts
deleted file mode 100644
index 40d8cbbc65831ea9e053322c74af1e920e906c3c..0000000000000000000000000000000000000000
--- a/test/unit-tests/stores/room-list/SlidingRoomListStore-test.ts
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-import { mocked } from "jest-mock";
-import { SlidingSync, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
-import { Room } from "matrix-js-sdk/src/matrix";
-
-import {
-    LISTS_UPDATE_EVENT,
-    SlidingRoomListStoreClass,
-    SlidingSyncSortToFilter,
-} from "../../../../src/stores/room-list/SlidingRoomListStore";
-import { SpaceStoreClass } from "../../../../src/stores/spaces/SpaceStore";
-import { MockEventEmitter, stubClient, untilEmission } from "../../../test-utils";
-import { TestSdkContext } from "../../TestSdkContext";
-import { SlidingSyncManager } from "../../../../src/SlidingSyncManager";
-import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
-import { MatrixDispatcher } from "../../../../src/dispatcher/dispatcher";
-import { SortAlgorithm } from "../../../../src/stores/room-list/algorithms/models";
-import { DefaultTagID, TagID } from "../../../../src/stores/room-list/models";
-import { MetaSpace, UPDATE_SELECTED_SPACE } from "../../../../src/stores/spaces";
-import { LISTS_LOADING_EVENT } from "../../../../src/stores/room-list/RoomListStore";
-import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore";
-
-jest.mock("../../../../src/SlidingSyncManager");
-const MockSlidingSyncManager = <jest.Mock<SlidingSyncManager>>(<unknown>SlidingSyncManager);
-
-describe("SlidingRoomListStore", () => {
-    let store: SlidingRoomListStoreClass;
-    let context: TestSdkContext;
-    let dis: MatrixDispatcher;
-    let activeSpace: string;
-
-    beforeEach(async () => {
-        context = new TestSdkContext();
-        context.client = stubClient();
-        context._SpaceStore = new MockEventEmitter<SpaceStoreClass>({
-            traverseSpace: jest.fn(),
-            get activeSpace() {
-                return activeSpace;
-            },
-        }) as SpaceStoreClass;
-        context._SlidingSyncManager = new MockSlidingSyncManager();
-        context._SlidingSyncManager.slidingSync = mocked(
-            new MockEventEmitter({
-                getListData: jest.fn(),
-            }) as unknown as SlidingSync,
-        );
-        context._RoomViewStore = mocked(
-            new MockEventEmitter({
-                getRoomId: jest.fn(),
-            }) as unknown as RoomViewStore,
-        );
-        mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({
-            ranges: [[0, 10]],
-        });
-
-        dis = new MatrixDispatcher();
-        store = new SlidingRoomListStoreClass(dis, context);
-    });
-
-    describe("spaces", () => {
-        it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => {
-            await store.start(); // call onReady
-            const spaceRoomId = "!foo:bar";
-
-            const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
-                return listName === DefaultTagID.Untagged && !isLoading;
-            });
-
-            // change the active space
-            activeSpace = spaceRoomId;
-            context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
-            await p;
-
-            expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
-                filters: expect.objectContaining({
-                    spaces: [spaceRoomId],
-                }),
-            });
-        });
-
-        it("gracefully handles subspaces in the home metaspace", async () => {
-            const subspace = "!sub:space";
-            mocked(context._SpaceStore!.traverseSpace).mockImplementation(
-                (spaceId: string, fn: (roomId: string) => void) => {
-                    fn(subspace);
-                },
-            );
-            activeSpace = MetaSpace.Home;
-            await store.start(); // call onReady
-
-            expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
-                filters: expect.objectContaining({
-                    spaces: [subspace],
-                }),
-            });
-        });
-
-        it("alters 'filters.spaces' on the DefaultTagID.Untagged list if it loads with an active space", async () => {
-            // change the active space before we are ready
-            const spaceRoomId = "!foo2:bar";
-            activeSpace = spaceRoomId;
-            const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
-                return listName === DefaultTagID.Untagged && !isLoading;
-            });
-            await store.start(); // call onReady
-            await p;
-            expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(
-                DefaultTagID.Untagged,
-                expect.objectContaining({
-                    filters: expect.objectContaining({
-                        spaces: [spaceRoomId],
-                    }),
-                }),
-            );
-        });
-
-        it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => {
-            await store.start(); // call onReady
-            const spaceRoomId = "!foo:bar";
-            const subSpace1 = "!ss1:bar";
-            const subSpace2 = "!ss2:bar";
-
-            const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
-                return listName === DefaultTagID.Untagged && !isLoading;
-            });
-
-            mocked(context._SpaceStore!.traverseSpace).mockImplementation(
-                (spaceId: string, fn: (roomId: string) => void) => {
-                    if (spaceId === spaceRoomId) {
-                        fn(subSpace1);
-                        fn(subSpace2);
-                    }
-                },
-            );
-
-            // change the active space
-            activeSpace = spaceRoomId;
-            context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
-            await p;
-
-            expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
-                filters: expect.objectContaining({
-                    spaces: [spaceRoomId, subSpace1, subSpace2],
-                }),
-            });
-        });
-    });
-
-    it("setTagSorting alters the 'sort' option in the list", async () => {
-        const tagId: TagID = "foo";
-        await store.setTagSorting(tagId, SortAlgorithm.Alphabetic);
-        expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(tagId, {
-            sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
-        });
-        expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic);
-
-        await store.setTagSorting(tagId, SortAlgorithm.Recent);
-        expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(tagId, {
-            sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
-        });
-        expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent);
-    });
-
-    it("getTagsForRoom gets the tags for the room", async () => {
-        await store.start();
-        const roomA = "!a:localhost";
-        const roomB = "!b:localhost";
-        const keyToListData: Record<string, { joinedCount: number; roomIndexToRoomId: Record<number, string> }> = {
-            [DefaultTagID.Untagged]: {
-                joinedCount: 10,
-                roomIndexToRoomId: {
-                    0: roomA,
-                    1: roomB,
-                },
-            },
-            [DefaultTagID.Favourite]: {
-                joinedCount: 2,
-                roomIndexToRoomId: {
-                    0: roomB,
-                },
-            },
-        };
-        mocked(context._SlidingSyncManager!.slidingSync!.getListData).mockImplementation((key: string) => {
-            return keyToListData[key] || null;
-        });
-
-        expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()!))).toEqual([
-            DefaultTagID.Untagged,
-        ]);
-        expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()!))).toEqual([
-            DefaultTagID.Favourite,
-            DefaultTagID.Untagged,
-        ]);
-    });
-
-    it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => {
-        await store.start();
-        const roomA = "!a:localhost";
-        const roomB = "!b:localhost";
-        const roomC = "!c:localhost";
-        const tagId = DefaultTagID.Favourite;
-        const joinCount = 10;
-        const roomIndexToRoomId = {
-            // mixed to ensure we sort
-            1: roomB,
-            2: roomC,
-            0: roomA,
-        };
-        const rooms = [
-            new Room(roomA, context.client!, context.client!.getUserId()!),
-            new Room(roomB, context.client!, context.client!.getUserId()!),
-            new Room(roomC, context.client!, context.client!.getUserId()!),
-        ];
-        mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
-            switch (roomId) {
-                case roomA:
-                    return rooms[0];
-                case roomB:
-                    return rooms[1];
-                case roomC:
-                    return rooms[2];
-            }
-            return null;
-        });
-        const p = untilEmission(store, LISTS_UPDATE_EVENT);
-        context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
-        await p;
-        expect(store.getCount(tagId)).toEqual(joinCount);
-        expect(store.orderedLists[tagId]).toEqual(rooms);
-    });
-
-    it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => {
-        await store.start();
-        // seed the store with 3 rooms
-        const roomIdA = "!a:localhost";
-        const roomIdB = "!b:localhost";
-        const roomIdC = "!c:localhost";
-        const tagId = DefaultTagID.Favourite;
-        const joinCount = 10;
-        const roomIndexToRoomId = {
-            // mixed to ensure we sort
-            1: roomIdB,
-            2: roomIdC,
-            0: roomIdA,
-        };
-        const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
-        const roomB = new Room(roomIdB, context.client!, context.client!.getUserId()!);
-        const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
-        mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
-            switch (roomId) {
-                case roomIdA:
-                    return roomA;
-                case roomIdB:
-                    return roomB;
-                case roomIdC:
-                    return roomC;
-            }
-            return null;
-        });
-        mocked(context._SlidingSyncManager!.slidingSync!.getListData).mockImplementation((key: string) => {
-            if (key !== tagId) {
-                return null;
-            }
-            return {
-                roomIndexToRoomId: roomIndexToRoomId,
-                joinedCount: joinCount,
-            };
-        });
-        let p = untilEmission(store, LISTS_UPDATE_EVENT);
-        context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
-        await p;
-        expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]);
-
-        // make roomB sticky and inform the store
-        mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdB);
-        context.roomViewStore.emit(UPDATE_EVENT);
-
-        // bump room C to the top, room B should not move from i=1 despite the list update saying to
-        roomIndexToRoomId[0] = roomIdC;
-        roomIndexToRoomId[1] = roomIdA;
-        roomIndexToRoomId[2] = roomIdB;
-        p = untilEmission(store, LISTS_UPDATE_EVENT);
-        context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
-        await p;
-
-        // check that B didn't move and that A was put below B
-        expect(store.orderedLists[tagId]).toEqual([roomC, roomB, roomA]);
-
-        // make room C sticky: rooms should move as a result, without needing an additional list update
-        mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdC);
-        p = untilEmission(store, LISTS_UPDATE_EVENT);
-        context.roomViewStore.emit(UPDATE_EVENT);
-        await p;
-        expect(store.orderedLists[tagId].map((r) => r.roomId)).toEqual([roomC, roomA, roomB].map((r) => r.roomId));
-    });
-
-    it("gracefully handles unknown room IDs", async () => {
-        await store.start();
-        const roomIdA = "!a:localhost";
-        const roomIdB = "!b:localhost"; // does not exist
-        const roomIdC = "!c:localhost";
-        const roomIndexToRoomId = {
-            0: roomIdA,
-            1: roomIdB, // does not exist
-            2: roomIdC,
-        };
-        const tagId = DefaultTagID.Favourite;
-        const joinCount = 10;
-        // seed the store with 2 rooms
-        const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
-        const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
-        mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
-            switch (roomId) {
-                case roomIdA:
-                    return roomA;
-                case roomIdC:
-                    return roomC;
-            }
-            return null;
-        });
-        mocked(context._SlidingSyncManager!.slidingSync!.getListData).mockImplementation((key: string) => {
-            if (key !== tagId) {
-                return null;
-            }
-            return {
-                roomIndexToRoomId: roomIndexToRoomId,
-                joinedCount: joinCount,
-            };
-        });
-        const p = untilEmission(store, LISTS_UPDATE_EVENT);
-        context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
-        await p;
-        expect(store.orderedLists[tagId]).toEqual([roomA, roomC]);
-    });
-});
diff --git a/test/unit-tests/stores/room-list/SpaceWatcher-test.ts b/test/unit-tests/stores/room-list/SpaceWatcher-test.ts
index 62686c7d3bdeae0aeefabf0df6fdeda76d6b9e2b..fdb2d4cb494d004e1d7c563ddf519c1c0c6f3ae1 100644
--- a/test/unit-tests/stores/room-list/SpaceWatcher-test.ts
+++ b/test/unit-tests/stores/room-list/SpaceWatcher-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import { SpaceWatcher } from "../../../../src/stores/room-list/SpaceWatcher";
 import type { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore";
diff --git a/test/unit-tests/stores/room-list/algorithms/Algorithm-test.ts b/test/unit-tests/stores/room-list/algorithms/Algorithm-test.ts
index 07a668624cedb883f586a6c22185002aad663781..4294f82af2ed0036e194b41610ca4a2dbc3c14af 100644
--- a/test/unit-tests/stores/room-list/algorithms/Algorithm-test.ts
+++ b/test/unit-tests/stores/room-list/algorithms/Algorithm-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import { PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { Widget } from "matrix-widget-api";
diff --git a/test/unit-tests/stores/room-list/algorithms/RecentAlgorithm-test.ts b/test/unit-tests/stores/room-list/algorithms/RecentAlgorithm-test.ts
index 42fe0ca3dde9487380443b525c0af15f6bccd493..3b7b031e2700894c429e6a4046281cfe90e894c4 100644
--- a/test/unit-tests/stores/room-list/algorithms/RecentAlgorithm-test.ts
+++ b/test/unit-tests/stores/room-list/algorithms/RecentAlgorithm-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { Room, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { mkMessage, mkRoom, stubClient } from "../../../../test-utils";
diff --git a/test/unit-tests/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm-test.ts b/test/unit-tests/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm-test.ts
index 5573157b1432a0abe5a840b16ac0053603445f1d..aed501eef9af4e9067ba748fbfdba4b6d5f36e89 100644
--- a/test/unit-tests/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm-test.ts
+++ b/test/unit-tests/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm-test.ts
@@ -67,9 +67,9 @@ describe("ImportanceAlgorithm", () => {
     };
 
     const unreadStates: Record<string, ReturnType<(typeof RoomNotifs)["determineUnreadState"]>> = {
-        red: { symbol: null, count: 1, level: NotificationLevel.Highlight },
-        grey: { symbol: null, count: 1, level: NotificationLevel.Notification },
-        none: { symbol: null, count: 0, level: NotificationLevel.None },
+        red: { symbol: null, count: 1, level: NotificationLevel.Highlight, invited: false },
+        grey: { symbol: null, count: 1, level: NotificationLevel.Notification, invited: false },
+        none: { symbol: null, count: 0, level: NotificationLevel.None, invited: false },
     };
 
     beforeEach(() => {
@@ -77,6 +77,7 @@ describe("ImportanceAlgorithm", () => {
             symbol: null,
             count: 0,
             level: NotificationLevel.None,
+            invited: false,
         });
     });
 
@@ -183,6 +184,7 @@ describe("ImportanceAlgorithm", () => {
                 symbol: null,
                 count: 0,
                 level: NotificationLevel.None,
+                invited: false,
             });
             const algorithm = setupAlgorithm(sortAlgorithm);
 
@@ -353,6 +355,7 @@ describe("ImportanceAlgorithm", () => {
                 symbol: null,
                 count: 0,
                 level: NotificationLevel.None,
+                invited: false,
             });
             const algorithm = setupAlgorithm(sortAlgorithm);
 
diff --git a/test/unit-tests/stores/room-list/algorithms/list-ordering/NaturalAlgorithm-test.ts b/test/unit-tests/stores/room-list/algorithms/list-ordering/NaturalAlgorithm-test.ts
index cf0fd59cd4231675415b7b13a512b6fb78197250..6fdf71b02d7b162190fca1c0fbac07b3d55fdacb 100644
--- a/test/unit-tests/stores/room-list/algorithms/list-ordering/NaturalAlgorithm-test.ts
+++ b/test/unit-tests/stores/room-list/algorithms/list-ordering/NaturalAlgorithm-test.ts
@@ -190,6 +190,7 @@ describe("NaturalAlgorithm", () => {
                 symbol: null,
                 count: 0,
                 level: NotificationLevel.None,
+                invited: false,
             });
         });
 
diff --git a/test/unit-tests/stores/room-list/filters/SpaceFilterCondition-test.ts b/test/unit-tests/stores/room-list/filters/SpaceFilterCondition-test.ts
index f0a27c43a1c2f3dad219c65a7344e1db1057077f..915094c844bcf857bf6ca1f41ddfa819df65c7d3 100644
--- a/test/unit-tests/stores/room-list/filters/SpaceFilterCondition-test.ts
+++ b/test/unit-tests/stores/room-list/filters/SpaceFilterCondition-test.ts
@@ -7,12 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { type Room } from "matrix-js-sdk/src/matrix";
 
 import SettingsStore from "../../../../../src/settings/SettingsStore";
 import { FILTER_CHANGED } from "../../../../../src/stores/room-list/filters/IFilterCondition";
 import { SpaceFilterCondition } from "../../../../../src/stores/room-list/filters/SpaceFilterCondition";
-import { MetaSpace, SpaceKey } from "../../../../../src/stores/spaces";
+import { MetaSpace, type SpaceKey } from "../../../../../src/stores/spaces";
 import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
 
 jest.mock("../../../../../src/settings/SettingsStore");
diff --git a/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts b/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
index 0ccb70c308c2692748c156f248d8190066e88161..95e0a3e103a672f8a6c4c25e3b86db57b8908ec7 100644
--- a/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
+++ b/test/unit-tests/stores/room-list/filters/VisibilityProvider-test.ts
@@ -7,25 +7,13 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { Room, RoomType } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomType } from "matrix-js-sdk/src/matrix";
 
 import { VisibilityProvider } from "../../../../../src/stores/room-list/filters/VisibilityProvider";
-import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
-import VoipUserMapper from "../../../../../src/VoipUserMapper";
 import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../../src/models/LocalRoom";
 import { RoomListCustomisations } from "../../../../../src/customisations/RoomList";
 import { createTestClient } from "../../../../test-utils";
 
-jest.mock("../../../../../src/VoipUserMapper", () => ({
-    sharedInstance: jest.fn(),
-}));
-
-jest.mock("../../../../../src/LegacyCallHandler", () => ({
-    instance: {
-        getSupportsVirtualRooms: jest.fn(),
-    },
-}));
-
 jest.mock("../../../../../src/customisations/RoomList", () => ({
     RoomListCustomisations: {
         isRoomVisible: jest.fn(),
@@ -46,16 +34,6 @@ const createLocalRoom = (): LocalRoom => {
 };
 
 describe("VisibilityProvider", () => {
-    let mockVoipUserMapper: VoipUserMapper;
-
-    beforeEach(() => {
-        mockVoipUserMapper = {
-            onNewInvitedRoom: jest.fn(),
-            isVirtualRoom: jest.fn(),
-        } as unknown as VoipUserMapper;
-        mocked(VoipUserMapper.sharedInstance).mockReturnValue(mockVoipUserMapper);
-    });
-
     describe("instance", () => {
         it("should return an instance", () => {
             const visibilityProvider = VisibilityProvider.instance;
@@ -64,28 +42,7 @@ describe("VisibilityProvider", () => {
         });
     });
 
-    describe("onNewInvitedRoom", () => {
-        it("should call onNewInvitedRoom on VoipUserMapper.sharedInstance", async () => {
-            const room = {} as unknown as Room;
-            await VisibilityProvider.instance.onNewInvitedRoom(room);
-            expect(mockVoipUserMapper.onNewInvitedRoom).toHaveBeenCalledWith(room);
-        });
-    });
-
     describe("isRoomVisible", () => {
-        describe("for a virtual room", () => {
-            beforeEach(() => {
-                mocked(LegacyCallHandler.instance.getSupportsVirtualRooms).mockReturnValue(true);
-                mocked(mockVoipUserMapper.isVirtualRoom).mockReturnValue(true);
-            });
-
-            it("should return return false", () => {
-                const room = createRoom();
-                expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
-                expect(mockVoipUserMapper.isVirtualRoom).toHaveBeenCalledWith(room);
-            });
-        });
-
         it("should return false without room", () => {
             expect(VisibilityProvider.instance.isRoomVisible()).toBe(false);
         });
diff --git a/test/unit-tests/stores/room-list/previews/PollStartEventPreview-test.ts b/test/unit-tests/stores/room-list/previews/PollStartEventPreview-test.ts
index 2b09294c616acf6f73cc793570e6ceb7b872bd4d..bf6f82a5d808ecba640a5bd944f7a89c8b479221 100644
--- a/test/unit-tests/stores/room-list/previews/PollStartEventPreview-test.ts
+++ b/test/unit-tests/stores/room-list/previews/PollStartEventPreview-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { PollStartEventPreview } from "../../../../../src/stores/room-list/previews/PollStartEventPreview";
 import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
diff --git a/test/unit-tests/stores/room-list/utils/roomMute-test.ts b/test/unit-tests/stores/room-list/utils/roomMute-test.ts
index 9e6009e52a739a90cc9136f19db848cba9179f9b..5934bcefdcda413d4fa40f649768bc901c563ab3 100644
--- a/test/unit-tests/stores/room-list/utils/roomMute-test.ts
+++ b/test/unit-tests/stores/room-list/utils/roomMute-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ConditionKind, EventType, IPushRule, MatrixEvent, PushRuleActionName } from "matrix-js-sdk/src/matrix";
+import { ConditionKind, EventType, type IPushRule, MatrixEvent, PushRuleActionName } from "matrix-js-sdk/src/matrix";
 
 import { getChangedOverrideRoomMutePushRules } from "../../../../../src/stores/room-list/utils/roomMute";
 import { DEFAULT_PUSH_RULES, getDefaultRuleWithKind, makePushRule } from "../../../../test-utils/pushRules";
diff --git a/test/unit-tests/stores/widgets/StopGapWidget-test.ts b/test/unit-tests/stores/widgets/StopGapWidget-test.ts
index f767c96a0284e1e2944cfe38229c2ec85221c777..d61070b1e4f504a72164cf08c335d3791a5634bd 100644
--- a/test/unit-tests/stores/widgets/StopGapWidget-test.ts
+++ b/test/unit-tests/stores/widgets/StopGapWidget-test.ts
@@ -6,26 +6,36 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, MockedObject } from "jest-mock";
-import { last } from "lodash";
+import { mocked, type MockedFunction, type MockedObject } from "jest-mock";
+import { findLast, last } from "lodash";
 import {
     MatrixEvent,
-    MatrixClient,
+    type MatrixClient,
     ClientEvent,
-    EventTimeline,
+    type EventTimeline,
     EventType,
     MatrixEventEvent,
+    RoomStateEvent,
+    type RoomState,
 } from "matrix-js-sdk/src/matrix";
 import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api";
 import { waitFor } from "jest-matrix-react";
+import { type Optional } from "matrix-events-sdk";
 
 import { stubClient, mkRoom, mkEvent } from "../../../test-utils";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget";
 import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
 import SettingsStore from "../../../../src/settings/SettingsStore";
+import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
+import { Action } from "../../../../src/dispatcher/actions";
+import { SdkContextClass } from "../../../../src/contexts/SDKContext";
+import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore";
 
-jest.mock("matrix-widget-api/lib/ClientWidgetApi");
+jest.mock("matrix-widget-api", () => ({
+    ...jest.requireActual("matrix-widget-api"),
+    ClientWidgetApi: (jest.createMockFromModule("matrix-widget-api") as any).ClientWidgetApi,
+}));
 
 describe("StopGapWidget", () => {
     let client: MockedObject<MatrixClient>;
@@ -53,6 +63,7 @@ describe("StopGapWidget", () => {
         // Start messaging without an iframe, since ClientWidgetApi is mocked
         widget.startMessaging(null as unknown as HTMLIFrameElement);
         messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
+        messaging.feedStateUpdate.mockResolvedValue();
     });
 
     afterEach(() => {
@@ -84,6 +95,38 @@ describe("StopGapWidget", () => {
         expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false);
     });
 
+    it("feeds incoming state updates to the widget", () => {
+        const event = mkEvent({
+            event: true,
+            type: "org.example.foo",
+            skey: "",
+            user: "@alice:example.org",
+            content: { hello: "world" },
+            room: "!1:example.org",
+        });
+
+        client.emit(RoomStateEvent.Events, event, {} as unknown as RoomState, null);
+        expect(messaging.feedStateUpdate).toHaveBeenCalledWith(event.getEffectiveEvent());
+    });
+
+    it("informs widget of theme changes", () => {
+        let theme = "light";
+        const settingsSpy = jest
+            .spyOn(SettingsStore, "getValue")
+            .mockImplementation((name) => (name === "theme" ? theme : null));
+        try {
+            // Indicate that the widget is ready
+            findLast(messaging.once.mock.calls, ([eventName]) => eventName === "ready")![1]();
+
+            // Now change the theme
+            theme = "dark";
+            defaultDispatcher.dispatch({ action: Action.RecheckTheme }, true);
+            expect(messaging.updateTheme).toHaveBeenLastCalledWith({ name: "dark" });
+        } finally {
+            settingsSpy.mockRestore();
+        }
+    });
+
     describe("feed event", () => {
         let event1: MatrixEvent;
         let event2: MatrixEvent;
@@ -118,24 +161,24 @@ describe("StopGapWidget", () => {
 
         it("feeds incoming event to the widget", async () => {
             client.emit(ClientEvent.Event, event1);
-            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
 
             client.emit(ClientEvent.Event, event2);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
         });
 
         it("should not feed incoming event to the widget if seen already", async () => {
             client.emit(ClientEvent.Event, event1);
-            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
 
             client.emit(ClientEvent.Event, event2);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
 
             client.emit(ClientEvent.Event, event1);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
         });
 
         it("feeds decrypted events asynchronously", async () => {
@@ -165,7 +208,7 @@ describe("StopGapWidget", () => {
             decryptingSpy2.mockReturnValue(false);
             client.emit(MatrixEventEvent.Decrypted, event2Encrypted);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(1);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent());
             // …then event 1
             event1Encrypted.event.type = event1.getType();
             event1Encrypted.event.content = event1.getContent();
@@ -175,7 +218,7 @@ describe("StopGapWidget", () => {
             // doesn't have to be blocked on the decryption of event 1 (or
             // worse, dropped)
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent());
         });
 
         it("should not feed incoming event if not in timeline", () => {
@@ -191,7 +234,7 @@ describe("StopGapWidget", () => {
             });
 
             client.emit(ClientEvent.Event, event);
-            expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent());
         });
 
         it("feeds incoming event that is not in timeline but relates to unknown parent to the widget", async () => {
@@ -211,18 +254,19 @@ describe("StopGapWidget", () => {
             });
 
             client.emit(ClientEvent.Event, event1);
-            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
 
             client.emit(ClientEvent.Event, event);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent());
 
             client.emit(ClientEvent.Event, event1);
             expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
-            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org");
+            expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent());
         });
     });
 });
+
 describe("StopGapWidget with stickyPromise", () => {
     let client: MockedObject<MatrixClient>;
     let widget: StopGapWidget;
@@ -288,3 +332,49 @@ describe("StopGapWidget with stickyPromise", () => {
         waitFor(() => expect(setPersistenceSpy).toHaveBeenCalled(), { interval: 5 });
     });
 });
+
+describe("StopGapWidget as an account widget", () => {
+    let widget: StopGapWidget;
+    let messaging: MockedObject<ClientWidgetApi>;
+    let getRoomId: MockedFunction<() => Optional<string>>;
+
+    beforeEach(() => {
+        stubClient();
+        // I give up, getting the return type of spyOn right is hopeless
+        getRoomId = jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId") as unknown as MockedFunction<
+            () => Optional<string>
+        >;
+        getRoomId.mockReturnValue("!1:example.org");
+
+        widget = new StopGapWidget({
+            app: {
+                id: "test",
+                creatorUserId: "@alice:example.org",
+                type: "example",
+                url: "https://example.org?user-id=$matrix_user_id&device-id=$org.matrix.msc3819.matrix_device_id&base-url=$org.matrix.msc4039.matrix_base_url&theme=$org.matrix.msc2873.client_theme",
+                roomId: "!1:example.org",
+            },
+            userId: "@alice:example.org",
+            creatorUserId: "@alice:example.org",
+            waitForIframeLoad: true,
+            userWidget: false,
+        });
+        // Start messaging without an iframe, since ClientWidgetApi is mocked
+        widget.startMessaging(null as unknown as HTMLIFrameElement);
+        messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
+    });
+
+    afterEach(() => {
+        widget.stopMessaging();
+        getRoomId.mockRestore();
+    });
+
+    it("updates viewed room", () => {
+        expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(1);
+        expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!1:example.org");
+        getRoomId.mockReturnValue("!2:example.org");
+        SdkContextClass.instance.roomViewStore.emit(UPDATE_EVENT);
+        expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(2);
+        expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!2:example.org");
+    });
+});
diff --git a/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts b/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts
index e484d0cc33fa68ea9795cb4f94c11c0327ed5bbd..e08a83dd02b8067dc50569e8c4f15107a29e86bf 100644
--- a/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts
+++ b/test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts
@@ -6,43 +6,44 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, MockedObject } from "jest-mock";
+import { mocked, type MockedObject } from "jest-mock";
 import fetchMockJest from "fetch-mock-jest";
 import {
-    MatrixClient,
+    type MatrixClient,
     ClientEvent,
-    ITurnServer as IClientTurnServer,
+    type ITurnServer as IClientTurnServer,
     Direction,
     EventType,
     MatrixEvent,
     MsgType,
     RelationType,
+    type Room,
 } from "matrix-js-sdk/src/matrix";
 import {
     Widget,
-    MatrixWidgetType,
     WidgetKind,
-    WidgetDriver,
-    ITurnServer,
+    type WidgetDriver,
+    type ITurnServer,
     SimpleObservable,
     OpenIDRequestState,
-    IOpenIDUpdate,
+    type IOpenIDUpdate,
     UpdateDelayedEventAction,
 } from "matrix-widget-api";
 import {
-    ApprovalOpts,
-    CapabilitiesOpts,
+    type ApprovalOpts,
+    type CapabilitiesOpts,
     WidgetLifecycle,
 } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
 
 import { SdkContextClass } from "../../../../src/contexts/SDKContext";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver";
-import { stubClient } from "../../../test-utils";
+import { mkEvent, stubClient } from "../../../test-utils";
 import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
 import dis from "../../../../src/dispatcher/dispatcher";
 import Modal from "../../../../src/Modal";
 import SettingsStore from "../../../../src/settings/SettingsStore";
+import { WidgetType } from "../../../../src/widgets/WidgetType.ts";
 
 describe("StopGapWidgetDriver", () => {
     let client: MockedObject<MatrixClient>;
@@ -78,7 +79,7 @@ describe("StopGapWidgetDriver", () => {
             new Widget({
                 id: "group_call",
                 creatorUserId: "@alice:example.org",
-                type: MatrixWidgetType.Custom,
+                type: WidgetType.CALL.preferred,
                 url: "https://call.element.io",
             }),
             WidgetKind.Room,
@@ -569,7 +570,7 @@ describe("StopGapWidgetDriver", () => {
 
         it("passes the flag through to getVisibleRooms", () => {
             const driver = mkDefaultDriver();
-            driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]);
+            driver.getKnownRooms();
             expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
         });
     });
@@ -584,7 +585,7 @@ describe("StopGapWidgetDriver", () => {
 
         it("passes the flag through to getVisibleRooms", () => {
             const driver = mkDefaultDriver();
-            driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]);
+            driver.getKnownRooms();
             expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
         });
     });
@@ -692,4 +693,114 @@ describe("StopGapWidgetDriver", () => {
             await expect(file.text()).resolves.toEqual("test contents");
         });
     });
+
+    describe("readRoomTimeline", () => {
+        const event1 = mkEvent({
+            event: true,
+            id: "$event-id1",
+            type: "org.example.foo",
+            user: "@alice:example.org",
+            content: { hello: "world" },
+            room: "!1:example.org",
+        });
+        const event2 = mkEvent({
+            event: true,
+            id: "$event-id2",
+            type: "org.example.foo",
+            user: "@alice:example.org",
+            skey: "",
+            content: { hello: "world" },
+            room: "!1:example.org",
+        });
+        let driver: WidgetDriver;
+
+        beforeEach(() => {
+            driver = mkDefaultDriver();
+            client.getRoom.mockReturnValue({
+                getLiveTimeline: () => ({ getEvents: () => [event1, event2] }),
+            } as unknown as Room);
+        });
+
+        it("reads all events", async () => {
+            expect(
+                await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 10, undefined),
+            ).toEqual([event2, event1].map((e) => e.getEffectiveEvent()));
+        });
+
+        it("reads state events", async () => {
+            expect(
+                await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, "", 10, undefined),
+            ).toEqual([event2.getEffectiveEvent()]);
+        });
+
+        it("reads up to a limit", async () => {
+            expect(
+                await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 1, undefined),
+            ).toEqual([event2.getEffectiveEvent()]);
+        });
+
+        it("reads up to a specific event", async () => {
+            expect(
+                await driver.readRoomTimeline(
+                    "!1:example.org",
+                    "org.example.foo",
+                    undefined,
+                    undefined,
+                    10,
+                    event1.getId(),
+                ),
+            ).toEqual([event2.getEffectiveEvent()]);
+        });
+    });
+
+    describe("readRoomState", () => {
+        const event1 = mkEvent({
+            event: true,
+            id: "$event-id1",
+            type: "org.example.foo",
+            user: "@alice:example.org",
+            content: { hello: "world" },
+            skey: "1",
+            room: "!1:example.org",
+        });
+        const event2 = mkEvent({
+            event: true,
+            id: "$event-id2",
+            type: "org.example.foo",
+            user: "@alice:example.org",
+            content: { hello: "world" },
+            skey: "2",
+            room: "!1:example.org",
+        });
+        let driver: WidgetDriver;
+        let getStateEvents: jest.Mock;
+
+        beforeEach(() => {
+            driver = mkDefaultDriver();
+            getStateEvents = jest.fn();
+            client.getRoom.mockReturnValue({
+                getLiveTimeline: () => ({ getState: () => ({ getStateEvents }) }),
+            } as unknown as Room);
+        });
+
+        it("reads a specific state key", async () => {
+            getStateEvents.mockImplementation((eventType, stateKey) => {
+                if (eventType === "org.example.foo" && stateKey === "1") return event1;
+                return undefined;
+            });
+            expect(await driver.readRoomState("!1:example.org", "org.example.foo", "1")).toEqual([
+                event1.getEffectiveEvent(),
+            ]);
+        });
+
+        it("reads all state keys", async () => {
+            getStateEvents.mockImplementation((eventType, stateKey) => {
+                if (eventType === "org.example.foo" && stateKey === undefined) return [event1, event2];
+                return [];
+            });
+            expect(await driver.readRoomState("!1:example.org", "org.example.foo", undefined)).toEqual(
+                [event1, event2].map((e) => e.getEffectiveEvent()),
+            );
+        });
+    });
 });
diff --git a/test/unit-tests/stores/widgets/WidgetPermissionStore-test.ts b/test/unit-tests/stores/widgets/WidgetPermissionStore-test.ts
index 1dc9936d895ea426be3ed1e68e72e8d8c8b7a5d0..81c7f35e7bd0fb12addc71c7e3a96ff1601577b0 100644
--- a/test/unit-tests/stores/widgets/WidgetPermissionStore-test.ts
+++ b/test/unit-tests/stores/widgets/WidgetPermissionStore-test.ts
@@ -7,16 +7,17 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { MatrixWidgetType, Widget, WidgetKind } from "matrix-widget-api";
+import { type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { Widget, WidgetKind } from "matrix-widget-api";
 
 import { OIDCState, WidgetPermissionStore } from "../../../../src/stores/widgets/WidgetPermissionStore";
 import SettingsStore from "../../../../src/settings/SettingsStore";
 import { TestSdkContext } from "../../TestSdkContext";
-import { SettingLevel } from "../../../../src/settings/SettingLevel";
+import { type SettingLevel } from "../../../../src/settings/SettingLevel";
 import { SdkContextClass } from "../../../../src/contexts/SDKContext";
 import { stubClient } from "../../../test-utils";
 import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver";
+import { WidgetType } from "../../../../src/widgets/WidgetType.ts";
 
 jest.mock("../../../../src/settings/SettingsStore");
 
@@ -34,7 +35,7 @@ describe("WidgetPermissionStore", () => {
     const elementCallWidget = new Widget({
         id: "group_call",
         creatorUserId: "@alice:example.org",
-        type: MatrixWidgetType.Custom,
+        type: WidgetType.CALL.preferred,
         url: "https://call.element.io",
     });
     let settings: Record<string, any> = {}; // key value store
diff --git a/test/unit-tests/submit-rageshake-test.ts b/test/unit-tests/submit-rageshake-test.ts
index 44e4e3b517d67f9aea761ef7ffa22b557e9898ef..4eda347a982552fe1ffd62a2721eb6d0e8e2963d 100644
--- a/test/unit-tests/submit-rageshake-test.ts
+++ b/test/unit-tests/submit-rageshake-test.ts
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked, mocked } from "jest-mock";
+import { type Mocked, mocked } from "jest-mock";
 import {
-    HttpApiEvent,
-    HttpApiEventHandlerMap,
-    IHttpOpts,
-    MatrixClient,
+    type HttpApiEvent,
+    type HttpApiEventHandlerMap,
+    type IHttpOpts,
+    type MatrixClient,
     TypedEventEmitter,
     MatrixHttpApi,
 } from "matrix-js-sdk/src/matrix";
@@ -20,8 +20,8 @@ import fetchMock from "fetch-mock-jest";
 import { getMockClientWithEventEmitter, mockClientMethodsCrypto, mockPlatformPeg } from "../test-utils";
 import { collectBugReport } from "../../src/rageshake/submit-rageshake";
 import SettingsStore from "../../src/settings/SettingsStore";
-import { ConsoleLogger } from "../../src/rageshake/rageshake";
-import { FeatureSettingKey, SettingKey } from "../../src/settings/Settings.tsx";
+import { type ConsoleLogger } from "../../src/rageshake/rageshake";
+import { type FeatureSettingKey, type SettingKey } from "../../src/settings/Settings.tsx";
 
 describe("Rageshakes", () => {
     const RUST_CRYPTO_VERSION = "Rust SDK 0.7.0 (691ec63), Vodozemac 0.5.0";
diff --git a/test/unit-tests/theme-test.ts b/test/unit-tests/theme-test.ts
index 6c2f247e4e1bfc46c30add5306a30ea8c0ff36c0..b9c7e9341c99ddcbf3c12b3e21baa162c567ce60 100644
--- a/test/unit-tests/theme-test.ts
+++ b/test/unit-tests/theme-test.ts
@@ -135,6 +135,54 @@ describe("theme", () => {
             expect(spy.mock.calls[0][0].textContent).toMatchSnapshot();
             spy.mockRestore();
         });
+
+        it("should handle 4-char rgba hex strings", async () => {
+            jest.spyOn(SettingsStore, "getValue").mockReturnValue([
+                {
+                    name: "blue",
+                    colors: {
+                        "sidebar-color": "#abcd",
+                    },
+                },
+            ]);
+
+            const spy = jest.fn();
+            jest.spyOn(document.body, "style", "get").mockReturnValue({
+                setProperty: spy,
+            } as any);
+            await new Promise((resolve) => {
+                setTheme("custom-blue").then(resolve);
+                lightCustomTheme.onload!({} as Event);
+            });
+            expect(spy).toHaveBeenCalledWith("--sidebar-color", "#abcd");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-0pct", "#aabbcc00");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-15pct", "#aabbcc21");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-50pct", "#aabbcc6f");
+        });
+
+        it("should handle 6-char rgb hex strings", async () => {
+            jest.spyOn(SettingsStore, "getValue").mockReturnValue([
+                {
+                    name: "blue",
+                    colors: {
+                        "sidebar-color": "#abcdef",
+                    },
+                },
+            ]);
+
+            const spy = jest.fn();
+            jest.spyOn(document.body, "style", "get").mockReturnValue({
+                setProperty: spy,
+            } as any);
+            await new Promise((resolve) => {
+                setTheme("custom-blue").then(resolve);
+                lightCustomTheme.onload!({} as Event);
+            });
+            expect(spy).toHaveBeenCalledWith("--sidebar-color", "#abcdef");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-0pct", "#abcdef00");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-15pct", "#abcdef26");
+            expect(spy).toHaveBeenCalledWith("--sidebar-color-50pct", "#abcdef80");
+        });
     });
 
     describe("enumerateThemes", () => {
diff --git a/test/unit-tests/toasts/IncomingCallToast-test.tsx b/test/unit-tests/toasts/IncomingCallToast-test.tsx
index 2d035c94dd3ff56c65a774ad269ac469b16f5754..f73a24dc449a2ce21516e4d5036f1b1b050008a9 100644
--- a/test/unit-tests/toasts/IncomingCallToast-test.tsx
+++ b/test/unit-tests/toasts/IncomingCallToast-test.tsx
@@ -8,12 +8,18 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen, cleanup, fireEvent, waitFor } from "jest-matrix-react";
-import { mocked, Mocked } from "jest-mock";
-import { Room, RoomStateEvent, MatrixEvent, MatrixEventEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { ClientWidgetApi, Widget } from "matrix-widget-api";
-import { ICallNotifyContent } from "matrix-js-sdk/src/matrixrtc";
+import { mocked, type Mocked } from "jest-mock";
+import {
+    Room,
+    RoomStateEvent,
+    type MatrixEvent,
+    MatrixEventEvent,
+    type MatrixClient,
+    type RoomMember,
+} from "matrix-js-sdk/src/matrix";
+import { type ClientWidgetApi, Widget } from "matrix-widget-api";
+import { type ICallNotifyContent } from "matrix-js-sdk/src/matrixrtc";
 
-import type { RoomMember } from "matrix-js-sdk/src/matrix";
 import {
     useMockedCalls,
     MockedCall,
diff --git a/test/unit-tests/toasts/SetupEncryptionToast-test.tsx b/test/unit-tests/toasts/SetupEncryptionToast-test.tsx
index 22b491817ccfafb9dbac77a947cc845edfb1e891..eb584aafd96e2e0a681bced389c0b8d342bf0192 100644
--- a/test/unit-tests/toasts/SetupEncryptionToast-test.tsx
+++ b/test/unit-tests/toasts/SetupEncryptionToast-test.tsx
@@ -7,24 +7,133 @@ Please see LICENSE files in the repository root for full details.
 
 import React from "react";
 import { render, screen } from "jest-matrix-react";
+import userEvent from "@testing-library/user-event";
 
+import * as SecurityManager from "../../../src/SecurityManager";
 import ToastContainer from "../../../src/components/structures/ToastContainer";
 import { Kind, showToast } from "../../../src/toasts/SetupEncryptionToast";
+import dis from "../../../src/dispatcher/dispatcher";
+import DeviceListener from "../../../src/DeviceListener";
+import Modal from "../../../src/Modal";
+import ConfirmKeyStorageOffDialog from "../../../src/components/views/dialogs/ConfirmKeyStorageOffDialog";
+
+jest.mock("../../../src/dispatcher/dispatcher", () => ({
+    dispatch: jest.fn(),
+    register: jest.fn(),
+    unregister: jest.fn(),
+}));
 
 describe("SetupEncryptionToast", () => {
     beforeEach(() => {
+        jest.resetAllMocks();
         render(<ToastContainer />);
     });
 
-    it("should render the 'set up recovery' toast", async () => {
-        showToast(Kind.SET_UP_RECOVERY);
+    describe("Set up recovery", () => {
+        it("should render the toast", async () => {
+            showToast(Kind.SET_UP_RECOVERY);
+
+            expect(await screen.findByRole("heading", { name: "Set up recovery" })).toBeInTheDocument();
+        });
+
+        it("should dismiss the toast when 'not now' button clicked", async () => {
+            jest.spyOn(DeviceListener.sharedInstance(), "dismissEncryptionSetup");
+
+            showToast(Kind.SET_UP_RECOVERY);
+
+            const user = userEvent.setup();
+            await user.click(await screen.findByRole("button", { name: "Not now" }));
+
+            expect(DeviceListener.sharedInstance().dismissEncryptionSetup).toHaveBeenCalled();
+        });
+    });
+
+    describe("Key storage out of sync", () => {
+        it("should render the toast", async () => {
+            showToast(Kind.KEY_STORAGE_OUT_OF_SYNC);
+
+            await expect(screen.findByText("Your key storage is out of sync.")).resolves.toBeInTheDocument();
+        });
+
+        it("should open settings to the reset flow when 'forgot recovery key' clicked", async () => {
+            showToast(Kind.KEY_STORAGE_OUT_OF_SYNC);
+
+            const user = userEvent.setup();
+            await user.click(await screen.findByText("Forgot recovery key?"));
+
+            expect(dis.dispatch).toHaveBeenCalledWith({
+                action: "view_user_settings",
+                initialTabId: "USER_ENCRYPTION_TAB",
+                props: { initialEncryptionState: "reset_identity_forgot" },
+            });
+        });
+
+        it("should open settings to the reset flow when recovering fails", async () => {
+            jest.spyOn(SecurityManager, "accessSecretStorage").mockImplementation(async () => {
+                throw new Error("Something went wrong while recovering!");
+            });
 
-        await expect(screen.findByText("Set up recovery")).resolves.toBeInTheDocument();
+            showToast(Kind.KEY_STORAGE_OUT_OF_SYNC);
+
+            const user = userEvent.setup();
+            await user.click(await screen.findByText("Enter recovery key"));
+
+            expect(dis.dispatch).toHaveBeenCalledWith({
+                action: "view_user_settings",
+                initialTabId: "USER_ENCRYPTION_TAB",
+                props: { initialEncryptionState: "reset_identity_sync_failed" },
+            });
+        });
     });
 
-    it("should render the 'key storage out of sync' toast", async () => {
-        showToast(Kind.KEY_STORAGE_OUT_OF_SYNC);
+    describe("Turn on key storage", () => {
+        it("should render the toast", async () => {
+            showToast(Kind.TURN_ON_KEY_STORAGE);
+
+            await expect(screen.findByText("Turn on key storage")).resolves.toBeInTheDocument();
+            await expect(screen.findByRole("button", { name: "Dismiss" })).resolves.toBeInTheDocument();
+            await expect(screen.findByRole("button", { name: "Continue" })).resolves.toBeInTheDocument();
+        });
+
+        it("should open settings to the Encryption tab when 'Continue' clicked", async () => {
+            jest.spyOn(DeviceListener.sharedInstance(), "recordKeyBackupDisabled");
+
+            showToast(Kind.TURN_ON_KEY_STORAGE);
+
+            const user = userEvent.setup();
+            await user.click(await screen.findByRole("button", { name: "Continue" }));
+
+            expect(dis.dispatch).toHaveBeenCalledWith({
+                action: "view_user_settings",
+                initialTabId: "USER_ENCRYPTION_TAB",
+            });
+
+            expect(DeviceListener.sharedInstance().recordKeyBackupDisabled).not.toHaveBeenCalled();
+        });
+
+        it("should open the confirm key storage off dialog when 'Dismiss' clicked", async () => {
+            jest.spyOn(DeviceListener.sharedInstance(), "recordKeyBackupDisabled");
+
+            // Given that as soon as the dialog opens, it closes and says "yes they clicked dismiss"
+            jest.spyOn(Modal, "createDialog").mockImplementation(() => {
+                return { finished: Promise.resolve([true]) } as any;
+            });
+
+            // When we show the toast, and click Dismiss
+            showToast(Kind.TURN_ON_KEY_STORAGE);
+
+            const user = userEvent.setup();
+            await user.click(await screen.findByRole("button", { name: "Dismiss" }));
+
+            // Then the dialog was opened
+            expect(Modal.createDialog).toHaveBeenCalledWith(
+                ConfirmKeyStorageOffDialog,
+                undefined,
+                "mx_ConfirmKeyStorageOffDialog",
+            );
 
-        await expect(screen.findByText("Your key storage is out of sync.")).resolves.toBeInTheDocument();
+            // And the backup was disabled when the dialog's onFinished was called
+            expect(DeviceListener.sharedInstance().recordKeyBackupDisabled).toHaveBeenCalledTimes(1);
+        });
     });
 });
diff --git a/test/unit-tests/toasts/UnverifiedSessionToast-test.tsx b/test/unit-tests/toasts/UnverifiedSessionToast-test.tsx
index 14cacb4629388ff7189e173600518d5f735bf0a4..ee67599b6396f7315bc1a0d503159e810ec52380 100644
--- a/test/unit-tests/toasts/UnverifiedSessionToast-test.tsx
+++ b/test/unit-tests/toasts/UnverifiedSessionToast-test.tsx
@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import React from "react";
-import { render, RenderResult, screen } from "jest-matrix-react";
+import { render, type RenderResult, screen } from "jest-matrix-react";
 import userEvent from "@testing-library/user-event";
-import { mocked, Mocked } from "jest-mock";
-import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
-import { CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
+import { mocked, type Mocked } from "jest-mock";
+import { type IMyDevice, type MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 
 import dis from "../../../src/dispatcher/dispatcher";
 import { showToast } from "../../../src/toasts/UnverifiedSessionToast";
diff --git a/test/unit-tests/toasts/__snapshots__/UnverifiedSessionToast-test.tsx.snap b/test/unit-tests/toasts/__snapshots__/UnverifiedSessionToast-test.tsx.snap
index 4b04f4321a22b7165966296b5707e9795a6442c2..1fac6059ccb312e72dc9bba097840689c2eac8f7 100644
--- a/test/unit-tests/toasts/__snapshots__/UnverifiedSessionToast-test.tsx.snap
+++ b/test/unit-tests/toasts/__snapshots__/UnverifiedSessionToast-test.tsx.snap
@@ -14,7 +14,7 @@ exports[`UnverifiedSessionToast when rendering the toast should render as expect
           class="mx_Toast_title"
         >
           <h2
-            class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
+            class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
           >
             New login. Was this you?
           </h2>
@@ -50,7 +50,7 @@ exports[`UnverifiedSessionToast when rendering the toast should render as expect
               class="mx_Toast_buttons"
             >
               <button
-                class="_button_i91xf_17 _destructive_i91xf_116"
+                class="_button_vczzf_8 _destructive_vczzf_107"
                 data-kind="secondary"
                 data-size="sm"
                 role="button"
@@ -59,7 +59,7 @@ exports[`UnverifiedSessionToast when rendering the toast should render as expect
                 No
               </button>
               <button
-                class="_button_i91xf_17"
+                class="_button_vczzf_8"
                 data-kind="primary"
                 data-size="sm"
                 role="button"
diff --git a/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx b/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx
index a49707e31017244e2a6277b13155e861920d0ebf..85d6309f07e6dc2949820b8d27310ffadcea9f41 100644
--- a/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx
+++ b/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { AutoDiscovery, AutoDiscoveryAction, ClientConfig } from "matrix-js-sdk/src/matrix";
+import { AutoDiscovery, AutoDiscoveryAction, type ClientConfig } from "matrix-js-sdk/src/matrix";
 import { logger } from "matrix-js-sdk/src/logger";
 import fetchMock from "fetch-mock-jest";
 
@@ -355,21 +355,19 @@ describe("AutoDiscoveryUtils", () => {
                 hsNameIsDifferent: true,
                 hsName: serverName,
                 delegatedAuthentication: expect.objectContaining({
-                    accountManagementActionsSupported: [
+                    issuer,
+                    account_management_actions_supported: [
                         "org.matrix.profile",
                         "org.matrix.sessions_list",
                         "org.matrix.session_view",
                         "org.matrix.session_end",
                         "org.matrix.cross_signing_reset",
                     ],
-                    accountManagementEndpoint: "https://auth.matrix.org/account/",
-                    authorizationEndpoint: "https://auth.matrix.org/auth",
-                    metadata: expect.objectContaining({
-                        issuer,
-                    }),
-                    registrationEndpoint: "https://auth.matrix.org/registration",
+                    account_management_uri: "https://auth.matrix.org/account/",
+                    authorization_endpoint: "https://auth.matrix.org/auth",
+                    registration_endpoint: "https://auth.matrix.org/registration",
                     signingKeys: [],
-                    tokenEndpoint: "https://auth.matrix.org/token",
+                    token_endpoint: "https://auth.matrix.org/token",
                 }),
                 warning: null,
             });
diff --git a/test/unit-tests/utils/DMRoomMap-test.ts b/test/unit-tests/utils/DMRoomMap-test.ts
index d8c44da954b541df7208667f510ddfba2ae9fb88..69360a277dc8c38a62fb235e1765048d09e4e933 100644
--- a/test/unit-tests/utils/DMRoomMap-test.ts
+++ b/test/unit-tests/utils/DMRoomMap-test.ts
@@ -6,9 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked } from "jest-mock";
+import { mocked, type Mocked } from "jest-mock";
 import { logger } from "matrix-js-sdk/src/logger";
-import { ClientEvent, EventType, IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import {
+    ClientEvent,
+    EventType,
+    type IContent,
+    type MatrixClient,
+    type MatrixEvent,
+    type Room,
+} from "matrix-js-sdk/src/matrix";
 
 import DMRoomMap from "../../../src/utils/DMRoomMap";
 import { mkEvent, stubClient } from "../../test-utils";
diff --git a/test/unit-tests/utils/ErrorUtils-test.ts b/test/unit-tests/utils/ErrorUtils-test.ts
index f3ede81eb7b43fc2122b6194d42e0d674036d019..170a639af31ce3745c2115b11fb5b7f5672564ff 100644
--- a/test/unit-tests/utils/ErrorUtils-test.ts
+++ b/test/unit-tests/utils/ErrorUtils-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ReactElement } from "react";
+import { type ReactElement } from "react";
 import { render } from "jest-matrix-react";
 import { MatrixError, ConnectionError } from "matrix-js-sdk/src/matrix";
 
diff --git a/test/unit-tests/utils/EventUtils-test.ts b/test/unit-tests/utils/EventUtils-test.ts
index 94f1c6d262768e72ccc55815bc8c9e6a44c56386..82c7eb719179debda7af3efa6bfbb8bb0a313902 100644
--- a/test/unit-tests/utils/EventUtils-test.ts
+++ b/test/unit-tests/utils/EventUtils-test.ts
@@ -10,8 +10,8 @@ import {
     M_LOCATION,
     EventStatus,
     EventType,
-    IEvent,
-    MatrixClient,
+    type IEvent,
+    type MatrixClient,
     MatrixEvent,
     MsgType,
     PendingEventOrdering,
diff --git a/test/unit-tests/utils/FileUtils-test.ts b/test/unit-tests/utils/FileUtils-test.ts
index c15c58fd879635267a50f94f62dc41161d22d1d1..68e0c3ae06b111357206007188b783b2deae9dcd 100644
--- a/test/unit-tests/utils/FileUtils-test.ts
+++ b/test/unit-tests/utils/FileUtils-test.ts
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MediaEventContent } from "matrix-js-sdk/src/types";
+import { type MediaEventContent } from "matrix-js-sdk/src/types";
 
 import { downloadLabelForFile } from "../../../src/utils/FileUtils.ts";
 
diff --git a/test/unit-tests/utils/MultiInviter-test.ts b/test/unit-tests/utils/MultiInviter-test.ts
index 15d1fc2dbb46ce2fedc5dca1235b471dad153b0c..998334c9af49a548c7d4c97d23077d7e6feb7c10 100644
--- a/test/unit-tests/utils/MultiInviter-test.ts
+++ b/test/unit-tests/utils/MultiInviter-test.ts
@@ -7,15 +7,15 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, MatrixError, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, MatrixError, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
-import Modal, { ComponentType, ComponentProps } from "../../../src/Modal";
+import Modal, { type ComponentType, type ComponentProps } from "../../../src/Modal";
 import SettingsStore from "../../../src/settings/SettingsStore";
-import MultiInviter, { CompletionStates } from "../../../src/utils/MultiInviter";
+import MultiInviter, { type CompletionStates } from "../../../src/utils/MultiInviter";
 import * as TestUtilsMatrix from "../../test-utils";
-import AskInviteAnywayDialog from "../../../src/components/views/dialogs/AskInviteAnywayDialog";
+import type AskInviteAnywayDialog from "../../../src/components/views/dialogs/AskInviteAnywayDialog";
 import ConfirmUserActionDialog from "../../../src/components/views/dialogs/ConfirmUserActionDialog";
 
 const ROOMID = "!room:server";
diff --git a/test/unit-tests/utils/PinningUtils-test.ts b/test/unit-tests/utils/PinningUtils-test.ts
index 59436ce48d75a939a64360f19c6d0bd851ddaa5d..38ca05244d6f59f42981fcf4bc86a501108a728c 100644
--- a/test/unit-tests/utils/PinningUtils-test.ts
+++ b/test/unit-tests/utils/PinningUtils-test.ts
@@ -6,7 +6,7 @@
  * Please see LICENSE files in the repository root for full details.
  */
 
-import { EventTimeline, EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { EventTimeline, EventType, type IEvent, type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
 import { createTestClient } from "../../test-utils";
diff --git a/test/unit-tests/utils/SessionLock-test.ts b/test/unit-tests/utils/SessionLock-test.ts
index fb3ab7e6ea100dc74c7d19478b26913c178136a8..67202c719330da6653467a7cbbdcb469aa1b1af9 100644
--- a/test/unit-tests/utils/SessionLock-test.ts
+++ b/test/unit-tests/utils/SessionLock-test.ts
@@ -71,15 +71,15 @@ describe("SessionLock", () => {
         jest.advanceTimersByTime(5000);
         expect(checkSessionLockFree()).toBe(false);
 
-        // second instance tries to start. This should block for 25 more seconds
+        // second instance tries to start. This should block for 10 more seconds
         const onNewInstance2 = jest.fn();
         let session2Result: boolean | undefined;
         getSessionLock(onNewInstance2).then((res) => {
             session2Result = res;
         });
 
-        // after another 24.5 seconds, we are still waiting
-        jest.advanceTimersByTime(24500);
+        // after another 9.5 seconds, we are still waiting
+        jest.advanceTimersByTime(9500);
         expect(session2Result).toBe(undefined);
         expect(checkSessionLockFree()).toBe(false);
 
@@ -92,6 +92,40 @@ describe("SessionLock", () => {
         expect(onNewInstance2).not.toHaveBeenCalled();
     });
 
+    it("A second instance starts up when the first terminated uncleanly and the clock was wound back", async () => {
+        // first instance starts...
+        expect(await getSessionLock(() => Promise.resolve())).toBe(true);
+        expect(checkSessionLockFree()).toBe(false);
+
+        // oops, now it dies. We simulate this by forcibly clearing the timers.
+        const time = Date.now();
+        jest.clearAllTimers();
+        expect(checkSessionLockFree()).toBe(false);
+
+        // Now, the clock gets wound back an hour.
+        jest.setSystemTime(time - 3600 * 1000);
+        expect(checkSessionLockFree()).toBe(false);
+
+        // second instance tries to start. This should block for 15 seconds
+        const onNewInstance2 = jest.fn();
+        let session2Result: boolean | undefined;
+        getSessionLock(onNewInstance2).then((res) => {
+            session2Result = res;
+        });
+
+        // after another 14.5 seconds, we are still waiting
+        jest.advanceTimersByTime(14500);
+        expect(session2Result).toBe(undefined);
+        expect(checkSessionLockFree()).toBe(false);
+
+        // another 500ms and we get the lock
+        await jest.advanceTimersByTimeAsync(500);
+        expect(session2Result).toBe(true);
+        expect(checkSessionLockFree()).toBe(false); // still false, because the new session has claimed it
+
+        expect(onNewInstance2).not.toHaveBeenCalled();
+    });
+
     it("A second instance waits for the first to shut down", async () => {
         // first instance starts. Once it gets the shutdown signal, it will wait two seconds and then release the lock.
         await getSessionLock(
diff --git a/test/unit-tests/utils/ShieldUtils-test.ts b/test/unit-tests/utils/ShieldUtils-test.ts
index 02c69444dc893943cdec7a77b58473d65a429549..43f978cd4ac67975ceb50d7ea853578a52b1dfc8 100644
--- a/test/unit-tests/utils/ShieldUtils-test.ts
+++ b/test/unit-tests/utils/ShieldUtils-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
 
 import { shieldStatusForRoom } from "../../../src/utils/ShieldUtils";
diff --git a/test/unit-tests/utils/__snapshots__/FormattingUtils-test.tsx.snap b/test/unit-tests/utils/__snapshots__/FormattingUtils-test.tsx.snap
index 32f0ec443c0209a05e807ca9ef5449fc9693a409..464963c1ef20b6f9b5bff36ce9d241f1293f1e43 100644
--- a/test/unit-tests/utils/__snapshots__/FormattingUtils-test.tsx.snap
+++ b/test/unit-tests/utils/__snapshots__/FormattingUtils-test.tsx.snap
@@ -1,48 +1,72 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`FormattingUtils formatList should return expected sentence in ReactNode when given 2 React children 1`] = `
-<span>
-  <span>
-    a
-  </span>
-   and 
-  <span>
-    b
-  </span>
-</span>
+<React.Fragment>
+  <React.Fragment>
+    <span>
+      a
+    </span>
+  </React.Fragment>
+  <React.Fragment>
+     and 
+  </React.Fragment>
+  <React.Fragment>
+    <span>
+      b
+    </span>
+  </React.Fragment>
+</React.Fragment>
 `;
 
 exports[`FormattingUtils formatList should return expected sentence in ReactNode when given more React children 1`] = `
-<span>
-  <span>
-    a
-  </span>
-  , 
-  <span>
-    b
-  </span>
-  , 
-  <span>
-    c
-  </span>
-   and 
-  <span>
-    d
-  </span>
-</span>
-`;
-
-exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = `
-<span>
-  <span>
+<React.Fragment>
+  <React.Fragment>
     <span>
       a
     </span>
+  </React.Fragment>
+  <React.Fragment>
     , 
+  </React.Fragment>
+  <React.Fragment>
     <span>
       b
     </span>
-  </span>
+  </React.Fragment>
+  <React.Fragment>
+    , 
+  </React.Fragment>
+  <React.Fragment>
+    <span>
+      c
+    </span>
+  </React.Fragment>
+  <React.Fragment>
+     and 
+  </React.Fragment>
+  <React.Fragment>
+    <span>
+      d
+    </span>
+  </React.Fragment>
+</React.Fragment>
+`;
+
+exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = `
+<span>
+  <React.Fragment>
+    <React.Fragment>
+      <span>
+        a
+      </span>
+      , 
+    </React.Fragment>
+    <React.Fragment>
+      <span>
+        b
+      </span>
+    </React.Fragment>
+  </React.Fragment>
    and 2 others
 </span>
 `;
diff --git a/test/unit-tests/utils/beacon/bounds-test.ts b/test/unit-tests/utils/beacon/bounds-test.ts
index b517a4910d8dba64656529d6af7999ac571ae85b..0a9876d2370b317de1c8d2b2f66c849e11cb5824 100644
--- a/test/unit-tests/utils/beacon/bounds-test.ts
+++ b/test/unit-tests/utils/beacon/bounds-test.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import { Beacon } from "matrix-js-sdk/src/matrix";
 
-import { Bounds, getBeaconBounds } from "../../../../src/utils/beacon/bounds";
+import { type Bounds, getBeaconBounds } from "../../../../src/utils/beacon/bounds";
 import { makeBeaconEvent, makeBeaconInfoEvent } from "../../../test-utils";
 
 describe("getBeaconBounds()", () => {
diff --git a/test/unit-tests/utils/beacon/geolocation-test.ts b/test/unit-tests/utils/beacon/geolocation-test.ts
index f509434772281cd65aaa72268e2dbbe5f9f99271..f8203519ee3331f46b62a93c6c24cf3b660d9a0f 100644
--- a/test/unit-tests/utils/beacon/geolocation-test.ts
+++ b/test/unit-tests/utils/beacon/geolocation-test.ts
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { logger } from "matrix-js-sdk/src/logger";
-import { Mocked } from "jest-mock";
+import { type Mocked } from "jest-mock";
 
 import {
-    GenericPosition,
+    type GenericPosition,
     GeolocationError,
     getGeoUri,
     mapGeolocationError,
diff --git a/test/unit-tests/utils/connection-test.ts b/test/unit-tests/utils/connection-test.ts
index f39c5164e49d481bdcb1fc48a944edda6243567b..1df67070b3fcdda2ae966b266a2f5736a9082b88 100644
--- a/test/unit-tests/utils/connection-test.ts
+++ b/test/unit-tests/utils/connection-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { ClientEvent, ClientEventHandlerMap, SyncState } from "matrix-js-sdk/src/matrix";
+import { type ClientEvent, type ClientEventHandlerMap, SyncState } from "matrix-js-sdk/src/matrix";
 
 import { createReconnectedListener } from "../../../src/utils/connection";
 
diff --git a/test/unit-tests/utils/createVoiceMessageContent-test.ts b/test/unit-tests/utils/createVoiceMessageContent-test.ts
index 6790104fc64ff131bf0300b5006c217870d1714b..83f14cbb9743594f2690fe729ca3efa8fe1121ac 100644
--- a/test/unit-tests/utils/createVoiceMessageContent-test.ts
+++ b/test/unit-tests/utils/createVoiceMessageContent-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EncryptedFile } from "matrix-js-sdk/src/types";
+import { type EncryptedFile } from "matrix-js-sdk/src/types";
 
 import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent";
 
diff --git a/test/unit-tests/utils/crypto/deviceInfo-test.ts b/test/unit-tests/utils/crypto/deviceInfo-test.ts
index a5348781fd06c7cb23e1e9101fec4a8e97ef420d..616b47ae69d35ade1f1d29c6ea63573a6934a249 100644
--- a/test/unit-tests/utils/crypto/deviceInfo-test.ts
+++ b/test/unit-tests/utils/crypto/deviceInfo-test.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked, mocked } from "jest-mock";
-import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Mocked, mocked } from "jest-mock";
+import { type Device, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { getDeviceCryptoInfo, getUserDeviceIds } from "../../../../src/utils/crypto/deviceInfo";
 import { getMockClientWithEventEmitter, mockClientMethodsCrypto } from "../../../test-utils";
diff --git a/test/unit-tests/utils/device/clientInformation-test.ts b/test/unit-tests/utils/device/clientInformation-test.ts
index c85dbba4e615b97a5ea75eaeb20623e490daed3b..18478dbdaf341db5db430fb81d26081aac1af7c8 100644
--- a/test/unit-tests/utils/device/clientInformation-test.ts
+++ b/test/unit-tests/utils/device/clientInformation-test.ts
@@ -8,12 +8,12 @@ Please see LICENSE files in the repository root for full details.
 
 import { MatrixEvent } from "matrix-js-sdk/src/matrix";
 
-import BasePlatform from "../../../../src/BasePlatform";
-import { IConfigOptions } from "../../../../src/IConfigOptions";
+import type BasePlatform from "../../../../src/BasePlatform";
+import { type IConfigOptions } from "../../../../src/IConfigOptions";
 import { getDeviceClientInformation, recordClientInformation } from "../../../../src/utils/device/clientInformation";
 import { getMockClientWithEventEmitter } from "../../../test-utils";
 import { DEFAULTS } from "../../../../src/SdkConfig";
-import { DeepReadonly } from "../../../../src/@types/common";
+import { type DeepReadonly } from "../../../../src/@types/common";
 
 describe("recordClientInformation()", () => {
     const deviceId = "my-device-id";
@@ -28,7 +28,7 @@ describe("recordClientInformation()", () => {
     const sdkConfig: DeepReadonly<IConfigOptions> = {
         ...DEFAULTS,
         brand: "Test Brand",
-        element_call: { url: "", use_exclusively: false, brand: "Element Call" },
+        element_call: { use_exclusively: false, brand: "Element Call" },
     };
 
     const platform = {
diff --git a/test/unit-tests/utils/device/parseUserAgent-test.ts b/test/unit-tests/utils/device/parseUserAgent-test.ts
index 26cdb22a37707047a3da81b07564400813d21dc4..7ed2af468212f6065943ddb68ec74b744d89a9b2 100644
--- a/test/unit-tests/utils/device/parseUserAgent-test.ts
+++ b/test/unit-tests/utils/device/parseUserAgent-test.ts
@@ -6,7 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { DeviceType, ExtendedDeviceInformation, parseUserAgent } from "../../../../src/utils/device/parseUserAgent";
+import {
+    DeviceType,
+    type ExtendedDeviceInformation,
+    parseUserAgent,
+} from "../../../../src/utils/device/parseUserAgent";
 
 const makeDeviceExtendedInfo = (
     deviceType: DeviceType,
diff --git a/test/unit-tests/utils/direct-messages-test.ts b/test/unit-tests/utils/direct-messages-test.ts
index cebb80db958217015bb8f1ce7819a4684bb1ba8b..45bf7d1f7b33c712a6b347e6045ae83386e827d8 100644
--- a/test/unit-tests/utils/direct-messages-test.ts
+++ b/test/unit-tests/utils/direct-messages-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { ClientEvent, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -22,7 +22,7 @@ import { waitForRoomReadyAndApplyAfterCreateCallbacks } from "../../../src/utils
 import { findDMRoom } from "../../../src/utils/dm/findDMRoom";
 import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom";
 import { startDm } from "../../../src/utils/dm/startDm";
-import { Member } from "../../../src/utils/direct-messages";
+import { type Member } from "../../../src/utils/direct-messages";
 import { resolveThreePids } from "../../../src/utils/threepids";
 
 jest.mock("../../../src/utils/rooms", () => ({
diff --git a/test/unit-tests/utils/dm/createDmLocalRoom-test.ts b/test/unit-tests/utils/dm/createDmLocalRoom-test.ts
index f42804489d7b7c268c1e8e1ebcde2ed870fb098e..bb23b2f2d5d17bf8da357e8c2bf9583e94309159 100644
--- a/test/unit-tests/utils/dm/createDmLocalRoom-test.ts
+++ b/test/unit-tests/utils/dm/createDmLocalRoom-test.ts
@@ -7,12 +7,12 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, KNOWN_SAFE_ROOM_VERSION, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { EventType, KNOWN_SAFE_ROOM_VERSION, type MatrixClient } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { canEncryptToAllUsers } from "../../../../src/createRoom";
-import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
-import { DirectoryMember, Member, ThreepidMember } from "../../../../src/utils/direct-messages";
+import { type LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
+import { DirectoryMember, type Member, ThreepidMember } from "../../../../src/utils/direct-messages";
 import { createDmLocalRoom } from "../../../../src/utils/dm/createDmLocalRoom";
 import { privateShouldBeEncrypted } from "../../../../src/utils/rooms";
 import { createTestClient } from "../../../test-utils";
diff --git a/test/unit-tests/utils/dm/findDMForUser-test.ts b/test/unit-tests/utils/dm/findDMForUser-test.ts
index 1e12eebcd14798a8f1bb7795514e49c9bde4f57b..c5bd6eef3870723898a50c0b078e6dbb49d7b510 100644
--- a/test/unit-tests/utils/dm/findDMForUser-test.ts
+++ b/test/unit-tests/utils/dm/findDMForUser-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
diff --git a/test/unit-tests/utils/dm/findDMRoom-test.ts b/test/unit-tests/utils/dm/findDMRoom-test.ts
index 17efd31d9ccb0dc01eb9bad5ee6a3faa62c90835..170ed774bffa716f45d73ecb6549e828abeaf46d 100644
--- a/test/unit-tests/utils/dm/findDMRoom-test.ts
+++ b/test/unit-tests/utils/dm/findDMRoom-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import { DirectoryMember, ThreepidMember } from "../../../../src/utils/direct-messages";
diff --git a/test/unit-tests/utils/export-test.tsx b/test/unit-tests/utils/export-test.tsx
index 3ff63da4b3bcd9a13a8762ed7a74ac0bca7705be..a5e0063f3b2f6bad80671919669db7176882eb61 100644
--- a/test/unit-tests/utils/export-test.tsx
+++ b/test/unit-tests/utils/export-test.tsx
@@ -8,18 +8,19 @@ Please see LICENSE files in the repository root for full details.
 
 import { render } from "jest-matrix-react";
 import {
-    IContent,
-    MatrixClient,
+    type IContent,
+    type MatrixClient,
     MatrixEvent,
     Room,
-    RoomMember,
+    type RoomMember,
     RelationType,
     EventType,
 } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
+import { type JSX } from "react";
 
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
-import { IExportOptions, ExportType, ExportFormat } from "../../../src/utils/exportUtils/exportUtils";
+import { type IExportOptions, ExportType, ExportFormat } from "../../../src/utils/exportUtils/exportUtils";
 import PlainTextExporter from "../../../src/utils/exportUtils/PlainTextExport";
 import HTMLExporter from "../../../src/utils/exportUtils/HtmlExport";
 import * as TestUtilsMatrix from "../../test-utils";
@@ -252,8 +253,9 @@ describe("export", function () {
             },
             setProgressText,
         );
-        const imageRegex = /<img.+ src="mxc:\/\/test.org" alt="image\.png"\/?>/;
-        expect(imageRegex.test(renderToString(exporter.getEventTile(mkImageEvent(), true)))).toBeTruthy();
+        expect(renderToString(exporter.getEventTile(mkImageEvent(), true))).toMatch(
+            /<img.+ alt="image\.png" src="mxc:\/\/test.org"\/?>/,
+        );
     });
 
     const invalidExportOptions: [string, IExportOptions][] = [
diff --git a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts
index e2ee0b670e3ee9022a7997fe881b13e5feeaae2e..6415efaf1d59c916a7e4b74a4eef69797afb84b1 100644
--- a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts
+++ b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts
@@ -8,10 +8,10 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     EventTimeline,
-    EventTimelineSet,
+    type EventTimelineSet,
     EventType,
-    IRoomEvent,
-    MatrixClient,
+    type IRoomEvent,
+    type MatrixClient,
     MatrixEvent,
     MsgType,
     Relations,
@@ -22,10 +22,10 @@ import {
 } from "matrix-js-sdk/src/matrix";
 import fetchMock from "fetch-mock-jest";
 import escapeHtml from "escape-html";
-import { RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
+import { type RelationsContainer } from "matrix-js-sdk/src/models/relations-container";
 
 import { filterConsole, mkReaction, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../../test-utils";
-import { ExportType, IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
+import { ExportType, type IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
 import SdkConfig from "../../../../src/SdkConfig";
 import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
diff --git a/test/unit-tests/utils/exportUtils/JSONExport-test.ts b/test/unit-tests/utils/exportUtils/JSONExport-test.ts
index 0dcf49059d83f22eb96caa019f093d7cc57c05d0..9cc071b4e24b3624d7a6be7d9b7b69cb7fe75c40 100644
--- a/test/unit-tests/utils/exportUtils/JSONExport-test.ts
+++ b/test/unit-tests/utils/exportUtils/JSONExport-test.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import JSONExporter from "../../../../src/utils/exportUtils/JSONExport";
 import { createTestClient, mkStubRoom, REPEATABLE_DATE } from "../../../test-utils";
-import { ExportType, IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
+import { ExportType, type IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
 
 describe("JSONExport", () => {
     beforeEach(() => {
diff --git a/test/unit-tests/utils/exportUtils/PlainTextExport-test.ts b/test/unit-tests/utils/exportUtils/PlainTextExport-test.ts
index 4b28c5bfb41d8984988f1288c3b710d11c6f7335..006e86b3bf66e382c4d08d6c43e6ca8f6b77f31c 100644
--- a/test/unit-tests/utils/exportUtils/PlainTextExport-test.ts
+++ b/test/unit-tests/utils/exportUtils/PlainTextExport-test.ts
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
 
 import { createTestClient, mkStubRoom, REPEATABLE_DATE } from "../../../test-utils";
-import { ExportType, IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
+import { ExportType, type IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
 import PlainTextExporter from "../../../../src/utils/exportUtils/PlainTextExport";
 import SettingsStore from "../../../../src/settings/SettingsStore";
 
diff --git a/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap b/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
index 94f31f76528bab3660f4f3f96f22fbb8f1dde2ed..56cc1d0f278febcf8318ecf8e886d39aff4927b7 100644
--- a/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
+++ b/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
@@ -18,7 +18,7 @@ exports[`HTMLExport should export 1`] = `
                         <div class="mx_MatrixChat">
                         <main class="mx_RoomView">
                             <div class="mx_Flex mx_RoomHeader light-panel">
-                                <span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size:32px">!</span>
+                                <span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size:32px">!</span>
                                 <div class="mx_RoomHeader_infoWrapper">
                                     <div
                                         dir="auto"
@@ -51,13 +51,13 @@ exports[`HTMLExport should export 1`] = `
                                         role="list"
                                     >
                                     <div class="mx_NewRoomIntro">
-                                            <span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size:32px">!</span>
+                                            <span role="presentation" title="!myroom:example.org" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size:32px">!</span>
                                             <h2> !myroom:example.org </h2>
                                             <p>  created this room. <br/><br/> <p><span>This is the start of export of <strong>!myroom:example.org</strong>. Exported by <a href="https://matrix.to/#/%40userId%3Amatrix.org" target="_blank" rel="noopener noreferrer"><strong>@userId:matrix.org</strong></a> at 11/17/2022.</span></p> </p>
                                             <br/>
                                             <p>  </p>
                                         </div>
-                                    <li><div class="mx_TimelineSeparator" role="separator" aria-label="Thu, Jan 1, 1970"><hr role="none"/><div class="mx_DateSeparator_dateContent"><h2 class="mx_DateSeparator_dateHeading" aria-hidden="true">Thu, Jan 1, 1970</h2></div><hr role="none"/></div></li><div class="mx_Export_EventWrapper" id="49"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="49" data-layout="group" data-self="false" data-event-id="49" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user49:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user49:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/49" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #49</div></div></div></li></div><div class="mx_Export_EventWrapper" id="48"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="48" data-layout="group" data-self="false" data-event-id="48" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user48:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user48:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/48" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #48</div></div></div></li></div><div class="mx_Export_EventWrapper" id="47"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="47" data-layout="group" data-self="false" data-event-id="47" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user47:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user47:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/47" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #47</div></div></div></li></div><div class="mx_Export_EventWrapper" id="46"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="46" data-layout="group" data-self="false" data-event-id="46" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user46:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user46:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/46" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #46</div></div></div></li></div><div class="mx_Export_EventWrapper" id="45"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="45" data-layout="group" data-self="false" data-event-id="45" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user45:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user45:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/45" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #45</div></div></div></li></div><div class="mx_Export_EventWrapper" id="44"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="44" data-layout="group" data-self="false" data-event-id="44" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user44:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user44:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/44" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #44</div></div></div></li></div><div class="mx_Export_EventWrapper" id="43"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="43" data-layout="group" data-self="false" data-event-id="43" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user43:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user43:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/43" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #43</div></div></div></li></div><div class="mx_Export_EventWrapper" id="42"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="42" data-layout="group" data-self="false" data-event-id="42" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user42:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user42:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/42" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #42</div></div></div></li></div><div class="mx_Export_EventWrapper" id="41"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="41" data-layout="group" data-self="false" data-event-id="41" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user41:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user41:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/41" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #41</div></div></div></li></div><div class="mx_Export_EventWrapper" id="40"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="40" data-layout="group" data-self="false" data-event-id="40" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user40:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user40:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/40" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #40</div></div></div></li></div><div class="mx_Export_EventWrapper" id="39"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="39" data-layout="group" data-self="false" data-event-id="39" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user39:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user39:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/39" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #39</div></div></div></li></div><div class="mx_Export_EventWrapper" id="38"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="38" data-layout="group" data-self="false" data-event-id="38" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user38:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user38:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/38" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #38</div></div></div></li></div><div class="mx_Export_EventWrapper" id="37"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="37" data-layout="group" data-self="false" data-event-id="37" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user37:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user37:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/37" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #37</div></div></div></li></div><div class="mx_Export_EventWrapper" id="36"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="36" data-layout="group" data-self="false" data-event-id="36" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user36:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user36:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/36" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #36</div></div></div></li></div><div class="mx_Export_EventWrapper" id="35"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="35" data-layout="group" data-self="false" data-event-id="35" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user35:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user35:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/35" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #35</div></div></div></li></div><div class="mx_Export_EventWrapper" id="34"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="34" data-layout="group" data-self="false" data-event-id="34" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user34:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user34:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/34" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #34</div></div></div></li></div><div class="mx_Export_EventWrapper" id="33"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="33" data-layout="group" data-self="false" data-event-id="33" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user33:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user33:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/33" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #33</div></div></div></li></div><div class="mx_Export_EventWrapper" id="32"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="32" data-layout="group" data-self="false" data-event-id="32" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user32:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user32:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/32" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #32</div></div></div></li></div><div class="mx_Export_EventWrapper" id="31"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="31" data-layout="group" data-self="false" data-event-id="31" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user31:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user31:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/31" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #31</div></div></div></li></div><div class="mx_Export_EventWrapper" id="30"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="30" data-layout="group" data-self="false" data-event-id="30" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user30:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user30:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/30" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #30</div></div></div></li></div><div class="mx_Export_EventWrapper" id="29"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="29" data-layout="group" data-self="false" data-event-id="29" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user29:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user29:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/29" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #29</div></div></div></li></div><div class="mx_Export_EventWrapper" id="28"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="28" data-layout="group" data-self="false" data-event-id="28" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user28:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user28:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/28" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #28</div></div></div></li></div><div class="mx_Export_EventWrapper" id="27"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="27" data-layout="group" data-self="false" data-event-id="27" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user27:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user27:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/27" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #27</div></div></div></li></div><div class="mx_Export_EventWrapper" id="26"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="26" data-layout="group" data-self="false" data-event-id="26" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user26:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user26:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/26" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #26</div></div></div></li></div><div class="mx_Export_EventWrapper" id="25"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="25" data-layout="group" data-self="false" data-event-id="25" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user25:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user25:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/25" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #25</div></div></div></li></div><div class="mx_Export_EventWrapper" id="24"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="24" data-layout="group" data-self="false" data-event-id="24" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user24:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user24:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/24" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #24</div></div></div></li></div><div class="mx_Export_EventWrapper" id="23"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="23" data-layout="group" data-self="false" data-event-id="23" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user23:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user23:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/23" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #23</div></div></div></li></div><div class="mx_Export_EventWrapper" id="22"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="22" data-layout="group" data-self="false" data-event-id="22" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user22:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user22:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/22" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #22</div></div></div></li></div><div class="mx_Export_EventWrapper" id="21"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="21" data-layout="group" data-self="false" data-event-id="21" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user21:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user21:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/21" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #21</div></div></div></li></div><div class="mx_Export_EventWrapper" id="20"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="20" data-layout="group" data-self="false" data-event-id="20" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user20:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user20:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/20" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #20</div></div></div></li></div><div class="mx_Export_EventWrapper" id="19"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="19" data-layout="group" data-self="false" data-event-id="19" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user19:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user19:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/19" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #19</div></div></div></li></div><div class="mx_Export_EventWrapper" id="18"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="18" data-layout="group" data-self="false" data-event-id="18" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user18:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user18:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/18" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #18</div></div></div></li></div><div class="mx_Export_EventWrapper" id="17"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="17" data-layout="group" data-self="false" data-event-id="17" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user17:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user17:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/17" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #17</div></div></div></li></div><div class="mx_Export_EventWrapper" id="16"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="16" data-layout="group" data-self="false" data-event-id="16" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user16:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user16:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/16" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #16</div></div></div></li></div><div class="mx_Export_EventWrapper" id="15"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="15" data-layout="group" data-self="false" data-event-id="15" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user15:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user15:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/15" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #15</div></div></div></li></div><div class="mx_Export_EventWrapper" id="14"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="14" data-layout="group" data-self="false" data-event-id="14" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user14:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user14:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/14" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #14</div></div></div></li></div><div class="mx_Export_EventWrapper" id="13"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="13" data-layout="group" data-self="false" data-event-id="13" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user13:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user13:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/13" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #13</div></div></div></li></div><div class="mx_Export_EventWrapper" id="12"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="12" data-layout="group" data-self="false" data-event-id="12" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user12:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user12:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/12" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #12</div></div></div></li></div><div class="mx_Export_EventWrapper" id="11"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="11" data-layout="group" data-self="false" data-event-id="11" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user11:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user11:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/11" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #11</div></div></div></li></div><div class="mx_Export_EventWrapper" id="10"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="10" data-layout="group" data-self="false" data-event-id="10" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user10:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user10:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/10" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #10</div></div></div></li></div><div class="mx_Export_EventWrapper" id="9"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="9" data-layout="group" data-self="false" data-event-id="9" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user9:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user9:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/9" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #9</div></div></div></li></div><div class="mx_Export_EventWrapper" id="8"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="8" data-layout="group" data-self="false" data-event-id="8" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user8:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user8:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/8" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #8</div></div></div></li></div><div class="mx_Export_EventWrapper" id="7"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="7" data-layout="group" data-self="false" data-event-id="7" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user7:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user7:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/7" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #7</div></div></div></li></div><div class="mx_Export_EventWrapper" id="6"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="6" data-layout="group" data-self="false" data-event-id="6" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user6:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user6:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/6" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #6</div></div></div></li></div><div class="mx_Export_EventWrapper" id="5"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="5" data-layout="group" data-self="false" data-event-id="5" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user5:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user5:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/5" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #5</div></div></div></li></div><div class="mx_Export_EventWrapper" id="4"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="4" data-layout="group" data-self="false" data-event-id="4" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user4:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user4:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/4" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #4</div></div></div></li></div><div class="mx_Export_EventWrapper" id="3"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="3" data-layout="group" data-self="false" data-event-id="3" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user3:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user3:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/3" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #3</div></div></div></li></div><div class="mx_Export_EventWrapper" id="2"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="2" data-layout="group" data-self="false" data-event-id="2" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user2:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user2:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/2" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #2</div></div></div></li></div><div class="mx_Export_EventWrapper" id="1"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="1" data-layout="group" data-self="false" data-event-id="1" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user1:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user1:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/1" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #1</div></div></div></li></div><div class="mx_Export_EventWrapper" id="0"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="0" data-layout="group" data-self="false" data-event-id="0" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user0:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user0:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/0" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #0</div></div></div><div class="mx_EventTile_footer"><div class="mx_ReactionsRow" role="toolbar" aria-label="Reactions"><div aria-label="@user0:example.com reacted with 🙃" role="button" tabindex="0" aria-disabled="true" disabled="" class="mx_AccessibleButton mx_ReactionsRowButton mx_AccessibleButton_disabled"><span class="mx_ReactionsRowButton_content" aria-hidden="true">🙃</span><span class="mx_ReactionsRowButton_count" aria-hidden="true">1</span></div></div></div></li></div>
+                                    <li><div class="mx_TimelineSeparator" role="separator" aria-label="Thu, Jan 1, 1970"><hr role="none"/><div class="mx_DateSeparator_dateContent"><h2 class="mx_DateSeparator_dateHeading" aria-hidden="true">Thu, Jan 1, 1970</h2></div><hr role="none"/></div></li><div class="mx_Export_EventWrapper" id="49"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="49" data-layout="group" data-self="false" data-event-id="49" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user49:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user49:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/49" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #49</div></div></div></li></div><div class="mx_Export_EventWrapper" id="48"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="48" data-layout="group" data-self="false" data-event-id="48" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user48:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user48:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/48" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #48</div></div></div></li></div><div class="mx_Export_EventWrapper" id="47"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="47" data-layout="group" data-self="false" data-event-id="47" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user47:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user47:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/47" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #47</div></div></div></li></div><div class="mx_Export_EventWrapper" id="46"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="46" data-layout="group" data-self="false" data-event-id="46" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user46:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user46:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/46" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #46</div></div></div></li></div><div class="mx_Export_EventWrapper" id="45"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="45" data-layout="group" data-self="false" data-event-id="45" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user45:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user45:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/45" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #45</div></div></div></li></div><div class="mx_Export_EventWrapper" id="44"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="44" data-layout="group" data-self="false" data-event-id="44" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user44:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user44:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/44" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #44</div></div></div></li></div><div class="mx_Export_EventWrapper" id="43"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="43" data-layout="group" data-self="false" data-event-id="43" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user43:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user43:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/43" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #43</div></div></div></li></div><div class="mx_Export_EventWrapper" id="42"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="42" data-layout="group" data-self="false" data-event-id="42" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user42:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user42:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/42" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #42</div></div></div></li></div><div class="mx_Export_EventWrapper" id="41"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="41" data-layout="group" data-self="false" data-event-id="41" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user41:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user41:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/41" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #41</div></div></div></li></div><div class="mx_Export_EventWrapper" id="40"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="40" data-layout="group" data-self="false" data-event-id="40" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user40:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user40:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/40" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #40</div></div></div></li></div><div class="mx_Export_EventWrapper" id="39"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="39" data-layout="group" data-self="false" data-event-id="39" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user39:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user39:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/39" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #39</div></div></div></li></div><div class="mx_Export_EventWrapper" id="38"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="38" data-layout="group" data-self="false" data-event-id="38" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user38:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user38:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/38" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #38</div></div></div></li></div><div class="mx_Export_EventWrapper" id="37"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="37" data-layout="group" data-self="false" data-event-id="37" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user37:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user37:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/37" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #37</div></div></div></li></div><div class="mx_Export_EventWrapper" id="36"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="36" data-layout="group" data-self="false" data-event-id="36" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user36:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user36:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/36" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #36</div></div></div></li></div><div class="mx_Export_EventWrapper" id="35"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="35" data-layout="group" data-self="false" data-event-id="35" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user35:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user35:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/35" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #35</div></div></div></li></div><div class="mx_Export_EventWrapper" id="34"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="34" data-layout="group" data-self="false" data-event-id="34" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user34:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user34:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/34" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #34</div></div></div></li></div><div class="mx_Export_EventWrapper" id="33"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="33" data-layout="group" data-self="false" data-event-id="33" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user33:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user33:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/33" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #33</div></div></div></li></div><div class="mx_Export_EventWrapper" id="32"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="32" data-layout="group" data-self="false" data-event-id="32" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user32:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user32:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/32" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #32</div></div></div></li></div><div class="mx_Export_EventWrapper" id="31"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="31" data-layout="group" data-self="false" data-event-id="31" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user31:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user31:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/31" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #31</div></div></div></li></div><div class="mx_Export_EventWrapper" id="30"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="30" data-layout="group" data-self="false" data-event-id="30" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user30:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user30:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/30" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #30</div></div></div></li></div><div class="mx_Export_EventWrapper" id="29"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="29" data-layout="group" data-self="false" data-event-id="29" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user29:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user29:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/29" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #29</div></div></div></li></div><div class="mx_Export_EventWrapper" id="28"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="28" data-layout="group" data-self="false" data-event-id="28" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user28:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user28:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/28" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #28</div></div></div></li></div><div class="mx_Export_EventWrapper" id="27"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="27" data-layout="group" data-self="false" data-event-id="27" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user27:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user27:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/27" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #27</div></div></div></li></div><div class="mx_Export_EventWrapper" id="26"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="26" data-layout="group" data-self="false" data-event-id="26" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user26:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user26:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/26" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #26</div></div></div></li></div><div class="mx_Export_EventWrapper" id="25"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="25" data-layout="group" data-self="false" data-event-id="25" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user25:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user25:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/25" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #25</div></div></div></li></div><div class="mx_Export_EventWrapper" id="24"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="24" data-layout="group" data-self="false" data-event-id="24" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user24:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user24:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/24" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #24</div></div></div></li></div><div class="mx_Export_EventWrapper" id="23"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="23" data-layout="group" data-self="false" data-event-id="23" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user23:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user23:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/23" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #23</div></div></div></li></div><div class="mx_Export_EventWrapper" id="22"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="22" data-layout="group" data-self="false" data-event-id="22" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user22:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user22:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/22" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #22</div></div></div></li></div><div class="mx_Export_EventWrapper" id="21"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="21" data-layout="group" data-self="false" data-event-id="21" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user21:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user21:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/21" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #21</div></div></div></li></div><div class="mx_Export_EventWrapper" id="20"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="20" data-layout="group" data-self="false" data-event-id="20" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user20:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user20:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/20" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #20</div></div></div></li></div><div class="mx_Export_EventWrapper" id="19"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="19" data-layout="group" data-self="false" data-event-id="19" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user19:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user19:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/19" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #19</div></div></div></li></div><div class="mx_Export_EventWrapper" id="18"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="18" data-layout="group" data-self="false" data-event-id="18" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user18:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user18:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/18" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #18</div></div></div></li></div><div class="mx_Export_EventWrapper" id="17"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="17" data-layout="group" data-self="false" data-event-id="17" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user17:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user17:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/17" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #17</div></div></div></li></div><div class="mx_Export_EventWrapper" id="16"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="16" data-layout="group" data-self="false" data-event-id="16" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user16:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user16:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/16" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #16</div></div></div></li></div><div class="mx_Export_EventWrapper" id="15"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="15" data-layout="group" data-self="false" data-event-id="15" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user15:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user15:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/15" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #15</div></div></div></li></div><div class="mx_Export_EventWrapper" id="14"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="14" data-layout="group" data-self="false" data-event-id="14" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user14:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user14:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/14" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #14</div></div></div></li></div><div class="mx_Export_EventWrapper" id="13"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="13" data-layout="group" data-self="false" data-event-id="13" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user13:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user13:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/13" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #13</div></div></div></li></div><div class="mx_Export_EventWrapper" id="12"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="12" data-layout="group" data-self="false" data-event-id="12" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user12:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user12:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/12" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #12</div></div></div></li></div><div class="mx_Export_EventWrapper" id="11"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="11" data-layout="group" data-self="false" data-event-id="11" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user11:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user11:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/11" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #11</div></div></div></li></div><div class="mx_Export_EventWrapper" id="10"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="10" data-layout="group" data-self="false" data-event-id="10" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user10:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user10:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/10" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #10</div></div></div></li></div><div class="mx_Export_EventWrapper" id="9"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="9" data-layout="group" data-self="false" data-event-id="9" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user9:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user9:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/9" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #9</div></div></div></li></div><div class="mx_Export_EventWrapper" id="8"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="8" data-layout="group" data-self="false" data-event-id="8" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user8:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user8:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/8" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #8</div></div></div></li></div><div class="mx_Export_EventWrapper" id="7"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="7" data-layout="group" data-self="false" data-event-id="7" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user7:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user7:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/7" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #7</div></div></div></li></div><div class="mx_Export_EventWrapper" id="6"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="6" data-layout="group" data-self="false" data-event-id="6" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user6:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user6:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/6" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #6</div></div></div></li></div><div class="mx_Export_EventWrapper" id="5"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="5" data-layout="group" data-self="false" data-event-id="5" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color2 mx_DisambiguatedProfile_displayName" dir="auto">@user5:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user5:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/5" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #5</div></div></div></li></div><div class="mx_Export_EventWrapper" id="4"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="4" data-layout="group" data-self="false" data-event-id="4" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color1 mx_DisambiguatedProfile_displayName" dir="auto">@user4:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user4:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="1" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/4" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #4</div></div></div></li></div><div class="mx_Export_EventWrapper" id="3"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="3" data-layout="group" data-self="false" data-event-id="3" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color6 mx_DisambiguatedProfile_displayName" dir="auto">@user3:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user3:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="6" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/3" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #3</div></div></div></li></div><div class="mx_Export_EventWrapper" id="2"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="2" data-layout="group" data-self="false" data-event-id="2" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color5 mx_DisambiguatedProfile_displayName" dir="auto">@user2:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user2:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="5" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/2" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #2</div></div></div></li></div><div class="mx_Export_EventWrapper" id="1"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="1" data-layout="group" data-self="false" data-event-id="1" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color4 mx_DisambiguatedProfile_displayName" dir="auto">@user1:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user1:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="4" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/1" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #1</div></div></div></li></div><div class="mx_Export_EventWrapper" id="0"><li class="mx_EventTile" tabindex="-1" aria-live="off" aria-atomic="true" data-scroll-tokens="0" data-layout="group" data-self="false" data-event-id="0" data-has-reply="false"><div class="mx_DisambiguatedProfile"><span class="mx_Username_color3 mx_DisambiguatedProfile_displayName" dir="auto">@user0:example.com</span></div><div class="mx_EventTile_avatar"><button role="button" aria-label="Profile picture" title="@user0:example.com" aria-live="off" data-testid="avatar-img" data-type="round" data-color="3" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" style="--cpd-avatar-size: 30px;">u</button></div><div class="mx_EventTile_line"><a href="https://matrix.to/#/!myroom:example.org/0" aria-label="00:00"><span class="mx_MessageTimestamp" aria-hidden="true" aria-live="off">00:00</span></a><div class="mx_MTextBody mx_EventTile_content"><div class="mx_EventTile_body translate" dir="auto">Message #0</div></div></div><div class="mx_EventTile_footer"><div class="mx_ReactionsRow" role="toolbar" aria-label="Reactions"><div aria-label="@user0:example.com reacted with 🙃" tabindex="0" role="button" aria-disabled="true" disabled="" class="mx_AccessibleButton mx_ReactionsRowButton mx_AccessibleButton_disabled"><span class="mx_ReactionsRowButton_content" aria-hidden="true">🙃</span><span class="mx_ReactionsRowButton_count" aria-hidden="true">1</span></div></div></div></li></div>
                                     </ol>
                                     </div>
                                 </div>
diff --git a/test/unit-tests/utils/leave-behaviour-test.ts b/test/unit-tests/utils/leave-behaviour-test.ts
index f42fc072339d43275563d2fba7501e33ef2d42e1..23be7fef9867141454848c23080d3e63c264ab90 100644
--- a/test/unit-tests/utils/leave-behaviour-test.ts
+++ b/test/unit-tests/utils/leave-behaviour-test.ts
@@ -6,21 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { mocked, Mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { mocked, type Mocked } from "jest-mock";
+import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
 import { sleep } from "matrix-js-sdk/src/utils";
 
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
 import { mkRoom, resetAsyncStoreWithClient, setupAsyncStoreWithClient, stubClient } from "../../test-utils";
 import defaultDispatcher from "../../../src/dispatcher/dispatcher";
-import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
+import { type ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
 import { Action } from "../../../src/dispatcher/actions";
 import { leaveRoomBehaviour } from "../../../src/utils/leave-behaviour";
 import { SdkContextClass } from "../../../src/contexts/SDKContext";
 import DMRoomMap from "../../../src/utils/DMRoomMap";
 import SpaceStore from "../../../src/stores/spaces/SpaceStore";
 import { MetaSpace } from "../../../src/stores/spaces";
-import { ActionPayload } from "../../../src/dispatcher/payloads";
+import { type ActionPayload } from "../../../src/dispatcher/payloads";
 import SettingsStore from "../../../src/settings/SettingsStore";
 
 describe("leaveRoomBehaviour", () => {
diff --git a/test/unit-tests/utils/local-room-test.ts b/test/unit-tests/utils/local-room-test.ts
index 91219dcb585b71a11d584c76cee9bb6d15bab34f..01ad1977b9923c407f9101a260e078b46af2d64b 100644
--- a/test/unit-tests/utils/local-room-test.ts
+++ b/test/unit-tests/utils/local-room-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../../../src/models/LocalRoom";
diff --git a/test/unit-tests/utils/localRoom/isRoomReady-test.ts b/test/unit-tests/utils/localRoom/isRoomReady-test.ts
index 2effc7c6a9a859eb08e20f8340886954e111dae9..d257f1e2a22daf47cefb16186ccbf58961dae1df 100644
--- a/test/unit-tests/utils/localRoom/isRoomReady-test.ts
+++ b/test/unit-tests/utils/localRoom/isRoomReady-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
 import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
diff --git a/test/unit-tests/utils/location/isSelfLocation-test.ts b/test/unit-tests/utils/location/isSelfLocation-test.ts
index 6cccc63f72b540d201af56a1fc30d1d2fdd6ac00..927fa2ed62ab389d59bbab5c1cb7eab624e35f18 100644
--- a/test/unit-tests/utils/location/isSelfLocation-test.ts
+++ b/test/unit-tests/utils/location/isSelfLocation-test.ts
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
 
 import {
     M_TEXT,
-    ILocationContent,
-    LocationAssetType,
+    type ILocationContent,
+    type LocationAssetType,
     M_ASSET,
     M_LOCATION,
     M_TIMESTAMP,
diff --git a/test/unit-tests/utils/membership-test.ts b/test/unit-tests/utils/membership-test.ts
index c63dbe9074438e76ffe522976488f146883148df..e0ade641860fb04773187f1ffc8bdc6df37238f0 100644
--- a/test/unit-tests/utils/membership-test.ts
+++ b/test/unit-tests/utils/membership-test.ts
@@ -6,7 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { MatrixClient, MatrixEvent, Room, RoomMember, RoomState, RoomStateEvent } from "matrix-js-sdk/src/matrix";
+import {
+    type MatrixClient,
+    type MatrixEvent,
+    Room,
+    type RoomMember,
+    type RoomState,
+    RoomStateEvent,
+} from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 import { mocked } from "jest-mock";
 
diff --git a/test/unit-tests/utils/notifications-test.ts b/test/unit-tests/utils/notifications-test.ts
index eeec145a367619f866ee28530e14e7a70349ef50..5a416deede93ae289f81a84428399117a0c66330 100644
--- a/test/unit-tests/utils/notifications-test.ts
+++ b/test/unit-tests/utils/notifications-test.ts
@@ -1,5 +1,5 @@
 /*
-Copyright 2024 New Vector Ltd.
+Copyright 2024, 2025 New Vector Ltd.
 Copyright 2022 The Matrix.org Foundation C.I.C.
 
 SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -10,11 +10,11 @@ import {
     MatrixEvent,
     NotificationCountType,
     Room,
-    MatrixClient,
+    type MatrixClient,
     ReceiptType,
-    AccountDataEvents,
+    type AccountDataEvents,
 } from "matrix-js-sdk/src/matrix";
-import { Mocked, mocked } from "jest-mock";
+import { type Mocked, mocked } from "jest-mock";
 
 import {
     localNotificationsAreSilenced,
@@ -28,13 +28,13 @@ import {
     getMarkedUnreadState,
     setMarkedUnreadState,
 } from "../../../src/utils/notifications";
-import SettingsStore from "../../../src/settings/SettingsStore";
-import { getMockClientWithEventEmitter } from "../../test-utils/client";
+import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../test-utils/client";
 import { mkMessage, stubClient } from "../../test-utils/test-utils";
 import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
 import { NotificationLevel } from "../../../src/stores/notifications/NotificationLevel";
-
-jest.mock("../../../src/settings/SettingsStore");
+import { SettingLevel } from "../../../src/settings/SettingLevel";
+import MatrixClientBackedController from "../../../src/settings/controllers/MatrixClientBackedController";
+import SettingsStore from "../../../src/settings/SettingsStore";
 
 describe("notifications", () => {
     let accountDataStore: Record<string, MatrixEvent> = {};
@@ -44,6 +44,7 @@ describe("notifications", () => {
     beforeEach(() => {
         jest.clearAllMocks();
         mockClient = getMockClientWithEventEmitter({
+            ...mockClientMethodsServer(),
             isGuest: jest.fn().mockReturnValue(false),
             getAccountData: jest.fn().mockImplementation((eventType) => accountDataStore[eventType]),
             setAccountData: jest.fn().mockImplementation((eventType, content) => {
@@ -52,10 +53,20 @@ describe("notifications", () => {
                     content,
                 });
             }),
+            isVersionSupported: jest.fn().mockImplementation(async (v) => v === "v1.4"),
         });
+
+        // Ensure unstable settings are supported, otherwise it will use the default value.
+        MatrixClientBackedController.matrixClient = mockClient;
         accountDataStore = {};
         accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId!);
-        mocked(SettingsStore).getValue.mockReturnValue(false);
+        // Disable all notifications
+        deviceNotificationSettingsKeys.forEach((k) => SettingsStore.setValue(k, null, SettingLevel.DEVICE, false));
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+        SettingsStore.reset();
     });
 
     describe("createLocalNotification", () => {
@@ -75,10 +86,15 @@ describe("notifications", () => {
         it.each(deviceNotificationSettingsKeys)(
             "unsilenced for existing sessions when %s setting is truthy",
             async (settingKey) => {
-                mocked(SettingsStore).getValue.mockImplementation((key): any => {
-                    return key === settingKey;
+                // We need to spy `getValue` because setting these keys requires mocking
+                // the platform to support notifications, which is out of scope for this test.
+                const origFn = SettingsStore.getValue;
+                jest.spyOn(SettingsStore, "getValue").mockImplementation((name, ...args) => {
+                    if (name === settingKey) {
+                        return true;
+                    }
+                    return origFn(name, ...args);
                 });
-
                 await createLocalNotificationSettingsIfNeeded(mockClient);
                 const event = mockClient.getAccountData(accountDataEventKey);
                 expect(event?.getContent().is_silenced).toBe(false);
@@ -116,7 +132,6 @@ describe("notifications", () => {
         const ROOM_ID = "123";
         const USER_ID = "@bob:example.org";
         let message: MatrixEvent;
-        let sendReceiptsSetting = true;
 
         beforeEach(() => {
             stubClient();
@@ -131,9 +146,7 @@ describe("notifications", () => {
             room.addLiveEvents([message], { addToState: true });
             sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({});
             jest.spyOn(client, "getRooms").mockReturnValue([room]);
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
-                return name === "sendReadReceipts" && sendReceiptsSetting;
-            });
+            SettingsStore.setValue("sendReadReceipts", null, SettingLevel.DEVICE, true);
         });
 
         it("sends a request even if everything has been read", async () => {
@@ -152,11 +165,8 @@ describe("notifications", () => {
         });
 
         describe("when sendReadReceipts setting is disabled", () => {
-            beforeEach(() => {
-                sendReceiptsSetting = false;
-            });
-
             it("should send a private read receipt", async () => {
+                SettingsStore.setValue("sendReadReceipts", null, SettingLevel.DEVICE, false);
                 await clearRoomNotification(room, client);
                 expect(sendReadReceiptSpy).toHaveBeenCalledWith(message, ReceiptType.ReadPrivate, true);
             });
@@ -177,9 +187,7 @@ describe("notifications", () => {
             room = new Room(ROOM_ID, client, USER_ID);
             sendReadReceiptSpy = jest.spyOn(client, "sendReadReceipt").mockResolvedValue({});
             jest.spyOn(client, "getRooms").mockReturnValue([room]);
-            jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => {
-                return name === "sendReadReceipts";
-            });
+            SettingsStore.setValue("sendReadReceipts", null, SettingLevel.DEVICE, true);
         });
 
         it("does not send any requests if everything has been read", () => {
@@ -212,7 +220,7 @@ describe("notifications", () => {
             room.addLiveEvents([message], { addToState: true });
             room.setUnreadNotificationCount(NotificationCountType.Total, 1);
 
-            jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
+            SettingsStore.setValue("sendReadReceipts", null, SettingLevel.DEVICE, false);
 
             await clearAllNotifications(client);
 
diff --git a/test/unit-tests/utils/oidc/TokenRefresher-test.ts b/test/unit-tests/utils/oidc/TokenRefresher-test.ts
index 643f61faacbe185abd62a247cb0c0333c7fc80bc..66f0e800e0247ad5da4f58f69cb227576da1f930 100644
--- a/test/unit-tests/utils/oidc/TokenRefresher-test.ts
+++ b/test/unit-tests/utils/oidc/TokenRefresher-test.ts
@@ -38,7 +38,7 @@ describe("TokenRefresher", () => {
     };
 
     beforeEach(() => {
-        fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig.metadata);
+        fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig);
         fetchMock.get(`${issuer}jwks`, {
             status: 200,
             headers: {
diff --git a/test/unit-tests/utils/oidc/authorize-test.ts b/test/unit-tests/utils/oidc/authorize-test.ts
index 72c42fab3705a4b136b4196ed2f5cca86384cf36..3ccc77e633d0e6ad44906909ddaeaaf6279c2038 100644
--- a/test/unit-tests/utils/oidc/authorize-test.ts
+++ b/test/unit-tests/utils/oidc/authorize-test.ts
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
 import fetchMock from "fetch-mock-jest";
 import { completeAuthorizationCodeGrant } from "matrix-js-sdk/src/oidc/authorize";
 import * as randomStringUtils from "matrix-js-sdk/src/randomstring";
-import { BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
+import { type BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
 import { mocked } from "jest-mock";
 import { Crypto } from "@peculiar/webcrypto";
 import { getRandomValues } from "node:crypto";
@@ -49,7 +49,7 @@ describe("OIDC authorization", () => {
             origin: baseUrl,
         };
 
-        jest.spyOn(randomStringUtils, "randomString").mockRestore();
+        jest.spyOn(randomStringUtils, "secureRandomString").mockRestore();
         mockPlatformPeg();
         Object.defineProperty(window, "crypto", {
             value: {
@@ -61,13 +61,11 @@ describe("OIDC authorization", () => {
     });
 
     beforeAll(() => {
-        fetchMock.get(
-            `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`,
-            delegatedAuthConfig.metadata,
-        );
+        fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig);
     });
 
     afterAll(() => {
+        // @ts-expect-error
         window.location = realWindowLocation;
     });
 
diff --git a/test/unit-tests/utils/oidc/persistOidcSettings-test.ts b/test/unit-tests/utils/oidc/persistOidcSettings-test.ts
index 4ffbfd43b238cb51feda351ba90dfa62b844de90..5b81d142b089f96bdf0418e48df9dd7d25117693 100644
--- a/test/unit-tests/utils/oidc/persistOidcSettings-test.ts
+++ b/test/unit-tests/utils/oidc/persistOidcSettings-test.ts
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { IdTokenClaims } from "oidc-client-ts";
+import { type IdTokenClaims } from "oidc-client-ts";
 import { decodeIdToken } from "matrix-js-sdk/src/matrix";
 import { mocked } from "jest-mock";
 
diff --git a/test/unit-tests/utils/oidc/registerClient-test.ts b/test/unit-tests/utils/oidc/registerClient-test.ts
index 0c6a41bc36d5a35221fab4c6a8b85386a7a5735c..b266f4f4cd45b35341086d2426cd02a0c5595539 100644
--- a/test/unit-tests/utils/oidc/registerClient-test.ts
+++ b/test/unit-tests/utils/oidc/registerClient-test.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
 
 import fetchMockJest from "fetch-mock-jest";
 import { OidcError } from "matrix-js-sdk/src/oidc/error";
-import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
+import { type OidcClientConfig } from "matrix-js-sdk/src/matrix";
 
 import { getOidcClientId } from "../../../../src/utils/oidc/registerClient";
 import { mockPlatformPeg } from "../../../test-utils";
@@ -58,7 +58,7 @@ describe("getOidcClientId()", () => {
         const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig(
             "https://issuerWithoutStaticClientId.org/",
         );
-        authConfigWithoutRegistration.registrationEndpoint = undefined;
+        authConfigWithoutRegistration.registration_endpoint = undefined;
         await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow(
             OidcError.DynamicRegistrationNotSupported,
         );
@@ -69,7 +69,7 @@ describe("getOidcClientId()", () => {
     it("should handle when staticOidcClients object is falsy", async () => {
         const authConfigWithoutRegistration: OidcClientConfig = {
             ...delegatedAuthConfig,
-            registrationEndpoint: undefined,
+            registration_endpoint: undefined,
         };
         await expect(getOidcClientId(authConfigWithoutRegistration)).rejects.toThrow(
             OidcError.DynamicRegistrationNotSupported,
@@ -79,14 +79,14 @@ describe("getOidcClientId()", () => {
     });
 
     it("should make correct request to register client", async () => {
-        fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
+        fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
             status: 200,
             body: JSON.stringify({ client_id: dynamicClientId }),
         });
         expect(await getOidcClientId(delegatedAuthConfig)).toEqual(dynamicClientId);
         // didn't try to register
         expect(fetchMockJest).toHaveBeenCalledWith(
-            delegatedAuthConfig.registrationEndpoint!,
+            delegatedAuthConfig.registration_endpoint!,
             expect.objectContaining({
                 headers: {
                     "Accept": "application/json",
@@ -111,14 +111,14 @@ describe("getOidcClientId()", () => {
     });
 
     it("should throw when registration request fails", async () => {
-        fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
+        fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
             status: 500,
         });
         await expect(getOidcClientId(delegatedAuthConfig)).rejects.toThrow(OidcError.DynamicRegistrationFailed);
     });
 
     it("should throw when registration response is invalid", async () => {
-        fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, {
+        fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, {
             status: 200,
             // no clientId in response
             body: "{}",
diff --git a/test/unit-tests/utils/permalinks/Permalinks-test.ts b/test/unit-tests/utils/permalinks/Permalinks-test.ts
index c961ff2646265a7e71d71baf845e2ca348b3a06b..fcb7d3257fceb712656d7258639de693a535e04a 100644
--- a/test/unit-tests/utils/permalinks/Permalinks-test.ts
+++ b/test/unit-tests/utils/permalinks/Permalinks-test.ts
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { EventEmitter } from "events";
+import { type EventEmitter } from "events";
 import { Room, RoomMember, EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
 import { KnownMembership } from "matrix-js-sdk/src/types";
 
@@ -19,7 +19,7 @@ import {
     parsePermalink,
     RoomPermalinkCreator,
 } from "../../../../src/utils/permalinks/Permalinks";
-import { IConfigOptions } from "../../../../src/IConfigOptions";
+import { type IConfigOptions } from "../../../../src/IConfigOptions";
 import SdkConfig from "../../../../src/SdkConfig";
 import { getMockClientWithEventEmitter } from "../../../test-utils";
 
@@ -116,7 +116,7 @@ describe("Permalinks", function () {
 
     it("should gracefully handle invalid MXIDs", () => {
         const roomId = "!fake:example.org";
-        const alice50 = makeMemberWithPL(roomId, "@alice:pl_50:org", 50);
+        const alice50 = makeMemberWithPL(roomId, "@alice:pl-50:org", 50);
         const room = mockRoom(roomId, [alice50]);
         const creator = new RoomPermalinkCreator(room);
         creator.load();
diff --git a/test/unit-tests/utils/pillify-test.tsx b/test/unit-tests/utils/pillify-test.tsx
deleted file mode 100644
index 4cca311c5769a0c30a59400947509643d6783691..0000000000000000000000000000000000000000
--- a/test/unit-tests/utils/pillify-test.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { act, render } from "jest-matrix-react";
-import { MatrixEvent, ConditionKind, EventType, PushRuleActionName, Room, TweakName } from "matrix-js-sdk/src/matrix";
-import { mocked } from "jest-mock";
-
-import { pillifyLinks } from "../../../src/utils/pillify";
-import { stubClient } from "../../test-utils";
-import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
-import DMRoomMap from "../../../src/utils/DMRoomMap";
-import { ReactRootManager } from "../../../src/utils/react.tsx";
-
-describe("pillify", () => {
-    const roomId = "!room:id";
-    const event = new MatrixEvent({
-        room_id: roomId,
-        type: EventType.RoomMessage,
-        content: {
-            body: "@room",
-        },
-    });
-
-    beforeEach(() => {
-        stubClient();
-        const cli = MatrixClientPeg.safeGet();
-        const room = new Room(roomId, cli, cli.getUserId()!);
-        room.currentState.mayTriggerNotifOfType = jest.fn().mockReturnValue(true);
-        (cli.getRoom as jest.Mock).mockReturnValue(room);
-        cli.pushRules!.global = {
-            override: [
-                {
-                    rule_id: ".m.rule.roomnotif",
-                    default: true,
-                    enabled: true,
-                    conditions: [
-                        {
-                            kind: ConditionKind.EventMatch,
-                            key: "content.body",
-                            pattern: "@room",
-                        },
-                    ],
-                    actions: [
-                        PushRuleActionName.Notify,
-                        {
-                            set_tweak: TweakName.Highlight,
-                            value: true,
-                        },
-                    ],
-                },
-                {
-                    rule_id: ".m.rule.is_room_mention",
-                    default: true,
-                    enabled: true,
-                    conditions: [
-                        {
-                            kind: ConditionKind.EventPropertyIs,
-                            key: "content.m\\.mentions.room",
-                            value: true,
-                        },
-                        {
-                            kind: ConditionKind.SenderNotificationPermission,
-                            key: "room",
-                        },
-                    ],
-                    actions: [
-                        PushRuleActionName.Notify,
-                        {
-                            set_tweak: TweakName.Highlight,
-                        },
-                    ],
-                },
-            ],
-        };
-
-        DMRoomMap.makeShared(cli);
-    });
-
-    it("should do nothing for empty element", () => {
-        const { container } = render(<div />);
-        const originalHtml = container.outerHTML;
-        const containers = new ReactRootManager();
-        pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
-        expect(containers.elements).toHaveLength(0);
-        expect(container.outerHTML).toEqual(originalHtml);
-    });
-
-    it("should pillify @room", () => {
-        const { container } = render(<div>@room</div>);
-        const containers = new ReactRootManager();
-        act(() => pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers));
-        expect(containers.elements).toHaveLength(1);
-        expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
-    });
-
-    it("should pillify @room in an intentional mentions world", () => {
-        mocked(MatrixClientPeg.safeGet().supportsIntentionalMentions).mockReturnValue(true);
-        const { container } = render(<div>@room</div>);
-        const containers = new ReactRootManager();
-        act(() =>
-            pillifyLinks(
-                MatrixClientPeg.safeGet(),
-                [container],
-                new MatrixEvent({
-                    room_id: roomId,
-                    type: EventType.RoomMessage,
-                    content: {
-                        "body": "@room",
-                        "m.mentions": {
-                            room: true,
-                        },
-                    },
-                }),
-                containers,
-            ),
-        );
-        expect(containers.elements).toHaveLength(1);
-        expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
-    });
-
-    it("should not double up pillification on repeated calls", () => {
-        const { container } = render(<div>@room</div>);
-        const containers = new ReactRootManager();
-        act(() => {
-            pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
-            pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
-            pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
-            pillifyLinks(MatrixClientPeg.safeGet(), [container], event, containers);
-        });
-        expect(containers.elements).toHaveLength(1);
-        expect(container.querySelector(".mx_Pill.mx_AtRoomPill")?.textContent).toBe("!@room");
-    });
-});
diff --git a/test/unit-tests/utils/room/getJoinedNonFunctionalMembers-test.ts b/test/unit-tests/utils/room/getJoinedNonFunctionalMembers-test.ts
index bd31269d3322ea6487bef59372be72816ea36f6c..1c397e143e6543d4860ace56de1788ea8404ed03 100644
--- a/test/unit-tests/utils/room/getJoinedNonFunctionalMembers-test.ts
+++ b/test/unit-tests/utils/room/getJoinedNonFunctionalMembers-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
 
 import { getFunctionalMembers } from "../../../../src/utils/room/getFunctionalMembers";
 import { getJoinedNonFunctionalMembers } from "../../../../src/utils/room/getJoinedNonFunctionalMembers";
diff --git a/test/unit-tests/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite-test.ts b/test/unit-tests/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite-test.ts
index 290dc8cf6c7a872758a87fec4e4c04e81270c3a3..8e3de54896bdb3ee7de68864c1c18732e29b8425 100644
--- a/test/unit-tests/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite-test.ts
+++ b/test/unit-tests/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { mocked } from "jest-mock";
-import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
+import { type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
 
 import DMRoomMap from "../../../../src/utils/DMRoomMap";
 import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../../../src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
diff --git a/test/unit-tests/utils/room/tagRoom-test.ts b/test/unit-tests/utils/room/tagRoom-test.ts
index 41dc9ef11526b0cee76365b1b0637ec8bb02a28d..fa1850415eebb30e4f8efa730e53181e02ce97b8 100644
--- a/test/unit-tests/utils/room/tagRoom-test.ts
+++ b/test/unit-tests/utils/room/tagRoom-test.ts
@@ -10,7 +10,7 @@ import { Room } from "matrix-js-sdk/src/matrix";
 
 import RoomListActions from "../../../../src/actions/RoomListActions";
 import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
-import { DefaultTagID, TagID } from "../../../../src/stores/room-list/models";
+import { DefaultTagID, type TagID } from "../../../../src/stores/room-list/models";
 import RoomListStore from "../../../../src/stores/room-list/RoomListStore";
 import { tagRoom } from "../../../../src/utils/room/tagRoom";
 import { getMockClientWithEventEmitter } from "../../../test-utils";
diff --git a/test/unit-tests/utils/threepids-test.ts b/test/unit-tests/utils/threepids-test.ts
index 01b580abb0a7294dc3aab369b004557f3cbac1fb..4cf2a427d10dbe3fc4a4a7da0d34451b4af03a8e 100644
--- a/test/unit-tests/utils/threepids-test.ts
+++ b/test/unit-tests/utils/threepids-test.ts
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
 Please see LICENSE files in the repository root for full details.
 */
 
-import { Mocked } from "jest-mock";
-import { IIdentityServerProvider, MatrixClient } from "matrix-js-sdk/src/matrix";
+import { type Mocked } from "jest-mock";
+import { type IIdentityServerProvider, type MatrixClient } from "matrix-js-sdk/src/matrix";
 
 import { DirectoryMember, ThreepidMember } from "../../../src/utils/direct-messages";
 import { lookupThreePids, resolveThreePids } from "../../../src/utils/threepids";
diff --git a/test/unit-tests/utils/tooltipify-test.tsx b/test/unit-tests/utils/tooltipify-test.tsx
deleted file mode 100644
index 459cf2d2bf3ef92993ba54d4daefe39c5c2eda78..0000000000000000000000000000000000000000
--- a/test/unit-tests/utils/tooltipify-test.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
-Please see LICENSE files in the repository root for full details.
-*/
-
-import React from "react";
-import { act, render } from "jest-matrix-react";
-
-import { tooltipifyLinks } from "../../../src/utils/tooltipify";
-import PlatformPeg from "../../../src/PlatformPeg";
-import BasePlatform from "../../../src/BasePlatform";
-import { ReactRootManager } from "../../../src/utils/react.tsx";
-
-describe("tooltipify", () => {
-    jest.spyOn(PlatformPeg, "get").mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform);
-
-    it("does nothing for empty element", () => {
-        const { container: root } = render(<div />);
-        const originalHtml = root.outerHTML;
-        const containers = new ReactRootManager();
-        tooltipifyLinks([root], [], containers);
-        expect(containers.elements).toHaveLength(0);
-        expect(root.outerHTML).toEqual(originalHtml);
-    });
-
-    it("wraps single anchor", () => {
-        const { container: root } = render(
-            <div>
-                <a href="/foo">click</a>
-            </div>,
-        );
-        const containers = new ReactRootManager();
-        tooltipifyLinks([root], [], containers);
-        expect(containers.elements).toHaveLength(1);
-        const anchor = root.querySelector("a");
-        expect(anchor?.getAttribute("href")).toEqual("/foo");
-        const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
-        expect(tooltip).toBeDefined();
-    });
-
-    it("ignores node", () => {
-        const { container: root } = render(
-            <div>
-                <a href="/foo">click</a>
-            </div>,
-        );
-        const originalHtml = root.outerHTML;
-        const containers = new ReactRootManager();
-        tooltipifyLinks([root], [root.children[0]], containers);
-        expect(containers.elements).toHaveLength(0);
-        expect(root.outerHTML).toEqual(originalHtml);
-    });
-
-    it("does not re-wrap if called multiple times", async () => {
-        const { container: root, unmount } = render(
-            <div>
-                <a href="/foo">click</a>
-            </div>,
-        );
-        const containers = new ReactRootManager();
-        tooltipifyLinks([root], [], containers);
-        tooltipifyLinks([root], [], containers);
-        tooltipifyLinks([root], [], containers);
-        tooltipifyLinks([root], [], containers);
-        expect(containers.elements).toHaveLength(1);
-        const anchor = root.querySelector("a");
-        expect(anchor?.getAttribute("href")).toEqual("/foo");
-        const tooltip = anchor!.querySelector(".mx_TextWithTooltip_target");
-        expect(tooltip).toBeDefined();
-        await act(async () => {
-            unmount();
-        });
-    });
-});
diff --git a/test/unit-tests/vector/__snapshots__/init-test.ts.snap b/test/unit-tests/vector/__snapshots__/init-test.ts.snap
index 4fd8e0345901f05fda611ef5df4b0682caf76b78..83306782d0b044e19b0ef5aea5f1db206008a6e5 100644
--- a/test/unit-tests/vector/__snapshots__/init-test.ts.snap
+++ b/test/unit-tests/vector/__snapshots__/init-test.ts.snap
@@ -17,17 +17,17 @@ exports[`showError should match snapshot 1`] = `
       class="mx_ErrorView_container"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
+        class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
       >
         Error title
       </h1>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         msg1
       </p>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         msg2
       </p>
@@ -53,17 +53,17 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
       class="mx_ErrorView_container"
     >
       <h1
-        class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
+        class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
       >
         Element does not support this browser
       </h1>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         Element uses some browser features which are not available in your current browser. If you continue, some features may stop working and there is a risk that you may lose data in the future.
       </p>
       <p
-        class="_typography_yh5dq_162 _font-body-lg-regular_yh5dq_78"
+        class="_typography_6v6n8_153 _font-body-lg-regular_6v6n8_69"
       >
         <span>
           For the best experience, use 
@@ -103,10 +103,10 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
       </p>
       <div
         class="mx_Flex mx_ErrorView_buttons"
-        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+        style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
       >
         <button
-          class="_button_i91xf_17 _has-icon_i91xf_66"
+          class="_button_vczzf_8 _has-icon_vczzf_57"
           data-kind="secondary"
           data-size="sm"
           role="button"
@@ -121,16 +121,16 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
             xmlns="http://www.w3.org/2000/svg"
           >
             <path
-              d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"
+              d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2"
             />
             <path
-              d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2Z"
+              d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2"
             />
           </svg>
           Learn more
         </button>
         <button
-          class="_button_i91xf_17"
+          class="_button_vczzf_8"
           data-kind="primary"
           data-size="sm"
           role="button"
@@ -141,22 +141,22 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
       </div>
     </div>
     <div
-      class="_separator_144s5_17"
+      class="_separator_7ckbw_8"
       data-kind="primary"
       data-orientation="horizontal"
       role="separator"
     />
     <h2
-      class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+      class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
     >
       Use Element Desktop instead
     </h2>
     <div
       class="mx_Flex"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-4x); --mx-flex-wrap: nowrap;"
     >
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://packages.element.io/desktop/install/macos/Element.dmg"
@@ -171,7 +171,7 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
         Mac
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe"
@@ -186,10 +186,10 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
         Windows (64-bit)
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
-        href="https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe"
+        href="https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe"
         role="link"
         tabindex="0"
       >
@@ -198,10 +198,10 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
           height="20"
           width="20"
         />
-        Windows (32-bit)
+        Windows (ARM 64-bit)
       </a>
       <a
-        class="_button_i91xf_17 _has-icon_i91xf_66"
+        class="_button_vczzf_8 _has-icon_vczzf_57"
         data-kind="secondary"
         data-size="lg"
         href="https://element.io/download#linux"
@@ -217,13 +217,13 @@ exports[`showIncompatibleBrowser should match snapshot 1`] = `
       </a>
     </div>
     <h2
-      class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
+      class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
     >
       Or use our mobile app
     </h2>
     <div
       class="mx_Flex"
-      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-6x);"
+      style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-6x); --mx-flex-wrap: nowrap;"
     >
       <a
         href="https://apps.apple.com/app/vector/id1083446067"
diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts
index 2633d15b1451c0da48d437fea11ccd58e99c2238..c4fda4a20f5ef4db182bd23cdd94f371900133b3 100644
--- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts
+++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts
@@ -31,6 +31,11 @@ describe("ElectronPlatform", () => {
     const mockElectron = {
         on: jest.fn(),
         send: jest.fn(),
+        initialise: jest.fn().mockResolvedValue({
+            protocol: "io.element.desktop",
+            sessionId: "session-id",
+            config: { _config: true },
+        }),
     };
 
     const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
@@ -64,6 +69,17 @@ describe("ElectronPlatform", () => {
         expect(rageshake.flush).toHaveBeenCalled();
     });
 
+    it("should load config", async () => {
+        const platform = new ElectronPlatform();
+        await expect(platform.getConfig()).resolves.toEqual({ _config: true });
+    });
+
+    it("should return oidc client state as expected", async () => {
+        const platform = new ElectronPlatform();
+        await platform.getConfig();
+        expect(platform.getOidcClientState()).toMatchInlineSnapshot(`":element-desktop-ssoid:session-id"`);
+    });
+
     it("dispatches view settings action on preferences event", () => {
         new ElectronPlatform();
         const [event, handler] = getElectronEventHandlerCall("preferences")!;
@@ -287,7 +303,7 @@ describe("ElectronPlatform", () => {
         });
     });
 
-    describe("breacrumbs", () => {
+    describe("breadcrumbs", () => {
         it("should send breadcrumb updates over the IPC", () => {
             const spy = jest.spyOn(BreadcrumbsStore.instance, "on");
             new ElectronPlatform();
diff --git a/test/unit-tests/vector/platform/WebPlatform-test.ts b/test/unit-tests/vector/platform/WebPlatform-test.ts
index b47f2d5537238289d98a1a32d68ee2c6d8fd7a29..7c1048fe0587707249c84282f505e25fb27dbb2b 100644
--- a/test/unit-tests/vector/platform/WebPlatform-test.ts
+++ b/test/unit-tests/vector/platform/WebPlatform-test.ts
@@ -12,6 +12,9 @@ import { UpdateCheckStatus } from "../../../../src/BasePlatform";
 import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 import WebPlatform from "../../../../src/vector/platform/WebPlatform";
 import { setupLanguageMock } from "../../../setup/setupLanguage";
+import ToastStore from "../../../../src/stores/ToastStore.ts";
+import defaultDispatcher from "../../../../src/dispatcher/dispatcher.ts";
+import { emitPromise } from "../../../test-utils";
 
 fetchMock.config.overwriteRoutes = true;
 
@@ -26,11 +29,32 @@ describe("WebPlatform", () => {
         expect(platform.getHumanReadableName()).toEqual("Web Platform");
     });
 
-    it("registers service worker", () => {
-        // @ts-ignore - mocking readonly object
-        navigator.serviceWorker = { register: jest.fn() };
-        new WebPlatform();
-        expect(navigator.serviceWorker.register).toHaveBeenCalled();
+    describe("service worker", () => {
+        it("registers successfully", () => {
+            // @ts-expect-error - mocking readonly object
+            navigator.serviceWorker = {
+                register: jest.fn().mockResolvedValue({
+                    update: jest.fn(),
+                }),
+                addEventListener: jest.fn(),
+            };
+            new WebPlatform();
+            expect(navigator.serviceWorker.register).toHaveBeenCalled();
+        });
+
+        it("handles errors", async () => {
+            // @ts-expect-error - mocking readonly object
+            navigator.serviceWorker = {
+                register: undefined,
+            };
+            new WebPlatform();
+
+            defaultDispatcher.dispatch({ action: "client_started" });
+            await emitPromise(ToastStore.sharedInstance(), "update");
+            const toasts = ToastStore.sharedInstance().getToasts();
+            expect(toasts).toHaveLength(1);
+            expect(toasts[0].title).toEqual("Failed to load service worker");
+        });
     });
 
     it("should call reload on window location object", () => {
@@ -146,24 +170,20 @@ describe("WebPlatform", () => {
         });
 
         describe("pollForUpdate()", () => {
-            it(
-                "should return not available and call showNoUpdate when current version " +
-                    "matches most recent version",
-                async () => {
-                    // @ts-ignore
-                    WebPlatform.VERSION = prodVersion;
-                    fetchMock.getOnce("/version", prodVersion);
-                    const platform = new WebPlatform();
+            it("should return not available and call showNoUpdate when current version matches most recent version", async () => {
+                // @ts-ignore
+                WebPlatform.VERSION = prodVersion;
+                fetchMock.getOnce("/version", prodVersion);
+                const platform = new WebPlatform();
 
-                    const showUpdate = jest.fn();
-                    const showNoUpdate = jest.fn();
-                    const result = await platform.pollForUpdate(showUpdate, showNoUpdate);
+                const showUpdate = jest.fn();
+                const showNoUpdate = jest.fn();
+                const result = await platform.pollForUpdate(showUpdate, showNoUpdate);
 
-                    expect(result).toEqual({ status: UpdateCheckStatus.NotAvailable });
-                    expect(showUpdate).not.toHaveBeenCalled();
-                    expect(showNoUpdate).toHaveBeenCalled();
-                },
-            );
+                expect(result).toEqual({ status: UpdateCheckStatus.NotAvailable });
+                expect(showUpdate).not.toHaveBeenCalled();
+                expect(showNoUpdate).toHaveBeenCalled();
+            });
 
             it("should strip v prefix from versions before comparing", async () => {
                 // @ts-ignore
diff --git a/test/unit-tests/vector/routing-test.ts b/test/unit-tests/vector/routing-test.ts
index 09b2df1d12454ff2af2599a0050eeaed983158c3..a9a078f76099f35cb6ed7ef676067c2b28a5c0ee 100644
--- a/test/unit-tests/vector/routing-test.ts
+++ b/test/unit-tests/vector/routing-test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
 */
 
 import { getInitialScreenAfterLogin, init, onNewScreen } from "../../../src/vector/routing";
-import MatrixChat from "../../../src/components/structures/MatrixChat.tsx";
+import type MatrixChat from "../../../src/components/structures/MatrixChat.tsx";
 
 describe("onNewScreen", () => {
     it("should replace history if stripping via fields", () => {
diff --git a/tsconfig.json b/tsconfig.json
index 854af09239c9cc36745a905ab1f2e2a79d7f6989..10d7c216b654928012bbf2eeb4311b6a53bbdf23 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,7 +14,7 @@
         "outDir": "./lib",
         "declaration": true,
         "jsx": "react",
-        "lib": ["es2022", "dom", "dom.iterable"],
+        "lib": ["es2022", "es2024.promise", "dom", "dom.iterable"],
         "strict": true,
         "paths": {
             "jest-matrix-react": ["./test/test-utils/jest-matrix-react"]
diff --git a/webpack.config.js b/webpack.config.js
index e8413f372c2c49f4b8503a1c1cf512ccdba3ae11..e0d7d4fe933fbf7e3c162f9c407eb0289e022987 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -673,7 +673,12 @@ module.exports = (env, argv) => {
                     { from: "decoder-ring/**", context: path.resolve(__dirname, "res") },
                     { from: "media/**", context: path.resolve(__dirname, "res/") },
                     { from: "config.json", noErrorOnMissing: true },
-                    "contribute.json",
+                    // Element Call embedded widget
+                    {
+                        from: "**",
+                        context: path.resolve(__dirname, "node_modules/@element-hq/element-call-embedded/dist"),
+                        to: path.join(__dirname, "webapp", "widgets", "element-call"),
+                    },
                 ],
             }),
 
diff --git a/yarn.lock b/yarn.lock
index 893d93a72506f85ffd381d8aa670664d3c573290..bfd0b76ef7f5ad2be0b66d39747505aae8bf81a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -27,14 +27,14 @@
     "@jridgewell/gen-mapping" "^0.3.5"
     "@jridgewell/trace-mapping" "^0.3.24"
 
-"@axe-core/playwright@^4.8.1":
+"@axe-core/playwright@^4.10.1":
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.10.1.tgz#c811ba8bfa244833cce422c4131e0043828c42cc"
   integrity sha512-EV5t39VV68kuAfMKqb/RL+YjYKhfuGim9rgIaQ6Vntb2HgaCaau0h98Y3WEUqW1+PbdzxDtDNjFAipbtZuBmEA==
   dependencies:
     axe-core "~4.10.2"
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.2":
   version "7.26.2"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
   integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
@@ -51,31 +51,35 @@
     "@babel/highlight" "^7.25.7"
     picocolors "^1.0.0"
 
-"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.0":
-  version "7.26.2"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e"
-  integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==
+"@babel/code-frame@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
+  integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.27.1"
+    js-tokens "^4.0.0"
+    picocolors "^1.1.1"
 
-"@babel/compat-data@^7.25.9":
-  version "7.26.3"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02"
-  integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==
+"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.27.2":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.3.tgz#cc49c2ac222d69b889bf34c795f537c0c6311111"
+  integrity sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==
 
 "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9", "@babel/core@^7.24.4":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40"
-  integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.3.tgz#d7d05502bccede3cab36373ed142e6a1df554c2f"
+  integrity sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==
   dependencies:
     "@ampproject/remapping" "^2.2.0"
-    "@babel/code-frame" "^7.26.0"
-    "@babel/generator" "^7.26.0"
-    "@babel/helper-compilation-targets" "^7.25.9"
-    "@babel/helper-module-transforms" "^7.26.0"
-    "@babel/helpers" "^7.26.0"
-    "@babel/parser" "^7.26.0"
-    "@babel/template" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
-    "@babel/types" "^7.26.0"
+    "@babel/code-frame" "^7.27.1"
+    "@babel/generator" "^7.27.3"
+    "@babel/helper-compilation-targets" "^7.27.2"
+    "@babel/helper-module-transforms" "^7.27.3"
+    "@babel/helpers" "^7.27.3"
+    "@babel/parser" "^7.27.3"
+    "@babel/template" "^7.27.2"
+    "@babel/traverse" "^7.27.3"
+    "@babel/types" "^7.27.3"
     convert-source-map "^2.0.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.2"
@@ -83,18 +87,18 @@
     semver "^6.3.1"
 
 "@babel/eslint-parser@^7.12.10":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.25.9.tgz#603c68a63078796527bc9d0833f5e52dd5f9224c"
-  integrity sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.27.1.tgz#e146fb2facef62c6c5d1a6fd07cfd79ee9f7b0f1"
+  integrity sha512-q8rjOuadH0V6Zo4XLMkJ3RMQ9MSBqwaDByyYB0izsYdaIWGNLmEblbCOf1vyFHICcg16CD7Fsi51vcQnYxmt6Q==
   dependencies:
     "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
     eslint-visitor-keys "^2.1.0"
     semver "^6.3.1"
 
 "@babel/eslint-plugin@^7.12.10":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.25.9.tgz#a5b6cc46085e0a7d45c5dae36055ce30c5125dab"
-  integrity sha512-MWg1lz+JiP9l1fXkE0qCUVo+1XwgNRPs6GTc88hmw6qN3AdgmfTSkyHt0e1xOTsKdXW5xlh2Lsk3wrFZbW5rzQ==
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.27.1.tgz#5e7cfeaa7a7b506d0ee74c62244e4e6b7a7043f6"
+  integrity sha512-vOG/EipZbIAcREK6XI4JRO3B3uZr70/KIhsrNLO9RXcgLMaW0sTsBpNeTpQUyelB0HsbWd45NIsuTgD3mqr/Og==
   dependencies:
     eslint-rule-composer "^0.3.0"
 
@@ -108,69 +112,85 @@
     "@jridgewell/trace-mapping" "^0.3.25"
     jsesc "^3.0.2"
 
-"@babel/generator@^7.26.0", "@babel/generator@^7.26.3":
-  version "7.26.3"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019"
-  integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==
+"@babel/generator@^7.26.10", "@babel/generator@^7.27.0":
+  version "7.27.0"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.0.tgz#764382b5392e5b9aff93cadb190d0745866cbc2c"
+  integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==
   dependencies:
-    "@babel/parser" "^7.26.3"
-    "@babel/types" "^7.26.3"
+    "@babel/parser" "^7.27.0"
+    "@babel/types" "^7.27.0"
     "@jridgewell/gen-mapping" "^0.3.5"
     "@jridgewell/trace-mapping" "^0.3.25"
     jsesc "^3.0.2"
 
-"@babel/helper-annotate-as-pure@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
-  integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
+"@babel/generator@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.3.tgz#ef1c0f7cfe3b5fc8cbb9f6cc69f93441a68edefc"
+  integrity sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==
   dependencies:
-    "@babel/types" "^7.25.9"
+    "@babel/parser" "^7.27.3"
+    "@babel/types" "^7.27.3"
+    "@jridgewell/gen-mapping" "^0.3.5"
+    "@jridgewell/trace-mapping" "^0.3.25"
+    jsesc "^3.0.2"
 
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz#f41752fe772a578e67286e6779a68a5a92de1ee9"
-  integrity sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==
+"@babel/helper-annotate-as-pure@^7.25.9", "@babel/helper-annotate-as-pure@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz#4345d81a9a46a6486e24d069469f13e60445c05d"
+  integrity sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==
   dependencies:
-    "@babel/traverse" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/types" "^7.27.1"
 
-"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875"
-  integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==
+"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2":
+  version "7.27.2"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d"
+  integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==
   dependencies:
-    "@babel/compat-data" "^7.25.9"
-    "@babel/helper-validator-option" "^7.25.9"
+    "@babel/compat-data" "^7.27.2"
+    "@babel/helper-validator-option" "^7.27.1"
     browserslist "^4.24.0"
     lru-cache "^5.1.1"
     semver "^6.3.1"
 
-"@babel/helper-create-class-features-plugin@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83"
-  integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==
+"@babel/helper-create-class-features-plugin@^7.18.6":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
+  integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.25.9"
     "@babel/helper-member-expression-to-functions" "^7.25.9"
     "@babel/helper-optimise-call-expression" "^7.25.9"
-    "@babel/helper-replace-supers" "^7.25.9"
+    "@babel/helper-replace-supers" "^7.26.5"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/traverse" "^7.26.9"
     semver "^6.3.1"
 
-"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz#3e8999db94728ad2b2458d7a470e7770b7764e26"
-  integrity sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==
+"@babel/helper-create-class-features-plugin@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz#5bee4262a6ea5ddc852d0806199eb17ca3de9281"
+  integrity sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-member-expression-to-functions" "^7.27.1"
+    "@babel/helper-optimise-call-expression" "^7.27.1"
+    "@babel/helper-replace-supers" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
+    semver "^6.3.1"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz#05b0882d97ba1d4d03519e4bce615d70afa18c53"
+  integrity sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    regexpu-core "^6.1.1"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    regexpu-core "^6.2.0"
     semver "^6.3.1"
 
-"@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3":
-  version "0.6.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21"
-  integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==
+"@babel/helper-define-polyfill-provider@^0.6.3", "@babel/helper-define-polyfill-provider@^0.6.4":
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz#15e8746368bfa671785f5926ff74b3064c291fab"
+  integrity sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==
   dependencies:
     "@babel/helper-compilation-targets" "^7.22.6"
     "@babel/helper-plugin-utils" "^7.22.5"
@@ -186,22 +206,39 @@
     "@babel/traverse" "^7.25.9"
     "@babel/types" "^7.25.9"
 
-"@babel/helper-module-imports@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
-  integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
+"@babel/helper-member-expression-to-functions@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44"
+  integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==
   dependencies:
-    "@babel/traverse" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/traverse" "^7.27.1"
+    "@babel/types" "^7.27.1"
 
-"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
-  integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
+"@babel/helper-module-imports@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
+  integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
   dependencies:
-    "@babel/helper-module-imports" "^7.25.9"
-    "@babel/helper-validator-identifier" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/traverse" "^7.27.1"
+    "@babel/types" "^7.27.1"
+
+"@babel/helper-module-transforms@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f"
+  integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==
+  dependencies:
+    "@babel/helper-module-imports" "^7.27.1"
+    "@babel/helper-validator-identifier" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
+
+"@babel/helper-module-transforms@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02"
+  integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==
+  dependencies:
+    "@babel/helper-module-imports" "^7.27.1"
+    "@babel/helper-validator-identifier" "^7.27.1"
+    "@babel/traverse" "^7.27.3"
 
 "@babel/helper-optimise-call-expression@^7.25.9":
   version "7.25.9"
@@ -210,41 +247,49 @@
   dependencies:
     "@babel/types" "^7.25.9"
 
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46"
-  integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==
+"@babel/helper-optimise-call-expression@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200"
+  integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==
+  dependencies:
+    "@babel/types" "^7.27.1"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c"
+  integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
 
 "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.7", "@babel/helper-plugin-utils@^7.8.0":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c"
   integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==
 
-"@babel/helper-remap-async-to-generator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92"
-  integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==
+"@babel/helper-remap-async-to-generator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6"
+  integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-wrap-function" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-wrap-function" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
-"@babel/helper-replace-supers@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz#ba447224798c3da3f8713fc272b145e33da6a5c5"
-  integrity sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==
+"@babel/helper-replace-supers@^7.26.5":
+  version "7.26.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
+  integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
   dependencies:
     "@babel/helper-member-expression-to-functions" "^7.25.9"
     "@babel/helper-optimise-call-expression" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/traverse" "^7.26.5"
 
-"@babel/helper-simple-access@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz#6d51783299884a2c74618d6ef0f86820ec2e7739"
-  integrity sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==
+"@babel/helper-replace-supers@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0"
+  integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==
   dependencies:
-    "@babel/traverse" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/helper-member-expression-to-functions" "^7.27.1"
+    "@babel/helper-optimise-call-expression" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
 "@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
   version "7.25.9"
@@ -254,37 +299,55 @@
     "@babel/traverse" "^7.25.9"
     "@babel/types" "^7.25.9"
 
-"@babel/helper-string-parser@^7.25.7", "@babel/helper-string-parser@^7.25.9":
+"@babel/helper-skip-transparent-expression-wrappers@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56"
+  integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==
+  dependencies:
+    "@babel/traverse" "^7.27.1"
+    "@babel/types" "^7.27.1"
+
+"@babel/helper-string-parser@^7.25.7":
   version "7.25.9"
   resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
   integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
 
-"@babel/helper-validator-identifier@^7.24.7", "@babel/helper-validator-identifier@^7.25.7", "@babel/helper-validator-identifier@^7.25.9":
+"@babel/helper-string-parser@^7.25.9", "@babel/helper-string-parser@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
+  integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+
+"@babel/helper-validator-identifier@^7.24.7", "@babel/helper-validator-identifier@^7.25.7":
   version "7.25.9"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
   integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
 
-"@babel/helper-validator-option@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
-  integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
+  integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
 
-"@babel/helper-wrap-function@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0"
-  integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==
+"@babel/helper-validator-option@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
+  integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
+
+"@babel/helper-wrap-function@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz#b88285009c31427af318d4fe37651cd62a142409"
+  integrity sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==
   dependencies:
-    "@babel/template" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/template" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
+    "@babel/types" "^7.27.1"
 
-"@babel/helpers@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4"
-  integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==
+"@babel/helpers@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.3.tgz#387d65d279290e22fe7a47a8ffcd2d0c0184edd0"
+  integrity sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==
   dependencies:
-    "@babel/template" "^7.25.9"
-    "@babel/types" "^7.26.0"
+    "@babel/template" "^7.27.2"
+    "@babel/types" "^7.27.3"
 
 "@babel/highlight@^7.25.7":
   version "7.25.9"
@@ -303,58 +366,89 @@
   dependencies:
     "@babel/types" "^7.25.8"
 
-"@babel/parser@^7.24.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3":
-  version "7.26.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234"
-  integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==
+"@babel/parser@^7.24.4":
+  version "7.26.10"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
+  integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
   dependencies:
-    "@babel/types" "^7.26.3"
+    "@babel/types" "^7.26.10"
 
-"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe"
-  integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==
+"@babel/parser@^7.26.10", "@babel/parser@^7.27.0":
+  version "7.27.0"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec"
+  integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/types" "^7.27.0"
 
-"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30"
-  integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==
+"@babel/parser@^7.27.2", "@babel/parser@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.3.tgz#1b7533f0d908ad2ac545c4d05cbe2fb6dc8cfaaf"
+  integrity sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/types" "^7.27.3"
 
-"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137"
-  integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9"
+  integrity sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1"
-  integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==
+"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz#43f70a6d7efd52370eefbdf55ae03d91b293856d"
+  integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-    "@babel/plugin-transform-optional-chaining" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e"
-  integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz#beb623bd573b8b6f3047bd04c32506adc3e58a72"
+  integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd"
+  integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
+    "@babel/plugin-transform-optional-chaining" "^7.27.1"
+
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz#bb1c25af34d75115ce229a1de7fa44bf8f955670"
+  integrity sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
+
+"@babel/plugin-proposal-decorators@^7.25.9":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz#3686f424b2f8b2fee7579aa4df133a4f5244a596"
+  integrity sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/plugin-syntax-decorators" "^7.27.1"
 
 "@babel/plugin-proposal-export-default-from@^7.12.1":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz#52702be6ef8367fc8f18b8438278332beeb8f87c"
-  integrity sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ==
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz#59b050b0e5fdc366162ab01af4fcbac06ea40919"
+  integrity sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+
+"@babel/plugin-proposal-private-methods@^7.18.6":
+  version "7.18.6"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
+  integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.18.6"
+    "@babel/helper-plugin-utils" "^7.18.6"
 
 "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
   version "7.21.0-placeholder-for-preset-env.2"
@@ -389,6 +483,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.14.5"
 
+"@babel/plugin-syntax-decorators@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz#ee7dd9590aeebc05f9d4c8c0560007b05979a63d"
+  integrity sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.27.1"
+
 "@babel/plugin-syntax-dynamic-import@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
@@ -396,12 +497,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-import-assertions@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f"
-  integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==
+"@babel/plugin-syntax-import-assertions@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd"
+  integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-syntax-import-attributes@^7.24.7":
   version "7.25.7"
@@ -410,12 +511,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.25.7"
 
-"@babel/plugin-syntax-import-attributes@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7"
-  integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==
+"@babel/plugin-syntax-import-attributes@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07"
+  integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-syntax-import-meta@^7.10.4":
   version "7.10.4"
@@ -431,12 +532,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-jsx@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290"
-  integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
+"@babel/plugin-syntax-jsx@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c"
+  integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-syntax-jsx@^7.7.2":
   version "7.25.7"
@@ -501,12 +602,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.14.5"
 
-"@babel/plugin-syntax-typescript@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399"
-  integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==
+"@babel/plugin-syntax-typescript@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18"
+  integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-syntax-typescript@^7.7.2":
   version "7.25.7"
@@ -523,304 +624,310 @@
     "@babel/helper-create-regexp-features-plugin" "^7.18.6"
     "@babel/helper-plugin-utils" "^7.18.6"
 
-"@babel/plugin-transform-arrow-functions@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845"
-  integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==
+"@babel/plugin-transform-arrow-functions@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a"
+  integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-async-generator-functions@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2"
-  integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==
+"@babel/plugin-transform-async-generator-functions@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz#ca433df983d68e1375398e7ca71bf2a4f6fd89d7"
+  integrity sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-remap-async-to-generator" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-remap-async-to-generator" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
-"@babel/plugin-transform-async-to-generator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71"
-  integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==
+"@babel/plugin-transform-async-to-generator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7"
+  integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==
   dependencies:
-    "@babel/helper-module-imports" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-remap-async-to-generator" "^7.25.9"
+    "@babel/helper-module-imports" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-remap-async-to-generator" "^7.27.1"
 
-"@babel/plugin-transform-block-scoped-functions@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz#5700691dbd7abb93de300ca7be94203764fce458"
-  integrity sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==
+"@babel/plugin-transform-block-scoped-functions@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9"
+  integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-block-scoping@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1"
-  integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==
+"@babel/plugin-transform-block-scoping@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz#bc0dbe8ac6de5602981ba58ef68c6df8ef9bfbb3"
+  integrity sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-class-properties@^7.12.1", "@babel/plugin-transform-class-properties@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f"
-  integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==
+"@babel/plugin-transform-class-properties@^7.12.1", "@babel/plugin-transform-class-properties@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925"
+  integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-class-static-block@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0"
-  integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==
+"@babel/plugin-transform-class-static-block@^7.26.0", "@babel/plugin-transform-class-static-block@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz#7e920d5625b25bbccd3061aefbcc05805ed56ce4"
+  integrity sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-classes@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52"
-  integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==
+"@babel/plugin-transform-classes@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz#03bb04bea2c7b2f711f0db7304a8da46a85cced4"
+  integrity sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-compilation-targets" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-replace-supers" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-compilation-targets" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-replace-supers" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
     globals "^11.1.0"
 
-"@babel/plugin-transform-computed-properties@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b"
-  integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==
+"@babel/plugin-transform-computed-properties@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa"
+  integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/template" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/template" "^7.27.1"
 
-"@babel/plugin-transform-destructuring@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1"
-  integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==
+"@babel/plugin-transform-destructuring@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz#d5916ef7089cb254df0418ae524533c1b72ba656"
+  integrity sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-dotall-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a"
-  integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==
+"@babel/plugin-transform-destructuring@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz#3cc8299ed798d9a909f8d66ddeb40849ec32e3b0"
+  integrity sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-duplicate-keys@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d"
-  integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==
+"@babel/plugin-transform-dotall-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d"
+  integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31"
-  integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==
+"@babel/plugin-transform-duplicate-keys@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz#f1fbf628ece18e12e7b32b175940e68358f546d1"
+  integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-dynamic-import@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8"
-  integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==
+"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec"
+  integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-exponentiation-operator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz#ece47b70d236c1d99c263a1e22b62dc20a4c8b0f"
-  integrity sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==
+"@babel/plugin-transform-dynamic-import@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz#4c78f35552ac0e06aa1f6e3c573d67695e8af5a4"
+  integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-export-namespace-from@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2"
-  integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==
+"@babel/plugin-transform-exponentiation-operator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz#fc497b12d8277e559747f5a3ed868dd8064f83e1"
+  integrity sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-for-of@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755"
-  integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==
+"@babel/plugin-transform-export-namespace-from@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23"
+  integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-function-name@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97"
-  integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==
+"@babel/plugin-transform-for-of@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a"
+  integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==
   dependencies:
-    "@babel/helper-compilation-targets" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
 
-"@babel/plugin-transform-json-strings@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660"
-  integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==
+"@babel/plugin-transform-function-name@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7"
+  integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-compilation-targets" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
-"@babel/plugin-transform-literals@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de"
-  integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==
+"@babel/plugin-transform-json-strings@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c"
+  integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-logical-assignment-operators@^7.20.7", "@babel/plugin-transform-logical-assignment-operators@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7"
-  integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==
+"@babel/plugin-transform-literals@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24"
+  integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-member-expression-literals@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de"
-  integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==
+"@babel/plugin-transform-logical-assignment-operators@^7.20.7", "@babel/plugin-transform-logical-assignment-operators@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz#890cb20e0270e0e5bebe3f025b434841c32d5baa"
+  integrity sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-modules-amd@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5"
-  integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==
+"@babel/plugin-transform-member-expression-literals@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9"
+  integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==
   dependencies:
-    "@babel/helper-module-transforms" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-modules-commonjs@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz#d165c8c569a080baf5467bda88df6425fc060686"
-  integrity sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==
+"@babel/plugin-transform-modules-amd@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz#a4145f9d87c2291fe2d05f994b65dba4e3e7196f"
+  integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-simple-access" "^7.25.9"
+    "@babel/helper-module-transforms" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-modules-systemjs@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8"
-  integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==
+"@babel/plugin-transform-modules-commonjs@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832"
+  integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-validator-identifier" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/helper-module-transforms" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-modules-umd@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9"
-  integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==
+"@babel/plugin-transform-modules-systemjs@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz#00e05b61863070d0f3292a00126c16c0e024c4ed"
+  integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-module-transforms" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-validator-identifier" "^7.27.1"
+    "@babel/traverse" "^7.27.1"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a"
-  integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==
+"@babel/plugin-transform-modules-umd@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz#63f2cf4f6dc15debc12f694e44714863d34cd334"
+  integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-module-transforms" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-new-target@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd"
-  integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1"
+  integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-nullish-coalescing-operator@^7.12.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz#bcb1b0d9e948168102d5f7104375ca21c3266949"
-  integrity sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==
+"@babel/plugin-transform-new-target@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb"
+  integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-numeric-separator@^7.12.7", "@babel/plugin-transform-numeric-separator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1"
-  integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==
+"@babel/plugin-transform-nullish-coalescing-operator@^7.12.1", "@babel/plugin-transform-nullish-coalescing-operator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d"
+  integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-object-rest-spread@^7.12.1", "@babel/plugin-transform-object-rest-spread@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18"
-  integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==
+"@babel/plugin-transform-numeric-separator@^7.12.7", "@babel/plugin-transform-numeric-separator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6"
+  integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==
   dependencies:
-    "@babel/helper-compilation-targets" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/plugin-transform-parameters" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-object-super@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03"
-  integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==
+"@babel/plugin-transform-object-rest-spread@^7.12.1", "@babel/plugin-transform-object-rest-spread@^7.27.2":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz#ce130aa73fef828bc3e3e835f9bc6144be3eb1c0"
+  integrity sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-replace-supers" "^7.25.9"
+    "@babel/helper-compilation-targets" "^7.27.2"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/plugin-transform-destructuring" "^7.27.3"
+    "@babel/plugin-transform-parameters" "^7.27.1"
 
-"@babel/plugin-transform-optional-catch-binding@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3"
-  integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==
+"@babel/plugin-transform-object-super@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5"
+  integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-replace-supers" "^7.27.1"
 
-"@babel/plugin-transform-optional-chaining@^7.12.7", "@babel/plugin-transform-optional-chaining@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd"
-  integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==
+"@babel/plugin-transform-optional-catch-binding@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c"
+  integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-parameters@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257"
-  integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==
+"@babel/plugin-transform-optional-chaining@^7.12.7", "@babel/plugin-transform-optional-chaining@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz#874ce3c4f06b7780592e946026eb76a32830454f"
+  integrity sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
 
-"@babel/plugin-transform-private-methods@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
-  integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==
+"@babel/plugin-transform-parameters@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz#80334b54b9b1ac5244155a0c8304a187a618d5a7"
+  integrity sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-private-property-in-object@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33"
-  integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==
+"@babel/plugin-transform-private-methods@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af"
+  integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-create-class-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-property-literals@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f"
-  integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==
+"@babel/plugin-transform-private-property-in-object@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11"
+  integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+
+"@babel/plugin-transform-property-literals@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424"
+  integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-transform-react-constant-elements@^7.21.3":
   version "7.25.7"
@@ -829,225 +936,224 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.25.7"
 
-"@babel/plugin-transform-react-display-name@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d"
-  integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==
+"@babel/plugin-transform-react-display-name@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz#43af31362d71f7848cfac0cbc212882b1a16e80f"
+  integrity sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-react-jsx-development@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz#8fd220a77dd139c07e25225a903b8be8c829e0d7"
-  integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==
+"@babel/plugin-transform-react-jsx-development@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz#47ff95940e20a3a70e68ad3d4fcb657b647f6c98"
+  integrity sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==
   dependencies:
-    "@babel/plugin-transform-react-jsx" "^7.25.9"
+    "@babel/plugin-transform-react-jsx" "^7.27.1"
 
-"@babel/plugin-transform-react-jsx@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166"
-  integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==
+"@babel/plugin-transform-react-jsx@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0"
+  integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-module-imports" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/plugin-syntax-jsx" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-module-imports" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/plugin-syntax-jsx" "^7.27.1"
+    "@babel/types" "^7.27.1"
 
-"@babel/plugin-transform-react-pure-annotations@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz#ea1c11b2f9dbb8e2d97025f43a3b5bc47e18ae62"
-  integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==
+"@babel/plugin-transform-react-pure-annotations@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz#339f1ce355eae242e0649f232b1c68907c02e879"
+  integrity sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-regenerator@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b"
-  integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==
+"@babel/plugin-transform-regenerator@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz#0a471df9213416e44cd66bf67176b66f65768401"
+  integrity sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    regenerator-transform "^0.15.2"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-regexp-modifiers@^7.26.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850"
-  integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==
+"@babel/plugin-transform-regexp-modifiers@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09"
+  integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-reserved-words@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce"
-  integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==
+"@babel/plugin-transform-reserved-words@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz#40fba4878ccbd1c56605a4479a3a891ac0274bb4"
+  integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/plugin-transform-runtime@^7.12.10":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz#62723ea3f5b31ffbe676da9d6dae17138ae580ea"
-  integrity sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.3.tgz#ad35f1eff5ba18a5e23f7270e939fb5a59d3ec0b"
+  integrity sha512-bA9ZL5PW90YwNgGfjg6U+7Qh/k3zCEQJ06BFgAGRp/yMjw9hP9UGbGPtx3KSOkHGljEPCCxaE+PH4fUR2h1sDw==
   dependencies:
-    "@babel/helper-module-imports" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-module-imports" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
     babel-plugin-polyfill-corejs2 "^0.4.10"
-    babel-plugin-polyfill-corejs3 "^0.10.6"
+    babel-plugin-polyfill-corejs3 "^0.11.0"
     babel-plugin-polyfill-regenerator "^0.6.1"
     semver "^6.3.1"
 
-"@babel/plugin-transform-shorthand-properties@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2"
-  integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==
+"@babel/plugin-transform-shorthand-properties@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90"
+  integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-spread@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9"
-  integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==
+"@babel/plugin-transform-spread@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08"
+  integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
 
-"@babel/plugin-transform-sticky-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32"
-  integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==
+"@babel/plugin-transform-sticky-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280"
+  integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-template-literals@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1"
-  integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==
+"@babel/plugin-transform-template-literals@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8"
+  integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-typeof-symbol@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz#224ba48a92869ddbf81f9b4a5f1204bbf5a2bc4b"
-  integrity sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==
+"@babel/plugin-transform-typeof-symbol@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz#70e966bb492e03509cf37eafa6dcc3051f844369"
+  integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-typescript@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz#69267905c2b33c2ac6d8fe765e9dc2ddc9df3849"
-  integrity sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==
+"@babel/plugin-transform-typescript@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz#d3bb65598bece03f773111e88cc4e8e5070f1140"
+  integrity sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.25.9"
-    "@babel/helper-create-class-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-    "@babel/plugin-syntax-typescript" "^7.25.9"
+    "@babel/helper-annotate-as-pure" "^7.27.1"
+    "@babel/helper-create-class-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
+    "@babel/plugin-syntax-typescript" "^7.27.1"
 
-"@babel/plugin-transform-unicode-escapes@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82"
-  integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==
+"@babel/plugin-transform-unicode-escapes@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806"
+  integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-unicode-property-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3"
-  integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==
+"@babel/plugin-transform-unicode-property-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956"
+  integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-unicode-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1"
-  integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==
+"@babel/plugin-transform-unicode-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97"
+  integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
-"@babel/plugin-transform-unicode-sets-regex@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe"
-  integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==
+"@babel/plugin-transform-unicode-sets-regex@^7.27.1":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1"
+  integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
+    "@babel/helper-create-regexp-features-plugin" "^7.27.1"
+    "@babel/helper-plugin-utils" "^7.27.1"
 
 "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.20.2":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.0.tgz#30e5c6bc1bcc54865bff0c5a30f6d4ccdc7fa8b1"
-  integrity sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==
-  dependencies:
-    "@babel/compat-data" "^7.26.0"
-    "@babel/helper-compilation-targets" "^7.25.9"
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-validator-option" "^7.25.9"
-    "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9"
-    "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9"
-    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9"
-    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9"
-    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9"
+  version "7.27.2"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.27.2.tgz#106e6bfad92b591b1f6f76fd4cf13b7725a7bf9a"
+  integrity sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==
+  dependencies:
+    "@babel/compat-data" "^7.27.2"
+    "@babel/helper-compilation-targets" "^7.27.2"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-validator-option" "^7.27.1"
+    "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.27.1"
+    "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1"
+    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1"
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1"
+    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.27.1"
     "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2"
-    "@babel/plugin-syntax-import-assertions" "^7.26.0"
-    "@babel/plugin-syntax-import-attributes" "^7.26.0"
+    "@babel/plugin-syntax-import-assertions" "^7.27.1"
+    "@babel/plugin-syntax-import-attributes" "^7.27.1"
     "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
-    "@babel/plugin-transform-arrow-functions" "^7.25.9"
-    "@babel/plugin-transform-async-generator-functions" "^7.25.9"
-    "@babel/plugin-transform-async-to-generator" "^7.25.9"
-    "@babel/plugin-transform-block-scoped-functions" "^7.25.9"
-    "@babel/plugin-transform-block-scoping" "^7.25.9"
-    "@babel/plugin-transform-class-properties" "^7.25.9"
-    "@babel/plugin-transform-class-static-block" "^7.26.0"
-    "@babel/plugin-transform-classes" "^7.25.9"
-    "@babel/plugin-transform-computed-properties" "^7.25.9"
-    "@babel/plugin-transform-destructuring" "^7.25.9"
-    "@babel/plugin-transform-dotall-regex" "^7.25.9"
-    "@babel/plugin-transform-duplicate-keys" "^7.25.9"
-    "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9"
-    "@babel/plugin-transform-dynamic-import" "^7.25.9"
-    "@babel/plugin-transform-exponentiation-operator" "^7.25.9"
-    "@babel/plugin-transform-export-namespace-from" "^7.25.9"
-    "@babel/plugin-transform-for-of" "^7.25.9"
-    "@babel/plugin-transform-function-name" "^7.25.9"
-    "@babel/plugin-transform-json-strings" "^7.25.9"
-    "@babel/plugin-transform-literals" "^7.25.9"
-    "@babel/plugin-transform-logical-assignment-operators" "^7.25.9"
-    "@babel/plugin-transform-member-expression-literals" "^7.25.9"
-    "@babel/plugin-transform-modules-amd" "^7.25.9"
-    "@babel/plugin-transform-modules-commonjs" "^7.25.9"
-    "@babel/plugin-transform-modules-systemjs" "^7.25.9"
-    "@babel/plugin-transform-modules-umd" "^7.25.9"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9"
-    "@babel/plugin-transform-new-target" "^7.25.9"
-    "@babel/plugin-transform-nullish-coalescing-operator" "^7.25.9"
-    "@babel/plugin-transform-numeric-separator" "^7.25.9"
-    "@babel/plugin-transform-object-rest-spread" "^7.25.9"
-    "@babel/plugin-transform-object-super" "^7.25.9"
-    "@babel/plugin-transform-optional-catch-binding" "^7.25.9"
-    "@babel/plugin-transform-optional-chaining" "^7.25.9"
-    "@babel/plugin-transform-parameters" "^7.25.9"
-    "@babel/plugin-transform-private-methods" "^7.25.9"
-    "@babel/plugin-transform-private-property-in-object" "^7.25.9"
-    "@babel/plugin-transform-property-literals" "^7.25.9"
-    "@babel/plugin-transform-regenerator" "^7.25.9"
-    "@babel/plugin-transform-regexp-modifiers" "^7.26.0"
-    "@babel/plugin-transform-reserved-words" "^7.25.9"
-    "@babel/plugin-transform-shorthand-properties" "^7.25.9"
-    "@babel/plugin-transform-spread" "^7.25.9"
-    "@babel/plugin-transform-sticky-regex" "^7.25.9"
-    "@babel/plugin-transform-template-literals" "^7.25.9"
-    "@babel/plugin-transform-typeof-symbol" "^7.25.9"
-    "@babel/plugin-transform-unicode-escapes" "^7.25.9"
-    "@babel/plugin-transform-unicode-property-regex" "^7.25.9"
-    "@babel/plugin-transform-unicode-regex" "^7.25.9"
-    "@babel/plugin-transform-unicode-sets-regex" "^7.25.9"
+    "@babel/plugin-transform-arrow-functions" "^7.27.1"
+    "@babel/plugin-transform-async-generator-functions" "^7.27.1"
+    "@babel/plugin-transform-async-to-generator" "^7.27.1"
+    "@babel/plugin-transform-block-scoped-functions" "^7.27.1"
+    "@babel/plugin-transform-block-scoping" "^7.27.1"
+    "@babel/plugin-transform-class-properties" "^7.27.1"
+    "@babel/plugin-transform-class-static-block" "^7.27.1"
+    "@babel/plugin-transform-classes" "^7.27.1"
+    "@babel/plugin-transform-computed-properties" "^7.27.1"
+    "@babel/plugin-transform-destructuring" "^7.27.1"
+    "@babel/plugin-transform-dotall-regex" "^7.27.1"
+    "@babel/plugin-transform-duplicate-keys" "^7.27.1"
+    "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1"
+    "@babel/plugin-transform-dynamic-import" "^7.27.1"
+    "@babel/plugin-transform-exponentiation-operator" "^7.27.1"
+    "@babel/plugin-transform-export-namespace-from" "^7.27.1"
+    "@babel/plugin-transform-for-of" "^7.27.1"
+    "@babel/plugin-transform-function-name" "^7.27.1"
+    "@babel/plugin-transform-json-strings" "^7.27.1"
+    "@babel/plugin-transform-literals" "^7.27.1"
+    "@babel/plugin-transform-logical-assignment-operators" "^7.27.1"
+    "@babel/plugin-transform-member-expression-literals" "^7.27.1"
+    "@babel/plugin-transform-modules-amd" "^7.27.1"
+    "@babel/plugin-transform-modules-commonjs" "^7.27.1"
+    "@babel/plugin-transform-modules-systemjs" "^7.27.1"
+    "@babel/plugin-transform-modules-umd" "^7.27.1"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1"
+    "@babel/plugin-transform-new-target" "^7.27.1"
+    "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1"
+    "@babel/plugin-transform-numeric-separator" "^7.27.1"
+    "@babel/plugin-transform-object-rest-spread" "^7.27.2"
+    "@babel/plugin-transform-object-super" "^7.27.1"
+    "@babel/plugin-transform-optional-catch-binding" "^7.27.1"
+    "@babel/plugin-transform-optional-chaining" "^7.27.1"
+    "@babel/plugin-transform-parameters" "^7.27.1"
+    "@babel/plugin-transform-private-methods" "^7.27.1"
+    "@babel/plugin-transform-private-property-in-object" "^7.27.1"
+    "@babel/plugin-transform-property-literals" "^7.27.1"
+    "@babel/plugin-transform-regenerator" "^7.27.1"
+    "@babel/plugin-transform-regexp-modifiers" "^7.27.1"
+    "@babel/plugin-transform-reserved-words" "^7.27.1"
+    "@babel/plugin-transform-shorthand-properties" "^7.27.1"
+    "@babel/plugin-transform-spread" "^7.27.1"
+    "@babel/plugin-transform-sticky-regex" "^7.27.1"
+    "@babel/plugin-transform-template-literals" "^7.27.1"
+    "@babel/plugin-transform-typeof-symbol" "^7.27.1"
+    "@babel/plugin-transform-unicode-escapes" "^7.27.1"
+    "@babel/plugin-transform-unicode-property-regex" "^7.27.1"
+    "@babel/plugin-transform-unicode-regex" "^7.27.1"
+    "@babel/plugin-transform-unicode-sets-regex" "^7.27.1"
     "@babel/preset-modules" "0.1.6-no-external-plugins"
     babel-plugin-polyfill-corejs2 "^0.4.10"
-    babel-plugin-polyfill-corejs3 "^0.10.6"
+    babel-plugin-polyfill-corejs3 "^0.11.0"
     babel-plugin-polyfill-regenerator "^0.6.1"
-    core-js-compat "^3.38.1"
+    core-js-compat "^3.40.0"
     semver "^6.3.1"
 
 "@babel/preset-modules@0.1.6-no-external-plugins":
@@ -1060,59 +1166,50 @@
     esutils "^2.0.2"
 
 "@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6":
-  version "7.26.3"
-  resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa"
-  integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.27.1.tgz#86ea0a5ca3984663f744be2fd26cb6747c3fd0ec"
+  integrity sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-validator-option" "^7.25.9"
-    "@babel/plugin-transform-react-display-name" "^7.25.9"
-    "@babel/plugin-transform-react-jsx" "^7.25.9"
-    "@babel/plugin-transform-react-jsx-development" "^7.25.9"
-    "@babel/plugin-transform-react-pure-annotations" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-validator-option" "^7.27.1"
+    "@babel/plugin-transform-react-display-name" "^7.27.1"
+    "@babel/plugin-transform-react-jsx" "^7.27.1"
+    "@babel/plugin-transform-react-jsx-development" "^7.27.1"
+    "@babel/plugin-transform-react-pure-annotations" "^7.27.1"
 
 "@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.21.0":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d"
-  integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.25.9"
-    "@babel/helper-validator-option" "^7.25.9"
-    "@babel/plugin-syntax-jsx" "^7.25.9"
-    "@babel/plugin-transform-modules-commonjs" "^7.25.9"
-    "@babel/plugin-transform-typescript" "^7.25.9"
-
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
-  version "7.26.0"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
-  integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz#190742a6428d282306648a55b0529b561484f912"
+  integrity sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.27.1"
+    "@babel/helper-validator-option" "^7.27.1"
+    "@babel/plugin-syntax-jsx" "^7.27.1"
+    "@babel/plugin-transform-modules-commonjs" "^7.27.1"
+    "@babel/plugin-transform-typescript" "^7.27.1"
+
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.3.tgz#10491113799fb8d77e1d9273384d5d68deeea8f6"
+  integrity sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==
+
+"@babel/template@^7.25.7", "@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3":
+  version "7.27.2"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
+  integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
+  dependencies:
+    "@babel/code-frame" "^7.27.1"
+    "@babel/parser" "^7.27.2"
+    "@babel/types" "^7.27.1"
+
+"@babel/template@^7.26.9", "@babel/template@^7.27.0":
+  version "7.27.0"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4"
+  integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==
   dependencies:
-    regenerator-runtime "^0.14.0"
-
-"@babel/runtime@^7.7.2":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.9.tgz#65884fd6dc255a775402cc1d9811082918f4bf00"
-  integrity sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==
-  dependencies:
-    regenerator-runtime "^0.14.0"
-
-"@babel/template@^7.25.7", "@babel/template@^7.3.3":
-  version "7.25.7"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769"
-  integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==
-  dependencies:
-    "@babel/code-frame" "^7.25.7"
-    "@babel/parser" "^7.25.7"
-    "@babel/types" "^7.25.7"
-
-"@babel/template@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
-  integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==
-  dependencies:
-    "@babel/code-frame" "^7.25.9"
-    "@babel/parser" "^7.25.9"
-    "@babel/types" "^7.25.9"
+    "@babel/code-frame" "^7.26.2"
+    "@babel/parser" "^7.27.0"
+    "@babel/types" "^7.27.0"
 
 "@babel/traverse@^7.18.5":
   version "7.25.7"
@@ -1127,16 +1224,42 @@
     debug "^4.3.1"
     globals "^11.1.0"
 
-"@babel/traverse@^7.25.9":
-  version "7.26.4"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd"
-  integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==
+"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5":
+  version "7.27.0"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70"
+  integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==
   dependencies:
     "@babel/code-frame" "^7.26.2"
-    "@babel/generator" "^7.26.3"
-    "@babel/parser" "^7.26.3"
-    "@babel/template" "^7.25.9"
-    "@babel/types" "^7.26.3"
+    "@babel/generator" "^7.27.0"
+    "@babel/parser" "^7.27.0"
+    "@babel/template" "^7.27.0"
+    "@babel/types" "^7.27.0"
+    debug "^4.3.1"
+    globals "^11.1.0"
+
+"@babel/traverse@^7.26.9":
+  version "7.26.10"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"
+  integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==
+  dependencies:
+    "@babel/code-frame" "^7.26.2"
+    "@babel/generator" "^7.26.10"
+    "@babel/parser" "^7.26.10"
+    "@babel/template" "^7.26.9"
+    "@babel/types" "^7.26.10"
+    debug "^4.3.1"
+    globals "^11.1.0"
+
+"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.3.tgz#8b62a6c2d10f9d921ba7339c90074708509cffae"
+  integrity sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==
+  dependencies:
+    "@babel/code-frame" "^7.27.1"
+    "@babel/generator" "^7.27.3"
+    "@babel/parser" "^7.27.3"
+    "@babel/template" "^7.27.2"
+    "@babel/types" "^7.27.3"
     debug "^4.3.1"
     globals "^11.1.0"
 
@@ -1149,7 +1272,7 @@
     "@babel/helper-validator-identifier" "^7.25.7"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.25.7", "@babel/types@^7.4.4":
+"@babel/types@^7.25.7":
   version "7.26.0"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff"
   integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==
@@ -1157,14 +1280,30 @@
     "@babel/helper-string-parser" "^7.25.9"
     "@babel/helper-validator-identifier" "^7.25.9"
 
-"@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3":
-  version "7.26.3"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
-  integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
+"@babel/types@^7.25.9", "@babel/types@^7.4.4":
+  version "7.27.1"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560"
+  integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==
+  dependencies:
+    "@babel/helper-string-parser" "^7.27.1"
+    "@babel/helper-validator-identifier" "^7.27.1"
+
+"@babel/types@^7.26.10", "@babel/types@^7.27.0":
+  version "7.27.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559"
+  integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==
   dependencies:
     "@babel/helper-string-parser" "^7.25.9"
     "@babel/helper-validator-identifier" "^7.25.9"
 
+"@babel/types@^7.27.1", "@babel/types@^7.27.3":
+  version "7.27.3"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.3.tgz#c0257bedf33aad6aad1f406d35c44758321eb3ec"
+  integrity sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==
+  dependencies:
+    "@babel/helper-string-parser" "^7.27.1"
+    "@babel/helper-validator-identifier" "^7.27.1"
+
 "@balena/dockerignore@^1.0.2":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
@@ -1214,31 +1353,16 @@
     "@csstools/color-helpers" "^5.0.1"
     "@csstools/css-calc" "^2.1.1"
 
-"@csstools/css-parser-algorithms@^3.0.1":
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.2.tgz#be03c710a60b34f95ea62e332c9ca0c2674f6d5f"
-  integrity sha512-6tC/MnlEvs5suR4Ahef4YlBccJDHZuxGsAlxXmybWjZ5jPxlzLSMlRZ9mVHSRvlD+CmtE7+hJ+UQbfXrws/rUQ==
-
 "@csstools/css-parser-algorithms@^3.0.4":
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356"
   integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==
 
-"@csstools/css-tokenizer@^3.0.1":
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.2.tgz#1c1d7298f6a7b3db94afe53d949b9a7d6a8ebc57"
-  integrity sha512-IuTRcD53WHsXPCZ6W7ubfGqReTJ9Ra0yRRFmXYP/Re8hFYYfoIYIK4080X5luslVLWimhIeFq0hj09urVMQzTw==
-
 "@csstools/css-tokenizer@^3.0.3":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2"
   integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==
 
-"@csstools/media-query-list-parser@^3.0.1":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz#9474e08e6d7767cf68c56bf1581b59d203360cb0"
-  integrity sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==
-
 "@csstools/media-query-list-parser@^4.0.2":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a"
@@ -1518,11 +1642,6 @@
   resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz#704a9b637975680e025e069a4c58b3beb3e2752a"
   integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==
 
-"@csstools/selector-specificity@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz#7dfccb9df5499e627e7bfdbb4021a06813a45dba"
-  integrity sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==
-
 "@csstools/selector-specificity@^5.0.0":
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b"
@@ -1548,6 +1667,51 @@
   resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
   integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
 
+"@element-hq/element-call-embedded@0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.11.1.tgz#24a78edad1a3ade879b99d48254c5155dde9da3f"
+  integrity sha512-VlDCX9ByTLaFsQ2jk5wKQ8NPv+K8uLJjWU7Lkz7zDwNMFkGlvBjymiM+Fq3YPZ2Ek+so89CxZnVrsvZAVVPnUA==
+
+"@element-hq/element-web-module-api@1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.0.0.tgz#df09108b0346a44ad2898c603d1a6cda5f50d80b"
+  integrity sha512-FYId5tYgaKvpqAXRXqs0pY4+7/A09bEl1mCxFqlS9jlZOCjlMZVvZuv8spbY8ZN9HaMvuVmx9J00Fn2gCJd0TQ==
+
+"@element-hq/element-web-playwright-common@^1.1.5":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@element-hq/element-web-playwright-common/-/element-web-playwright-common-1.2.0.tgz#63808520825899701d9359001430e91b0ca6d1e0"
+  integrity sha512-I+W+0PThZkwwfP03xT19q6C0jrMqSimD+6QjsKV+DdwJyoJrFfhHD10jil8dWelDMwrhyfk+qLiu6Go7UDgaog==
+  dependencies:
+    "@axe-core/playwright" "^4.10.1"
+    "@testcontainers/postgresql" "^11.0.0"
+    lodash-es "^4.17.21"
+    mailpit-api "^1.2.0"
+    strip-ansi "^7.1.0"
+    testcontainers "^11.0.0"
+    yaml "^2.7.0"
+
+"@emnapi/core@^1.4.3":
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6"
+  integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==
+  dependencies:
+    "@emnapi/wasi-threads" "1.0.2"
+    tslib "^2.4.0"
+
+"@emnapi/runtime@^1.4.3":
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d"
+  integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==
+  dependencies:
+    tslib "^2.4.0"
+
+"@emnapi/wasi-threads@1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c"
+  integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==
+  dependencies:
+    tslib "^2.4.0"
+
 "@eslint-community/eslint-utils@^4.2.0":
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -1556,9 +1720,16 @@
     eslint-visitor-keys "^3.3.0"
 
 "@eslint-community/eslint-utils@^4.4.0":
-  version "4.4.1"
-  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
-  integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz#e4c58fdcf0696e7a5f19c30201ed43123ab15abc"
+  integrity sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==
+  dependencies:
+    eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/eslint-utils@^4.7.0":
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
+  integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
   dependencies:
     eslint-visitor-keys "^3.4.3"
 
@@ -1592,11 +1763,6 @@
   resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
   integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
 
-"@fastify/busboy@^2.0.0":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
-  integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
-
 "@floating-ui/core@^1.6.0":
   version "1.6.8"
   resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12"
@@ -1634,47 +1800,65 @@
   integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
 
 "@fontsource/inconsolata@^5":
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.1.0.tgz#f6a76680173336d02d2ce4009699821a6be239ce"
-  integrity sha512-vYPdG3R46MhK+99De8e8MMyNad5BAb1oTnHMpojlctZyWJIcin8bKHFPUpQSNRhZ4HQL/+DCW+RTiG2RbnweTw==
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.2.5.tgz#8f220d83567dc27b7b54fd583044cb2ebbbd2759"
+  integrity sha512-OvzkZY5qYghv/jEV6cfGZzFhdFTvSnU+ExPC7WcZ7w8PdRhtiu/SpcBWOBt+3LXgS0n9qyepgq4zZmxlDTlGGQ==
 
 "@fontsource/inter@^5":
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47"
-  integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A==
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.2.5.tgz#69efffe6ccfae138cbe1806f41daa39cef986b59"
+  integrity sha512-kbsPKj0S4p44JdYRFiW78Td8Ge2sBVxi/PIBwmih+RpSXUdvS9nbs1HIiuUSPtRMi14CqLEZ/fbk7dj7vni1Sg==
 
-"@formatjs/ecma402-abstract@2.3.2":
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a"
-  integrity sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg==
+"@formatjs/ecma402-abstract@2.3.4":
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz#e90c5a846ba2b33d92bc400fdd709da588280fbc"
+  integrity sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==
   dependencies:
-    "@formatjs/fast-memoize" "2.2.6"
-    "@formatjs/intl-localematcher" "0.5.10"
-    decimal.js "10"
-    tslib "2"
+    "@formatjs/fast-memoize" "2.2.7"
+    "@formatjs/intl-localematcher" "0.6.1"
+    decimal.js "^10.4.3"
+    tslib "^2.8.0"
 
-"@formatjs/fast-memoize@2.2.6":
-  version "2.2.6"
-  resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz#fac0a84207a1396be1f1aa4ee2805b179e9343d1"
-  integrity sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==
+"@formatjs/fast-memoize@2.2.7":
+  version "2.2.7"
+  resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz#707f9ddaeb522a32f6715bb7950b0831f4cc7b15"
+  integrity sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==
   dependencies:
-    tslib "2"
+    tslib "^2.8.0"
 
-"@formatjs/intl-localematcher@0.5.10":
-  version "0.5.10"
-  resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz#1e0bd3fc1332c1fe4540cfa28f07e9227b659a58"
-  integrity sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==
+"@formatjs/intl-localematcher@0.6.1":
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz#25dc30675320bf65a9d7f73876fc1e4064c0e299"
+  integrity sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==
   dependencies:
-    tslib "2"
+    tslib "^2.8.0"
 
 "@formatjs/intl-segmenter@^11.5.7":
-  version "11.7.8"
-  resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.8.tgz#85990c7e3961ef686ed78b8cacb216852cadb061"
-  integrity sha512-+nqMCJ6LNLl+qXldE2uthF82O/2Yo6GZlyWbOY25fe3066iaHjmrR4nXd6AKRMCHNeBBx3rANFLm2B5cNTBzTQ==
-  dependencies:
-    "@formatjs/ecma402-abstract" "2.3.2"
-    "@formatjs/intl-localematcher" "0.5.10"
-    tslib "2"
+  version "11.7.10"
+  resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.10.tgz#31308d6d3a22f240f744b76fc5217fec464cf107"
+  integrity sha512-P938HeVcdhFFShvBEZWlQF1t6ES9CnwPgeGboLspc0p5q9rCjQ6atNEkLZoDjes0MKAWbgJzTsPjNjTh4ggqoA==
+  dependencies:
+    "@formatjs/ecma402-abstract" "2.3.4"
+    "@formatjs/intl-localematcher" "0.6.1"
+    tslib "^2.8.0"
+
+"@grpc/grpc-js@^1.11.1":
+  version "1.13.4"
+  resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.4.tgz#922fbc496e229c5fa66802d2369bf181c1df1c5a"
+  integrity sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==
+  dependencies:
+    "@grpc/proto-loader" "^0.7.13"
+    "@js-sdsl/ordered-map" "^4.4.2"
+
+"@grpc/proto-loader@^0.7.13":
+  version "0.7.15"
+  resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60"
+  integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==
+  dependencies:
+    lodash.camelcase "^4.3.0"
+    long "^5.0.0"
+    protobufjs "^7.2.5"
+    yargs "^17.7.2"
 
 "@humanwhocodes/config-array@^0.13.0":
   version "0.13.0"
@@ -1955,7 +2139,7 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
-"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
   version "0.3.25"
   resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
   integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
@@ -1963,15 +2147,20 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
+"@js-sdsl/ordered-map@^4.4.2":
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
+  integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
+
 "@jsonjoy.com/base64@^1.1.1":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578"
   integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==
 
 "@jsonjoy.com/json-pack@^1.0.3":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz#33ca57ee29d12feef540f2139225597469dec894"
-  integrity sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz#e658900e81d194903171c42546e1aa27f446846a"
+  integrity sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==
   dependencies:
     "@jsonjoy.com/base64" "^1.1.1"
     "@jsonjoy.com/util" "^1.1.2"
@@ -1983,6 +2172,13 @@
   resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.5.0.tgz#6008e35b9d9d8ee27bc4bfaa70c8cbf33a537b4c"
   integrity sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==
 
+"@keyv/serialize@^1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.0.3.tgz#e0fe3710e2a379cb0490cd41e5a5ffa2bab58bf6"
+  integrity sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==
+  dependencies:
+    buffer "^6.0.3"
+
 "@leichtgewicht/ip-codec@^2.0.1":
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
@@ -2028,10 +2224,10 @@
   resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
   integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
 
-"@maplibre/maplibre-gl-style-spec@^22.0.1":
-  version "22.0.1"
-  resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-22.0.1.tgz#49210dd9c08853130c453b2acb9439216ab81402"
-  integrity sha512-V7bSw7Ui6+NhpeeuYqGoqamvKuy+3+uCvQ/t4ZJkwN8cx527CAlQQQ2kp+w5R9q+Tw6bUAH+fsq+mPEkicgT8g==
+"@maplibre/maplibre-gl-style-spec@^23.2.2":
+  version "23.2.2"
+  resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.2.2.tgz#9030560d2b92c33132a7cea1d537a154d7674580"
+  integrity sha512-kLcVlItPCULc20SM6pSVA7u8nST9xmQA8d7utc9j3KB0Tf/xhM4GgCn/QsZcmlbN/wW0ujyomDrvZ3/LbwvAmw==
   dependencies:
     "@mapbox/jsonlint-lines-primitives" "~2.0.2"
     "@mapbox/unitbezier" "^0.0.1"
@@ -2041,23 +2237,23 @@
     rw "^1.3.3"
     tinyqueue "^3.0.0"
 
-"@matrix-org/analytics-events@^0.29.0":
-  version "0.29.1"
-  resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.29.1.tgz#b812b932d82de1409fa47199260c9a4d4f8349e8"
-  integrity sha512-EyN6TMG4fCeNoQEa0uYTNnMLT4M/F3eCU/usjLDHkVgIcwevvBCHxw2379IbOm4kJBbhSW/pcNkGRKntWu0J9g==
+"@matrix-org/analytics-events@^0.29.2":
+  version "0.29.2"
+  resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.29.2.tgz#20d9877f11d5e411f1610f396f9e490673d6da50"
+  integrity sha512-kpCdf6DBxgE7MbBbYr7FvahrktHHtiph3QN10I6nBAAPQ+hmR3aZHBECxjxLQ9RxvtBF9nlKK4bgy2YrNp6j3A==
 
-"@matrix-org/emojibase-bindings@^1.3.3":
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.3.3.tgz#cee82a739c0866bf3100b03755647ace1f3ba6ef"
-  integrity sha512-GwuZdmF+wZT34RKehQYjTzdgba1ju2W3FM4jPJfwqh0jUxVXZLb+6b6dV3lna6/7EDzgGvOMwTwCAolILDwS0g==
+"@matrix-org/emojibase-bindings@^1.3.4":
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.3.4.tgz#b0dad8e8b8bbe433e419b59e38f933bcdaf9c271"
+  integrity sha512-+nhBg0dxjy3U4/Tn6WIsnzqiqazc0pfStc2dkSBxDnc4xnimDB6vcIad53fUIsl7SeT50ake0hhnBJs0ZDDk6Q==
   dependencies:
     emojibase "^15.3.1"
     emojibase-data "^15.3.1"
 
-"@matrix-org/matrix-sdk-crypto-wasm@^12.1.0":
-  version "12.1.0"
-  resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-12.1.0.tgz#2aef64eab2d30c0a1ace9c0fe876f53aa2949f14"
-  integrity sha512-NhJFu/8FOGjnW7mDssRUzaMSwXrYOcCqgAjZyAw9KQ9unNADKEi7KoIKe7GtrG2PWtm36y2bUf+hB8vhSY6Wdw==
+"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1":
+  version "14.0.1"
+  resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.0.1.tgz#e258ef84bcc7889f0e7eb3a7dbecf0830a6dd606"
+  integrity sha512-CgLpHs6nTw5pjSsMBi9xbQnBXf2l8YhImQP9cv8nbGSCYdYjFI0FilMXffzjWV5HThpNHri/3pF20ahZtuS3VA==
 
 "@matrix-org/olm@3.2.15":
   version "3.2.15"
@@ -2072,9 +2268,18 @@
     "@babel/runtime" "^7.17.9"
 
 "@matrix-org/spec@^1.7.0":
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.12.0.tgz#9e8d37f65dc8029ceeac1da47556e6ba59d44733"
-  integrity sha512-QHUQ79dMtd0eKQgebRye8li1y/S1172T6fXhu10dsFKtirSWblneoeFnCUR8hsUSBGWLtqBFcryVGa9uoBhqxg==
+  version "1.14.0"
+  resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.14.0.tgz#cedc4f9cc86c9e623ccd4f643629bac97bc0fed2"
+  integrity sha512-UI4kY6WXniRV++ZeMesmCvZMH8lANLc12rnca1/M+nkPgJgU9GQd5X9ezFoOLabG6tGdEUPPKr5/S2EP1FdGLA==
+
+"@napi-rs/wasm-runtime@^0.2.9":
+  version "0.2.10"
+  resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz#f3b7109419c6670000b2401e0c778b98afc25f84"
+  integrity sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==
+  dependencies:
+    "@emnapi/core" "^1.4.3"
+    "@emnapi/runtime" "^1.4.3"
+    "@tybys/wasm-util" "^0.9.0"
 
 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
   version "5.1.1-v1"
@@ -2096,7 +2301,7 @@
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
   integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
 
-"@nodelib/fs.walk@1.2.8", "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
   integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -2104,6 +2309,73 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
+"@oxc-resolver/binding-darwin-arm64@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-9.0.2.tgz#345ff11258dbec11d333bf088a79b864f5f03ec5"
+  integrity sha512-MVyRgP2gzJJtAowjG/cHN3VQXwNLWnY+FpOEsyvDepJki1SdAX/8XDijM1yN6ESD1kr9uhBKjGelC6h3qtT+rA==
+
+"@oxc-resolver/binding-darwin-x64@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-9.0.2.tgz#cd19f263a31b601356002ebd2eb8dda193753704"
+  integrity sha512-7kV0EOFEZ3sk5Hjy4+bfA6XOQpCwbDiDkkHN4BHHyrBHsXxUR05EcEJPPL1WjItefg+9+8hrBmoK0xRoDs41+A==
+
+"@oxc-resolver/binding-freebsd-x64@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-9.0.2.tgz#e6e0b9b9409cb4eb71307ca880f1d868cce88c94"
+  integrity sha512-6OvkEtRXrt8sJ4aVfxHRikjain9nV1clIsWtJ1J3J8NG1ZhjyJFgT00SCvqxbK+pzeWJq6XzHyTCN78ML+lY2w==
+
+"@oxc-resolver/binding-linux-arm-gnueabihf@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-9.0.2.tgz#e4ef8e0a831fa05101ca8fc0501cd7b536093e66"
+  integrity sha512-aYpNL6o5IRAUIdoweW21TyLt54Hy/ZS9tvzNzF6ya1ckOQ8DLaGVPjGpmzxdNja9j/bbV6aIzBH7lNcBtiOTkQ==
+
+"@oxc-resolver/binding-linux-arm64-gnu@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-9.0.2.tgz#c95345cf91b9597a469a8d3028d0b182e2e22055"
+  integrity sha512-RGFW4vCfKMFEIzb9VCY0oWyyY9tR1/o+wDdNePhiUXZU4SVniRPQaZ1SJ0sUFI1k25pXZmzQmIP6cBmazi/Dew==
+
+"@oxc-resolver/binding-linux-arm64-musl@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-9.0.2.tgz#d968aadd446a6c1d729764f3ddd0e1b66e3ec53f"
+  integrity sha512-lxx/PibBfzqYvut2Y8N2D0Ritg9H8pKO+7NUSJb9YjR/bfk2KRmP8iaUz3zB0JhPtf/W3REs65oKpWxgflGToA==
+
+"@oxc-resolver/binding-linux-riscv64-gnu@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-9.0.2.tgz#698f364bf15489e69c8441c842a09a468c7389ca"
+  integrity sha512-yD28ptS/OuNhwkpXRPNf+/FvrO7lwURLsEbRVcL1kIE0GxNJNMtKgIE4xQvtKDzkhk6ZRpLho5VSrkkF+3ARTQ==
+
+"@oxc-resolver/binding-linux-s390x-gnu@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-9.0.2.tgz#dfcbf0531f0ade1197275f40e6f8900635af187c"
+  integrity sha512-WBwEJdspoga2w+aly6JVZeHnxuPVuztw3fPfWrei2P6rNM5hcKxBGWKKT6zO1fPMCB4sdDkFohGKkMHVV1eryQ==
+
+"@oxc-resolver/binding-linux-x64-gnu@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-9.0.2.tgz#73f7157f3b052c44c206b672b970da060125c533"
+  integrity sha512-a2z3/cbOOTUq0UTBG8f3EO/usFcdwwXnCejfXv42HmV/G8GjrT4fp5+5mVDoMByH3Ce3iVPxj1LmS6OvItKMYQ==
+
+"@oxc-resolver/binding-linux-x64-musl@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-9.0.2.tgz#2b047ce69cb7fa1900174d43e4a7b6027146a449"
+  integrity sha512-bHZF+WShYQWpuswB9fyxcgMIWVk4sZQT0wnwpnZgQuvGTZLkYJ1JTCXJMtaX5mIFHf69ngvawnwPIUA4Feil0g==
+
+"@oxc-resolver/binding-wasm32-wasi@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-9.0.2.tgz#df8eab1815ae0da0c70f0f9dda4bcd84c70d7024"
+  integrity sha512-I5cSgCCh5nFozGSHz+PjIOfrqW99eUszlxKLgoNNzQ1xQ2ou9ZJGzcZ94BHsM9SpyYHLtgHljmOZxCT9bgxYNA==
+  dependencies:
+    "@napi-rs/wasm-runtime" "^0.2.9"
+
+"@oxc-resolver/binding-win32-arm64-msvc@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-9.0.2.tgz#9530c2e08ebb5d02870b004135ef0b4438a620b7"
+  integrity sha512-5IhoOpPr38YWDWRCA5kP30xlUxbIJyLAEsAK7EMyUgqygBHEYLkElaKGgS0X5jRXUQ6l5yNxuW73caogb2FYaw==
+
+"@oxc-resolver/binding-win32-x64-msvc@9.0.2":
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-9.0.2.tgz#15dc9cecd2e89bbcad07247477fc04dd8c3ffbbe"
+  integrity sha512-Qc40GDkaad9rZksSQr2l/V9UubigIHsW69g94Gswc2sKYB3XfJXfIfyV8WTJ67u6ZMXsZ7BH1msSC6Aen75mCg==
+
 "@peculiar/asn1-schema@^2.3.13", "@peculiar/asn1-schema@^2.3.8":
   version "2.3.13"
   resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz#ec8509cdcbc0da3abe73fd7e690556b57a61b8f4"
@@ -2136,12 +2408,12 @@
   resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
   integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
 
-"@playwright/test@^1.40.1":
-  version "1.49.1"
-  resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.1.tgz#55fa360658b3187bfb6371e2f8a64f50ef80c827"
-  integrity sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==
+"@playwright/test@1.52.0", "@playwright/test@^1.50.1":
+  version "1.52.0"
+  resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.52.0.tgz#267ec595b43a8f4fa5e444ea503689629e91a5b8"
+  integrity sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==
   dependencies:
-    playwright "1.49.1"
+    playwright "1.52.0"
 
 "@polka/url@^1.0.0-next.24":
   version "1.0.0-next.28"
@@ -2153,6 +2425,59 @@
   resolved "https://registry.yarnpkg.com/@principalstudio/html-webpack-inject-preload/-/html-webpack-inject-preload-1.2.7.tgz#0c1f0b32a34d814b36ce84111f89990441cc64e8"
   integrity sha512-KJKkiKG63ugBjf8U0e9jUcI9CLPTFIsxXplEDE0oi3mPpxd90X9SJovo3W2l7yh/ARKIYXhQq8fSXUN7M29TzQ==
 
+"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
+  integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
+
+"@protobufjs/base64@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
+  integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
+"@protobufjs/codegen@^2.0.4":
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
+  integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
+
+"@protobufjs/eventemitter@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
+  integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
+
+"@protobufjs/fetch@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
+  integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
+  dependencies:
+    "@protobufjs/aspromise" "^1.1.1"
+    "@protobufjs/inquire" "^1.1.0"
+
+"@protobufjs/float@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
+  integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
+
+"@protobufjs/inquire@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
+  integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
+
+"@protobufjs/path@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
+  integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
+
+"@protobufjs/pool@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
+  integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
+
+"@protobufjs/utf8@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
+  integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+
 "@radix-ui/primitive@1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
@@ -2434,110 +2759,115 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
   integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
 
+"@rrweb/types@^2.0.0-alpha.18":
+  version "2.0.0-alpha.18"
+  resolved "https://registry.yarnpkg.com/@rrweb/types/-/types-2.0.0-alpha.18.tgz#e1d9af844cebbf30a2be8808f6cf64f5df3e7f50"
+  integrity sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==
+
 "@rtsao/scc@^1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
   integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
 
-"@sentry-internal/browser-utils@8.43.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.43.0.tgz#b064908a537d1cc17d8ddaf0f4c5d712557cbf40"
-  integrity sha512-5WhJZ3SA5sZVDBwOsChDd5JCzYcwBX7sEqBqEcm3pFru6TUihEnFIJmDIbreIyrQMwUhs3dTxnfnidgjr5z1Ag==
+"@sentry-internal/browser-utils@9.23.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.23.0.tgz#19cd82bc42654883190423126e666c6e5db66b43"
+  integrity sha512-hyN2Q6mh7ggw8sDVHeRyWz5LR6gjvf8zHSzQnMaF7QkeSyaeGM/SVSL4ODwqR9TRH7U2ku6nZFMbKhaBPV+Hfg==
   dependencies:
-    "@sentry/core" "8.43.0"
+    "@sentry/core" "9.23.0"
 
-"@sentry-internal/feedback@8.43.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.43.0.tgz#9477b999c9bca62335eb944a6f7246a96beb0111"
-  integrity sha512-rcGR2kzFu4vLXBQbI9eGJwjyToyjl36O2q/UKbiZBNJ5IFtDvKRLke6jIHq/YqiHPfFGpVtq5M/lYduDfA/eaQ==
+"@sentry-internal/feedback@9.23.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.23.0.tgz#088c52187cb1a9bb0b22c3df14a6d0b8fafec0e4"
+  integrity sha512-Xf+KqV69TBiPo1gk2EsU6O/dumuTMxWOF52uVWJddQYI3pQTU5DqSeoZ5AY76bIIhV9n6AEFDGqNPXmuj4Acrw==
   dependencies:
-    "@sentry/core" "8.43.0"
+    "@sentry/core" "9.23.0"
 
-"@sentry-internal/replay-canvas@8.43.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.43.0.tgz#f5672a08c9eb588afa0bf36f07b9f5c29b5c9920"
-  integrity sha512-rL8G7E1GtozH8VNalRrBQNjYDJ5ChWS/vpQI5hUG11PZfvQFXEVatLvT3uO2l0xIlHm4idTsHOSLTe/usxnogQ==
+"@sentry-internal/replay-canvas@9.23.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.23.0.tgz#7e384e8ab50d99d363c5e01cd255e04af1b9623b"
+  integrity sha512-cYlw5svJjyPequm0PJjFGLpee86L1NieONEHlujOXkIG6IEriiorMm+8bNpGsHRuyvg41B+4P/YmcQAGtEGxXg==
   dependencies:
-    "@sentry-internal/replay" "8.43.0"
-    "@sentry/core" "8.43.0"
+    "@sentry-internal/replay" "9.23.0"
+    "@sentry/core" "9.23.0"
 
-"@sentry-internal/replay@8.43.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.43.0.tgz#4e2e3844f52b47b16bf816d21857921bbfe85d62"
-  integrity sha512-geV5/zejLfGGwWHjylzrb1w8NI3U37GMG9/53nmv13FmTXUDF5XF2lh41KXFVYwvp7Ha4bd1FRQ9IU9YtBWskw==
+"@sentry-internal/replay@9.23.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.23.0.tgz#6b1369a8712323666fd04a4f86d3ab8865ff4f37"
+  integrity sha512-0/q15tvSboaK7/05BFQhs71bqgHKejJoDJgXmH0lySqgsRh/S18867ZxQNiuYhuVt337h07u1QaCyjnNJKHfuA==
   dependencies:
-    "@sentry-internal/browser-utils" "8.43.0"
-    "@sentry/core" "8.43.0"
+    "@sentry-internal/browser-utils" "9.23.0"
+    "@sentry/core" "9.23.0"
 
-"@sentry/babel-plugin-component-annotate@2.22.7":
-  version "2.22.7"
-  resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.7.tgz#604c7e33d48528a13477e7af597c4d5fca51b8bd"
-  integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ==
+"@sentry/babel-plugin-component-annotate@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz#1b0d01f903b725da876117d551610085c3dd21c7"
+  integrity sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==
 
-"@sentry/browser@^8.0.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.43.0.tgz#4eec67bc6fb278727304045b612ac392674cade6"
-  integrity sha512-LGvLLnfmR8+AEgFmd7Q7KHiOTiV0P1Lvio2ENDELhEqJOIiICauttibVmig+AW02qg4kMeywvleMsUYaZv2RVA==
+"@sentry/browser@^9.0.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.23.0.tgz#8fdc4e393c8b893ca467d577bb7b11d038650641"
+  integrity sha512-QRkNxWys8e088260vByztoTsEVZ0W6v/XnZfKT6wkPPGn0aFeOrg/xjgxfI8D5huqZCxT28Cf23akOOly4FXjg==
   dependencies:
-    "@sentry-internal/browser-utils" "8.43.0"
-    "@sentry-internal/feedback" "8.43.0"
-    "@sentry-internal/replay" "8.43.0"
-    "@sentry-internal/replay-canvas" "8.43.0"
-    "@sentry/core" "8.43.0"
+    "@sentry-internal/browser-utils" "9.23.0"
+    "@sentry-internal/feedback" "9.23.0"
+    "@sentry-internal/replay" "9.23.0"
+    "@sentry-internal/replay-canvas" "9.23.0"
+    "@sentry/core" "9.23.0"
 
-"@sentry/bundler-plugin-core@2.22.7":
-  version "2.22.7"
-  resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.7.tgz#28204a224cd1fef58d157e5beeb2493947a9bc35"
-  integrity sha512-ouQh5sqcB8vsJ8yTTe0rf+iaUkwmeUlGNFi35IkCFUQlWJ22qS6OfvNjOqFI19e6eGUXks0c/2ieFC4+9wJ+1g==
+"@sentry/bundler-plugin-core@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz#b62af5be1b1a862e7062181655829c556c7d7c0b"
+  integrity sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ==
   dependencies:
     "@babel/core" "^7.18.5"
-    "@sentry/babel-plugin-component-annotate" "2.22.7"
-    "@sentry/cli" "2.39.1"
+    "@sentry/babel-plugin-component-annotate" "3.5.0"
+    "@sentry/cli" "2.42.2"
     dotenv "^16.3.1"
     find-up "^5.0.0"
     glob "^9.3.2"
     magic-string "0.30.8"
     unplugin "1.0.1"
 
-"@sentry/cli-darwin@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781"
-  integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==
-
-"@sentry/cli-linux-arm64@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6"
-  integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==
-
-"@sentry/cli-linux-arm@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c"
-  integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==
-
-"@sentry/cli-linux-i686@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201"
-  integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==
-
-"@sentry/cli-linux-x64@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889"
-  integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==
-
-"@sentry/cli-win32-i686@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35"
-  integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==
-
-"@sentry/cli-win32-x64@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb"
-  integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==
-
-"@sentry/cli@2.39.1":
-  version "2.39.1"
-  resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29"
-  integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ==
+"@sentry/cli-darwin@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz#a32a4f226e717122b37d9969e8d4d0e14779f720"
+  integrity sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg==
+
+"@sentry/cli-linux-arm64@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz#1c06c83ff21f51ec23acf5be3b1f8c7553bf86b1"
+  integrity sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw==
+
+"@sentry/cli-linux-arm@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz#00cadc359ae3c051efb3e63873c033c61dbd1ca1"
+  integrity sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg==
+
+"@sentry/cli-linux-i686@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz#3b817b715dd806c20dfbffd539725ad8089c310a"
+  integrity sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ==
+
+"@sentry/cli-linux-x64@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz#ddf906bc3071cc79ce6e633eddcb76bb9068e688"
+  integrity sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw==
+
+"@sentry/cli-win32-i686@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz#9036085c7c6ce455ad45fda411c55ff39c06eb95"
+  integrity sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw==
+
+"@sentry/cli-win32-x64@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz#7d6464b63f32c9f97fff428f246b1f039b402233"
+  integrity sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw==
+
+"@sentry/cli@2.42.2":
+  version "2.42.2"
+  resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.42.2.tgz#8173df4d057d600a9ef0cf1e9b42b0c6607b46e4"
+  integrity sha512-spb7S/RUumCGyiSTg8DlrCX4bivCNmU/A1hcfkwuciTFGu8l5CDc2I6jJWWZw8/0enDGxuj5XujgXvU5tr4bxg==
   dependencies:
     https-proxy-agent "^5.0.0"
     node-fetch "^2.6.7"
@@ -2545,25 +2875,25 @@
     proxy-from-env "^1.1.0"
     which "^2.0.2"
   optionalDependencies:
-    "@sentry/cli-darwin" "2.39.1"
-    "@sentry/cli-linux-arm" "2.39.1"
-    "@sentry/cli-linux-arm64" "2.39.1"
-    "@sentry/cli-linux-i686" "2.39.1"
-    "@sentry/cli-linux-x64" "2.39.1"
-    "@sentry/cli-win32-i686" "2.39.1"
-    "@sentry/cli-win32-x64" "2.39.1"
-
-"@sentry/core@8.43.0":
-  version "8.43.0"
-  resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.43.0.tgz#e96a489e87a9999199f5ac27d8860da37c1fa8b4"
-  integrity sha512-ktyovtjkTMNud+kC/XfqHVCoQKreIKgx/hgeRvzPwuPyd1t1KzYmRL3DBkbcWVnyOPpVTHn+RsEI1eRcVYHtvw==
-
-"@sentry/webpack-plugin@^2.7.1":
-  version "2.22.7"
-  resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.7.tgz#992c6c782c736f22e72eb318745e28cc24aabad7"
-  integrity sha512-j5h5LZHWDlm/FQCCmEghQ9FzYXwfZdlOf3FE/X6rK6lrtx0JCAkq+uhMSasoyP4XYKL4P4vRS6WFSos4jxf/UA==
-  dependencies:
-    "@sentry/bundler-plugin-core" "2.22.7"
+    "@sentry/cli-darwin" "2.42.2"
+    "@sentry/cli-linux-arm" "2.42.2"
+    "@sentry/cli-linux-arm64" "2.42.2"
+    "@sentry/cli-linux-i686" "2.42.2"
+    "@sentry/cli-linux-x64" "2.42.2"
+    "@sentry/cli-win32-i686" "2.42.2"
+    "@sentry/cli-win32-x64" "2.42.2"
+
+"@sentry/core@9.23.0":
+  version "9.23.0"
+  resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.23.0.tgz#96a514cc9d8ac043dea0a4648ed1205c25ff5ae5"
+  integrity sha512-9846pn/BvASGgl7WsnKY4xry98WreP9ToeLfCQTQOf+pNfD/qNPn1/0xPInGni3LVMAXRtfHHMPm2Ghz255N7A==
+
+"@sentry/webpack-plugin@^3.0.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz#cde95534f1e945a4002d47465aeda01d382cd279"
+  integrity sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ==
+  dependencies:
+    "@sentry/bundler-plugin-core" "3.5.0"
     unplugin "1.0.1"
     uuid "^9.0.0"
 
@@ -2572,11 +2902,6 @@
   resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
   integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
 
-"@sindresorhus/merge-streams@^2.1.0":
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958"
-  integrity sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==
-
 "@sinonjs/commons@^3.0.0":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
@@ -2591,21 +2916,12 @@
   dependencies:
     "@sinonjs/commons" "^3.0.0"
 
-"@snyk/github-codeowners@1.1.0":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@snyk/github-codeowners/-/github-codeowners-1.1.0.tgz#45b99732c3c38b5f5b47e43d2b0c9db67a6d2bcc"
-  integrity sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==
-  dependencies:
-    commander "^4.1.1"
-    ignore "^5.1.8"
-    p-map "^4.0.0"
-
-"@stylistic/eslint-plugin@^2.9.0":
-  version "2.11.0"
-  resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz#50d0289f36f7201055b7fa1729fdc1d8c46e93fa"
-  integrity sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==
+"@stylistic/eslint-plugin@^4.0.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-4.4.0.tgz#e1a3c9fd7109411d32dc0bcb575d2b4066fbbc63"
+  integrity sha512-bIh/d9X+OQLCAMdhHtps+frvyjvAM4B1YlSJzcEEhl7wXLIqPar3ngn9DrHhkBOrTA/z9J0bUMtctAspe0dxdQ==
   dependencies:
-    "@typescript-eslint/utils" "^8.13.0"
+    "@typescript-eslint/utils" "^8.32.1"
     eslint-visitor-keys "^4.2.0"
     espree "^10.3.0"
     estraverse "^5.3.0"
@@ -2717,12 +3033,12 @@
     "@svgr/plugin-jsx" "8.1.0"
     "@svgr/plugin-svgo" "8.1.0"
 
-"@testcontainers/postgresql@^10.16.0":
-  version "10.16.0"
-  resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-10.16.0.tgz#0437a9b426d64ea958e745a0e2ae19462b786f81"
-  integrity sha512-zWFQI+3QxlEELRvVv27i6zlVEPNUz9zKaSh7iWmFlCdfhcyr78daS0FG8FIfdQ79VK7YXA4jv+dTYXa2SwXu/w==
+"@testcontainers/postgresql@^11.0.0":
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-11.0.0.tgz#6590f2398fae2d9ad78a748e92b89053d2380f56"
+  integrity sha512-cw2vIz5MS1AjXkxcPmOQh0NRdh7p6frSLOkjsBFuR5lUo3qnV3LmvCfanyRzgXs5aHGJLwc0mlQNfXnKedBF6Q==
   dependencies:
-    testcontainers "^10.16.0"
+    testcontainers "^11.0.0"
 
 "@testing-library/dom@^10.4.0":
   version "10.4.0"
@@ -2752,16 +3068,16 @@
     redent "^3.0.0"
 
 "@testing-library/react@^16.0.0":
-  version "16.1.0"
-  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.1.0.tgz#aa0c61398bac82eaf89776967e97de41ac742d71"
-  integrity sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==
+  version "16.3.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.0.tgz#3a85bb9bdebf180cd76dba16454e242564d598a6"
+  integrity sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==
   dependencies:
     "@babel/runtime" "^7.12.5"
 
 "@testing-library/user-event@^14.5.2":
-  version "14.5.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
-  integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==
+  version "14.6.1"
+  resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
+  integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
 
 "@tootallnate/once@2":
   version "2.0.0"
@@ -2793,6 +3109,13 @@
   resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
   integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
 
+"@tybys/wasm-util@^0.9.0":
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355"
+  integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==
+  dependencies:
+    tslib "^2.4.0"
+
 "@types/aria-query@^5.0.1":
   version "5.0.4"
   resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
@@ -2889,10 +3212,10 @@
     "@types/node" "*"
     "@types/ssh2" "*"
 
-"@types/dockerode@^3.3.29":
-  version "3.3.33"
-  resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.33.tgz#67d9b4223caf41a0735695abe89c292e05d305c9"
-  integrity sha512-7av8lVOhkW7Xd11aZTSq5zhdpyNraldXwQR0pxUCiSNTvIzsP86KrFrmrZgxtrXD2Zrtzwt4H6OYLbATONWzWg==
+"@types/dockerode@^3.3.39":
+  version "3.3.39"
+  resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.39.tgz#629adef5db9b3bb955423db1ccd586ee2b36e2e7"
+  integrity sha512-uMPmxehH6ofeYjaslASPtjvyH8FRJdM9fZ+hjhGzL4Jq3bGjr9D7TKmp9soSwgFncNk0HOwmyBxjqOb3ikjjsA==
   dependencies:
     "@types/docker-modem" "*"
     "@types/node" "*"
@@ -2920,9 +3243,9 @@
     "@types/json-schema" "*"
 
 "@types/estree@*", "@types/estree@^1.0.6":
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
-  integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
+  integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
 
 "@types/events@^3.0.0":
   version "3.0.3"
@@ -2930,16 +3253,16 @@
   integrity sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==
 
 "@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz#812d2871e5eea17fb0bd5214dda7a7b748c0e12a"
-  integrity sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==
+  version "5.0.6"
+  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz#41fec4ea20e9c7b22f024ab88a95c6bb288f51b8"
+  integrity sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==
   dependencies:
     "@types/node" "*"
     "@types/qs" "*"
     "@types/range-parser" "*"
     "@types/send" "*"
 
-"@types/express-serve-static-core@^4.17.33":
+"@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33":
   version "4.19.6"
   resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267"
   integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==
@@ -2950,13 +3273,12 @@
     "@types/send" "*"
 
 "@types/express@*", "@types/express@^5.0.0":
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c"
-  integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.2.tgz#7be9e337a5745d6b43ef5b0c352dad94a7f0c256"
+  integrity sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g==
   dependencies:
     "@types/body-parser" "*"
     "@types/express-serve-static-core" "^5.0.0"
-    "@types/qs" "*"
     "@types/serve-static" "*"
 
 "@types/express@^4.17.21":
@@ -2981,15 +3303,10 @@
   dependencies:
     "@types/geojson" "*"
 
-"@types/geojson@*":
-  version "7946.0.14"
-  resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613"
-  integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==
-
-"@types/geojson@^7946.0.15":
-  version "7946.0.15"
-  resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.15.tgz#f9d55fd5a0aa2de9dc80b1b04e437538b7298868"
-  integrity sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==
+"@types/geojson@*", "@types/geojson@^7946.0.16":
+  version "7946.0.16"
+  resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
+  integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==
 
 "@types/glob-to-regexp@^0.4.1":
   version "0.4.4"
@@ -3022,9 +3339,9 @@
   integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
 
 "@types/http-proxy@^1.17.8":
-  version "1.17.15"
-  resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36"
-  integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==
+  version "1.17.16"
+  resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.16.tgz#dee360707b35b3cc85afcde89ffeebff7d7f9240"
+  integrity sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==
   dependencies:
     "@types/node" "*"
 
@@ -3069,7 +3386,7 @@
     "@types/tough-cookie" "*"
     parse5 "^7.0.0"
 
-"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
   version "7.0.15"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
   integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -3090,9 +3407,9 @@
   integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
 
 "@types/lodash@^4.14.168":
-  version "4.17.14"
-  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.14.tgz#bafc053533f4cdc5fcc9635af46a963c1f3deaff"
-  integrity sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==
+  version "4.17.17"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.17.tgz#fb85a04f47e9e4da888384feead0de05f7070355"
+  integrity sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==
 
 "@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4":
   version "0.1.4"
@@ -3138,24 +3455,17 @@
   dependencies:
     "@types/node" "*"
 
-"@types/node@*":
-  version "22.10.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.1.tgz#41ffeee127b8975a05f8c4f83fb89bcb2987d766"
-  integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==
-  dependencies:
-    undici-types "~6.20.0"
-
-"@types/node@18":
-  version "18.19.70"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.70.tgz#5a77508f5568d16fcd3b711c8102d7a430a04df7"
-  integrity sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==
+"@types/node@*", "@types/node@>=13.7.0":
+  version "22.15.23"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.23.tgz#a0b7c03f951f1ffe381a6a345c68d80e48043dd0"
+  integrity sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==
   dependencies:
-    undici-types "~5.26.4"
+    undici-types "~6.21.0"
 
-"@types/node@^18.11.18":
-  version "18.19.69"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.69.tgz#748d301818ba4b238854c53d290257a70aae7d01"
-  integrity sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==
+"@types/node@18", "@types/node@^18.11.18":
+  version "18.19.105"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.105.tgz#44bae77ea9832da4357e1d35df37cb86927e1405"
+  integrity sha512-a+DrwD2VyzqQR2W0EVF8EaCh6Em4ilQAYLEPZnMNkQHXR7ziWW7RUhZMWZAgRpkDDAdUIcJOXSPJT/zBEwz3sA==
   dependencies:
     undici-types "~5.26.4"
 
@@ -3180,9 +3490,9 @@
   integrity sha512-z6djfFIbrrddtunoMJBOPlyZrnmeuG1kkvHUNi2QfpOb+JMMLuLliHHTmMyRi7k7LiTAut0HbdGCF6ibDtQAHQ==
 
 "@types/prop-types@*":
-  version "15.7.13"
-  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
-  integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
+  version "15.7.14"
+  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2"
+  integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
 
 "@types/qrcode@^1.3.5":
   version "1.5.5"
@@ -3192,9 +3502,9 @@
     "@types/node" "*"
 
 "@types/qs@*":
-  version "6.9.16"
-  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794"
-  integrity sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==
+  version "6.14.0"
+  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
+  integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==
 
 "@types/range-parser@*":
   version "1.2.7"
@@ -3208,10 +3518,10 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-dom@18.3.5":
-  version "18.3.5"
-  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716"
-  integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==
+"@types/react-dom@19.1.5":
+  version "19.1.5"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.5.tgz#cdfe2c663742887372f54804b16e8dbc26bd794a"
+  integrity sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==
 
 "@types/react-redux@^7.1.20":
   version "7.1.34"
@@ -3224,26 +3534,23 @@
     redux "^4.0.0"
 
 "@types/react-transition-group@^4.4.0":
-  version "4.4.11"
-  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5"
-  integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==
-  dependencies:
-    "@types/react" "*"
+  version "4.4.12"
+  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
+  integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
 
 "@types/react-virtualized@^9.21.30":
-  version "9.22.0"
-  resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.22.0.tgz#2ff9b3692fa04a429df24ffc7d181d9f33b3831d"
-  integrity sha512-JL/YCCFZ123za//cj10Apk54F0UGFMrjOE0QHTuXt1KBMFrzLOGv9/x6Uc/pZ0Gaf4o6w61Fostvlw0DwuPXig==
+  version "9.22.2"
+  resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.22.2.tgz#97674f050a85d0f7aab827b3d894f3f1b237922a"
+  integrity sha512-0Eg/ME3OHYWGxs+/n4VelfYrhXssireZaa1Uqj5SEkTpSaBu5ctFGOCVxcOqpGXRiEdrk/7uho9tlZaryCIjHA==
   dependencies:
     "@types/prop-types" "*"
     "@types/react" "*"
 
-"@types/react@*", "@types/react@18.3.18":
-  version "18.3.18"
-  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.18.tgz#9b382c4cd32e13e463f97df07c2ee3bbcd26904b"
-  integrity sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==
+"@types/react@*", "@types/react@19.1.6":
+  version "19.1.6"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.6.tgz#dee39f3e1e9a7d693f156a5840570b6d57f325ea"
+  integrity sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==
   dependencies:
-    "@types/prop-types" "*"
     csstype "^3.0.2"
 
 "@types/retry@0.12.0":
@@ -3256,10 +3563,10 @@
   resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a"
   integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==
 
-"@types/sanitize-html@2.13.0":
-  version "2.13.0"
-  resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.13.0.tgz#ac3620e867b7c68deab79c72bd117e2049cdd98e"
-  integrity sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==
+"@types/sanitize-html@2.16.0":
+  version "2.16.0"
+  resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.16.0.tgz#860d72c1ba8a5d044946f37559cc359c0a13b24e"
+  integrity sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==
   dependencies:
     htmlparser2 "^8.0.0"
 
@@ -3269,9 +3576,9 @@
   integrity sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==
 
 "@types/semver@^7.5.8":
-  version "7.5.8"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
-  integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
+  version "7.7.0"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e"
+  integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==
 
 "@types/send@*":
   version "0.17.4"
@@ -3312,9 +3619,9 @@
     "@types/node" "*"
 
 "@types/ssh2@*":
-  version "1.15.1"
-  resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.1.tgz#4db4b6864abca09eb299fe5354fa591add412223"
-  integrity sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==
+  version "1.15.5"
+  resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.5.tgz#6d8f45db2f39519b8d9377268fa71ed77d969686"
+  integrity sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==
   dependencies:
     "@types/node" "^18.11.18"
 
@@ -3359,9 +3666,9 @@
   integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==
 
 "@types/ws@^8.5.10":
-  version "8.5.12"
-  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
-  integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==
+  version "8.18.1"
+  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
+  integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
   dependencies:
     "@types/node" "*"
 
@@ -3378,129 +3685,145 @@
     "@types/yargs-parser" "*"
 
 "@typescript-eslint/eslint-plugin@^8.19.0":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz#5f26c0a833b27bcb1aa402b82e76d3b8dda0b247"
-  integrity sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz#51ed03649575ba51bcee7efdbfd85283249b5447"
+  integrity sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==
   dependencies:
     "@eslint-community/regexpp" "^4.10.0"
-    "@typescript-eslint/scope-manager" "8.19.1"
-    "@typescript-eslint/type-utils" "8.19.1"
-    "@typescript-eslint/utils" "8.19.1"
-    "@typescript-eslint/visitor-keys" "8.19.1"
+    "@typescript-eslint/scope-manager" "8.33.0"
+    "@typescript-eslint/type-utils" "8.33.0"
+    "@typescript-eslint/utils" "8.33.0"
+    "@typescript-eslint/visitor-keys" "8.33.0"
     graphemer "^1.4.0"
-    ignore "^5.3.1"
+    ignore "^7.0.0"
     natural-compare "^1.4.0"
-    ts-api-utils "^2.0.0"
+    ts-api-utils "^2.1.0"
 
 "@typescript-eslint/parser@^8.19.0":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.19.1.tgz#b836fcfe7a704c8c65f5a50e5b0ff8acfca5c21b"
-  integrity sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==
-  dependencies:
-    "@typescript-eslint/scope-manager" "8.19.1"
-    "@typescript-eslint/types" "8.19.1"
-    "@typescript-eslint/typescript-estree" "8.19.1"
-    "@typescript-eslint/visitor-keys" "8.19.1"
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.0.tgz#8e523c2b447ad7cd6ac91b719d8b37449481784d"
+  integrity sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==
+  dependencies:
+    "@typescript-eslint/scope-manager" "8.33.0"
+    "@typescript-eslint/types" "8.33.0"
+    "@typescript-eslint/typescript-estree" "8.33.0"
+    "@typescript-eslint/visitor-keys" "8.33.0"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@8.16.0":
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz#ebc9a3b399a69a6052f3d88174456dd399ef5905"
-  integrity sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==
+"@typescript-eslint/project-service@8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz#71f37ef9010de47bf20963914743c5cbef851e08"
+  integrity sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==
   dependencies:
-    "@typescript-eslint/types" "8.16.0"
-    "@typescript-eslint/visitor-keys" "8.16.0"
+    "@typescript-eslint/tsconfig-utils" "^8.33.0"
+    "@typescript-eslint/types" "^8.33.0"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz#794cfc8add4f373b9cd6fa32e367e7565a0e231b"
-  integrity sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==
+"@typescript-eslint/scope-manager@8.23.0":
+  version "8.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz#ee3bb7546421ca924b9b7a8b62a77d388193ddec"
+  integrity sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==
   dependencies:
-    "@typescript-eslint/types" "8.19.1"
-    "@typescript-eslint/visitor-keys" "8.19.1"
+    "@typescript-eslint/types" "8.23.0"
+    "@typescript-eslint/visitor-keys" "8.23.0"
 
-"@typescript-eslint/type-utils@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz#23710ab52643c19f74601b3f4a076c98f4e159aa"
-  integrity sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==
+"@typescript-eslint/scope-manager@8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz#459cf0c49d410800b1a023b973c62d699b09bf4c"
+  integrity sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==
   dependencies:
-    "@typescript-eslint/typescript-estree" "8.19.1"
-    "@typescript-eslint/utils" "8.19.1"
+    "@typescript-eslint/types" "8.33.0"
+    "@typescript-eslint/visitor-keys" "8.33.0"
+
+"@typescript-eslint/tsconfig-utils@8.33.0", "@typescript-eslint/tsconfig-utils@^8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz#316adab038bbdc43e448781d5a816c2973eab73e"
+  integrity sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==
+
+"@typescript-eslint/type-utils@8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz#f06124b2d6db8a51b24990cb123c9543af93fef5"
+  integrity sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==
+  dependencies:
+    "@typescript-eslint/typescript-estree" "8.33.0"
+    "@typescript-eslint/utils" "8.33.0"
     debug "^4.3.4"
-    ts-api-utils "^2.0.0"
+    ts-api-utils "^2.1.0"
 
-"@typescript-eslint/types@8.16.0":
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.16.0.tgz#49c92ae1b57942458ab83d9ec7ccab3005e64737"
-  integrity sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==
+"@typescript-eslint/types@8.23.0":
+  version "8.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.23.0.tgz#3355f6bcc5ebab77ef6dcbbd1113ec0a683a234a"
+  integrity sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==
 
-"@typescript-eslint/types@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.19.1.tgz#015a991281754ed986f2e549263a1188d6ed0a8c"
-  integrity sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==
+"@typescript-eslint/types@8.33.0", "@typescript-eslint/types@^8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.0.tgz#02a7dbba611a8abf1ad2a9e00f72f7b94b5ab0ee"
+  integrity sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==
 
-"@typescript-eslint/typescript-estree@8.16.0":
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz#9d741e56e5b13469b5190e763432ce5551a9300c"
-  integrity sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==
+"@typescript-eslint/typescript-estree@8.23.0":
+  version "8.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz#f633ef08efa656e386bc44b045ffcf9537cc6924"
+  integrity sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==
   dependencies:
-    "@typescript-eslint/types" "8.16.0"
-    "@typescript-eslint/visitor-keys" "8.16.0"
+    "@typescript-eslint/types" "8.23.0"
+    "@typescript-eslint/visitor-keys" "8.23.0"
     debug "^4.3.4"
     fast-glob "^3.3.2"
     is-glob "^4.0.3"
     minimatch "^9.0.4"
     semver "^7.6.0"
-    ts-api-utils "^1.3.0"
+    ts-api-utils "^2.0.1"
 
-"@typescript-eslint/typescript-estree@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz#c1094bb00bc251ac76cf215569ca27236435036b"
-  integrity sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==
+"@typescript-eslint/typescript-estree@8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz#abcc1d3db75a8e9fd2e274ee8c4099fa2399abfd"
+  integrity sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==
   dependencies:
-    "@typescript-eslint/types" "8.19.1"
-    "@typescript-eslint/visitor-keys" "8.19.1"
+    "@typescript-eslint/project-service" "8.33.0"
+    "@typescript-eslint/tsconfig-utils" "8.33.0"
+    "@typescript-eslint/types" "8.33.0"
+    "@typescript-eslint/visitor-keys" "8.33.0"
     debug "^4.3.4"
     fast-glob "^3.3.2"
     is-glob "^4.0.3"
     minimatch "^9.0.4"
     semver "^7.6.0"
-    ts-api-utils "^2.0.0"
+    ts-api-utils "^2.1.0"
 
-"@typescript-eslint/utils@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.19.1.tgz#dd8eabd46b92bf61e573286e1c0ba6bd243a185b"
-  integrity sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==
+"@typescript-eslint/utils@8.33.0", "@typescript-eslint/utils@^8.32.1":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.0.tgz#574ad5edee371077b9e28ca6fb804f2440f447c1"
+  integrity sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==
   dependencies:
-    "@eslint-community/eslint-utils" "^4.4.0"
-    "@typescript-eslint/scope-manager" "8.19.1"
-    "@typescript-eslint/types" "8.19.1"
-    "@typescript-eslint/typescript-estree" "8.19.1"
+    "@eslint-community/eslint-utils" "^4.7.0"
+    "@typescript-eslint/scope-manager" "8.33.0"
+    "@typescript-eslint/types" "8.33.0"
+    "@typescript-eslint/typescript-estree" "8.33.0"
 
-"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.13.0":
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.16.0.tgz#c71264c437157feaa97842809836254a6fc833c3"
-  integrity sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==
+"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0":
+  version "8.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.23.0.tgz#b269cbdc77129fd6e0e600b168b5ef740a625554"
+  integrity sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
-    "@typescript-eslint/scope-manager" "8.16.0"
-    "@typescript-eslint/types" "8.16.0"
-    "@typescript-eslint/typescript-estree" "8.16.0"
+    "@typescript-eslint/scope-manager" "8.23.0"
+    "@typescript-eslint/types" "8.23.0"
+    "@typescript-eslint/typescript-estree" "8.23.0"
 
-"@typescript-eslint/visitor-keys@8.16.0":
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz#d5086afc060b01ff7a4ecab8d49d13d5a7b07705"
-  integrity sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==
+"@typescript-eslint/visitor-keys@8.23.0":
+  version "8.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz#40405fd26a61d23f5f4c2ed0f016a47074781df8"
+  integrity sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==
   dependencies:
-    "@typescript-eslint/types" "8.16.0"
+    "@typescript-eslint/types" "8.23.0"
     eslint-visitor-keys "^4.2.0"
 
-"@typescript-eslint/visitor-keys@8.19.1":
-  version "8.19.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz#fce54d7cfa5351a92387d6c0c5be598caee072e0"
-  integrity sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==
+"@typescript-eslint/visitor-keys@8.33.0":
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz#fbae16fd3594531f8cad95d421125d634e9974fe"
+  integrity sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==
   dependencies:
-    "@typescript-eslint/types" "8.19.1"
+    "@typescript-eslint/types" "8.33.0"
     eslint-visitor-keys "^4.2.0"
 
 "@ungap/structured-clone@^1.2.0":
@@ -3508,15 +3831,15 @@
   resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
   integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
 
-"@vector-im/compound-design-tokens@^2.1.0":
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.3.tgz#8205ffb455a09d71a02d838f3dbb8503c4e6ec27"
-  integrity sha512-U4UF7MVguENf0lQnkU2a9p/3llTsLXzbzmFFOxi0h6ny2igNxZj/kROP/jXTxxV9xD4TNn3z098Bos4J/qJpBA==
+"@vector-im/compound-design-tokens@^4.0.0":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-4.0.2.tgz#27363d26446eaa21880ab126fa51fec112e6fd86"
+  integrity sha512-y13bhPyJ5OzbGRl21F6+Y2adrjyK+mu67yKTx+o8MfmIpJzMSn4KkHZtcujMquWSh0e5ZAufsnk4VYvxbSpr1A==
 
-"@vector-im/compound-web@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.5.0.tgz#1547af5f0ee27b94f79ab11eee006059f3d09707"
-  integrity sha512-Xhef8H5WrRmPuanzRBs8rnl+hwbcQnC7nKSCupUczAQ5hjlieBx4vcQYQ/nMkrs4rMGjgfFtR3E18wT5LlML/A==
+"@vector-im/compound-web@^7.11.0":
+  version "7.11.0"
+  resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.11.0.tgz#b7c466e64089320b41f8eaf6f2b30950e9692ca2"
+  integrity sha512-lRxXUOQJHdBswhykpNs/J/cBW4fPY1qbwyDexlWxX5zCVAYiuMCWo2tI+Y7/SK4tNbDr7nwoTDRh4H9CO1L5LQ==
   dependencies:
     "@floating-ui/react" "^0.27.0"
     "@radix-ui/react-context-menu" "^2.2.1"
@@ -3526,18 +3849,17 @@
     "@radix-ui/react-separator" "^1.1.0"
     "@radix-ui/react-slot" "^1.1.0"
     classnames "^2.5.1"
-    ts-xor "^1.3.0"
     vaul "^1.0.0"
 
-"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.0-af862ffd231dc0a6b8d6f2cb3601e68456c0ff24-integrity/node_modules/bindings/wysiwyg-wasm":
+"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm":
   version "0.0.0"
 
-"@vector-im/matrix-wysiwyg@2.38.0":
-  version "2.38.0"
-  resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.0.tgz#af862ffd231dc0a6b8d6f2cb3601e68456c0ff24"
-  integrity sha512-cMEVicFYVzFxuSyWON0aVGjAJMcgJZ+LxuLTEp8EGuu8cRacuh0RN5rapb11YVZygzFvE7X1cMedJ/fKd5vRLA==
+"@vector-im/matrix-wysiwyg@2.38.3":
+  version "2.38.3"
+  resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.3.tgz#cc54d8b3e9472bcd8e622126ba364ee31952cd8a"
+  integrity sha512-fqo8P55Vc/t0vxpFar9RDJN5gKEjJmzrLo+O4piDbFda6VrRoqrWAtiu0Au0g6B4hRDPKIuFupk8v9Ja7q8Hvg==
   dependencies:
-    "@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.0-af862ffd231dc0a6b8d6f2cb3601e68456c0ff24-integrity/node_modules/bindings/wysiwyg-wasm"
+    "@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.3-cc54d8b3e9472bcd8e622126ba364ee31952cd8a-integrity/node_modules/bindings/wysiwyg-wasm"
 
 "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1":
   version "1.14.1"
@@ -3685,6 +4007,11 @@
   resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
+"@yarnpkg/lockfile@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+  integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
 "@zxcvbn-ts/core@^3.0.4":
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-3.0.4.tgz#c5bde72235eb6c273cec78b672bb47c0d7045cad"
@@ -3714,7 +4041,15 @@ abort-controller@^3.0.0:
   dependencies:
     event-target-shim "^5.0.0"
 
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+accepts@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
+  integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
+  dependencies:
+    mime-types "^3.0.0"
+    negotiator "^1.0.0"
+
+accepts@~1.3.4, accepts@~1.3.8:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
   integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -3742,15 +4077,15 @@ acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1:
   dependencies:
     acorn "^8.11.0"
 
-acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0:
+acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.9.0:
   version "8.13.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3"
   integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==
 
-acorn@^8.14.0, acorn@^8.8.1, acorn@^8.8.2:
-  version "8.14.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
-  integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
+acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2:
+  version "8.14.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
+  integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
 
 agent-base@6:
   version "6.0.2"
@@ -3759,14 +4094,6 @@ agent-base@6:
   dependencies:
     debug "4"
 
-aggregate-error@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
-  integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
-  dependencies:
-    clean-stack "^2.0.0"
-    indent-string "^4.0.0"
-
 ajv-formats@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
@@ -3934,13 +4261,13 @@ aria-query@^5.0.0, aria-query@^5.3.2:
   resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
   integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
 
-array-buffer-byte-length@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f"
-  integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==
+array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b"
+  integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
   dependencies:
-    call-bind "^1.0.5"
-    is-array-buffer "^3.0.4"
+    call-bound "^1.0.3"
+    is-array-buffer "^3.0.5"
 
 array-flatten@1.1.1:
   version "1.1.1"
@@ -3988,7 +4315,17 @@ array.prototype.findlastindex@^1.2.5:
     es-object-atoms "^1.0.0"
     es-shim-unscopables "^1.0.2"
 
-array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2:
+array.prototype.flat@^1.3.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5"
+  integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==
+  dependencies:
+    call-bind "^1.0.8"
+    define-properties "^1.2.1"
+    es-abstract "^1.23.5"
+    es-shim-unscopables "^1.0.2"
+
+array.prototype.flat@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18"
   integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==
@@ -4008,6 +4345,16 @@ array.prototype.flatmap@^1.3.2:
     es-abstract "^1.22.1"
     es-shim-unscopables "^1.0.0"
 
+array.prototype.flatmap@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b"
+  integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==
+  dependencies:
+    call-bind "^1.0.8"
+    define-properties "^1.2.1"
+    es-abstract "^1.23.5"
+    es-shim-unscopables "^1.0.2"
+
 array.prototype.tosorted@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc"
@@ -4019,19 +4366,18 @@ array.prototype.tosorted@^1.1.4:
     es-errors "^1.3.0"
     es-shim-unscopables "^1.0.2"
 
-arraybuffer.prototype.slice@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6"
-  integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==
+arraybuffer.prototype.slice@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c"
+  integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==
   dependencies:
     array-buffer-byte-length "^1.0.1"
-    call-bind "^1.0.5"
+    call-bind "^1.0.8"
     define-properties "^1.2.1"
-    es-abstract "^1.22.3"
-    es-errors "^1.2.1"
-    get-intrinsic "^1.2.3"
+    es-abstract "^1.23.5"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.6"
     is-array-buffer "^3.0.4"
-    is-shared-array-buffer "^1.0.2"
 
 asn1@^0.2.6:
   version "0.2.6"
@@ -4059,6 +4405,11 @@ astral-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
   integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
 
+async-function@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b"
+  integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
+
 async-lock@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f"
@@ -4074,6 +4425,11 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
 
+at-least-node@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+  integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
 autoprefixer@^10.4.19:
   version "10.4.20"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
@@ -4098,11 +4454,25 @@ await-lock@^2.1.0:
   resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
   integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
 
-axe-core@^4.10.0, axe-core@~4.10.2:
+axe-core@^4.10.0:
   version "4.10.2"
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
   integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
 
+axe-core@~4.10.2:
+  version "4.10.3"
+  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc"
+  integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==
+
+axios@^1.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901"
+  integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==
+  dependencies:
+    follow-redirects "^1.15.6"
+    form-data "^4.0.0"
+    proxy-from-env "^1.1.0"
+
 axobject-query@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
@@ -4126,13 +4496,12 @@ babel-jest@^29.0.0, babel-jest@^29.7.0:
     graceful-fs "^4.2.9"
     slash "^3.0.0"
 
-babel-loader@^9.0.0:
-  version "9.2.1"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b"
-  integrity sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==
+babel-loader@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-10.0.0.tgz#b9743714c0e1e084b3e4adef3cd5faee33089977"
+  integrity sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==
   dependencies:
-    find-cache-dir "^4.0.0"
-    schema-utils "^4.0.0"
+    find-up "^5.0.0"
 
 babel-plugin-istanbul@^6.1.1:
   version "6.1.1"
@@ -4161,28 +4530,28 @@ babel-plugin-jsx-remove-data-test-id@^3.0.0:
   integrity sha512-E4uM/LIUizjy2Z5tVAfa8pSXsYgoKWJ97kzuEMfsIxSLSNDWsAhgFVPkgNuakViX5dkNjw1DKIi0VpWP6djqbw==
 
 babel-plugin-polyfill-corejs2@^0.4.10:
-  version "0.4.12"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz#ca55bbec8ab0edeeef3d7b8ffd75322e210879a9"
-  integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==
+  version "0.4.13"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz#7d445f0e0607ebc8fb6b01d7e8fb02069b91dd8b"
+  integrity sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==
   dependencies:
     "@babel/compat-data" "^7.22.6"
-    "@babel/helper-define-polyfill-provider" "^0.6.3"
+    "@babel/helper-define-polyfill-provider" "^0.6.4"
     semver "^6.3.1"
 
-babel-plugin-polyfill-corejs3@^0.10.6:
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7"
-  integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==
+babel-plugin-polyfill-corejs3@^0.11.0:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz#4e4e182f1bb37c7ba62e2af81d8dd09df31344f6"
+  integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==
   dependencies:
-    "@babel/helper-define-polyfill-provider" "^0.6.2"
-    core-js-compat "^3.38.0"
+    "@babel/helper-define-polyfill-provider" "^0.6.3"
+    core-js-compat "^3.40.0"
 
 babel-plugin-polyfill-regenerator@^0.6.1:
-  version "0.6.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8"
-  integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz#428c615d3c177292a22b4f93ed99e358d7906a9b"
+  integrity sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==
   dependencies:
-    "@babel/helper-define-polyfill-provider" "^0.6.3"
+    "@babel/helper-define-polyfill-provider" "^0.6.4"
 
 babel-preset-current-node-syntax@^1.0.0:
   version "1.1.0"
@@ -4223,43 +4592,43 @@ balanced-match@^2.0.0:
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
   integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
 
-bare-events@^2.0.0, bare-events@^2.2.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc"
-  integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==
+bare-events@^2.2.0, bare-events@^2.5.4:
+  version "2.5.4"
+  resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745"
+  integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==
 
-bare-fs@^2.1.1:
-  version "2.3.5"
-  resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.5.tgz#05daa8e8206aeb46d13c2fe25a2cd3797b0d284a"
-  integrity sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==
+bare-fs@^4.0.1:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.1.5.tgz#1d06c076e68cc8bf97010d29af9e3ac3808cdcf7"
+  integrity sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==
   dependencies:
-    bare-events "^2.0.0"
-    bare-path "^2.0.0"
-    bare-stream "^2.0.0"
+    bare-events "^2.5.4"
+    bare-path "^3.0.0"
+    bare-stream "^2.6.4"
 
-bare-os@^2.1.0:
-  version "2.4.4"
-  resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.4.4.tgz#01243392eb0a6e947177bb7c8a45123d45c9b1a9"
-  integrity sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==
+bare-os@^3.0.1:
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.6.1.tgz#9921f6f59edbe81afa9f56910658422c0f4858d4"
+  integrity sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==
 
-bare-path@^2.0.0, bare-path@^2.1.0:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.3.tgz#594104c829ef660e43b5589ec8daef7df6cedb3e"
-  integrity sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==
+bare-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.0.0.tgz#b59d18130ba52a6af9276db3e96a2e3d3ea52178"
+  integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==
   dependencies:
-    bare-os "^2.1.0"
+    bare-os "^3.0.1"
 
-bare-stream@^2.0.0:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.1.tgz#b3b9874fab05b662c9aea2706a12fb0698c46836"
-  integrity sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==
+bare-stream@^2.6.4:
+  version "2.6.5"
+  resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.5.tgz#bba8e879674c4c27f7e27805df005c15d7a2ca07"
+  integrity sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==
   dependencies:
     streamx "^2.21.0"
 
 base-x@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b"
-  integrity sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.1.tgz#16bf35254be1df8aca15e36b7c1dda74b2aa6b03"
+  integrity sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==
 
 base64-arraybuffer@^1.0.2:
   version "1.0.2"
@@ -4344,10 +4713,25 @@ body-parser@1.20.3:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+body-parser@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa"
+  integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==
+  dependencies:
+    bytes "^3.1.2"
+    content-type "^1.0.5"
+    debug "^4.4.0"
+    http-errors "^2.0.0"
+    iconv-lite "^0.6.3"
+    on-finished "^2.4.1"
+    qs "^6.14.0"
+    raw-body "^3.0.0"
+    type-is "^2.0.0"
+
 bonjour-service@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02"
-  integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722"
+  integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==
   dependencies:
     fast-deep-equal "^3.1.3"
     multicast-dns "^7.2.5"
@@ -4379,25 +4763,15 @@ braces@^3.0.3, braces@~3.0.2:
   dependencies:
     fill-range "^7.1.1"
 
-browserslist@^4.0.0, browserslist@^4.23.2, browserslist@^4.24.0, browserslist@^4.24.2:
-  version "4.24.2"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580"
-  integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==
-  dependencies:
-    caniuse-lite "^1.0.30001669"
-    electron-to-chromium "^1.5.41"
-    node-releases "^2.0.18"
-    update-browserslist-db "^1.1.1"
-
-browserslist@^4.23.1, browserslist@^4.23.3:
-  version "4.24.4"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
-  integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3, browserslist@^4.24.4:
+  version "4.24.5"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b"
+  integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==
   dependencies:
-    caniuse-lite "^1.0.30001688"
-    electron-to-chromium "^1.5.73"
+    caniuse-lite "^1.0.30001716"
+    electron-to-chromium "^1.5.149"
     node-releases "^2.0.19"
-    update-browserslist-db "^1.1.1"
+    update-browserslist-db "^1.1.3"
 
 bs58@^6.0.0:
   version "6.0.0"
@@ -4461,16 +4835,19 @@ byline@^5.0.0:
   resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
   integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==
 
-bytes@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
-  integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
-
-bytes@3.1.2:
+bytes@3.1.2, bytes@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
   integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
 
+cacheable@^1.8.9:
+  version "1.8.10"
+  resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-1.8.10.tgz#c1b36260240d812912a6d3503da3a6e00166f75c"
+  integrity sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==
+  dependencies:
+    hookified "^1.8.1"
+    keyv "^5.3.2"
+
 call-bind-apply-helpers@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
@@ -4479,18 +4856,15 @@ call-bind-apply-helpers@^1.0.0:
     es-errors "^1.3.0"
     function-bind "^1.1.2"
 
-call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
-  integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
+  integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
   dependencies:
-    es-define-property "^1.0.0"
     es-errors "^1.3.0"
     function-bind "^1.1.2"
-    get-intrinsic "^1.2.4"
-    set-function-length "^1.2.1"
 
-call-bind@^1.0.7:
+call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
   integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
@@ -4500,6 +4874,22 @@ call-bind@^1.0.7:
     get-intrinsic "^1.2.4"
     set-function-length "^1.2.2"
 
+call-bound@^1.0.2:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+  integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+  dependencies:
+    call-bind-apply-helpers "^1.0.2"
+    get-intrinsic "^1.3.0"
+
+call-bound@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681"
+  integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==
+  dependencies:
+    call-bind-apply-helpers "^1.0.1"
+    get-intrinsic "^1.2.6"
+
 callsites@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -4538,10 +4928,10 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@1.0.30001690, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669, caniuse-lite@^1.0.30001688:
-  version "1.0.30001690"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8"
-  integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==
+caniuse-lite@1.0.30001718, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001716:
+  version "1.0.30001718"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz#dae13a9c80d517c30c6197515a96131c194d8f82"
+  integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
 
 chalk@5.2.0:
   version "5.2.0"
@@ -4573,10 +4963,10 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@~5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
-  integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
+chalk@^5.4.1:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
+  integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
 
 char-regex@^1.0.2:
   version "1.0.2"
@@ -4599,9 +4989,9 @@ chokidar@^3.5.3, chokidar@^3.6.0:
     fsevents "~2.3.2"
 
 chokidar@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
-  integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
+  integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
   dependencies:
     readdirp "^4.0.1"
 
@@ -4615,7 +5005,7 @@ chrome-trace-event@^1.0.2:
   resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
   integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
 
-ci-info@^3.2.0:
+ci-info@^3.2.0, ci-info@^3.7.0:
   version "3.9.0"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
   integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
@@ -4649,11 +5039,6 @@ clean-regexp@^1.0.0:
   dependencies:
     escape-string-regexp "^1.0.5"
 
-clean-stack@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
-  integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
-
 cli-cursor@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38"
@@ -4696,11 +5081,6 @@ clone-deep@^4.0.1:
     kind-of "^6.0.2"
     shallow-clone "^3.0.0"
 
-clone@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
-  integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
-
 clsx@^1.0.4:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
@@ -4757,21 +5137,21 @@ combined-stream@^1.0.8:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^12.1.0, commander@~12.1.0:
+commander@^12.1.0:
   version "12.1.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
   integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
 
+commander@^13.1.0:
+  version "13.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46"
+  integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==
+
 commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
 
-commander@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
-  integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
-
 commander@^7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
@@ -4782,11 +5162,6 @@ commander@^8.3.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
   integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
 
-common-path-prefix@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
-  integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==
-
 commonmark@^0.31.0:
   version "0.31.2"
   resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.31.2.tgz#9d8d5439c82c9a235154d858a53e1a7965d573a5"
@@ -4807,7 +5182,7 @@ compress-commons@^6.0.2:
     normalize-path "^3.0.0"
     readable-stream "^4.0.0"
 
-compressible@~2.0.16:
+compressible@~2.0.18:
   version "2.0.18"
   resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
   integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
@@ -4815,16 +5190,16 @@ compressible@~2.0.16:
     mime-db ">= 1.43.0 < 2"
 
 compression@^1.7.4:
-  version "1.7.4"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
-  integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.0.tgz#09420efc96e11a0f44f3a558de59e321364180f7"
+  integrity sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==
   dependencies:
-    accepts "~1.3.5"
-    bytes "3.0.0"
-    compressible "~2.0.16"
+    bytes "3.1.2"
+    compressible "~2.0.18"
     debug "2.6.9"
+    negotiator "~0.6.4"
     on-headers "~1.0.2"
-    safe-buffer "5.1.2"
+    safe-buffer "5.2.1"
     vary "~1.1.2"
 
 concat-map@0.0.1:
@@ -4833,9 +5208,9 @@ concat-map@0.0.1:
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
 concurrently@^9.0.0:
-  version "9.1.0"
-  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.1.0.tgz#8da6d609f4321752912dab9be8710232ac496aa0"
-  integrity sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==
+  version "9.1.2"
+  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.1.2.tgz#22d9109296961eaee773e12bfb1ce9a66bc9836c"
+  integrity sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==
   dependencies:
     chalk "^4.1.2"
     lodash "^4.17.21"
@@ -4857,7 +5232,14 @@ content-disposition@0.5.4:
   dependencies:
     safe-buffer "5.2.1"
 
-content-type@^1.0.4, content-type@~1.0.4, content-type@~1.0.5:
+content-disposition@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2"
+  integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==
+  dependencies:
+    safe-buffer "5.2.1"
+
+content-type@^1.0.4, content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
   integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
@@ -4872,34 +5254,50 @@ cookie-signature@1.0.6:
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
   integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
 
+cookie-signature@^1.2.1:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
+  integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
+
 cookie@0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
   integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
 
-copy-webpack-plugin@^12.0.0:
-  version "12.0.2"
-  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz#935e57b8e6183c82f95bd937df658a59f6a2da28"
-  integrity sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==
+cookie@^0.7.1:
+  version "0.7.2"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
+  integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
+
+copy-webpack-plugin@^13.0.0:
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz#793342576eed76fdbc7936b873eae17aa7a7d9a3"
+  integrity sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==
   dependencies:
-    fast-glob "^3.3.2"
     glob-parent "^6.0.1"
-    globby "^14.0.0"
     normalize-path "^3.0.0"
     schema-utils "^4.2.0"
     serialize-javascript "^6.0.2"
+    tinyglobby "^0.2.12"
 
-core-js-compat@^3.38.0, core-js-compat@^3.38.1:
-  version "3.39.0"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.39.0.tgz#b12dccb495f2601dc860bdbe7b4e3ffa8ba63f61"
-  integrity sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==
+core-js-compat@^3.38.1:
+  version "3.40.0"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38"
+  integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
   dependencies:
-    browserslist "^4.24.2"
+    browserslist "^4.24.3"
+
+core-js-compat@^3.40.0:
+  version "3.42.0"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.42.0.tgz#ce19c29706ee5806e26d3cb3c542d4cfc0ed51bb"
+  integrity sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==
+  dependencies:
+    browserslist "^4.24.4"
 
 core-js@^3.0.0, core-js@^3.38.1:
-  version "3.39.0"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83"
-  integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==
+  version "3.42.0"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.42.0.tgz#edbe91f78ac8cfb6df8d997e74d368a68082fe37"
+  integrity sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==
 
 core-util-is@~1.0.0:
   version "1.0.3"
@@ -4982,11 +5380,11 @@ create-require@^1.1.0:
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
 cronstrue@^2.41.0:
-  version "2.52.0"
-  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.52.0.tgz#00af1a8dcf76a1dece149e4416db823105b28cdb"
-  integrity sha512-NKgHbWkSZXJUcaBHSsyzC8eegD6bBd4O0oCI6XMIJ+y4Bq3v4w7sY3wfWoKPuVlq9pQHRB6od0lmKpIqi8TlKA==
+  version "2.61.0"
+  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.61.0.tgz#97c79c77045c052afb44cb9f5f8eaf54398094f2"
+  integrity sha512-ootN5bvXbIQI9rW94+QsXN5eROtXWwew6NkdGxIRpS/UFWRggL0G5Al7a9GTBFEsuvVhJ2K3CntIIVt7L2ILhA==
 
-cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.2:
   version "7.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82"
   integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==
@@ -4995,6 +5393,15 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+cross-spawn@^7.0.3, cross-spawn@^7.0.6:
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
+  integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+  dependencies:
+    path-key "^3.1.0"
+    shebang-command "^2.0.0"
+    which "^2.0.1"
+
 css-blank-pseudo@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz#32020bff20a209a53ad71b8675852b49e8d57e46"
@@ -5043,14 +5450,14 @@ css-loader@^7.0.0:
     semver "^7.5.4"
 
 css-minimizer-webpack-plugin@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.0.tgz#b77a3d2f7c0fd02d3ac250dcc2f79065363f3cd3"
-  integrity sha512-niy66jxsQHqO+EYbhPuIhqRQ1mNcNVUHrMnkzzir9kFOERJUaQDDRhh7dKDz33kBpkWMF9M8Vx0QlDbc5AHOsw==
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.2.tgz#aa1b01c6033f5b2f86ddb60c1f5bddd012b50cac"
+  integrity sha512-nBRWZtI77PBZQgcXMNqiIXVshiQOVLGSf2qX/WZfG8IQfMbeHUMXaBWQmiiSTmPJUflQxHjZjzAmuyO7tpL2Jg==
   dependencies:
     "@jridgewell/trace-mapping" "^0.3.25"
-    cssnano "^7.0.1"
+    cssnano "^7.0.4"
     jest-worker "^29.7.0"
-    postcss "^8.4.38"
+    postcss "^8.4.40"
     schema-utils "^4.2.0"
     serialize-javascript "^6.0.2"
 
@@ -5089,7 +5496,7 @@ css-tree@^2.3.1:
     mdn-data "2.0.30"
     source-map-js "^1.0.1"
 
-css-tree@^3.0.0, css-tree@^3.0.1:
+css-tree@^3.0.0, css-tree@^3.0.1, css-tree@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.1.0.tgz#7aabc035f4e66b5c86f54570d55e05b1346eb0fd"
   integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==
@@ -5171,7 +5578,7 @@ cssnano-utils@^5.0.0:
   resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-5.0.0.tgz#b53a0343dd5d21012911882db6ae7d2eae0e3687"
   integrity sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==
 
-cssnano@^7.0.1:
+cssnano@^7.0.4:
   version "7.0.6"
   resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-7.0.6.tgz#63d54fd42bc017f6aaed69e47d9aaef85b7850ec"
   integrity sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==
@@ -5227,30 +5634,30 @@ data-urls@^3.0.2:
     whatwg-mimetype "^3.0.0"
     whatwg-url "^11.0.0"
 
-data-view-buffer@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
-  integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==
+data-view-buffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570"
+  integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==
   dependencies:
-    call-bind "^1.0.6"
+    call-bound "^1.0.3"
     es-errors "^1.3.0"
-    is-data-view "^1.0.1"
+    is-data-view "^1.0.2"
 
-data-view-byte-length@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2"
-  integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==
+data-view-byte-length@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735"
+  integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
     es-errors "^1.3.0"
-    is-data-view "^1.0.1"
+    is-data-view "^1.0.2"
 
-data-view-byte-offset@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a"
-  integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==
+data-view-byte-offset@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191"
+  integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==
   dependencies:
-    call-bind "^1.0.6"
+    call-bound "^1.0.2"
     es-errors "^1.3.0"
     is-data-view "^1.0.1"
 
@@ -5271,10 +5678,10 @@ debug@2.6.9:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.5:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
-  integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5, debug@^4.4.1:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
+  integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
   dependencies:
     ms "^2.1.3"
 
@@ -5285,23 +5692,35 @@ debug@^3.2.7:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@~4.3.6:
+debug@^4.3.2:
   version "4.3.7"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
   integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
   dependencies:
     ms "^2.1.3"
 
+debug@^4.3.7, debug@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+  integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+  dependencies:
+    ms "^2.1.3"
+
 decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
 
-decimal.js@10, decimal.js@^10.4.2:
+decimal.js@^10.4.2:
   version "10.4.3"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
   integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
 
+decimal.js@^10.4.3:
+  version "10.5.0"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22"
+  integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==
+
 dedent@^1.0.0:
   version "1.5.3"
   resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a"
@@ -5330,13 +5749,6 @@ default-browser@^5.2.1:
     bundle-name "^4.1.0"
     default-browser-id "^5.0.0"
 
-defaults@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
-  integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==
-  dependencies:
-    clone "^1.0.2"
-
 define-data-property@^1.0.1, define-data-property@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
@@ -5365,7 +5777,7 @@ delayed-stream@~1.0.0:
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
 
-depd@2.0.0:
+depd@2.0.0, depd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
   integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -5401,9 +5813,9 @@ detect-node@^2.0.4:
   integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
 
 diff-dom@^5.0.0:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-5.1.4.tgz#20d0f6e39558f59dae0e2943b69360fc15e371cb"
-  integrity sha512-TSEaVdVGictY1KHg7VpVw2nuM02YKC9C8/qBkGiCnkiAybVbu1zQTMj2/dnVLRO7Z62UsqzHGpXweiOj5/jaZg==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-5.2.0.tgz#bc6756e0dca58bc7565579daf566ce6b259821bf"
+  integrity sha512-+FEFvDJljxPBz9ddkxCpn7I/5SFFYYfvqcS63zE+Aw+3TgTyHAI1RGXEBGLfrkbVQTYCSpvc0nxNNMyNBA60tQ==
 
 diff-match-patch@^1.0.5:
   version "1.0.5"
@@ -5439,31 +5851,35 @@ dns-packet@^5.2.2:
   dependencies:
     "@leichtgewicht/ip-codec" "^2.0.1"
 
-docker-compose@^0.24.8:
-  version "0.24.8"
-  resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.24.8.tgz#6c125e6b9e04cf68ced47e2596ef2bb93ee9694e"
-  integrity sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==
+docker-compose@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-1.2.0.tgz#e4c30afdd1a111b47e87d97754baa304302586fc"
+  integrity sha512-wIU1eHk3Op7dFgELRdmOYlPYS4gP8HhH1ZmZa13QZF59y0fblzFDFmKPhyc05phCy2hze9OEvNZAsoljrs+72w==
   dependencies:
     yaml "^2.2.2"
 
-docker-modem@^3.0.0:
-  version "3.0.8"
-  resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a"
-  integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==
+docker-modem@^5.0.6:
+  version "5.0.6"
+  resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-5.0.6.tgz#cbe9d86a1fe66d7a072ac7fb99a9fc390a3e8b9a"
+  integrity sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==
   dependencies:
     debug "^4.1.1"
     readable-stream "^3.5.0"
     split-ca "^1.0.1"
-    ssh2 "^1.11.0"
+    ssh2 "^1.15.0"
 
-dockerode@^3.3.5:
-  version "3.3.5"
-  resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
-  integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
+dockerode@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.6.tgz#e53978605346afaaed940a72e24d4e8490d52437"
+  integrity sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w==
   dependencies:
     "@balena/dockerignore" "^1.0.2"
-    docker-modem "^3.0.0"
-    tar-fs "~2.0.1"
+    "@grpc/grpc-js" "^1.11.1"
+    "@grpc/proto-loader" "^0.7.13"
+    docker-modem "^5.0.6"
+    protobufjs "^7.3.2"
+    tar-fs "~2.1.2"
+    uuid "^10.0.0"
 
 doctrine@^2.1.0:
   version "2.1.0"
@@ -5534,6 +5950,13 @@ domexception@^4.0.0:
   dependencies:
     webidl-conversions "^7.0.0"
 
+domhandler@5.0.3, domhandler@^5.0.2, domhandler@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
 domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
@@ -5541,13 +5964,6 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   dependencies:
     domelementtype "^2.2.0"
 
-domhandler@^5.0.2, domhandler@^5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
-  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
-  dependencies:
-    domelementtype "^2.3.0"
-
 domutils@^2.5.2, domutils@^2.8.0:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
@@ -5557,10 +5973,10 @@ domutils@^2.5.2, domutils@^2.8.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
-domutils@^3.0.1:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
-  integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+domutils@^3.0.1, domutils@^3.2.1, domutils@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
+  integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
   dependencies:
     dom-serializer "^2.0.0"
     domelementtype "^2.3.0"
@@ -5575,16 +5991,16 @@ dot-case@^3.0.4:
     tslib "^2.0.3"
 
 dotenv@^16.0.2, dotenv@^16.3.1:
-  version "16.4.7"
-  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
-  integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
+  version "16.5.0"
+  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692"
+  integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==
 
-dunder-proto@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80"
-  integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==
+dunder-proto@^1.0.0, dunder-proto@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
+  integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
   dependencies:
-    call-bind-apply-helpers "^1.0.0"
+    call-bind-apply-helpers "^1.0.1"
     es-errors "^1.3.0"
     gopd "^1.2.0"
 
@@ -5603,15 +6019,6 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
-easy-table@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/easy-table/-/easy-table-1.2.0.tgz#ba9225d7138fee307bfd4f0b5bc3c04bdc7c54eb"
-  integrity sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==
-  dependencies:
-    ansi-regex "^5.0.1"
-  optionalDependencies:
-    wcwidth "^1.0.1"
-
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -5624,10 +6031,10 @@ ejs@^3.1.8:
   dependencies:
     jake "^10.8.5"
 
-electron-to-chromium@^1.5.41, electron-to-chromium@^1.5.73:
-  version "1.5.79"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz#4424f23f319db7a653cf9ee76102e4ac283e6b3e"
-  integrity sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==
+electron-to-chromium@^1.5.149:
+  version "1.5.159"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz#b909c4a5dbd00674f18419199f71c945a199effe"
+  integrity sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA==
 
 emittery@^0.13.1:
   version "0.13.1"
@@ -5669,16 +6076,16 @@ emojis-list@^3.0.0:
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
   integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
 
+encodeurl@^2.0.0, encodeurl@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+  integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
 encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
-encodeurl@~2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
-  integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
-
 end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -5687,9 +6094,9 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
     once "^1.4.0"
 
 enhanced-resolve@^5.17.1:
-  version "5.17.1"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
-  integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
+  version "5.18.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf"
+  integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==
   dependencies:
     graceful-fs "^4.2.4"
     tapable "^2.2.0"
@@ -5704,6 +6111,11 @@ entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
   integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
 
+entities@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.0.tgz#09c9e29cb79b0a6459a9b9db9efb418ac5bb8e51"
+  integrity sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==
+
 entities@~3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
@@ -5731,109 +6143,116 @@ error-ex@^1.3.1:
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5:
-  version "1.23.5"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.5.tgz#f4599a4946d57ed467515ed10e4f157289cd52fb"
-  integrity sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==
+es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
+  version "1.23.9"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606"
+  integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==
   dependencies:
-    array-buffer-byte-length "^1.0.1"
-    arraybuffer.prototype.slice "^1.0.3"
+    array-buffer-byte-length "^1.0.2"
+    arraybuffer.prototype.slice "^1.0.4"
     available-typed-arrays "^1.0.7"
-    call-bind "^1.0.7"
-    data-view-buffer "^1.0.1"
-    data-view-byte-length "^1.0.1"
-    data-view-byte-offset "^1.0.0"
-    es-define-property "^1.0.0"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
+    data-view-buffer "^1.0.2"
+    data-view-byte-length "^1.0.2"
+    data-view-byte-offset "^1.0.1"
+    es-define-property "^1.0.1"
     es-errors "^1.3.0"
     es-object-atoms "^1.0.0"
-    es-set-tostringtag "^2.0.3"
-    es-to-primitive "^1.2.1"
-    function.prototype.name "^1.1.6"
-    get-intrinsic "^1.2.4"
-    get-symbol-description "^1.0.2"
+    es-set-tostringtag "^2.1.0"
+    es-to-primitive "^1.3.0"
+    function.prototype.name "^1.1.8"
+    get-intrinsic "^1.2.7"
+    get-proto "^1.0.0"
+    get-symbol-description "^1.1.0"
     globalthis "^1.0.4"
-    gopd "^1.0.1"
+    gopd "^1.2.0"
     has-property-descriptors "^1.0.2"
-    has-proto "^1.0.3"
-    has-symbols "^1.0.3"
+    has-proto "^1.2.0"
+    has-symbols "^1.1.0"
     hasown "^2.0.2"
-    internal-slot "^1.0.7"
-    is-array-buffer "^3.0.4"
+    internal-slot "^1.1.0"
+    is-array-buffer "^3.0.5"
     is-callable "^1.2.7"
-    is-data-view "^1.0.1"
-    is-negative-zero "^2.0.3"
-    is-regex "^1.1.4"
-    is-shared-array-buffer "^1.0.3"
-    is-string "^1.0.7"
-    is-typed-array "^1.1.13"
-    is-weakref "^1.0.2"
+    is-data-view "^1.0.2"
+    is-regex "^1.2.1"
+    is-shared-array-buffer "^1.0.4"
+    is-string "^1.1.1"
+    is-typed-array "^1.1.15"
+    is-weakref "^1.1.0"
+    math-intrinsics "^1.1.0"
     object-inspect "^1.13.3"
     object-keys "^1.1.1"
-    object.assign "^4.1.5"
+    object.assign "^4.1.7"
+    own-keys "^1.0.1"
     regexp.prototype.flags "^1.5.3"
-    safe-array-concat "^1.1.2"
-    safe-regex-test "^1.0.3"
-    string.prototype.trim "^1.2.9"
-    string.prototype.trimend "^1.0.8"
+    safe-array-concat "^1.1.3"
+    safe-push-apply "^1.0.0"
+    safe-regex-test "^1.1.0"
+    set-proto "^1.0.0"
+    string.prototype.trim "^1.2.10"
+    string.prototype.trimend "^1.0.9"
     string.prototype.trimstart "^1.0.8"
-    typed-array-buffer "^1.0.2"
-    typed-array-byte-length "^1.0.1"
-    typed-array-byte-offset "^1.0.2"
-    typed-array-length "^1.0.6"
-    unbox-primitive "^1.0.2"
-    which-typed-array "^1.1.15"
+    typed-array-buffer "^1.0.3"
+    typed-array-byte-length "^1.0.3"
+    typed-array-byte-offset "^1.0.4"
+    typed-array-length "^1.0.7"
+    unbox-primitive "^1.1.0"
+    which-typed-array "^1.1.18"
 
 es-define-property@^1.0.0, es-define-property@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
   integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
 
-es-errors@^1.2.1, es-errors@^1.3.0:
+es-errors@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
 
-es-iterator-helpers@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz#2f1a3ab998b30cb2d10b195b587c6d9ebdebf152"
-  integrity sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==
+es-iterator-helpers@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75"
+  integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
     define-properties "^1.2.1"
-    es-abstract "^1.23.3"
+    es-abstract "^1.23.6"
     es-errors "^1.3.0"
     es-set-tostringtag "^2.0.3"
     function-bind "^1.1.2"
-    get-intrinsic "^1.2.4"
+    get-intrinsic "^1.2.6"
     globalthis "^1.0.4"
-    gopd "^1.0.1"
+    gopd "^1.2.0"
     has-property-descriptors "^1.0.2"
-    has-proto "^1.0.3"
-    has-symbols "^1.0.3"
-    internal-slot "^1.0.7"
-    iterator.prototype "^1.1.3"
-    safe-array-concat "^1.1.2"
+    has-proto "^1.2.0"
+    has-symbols "^1.1.0"
+    internal-slot "^1.1.0"
+    iterator.prototype "^1.1.4"
+    safe-array-concat "^1.1.3"
 
 es-module-lexer@^1.2.1:
-  version "1.5.4"
-  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78"
-  integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a"
+  integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
 
-es-object-atoms@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
-  integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
+  integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
   dependencies:
     es-errors "^1.3.0"
 
-es-set-tostringtag@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777"
-  integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==
+es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
+  integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
   dependencies:
-    get-intrinsic "^1.2.4"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.6"
     has-tostringtag "^1.0.2"
-    hasown "^2.0.1"
+    hasown "^2.0.2"
 
 es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2:
   version "1.0.2"
@@ -5842,7 +6261,7 @@ es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2:
   dependencies:
     hasown "^2.0.0"
 
-es-to-primitive@^1.2.1:
+es-to-primitive@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18"
   integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==
@@ -5892,10 +6311,10 @@ eslint-config-google@^0.14.0:
   resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
   integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
 
-eslint-config-prettier@^9.0.0:
-  version "9.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f"
-  integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==
+eslint-config-prettier@^10.0.0:
+  version "10.1.5"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz#00c18d7225043b6fbce6a665697377998d453782"
+  integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==
 
 eslint-import-resolver-node@^0.3.9:
   version "0.3.9"
@@ -5944,9 +6363,9 @@ eslint-plugin-import@^2.25.4:
     tsconfig-paths "^3.15.0"
 
 eslint-plugin-jest@^28.0.0:
-  version "28.9.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.9.0.tgz#19168dfaed124339cd2252c4c4d1ac3688aeb243"
-  integrity sha512-rLu1s1Wf96TgUUxSw6loVIkNtUjq1Re7A9QdCCHSohnvXEBAjuL420h0T/fMmkQlNsQP2GhQzEUpYHPfxBkvYQ==
+  version "28.11.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz#2641ecb4411941bbddb3d7cf8a8ff1163fbb510e"
+  integrity sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==
   dependencies:
     "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0"
 
@@ -5972,49 +6391,49 @@ eslint-plugin-jsx-a11y@^6.5.1:
     string.prototype.includes "^2.0.1"
 
 eslint-plugin-matrix-org@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.0.2.tgz#95b86b0f16704ab19740f7c3c62eae69e20365e6"
-  integrity sha512-cQy5Rjeq6uyu1mLXlPZwEJdyM0NmclrnEz68y792FSuuxzMyJNNYLGDQ5CkYW8H+PrD825HUFZ34pNXnjMOzOw==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.1.0.tgz#9053726119f107154f5210665c2f84219425c190"
+  integrity sha512-YjVQ0qunzVV34tpUchLWhOrOalGfRLm0tclS4dPYnXS8Ui+p12o/YtRHt+26Mg5tJ0QH76HsGC0LJKLVLNoqfg==
 
 eslint-plugin-react-compiler@^19.0.0-beta-df7b47d-20241124:
-  version "19.0.0-beta-df7b47d-20241124"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-df7b47d-20241124.tgz#468751d3a8a6781189405ee56b39b80545306df8"
-  integrity sha512-82PfnllC8jP/68KdLAbpWuYTcfmtGLzkqy2IW85WopKMTr+4rdQpp+lfliQ/QE79wWrv/dRoADrk3Pdhq25nTw==
+  version "19.0.0-beta-e552027-20250112"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-e552027-20250112.tgz#f4ad9cebe47615ebf6097a8084a30d761ee164f4"
+  integrity sha512-VjkIXHouCYyJHgk5HmZ1LH+fAK5CX+ULRX9iNYtwYJ+ljbivFhIT+JJyxNT/USQpCeS2Dt5ahjFeeMv0RRwTww==
   dependencies:
     "@babel/core" "^7.24.4"
     "@babel/parser" "^7.24.4"
-    "@babel/plugin-transform-private-methods" "^7.25.9"
+    "@babel/plugin-proposal-private-methods" "^7.18.6"
     hermes-parser "^0.25.1"
     zod "^3.22.4"
     zod-validation-error "^3.0.3"
 
 eslint-plugin-react-hooks@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101"
-  integrity sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3"
+  integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
 
 eslint-plugin-react@^7.28.0:
-  version "7.37.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz#cd0935987876ba2900df2f58339f6d92305acc7a"
-  integrity sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==
+  version "7.37.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz#1b6c80b6175b6ae4b26055ae4d55d04c414c7181"
+  integrity sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==
   dependencies:
     array-includes "^3.1.8"
     array.prototype.findlast "^1.2.5"
-    array.prototype.flatmap "^1.3.2"
+    array.prototype.flatmap "^1.3.3"
     array.prototype.tosorted "^1.1.4"
     doctrine "^2.1.0"
-    es-iterator-helpers "^1.1.0"
+    es-iterator-helpers "^1.2.1"
     estraverse "^5.3.0"
     hasown "^2.0.2"
     jsx-ast-utils "^2.4.1 || ^3.0.0"
     minimatch "^3.1.2"
     object.entries "^1.1.8"
     object.fromentries "^2.0.8"
-    object.values "^1.2.0"
+    object.values "^1.2.1"
     prop-types "^15.8.1"
     resolve "^2.0.0-next.5"
     semver "^6.3.1"
-    string.prototype.matchall "^4.0.11"
+    string.prototype.matchall "^4.0.12"
     string.prototype.repeat "^1.0.0"
 
 eslint-plugin-unicorn@^56.0.0:
@@ -6171,7 +6590,7 @@ esutils@^2.0.2:
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
   integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
 
-etag@~1.8.1:
+etag@^1.8.1, etag@~1.8.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
@@ -6218,21 +6637,6 @@ execa@^5.0.0:
     signal-exit "^3.0.3"
     strip-final-newline "^2.0.0"
 
-execa@~8.0.1:
-  version "8.0.1"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
-  integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
-  dependencies:
-    cross-spawn "^7.0.3"
-    get-stream "^8.0.1"
-    human-signals "^5.0.0"
-    is-stream "^3.0.0"
-    merge-stream "^2.0.0"
-    npm-run-path "^5.1.0"
-    onetime "^6.0.0"
-    signal-exit "^4.1.0"
-    strip-final-newline "^3.0.0"
-
 exit@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -6249,7 +6653,7 @@ expect@^29.0.0, expect@^29.7.0:
     jest-message-util "^29.7.0"
     jest-util "^29.7.0"
 
-express@^4.18.2, express@^4.19.2:
+express@^4.21.2:
   version "4.21.2"
   resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32"
   integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
@@ -6286,15 +6690,48 @@ express@^4.18.2, express@^4.19.2:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+express@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9"
+  integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==
+  dependencies:
+    accepts "^2.0.0"
+    body-parser "^2.2.0"
+    content-disposition "^1.0.0"
+    content-type "^1.0.5"
+    cookie "^0.7.1"
+    cookie-signature "^1.2.1"
+    debug "^4.4.0"
+    encodeurl "^2.0.0"
+    escape-html "^1.0.3"
+    etag "^1.8.1"
+    finalhandler "^2.1.0"
+    fresh "^2.0.0"
+    http-errors "^2.0.0"
+    merge-descriptors "^2.0.0"
+    mime-types "^3.0.0"
+    on-finished "^2.4.1"
+    once "^1.4.0"
+    parseurl "^1.3.3"
+    proxy-addr "^2.0.7"
+    qs "^6.14.0"
+    range-parser "^1.2.1"
+    router "^2.2.0"
+    send "^1.1.0"
+    serve-static "^2.2.0"
+    statuses "^2.0.1"
+    type-is "^2.0.1"
+    vary "^1.1.2"
+
 extend@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
 
 fake-indexeddb@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz#3173d5ad141436dace95f8de6e9ecdc3d9787d5d"
-  integrity sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-6.0.1.tgz#03937a9065c2ea09733e2147a473c904411b6f2c"
+  integrity sha512-He2AjQGHe46svIFq5+L2Nx/eHDTI1oKgoevBP+TthnjymXiKkeJQ3+ITeWey99Y5+2OaPFbI1qEsx/5RsGtWnQ==
 
 fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
@@ -6306,16 +6743,16 @@ fast-fifo@^1.2.0, fast-fifo@^1.3.2:
   resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
   integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
 
-fast-glob@^3.2.9, fast-glob@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
-  integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
+fast-glob@^3.2.9, fast-glob@^3.3.2, fast-glob@^3.3.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
+  integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
   dependencies:
     "@nodelib/fs.stat" "^2.0.2"
     "@nodelib/fs.walk" "^1.2.3"
     glob-parent "^5.1.2"
     merge2 "^1.3.0"
-    micromatch "^4.0.4"
+    micromatch "^4.0.8"
 
 fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
   version "2.1.0"
@@ -6328,9 +6765,9 @@ fast-levenshtein@^2.0.6:
   integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
 
 fast-uri@^3.0.1:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241"
-  integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748"
+  integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
 
 fastest-levenshtein@1.0.16, fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16:
   version "1.0.16"
@@ -6338,9 +6775,9 @@ fastest-levenshtein@1.0.16, fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.
   integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
 
 fastq@^1.6.0:
-  version "1.17.1"
-  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
-  integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
+  version "1.19.1"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
+  integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
   dependencies:
     reusify "^1.0.4"
 
@@ -6358,11 +6795,23 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "2.1.1"
 
+fd-package-json@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/fd-package-json/-/fd-package-json-1.2.0.tgz#4f218bb8ff65c21011d1f4f17cb3d0c9e72f8da7"
+  integrity sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==
+  dependencies:
+    walk-up-path "^3.0.1"
+
 fdir@^6.4.0:
   version "6.4.2"
   resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689"
   integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==
 
+fdir@^6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72"
+  integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==
+
 fetch-mock-jest@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd"
@@ -6391,6 +6840,13 @@ fflate@^0.4.8:
   resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
   integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
 
+file-entry-cache@^10.0.8:
+  version "10.0.8"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-10.0.8.tgz#2b7a32c40615c4a6b59c385fb059a2762faf9624"
+  integrity sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==
+  dependencies:
+    flat-cache "^6.1.8"
+
 file-entry-cache@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -6398,13 +6854,6 @@ file-entry-cache@^6.0.1:
   dependencies:
     flat-cache "^3.0.4"
 
-file-entry-cache@^9.1.0:
-  version "9.1.0"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.1.0.tgz#2e66ad98ce93f49aed1b178c57b0b5741591e075"
-  integrity sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==
-  dependencies:
-    flat-cache "^5.0.0"
-
 file-loader@^6.0.0:
   version "6.2.0"
   resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@@ -6455,13 +6904,17 @@ finalhandler@1.3.1:
     statuses "2.0.1"
     unpipe "~1.0.0"
 
-find-cache-dir@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2"
-  integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==
+finalhandler@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f"
+  integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==
   dependencies:
-    common-path-prefix "^3.0.0"
-    pkg-dir "^7.0.0"
+    debug "^4.4.0"
+    encodeurl "^2.0.0"
+    escape-html "^1.0.3"
+    on-finished "^2.4.1"
+    parseurl "^1.3.3"
+    statuses "^2.0.1"
 
 find-up@^4.0.0, find-up@^4.1.0:
   version "4.1.0"
@@ -6479,13 +6932,12 @@ find-up@^5.0.0:
     locate-path "^6.0.0"
     path-exists "^4.0.0"
 
-find-up@^6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790"
-  integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==
+find-yarn-workspace-root@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
+  integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
   dependencies:
-    locate-path "^7.1.0"
-    path-exists "^5.0.0"
+    micromatch "^4.0.2"
 
 flat-cache@^3.0.4:
   version "3.2.0"
@@ -6496,42 +6948,48 @@ flat-cache@^3.0.4:
     keyv "^4.5.3"
     rimraf "^3.0.2"
 
-flat-cache@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-5.0.0.tgz#26c4da7b0f288b408bb2b506b2cb66c240ddf062"
-  integrity sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==
+flat-cache@^6.1.8:
+  version "6.1.8"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.8.tgz#968fb89b19df488fe60f346857ffc54b8dd0ba14"
+  integrity sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==
   dependencies:
-    flatted "^3.3.1"
-    keyv "^4.5.4"
+    cacheable "^1.8.9"
+    flatted "^3.3.3"
+    hookified "^1.8.1"
 
 flat@^5.0.2:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
   integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
 
-flatted@^3.2.9, flatted@^3.3.1:
+flatted@^3.2.9:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
   integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
 
-focus-lock@^1.3.5:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-1.3.5.tgz#aa644576e5ec47d227b57eb14e1efb2abf33914c"
-  integrity sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==
+flatted@^3.3.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
+  integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
+
+focus-lock@^1.3.6:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-1.3.6.tgz#955eec1e10591d56f679258edb94aedb11d691cd"
+  integrity sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==
   dependencies:
     tslib "^2.0.3"
 
-follow-redirects@^1.0.0:
+follow-redirects@^1.0.0, follow-redirects@^1.15.6:
   version "1.15.9"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
   integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
 
 for-each@^0.3.3:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
-  integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.4.tgz#814517ffc303d1399b2564d8165318e735d0341c"
+  integrity sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==
   dependencies:
-    is-callable "^1.1.3"
+    is-callable "^1.2.7"
 
 foreachasync@^3.0.0:
   version "3.0.0"
@@ -6539,22 +6997,30 @@ foreachasync@^3.0.0:
   integrity sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==
 
 foreground-child@^3.1.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
-  integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f"
+  integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==
   dependencies:
-    cross-spawn "^7.0.0"
+    cross-spawn "^7.0.6"
     signal-exit "^4.0.1"
 
 form-data@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
-  integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
+  integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
   dependencies:
     asynckit "^0.4.0"
     combined-stream "^1.0.8"
+    es-set-tostringtag "^2.1.0"
     mime-types "^2.1.12"
 
+formatly@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/formatly/-/formatly-0.2.3.tgz#30c4d3605c4f66d97a97a7dafbd9bb4a2467b26f"
+  integrity sha512-WH01vbXEjh9L3bqn5V620xUAWs32CmK4IzWRRY6ep5zpa/mrisL4d9+pRVuETORVDTQw8OycSO1WC68PL51RaA==
+  dependencies:
+    fd-package-json "^1.2.0"
+
 forwarded@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -6570,11 +7036,26 @@ fresh@0.5.2:
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
 
+fresh@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
+  integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
+
 fs-constants@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
 
+fs-extra@^9.0.0:
+  version "9.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+  integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+  dependencies:
+    at-least-node "^1.0.0"
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -6600,15 +7081,17 @@ function-bind@^1.1.2:
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
   integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
 
-function.prototype.name@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd"
-  integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==
+function.prototype.name@^1.1.6, function.prototype.name@^1.1.8:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78"
+  integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==
   dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.2.0"
-    es-abstract "^1.22.1"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
+    define-properties "^1.2.1"
     functions-have-names "^1.2.3"
+    hasown "^2.0.2"
+    is-callable "^1.2.7"
 
 functions-have-names@^1.2.3:
   version "1.2.3"
@@ -6635,30 +7118,37 @@ get-east-asian-width@^1.0.0:
   resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389"
   integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==
 
-get-intrinsic@^1.2.1, get-intrinsic@^1.2.3:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
-  integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+get-intrinsic@^1.2.4, get-intrinsic@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044"
+  integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==
   dependencies:
+    call-bind-apply-helpers "^1.0.1"
+    es-define-property "^1.0.1"
     es-errors "^1.3.0"
+    es-object-atoms "^1.0.0"
     function-bind "^1.1.2"
-    has-proto "^1.0.1"
-    has-symbols "^1.0.3"
-    hasown "^2.0.0"
+    get-proto "^1.0.0"
+    gopd "^1.2.0"
+    has-symbols "^1.1.0"
+    hasown "^2.0.2"
+    math-intrinsics "^1.1.0"
 
-get-intrinsic@^1.2.4:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7"
-  integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==
+get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
+  integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
   dependencies:
-    call-bind-apply-helpers "^1.0.0"
-    dunder-proto "^1.0.0"
+    call-bind-apply-helpers "^1.0.2"
     es-define-property "^1.0.1"
     es-errors "^1.3.0"
+    es-object-atoms "^1.1.1"
     function-bind "^1.1.2"
+    get-proto "^1.0.1"
     gopd "^1.2.0"
     has-symbols "^1.1.0"
     hasown "^2.0.2"
+    math-intrinsics "^1.1.0"
 
 get-nonce@^1.0.0:
   version "1.0.1"
@@ -6670,29 +7160,32 @@ get-package-type@^0.1.0:
   resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
   integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
 
-get-port@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
-  integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
+get-port@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/get-port/-/get-port-7.1.0.tgz#d5a500ebfc7aa705294ec2b83cc38c5d0e364fec"
+  integrity sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==
+
+get-proto@^1.0.0, get-proto@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
+  integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+  dependencies:
+    dunder-proto "^1.0.1"
+    es-object-atoms "^1.0.0"
 
 get-stream@^6.0.0, get-stream@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
   integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
 
-get-stream@^8.0.1:
-  version "8.0.1"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
-  integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
-
-get-symbol-description@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5"
-  integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==
+get-symbol-description@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee"
+  integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==
   dependencies:
-    call-bind "^1.0.5"
+    call-bound "^1.0.3"
     es-errors "^1.3.0"
-    get-intrinsic "^1.2.4"
+    get-intrinsic "^1.2.6"
 
 github-markdown-css@^5.5.1:
   version "5.8.1"
@@ -6736,9 +7229,9 @@ glob@^10.0.0:
     path-scurry "^1.11.1"
 
 glob@^11.0.0:
-  version "11.0.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e"
-  integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==
+  version "11.0.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.2.tgz#3261e3897bbc603030b041fd77ba636022d51ce0"
+  integrity sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==
   dependencies:
     foreground-child "^3.1.0"
     jackspeak "^4.0.1"
@@ -6831,18 +7324,6 @@ globby@^11.1.0:
     merge2 "^1.4.1"
     slash "^3.0.0"
 
-globby@^14.0.0:
-  version "14.0.2"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.2.tgz#06554a54ccfe9264e5a9ff8eded46aa1e306482f"
-  integrity sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==
-  dependencies:
-    "@sindresorhus/merge-streams" "^2.1.0"
-    fast-glob "^3.3.2"
-    ignore "^5.2.4"
-    path-type "^5.0.0"
-    slash "^5.1.0"
-    unicorn-magic "^0.1.0"
-
 globjoin@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
@@ -6853,14 +7334,7 @@ gopd@^1.0.1, gopd@^1.2.0:
   resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
   integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
 
-gopd@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.1.0.tgz#df8f0839c2d48caefc32a025a49294d39606c912"
-  integrity sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==
-  dependencies:
-    get-intrinsic "^1.2.4"
-
-graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
   version "4.2.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
   integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -6882,10 +7356,10 @@ handle-thing@^2.0.0:
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
   integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
 
-has-bigints@^1.0.1, has-bigints@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
-  integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
+has-bigints@^1.0.2:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe"
+  integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==
 
 has-flag@^3.0.0:
   version "3.0.0"
@@ -6904,25 +7378,13 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
   dependencies:
     es-define-property "^1.0.0"
 
-has-proto@^1.0.1:
+has-proto@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5"
   integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==
   dependencies:
     dunder-proto "^1.0.0"
 
-has-proto@^1.0.3:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.1.0.tgz#deb10494cbbe8809bce168a3b961f42969f5ed43"
-  integrity sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==
-  dependencies:
-    call-bind "^1.0.7"
-
-has-symbols@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
-  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
-
 has-symbols@^1.0.3, has-symbols@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
@@ -6935,7 +7397,7 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
   dependencies:
     has-symbols "^1.0.3"
 
-hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
+hasown@^2.0.0, hasown@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
   integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
@@ -6960,9 +7422,9 @@ hermes-parser@^0.25.1:
     hermes-estree "0.25.1"
 
 highlight.js@^11.3.1:
-  version "11.10.0"
-  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92"
-  integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==
+  version "11.11.1"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585"
+  integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
 
 hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
@@ -6971,6 +7433,11 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
   dependencies:
     react-is "^16.7.0"
 
+hookified@^1.8.1:
+  version "1.8.2"
+  resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.8.2.tgz#b365a89dfce3da43e790673a6a97d3b896ae5fa7"
+  integrity sha512-5nZbBNP44sFCDjSoB//0N7m508APCgbQ4mGGo1KJGBYyCKNHfry1Pvd0JVHZIxjdnqn8nFRBAN/eFB6Rk/4w5w==
+
 hosted-git-info@^2.1.4:
   version "2.8.9"
   resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -6986,6 +7453,14 @@ hpack.js@^2.1.6:
     readable-stream "^2.0.1"
     wbuf "^1.1.0"
 
+html-dom-parser@5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-5.1.1.tgz#9efb2cfa055f6a71de1bb2f07c5b019db5004f9e"
+  integrity sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==
+  dependencies:
+    domhandler "5.0.3"
+    htmlparser2 "10.0.0"
+
 html-encoding-sniffer@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
@@ -6993,10 +7468,10 @@ html-encoding-sniffer@^3.0.0:
   dependencies:
     whatwg-encoding "^2.0.0"
 
-html-entities@^2.0.0, html-entities@^2.4.0:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
-  integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
+html-entities@^2.0.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8"
+  integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==
 
 html-escaper@^2.0.0, html-escaper@^2.0.2:
   version "2.0.2"
@@ -7016,6 +7491,16 @@ html-minifier-terser@^6.0.2:
     relateurl "^0.2.7"
     terser "^5.10.0"
 
+html-react-parser@^5.2.2:
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-5.2.5.tgz#4a0d62c129d5d5c63cc49f986c946552d737190c"
+  integrity sha512-bRPdv8KTqG9CEQPMNGksDqmbiRfVQeOidry8pVetdh/1jQ1Edx4KX5m0lWvDD89Pt4CqTYjK1BLz6NoNVxN/Uw==
+  dependencies:
+    domhandler "5.0.3"
+    html-dom-parser "5.1.1"
+    react-property "2.0.2"
+    style-to-js "1.1.16"
+
 html-tags@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
@@ -7032,6 +7517,16 @@ html-webpack-plugin@^5.5.3:
     pretty-error "^4.0.0"
     tapable "^2.0.0"
 
+htmlparser2@10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d"
+  integrity sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+    domutils "^3.2.1"
+    entities "^6.0.0"
+
 htmlparser2@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
@@ -7057,7 +7552,7 @@ http-deceiver@^1.2.7:
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
   integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
 
-http-errors@2.0.0:
+http-errors@2.0.0, http-errors@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
   integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
@@ -7079,9 +7574,9 @@ http-errors@~1.6.2:
     statuses ">= 1.4.0 < 2"
 
 http-parser-js@>=0.5.1:
-  version "0.5.8"
-  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
-  integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
+  version "0.5.9"
+  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.9.tgz#b817b3ca0edea6236225000d795378707c169cec"
+  integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==
 
 http-proxy-agent@^5.0.0:
   version "5.0.0"
@@ -7092,10 +7587,10 @@ http-proxy-agent@^5.0.0:
     agent-base "6"
     debug "4"
 
-http-proxy-middleware@^2.0.3:
-  version "2.0.7"
-  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
-  integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
+http-proxy-middleware@^2.0.7:
+  version "2.0.9"
+  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef"
+  integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==
   dependencies:
     "@types/http-proxy" "^1.17.8"
     http-proxy "^1.18.1"
@@ -7125,11 +7620,6 @@ human-signals@^2.1.0:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
-human-signals@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
-  integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
-
 husky@^9.0.0:
   version "9.1.7"
   resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d"
@@ -7147,7 +7637,7 @@ iconv-lite@0.4.24:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@0.6.3, iconv-lite@^0.6, iconv-lite@^0.6.3:
+iconv-lite@0.6.3, iconv-lite@^0.6.3:
   version "0.6.3"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
   integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -7164,22 +7654,22 @@ ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.2.1:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore@^5.1.8, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1:
+ignore@^5.2.0:
   version "5.3.2"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
   integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
 
-ignore@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283"
-  integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==
+ignore@^7.0.0, ignore@^7.0.3:
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078"
+  integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==
 
 immediate@~3.0.5:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
   integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
 
-import-fresh@^3.2.1, import-fresh@^3.3.0:
+import-fresh@^3.2.1:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
   integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -7187,6 +7677,14 @@ import-fresh@^3.2.1, import-fresh@^3.3.0:
     parent-module "^1.0.0"
     resolve-from "^4.0.0"
 
+import-fresh@^3.3.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
+  integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 import-local@^3.0.2:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260"
@@ -7238,14 +7736,19 @@ ini@^4.1.3:
   resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795"
   integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==
 
-internal-slot@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802"
-  integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==
+inline-style-parser@0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
+  integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
+
+internal-slot@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961"
+  integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==
   dependencies:
     es-errors "^1.3.0"
-    hasown "^2.0.0"
-    side-channel "^1.0.4"
+    hasown "^2.0.2"
+    side-channel "^1.1.0"
 
 interpret@^3.1.1:
   version "3.1.1"
@@ -7282,13 +7785,14 @@ is-arguments@^1.0.4:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
-is-array-buffer@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98"
-  integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==
+is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
+  integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
   dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.2.1"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
+    get-intrinsic "^1.2.6"
 
 is-arrayish@^0.2.1:
   version "0.2.1"
@@ -7296,18 +7800,22 @@ is-arrayish@^0.2.1:
   integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
 
 is-async-function@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646"
-  integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523"
+  integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==
   dependencies:
-    has-tostringtag "^1.0.0"
+    async-function "^1.0.0"
+    call-bound "^1.0.3"
+    get-proto "^1.0.1"
+    has-tostringtag "^1.0.2"
+    safe-regex-test "^1.1.0"
 
-is-bigint@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
-  integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
+is-bigint@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672"
+  integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==
   dependencies:
-    has-bigints "^1.0.1"
+    has-bigints "^1.0.2"
 
 is-binary-path@~2.1.0:
   version "2.1.0"
@@ -7316,12 +7824,12 @@ is-binary-path@~2.1.0:
   dependencies:
     binary-extensions "^2.0.0"
 
-is-boolean-object@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.0.tgz#9743641e80a62c094b5941c5bb791d66a88e497a"
-  integrity sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==
+is-boolean-object@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.1.tgz#c20d0c654be05da4fbc23c562635c019e93daf89"
+  integrity sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.2"
     has-tostringtag "^1.0.2"
 
 is-buffer@^2.0.5:
@@ -7336,31 +7844,46 @@ is-builtin-module@^3.2.1:
   dependencies:
     builtin-modules "^3.3.0"
 
-is-callable@^1.1.3, is-callable@^1.2.7:
+is-callable@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
   integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
 
-is-core-module@^2.13.0, is-core-module@^2.15.1:
+is-core-module@^2.13.0, is-core-module@^2.16.0:
+  version "2.16.1"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
+  integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+  dependencies:
+    hasown "^2.0.2"
+
+is-core-module@^2.15.1:
   version "2.15.1"
   resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
   integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
   dependencies:
     hasown "^2.0.2"
 
-is-data-view@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f"
-  integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==
+is-data-view@^1.0.1, is-data-view@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e"
+  integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==
   dependencies:
+    call-bound "^1.0.2"
+    get-intrinsic "^1.2.6"
     is-typed-array "^1.1.13"
 
-is-date-object@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
-  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
+is-date-object@^1.0.5, is-date-object@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7"
+  integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==
   dependencies:
-    has-tostringtag "^1.0.0"
+    call-bound "^1.0.2"
+    has-tostringtag "^1.0.2"
+
+is-docker@^2.0.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+  integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
 
 is-docker@^3.0.0:
   version "3.0.0"
@@ -7373,11 +7896,11 @@ is-extglob@^2.1.1:
   integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
 
 is-finalizationregistry@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz#d74a7d0c5f3578e34a20729e69202e578d495dc2"
-  integrity sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90"
+  integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
 
 is-fullwidth-code-point@^3.0.0:
   version "3.0.0"
@@ -7401,7 +7924,17 @@ is-generator-fn@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
   integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
 
-is-generator-function@^1.0.10, is-generator-function@^1.0.7:
+is-generator-function@^1.0.10:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca"
+  integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==
+  dependencies:
+    call-bound "^1.0.3"
+    get-proto "^1.0.0"
+    has-tostringtag "^1.0.2"
+    safe-regex-test "^1.1.0"
+
+is-generator-function@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
   integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
@@ -7434,22 +7967,17 @@ is-map@^2.0.3:
   resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
   integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
 
-is-negative-zero@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747"
-  integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==
-
 is-network-error@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997"
   integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==
 
-is-number-object@^1.0.4:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.0.tgz#5a867e9ecc3d294dda740d9f127835857af7eb05"
-  integrity sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==
+is-number-object@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541"
+  integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
     has-tostringtag "^1.0.2"
 
 is-number@^7.0.0:
@@ -7484,13 +8012,18 @@ is-potential-custom-element-name@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
   integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
 
-is-regex@^1.1.4:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.0.tgz#41b9d266e7eb7451312c64efc37e8a7d453077cf"
-  integrity sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==
+is-promise@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
+  integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
+
+is-regex@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
+  integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==
   dependencies:
-    call-bind "^1.0.7"
-    gopd "^1.1.0"
+    call-bound "^1.0.2"
+    gopd "^1.2.0"
     has-tostringtag "^1.0.2"
     hasown "^2.0.2"
 
@@ -7499,29 +8032,24 @@ is-set@^2.0.3:
   resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d"
   integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
 
-is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688"
-  integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==
+is-shared-array-buffer@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f"
+  integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
 
 is-stream@^2.0.0, is-stream@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
   integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
 
-is-stream@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
-  integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
-
-is-string@^1.0.5, is-string@^1.0.7:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.0.tgz#8cb83c5d57311bf8058bc6c8db294711641da45d"
-  integrity sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==
+is-string@^1.0.7, is-string@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9"
+  integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
     has-tostringtag "^1.0.2"
 
 is-subset@^0.1.1:
@@ -7529,14 +8057,23 @@ is-subset@^0.1.1:
   resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
   integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==
 
-is-symbol@^1.0.3, is-symbol@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
-  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+is-symbol@^1.0.4, is-symbol@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634"
+  integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==
   dependencies:
-    has-symbols "^1.0.2"
+    call-bound "^1.0.2"
+    has-symbols "^1.1.0"
+    safe-regex-test "^1.1.0"
+
+is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
+  version "1.1.15"
+  resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b"
+  integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+  dependencies:
+    which-typed-array "^1.1.16"
 
-is-typed-array@^1.1.13, is-typed-array@^1.1.3:
+is-typed-array@^1.1.3:
   version "1.1.13"
   resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229"
   integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==
@@ -7548,20 +8085,27 @@ is-weakmap@^2.0.2:
   resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd"
   integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==
 
-is-weakref@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
-  integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
+is-weakref@^1.0.2, is-weakref@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293"
+  integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==
   dependencies:
-    call-bind "^1.0.2"
+    call-bound "^1.0.3"
 
 is-weakset@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007"
-  integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca"
+  integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==
   dependencies:
-    call-bind "^1.0.7"
-    get-intrinsic "^1.2.4"
+    call-bound "^1.0.3"
+    get-intrinsic "^1.2.6"
+
+is-wsl@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+  integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+  dependencies:
+    is-docker "^2.0.0"
 
 is-wsl@^3.1.0:
   version "3.1.0"
@@ -7648,16 +8192,17 @@ istanbul-reports@^3.1.3:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-iterator.prototype@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c"
-  integrity sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==
+iterator.prototype@^1.1.4:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39"
+  integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==
   dependencies:
-    define-properties "^1.2.1"
-    get-intrinsic "^1.2.1"
-    has-symbols "^1.0.3"
-    reflect.getprototypeof "^1.0.4"
-    set-function-name "^2.0.1"
+    define-data-property "^1.1.4"
+    es-object-atoms "^1.0.0"
+    get-intrinsic "^1.2.6"
+    get-proto "^1.0.0"
+    has-symbols "^1.1.0"
+    set-function-name "^2.0.2"
 
 jackspeak@^3.1.2:
   version "3.4.3"
@@ -7669,9 +8214,9 @@ jackspeak@^3.1.2:
     "@pkgjs/parseargs" "^0.11.0"
 
 jackspeak@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015"
-  integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.0.tgz#c489c079f2b636dc4cbe9b0312a13ff1282e561b"
+  integrity sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==
   dependencies:
     "@isaacs/cliui" "^8.0.2"
 
@@ -8084,10 +8629,10 @@ jiti@^1.20.0:
   resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
   integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
 
-jiti@^2.4.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.1.tgz#4de9766ccbfa941d9b6390d2b159a4b295a52e6b"
-  integrity sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==
+jiti@^2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
+  integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==
 
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
@@ -8186,6 +8731,17 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 
+json-stable-stringify@^1.0.2:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz#addb683c2b78014d0b78d704c2fcbdf0695a60e2"
+  integrity sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==
+  dependencies:
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
+    isarray "^2.0.5"
+    jsonify "^0.0.1"
+    object-keys "^1.1.1"
+
 json-stringify-pretty-compact@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz#cf4844770bddee3cb89a6170fe4b00eee5dbf1d4"
@@ -8203,6 +8759,20 @@ json5@^2.1.2, json5@^2.2.3:
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
 
+jsonfile@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+  integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+  dependencies:
+    universalify "^2.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
+jsonify@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
+  integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
+
 jsqr@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1"
@@ -8239,9 +8809,9 @@ jwt-decode@4.0.0, jwt-decode@^4.0.0:
   integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
 
 katex@^0.16.0:
-  version "0.16.21"
-  resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.21.tgz#8f63c659e931b210139691f2cc7bb35166b792a3"
-  integrity sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==
+  version "0.16.22"
+  resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.22.tgz#d2b3d66464b1e6d69e6463b28a86ced5a02c5ccd"
+  integrity sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==
   dependencies:
     commander "^8.3.0"
 
@@ -8250,54 +8820,60 @@ kdbush@^4.0.2:
   resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39"
   integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==
 
-keyv@^4.5.3, keyv@^4.5.4:
+keyv@^4.5.3:
   version "4.5.4"
   resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
   integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
   dependencies:
     json-buffer "3.0.1"
 
+keyv@^5.3.2:
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.3.3.tgz#ec2d723fbd7b908de5ee7f56b769d46dbbeaf8ba"
+  integrity sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==
+  dependencies:
+    "@keyv/serialize" "^1.0.3"
+
 kind-of@^6.0.2, kind-of@^6.0.3:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
+klaw-sync@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
+  integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+  dependencies:
+    graceful-fs "^4.1.11"
+
 kleur@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
 knip@^5.36.2:
-  version "5.39.2"
-  resolved "https://registry.yarnpkg.com/knip/-/knip-5.39.2.tgz#1faacd8d8ef36b509b2f6e396cce85b645abb04e"
-  integrity sha512-BuvuWRllLWV/r2G4m9ggNH+DZ6gouP/dhtJPXVlMbWNF++w9/EfrF6k2g7YBKCwjzCC+PXmYtpH8S2t8RjnY4Q==
+  version "5.58.1"
+  resolved "https://registry.yarnpkg.com/knip/-/knip-5.58.1.tgz#546afea96e8a3893ad04015dc73038549b13eead"
+  integrity sha512-9FKnuGhGFZ8wDgcjEYQ6btMmtGHa7dkvNU2ZHuMcv2NaxL7xDeFXZKjETbaiJBjWnLytrDXrh7a58lGXcE0Zfw==
   dependencies:
-    "@nodelib/fs.walk" "1.2.8"
-    "@snyk/github-codeowners" "1.1.0"
-    easy-table "1.2.0"
-    enhanced-resolve "^5.17.1"
-    fast-glob "^3.3.2"
-    jiti "^2.4.0"
+    "@nodelib/fs.walk" "^1.2.3"
+    fast-glob "^3.3.3"
+    formatly "^0.2.3"
+    jiti "^2.4.2"
     js-yaml "^4.1.0"
     minimist "^1.2.8"
+    oxc-resolver "^9.0.2"
     picocolors "^1.1.0"
     picomatch "^4.0.1"
-    pretty-ms "^9.0.0"
     smol-toml "^1.3.1"
     strip-json-comments "5.0.1"
-    summary "2.1.0"
     zod "^3.22.4"
     zod-validation-error "^3.0.3"
 
-known-css-properties@^0.34.0:
-  version "0.34.0"
-  resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.34.0.tgz#ccd7e9f4388302231b3f174a8b1d5b1f7b576cea"
-  integrity sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==
-
-known-css-properties@^0.35.0:
-  version "0.35.0"
-  resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.35.0.tgz#f6f8e40ab4e5700fa32f5b2ef5218a56bc853bd6"
-  integrity sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==
+known-css-properties@^0.36.0:
+  version "0.36.0"
+  resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.36.0.tgz#5c4365f3c9549ca2e813d2e729e6c47ef6a6cb60"
+  integrity sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==
 
 language-subtag-registry@^0.3.20:
   version "0.3.23"
@@ -8312,9 +8888,9 @@ language-tags@^1.0.9:
     language-subtag-registry "^0.3.20"
 
 launch-editor@^2.6.1:
-  version "2.9.1"
-  resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047"
-  integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.10.0.tgz#5ca3edfcb9667df1e8721310f3a40f1127d4bc42"
+  integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==
   dependencies:
     picocolors "^1.0.0"
     shell-quote "^1.8.1"
@@ -8346,21 +8922,16 @@ lie@~3.3.0:
   dependencies:
     immediate "~3.0.5"
 
-lilconfig@^3.1.2, lilconfig@~3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb"
-  integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==
+lilconfig@^3.1.2, lilconfig@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
+  integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
 
 lines-and-columns@^1.1.6:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
   integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
 
-linkify-element@4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/linkify-element/-/linkify-element-4.2.0.tgz#fb5c6d47576487a463fd22a0cc889e15833aa943"
-  integrity sha512-LahyRMhXAgWTP9TOid7pTv8UUZFDz+saLkIVAoGNmOvISt+uSeBzdGhk3dsvkdzAh1QMhkO+fVJjmkMEITre5g==
-
 linkify-it@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec"
@@ -8368,41 +8939,41 @@ linkify-it@^4.0.1:
   dependencies:
     uc.micro "^1.0.1"
 
-linkify-react@4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.2.0.tgz#d143b2af8efa5e3b09517b66ed442624c3e06bcf"
-  integrity sha512-dIcDGo+n4FP2FPIHDcqB7cUE+omkcEgQJpc7sNNP4+XZ9FUhFAkKjGnHMzsZM+B4yF93sK166z9K5cKTe/JpzA==
+linkify-react@4.3.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.3.1.tgz#0655632d654a881e54d955ec12b1ab817d879f50"
+  integrity sha512-w8ahBdCwF9C/doS4V3nE93QF1oyORmosvi8UEUbpHYws077eGzhkxUzJQcE2/SU5Q2K7SD80M4ybwwZGHErx5Q==
 
-linkify-string@4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.2.0.tgz#e8c5a9d57698e81e7cce7f4915ddbcbde17134c0"
-  integrity sha512-LqOKk0+RlqibFkxjPAGOL7Mfssqj6/SdG9QWGvzeVDpoWXhaw9OXxseCtFanjIl7C6UyTTZizhyGr4IWLfijiw==
+linkify-string@4.3.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.3.1.tgz#d6f8b7166d588a64943e3bb23302ce44047f61a2"
+  integrity sha512-1AnH52wZwuJi+skG/9dUphhQEUblVGSf0ntkM8z21RS9bF7xR0qPpqnNTyCo2Obqs5MR5wi8y5wOLPoBbzxm2w==
 
-linkifyjs@4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08"
-  integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==
-
-lint-staged@^15.0.2:
-  version "15.2.10"
-  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.10.tgz#92ac222f802ba911897dcf23671da5bb80643cd2"
-  integrity sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==
-  dependencies:
-    chalk "~5.3.0"
-    commander "~12.1.0"
-    debug "~4.3.6"
-    execa "~8.0.1"
-    lilconfig "~3.1.2"
-    listr2 "~8.2.4"
-    micromatch "~4.0.8"
-    pidtree "~0.6.0"
-    string-argv "~0.3.2"
-    yaml "~2.5.0"
-
-listr2@~8.2.4:
-  version "8.2.5"
-  resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.5.tgz#5c9db996e1afeb05db0448196d3d5f64fec2593d"
-  integrity sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==
+linkifyjs@4.3.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.1.tgz#1f246ebf4be040002accd1f4535b6af7c7e37898"
+  integrity sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg==
+
+lint-staged@^16.0.0:
+  version "16.0.0"
+  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-16.0.0.tgz#31826709bde6a62542431da3055f038e386a20db"
+  integrity sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==
+  dependencies:
+    chalk "^5.4.1"
+    commander "^13.1.0"
+    debug "^4.4.0"
+    lilconfig "^3.1.3"
+    listr2 "^8.3.3"
+    micromatch "^4.0.8"
+    nano-spawn "^1.0.0"
+    pidtree "^0.6.0"
+    string-argv "^0.3.2"
+    yaml "^2.7.1"
+
+listr2@^8.3.3:
+  version "8.3.3"
+  resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.3.3.tgz#815fc8f738260ff220981bf9e866b3e11e8121bf"
+  integrity sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==
   dependencies:
     cli-truncate "^4.0.0"
     colorette "^2.0.20"
@@ -8439,12 +9010,15 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
-locate-path@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a"
-  integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
-  dependencies:
-    p-locate "^6.0.0"
+lodash-es@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+  integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+lodash.camelcase@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+  integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
 
 lodash.debounce@^4.0.8:
   version "4.0.8"
@@ -8497,17 +9071,22 @@ log-update@^6.1.0:
     strip-ansi "^7.1.0"
     wrap-ansi "^9.0.0"
 
-loglevel@^1.7.1:
+loglevel@^1.9.2:
   version "1.9.2"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08"
   integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
 
+long@^5.0.0:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83"
+  integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
+
 long@^5.2.0:
   version "5.2.3"
   resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
   integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
 
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -8527,9 +9106,9 @@ lru-cache@^10.2.0:
   integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
 
 lru-cache@^11.0.0:
-  version "11.0.1"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.1.tgz#3a732fbfedb82c5ba7bca6564ad3f42afcb6e147"
-  integrity sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117"
+  integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==
 
 lru-cache@^5.1.1:
   version "5.1.1"
@@ -8550,12 +9129,12 @@ magic-string@0.30.8:
   dependencies:
     "@jridgewell/sourcemap-codec" "^1.4.15"
 
-mailhog@^4.16.0:
-  version "4.16.0"
-  resolved "https://registry.yarnpkg.com/mailhog/-/mailhog-4.16.0.tgz#1ad4dda104505399f3f17824737a962696e7d240"
-  integrity sha512-wXrGik+0MaAy4dbYTImxa8niX9a4aRpZTzC/b1GzCvQs09khhs0aKZgHjgScakI4Y18WInDvvF48hhEz9ifN4g==
-  optionalDependencies:
-    iconv-lite "^0.6"
+mailpit-api@^1.2.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/mailpit-api/-/mailpit-api-1.4.0.tgz#881b473434b86c13ff85c3d92e9474daf4376135"
+  integrity sha512-A7hMRDQGo0jf1HZM06tw8dSgT2cYEev7lh1ehUXDWIBWXuK+Rq6HHCCQz624uAFYWEngmt4RGG1kMGGUC7iY6A==
+  dependencies:
+    axios "^1.9.0"
 
 make-dir@^4.0.0:
   version "4.0.0"
@@ -8577,9 +9156,9 @@ makeerror@1.0.12:
     tmpl "1.0.5"
 
 maplibre-gl@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-5.0.0.tgz#d9120b6ced7df5d1c791497f25bbe4edd5039d96"
-  integrity sha512-WG8IYFK2gfJYXvWjlqg1yavo/YO/JlNkblAJMt19sjIafP5oJzTgXFiOLUIYkjtrv5pKiAWuSYsx4CD3ithJqw==
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-5.5.0.tgz#0a295a634e745a5c31848ac7198893da417d09ae"
+  integrity sha512-p8AOPuzzqn1ZA9gcXxKw0IED715we/2Owa/YUr6PANmgMvNMe/JG+V/C1hRra43Wm62Biz+Aa8AgbOLJimA8tA==
   dependencies:
     "@mapbox/geojson-rewind" "^0.5.2"
     "@mapbox/jsonlint-lines-primitives" "^2.0.2"
@@ -8588,8 +9167,8 @@ maplibre-gl@^5.0.0:
     "@mapbox/unitbezier" "^0.0.1"
     "@mapbox/vector-tile" "^1.3.1"
     "@mapbox/whoots-js" "^3.1.0"
-    "@maplibre/maplibre-gl-style-spec" "^22.0.1"
-    "@types/geojson" "^7946.0.15"
+    "@maplibre/maplibre-gl-style-spec" "^23.2.2"
+    "@types/geojson" "^7946.0.16"
     "@types/geojson-vt" "3.2.5"
     "@types/mapbox__point-geometry" "^0.1.4"
     "@types/mapbox__vector-tile" "^1.3.4"
@@ -8619,6 +9198,11 @@ markdown-it@^13.0.2:
     mdurl "^1.0.1"
     uc.micro "^1.0.5"
 
+math-intrinsics@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
+  integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
 mathml-tag-names@^2.1.3:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@@ -8635,17 +9219,17 @@ matrix-events-sdk@0.0.1:
   integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
 
 "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
-  version "36.0.0"
-  resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/07f97d724f755a131571511af6662d4e3b345728"
+  version "37.6.0"
+  resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c35c7d1a3be073e0922cf28f13225805fc2a78f8"
   dependencies:
     "@babel/runtime" "^7.12.5"
-    "@matrix-org/matrix-sdk-crypto-wasm" "^12.1.0"
+    "@matrix-org/matrix-sdk-crypto-wasm" "^14.0.1"
     "@matrix-org/olm" "3.2.15"
     another-json "^0.2.0"
     bs58 "^6.0.0"
     content-type "^1.0.4"
     jwt-decode "^4.0.0"
-    loglevel "^1.7.1"
+    loglevel "^1.9.2"
     matrix-events-sdk "0.0.1"
     matrix-widget-api "^1.10.0"
     oidc-client-ts "^3.0.1"
@@ -8666,9 +9250,9 @@ matrix-web-i18n@^3.2.1:
     walk "^2.3.15"
 
 matrix-widget-api@^1.10.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.10.0.tgz#d31ea073a5871a1fb1a511ef900b0c125a37bf55"
-  integrity sha512-rkAJ29briYV7TJnfBVLVSKtpeBrBju15JZFSDP6wj8YdbCu1bdmlplJayQ+vYaw1x4fzI49Q+Nz3E85s46sRDw==
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.13.1.tgz#5b1caeed2fc58148bcd2984e0546d2d06a1713ad"
+  integrity sha512-mkOHUVzaN018TCbObfGOSaMW2GoUxOfcxNNlTVx5/HeMk3OSQPQM0C9oEME5Liiv/dBUoSrEB64V8wF7e/gb1w==
   dependencies:
     "@types/events" "^3.0.0"
     events "^3.2.0"
@@ -8683,11 +9267,16 @@ mdn-data@2.0.30:
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
   integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
 
-mdn-data@2.12.2, mdn-data@^2.12.2:
+mdn-data@2.12.2:
   version "2.12.2"
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.2.tgz#9ae6c41a9e65adf61318b32bff7b64fbfb13f8cf"
   integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==
 
+mdn-data@^2.21.0:
+  version "2.21.0"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.21.0.tgz#f3a495e8b1e60cb4fbeaf9136aefba2f987a56e1"
+  integrity sha512-+ZKPQezM5vYJIkCxaC+4DTnRrVZR1CgsKLu5zsQERQx6Tea8Y+wMx5A24rq8A8NepCeatIQufVAekKNgiBMsGQ==
+
 mdurl@^1.0.1, mdurl@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@@ -8698,10 +9287,15 @@ media-typer@0.3.0:
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
 
+media-typer@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
+  integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
+
 memfs@^4.6.0:
-  version "4.14.0"
-  resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.14.0.tgz#48d5e85a03ea0b428280003212fbca3063531be3"
-  integrity sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==
+  version "4.17.0"
+  resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.0.tgz#a3c4b5490b9b1e7df5d433adc163e08208ce7ca2"
+  integrity sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==
   dependencies:
     "@jsonjoy.com/json-pack" "^1.0.3"
     "@jsonjoy.com/util" "^1.3.0"
@@ -8728,6 +9322,11 @@ merge-descriptors@1.0.3:
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
   integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
 
+merge-descriptors@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
+  integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
+
 merge-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -8743,7 +9342,7 @@ methods@~1.1.2:
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
 
-micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8, micromatch@~4.0.8:
+micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
   integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -8756,10 +9355,10 @@ mime-db@1.52.0:
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
-"mime-db@>= 1.43.0 < 2":
-  version "1.53.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447"
-  integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
+"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0:
+  version "1.54.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
+  integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
 
 mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
@@ -8768,26 +9367,28 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17,
   dependencies:
     mime-db "1.52.0"
 
+mime-types@^3.0.0, mime-types@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce"
+  integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==
+  dependencies:
+    mime-db "^1.54.0"
+
 mime@1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
 mime@^4.0.4:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.4.tgz#9f851b0fc3c289d063b20a7a8055b3014b25664b"
-  integrity sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13"
+  integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==
 
 mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
-mimic-fn@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
-  integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
-
 mimic-function@^5.0.0:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076"
@@ -8927,15 +9528,25 @@ murmurhash-js@^1.0.0:
   integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
 
 nan@^2.19.0, nan@^2.20.0:
-  version "2.22.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3"
-  integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==
+  version "2.22.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
+  integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==
+
+nano-spawn@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.1.tgz#c8e4c1e133e567e3efba44041dcfb12113d861b6"
+  integrity sha512-BfcvzBlUTxSDWfT+oH7vd6CbUV+rThLLHCIym/QO6GGLBsyVXleZs00fto2i2jzC/wPiBYk5jyOmpXWg4YopiA==
 
 nanoid@^3.3.7:
   version "3.3.8"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
   integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
 
+nanoid@^3.3.8:
+  version "3.3.11"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
+  integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -8946,6 +9557,16 @@ negotiator@0.6.3:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
   integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
 
+negotiator@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
+  integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
+
+negotiator@~0.6.4:
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
+  integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
+
 neo-async@^2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
@@ -8976,7 +9597,7 @@ node-int64@^0.4.0:
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
   integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
 
-node-releases@^2.0.18, node-releases@^2.0.19:
+node-releases@^2.0.19:
   version "2.0.19"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
   integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
@@ -9008,13 +9629,6 @@ npm-run-path@^4.0.1:
   dependencies:
     path-key "^3.0.0"
 
-npm-run-path@^5.1.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f"
-  integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==
-  dependencies:
-    path-key "^4.0.0"
-
 nth-check@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
@@ -9032,24 +9646,26 @@ object-assign@^4.1.1:
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
 
-object-inspect@^1.13.1, object-inspect@^1.13.3:
-  version "1.13.3"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a"
-  integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==
+object-inspect@^1.13.3:
+  version "1.13.4"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
+  integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
 
 object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
-object.assign@^4.1.4, object.assign@^4.1.5:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
-  integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
+object.assign@^4.1.4, object.assign@^4.1.7:
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d"
+  integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
   dependencies:
-    call-bind "^1.0.5"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
     define-properties "^1.2.1"
-    has-symbols "^1.0.3"
+    es-object-atoms "^1.0.0"
+    has-symbols "^1.1.0"
     object-keys "^1.1.1"
 
 object.entries@^1.1.8:
@@ -9080,7 +9696,17 @@ object.groupby@^1.0.3:
     define-properties "^1.2.1"
     es-abstract "^1.23.2"
 
-object.values@^1.1.6, object.values@^1.2.0:
+object.values@^1.1.6, object.values@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216"
+  integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==
+  dependencies:
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
+    define-properties "^1.2.1"
+    es-object-atoms "^1.0.0"
+
+object.values@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b"
   integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==
@@ -9094,10 +9720,10 @@ obuf@^1.0.0, obuf@^1.1.2:
   resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
   integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
-oidc-client-ts@3.1.0, oidc-client-ts@^3.0.1:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz#28d3254951a1c64cc9780042c61492a71b2240dd"
-  integrity sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==
+oidc-client-ts@3.2.1, oidc-client-ts@^3.0.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.2.1.tgz#d71d899dc0cddd11a8b84e41265fd79d7e0f152a"
+  integrity sha512-hS5AJ5s/x4bXhHvNJT1v+GGvzHUwdRWqNQQbSrp10L1IRmzfRGKQ3VWN3dstJb+oF3WtAyKezwD2+dTEIyBiAA==
   dependencies:
     jwt-decode "^4.0.0"
 
@@ -9127,13 +9753,6 @@ onetime@^5.1.2:
   dependencies:
     mimic-fn "^2.1.0"
 
-onetime@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4"
-  integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==
-  dependencies:
-    mimic-fn "^4.0.0"
-
 onetime@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60"
@@ -9151,6 +9770,14 @@ open@^10.0.3:
     is-inside-container "^1.0.0"
     is-wsl "^3.1.0"
 
+open@^7.4.2:
+  version "7.4.2"
+  resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+  integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+  dependencies:
+    is-docker "^2.0.0"
+    is-wsl "^2.1.1"
+
 opener@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
@@ -9173,6 +9800,39 @@ opus-recorder@^8.0.3:
   resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.5.tgz#06d3e32e15da57ebc3f57e41b93033475fcb4e3e"
   integrity sha512-tBRXc9Btds7i3bVfA7d5rekAlyOcfsivt5vSIXHxRV1Oa+s6iXFW8omZ0Lm3ABWotVcEyKt96iIIUcgbV07YOw==
 
+os-tmpdir@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+  integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+
+own-keys@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358"
+  integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==
+  dependencies:
+    get-intrinsic "^1.2.6"
+    object-keys "^1.1.1"
+    safe-push-apply "^1.0.0"
+
+oxc-resolver@^9.0.2:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-9.0.2.tgz#0a86ee1e26f6c3f5f2af73dece276f8f588d4ef8"
+  integrity sha512-w838ygc1p7rF+7+h5vR9A+Y9Fc4imy6C3xPthCMkdFUgFvUWkmABeNB8RBDQ6+afk44Q60/UMMQ+gfDUW99fBA==
+  optionalDependencies:
+    "@oxc-resolver/binding-darwin-arm64" "9.0.2"
+    "@oxc-resolver/binding-darwin-x64" "9.0.2"
+    "@oxc-resolver/binding-freebsd-x64" "9.0.2"
+    "@oxc-resolver/binding-linux-arm-gnueabihf" "9.0.2"
+    "@oxc-resolver/binding-linux-arm64-gnu" "9.0.2"
+    "@oxc-resolver/binding-linux-arm64-musl" "9.0.2"
+    "@oxc-resolver/binding-linux-riscv64-gnu" "9.0.2"
+    "@oxc-resolver/binding-linux-s390x-gnu" "9.0.2"
+    "@oxc-resolver/binding-linux-x64-gnu" "9.0.2"
+    "@oxc-resolver/binding-linux-x64-musl" "9.0.2"
+    "@oxc-resolver/binding-wasm32-wasi" "9.0.2"
+    "@oxc-resolver/binding-win32-arm64-msvc" "9.0.2"
+    "@oxc-resolver/binding-win32-x64-msvc" "9.0.2"
+
 p-limit@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -9187,13 +9847,6 @@ p-limit@^3.0.2, p-limit@^3.1.0:
   dependencies:
     yocto-queue "^0.1.0"
 
-p-limit@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644"
-  integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==
-  dependencies:
-    yocto-queue "^1.0.0"
-
 p-locate@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -9208,20 +9861,6 @@ p-locate@^5.0.0:
   dependencies:
     p-limit "^3.0.2"
 
-p-locate@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f"
-  integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==
-  dependencies:
-    p-limit "^4.0.0"
-
-p-map@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
-  integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
-  dependencies:
-    aggregate-error "^3.0.0"
-
 p-retry@4:
   version "4.6.2"
   resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
@@ -9231,9 +9870,9 @@ p-retry@4:
     retry "^0.13.1"
 
 p-retry@^6.2.0:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd"
-  integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af"
+  integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==
   dependencies:
     "@types/retry" "0.12.2"
     is-network-error "^1.0.0"
@@ -9284,11 +9923,6 @@ parse-json@^5.0.0, parse-json@^5.2.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parse-ms@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-4.0.0.tgz#c0c058edd47c2a590151a718990533fd62803df4"
-  integrity sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==
-
 parse-srcset@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
@@ -9301,7 +9935,7 @@ parse5@^7.0.0, parse5@^7.1.1:
   dependencies:
     entities "^4.5.0"
 
-parseurl@~1.3.2, parseurl@~1.3.3:
+parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
@@ -9314,16 +9948,32 @@ pascal-case@^3.1.2:
     no-case "^3.0.4"
     tslib "^2.0.3"
 
+patch-package@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
+  integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
+  dependencies:
+    "@yarnpkg/lockfile" "^1.1.0"
+    chalk "^4.1.2"
+    ci-info "^3.7.0"
+    cross-spawn "^7.0.3"
+    find-yarn-workspace-root "^2.0.0"
+    fs-extra "^9.0.0"
+    json-stable-stringify "^1.0.2"
+    klaw-sync "^6.0.0"
+    minimist "^1.2.6"
+    open "^7.4.2"
+    rimraf "^2.6.3"
+    semver "^7.5.3"
+    slash "^2.0.0"
+    tmp "^0.0.33"
+    yaml "^2.2.2"
+
 path-exists@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
   integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
 
-path-exists@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7"
-  integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==
-
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -9334,11 +9984,6 @@ path-key@^3.0.0, path-key@^3.1.0:
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
-path-key@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
-  integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
-
 path-parse@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@@ -9370,16 +10015,16 @@ path-to-regexp@^2.2.1:
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
   integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
 
+path-to-regexp@^8.0.0:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4"
+  integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==
+
 path-type@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
-path-type@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8"
-  integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==
-
 pbf@^3.2.1, pbf@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f"
@@ -9403,7 +10048,7 @@ picomatch@^4.0.1, picomatch@^4.0.2:
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
   integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
 
-pidtree@~0.6.0:
+pidtree@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"
   integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==
@@ -9425,24 +10070,17 @@ pkg-dir@^4.2.0:
   dependencies:
     find-up "^4.0.0"
 
-pkg-dir@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11"
-  integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==
-  dependencies:
-    find-up "^6.3.0"
-
-playwright-core@1.49.1, playwright-core@^1.45.1:
-  version "1.49.1"
-  resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.1.tgz#32c62f046e950f586ff9e35ed490a424f2248015"
-  integrity sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==
+playwright-core@1.52.0, playwright-core@^1.51.0:
+  version "1.52.0"
+  resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca"
+  integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==
 
-playwright@1.49.1:
-  version "1.49.1"
-  resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.1.tgz#830266dbca3008022afa7b4783565db9944ded7c"
-  integrity sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==
+playwright@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.52.0.tgz#26cb9a63346651e1c54c8805acfd85683173d4bd"
+  integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==
   dependencies:
-    playwright-core "1.49.1"
+    playwright-core "1.52.0"
   optionalDependencies:
     fsevents "2.3.2"
 
@@ -9481,11 +10119,11 @@ postcss-attribute-case-insensitive@^7.0.1:
     postcss-selector-parser "^7.0.0"
 
 postcss-calc@^10.0.2:
-  version "10.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-10.0.2.tgz#15f01635a27b9d38913a98c4ef2877f5b715b439"
-  integrity sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==
+  version "10.1.1"
+  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-10.1.1.tgz#52b385f2e628239686eb6e3a16207a43f36064ca"
+  integrity sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==
   dependencies:
-    postcss-selector-parser "^6.1.2"
+    postcss-selector-parser "^7.0.0"
     postcss-value-parser "^4.2.0"
 
 postcss-clamp@^4.1.0:
@@ -10036,6 +10674,14 @@ postcss-selector-parser@^7.0.0:
     cssesc "^3.0.0"
     util-deprecate "^1.0.2"
 
+postcss-selector-parser@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262"
+  integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==
+  dependencies:
+    cssesc "^3.0.0"
+    util-deprecate "^1.0.2"
+
 postcss-simple-vars@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c"
@@ -10070,7 +10716,7 @@ postcss@8.4.46:
     picocolors "^1.1.0"
     source-map-js "^1.2.1"
 
-postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
+postcss@^8.3.11, postcss@^8.4.33:
   version "8.4.47"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
   integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
@@ -10079,14 +10725,24 @@ postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
     picocolors "^1.1.0"
     source-map-js "^1.2.1"
 
-posthog-js@1.157.2:
-  version "1.157.2"
-  resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.157.2.tgz#dc2515818ead408aefb900e90c535fb57beb1f59"
-  integrity sha512-ATYKGs+Q51u26nHHhrhWNh1whqFm7j/rwQQYw+y6/YzNmRlo+YsqrGZji9nqXb9/4fo0ModDr+ZmuOI3hKkUXA==
+postcss@^8.4.40, postcss@^8.5.3:
+  version "8.5.3"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
+  integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
+  dependencies:
+    nanoid "^3.3.8"
+    picocolors "^1.1.1"
+    source-map-js "^1.2.1"
+
+posthog-js@1.246.0:
+  version "1.246.0"
+  resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.246.0.tgz#8ce595d0be3487629c988a058f350d6d83ac0531"
+  integrity sha512-5lN/UMqDfxsLeSnT3LsY4P+eD1H+P9qxgN/iUk473LmhCM7IV8TAfdjJj23nnXk/euGNQtvyINYoD2DG+d4eEw==
   dependencies:
+    core-js "^3.38.1"
     fflate "^0.4.8"
     preact "^10.19.3"
-    web-vitals "^4.0.1"
+    web-vitals "^4.2.4"
 
 potpack@^2.0.0:
   version "2.0.0"
@@ -10103,10 +10759,10 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prettier@3.4.2:
-  version "3.4.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
-  integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
+prettier@3.5.3:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
+  integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
 
 prettier@^2.6.2:
   version "2.8.8"
@@ -10139,13 +10795,6 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
     ansi-styles "^5.0.0"
     react-is "^18.0.0"
 
-pretty-ms@^9.0.0:
-  version "9.2.0"
-  resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-9.2.0.tgz#e14c0aad6493b69ed63114442a84133d7e560ef0"
-  integrity sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==
-  dependencies:
-    parse-ms "^4.0.0"
-
 process-nextick-args@~2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -10194,12 +10843,30 @@ properties-reader@^2.3.0:
   dependencies:
     mkdirp "^1.0.4"
 
+protobufjs@^7.2.5, protobufjs@^7.3.2:
+  version "7.4.0"
+  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a"
+  integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==
+  dependencies:
+    "@protobufjs/aspromise" "^1.1.2"
+    "@protobufjs/base64" "^1.1.2"
+    "@protobufjs/codegen" "^2.0.4"
+    "@protobufjs/eventemitter" "^1.1.0"
+    "@protobufjs/fetch" "^1.1.0"
+    "@protobufjs/float" "^1.0.2"
+    "@protobufjs/inquire" "^1.1.0"
+    "@protobufjs/path" "^1.1.2"
+    "@protobufjs/pool" "^1.1.0"
+    "@protobufjs/utf8" "^1.1.0"
+    "@types/node" ">=13.7.0"
+    long "^5.0.0"
+
 protocol-buffers-schema@^3.3.1:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
   integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
 
-proxy-addr@~2.0.7:
+proxy-addr@^2.0.7, proxy-addr@~2.0.7:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
   integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
@@ -10263,6 +10930,13 @@ qs@6.13.0:
   dependencies:
     side-channel "^1.0.6"
 
+qs@^6.14.0:
+  version "6.14.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
+  integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
+  dependencies:
+    side-channel "^1.1.0"
+
 querystring@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
@@ -10278,11 +10952,6 @@ queue-microtask@^1.2.2:
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
-queue-tick@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
-  integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
-
 quickselect@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
@@ -10315,6 +10984,16 @@ raw-body@2.5.2:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
+raw-body@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f"
+  integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==
+  dependencies:
+    bytes "3.1.2"
+    http-errors "2.0.0"
+    iconv-lite "0.6.3"
+    unpipe "1.0.0"
+
 raw-loader@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
@@ -10323,10 +11002,10 @@ raw-loader@^4.0.2:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
-re-resizable@6.10.3:
-  version "6.10.3"
-  resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.10.3.tgz#72c42532ede0cbcaf93308bcbfed782abbf97e79"
-  integrity sha512-zvWb7X3RJMA4cuSrqoxgs3KR+D+pEXnGrD2FAD6BMYAULnZsSF4b7AOVyG6pC3VVNVOtlagGDCDmZSwWLjjBBw==
+re-resizable@6.11.2:
+  version "6.11.2"
+  resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.11.2.tgz#2e8f7119ca3881d5b5aea0ffa014a80e5c1252b3"
+  integrity sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==
 
 react-beautiful-dnd@^13.1.0:
   version "13.1.1"
@@ -10346,53 +11025,57 @@ react-blurhash@^0.3.0:
   resolved "https://registry.yarnpkg.com/react-blurhash/-/react-blurhash-0.3.0.tgz#f125801d052644f74420c79b05981691d17c9705"
   integrity sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==
 
-react-clientside-effect@^1.2.6:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a"
-  integrity sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==
+react-clientside-effect@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.7.tgz#78eb62e3be36208d4d8d5b2668ae630a32deca73"
+  integrity sha512-gce9m0Pk/xYYMEojRI9bgvqQAkl6hm7ozQvqWPyQx+kULiatdHgkNM1QG4DQRx5N9BAzWSCJmt9mMV8/KsdgVg==
   dependencies:
     "@babel/runtime" "^7.12.13"
 
-react-dom@^18.3.1:
-  version "18.3.1"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
-  integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+react-dom@^19.0.0:
+  version "19.1.0"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623"
+  integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
   dependencies:
-    loose-envify "^1.1.0"
-    scheduler "^0.23.2"
+    scheduler "^0.26.0"
 
 react-focus-lock@^2.5.1:
-  version "2.13.2"
-  resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.13.2.tgz#e1addac2f8b9550bc0581f3c416755ba0f81f5ef"
-  integrity sha512-T/7bsofxYqnod2xadvuwjGKHOoL5GH7/EIPI5UyEvaU/c2CcphvGI371opFtuY/SYdbMsNiuF4HsHQ50nA/TKQ==
+  version "2.13.6"
+  resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.13.6.tgz#29751bf2e4e30f6248673cd87a347c74ff2af672"
+  integrity sha512-ehylFFWyYtBKXjAO9+3v8d0i+cnc1trGS0vlTGhzFW1vbFXVUTmR8s2tt/ZQG8x5hElg6rhENlLG1H3EZK0Llg==
   dependencies:
     "@babel/runtime" "^7.0.0"
-    focus-lock "^1.3.5"
+    focus-lock "^1.3.6"
     prop-types "^15.6.2"
-    react-clientside-effect "^1.2.6"
-    use-callback-ref "^1.3.2"
-    use-sidecar "^1.1.2"
+    react-clientside-effect "^1.2.7"
+    use-callback-ref "^1.3.3"
+    use-sidecar "^1.1.3"
+
+react-is@19.1.0, react-is@^17.0.1, react-is@^18.0.0:
+  version "19.1.0"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.1.0.tgz#805bce321546b7e14c084989c77022351bbdd11b"
+  integrity sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==
 
 react-is@^16.13.1, react-is@^16.7.0:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-is@^17.0.1, react-is@^17.0.2:
+react-is@^17.0.2:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
   integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
 
-react-is@^18.0.0:
-  version "18.3.1"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
-  integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-
 react-lifecycles-compat@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
+react-property@2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
+  integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
+
 react-redux@^7.2.0:
   version "7.2.9"
   resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
@@ -10424,6 +11107,11 @@ react-remove-scroll@2.6.0:
     use-callback-ref "^1.3.0"
     use-sidecar "^1.1.2"
 
+react-string-replace@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-1.1.1.tgz#8413a598c60e397fe77df3464f2889f00ba25989"
+  integrity sha512-26TUbLzLfHQ5jO5N7y3Mx88eeKo0Ml0UjCQuX4BMfOd/JX+enQqlKpL1CZnmjeBRvQE8TR+ds9j1rqx9CxhKHQ==
+
 react-style-singleton@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
@@ -10444,9 +11132,9 @@ react-transition-group@^4.4.1:
     prop-types "^15.6.2"
 
 react-virtualized@^9.22.5:
-  version "9.22.5"
-  resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.5.tgz#bfb96fed519de378b50d8c0064b92994b3b91620"
-  integrity sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==
+  version "9.22.6"
+  resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.6.tgz#3ae2aa69eca61cf3af332e2f9d6b4aa5638786d5"
+  integrity sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==
   dependencies:
     "@babel/runtime" "^7.7.2"
     clsx "^1.0.4"
@@ -10455,12 +11143,10 @@ react-virtualized@^9.22.5:
     prop-types "^15.7.2"
     react-lifecycles-compat "^3.0.4"
 
-react@^18.3.1:
-  version "18.3.1"
-  resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
-  integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
-  dependencies:
-    loose-envify "^1.1.0"
+react@^19.0.0:
+  version "19.1.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
+  integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
 
 read-cache@^1.0.0:
   version "1.0.0"
@@ -10511,9 +11197,9 @@ readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable
     util-deprecate "^1.0.1"
 
 readable-stream@^4.0.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.6.0.tgz#ce412dfb19c04efde1c5936d99c27f37a1ff94c9"
-  integrity sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw==
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91"
+  integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
   dependencies:
     abort-controller "^3.0.0"
     buffer "^6.0.3"
@@ -10529,9 +11215,9 @@ readdir-glob@^1.1.2:
     minimatch "^5.1.0"
 
 readdirp@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
-  integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55"
+  integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==
 
 readdirp@~3.6.0:
   version "3.6.0"
@@ -10567,18 +11253,19 @@ reflect-metadata@^0.1.13:
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859"
   integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==
 
-reflect.getprototypeof@^1.0.4, reflect.getprototypeof@^1.0.6:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz#04311b33a1b713ca5eb7b5aed9950a86481858e5"
-  integrity sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==
+reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
+  integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
     define-properties "^1.2.1"
-    es-abstract "^1.23.5"
+    es-abstract "^1.23.9"
     es-errors "^1.3.0"
-    get-intrinsic "^1.2.4"
-    gopd "^1.0.1"
-    which-builtin-type "^1.1.4"
+    es-object-atoms "^1.0.0"
+    get-intrinsic "^1.2.7"
+    get-proto "^1.0.1"
+    which-builtin-type "^1.2.1"
 
 regenerate-unicode-properties@^10.2.0:
   version "10.2.0"
@@ -10592,42 +11279,32 @@ regenerate@^1.4.2:
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
   integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
 
-regenerator-runtime@^0.14.0:
-  version "0.14.1"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
-  integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
-
-regenerator-transform@^0.15.2:
-  version "0.15.2"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4"
-  integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==
-  dependencies:
-    "@babel/runtime" "^7.8.4"
-
 regexp-tree@^0.1.27:
   version "0.1.27"
   resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
   integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==
 
-regexp.prototype.flags@^1.5.2, regexp.prototype.flags@^1.5.3:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42"
-  integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==
+regexp.prototype.flags@^1.5.3:
+  version "1.5.4"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19"
+  integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
     define-properties "^1.2.1"
     es-errors "^1.3.0"
+    get-proto "^1.0.1"
+    gopd "^1.2.0"
     set-function-name "^2.0.2"
 
-regexpu-core@^6.1.1:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.1.1.tgz#b469b245594cb2d088ceebc6369dceb8c00becac"
-  integrity sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==
+regexpu-core@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826"
+  integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==
   dependencies:
     regenerate "^1.4.2"
     regenerate-unicode-properties "^10.2.0"
     regjsgen "^0.8.0"
-    regjsparser "^0.11.0"
+    regjsparser "^0.12.0"
     unicode-match-property-ecmascript "^2.0.0"
     unicode-match-property-value-ecmascript "^2.1.0"
 
@@ -10643,10 +11320,10 @@ regjsparser@^0.10.0:
   dependencies:
     jsesc "~0.5.0"
 
-regjsparser@^0.11.0:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.11.2.tgz#7404ad42be00226d72bcf1f003f1f441861913d8"
-  integrity sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==
+regjsparser@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc"
+  integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==
   dependencies:
     jsesc "~3.0.2"
 
@@ -10720,7 +11397,7 @@ resolve.exports@^2.0.0:
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
   integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
 
-resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.22.8:
+resolve@^1.1.7, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.22.8:
   version "1.22.8"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
   integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -10729,6 +11406,15 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22
     path-parse "^1.0.7"
     supports-preserve-symlinks-flag "^1.0.0"
 
+resolve@^1.14.2:
+  version "1.22.10"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
+  integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
+  dependencies:
+    is-core-module "^2.16.0"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
 resolve@^2.0.0-next.5:
   version "2.0.0-next.5"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c"
@@ -10757,20 +11443,27 @@ retry@^0.13.1:
   integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
 
 reusify@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
-  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
+  integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
 
 rfc4648@^1.4.0:
-  version "1.5.3"
-  resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.3.tgz#e62b81736c10361ca614efe618a566e93d0b41c0"
-  integrity sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==
+  version "1.5.4"
+  resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.4.tgz#1174c0afba72423a0b70c386ecfeb80aa61b05ca"
+  integrity sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==
 
 rfdc@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
   integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
 
+rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
 rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -10786,6 +11479,17 @@ rimraf@^6.0.0:
     glob "^11.0.0"
     package-json-from-dist "^1.0.0"
 
+router@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
+  integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
+  dependencies:
+    debug "^4.4.0"
+    depd "^2.0.0"
+    is-promise "^4.0.0"
+    parseurl "^1.3.3"
+    path-to-regexp "^8.0.0"
+
 run-applescript@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb"
@@ -10810,34 +11514,43 @@ rxjs@^7.8.1:
   dependencies:
     tslib "^2.1.0"
 
-safe-array-concat@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb"
-  integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==
+safe-array-concat@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3"
+  integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==
   dependencies:
-    call-bind "^1.0.7"
-    get-intrinsic "^1.2.4"
-    has-symbols "^1.0.3"
+    call-bind "^1.0.8"
+    call-bound "^1.0.2"
+    get-intrinsic "^1.2.6"
+    has-symbols "^1.1.0"
     isarray "^2.0.5"
 
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
 safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
-safe-regex-test@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
-  integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-push-apply@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
+  integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==
+  dependencies:
+    es-errors "^1.3.0"
+    isarray "^2.0.5"
+
+safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1"
+  integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==
   dependencies:
-    call-bind "^1.0.6"
+    call-bound "^1.0.2"
     es-errors "^1.3.0"
-    is-regex "^1.1.4"
+    is-regex "^1.2.1"
 
 "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0:
   version "2.1.2"
@@ -10851,10 +11564,10 @@ sanitize-filename@^1.6.3:
   dependencies:
     truncate-utf8-bytes "^1.0.0"
 
-sanitize-html@2.14.0:
-  version "2.14.0"
-  resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.14.0.tgz#bd2a7b97ee1d86a7f0e0babf3a4468f639c3a429"
-  integrity sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g==
+sanitize-html@2.17.0:
+  version "2.17.0"
+  resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.0.tgz#a8f66420a6be981d8fe412e3397cc753782598e4"
+  integrity sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==
   dependencies:
     deepmerge "^4.2.2"
     escape-string-regexp "^4.0.0"
@@ -10870,14 +11583,12 @@ saxes@^6.0.0:
   dependencies:
     xmlchars "^2.2.0"
 
-scheduler@^0.23.2:
-  version "0.23.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
-  integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
-  dependencies:
-    loose-envify "^1.1.0"
+scheduler@^0.26.0:
+  version "0.26.0"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
+  integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
 
-schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
+schema-utils@^3.0.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
   integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
@@ -10887,9 +11598,19 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
     ajv-keywords "^3.5.2"
 
 schema-utils@^4.0.0, schema-utils@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"
-  integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0"
+  integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==
+  dependencies:
+    "@types/json-schema" "^7.0.9"
+    ajv "^8.9.0"
+    ajv-formats "^2.1.1"
+    ajv-keywords "^5.1.0"
+
+schema-utils@^4.3.0, schema-utils@^4.3.2:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae"
+  integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==
   dependencies:
     "@types/json-schema" "^7.0.9"
     ajv "^8.9.0"
@@ -10930,9 +11651,9 @@ semver@^6.3.0, semver@^6.3.1:
   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
 semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3:
-  version "7.6.3"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
-  integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+  version "7.7.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
+  integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
 
 send@0.19.0:
   version "0.19.0"
@@ -10953,7 +11674,24 @@ send@0.19.0:
     range-parser "~1.2.1"
     statuses "2.0.1"
 
-serialize-javascript@^6.0.1, serialize-javascript@^6.0.2:
+send@^1.1.0, send@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212"
+  integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==
+  dependencies:
+    debug "^4.3.5"
+    encodeurl "^2.0.0"
+    escape-html "^1.0.3"
+    etag "^1.8.1"
+    fresh "^2.0.0"
+    http-errors "^2.0.0"
+    mime-types "^3.0.1"
+    ms "^2.1.3"
+    on-finished "^2.4.1"
+    range-parser "^1.2.1"
+    statuses "^2.0.1"
+
+serialize-javascript@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
   integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
@@ -10983,12 +11721,22 @@ serve-static@1.16.2:
     parseurl "~1.3.3"
     send "0.19.0"
 
+serve-static@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9"
+  integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==
+  dependencies:
+    encodeurl "^2.0.0"
+    escape-html "^1.0.3"
+    parseurl "^1.3.3"
+    send "^1.2.0"
+
 set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
 
-set-function-length@^1.2.1, set-function-length@^1.2.2:
+set-function-length@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
   integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
@@ -11000,7 +11748,7 @@ set-function-length@^1.2.1, set-function-length@^1.2.2:
     gopd "^1.0.1"
     has-property-descriptors "^1.0.2"
 
-set-function-name@^2.0.1, set-function-name@^2.0.2:
+set-function-name@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
   integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
@@ -11010,6 +11758,15 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
     functions-have-names "^1.2.3"
     has-property-descriptors "^1.0.2"
 
+set-proto@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e"
+  integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==
+  dependencies:
+    dunder-proto "^1.0.1"
+    es-errors "^1.3.0"
+    es-object-atoms "^1.0.0"
+
 setimmediate@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@@ -11049,15 +11806,45 @@ shell-quote@^1.8.1:
   resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a"
   integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==
 
-side-channel@^1.0.4, side-channel@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
-  integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+side-channel-list@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
+  integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
   dependencies:
-    call-bind "^1.0.7"
     es-errors "^1.3.0"
-    get-intrinsic "^1.2.4"
-    object-inspect "^1.13.1"
+    object-inspect "^1.13.3"
+
+side-channel-map@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
+  integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+  dependencies:
+    call-bound "^1.0.2"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.5"
+    object-inspect "^1.13.3"
+
+side-channel-weakmap@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
+  integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
+  dependencies:
+    call-bound "^1.0.2"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.5"
+    object-inspect "^1.13.3"
+    side-channel-map "^1.0.1"
+
+side-channel@^1.0.6, side-channel@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
+  integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+  dependencies:
+    es-errors "^1.3.0"
+    object-inspect "^1.13.3"
+    side-channel-list "^1.0.0"
+    side-channel-map "^1.0.1"
+    side-channel-weakmap "^1.0.2"
 
 signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
   version "3.0.7"
@@ -11083,16 +11870,16 @@ sisteransi@^1.0.5:
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
+slash@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+  integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
 slash@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
-slash@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
-  integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
-
 slice-ansi@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
@@ -11119,9 +11906,9 @@ slice-ansi@^7.1.0:
     is-fullwidth-code-point "^5.0.0"
 
 smol-toml@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.1.tgz#d9084a9e212142e3cab27ef4e2b8e8ba620bfe15"
-  integrity sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.4.tgz#4ec76e0e709f586bc50ba30eb79024173c2b2221"
+  integrity sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==
 
 snake-case@^3.0.4:
   version "3.0.4"
@@ -11246,7 +12033,7 @@ ssh-remote-port-forward@^1.0.4:
     "@types/ssh2" "^0.5.48"
     ssh2 "^1.4.0"
 
-ssh2@^1.11.0, ssh2@^1.4.0:
+ssh2@^1.15.0, ssh2@^1.4.0:
   version "1.16.0"
   resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.16.0.tgz#79221d40cbf4d03d07fe881149de0a9de928c9f0"
   integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==
@@ -11264,7 +12051,7 @@ stack-utils@^2.0.3:
   dependencies:
     escape-string-regexp "^2.0.0"
 
-statuses@2.0.1:
+statuses@2.0.1, statuses@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
   integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
@@ -11275,17 +12062,16 @@ statuses@2.0.1:
   integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
 
 streamx@^2.15.0, streamx@^2.21.0:
-  version "2.21.1"
-  resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.1.tgz#f02979d8395b6b637d08a589fb514498bed55845"
-  integrity sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==
+  version "2.22.0"
+  resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7"
+  integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==
   dependencies:
     fast-fifo "^1.3.2"
-    queue-tick "^1.0.1"
     text-decoder "^1.1.0"
   optionalDependencies:
     bare-events "^2.2.0"
 
-string-argv@~0.3.2:
+string-argv@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
   integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
@@ -11343,23 +12129,24 @@ string.prototype.includes@^2.0.1:
     define-properties "^1.2.1"
     es-abstract "^1.23.3"
 
-string.prototype.matchall@^4.0.11:
-  version "4.0.11"
-  resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"
-  integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==
+string.prototype.matchall@^4.0.12:
+  version "4.0.12"
+  resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0"
+  integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
     define-properties "^1.2.1"
-    es-abstract "^1.23.2"
+    es-abstract "^1.23.6"
     es-errors "^1.3.0"
     es-object-atoms "^1.0.0"
-    get-intrinsic "^1.2.4"
-    gopd "^1.0.1"
-    has-symbols "^1.0.3"
-    internal-slot "^1.0.7"
-    regexp.prototype.flags "^1.5.2"
+    get-intrinsic "^1.2.6"
+    gopd "^1.2.0"
+    has-symbols "^1.1.0"
+    internal-slot "^1.1.0"
+    regexp.prototype.flags "^1.5.3"
     set-function-name "^2.0.2"
-    side-channel "^1.0.6"
+    side-channel "^1.1.0"
 
 string.prototype.repeat@^1.0.0:
   version "1.0.0"
@@ -11369,22 +12156,26 @@ string.prototype.repeat@^1.0.0:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
 
-string.prototype.trim@^1.2.9:
-  version "1.2.9"
-  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4"
-  integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==
+string.prototype.trim@^1.2.10:
+  version "1.2.10"
+  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81"
+  integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
+    call-bound "^1.0.2"
+    define-data-property "^1.1.4"
     define-properties "^1.2.1"
-    es-abstract "^1.23.0"
+    es-abstract "^1.23.5"
     es-object-atoms "^1.0.0"
+    has-property-descriptors "^1.0.2"
 
-string.prototype.trimend@^1.0.8:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229"
-  integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==
+string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942"
+  integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
+    call-bound "^1.0.2"
     define-properties "^1.2.1"
     es-object-atoms "^1.0.0"
 
@@ -11447,11 +12238,6 @@ strip-final-newline@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
   integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
 
-strip-final-newline@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
-  integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
-
 strip-indent@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
@@ -11469,6 +12255,20 @@ strip-json-comments@^3.1.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
+style-to-js@1.1.16:
+  version "1.1.16"
+  resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a"
+  integrity sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==
+  dependencies:
+    style-to-object "1.0.8"
+
+style-to-object@1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292"
+  integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==
+  dependencies:
+    inline-style-parser "0.2.4"
+
 stylehacks@^7.0.4:
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-7.0.4.tgz#9c21f7374f4bccc0082412b859b3c89d77d3277c"
@@ -11477,30 +12277,30 @@ stylehacks@^7.0.4:
     browserslist "^4.23.3"
     postcss-selector-parser "^6.1.2"
 
-stylelint-config-recommended@^14.0.1:
-  version "14.0.1"
-  resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz#d25e86409aaf79ee6c6085c2c14b33c7e23c90c6"
-  integrity sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==
+stylelint-config-recommended@^16.0.0:
+  version "16.0.0"
+  resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz#0221f19902816fe7d53d9a01eb0be4cc7b4fe80a"
+  integrity sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==
 
-stylelint-config-standard@^36.0.0:
-  version "36.0.1"
-  resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz#727cbb2a1ef3e210f5ce8329cde531129f156609"
-  integrity sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==
+stylelint-config-standard@^38.0.0:
+  version "38.0.0"
+  resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz#9d673ec1f35d7569476ee4b0582e7dd5faebf036"
+  integrity sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==
   dependencies:
-    stylelint-config-recommended "^14.0.1"
+    stylelint-config-recommended "^16.0.0"
 
 stylelint-scss@^6.0.0:
-  version "6.10.0"
-  resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.10.0.tgz#ba5b807793e145421e9879dd15ae672af6820a45"
-  integrity sha512-y03if6Qw9xBMoVaf7tzp5BbnYhYvudIKzURkhSHzcHG0bW0fAYvQpTUVJOe7DyhHaxeThBil4ObEMvGbV7+M+w==
+  version "6.12.0"
+  resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.12.0.tgz#38cf41c3b8a76f34cd7267e4c30e7e66d35619c2"
+  integrity sha512-U7CKhi1YNkM1pXUXl/GMUXi8xKdhl4Ayxdyceie1nZ1XNIdaUgMV6OArpooWcDzEggwgYD0HP/xIgVJo9a655w==
   dependencies:
     css-tree "^3.0.1"
     is-plain-object "^5.0.0"
-    known-css-properties "^0.35.0"
-    mdn-data "^2.12.2"
+    known-css-properties "^0.36.0"
+    mdn-data "^2.21.0"
     postcss-media-query-parser "^0.2.3"
     postcss-resolve-nested-selector "^0.1.6"
-    postcss-selector-parser "^7.0.0"
+    postcss-selector-parser "^7.1.0"
     postcss-value-parser "^4.2.0"
 
 stylelint-value-no-unknown-custom-properties@^6.0.1:
@@ -11511,48 +12311,48 @@ stylelint-value-no-unknown-custom-properties@^6.0.1:
     postcss-value-parser "^4.2.0"
     resolve "^1.22.8"
 
-stylelint@^16.1.0:
-  version "16.10.0"
-  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.10.0.tgz#452b42a5d82f2ad910954eb2ba2b3a2ec583cd75"
-  integrity sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==
+stylelint@^16.13.0:
+  version "16.19.1"
+  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.19.1.tgz#486b95fa7518a3077ee2802bc6dda2174bc097bb"
+  integrity sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==
   dependencies:
-    "@csstools/css-parser-algorithms" "^3.0.1"
-    "@csstools/css-tokenizer" "^3.0.1"
-    "@csstools/media-query-list-parser" "^3.0.1"
-    "@csstools/selector-specificity" "^4.0.0"
+    "@csstools/css-parser-algorithms" "^3.0.4"
+    "@csstools/css-tokenizer" "^3.0.3"
+    "@csstools/media-query-list-parser" "^4.0.2"
+    "@csstools/selector-specificity" "^5.0.0"
     "@dual-bundle/import-meta-resolve" "^4.1.0"
     balanced-match "^2.0.0"
     colord "^2.9.3"
     cosmiconfig "^9.0.0"
     css-functions-list "^3.2.3"
-    css-tree "^3.0.0"
+    css-tree "^3.1.0"
     debug "^4.3.7"
-    fast-glob "^3.3.2"
+    fast-glob "^3.3.3"
     fastest-levenshtein "^1.0.16"
-    file-entry-cache "^9.1.0"
+    file-entry-cache "^10.0.8"
     global-modules "^2.0.0"
     globby "^11.1.0"
     globjoin "^0.1.4"
     html-tags "^3.3.1"
-    ignore "^6.0.2"
+    ignore "^7.0.3"
     imurmurhash "^0.1.4"
     is-plain-object "^5.0.0"
-    known-css-properties "^0.34.0"
+    known-css-properties "^0.36.0"
     mathml-tag-names "^2.1.3"
     meow "^13.2.0"
     micromatch "^4.0.8"
     normalize-path "^3.0.0"
-    picocolors "^1.0.1"
-    postcss "^8.4.47"
+    picocolors "^1.1.1"
+    postcss "^8.5.3"
     postcss-resolve-nested-selector "^0.1.6"
     postcss-safe-parser "^7.0.1"
-    postcss-selector-parser "^6.1.2"
+    postcss-selector-parser "^7.1.0"
     postcss-value-parser "^4.2.0"
     resolve-from "^5.0.0"
     string-width "^4.2.3"
-    supports-hyperlinks "^3.1.0"
+    supports-hyperlinks "^3.2.0"
     svg-tags "^1.0.0"
-    table "^6.8.2"
+    table "^6.9.0"
     write-file-atomic "^5.0.1"
 
 sugarss@^4.0.1:
@@ -11560,11 +12360,6 @@ sugarss@^4.0.1:
   resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-4.0.1.tgz#128a783ed71ee0fc3b489ce1f7d5a89bc1e24383"
   integrity sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==
 
-summary@2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/summary/-/summary-2.1.0.tgz#be8a49a0aa34eb6ceea56042cae88f8add4b0885"
-  integrity sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==
-
 supercluster@^8.0.1:
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
@@ -11593,10 +12388,10 @@ supports-color@^8.0.0, supports-color@^8.1.1:
   dependencies:
     has-flag "^4.0.0"
 
-supports-hyperlinks@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac"
-  integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==
+supports-hyperlinks@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz#b8e485b179681dea496a1e7abdf8985bd3145461"
+  integrity sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==
   dependencies:
     has-flag "^4.0.0"
     supports-color "^7.0.0"
@@ -11639,10 +12434,10 @@ tabbable@^6.0.0:
   resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
   integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
 
-table@^6.8.2:
-  version "6.8.2"
-  resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58"
-  integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==
+table@^6.9.0:
+  version "6.9.0"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5"
+  integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==
   dependencies:
     ajv "^8.0.1"
     lodash.truncate "^4.4.2"
@@ -11650,38 +12445,43 @@ table@^6.8.2:
     string-width "^4.2.3"
     strip-ansi "^6.0.1"
 
-tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
+tapable@^2.0.0, tapable@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
   integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
 
-tar-fs@^3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217"
-  integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==
+tapable@^2.1.1, tapable@^2.2.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.2.tgz#ab4984340d30cb9989a490032f086dbb8b56d872"
+  integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==
+
+tar-fs@^3.0.9:
+  version "3.0.9"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.9.tgz#d570793c6370d7078926c41fa422891566a0b617"
+  integrity sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==
   dependencies:
     pump "^3.0.0"
     tar-stream "^3.1.5"
   optionalDependencies:
-    bare-fs "^2.1.1"
-    bare-path "^2.1.0"
+    bare-fs "^4.0.1"
+    bare-path "^3.0.0"
 
-tar-fs@~2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
-  integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==
+tar-fs@~2.1.2:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92"
+  integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==
   dependencies:
     chownr "^1.1.1"
     mkdirp-classic "^0.5.2"
     pump "^3.0.0"
-    tar-stream "^2.0.0"
+    tar-stream "^2.1.4"
 
 tar-js@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/tar-js/-/tar-js-0.3.0.tgz#6949aabfb0ba18bb1562ae51a439fd0f30183a17"
   integrity sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA==
 
-tar-stream@^2.0.0:
+tar-stream@^2.1.4:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
   integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
@@ -11701,28 +12501,28 @@ tar-stream@^3.0.0, tar-stream@^3.1.5:
     fast-fifo "^1.2.0"
     streamx "^2.15.0"
 
-temporal-polyfill@^0.2.5:
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/temporal-polyfill/-/temporal-polyfill-0.2.5.tgz#0796c40a50754c69ec0f9a2db3f6c582b9721aaf"
-  integrity sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==
+temporal-polyfill@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/temporal-polyfill/-/temporal-polyfill-0.3.0.tgz#7fe90e913ac5ec8e0d508fb50d04dd7a74cec23e"
+  integrity sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g==
   dependencies:
-    temporal-spec "^0.2.4"
+    temporal-spec "0.3.0"
 
-temporal-spec@^0.2.4:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/temporal-spec/-/temporal-spec-0.2.4.tgz#7eb10447a62429ffaaa80b42b869b138ae306a75"
-  integrity sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==
+temporal-spec@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/temporal-spec/-/temporal-spec-0.3.0.tgz#8c4210c575fb28ba0a1c2e02ad68d1be5956a11f"
+  integrity sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ==
 
-terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.9:
-  version "5.3.10"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199"
-  integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==
+terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.9:
+  version "5.3.14"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06"
+  integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==
   dependencies:
-    "@jridgewell/trace-mapping" "^0.3.20"
+    "@jridgewell/trace-mapping" "^0.3.25"
     jest-worker "^27.4.5"
-    schema-utils "^3.1.1"
-    serialize-javascript "^6.0.1"
-    terser "^5.26.0"
+    schema-utils "^4.3.0"
+    serialize-javascript "^6.0.2"
+    terser "^5.31.1"
 
 terser@^5.10.0:
   version "5.36.0"
@@ -11734,13 +12534,13 @@ terser@^5.10.0:
     commander "^2.20.0"
     source-map-support "~0.5.20"
 
-terser@^5.26.0:
-  version "5.37.0"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-5.37.0.tgz#38aa66d1cfc43d0638fab54e43ff8a4f72a21ba3"
-  integrity sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==
+terser@^5.31.1:
+  version "5.40.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.40.0.tgz#839a80db42bfee8340085f44ea99b5cba36c55c8"
+  integrity sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==
   dependencies:
     "@jridgewell/source-map" "^0.3.3"
-    acorn "^8.8.2"
+    acorn "^8.14.0"
     commander "^2.20.0"
     source-map-support "~0.5.20"
 
@@ -11753,26 +12553,26 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
-testcontainers@^10.16.0:
-  version "10.16.0"
-  resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-10.16.0.tgz#8a7e69ada5cd2c6cce1c6db72b3a3e8e412fcaf6"
-  integrity sha512-oxPLuOtrRWS11A+Yn0+zXB7GkmNarflWqmy6CQJk8KJ75LZs2/zlUXDpizTbPpCGtk4kE2EQYwFZjrE967F8Wg==
+testcontainers@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-11.0.0.tgz#248d8e59b922c993da378e6ef8bb018c0fc29330"
+  integrity sha512-8zY2V+eovC6aylgMqMR3A7H+un2gqpqepbvBCnjo7QP2fpI0pJZhSus+A5TckHpF2CR2d1Zj/IQ5rNPW/HjS6g==
   dependencies:
     "@balena/dockerignore" "^1.0.2"
-    "@types/dockerode" "^3.3.29"
+    "@types/dockerode" "^3.3.39"
     archiver "^7.0.1"
     async-lock "^1.4.1"
     byline "^5.0.0"
-    debug "^4.3.5"
-    docker-compose "^0.24.8"
-    dockerode "^3.3.5"
-    get-port "^5.1.1"
+    debug "^4.4.1"
+    docker-compose "^1.2.0"
+    dockerode "^4.0.6"
+    get-port "^7.1.0"
     proper-lockfile "^4.1.2"
     properties-reader "^2.3.0"
     ssh-remote-port-forward "^1.0.4"
-    tar-fs "^3.0.6"
+    tar-fs "^3.0.9"
     tmp "^0.2.3"
-    undici "^5.28.4"
+    undici "^7.10.0"
 
 text-decoder@^1.1.0:
   version "1.2.3"
@@ -11801,6 +12601,14 @@ tiny-invariant@^1.0.6:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
   integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
 
+tinyglobby@^0.2.12:
+  version "0.2.12"
+  resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5"
+  integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==
+  dependencies:
+    fdir "^6.4.3"
+    picomatch "^4.0.2"
+
 tinyglobby@^0.2.7:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.9.tgz#6baddd1b0fe416403efb0dd40442c7d7c03c1c66"
@@ -11814,6 +12622,13 @@ tinyqueue@^3.0.0:
   resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e"
   integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==
 
+tmp@^0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+  dependencies:
+    os-tmpdir "~1.0.2"
+
 tmp@^0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
@@ -11892,15 +12707,10 @@ truncate-utf8-bytes@^1.0.0:
   dependencies:
     utf8-byte-length "^1.0.1"
 
-ts-api-utils@^1.3.0:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064"
-  integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==
-
-ts-api-utils@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.0.tgz#b9d7d5f7ec9f736f4d0f09758b8607979044a900"
-  integrity sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==
+ts-api-utils@^2.0.1, ts-api-utils@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
+  integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
 
 ts-node@^10.9.1:
   version "10.9.2"
@@ -11921,11 +12731,6 @@ ts-node@^10.9.1:
     v8-compile-cache-lib "^3.0.1"
     yn "3.1.1"
 
-ts-xor@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/ts-xor/-/ts-xor-1.3.0.tgz#3e59f24f0321f9f10f350e0cee3b534b89a2c70b"
-  integrity sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA==
-
 tsconfig-paths@^3.15.0:
   version "3.15.0"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
@@ -11936,12 +12741,12 @@ tsconfig-paths@^3.15.0:
     minimist "^1.2.6"
     strip-bom "^3.0.0"
 
-tslib@2, tslib@^2.0.3, tslib@^2.1.0:
+tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.0:
   version "2.8.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
   integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
 
-tslib@^2.0.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0:
+tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b"
   integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
@@ -11983,6 +12788,15 @@ type-fest@^0.8.1:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
   integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
 
+type-is@^2.0.0, type-is@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97"
+  integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==
+  dependencies:
+    content-type "^1.0.5"
+    media-typer "^1.1.0"
+    mime-types "^3.0.0"
+
 type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -11991,40 +12805,40 @@ type-is@~1.6.18:
     media-typer "0.3.0"
     mime-types "~2.1.24"
 
-typed-array-buffer@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3"
-  integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==
+typed-array-buffer@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
+  integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.3"
     es-errors "^1.3.0"
-    is-typed-array "^1.1.13"
+    is-typed-array "^1.1.14"
 
-typed-array-byte-length@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67"
-  integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==
+typed-array-byte-length@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce"
+  integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==
   dependencies:
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
     for-each "^0.3.3"
-    gopd "^1.0.1"
-    has-proto "^1.0.3"
-    is-typed-array "^1.1.13"
+    gopd "^1.2.0"
+    has-proto "^1.2.0"
+    is-typed-array "^1.1.14"
 
-typed-array-byte-offset@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz#3fa9f22567700cc86aaf86a1e7176f74b59600f2"
-  integrity sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==
+typed-array-byte-offset@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355"
+  integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==
   dependencies:
     available-typed-arrays "^1.0.7"
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
     for-each "^0.3.3"
-    gopd "^1.0.1"
-    has-proto "^1.0.3"
-    is-typed-array "^1.1.13"
-    reflect.getprototypeof "^1.0.6"
+    gopd "^1.2.0"
+    has-proto "^1.2.0"
+    is-typed-array "^1.1.15"
+    reflect.getprototypeof "^1.0.9"
 
-typed-array-length@^1.0.6:
+typed-array-length@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d"
   integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==
@@ -12036,30 +12850,30 @@ typed-array-length@^1.0.6:
     possible-typed-array-names "^1.0.0"
     reflect.getprototypeof "^1.0.6"
 
-typescript@5.7.2:
-  version "5.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
-  integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
+typescript@5.8.3:
+  version "5.8.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
+  integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
 
 ua-parser-js@^1.0.2:
-  version "1.0.39"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.39.tgz#bfc07f361549bf249bd8f4589a4cccec18fd2018"
-  integrity sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==
+  version "1.0.40"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.40.tgz#ac6aff4fd8ea3e794a6aa743ec9c2fc29e75b675"
+  integrity sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==
 
 uc.micro@^1.0.1, uc.micro@^1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
   integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
 
-unbox-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
-  integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+unbox-primitive@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
+  integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==
   dependencies:
-    call-bind "^1.0.2"
+    call-bound "^1.0.3"
     has-bigints "^1.0.2"
-    has-symbols "^1.0.3"
-    which-boxed-primitive "^1.0.2"
+    has-symbols "^1.1.0"
+    which-boxed-primitive "^1.1.1"
 
 underscore@^1.13.6:
   version "1.13.7"
@@ -12071,17 +12885,15 @@ undici-types@~5.26.4:
   resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
   integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
 
-undici-types@~6.20.0:
-  version "6.20.0"
-  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
-  integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
+undici-types@~6.21.0:
+  version "6.21.0"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
+  integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
 
-undici@^5.28.4:
-  version "5.28.4"
-  resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
-  integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==
-  dependencies:
-    "@fastify/busboy" "^2.0.0"
+undici@^7.10.0:
+  version "7.10.0"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-7.10.0.tgz#8ae17a976acc6593b13c9ff3342840bea9b24670"
+  integrity sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==
 
 unhomoglyph@^1.0.6:
   version "1.0.6"
@@ -12111,16 +12923,16 @@ unicode-property-aliases-ecmascript@^2.0.0:
   resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
   integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
 
-unicorn-magic@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4"
-  integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==
-
 universalify@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
   integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
 
+universalify@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+  integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -12136,10 +12948,10 @@ unplugin@1.0.1:
     webpack-sources "^3.2.3"
     webpack-virtual-modules "^0.5.0"
 
-update-browserslist-db@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580"
-  integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
+update-browserslist-db@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
+  integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
   dependencies:
     escalade "^3.2.0"
     picocolors "^1.1.1"
@@ -12159,22 +12971,29 @@ url-parse@^1.5.3:
     querystringify "^2.1.1"
     requires-port "^1.0.0"
 
-use-callback-ref@^1.3.0, use-callback-ref@^1.3.2:
+use-callback-ref@^1.3.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693"
   integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==
   dependencies:
     tslib "^2.0.0"
 
+use-callback-ref@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf"
+  integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==
+  dependencies:
+    tslib "^2.0.0"
+
 use-memo-one@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
   integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
 
-use-sidecar@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
-  integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==
+use-sidecar@^1.1.2, use-sidecar@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb"
+  integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==
   dependencies:
     detect-node-es "^1.1.0"
     tslib "^2.0.0"
@@ -12211,15 +13030,20 @@ utils-merge@1.0.1:
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
 uuid@11, uuid@^11.0.0:
-  version "11.0.3"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d"
-  integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
+  integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
 
 uuid@8.3.2, uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
+uuid@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
+  integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
+
 uuid@^9.0.0:
   version "9.0.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
@@ -12247,7 +13071,7 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-vary@~1.1.2:
+vary@^1.1.2, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
@@ -12275,6 +13099,11 @@ w3c-xmlserializer@^4.0.0:
   dependencies:
     xml-name-validator "^4.0.0"
 
+walk-up-path@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886"
+  integrity sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==
+
 walk@^2.3.15:
   version "2.3.15"
   resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.15.tgz#1b4611e959d656426bc521e2da5db3acecae2424"
@@ -12290,9 +13119,9 @@ walker@^1.0.8:
     makeerror "1.0.12"
 
 watchpack@^2.4.1:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da"
-  integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947"
+  integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==
   dependencies:
     glob-to-regexp "^0.4.1"
     graceful-fs "^4.1.2"
@@ -12304,22 +13133,15 @@ wbuf@^1.1.0, wbuf@^1.7.3:
   dependencies:
     minimalistic-assert "^1.0.0"
 
-wcwidth@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
-  integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==
-  dependencies:
-    defaults "^1.0.3"
-
 web-streams-polyfill@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz#74cedf168339ee6e709532f76c49313a8c7acdac"
-  integrity sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.1.0.tgz#3ba095d0eb3ef6377cd126e8354b2cdba286e0d3"
+  integrity sha512-A7Jxrg7+eV+eZR/CIdESDnRGFb6/bcKukGvJBB5snI6cw3is1c2qamkYstC1bY1p08TyMRlN9eTMkxmnKJBPBw==
 
-web-vitals@^4.0.1:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7"
-  integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q==
+web-vitals@^4.2.4:
+  version "4.2.4"
+  resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7"
+  integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==
 
 webcrypto-core@^1.8.0:
   version "1.8.1"
@@ -12397,13 +13219,14 @@ webpack-dev-middleware@^7.4.2:
     schema-utils "^4.0.0"
 
 webpack-dev-server@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz#8f44147402b4d8ab99bfeb9b6880daa1411064e5"
-  integrity sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz#049072d6e19cbda8cf600b9e364e6662d61218ba"
+  integrity sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==
   dependencies:
     "@types/bonjour" "^3.5.13"
     "@types/connect-history-api-fallback" "^1.5.4"
     "@types/express" "^4.17.21"
+    "@types/express-serve-static-core" "^4.17.21"
     "@types/serve-index" "^1.9.4"
     "@types/serve-static" "^1.15.5"
     "@types/sockjs" "^0.3.36"
@@ -12414,10 +13237,9 @@ webpack-dev-server@^5.0.0:
     colorette "^2.0.10"
     compression "^1.7.4"
     connect-history-api-fallback "^2.0.0"
-    express "^4.19.2"
+    express "^4.21.2"
     graceful-fs "^4.2.6"
-    html-entities "^2.4.0"
-    http-proxy-middleware "^2.0.3"
+    http-proxy-middleware "^2.0.7"
     ipaddr.js "^2.1.0"
     launch-editor "^2.6.1"
     open "^10.0.3"
@@ -12447,9 +13269,9 @@ webpack-retry-chunk-load-plugin@^3.1.1:
     prettier "^2.6.2"
 
 webpack-sources@^3.2.3:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
-  integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.0.tgz#8d3449f1ed3f254e722a529a0a344a37d2d17048"
+  integrity sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==
 
 webpack-version-file-plugin@^0.5.0:
   version "0.5.0"
@@ -12466,12 +13288,13 @@ webpack-virtual-modules@^0.5.0:
   integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==
 
 webpack@^5.89.0:
-  version "5.97.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.97.1.tgz#972a8320a438b56ff0f1d94ade9e82eac155fa58"
-  integrity sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==
+  version "5.99.9"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.99.9.tgz#d7de799ec17d0cce3c83b70744b4aedb537d8247"
+  integrity sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==
   dependencies:
     "@types/eslint-scope" "^3.7.7"
     "@types/estree" "^1.0.6"
+    "@types/json-schema" "^7.0.15"
     "@webassemblyjs/ast" "^1.14.1"
     "@webassemblyjs/wasm-edit" "^1.14.1"
     "@webassemblyjs/wasm-parser" "^1.14.1"
@@ -12488,9 +13311,9 @@ webpack@^5.89.0:
     loader-runner "^4.2.0"
     mime-types "^2.1.27"
     neo-async "^2.6.2"
-    schema-utils "^3.2.0"
+    schema-utils "^4.3.2"
     tapable "^2.1.1"
-    terser-webpack-plugin "^5.3.10"
+    terser-webpack-plugin "^5.3.11"
     watchpack "^2.4.1"
     webpack-sources "^3.2.3"
 
@@ -12550,35 +13373,35 @@ whatwg-url@^6.5.0:
     tr46 "^1.0.1"
     webidl-conversions "^4.0.2"
 
-which-boxed-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
-  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e"
+  integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
   dependencies:
-    is-bigint "^1.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
-    is-symbol "^1.0.3"
+    is-bigint "^1.1.0"
+    is-boolean-object "^1.2.1"
+    is-number-object "^1.1.1"
+    is-string "^1.1.1"
+    is-symbol "^1.1.1"
 
-which-builtin-type@^1.1.4:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.0.tgz#58042ac9602d78a6d117c7e811349df1268ba63c"
-  integrity sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==
+which-builtin-type@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e"
+  integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==
   dependencies:
-    call-bind "^1.0.7"
+    call-bound "^1.0.2"
     function.prototype.name "^1.1.6"
     has-tostringtag "^1.0.2"
     is-async-function "^2.0.0"
-    is-date-object "^1.0.5"
+    is-date-object "^1.1.0"
     is-finalizationregistry "^1.1.0"
     is-generator-function "^1.0.10"
-    is-regex "^1.1.4"
+    is-regex "^1.2.1"
     is-weakref "^1.0.2"
     isarray "^2.0.5"
-    which-boxed-primitive "^1.0.2"
+    which-boxed-primitive "^1.1.0"
     which-collection "^1.0.2"
-    which-typed-array "^1.1.15"
+    which-typed-array "^1.1.16"
 
 which-collection@^1.0.2:
   version "1.0.2"
@@ -12595,15 +13418,16 @@ which-module@^2.0.0:
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
   integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
 
-which-typed-array@^1.1.14, which-typed-array@^1.1.15:
-  version "1.1.16"
-  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b"
-  integrity sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==
+which-typed-array@^1.1.14, which-typed-array@^1.1.16, which-typed-array@^1.1.18:
+  version "1.1.18"
+  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad"
+  integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==
   dependencies:
     available-typed-arrays "^1.0.7"
-    call-bind "^1.0.7"
+    call-bind "^1.0.8"
+    call-bound "^1.0.3"
     for-each "^0.3.3"
-    gopd "^1.0.1"
+    gopd "^1.2.0"
     has-tostringtag "^1.0.2"
 
 which-typed-array@^1.1.2:
@@ -12692,11 +13516,16 @@ ws@^7.3.1:
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
   integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
 
-ws@^8.11.0, ws@^8.18.0:
+ws@^8.11.0:
   version "8.18.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
   integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
 
+ws@^8.18.0:
+  version "8.18.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
+  integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
+
 xml-name-validator@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
@@ -12734,20 +13563,10 @@ yallist@^3.0.2:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
   integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
 
-yaml@^2.2.2:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
-  integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
-
-yaml@^2.3.3:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773"
-  integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==
-
-yaml@~2.5.0:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130"
-  integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==
+yaml@^2.2.2, yaml@^2.3.3, yaml@^2.7.0, yaml@^2.7.1:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"
+  integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
 
 yargs-parser@^18.1.2:
   version "18.1.3"
@@ -12802,11 +13621,6 @@ yocto-queue@^0.1.0:
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 
-yocto-queue@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
-  integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
-
 zip-stream@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb"
@@ -12817,11 +13631,11 @@ zip-stream@^6.0.1:
     readable-stream "^4.0.0"
 
 zod-validation-error@^3.0.3:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6"
-  integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.1.tgz#fb0a64f15d90f4aafe9ccc804331853609aad408"
+  integrity sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==
 
 zod@^3.22.4:
-  version "3.24.0"
-  resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.0.tgz#babb32313f7c5f4a99812feee806d186b4f76bde"
-  integrity sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==
+  version "3.25.32"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.32.tgz#769cc684072df780fc8f38130b0cd9283a8d3818"
+  integrity sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==